28 января

Embox на процессоре Эльбрус. Или никогда не забывайте о том, что получили при разведке

Блог компании EmboxOpen sourceСистемное программированиеПроцессоры
Данная статья является логическим завершением серии статьей “восхождение на Эльбрус” об ознакомлении Embox с процессорной архитектурой Эльбрус (E2K) . Почему логическим завершением, потому что в результате удалось через telnet запустить приложение, которое выводит на экран изображение, то есть добиться полноценной работы Embox на данной архитектуре. Дальнейшие исследования, вряд ли можно назвать ознакомлением, хотя конечно, многое осталось не ясно. И сама архитектура имеет много интересных особенностей, которые также на данный момент не изучены. В данной статье речь пойдет об организации виртуальной памяти, затронем PCI, немного поговорим о сетевой карте и коснемся видеокарты на конкретном железе, которое есть у нас.

Для тех, кому лень читать статью, сразу приведу короткое видео с результатами.


А теперь для тех, кому интересно, раскроем технические детали, которые нам удалось понять в процессе работы.

Виртуальная память


Наша ошибка со стеками


Начнем с виртуальной памяти. Собственно, это то, на чем мы остановились в предыдущей статье серии. Сразу стоит вспомнить, для чего же нам потребовалась виртуальная память, ведь Embox может работать и без нее. Все просто: дело в кэшировании. Видяха работала, но приходилось два раза одно и тоже записывать в видео память для надежного получения картинки. Конечно, можно было разобраться с кэшем, но наши примеры, предполагают использование видеопамяти напрямую из пользовательского приложения, без всяких ядерных штук типа управления кэшем, поэтому правильным было освоить мэпирование памяти как некэшируемой. Тоже самое можно сделать и в Linux, замэпировав устройсто fb (пример).

Стоит отметить, что хотя мы долго не писали об Эльбрусе и могло показаться, что MMU в этой архитектуре какое-то суперсложное, но дело в другом. На самом деле, добавили поддержку мы еще летом, просто руки не доходили написать об этом. А продолжительное время (несколько месяцев) было потрачено из-за нашей глупой ошибки. Эта ошибка даже вынесена в названии статьи (“Или никогда не забывайте о том, что получили при разведке”). Речь идет о стеках с которыми мы довольно прилично разобрались и описали это в статье Восхождение на «Эльбрус — Разведка боем. Техническая Часть 1. Регистры, стеки и другие технические детали» . Мы очень долго мучались, по глупости упустив, что начальный стек (на котором происходит инициализация системы) мы берем откуда-то извне, и замэпировав все. что нам необходимо для работы Embox, мы не мэпировали эти данные.

Под катом приведу новую функцию e2k_entry, описанную во второй статье статье серии.

При желании можно сравнить.

__attribute__ ((__section__(".e2k_entry")))
void e2k_entry(struct pt_regs *regs) {
    /* Since we enable exceptions only when all CPUs except the main one
     * reached the idle state (cpu_idle), we can rely that order and can
     * guarantee exceptions happen strictly after all CPUS entries. */
    if (entries_count >= CPU_COUNT) {
        /* Entering here because of exception or interrupt */
        e2k_trap_handler(regs);

        RESTORE_COMMON_REGS(regs);

        E2K_DONE;
    }

    /* It wasn't exception, so we decide this usual program execution,
     * that is, Embox started on CPU0 or CPU1 */

    e2k_wait_all();

    entries_count = __e2k_atomic32_add(1, &entries_count);

    if (entries_count > 1) {
        /* XXX currently we support only single core */
        /* Run cpu_idle on 2nd CPU */

        /* it's just needed if log_debug enabled in e2k_context module
         * else output will be wrong because 2 cpu printing at the same time */
        while(!sync_count);

        context_init(&cpu_ctx[0], CONTEXT_PRIVELEGED | CONTEXT_IRQDISABLE, cpu_idle, idle_stack, sizeof(idle_stack));
        context_switch(&cpu_ctx_prev[0], &cpu_ctx[0]);
    }
    /* Run e2k_kernel_start on 1st CPU */
    context_init(&cpu_ctx[1], CONTEXT_PRIVELEGED | CONTEXT_IRQDISABLE, e2k_kernel_start, &_stack_top, KERNEL_STACK_SZ);
    sync_count = __e2k_atomic32_add(1, &sync_count);
    context_switch(&cpu_ctx_prev[1], &cpu_ctx[1]);
}

