Embox en el procesador Elbrus. O nunca olvides lo que tienes en inteligencia

Este artículo es la conclusión lógica de una serie de artículos de "escalada Elbrus" sobre la introducción de Embox a la arquitectura del procesador Elbrus (E2K) . Por qué una conclusión lógica, porque como resultado, fue posible ejecutar a través de telnet una aplicación que muestra la imagen en la pantalla, es decir, para lograr el funcionamiento completo de Embox en esta arquitectura. La investigación adicional difícilmente puede considerarse una introducción, aunque, por supuesto, queda mucho por aclarar. Y la arquitectura en sí tiene muchas características interesantes, que tampoco se entienden actualmente. En este artículo, nos centraremos en la organización de la memoria virtual, tocaremos PCI, hablaremos un poco sobre una tarjeta de red y tocaremos una tarjeta de video en un hardware específico que tenemos.

Para aquellos que son demasiado flojos para leer el artículo, les daré de inmediato un breve video con los resultados.


Y ahora, para aquellos que estén interesados, revelaremos los detalles técnicos que pudimos entender en el proceso.

Memoria virtual


Nuestro error de pila


Comencemos con la memoria virtual. En realidad, esto es lo que resolvimos en el artículo anterior de la serie. Vale la pena recordar de inmediato por qué necesitábamos memoria virtual, porque Embox puede funcionar sin ella. Es simple: la cosa es el almacenamiento en caché. Vidyaha funcionó, pero tuvo que escribir lo mismo dos veces en la memoria de video para una recuperación de imagen confiable. Por supuesto, era posible lidiar con la memoria caché, pero nuestros ejemplos implican el uso de memoria de video directamente desde una aplicación de usuario, sin ningún elemento nuclear como la administración de la memoria caché, por lo que fue correcto aprender a asignar la memoria como no almacenable en caché. Lo mismo se puede hacer en Linux mapeando fb ( ejemplo ).

Vale la pena señalar que aunque no escribimos sobre Elbrus durante mucho tiempo y podría parecer que MMU en esta arquitectura es una especie de súper complicado, pero la cosa es diferente. De hecho, agregamos apoyo en el verano, simplemente no llegamos a nuestras manos para escribir sobre ello. Pasamos mucho tiempo (varios meses) debido a nuestro estúpido error. Este error incluso se cometió en el título del artículo ("O nunca olvides lo que obtuviste durante la inteligencia"). Estamos hablando de pilas con las que tratamos bastante bien y lo describimos en el artículo Climbing Elbrus - Reconnaissance. Parte técnica 1. Registros, pilas y otros detalles técnicos " . Sufrimos durante mucho tiempo, perdiendo estúpidamente el hecho de que tomamos la pila inicial (en la que se inicializa el sistema) desde algún lugar fuera, y mapeamos todo. lo que necesitamos para que Embox funcione, no mapeamos estos datos.

Debajo del gato, le daré una nueva función e2k_entry, descrita en el segundo artículo de la serie .

Si lo desea, puede comparar.

__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]); } 

Solo explicaré que ahora usamos las funciones context_init () y context_switch () solo para cambiar la pila a la memoria en el espacio Embox. Y hacemos esto para todos los núcleos, incluidos los que no se utilizan.

Organización MMU


Ahora hablaré un poco sobre la organización de MMU en la arquitectura E2k.

En general, la arquitectura MMU es bastante común y tiene tablas de cuatro niveles (o tres cuando se usa una página de 4 MB).

Hay varios registros de servicio en la arquitectura E2k, se accede a ellos mediante comandos de acceso a espacios alternativos, así como al espacio de E / S que se describe brevemente en el artículo "Embox comienza a escalar Elbrus" .

Necesitaremos tales registros:

 #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 *// 

En realidad, este es un registro de control, un registro de número de contexto, un registro raíz de tablas y un poco oscuro MMU_REG_ELB_PTB. Comencemos con esto, este registro debe establecerse en algún valor, el procesador utilizará los siguientes 512 GB para las necesidades del equipo, y estas direcciones no estarán disponibles para el programador. Proporcionaré explicaciones de la carta del especialista de ICST, difícilmente puedo explicar mejor:

En Linux, configuramos MMU_ELB_PTB en 0xff1 << 39, y luego
área superior de la memoria virtual (0xff8000000000 - 0xffffffffffff)
reservado para las necesidades de equipos, a saber, TLB. Cada página
la tabla de páginas (TS) obtiene su dirección única en esta área,
Además, estas direcciones se obtienen fácilmente de la dirección en la que el programa
apeló a la memoria. Y desde TLB almacena asignaciones de direcciones virtuales
físico, esto le permite almacenar en caché en el mismo búfer TLB
difunde no solo para las direcciones de los usuarios, sino también para el propio vehículo.

En aquellos procesadores / arquitecturas donde se realizan TLB separados para diferentes
niveles de la tabla de páginas, tal truco se vuelve innecesario.

Por lo tanto, cuando pierde el TLB, es posible no comenzar la búsqueda
desde el nivel cero (pgd *), e inmediatamente verifique el último nivel del vehículo (pte *).
No hay requisitos de hardware para mapear esta área en sí, Wirth. direcciones de
solo es necesario como índices para la búsqueda de TLB. Sin embargo, en el núcleo de
el último pgd en el nivel cero de la tabla de páginas se escribe nat. la dirección de este
nivel más cero Como resultado, solo
los últimos 4 KB del área ff80'0000'0000 - ffff'ffff'ffff - es decir justo
Nivel cero del vehículo. Esto permite que pgd * sea accesible por ordinario
leer / escribir instrucciones trabajando en direcciones virtuales.

Como resultado, se decidió simplemente poner en este registro un gran valor, que no nos molestará. Después de todo, la solución propuesta nos permite optimizar la búsqueda de páginas, pero aún no nos hemos dedicado a la optimización. Se entrega igual que en Linux.

Ahora el registro de control. Necesita habilitar MMU a través de él. Los bits conocidos se ven así:

 #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 */ 

Estamos interesados ​​en el primer bit, que incluye la traducción de direcciones.

También configuramos _MMU_CR_SET3, pero no hemos descubierto en qué casos particulares se debe hacer esto.

Registro de concurso Bueno, si es simple, entonces este es el PID del proceso o espacio de direcciones. Más técnicamente, esta es una extensión de dirección de 11 bits. En nuestro caso, convertimos todas las páginas en nucleares, al establecer un poco de globalidad en todas nuestras páginas, usamos el mismo espacio de direcciones y, por lo tanto, podemos usar cero en este registro.

En el registro de la tabla raíz hay un puntero a la dirección física del comienzo de la tabla de traducción. Puede hacer un fraude asignando la tabla también a la dirección especificada en el registro MMU_REG_ELB_PTB, pero como dije, no estábamos enfocados en la optimización.

¿Qué más puedo decir? La estructura de las tablas es bastante común, las banderas son las siguientes:

 #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 */ 

Para la tabla de 4 niveles, los cambios de dirección son los siguientes:

 #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 */ 

Un poco sobre PCI


Comunicación a través de espacios de direcciones alternativos


Antes de pasar a vidyaha y tarjeta de red, regresemos brevemente a PCI. Ya hablamos un poco sobre esto en la primera parte de "Embox comienza a escalar el Monte Elbrus" . Mostró macros para comunicarse con espacios de direcciones alternativos:

 #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"); \ }) 

Y había una referencia al principio de los espacios de direcciones. Se definen diferentes espacios de direcciones utilizando el MAS (especificador de dirección de memoria). Y, por ejemplo, para acceder al IO, a través del cual se accede al PCI, debe usar 6 y MMU 7.

Pero con un estudio más exhaustivo de la macro, puede notar algún tipo de chan_letter. Y si nos fijamos en la descripción de los comandos e2k, encontramos
LDD ddd lectura de doble palabra
ldd [dirección] mas, dst

Es decir, a primera vista, no hay canales. Pero si sigue los enlaces, resulta que el código para la operación dada ldd es 67. Pero 67 es el código para ldd solo para los canales AL0 / AL3 y AL2 / AL5, y para los canales AL1 / AL4 este código corresponde a la operación POPCNTd.

Por lo tanto, no fue posible comprender completamente qué canales hay en la terminología de Elbrus. Me atrevería a sugerir que esto ya está conectado con el principio vliw, cuando puede especificar qué alu se usa, porque en este tipo de arquitectura una de las características es la presencia de varios dispositivos informáticos independientes. Puedo, por supuesto, estar equivocado, pero el hecho es que necesita usar el segundo o quinto canal para acceder a PCI o MMU. Por lo tanto, el comando se verá así:
ldd, 2 0x0, [addr_in_mas] mas_id,% reg

