Ethernet II или сырые Ethernet пакеты на Nios II Altera часть 3

Ethernet трансивер на ПЛИСПосле создания системы на Nios II можно приступать к написанию программы которая будет посылать в регистр данных МАС eth_ocm 'сырые' даные. Для этого определим пакет из 1168 бай случайных данных. Почему так? Все привязано к 48 элементам гидроакустической антенной решетки минус первые 16 служебных байт формата Ethernet II.

Ниже по тексту представлен тестовый вариант Ethernet передатчика и приемника пакетов формата Ethernet II. Автор Ethernet драйвера - функций инициализации MAC, функций приема и передачи для модуля eth_ocm Вадим Котельников.


#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "alt_types.h"
#include "stdio.h"
#include "eth_driver.h"
#include "sys/alt_irq.h"
#include "sys/alt_cache.h"
#include "eth_ocm_regs.h"
#include "eth_ocm_desc.h"

//---------------------------------------------------------------------------------------------
// Объявления функций

static void eth_ocm_isr();
static int  eth_ocm_read_init();
static int eth_ocm_rx_isr();

//---------------------------------------------------------------------------------------------
// Глобальные переменные

volatile char pkt[1562];
volatile char ovr_pkt[1568];
char ovr_tx_pkt[1568];
int sem;
char Start = 0;
char PhyReg = 0x1f;


//---------------------------------------------------------------------------------------------
// Задержка
void Delay ( tick)
{
    while(tick--);
}    
//---------------------------------------------------------------------------------------------
// Инициализация MAC
int MAC_init()
{
    alt_u32 t2=0;
    int status=0;
    
//---------------------------------------------------------------------------------------------
    // Ожидать пока не появится линк
    while((eth_ocm_read_phy_reg(ETH_OCM_0_BASE, PHY, 0x01)&0x04)==0)
    {
        // скорость 100Mbps       
        eth_ocm_write_phy_reg(ETH_OCM_0_BASE, PHY, 0x00, 0xB300);
        // скорость 10Mbps
//        eth_ocm_write_phy_reg(ETH_OCM_0_BASE, PHY, 0x00, 0x9300);
        Delay(0xFFFFF);
    }
   
    // Обнуление дескрипторов Tx и Rx
    for(t2=ETH_OCM_DESC_START;t2<ETH_OCM_DESC_END;++t2)
    {
        IOWR(ETH_OCM_0_BASE,t2,0);
    }    
    IOWR_ETH_OCM_MODER(ETH_OCM_0_BASE, 0);
    IOWR_ETH_OCM_TX_BD_NUM(ETH_OCM_0_BASE,ETH_OCM_TX_DESC_COUNT);
    IOWR_ETH_OCM_INT_MASK(ETH_OCM_0_BASE, ETH_OCM_DEFAULT_INTERRUPT_MASK);
    IOWR_ETH_OCM_INT_SOURCE(ETH_OCM_0_BASE,0xFFFFFFFF);
    IOWR_ETH_OCM_IPGT(ETH_OCM_0_BASE, ETH_OCM_FULL_DUPLEX_IPGT);//!FOR DUPLEX!
    IOWR_ETH_OCM_IPGR1(ETH_OCM_0_BASE, 0x0000000C);
    IOWR_ETH_OCM_IPGR2(ETH_OCM_0_BASE, 0x00000012);
    IOWR_ETH_OCM_PACKETLEN(ETH_OCM_0_BASE, 0x00400600);
    IOWR_ETH_OCM_COLLCONF(ETH_OCM_0_BASE, 0x000F003F);
    IOWR_ETH_OCM_CTRLMODER(ETH_OCM_0_BASE, 0);
    // MAC адрес 010101010101
    IOWR_ETH_OCM_MAC_ADDR0(ETH_OCM_0_BASE, ((int)(0x01) | (int)(0x01 <<  8) | (int)(0x01 << 16) | (int)(0x01 << 24)));
    IOWR_ETH_OCM_MAC_ADDR1(ETH_OCM_0_BASE, (((int)(0x01) | (int)(0x01 <<  8)) & 0xFFFF));

    // Без маски ETH_OCM_MODER_PRO_MSK
    IOWR_ETH_OCM_MODER(ETH_OCM_0_BASE, 
                                        ETH_OCM_MODER_PAD_MSK    |
                                        ETH_OCM_MODER_PRO_MSK    |
                                        ETH_OCM_MODER_CRCEN_MSK  |
                                        ETH_OCM_MODER_RXEN_MSK   |
                                        ETH_OCM_MODER_TXEN_MSK   |
                                        ETH_OCM_MODER_FULLD_MSK);

    // Регистрация обработчика прерываний eth_ocm_isr по ETH_OCM_0_IRQ
    t2=alt_irq_register(ETH_OCM_0_IRQ, (void*)ETH_OCM_0_BASE, eth_ocm_isr);
    // Инициализация Ethernet чтения 
    eth_ocm_read_init();
    sem=0;
    return status;
}
//---------------------------------------------------------------------------------------------
// Обработчик прерываний ETH_OCM_0_IRQ
static void eth_ocm_isr()
{
    int result;

    result=IORD_ETH_OCM_INT_SOURCE(ETH_OCM_0_BASE);

    while(result)
    {
        IOWR_ETH_OCM_INT_SOURCE(ETH_OCM_0_BASE, result);
        // Если прерывание по чтению то eth_ocm_rx_isr()
        if(result & (ETH_OCM_INT_MASK_RXB_MSK | ETH_OCM_INT_MASK_RXE_MSK))
        {
            eth_ocm_rx_isr();
        }
        // Если прерывание на передачу то ничего не делать
        if(result & (ETH_OCM_INT_MASK_TXE_MSK | ETH_OCM_INT_MASK_TXB_MSK))
        {
        }
        result=IORD_ETH_OCM_INT_SOURCE(ETH_OCM_0_BASE);
    }
}