Я просто поясню, что теперь мы используем функции context_init() и context_switch() только для того, чтобы переключить стек на память в пространстве Embox. Причем делаем это для всех ядер, в том числе и для не использующихся.

Организация MMU


Теперь немного расскажу про организацию MMU в архитектуре E2k.

В общем и целом архитектура MMU вполне обычная и имеет четырехуровневые таблицы (или трех при использование 4Мб страницы).

В архитектуре E2k есть несколько служебных регистров, обращение к ним идет с помощью команд доступа к альтернативным пространствам, также как к пространству ввода-вывода, кратко описанному с статье “Embox начинает восхождение на Эльбрус”.

Такие регистры нам понадобятся:

#define MMU_REG_CR          0x00  /* Control register */
#define MMU_REG_CONT        0x10  /* Context register */
#define MMU_REG_CR3_RG      0x20  /* CR3 register for INTEL only */
#define MMU_REG_ELB_PTB     0x30  /* ELBRUS page table virtual base */
#define MMU_REG_ROOT_PTB    0x40  /* Root Page Table Base register *//

Собственно, это контрольный регистр, регистр номера контекста, корневой регистр таблиц, ну и немного непонятный MMU_REG_ELB_PTB. C него и начнем, этот регистр должен быть установлен в некоторое значение, следующие 512Гб будут использованы процессором для нужд аппаратуры, и эти адреса будут недоступны программисту. Приведу пояснения из письма специалиста МЦСТ, врядли я смогу объяснить лучше:

В Linux мы выставляем MMU_ELB_PTB в 0xff1 << 39, и тогда
верхняя область виртуальной памяти (0xff8000000000 — 0xffffffffffff)
резервируется под нужды аппаратуры, а именно, TLB. Каждая страница
таблицы страниц (ТС) получает свой уникальный адрес в этой области,
причём эти адреса легко получаются из адреса, по которому программа
обращалась в память. А т.к. TLB хранит соответствия виртуальных адресов
физическим, это позволяет кэшировать в одном и том же буфере TLB
трансляции не только для пользовательских адресов, но и для самой ТС.

В тех процессорах/архитектурах, где сделаны отдельные TLB для разных
уровней таблицы страниц, такая хитрость становится ненужной.

Таким образом, при промахе в TLB становится возможным не начинать поиск
с нулевого уровня (pgd*), а сразу проверить последний уровень ТС (pte*).
Аппаратного требования мапировать саму эту область нет, вирт. адреса из
неё нужны лишь как индексы для поиска по TLB. Тем не менее, в ядре в
последнюю pgd в нулевом уровне таблицы страниц пишется физ. адрес этого
самого нулевого уровня. В результате замапированными оказываются только
последнии 4 Кб области ff80'0000'0000 — ffff'ffff'ffff — т.е. как раз
нулевой уровень ТС. Это позволяет обращаться к pgd* обычными
инструкциями чтения/записи, работающими по виртуальным адресам.

В итоге было решено просто поставить в этом регистре большое значение, которое не будет нам мешать. Ведь предложенное решение позволяет оптимизировать поиск страниц, а мы пока оптимизацией не занимались. Поставили такое же как в Linux.

Теперь контрольный регистр. Включать MMU нужно именно через него. Известные биты выглядят так:

#define _MMU_CR_TLB_EN   0x0000000000000001 /* translation enable */
#define _MMU_CR_CD_MASK  0x0000000000000006 /* cache disable bits */
#define _MMU_CR_SET1     0x0000000000000008 /* set #1 enable for 4 MB pages */
#define _MMU_CR_SET2     0x0000000000000010 /* set #2 enable for 4 MB pages */
#define _MMU_CR_SET3     0x0000000000000020 /* set #3 enable for 4 MB pages */
/* paging enable for second space INTEL */
#define _MMU_CR_CR0_PG   0x0000000000000040
/* page size 4Mb enable for second space INTEL */
#define _MMU_CR_CR4_PSE  0x0000000000000080
/* cache disable for secondary space INTEL */
#define _MMU_CR_CR0_CD   0x0000000000000100
/* TLU enable for secondary space INTEL */
#define _MMU_CR_TLU2_EN  0x0000000000000200
/* memory protection table enable for LD from secondary space INTEL */
#define _MMU_CR_LD_MPT   0x0000000000000400
#define _MMU_CR_IPD_MASK 0x0000000000000800 /* Instruction Prefetch Depth */
#define _MMU_CR_UPT_EN   0x0000000000001000 /* enable UPT */

