/* * RTEMS driver for Minimac2 ethernet IP-core of Milkymist SoC * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.org/license/LICENSE. * * COPYRIGHT (c) Yann Sionneau (GSoC 2010) * Telecom SudParis, France * Copyright (C) 2011 Sebastien Bourdeauducq */ #define RTEMS_STATUS_CHECKS_USE_PRINTK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bspopts.h" #include "../include/system_conf.h" #include "network.h" #define CTS_EVENT RTEMS_EVENT_1 #define RX_EVENT RTEMS_EVENT_1 #define START_TRANSMIT_EVENT RTEMS_EVENT_2 static struct arpcom arpcom; static rtems_id rx_daemon_id; static rtems_id tx_daemon_id; static void minimac_init(void *arg); static int minimac_ioctl(struct ifnet *ifp, ioctl_command_t command, caddr_t data); static void minimac_start(struct ifnet *ifp); static void rx_daemon(void *arg); static void tx_daemon(void *arg); static rtems_isr rx_interrupt_handler(rtems_vector_number vector); static rtems_isr tx_interrupt_handler(rtems_vector_number vector); static bool validate_mac(const char *m) { int i; for(i=0;i<6;i++) if((m[i] != 0x00) && (m[i] != 0xff)) return true; return false; } static const char *get_mac_address(void) { const char *flash_mac = (const char *)FLASH_OFFSET_MAC_ADDRESS; static const char fallback_mac[6] = { 0x10, 0xe2, 0xd5, 0x00, 0x00, 0x00 }; if(validate_mac(flash_mac)) return flash_mac; else { printk("Warning: using fallback MAC address\n"); return fallback_mac; } } int rtems_minimac_driver_attach(struct rtems_bsdnet_ifconfig *config, int attaching) { struct ifnet *ifp; rtems_isr_entry dummy; int i; static int registered; uint8_t *tx_buffer = (uint8_t *)MINIMAC_TX_BASE; if(!attaching) { printk("Minimac driver cannot be detached.\n"); return 0; } ifp = &(arpcom.ac_if); if(registered) { printk("Minimac driver already in use.\n"); return 0; } registered = 1; memcpy(arpcom.ac_enaddr, get_mac_address(), 6); ifp->if_mtu = ETHERMTU; ifp->if_unit = 0; ifp->if_name = "minimac"; ifp->if_init = minimac_init; ifp->if_ioctl = minimac_ioctl; ifp->if_start = minimac_start; ifp->if_output = ether_output; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX; ifp->if_snd.ifq_maxlen = ifqmaxlen; if_attach(ifp); ether_ifattach(ifp); rx_daemon_id = rtems_bsdnet_newproc("mrxd", 4096, rx_daemon, NULL); tx_daemon_id = rtems_bsdnet_newproc("mtxd", 4096, tx_daemon, NULL); rtems_interrupt_catch(rx_interrupt_handler, MM_IRQ_ETHRX, &dummy); rtems_interrupt_catch(tx_interrupt_handler, MM_IRQ_ETHTX, &dummy); MM_WRITE(MM_MINIMAC_STATE0, MINIMAC_STATE_LOADED); MM_WRITE(MM_MINIMAC_STATE1, MINIMAC_STATE_LOADED); for(i=0;i<7; i++) tx_buffer[i] = 0x55; tx_buffer[7] = 0xd5; MM_WRITE(MM_MINIMAC_SETUP, 0); rtems_bsdnet_event_send(tx_daemon_id, CTS_EVENT); bsp_interrupt_vector_enable(MM_IRQ_ETHRX); bsp_interrupt_vector_enable(MM_IRQ_ETHTX); return 1; } static void minimac_start(struct ifnet *ifp) { rtems_bsdnet_event_send(tx_daemon_id, START_TRANSMIT_EVENT); ifp->if_flags |= IFF_OACTIVE; } static void minimac_init(void *arg) { struct ifnet *ifp = &arpcom.ac_if; ifp->if_flags |= IFF_RUNNING; } static void minimac_stop(void) { struct ifnet *ifp = &arpcom.ac_if; ifp->if_flags &= ~IFF_RUNNING; } static int minimac_ioctl(struct ifnet *ifp, ioctl_command_t command, caddr_t data) { int error; error = 0; switch (command) { case SIOCGIFADDR: case SIOCSIFADDR: ether_ioctl(ifp, command, data); break; case SIOCSIFFLAGS: switch(ifp->if_flags & (IFF_UP | IFF_RUNNING)) { case IFF_RUNNING: minimac_stop(); break; case IFF_UP: minimac_init(NULL); break; case IFF_UP | IFF_RUNNING: minimac_stop(); minimac_init(NULL); break; default: break; } break; default: error = EINVAL; break; } return error; } static rtems_isr rx_interrupt_handler(rtems_vector_number vector) { /* Deassert IRQ line. * The RX daemon will then read all the slots we marked as empty. */ if(MM_READ(MM_MINIMAC_STATE0) == MINIMAC_STATE_PENDING) MM_WRITE(MM_MINIMAC_STATE0, MINIMAC_STATE_EMPTY); if(MM_READ(MM_MINIMAC_STATE1) == MINIMAC_STATE_PENDING) MM_WRITE(MM_MINIMAC_STATE1, MINIMAC_STATE_EMPTY); rtems_bsdnet_event_send(rx_daemon_id, RX_EVENT); lm32_interrupt_ack(1 << MM_IRQ_ETHRX); } static void receive_packet(uint8_t *buffer, int length) { struct ifnet *ifp = &arpcom.ac_if; struct mbuf *m; struct ether_header *eh; uint32_t computed_crc, net_crc; if(length < 64) { printk("Warning: Ethernet packet too short\n"); return; } length -= 4; /* strip CRC */ net_crc = ((uint32_t)buffer[length]) | ((uint32_t)buffer[length+1] << 8) | ((uint32_t)buffer[length+2] << 16) | ((uint32_t)buffer[length+3] << 24); length -= 8; /* strip preamble */ computed_crc = ether_crc32_le(&buffer[8], length) ^ 0xffffffff; if(computed_crc == net_crc) { MGETHDR(m, M_WAIT, MT_DATA); MCLGET(m, M_WAIT); length -= sizeof(struct ether_header); /* strip Ethernet header */ memcpy(m->m_data, &buffer[8+sizeof(struct ether_header)], length); m->m_len = m->m_pkthdr.len = length; m->m_pkthdr.rcvif = ifp; eh = (struct ether_header *)&buffer[8]; ether_input(ifp, eh, m); } else printk("Ethernet CRC error: got %08x expected %08x (len=%d)\n", net_crc, computed_crc, length); } static void rx_daemon(void *arg) { rtems_event_set events; while(1) { rtems_bsdnet_event_receive(RX_EVENT, RTEMS_EVENT_ANY | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events); if(MM_READ(MM_MINIMAC_STATE0) == MINIMAC_STATE_EMPTY) { receive_packet((uint8_t *)MINIMAC_RX0_BASE, MM_READ(MM_MINIMAC_COUNT0)); MM_WRITE(MM_MINIMAC_STATE0, MINIMAC_STATE_LOADED); } if(MM_READ(MM_MINIMAC_STATE1) == MINIMAC_STATE_EMPTY) { receive_packet((uint8_t *)MINIMAC_RX1_BASE, MM_READ(MM_MINIMAC_COUNT1)); MM_WRITE(MM_MINIMAC_STATE1, MINIMAC_STATE_LOADED); } } } /* RTEMS apparently doesn't support m_length() ... */ static int copy_mbuf_chain(struct mbuf *m, uint8_t *target) { int len; len = 0; while(m != NULL) { if(m->m_len > 0) { m_copydata(m, 0, m->m_len, (caddr_t)(target + len)); len += m->m_len; } m = m->m_next; } return len; } static void send_packet(struct ifnet *ifp, struct mbuf *m) { unsigned int len; unsigned int crc; uint8_t *tx_buffer = (uint8_t *)(MINIMAC_TX_BASE+8); len = copy_mbuf_chain(m, tx_buffer); for(;len<60;len++) tx_buffer[len] = 0x00; // Padding crc = ether_crc32_le(tx_buffer, len) ^ 0xffffffff; tx_buffer[len] = crc & 0xff; tx_buffer[len+1] = (crc & 0xff00) >> 8; tx_buffer[len+2] = (crc & 0xff0000) >> 16; tx_buffer[len+3] = crc >> 24; len += 4; // We add 4 bytes of CRC32 MM_WRITE(MM_MINIMAC_TXCOUNT, len + 8); } static rtems_isr tx_interrupt_handler(rtems_vector_number vector) { lm32_interrupt_ack(1 << MM_IRQ_ETHTX); rtems_bsdnet_event_send(tx_daemon_id, CTS_EVENT); } static void tx_daemon(void *arg) { struct ifnet *ifp = &arpcom.ac_if; rtems_event_set events; struct mbuf *m; while(1) { rtems_bsdnet_event_receive(START_TRANSMIT_EVENT, RTEMS_EVENT_ANY | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events); while(1) { IF_DEQUEUE(&ifp->if_snd, m); if(m == NULL) break; rtems_bsdnet_event_receive(CTS_EVENT, RTEMS_EVENT_ANY | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events); send_packet(ifp, m); m_freem(m); } ifp->if_flags &= ~IFF_OACTIVE; } }