lspci


Ahora daré el resultado de la salida del comando lspci en el dispositivo que tenemos:
root @ embox: (nulo) #lspci
00: 0.0 (PCI dev E3E3: ABCD) [6 4]
Puente PCI a PCI: (nulo) Puente Elbrus PCIe (rev 01)
00: 1.0 (PCI dev 8086: E3E3) [6 4]
Puente PCI a PCI: puente Intel Corporation Elbrus Virt PCI (rev 01)
01: 0.0 (PCI dev 1FFF: 8000) [6 4]
Puente PCI a PCI: (nulo) Puente Elbrus PCI (rev 05)
01: 1.0 (PCI dev 8086: 4D45) [2 0]
Controlador Ethernet: Intel Corporation MCST ETH1000 Gigabit Ethernet (rev 01)
01: 2.0 (PCI dev 8086: 4D49) [1 1]
Controlador IDE: Intel Corporation MCST IDE (rev 128)
01: 2.1 (PCI dev 8086: 0002) [7 2]
Comunicación simple controlador: Intel Corporation (nulo) (rev 05)
01: 2.2 (PCI dev 8086: 8000) [7 128]
Comunicación simple Controlador: Intel Corporation Elbrus PCI bridge (rev 00)
01: 2.3 (PCI dev 1013: 6005) [4 1]
Dispositivo multimedia: Cirrus Logic Crystal CS4281 PCI Audio (rev 01)
01: 3.0 (PCI dev 8086: 4748) [1 6]
Controlador de almacenamiento masivo: Intel Corporation MCST SATA (rev 00)
01: 4.0 (PCI dev 8086: 554F) [12 3]
Dispositivo USB: Intel Corporation OHCI para Elbrus (rev 00)
01: 4.1 (PCI dev 8086: 5545) [12 3]
Dispositivo USB: Intel Corporation EHCI para Elbrus (rev 00)
02: 1.0 (PCI dev 126F: 0718) [3 0]
Controlador compatible con VGA: Silicon Motion, Inc. SM718 LynxSE + (rev 160)
root @ embox: (nulo) #

Nota
01: 2.2 (PCI dev 8086: 8000) [7 128]
Comunicación simple Controlador: Intel Corporation Elbrus PCI bridge (rev 00)

De hecho, es un puerto serie del MCST similar al am85c30, al menos a través de este dispositivo nos comunicamos a través de minicom.

Tarjeta de red


Estructura general


Ahora vamos a la tarjeta de red.

Si entiendo correctamente, esta es la tarjeta de red original, un poco similar en funcionamiento a e1000, pero solo en funcionamiento (como la presencia de descriptores en las colas de recepción y transmisión).

Ahora más sobre los puntos importantes que encontramos.

Tarjeta de red PCI VID: PID 0x8086: 0x4D45. No se sorprenda de que el VID sea el mismo que Intel, el MCST a menudo usa este VID en particular, observe al menos el dispositivo de puerto serie mencionado anteriormente.

BAR0 contiene una base de registro. Los registros son los siguientes:

 #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 */ 

Los últimos tres (L_E1000_PSF_CSR, L_E1000_PSF_DATA, L_E1000_INT_DELAY) no los utilizamos, por lo que no hablaremos de ellos. Comencemos con MGIO, todo es simple: lectura-escritura usando el protocolo MII, es decir, comunicación con el chip PHY. Específicamente, tenemos un chip DP83865.

Los procedimientos no son particularmente notables, simplemente los enumeraré.

Lectura:

 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; } 

Registro:

 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; } 

Ahora L_E1000_DMA_BASE_ADDR y L_E1000_E_BASE_ADDR, de hecho, describen un parámetro, la dirección del bloque de descripción de la tarjeta de red. Es decir, la dirección en Elbrus es de 64 bits y los registros son de 32 bits.

En realidad codificar:

  /* 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); /************************************************************************/ 

De lo que se puede ver que L_E1000_DMA_BASE_ADDR es la parte superior, y L_E1000_DMA_BASE_ADDR es la parte inferior de la dirección de un determinado bloque de inicialización (en realidad un bloque de descripción de tarjeta).