Нас интересует первый бит, который и включает трансляцию адресов.

Еще мы выставляем _MMU_CR_SET3, но так и не разобрались в каких именно случаях это нужно делать.

Регистр контеста. Ну, если просто, то это PID процесса или адресного пространства. Если более технически, то это 11-битное расширение адреса. В нашем случае мы сделали все страницы ядерными, поставив бит глобальности во всех наших страницах, используем одно адресное пространство и следовательно можем использовать ноль в этом регистре.

В регистре корневой таблицы лежит указатель на физический адрес начала таблицы трансляции. Можно как раз сделать махинацию, замэпировав таблицу ещё и в адрес, указанный в регистре MMU_REG_ELB_PTB, но как я уже и говорил, мы не были сосредоточены на оптимизации.

Что тут еще можно сказать, структура таблиц вполне обычная, флаги следующие:

#define E2K_MMU_PAGE_P       0x0000000000000001ULL /* Page Present bit */
#define E2K_MMU_PAGE_W       0x0000000000000002ULL /* Writable (0 - only read) */
#define E2K_MMU_PAGE_UU2     0x0000000000000004ULL /* unused bit # 2 */
#define E2K_MMU_PAGE_PWT     0x0000000000000008ULL /* Write Through */
#define E2K_MMU_PAGE_CD1     0x0000000000000010ULL /* Cache disable (right bit) */
#define E2K_MMU_PAGE_A       0x0000000000000020ULL /* Accessed Page */
#define E2K_MMU_PAGE_D       0x0000000000000040ULL /* Page Dirty */
#define E2K_MMU_PAGE_HUGE    0x0000000000000080ULL /* Page Size */
#define E2K_MMU_PAGE_G       0x0000000000000100ULL /* Global Page */
#define E2K_MMU_PAGE_CD2     0x0000000000000200ULL /* Cache disable (left bit) */
#define E2K_MMU_PAGE_NWA     0x0000000000000400ULL /* Prohibit address writing */
#define E2K_MMU_PAGE_AVAIL   0x0000000000000800ULL /* Available page */
#define E2K_MMU_PAGE_PFN     0x000000fffffff000ULL /* Physical Page Number */
#define E2K_MMU_PAGE_VALID   0x0000010000000000ULL /* Valid Page */
#define E2K_MMU_PAGE_PV      0x0000020000000000ULL /* PriVileged Page */
#define E2K_MMU_PAGE_INT_PR  0x0000040000000000ULL /* Integer address access Protection */
#define E2K_MMU_PAGE_NON_EX  0x0000080000000000ULL /* Non Executable Page */
#define E2K_MMU_PAGE_RES     0x0000f00000000000ULL /* Reserved bits */
#define E2K_MMU_PAGE_C_UNIT  0xffff000000000000ULL /* Compilation Unit */

Для 4 уровневой таблицы сдвиги адресов следующие:


#define __MMU_PGD_SHIFT      (PAGE_SHIFT + 3 * (PAGE_SHIFT-3)) /* 39 */
#define __MMU_PUD_SHIFT      (PAGE_SHIFT + 2 * (PAGE_SHIFT-3)) /* 30 */
#define __MMU_PMD_SHIFT      (PAGE_SHIFT + 1 * (PAGE_SHIFT-3)) /* 21 */

Немного о PCI


Общение через альтернативные адресные пространства


Прежде чем перейти к видяхе и сетевухе, ненадолго вернемся к PCI. Немного мы об этом уже рассказывали в самой первой части «Embox начинает восхождение на Эльбрус». В ней были показаны макросы для общения с альтернативными адресными пространствами:

#define _E2K_READ_MAS(addr, mas, type, size_letter, chan_letter) \
({ \
    register type res; \
    asm volatile ("ld" #size_letter "," #chan_letter " \t0x0, [%1] %2, %0" \
                  : "=r" (res) \
                  : "r" ((__e2k_ptr_t) (addr)), \
                    "i" (mas)); \
    res; \
})

#define _E2K_WRITE_MAS(addr, val, mas, type, size_letter, chan_letter) \
({ \
    asm volatile ("st" #size_letter "," #chan_letter " \t0x0, [%0] %2, %1" \
                  : \
                  : "r" ((__e2k_ptr_t) (addr)), \
                    "r" ((type) (val)), \
            "i" (mas) \
          : "memory"); \
})