//---------------------------------------------------------------------------------------------
static int  eth_ocm_read_init()
{
    alt_u8 *buf_ptr;

    buf_ptr = (alt_u8*)alt_remap_cached ((volatile void*) pkt, 4);
    buf_ptr = (alt_u8*)(((unsigned int)buf_ptr) + ETHHDR_BIAS);
    if(!(IORD_ETH_OCM_DESC_CTRL(ETH_OCM_0_BASE, 1)Ð_OCM_RXDESC_EMPTY_MSK))
    {
        IOWR_ETH_OCM_DESC_PTR(ETH_OCM_0_BASE, 1, (alt_u32)buf_ptr);
        IOWR_ETH_OCM_DESC_CTRL(ETH_OCM_0_BASE, 1, ETH_OCM_RXDESC_EMPTY_MSK | ETH_OCM_RXDESC_IRQ_MSK | ETH_OCM_RXDESC_WRAP_MSK);
    }
    return 0;
}
//---------------------------------------------------------------------------------------------

static int eth_ocm_rx_isr()
{
    alt_u32 stat;
    alt_u8 *buf_ptr;
    int pklen;

    // чтение ETH_OCM_DESC_CTRL
    stat=IORD_ETH_OCM_DESC_CTRL(ETH_OCM_0_BASE, 1);
    while(!(stat & ETH_OCM_RXDESC_EMPTY_MSK))
    {

        pklen = stat & ETH_OCM_RXDESC_LEN_MSK;
        pklen = pklen >> ETH_OCM_RXDESC_LEN_OFST;

        if (pklen<1550)
        {
            if(!(stat & ETH_OCM_RXDESC_ERROR_MSK))
            {
                //Ok. Process packet here!
                // Зажечь все светодиоды
                IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, 0xff);
                // Установить признак разрешения передачи
                Start = 1;
            }
        }
        buf_ptr = (alt_u8*)alt_remap_cached ((volatile void*) pkt, 4);
        buf_ptr = (alt_u8*)(((unsigned int)buf_ptr) + ETHHDR_BIAS);
        IOWR_ETH_OCM_DESC_PTR(ETH_OCM_0_BASE, 1, (alt_u32)buf_ptr);
        IOWR_ETH_OCM_DESC_CTRL(ETH_OCM_0_BASE, 1, ETH_OCM_RXDESC_EMPTY_MSK | ETH_OCM_RXDESC_IRQ_MSK | ETH_OCM_RXDESC_WRAP_MSK);
        stat=IORD_ETH_OCM_DESC_CTRL(ETH_OCM_0_BASE, 1);
    }
    return 0;
}