La estructura de descripción es la siguiente:

 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 - no entendí, por alguna razón se pone a cero, hicimos lo mismo.

paddr: como puede suponer, mac es la dirección de la tarjeta de red.

rdra y tdra contienen las direcciones de los anillos de los descriptores de memoria, los 4 bits inferiores se asignan al tamaño del anillo, y este es el logaritmo del tamaño. Es decir, si hay 8, entonces el número de descriptores en el anillo será 2 ^ 8 (1 << 8 == 256).

modo es el modo de la tarjeta, los bits son los siguientes:

 #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 */ 

Es decir, cuando todo está configurado, debe establecer el bit 10. Si desea un modo promiscuo, entonces también 15.

Descriptores de paquetes


Ahora sobre el formato de los descriptores de paquetes.

En la recepción:

 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)); 

base: probablemente comprenda que esta es la dirección del búfer para el paquete
buf_length - tamaño del búfer
msg_length: después de recibir, contiene la longitud del paquete recibido
estado: estado del descriptor. Cuando el paquete se prepara y se entrega a la DMA (tarjeta), debe establecer el bit 15 (RD_OWN). Si todo está bien, luego de recibir el paquete en este descriptor, este bit se restablecerá y se establecerán 9 (RD_STP) y 8 (RD_ENP).

Todos los bits de estado son los siguientes:

 /* 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) 

En transferencia:

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

Casi lo mismo que recibir, los bits de estado son los siguientes:

 /* 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) 

Cuando se envía un paquete, es necesario establecer 15 (TD_OWN), 9 (TD_STP) y 8 (TD_ENP) en consecuencia. El bit 8 significa que este es el último paquete que se procesará, por lo tanto, si se envía un paquete, solo necesita instalarlo en el último.

También olvidé una característica importante, la longitud del búfer en los descriptores se escribe con un signo menos, probablemente en un código adicional. Incluso en little-endian, pero dado que Elbrus tiene el mismo orden de bytes, esto probablemente no sea importante.

Registro de gestión


Ahora describimos el último registro sin ensamblar 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 */ 

Inicialización


Hay una secuencia de inicialización algo inusual:
STOP-> INIT-> IDON-> STRT

En este caso, los bits RXON y TXON aumentan independientemente.
Más detalles se pueden encontrar en nuestro controlador.

Tarjeta de video


Como ya se señaló, nuestro dispositivo utiliza una vidyah Silicon Motion llamada SM718 LynxSE +. Por lo tanto, todo es simple, hay fuentes de controladores en Linux y no hay nada que describir en realidad.

Bueno, excepto que el video muestra que resultó ser muy bajo en fps, se siente como un acceso lento a la memoria. Pero esto es sin la optimización del compilador, y en general, tal vez este es nuestro problema asociado con el uso incorrecto de la arquitectura e2k.

Bueno, ¿qué más decir sobre Sakhalin Elbrus?


En principio, el clima es normal :)

Aparentemente, Elbrus existe, trabaja. Personalmente, veo el principal problema del desarrollo de esta interesante arquitectura como su cercanía. Es difícil creer que una empresa relativamente pequeña pueda crear un procesador, un compilador, brindar soporte y todo lo demás. Sí, comenzaron a aparecer desarrolladores de software de terceros, el mismo Basalt-SPO es compatible con Alt-Linux, que se puede instalar en Elbrus .

Sí, hubo informes de que desarrolladores externos están fabricando hardware basado en el procesador Elbrus, por ejemplo Fastwel . Pero todos estos son solo pequeños avances hacia la apertura. Un ejemplo muy simple, para reproducir lo que hemos dicho y mostrado aquí, necesitamos un compilador, y solo el MCST lo tiene , la información proporcionada en el artículo es una adición a la información recibida de, nuevamente, el MCST , y todavía no digo que es poco probable encontrar una pieza de hierro incluso en el MCST. Es bastante antiguo e ICST ofrece modelos más nuevos.

PD: Naturalmente, puedes ver todo en el repositorio de Embox .

PPS Ven al canal de telegramas ruso a través de Embox ( https://t.me/embox_chat ).

PPS Embox ha actualizado el segundo componente de la versión, ahora el actual 0.4.0

Source: https://habr.com/ru/post/485694/


All Articles