И была ссылка на принцип адресных пространств. Различные адресные пространства определяются с помощью MAS (memory address specifier). И например для доступа к IO, через которое идет обращение к PCI нужно использовать 6, а для MMU 7.

Но при более тщательном изучении макроса можно заметить какое-то chan_letter. А если заглянуть в описание команд e2k, то найдем
LDD ddd считывание двойного слова
ldd [ address ] mas, dst

То есть на первый взгляд, никаких каналов нет. Но если пойти по ссылкам, то окажется, что код приведенной операции ldd является 67. Но 67 является кодом для ldd только для каналов AL0/AL3 и AL2/AL5, а для каналов AL1/AL4 этому коду соответствует операция POPCNTd.

Так вот, до конца понять что же такое каналы в терминологии Эльбруса, не удалось. Рискну предположить, что это связано уже с принципом vliw, когда можно указать, какое именно alu используется, ведь в этом типе архитектуры одной из особенностей является наличие нескольких независимых вычислительных устройств. Могу, конечно, ошибаться, но факт в том, что для обращения к PCI или MMU нужно использовать второй или пятый канал. Таким образом, команда будет выглядеть приблизительно так:
ldd,2 0x0, [addr_in_mas] mas_id, %reg

lspci


Теперь приведу результат вывода команды lspci на присутствующем у нас устройстве:
root@embox:(null)#lspci
00: 0.0 (PCI dev E3E3:ABCD) [6 4]
PCI-to-PCI bridge: (null) Elbrus PCIe bridge (rev 01)
00: 1.0 (PCI dev 8086:E3E3) [6 4]
PCI-to-PCI bridge: Intel Corporation Elbrus Virt PCI bridge (rev 01)
01: 0.0 (PCI dev 1FFF:8000) [6 4]
PCI-to-PCI bridge: (null) Elbrus PCI bridge (rev 05)
01: 1.0 (PCI dev 8086:4D45) [2 0]
Ethernet controller: Intel Corporation MCST ETH1000 Gigabit Ethernet (rev 01)
01: 2.0 (PCI dev 8086:4D49) [1 1]
IDE controller: Intel Corporation MCST IDE (rev 128)
01: 2.1 (PCI dev 8086:0002) [7 2]
Simple comm. controller: Intel Corporation (null) (rev 05)
01: 2.2 (PCI dev 8086:8000) [7 128]
Simple comm. controller: Intel Corporation Elbrus PCI bridge (rev 00)
01: 2.3 (PCI dev 1013:6005) [4 1]
Multimedia device: Cirrus Logic Crystal CS4281 PCI Audio (rev 01)
01: 3.0 (PCI dev 8086:4748) [1 6]
Mass storage controller: Intel Corporation MCST SATA (rev 00)
01: 4.0 (PCI dev 8086:554F) [12 3]
USB device: Intel Corporation OHCI for Elbrus (rev 00)
01: 4.1 (PCI dev 8086:5545) [12 3]
USB device: Intel Corporation EHCI for Elbrus (rev 00)
02: 1.0 (PCI dev 126F:0718) [3 0]
VGA-compatible controller: Silicon Motion, Inc. SM718 LynxSE+ (rev 160)
root@embox:(null)#

Примечание
01: 2.2 (PCI dev 8086:8000) [7 128]
Simple comm. controller: Intel Corporation Elbrus PCI bridge (rev 00)

На самом деле является последовательным портом от МЦСТ похожим на am85c30, по крайней мере через это устройство мы и общаемся через minicom.

Сетевая карта


Общая структура


Теперь приступим к сетевухе.

Если я правильно понял, то это оригинальная сетевуха, немного похожая по принципам работы на e1000, но только по принципам работы (типа наличие дескрипторов в очередях на прием и передачу).

Теперь подробнее про важные моменты, с которыми мы столкнулись.

Сетевуха PCI -ная VID:PID 0x8086:0x4D45. Не удивляйтесь что VID совпадает с Intel, МЦСТ часто использует именно этот VID, посмотреть хотя бы на упомянутое выше устройство последовательного порта.

В BAR0 лежит база регистров. Регистры следующие:

#define L_E1000_E_CSR         0x00 /* Ethernet Control/Status Register */
#define L_E1000_MGIO_CSR      0x04 /* MGIO Control/Status Register */
#define L_E1000_MGIO_DATA     0x08 /* MGIO Data Register */
#define L_E1000_E_BASE_ADDR   0x0c /* EthernetBase Address Register */
#define L_E1000_DMA_BASE_ADDR 0x10 /* DMA Base Address Register */
#define L_E1000_PSF_CSR       0x14 /* Pause Frame Control/Status Register */
#define L_E1000_PSF_DATA      0x18 /* Pause Frame Data Register */
#define L_E1000_INT_DELAY     0x1c /* Interrupt Delay Register */

Последние три (L_E1000_PSF_CSR, L_E1000_PSF_DATA, L_E1000_INT_DELAY) мы не использовали, поэтому о них не будем рассказывать. Начнем с MGIO, тут все просто: чтение-запись по MII-протоколу, то есть общение с микросхемой PHY. Конкретно у нас стоит микросхема DP83865.

Процедуры ничем особо не примечательные, просто приведу их листинг.

Чтение:

static int e1000_mii_readreg(struct net_device *dev, int phy_id, int reg_num) {
	struct l_e1000_priv *ep = netdev_priv(dev);
	uint32_t rd;
	uint16_t val_out = 0;
	int i = 0;

	rd = 0;
	rd |= 0x2 << MGIO_CS_OFF;
	rd |= 0x1 << MGIO_ST_OF_F_OFF;
	rd |= 0x2 << MGIO_OP_CODE_OFF; /* Read */
	rd |= (phy_id  & 0x1f) << MGIO_PHY_AD_OFF;
	rd |= (reg_num & 0x1f) << MGIO_REG_AD_OFF;

	e1000_write_mgio_data(ep, rd);
	rd = 0;

	for (i = 0; i != 1000; i++) {
		if (e1000_read_mgio_csr(ep) & MGIO_CSR_RRDY) {
			rd = (uint16_t)e1000_read_mgio_data(ep);

			val_out = rd & 0xffff;
			log_debug("reg 0x%x >>> 0x%x", reg_num, val_out);
			return val_out;
		}
		usleep(100);
	}

	log_error("mdio_read: Unable to read from MGIO_DATA reg\n");

	return val_out;
}

Запись:

static void e1000_mii_writereg(struct net_device *dev, int phy_id, int reg_num, int val) {
	struct l_e1000_priv *ep = netdev_priv(dev);
	uint32_t wr;
	int i = 0;

	wr = 0;
	wr |= 0x2 << MGIO_CS_OFF;
	wr |= 0x1 << MGIO_ST_OF_F_OFF;
	wr |= 0x1 << MGIO_OP_CODE_OFF; /* Write */
	wr |= (phy_id  & 0x1f) << MGIO_PHY_AD_OFF;
	wr |= (reg_num & 0x1f) << MGIO_REG_AD_OFF;
	wr |= val & 0xffff;

	log_debug("reg 0x%x <<< 0x%x", reg_num, val);
	e1000_write_mgio_data(ep, wr);

	for (i = 0; i != 1000; i++) {
		if (e1000_read_mgio_csr(ep) & MGIO_CSR_RRDY) {
			return;
		}
		usleep(100);
	}
	log_error("Unable to write MGIO_DATA reg: val = 0x%x", wr);
	return;
}

Теперь L_E1000_DMA_BASE_ADDR и L_E1000_E_BASE_ADDR, на самом деле они описывают один параметр, адрес блока описания сетевой карты. То есть адрес в Эльбрусе 64-разрядный, а регистры 32-разрядные.

Собственно код:

    /* low 32 bits */
    init_block_addr_part = (uint32_t)((uintptr_t)ep->init_block & 0xffffffff);
    e1000_write_e_base_addr(ep, init_block_addr_part);
    log_debug("Init Block Low  DMA addr: 0x%x", init_block_addr_part);
    /* high 32 bits */
    init_block_addr_part = (uint32_t)(((uintptr_t)(ep->init_block) >> 32) & 0xffffffff);
    e1000_write_dma_base_addr(ep, init_block_addr_part);
    log_debug("Init Block High DMA addr: 0x%x", init_block_addr_part);
    /************************************************************************/

Из которого видно, что L_E1000_DMA_BASE_ADDR — это старшая часть, а L_E1000_DMA_BASE_ADDR — младшая часть адреса некоего блока инициализации (на самом деле блока описания карты).

Структура описания следующая:


struct l_e1000_init_block {
    uint16_t mode;
    uint8_t  paddr[6];
    uint64_t laddrf;
    /* 31:4 = addr of rx desc ring (16 bytes align) +
     * 3:0  = number of descriptors (the power of two)
     * 0x09 is max value (desc number = 512 if [3:0] >= 0x09)
     */
    uint32_t rdra;
    /* 31:4 = addr of tx desc ring (16 bytes align) +
     * 3:0  = number of descriptors (the power of two)
     * 0x09 is max value (desc number = 512 if [3:0] >= 0x09)
     */
    uint32_t tdra;
} __attribute__((packed));

C laddrf — не разобрались, он почему то ставится в ноль, мы сделали так же.

paddr — как нетрудно догадаться, mac адрес сетевой карты.

rdra и tdra содержат адреса колец дескрипторов памяти, младшие 4 бита отведены под размер кольца, причем это логарифм от размера. То есть если будет 8, то количество дескрипторов в кольце будет 2^8 (1 << 8 == 256).

mode — это режим работы карты, биты следующие:

#define DRX     (1 << 0)  /* Receiver disable */
#define DTX     (1 << 1)  /* Transmitter disable */
#define LOOP    (1 << 2)  /* loopback */
#define DTCR    (1 << 3)  /* disable transmit crc */
#define COLL    (1 << 4)  /* force collision */
#define DRTY    (1 << 5)  /* disable retry */
#define INTL    (1 << 6)  /* Internal loopback */
#define EMBA    (1 << 7)  /* enable modified back-off algorithm */
#define EJMF    (1 << 8)  /* enable jambo frame */
#define EPSF    (1 << 9)  /* enable pause frame */
#define FULL    (1 << 10)  /* full packet mode */
#define PROM    (1 << 15) /* promiscuous mode */

То есть когда все настроено, нужно установить бит 10. Если хочется promiscuous режим, то еще и 15.

Дескрипторы пакетов


Теперь про формат дескрипторов пакетов.

На прием:


struct l_e1000_rx_desc {
    uint32_t    base;
    int16_t     buf_length;
    int16_t     status;
    int16_t     msg_length;
    uint16_t    reserved1;
    uint32_t    etmr;
} __attribute__((packed));

вase — наверное понятно, что это адрес буфера под пакет
buf_length — размер буфера
msg_length — собственно после приема содержит длину принятого пакета
status — статус дескриптора. Когда пакет подготовлен и отдается DMA (карточке), то нужно установить бит 15 (RD_OWN). Если все хорошо, то после приема пакета в этот дескриптор этот бит сброситься и установится 9 (RD_STP) и 8 (RD_ENP).

Все биты статуса следующие:

/* RX Descriptor status bits */
#define RD_OWN     (1 << 15)
#define RD_ERR     (1 << 14)
#define RD_FRAM    (1 << 13)
#define RD_OFLO    (1 << 12)
#define RD_CRC     (1 << 11)
#define RD_BUFF    (1 << 10)
#define RD_STP     (1 << 9)
#define RD_ENP     (1 << 8)
#define RD_PAM     (1 << 6)
#define RD_LAFM    (1 << 4)
#define RD_BAM     (1 << 3)

На передачу:

struct l_e1000_tx_desc {
    uint32_t    base;
    int16_t     buf_length;
    int16_t     status;
    uint32_t    misc;
    uint32_t    etmr;
} __attribute__((packed));

Почти тоже самое что и на прием, статусные биты следующие:

/* TX Descriptor status bits */
#define TD_OWN      (1 << 15)
#define TD_ERR      (1 << 14)
#define TD_AFCS     (1 << 13)
#define TD_NOINTR   (1 << 13)
#define TD_MORE     (1 << 12)
#define TD_ONE      (1 << 11)
#define TD_DEF      (1 << 10)
#define TD_STP      (1 << 9)
#define TD_ENP      (1 << 8)

Когда отправляется пакет, то соответственно нужно установить 15 (TD_OWN), 9 (TD_STP) и 8 (TD_ENP). Бит 8 означает что это последний пакет на обработку, следовательно если отправляется пачка пакетов, то нужно устанавливать только в последнем.

А еще забыл важную особенность, длина буфера в дескрипторах записывается со знаком минус, в дополнительном коде, наверное. Еще в little-endian, но поскольку Эльбрусы имеют такой же порядок байт, то это, наверное, не важно.

Регистр управления