//---------------------------------------------------------------------------------------------
// Установка адреса регистра PHY
void eth_ocm_set_phy_addr(int base, int phyad, int reg)
{
    phyad &= ETH_OCM_MIIADDRESS_FIAD_MSK;
    reg = reg << ETH_OCM_MIIADDRESS_RGAD_OFST; 
    reg &= ETH_OCM_MIIADDRESS_RGAD_MSK;
    phyad |= reg;
    IOWR_ETH_OCM_MIIADDRESS(base, phyad);
}
//---------------------------------------------------------------------------------------------
// Запись данных в регистр PHY
void eth_ocm_write_phy_reg(int base, int phyad, int reg, int data)
{
    eth_ocm_set_phy_addr(base, phyad, reg);
    IOWR_ETH_OCM_MIITX_DATA(base, data);
    IOWR_ETH_OCM_MIICOMMAND(base, ETH_OCM_MIICOMMAND_WCTRLDATA_MSK);
    Delay(2000);
}
//---------------------------------------------------------------------------------------------
// чтение данных из регистра PHY
int eth_ocm_read_phy_reg(int base, int phyad, int reg)
{
    int result;
    eth_ocm_set_phy_addr(base, phyad, reg);
    IOWR_ETH_OCM_MIICOMMAND(base, ETH_OCM_MIICOMMAND_RSTAT_MSK);
    Delay(2000);
    result = IORD_ETH_OCM_MIIRX_DATA(base);
    return result;
}
//---------------------------------------------------------------------------------------------
// Посылка eth пакета (*data - указатель на массив данных, data_bytes - длина пакета)
int eth_ocm_raw_send(char * data, unsigned data_bytes)
{
    alt_u8 *buf;
    int result;
    int i=0; 
    result=0;
    if (sem)
    {
        return 1;
    }
    else
    {
        sem=1;
        buf = (alt_u8 *)alt_remap_cached( (volatile void *)data, 4);
        if(data_bytes < ETH_OCM_MIN_MTU)
            result = 2;        //packet too small
        if(data_bytes > ETH_OCM_MAX_MTU)
            result = 3;    //packet too big
        if(IORD_ETH_OCM_DESC_CTRL(ETH_OCM_0_BASE, 0) & ETH_OCM_TXDESC_READY_MSK)
            result = 4;    //DMA not available
        if(!result)
        {
            IOWR_ETH_OCM_DESC_PTR(ETH_OCM_0_BASE, 0, (unsigned int)buf);
            IOWR_ETH_OCM_DESC_CTRL(ETH_OCM_0_BASE, 0, (data_bytes << ETH_OCM_TXDESC_LEN_OFST) | ETH_OCM_TXDESC_READY_MSK | ETH_OCM_TXDESC_WRAP_MSK );
        }
        do
        {
            result = IORD_ETH_OCM_DESC_CTRL(ETH_OCM_0_BASE, 0);
            ++i;
        }
        while((result & ETH_OCM_TXDESC_READY_MSK) && i<ETH_OCM_TRANSMIT_TIMEOUT);
        if(i<ETH_OCM_TRANSMIT_TIMEOUT)
        {
            if(result &
                    (ETH_OCM_TXDESC_UR_MSK      |
                    ETH_OCM_TXDESC_RL_MSK       |
                    ETH_OCM_TXDESC_LC_MSK       |
                    ETH_OCM_TXDESC_CS_MSK))
            {
                result = 5;
            }
            else
            {
                if(result & ETH_OCM_TXDESC_RTRY_MSK)
                {
                    result = 0;
                }
            }
        }
        else
        {
            result = 6;
        }
        sem=0;
        return result;
    }
} 
int main (void)
{
    alt_u8 led = 0x2;        
    alt_u8 dir = 0;        

    volatile int i = 0, j = 16;
    
    // Neptun Broadcast    
    static char ethmass[1168] = {0x00,0x90,0xf5,0x6c,0x3d,0x9f,0xff,0xff,0xff,0xff,0xff,0xff,0x23,0x21,0x32,0x21};

    // Инициализация MAC 
    MAC_init();
    
    while (1)        
    {
        // формирование случайных данных                
        ethmass[j] = led;
        j++; 
        if(j == 1168) j = 16;

        // бегущий светодиод    
        if(led & 0x81)                
        {                        
            dir = (dir ^ 0x1);                
        }
        if(dir) 
            led = led >> 1;                
        else                        
            led = led << 1;   

        for(i = 0; i < 10; i++)
        {
            if (i == 0 && Start == 1)
            {
                // запись в PIO_0_BASE (0x00000000) led
                IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, led);
                // послать Ethernet пакет  
                eth_ocm_raw_send(ethmass, sizeof(ethmass));
            }                
        };        
    }        
    return 0;        
}