Теперь опишем последний неразобранный регистр L_E1000_E_CSR:


/* E_CSR register bits */
/* 31:21 unused, readed as 0 */
#define E_CSR_ATME        (1 << 24) /* RW, Add Timer Enable */
#define E_CSR_TMCE        (1 << 23) /* RW, Timer Clear Enable */
#define E_CSR_DRIN        (1 << 22) /* RW, Disable RX Interrupt */
#define E_CSR_DTIN        (1 << 21) /* RW, Disable TX Interrupt */
#define E_CSR_ESLE        (1 << 20) /* RW, Enable Slave Error */
#define E_CSR_SLVE        (1 << 19) /* RW1c, Slave Error */
#define E_CSR_PSFI        (1 << 18) /* RW1c, Pause Frame Interrupt */
/* 17 unused, read as 0  */
#define E_CSR_SINT       (1 << 16) /* R, Status Interrupt */
#define E_CSR_ERR        (1 << 15) /* R, Error */
#define E_CSR_BABL       (1 << 14) /* RW1c, Babble */
#define E_CSR_CERR       (1 << 13) /* RW1c, Collision Error */
#define E_CSR_MISS       (1 << 12) /* RW1c, Missed Packet */
#define E_CSR_MERR       (1 << 11) /* RW1c, Memory Error */
#define E_CSR_RINT       (1 << 10) /* RW1c, Receiver Interrupt */
#define E_CSR_TINT       (1 << 9)  /* RW1c, Transmiter Interrupt */
#define E_CSR_IDON       (1 << 8)  /* RW1c, Initialization Done */
#define E_CSR_INTR       (1 << 7)  /* R, Interrupt Flag */
#define E_CSR_INEA       (1 << 6)  /* RW, Interrupt Enable */
#define E_CSR_RXON       (1 << 5)  /* R, Receiver On */
#define E_CSR_TXON       (1 << 4)  /* R, Transmiter On */
#define E_CSR_TDMD       (1 << 3)  /* RW1, Transmit Demand */
#define E_CSR_STOP       (1 << 2)  /* RW1, Stop */
#define E_CSR_STRT       (1 << 1)  /* RW1, Start */
#define E_CSR_INIT       (1 << 0)  /* RW1, Initialize */

Инициализация


Присутствует несколько необычная последовательность инициализации:
STOP->INIT->IDON->STRT

При этом биты RXON & TXON поднимаются самостоятельно.
Более подробно можно посмотреть у нас в драйвере.

Видеокарта


Как уже было отмечено, в нашем устройстве используется видяха компании Silicon Motion, под названием SM718 LynxSE+. Поэтому все просто, существуют исходники драйвера в Linux и описывать собственно нечего.

Ну разве что на видео видно, что получился очень низкий fps, такое ощущение, что медленный доступ к памяти. Но это без компиляторной оптимизации, и вообще может это наша проблема связаная с некорректным использованием архитектуры e2k.

Ну что еще сказать про Сахалин Эльбрус?


В принципе, нормальная погода :)

Как видно, Эльбрусы существуют, работают. Лично мне видится основной проблемой развития этой интересной архитектуры ее закрытость. Слабо верится, что относительно небольшая компания может создать процессор, компилятор, осуществлять поддержку и все остальное. Да, стали появляться, сторонние разработчики ПО, тот же Базальт-СПО поддерживает Альт-Linux, который можно поставить на Эльбрус.

Да, появились сообщения о том, что сторонние разработчики делают аппаратуру на базе процессора Эльбрус, например Fastwel. Но все это лишь небольшие подвижки в сторону открытости. Совсем простой пример, для того чтобы воспроизвести то, что мы тут рассказали и показали, нужен компилятор, а он есть только у МЦСТ, информация приведенная в статье является дополнением к информации полученной у, опять же, МЦСТ, и это я еще не говорю, о том, что железку вряд ли удастся найти даже у МЦСТ. Она довольно старая, и МЦСТ предлагает более новые модели.

P.S. Естественно все можно посмотреть в репозитории Embox.

P.P.S Приходите в русский телеграмм канал по Embox (https://t.me/embox_chat).

P.P.S. У Embox обновилась вторая составляющая в версии, теперь актуальная 0.4.0
Теги:emboxэльбрусe2kпроцессор эльбрус
Хабы: Блог компании Embox Open source Системное программирование Процессоры
+26
7,4k 22
Комментарии 21
Лучшие публикации за сутки