Попробуем разобрать представленный код. Перед тем как послать байт в регистр данных МАС, необходимо его инициализировать. Функция инициализацми int MAC_init() включает в себя цикл ожидания линка PHY, цикл обнуления дескрипторов записи и чтеня (Tx и Rx) MAC. Затем производится настройка регистров MAC (смотри документацию на eth_ocm ocmacspec.pdf - находится в директории eth_ocm проекта) и устанавливается MAC адрес в регистрах MAC ADDR0, ADDR1. Также при инициализации MAC производится регистрация обработчика прерывания по приему alt_irq_register(... и вызов функции инициализации чтения данных eth_ocm_read_init();.

Функции общения с PHY eth_ocm_set_phy_addr(int base, int phyad, int reg), eth_ocm_write_phy_reg(int base, int phyad, int reg, int data), eth_ocm_read_phy_reg(int base, int phyad, int reg) позволяют записывать и читатать данные в (из) регистров PHY.

Функция main выполняет несколько простых операций. Первая из них это установка первых 16 байт данных пакета формата Ethernet II static char ethmass[1168] в которых первые 6 байт это MAC адрес приемника инфрмации, точнее MAC адрес сетевой карты компа куда будет подключен Ethernet модуль с NIOS II, вторые 6 байт это MAC адрес источника информации, а точнее MAC адрес Ethernet модуля с NIOS II (обычно устанавливается широковещательный ff:ff:ff:ff:ff:ff), далее определен тип протокола 0x2321 (формат Ethernet II) и последние 2 байта из 16 никак не используются.

Вторая это собственно инициализация MAC и третья это бесконечный цикл в котором заполняется массив ethmass[1168] начиная с адреса 0x0010 и затем с небольшой задержкой присходит поочередное зажигание светодиодов (бегущий светодиод) на отладочной плате DE0 с одновременной отсылкой Ethernet пакета с помощью функции eth_ocm_raw_send(ethmass, sizeof(ethmass)). Третья операция взводится по прерыванию на прием любого пакета (бит ETH_OCM_MODER_PRO_MSK установлен) пришедшего на eth_ocm (устанавливается переменная Start (if (i == 0 && Start == 1)) ).

Акхив с готовым проектом Ethernet приемопередатчика Nios II + eth_ocm + DE0+ SK-Ethernet Plug под Quartus 90 ниже по тексту. Наверняка будет работать и под более поздними версиями Quartus.

Кому все это пригодилось просьба отозваться в комментах к данному посту не забыв про респект автору драйвера к корке Ethernet MAC eth_ocm профессионалу форума Electronix.ru Вадиму Котельникову.

В следующем посте из серии Ethernet на Nios II о том как всю эту махарайку запустить и пронаблюдать ее в действии.

Top.Mail.Ru