/** @file * * Ethernet network driver for IP */ /* * Copyright (c) 2001-2003 Leon Woestenberg * Copyright (c) 2001-2003 Axon Digital Design B.V., The Netherlands. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * This file is part of the lwIP TCP/IP stack. * * Author: Leon Woestenberg * Modified by: Eric Shufro * * This is a device driver for the Crystal Semiconductor CS8900 * chip in combination with the lwIP stack. * * This is work under development. Please coordinate changes * and requests with Leon Woestenberg * * The Swedish Institute of Computer Science and Adam Dunkels * are specifically granted permission to redistribute this * source code under any conditions they seem fit. * * A quick function roadmap: * * cs8900_*() are low level, cs8900 hardware specific functions. * These are declared static in the device driver source and * SHOULD NOT need to be called from outside this source. * * cs8900if_*() are the lwIP network interface functions. * * cs8900_init() sets up the cs8900, using its register set. When * using the driver on your particular hardware platform, make sure * the register setups match. * Function is called from cs8900if_init(). * * cs8900_input() transfers a received packet from the chip. * Function is called from cs8900if_input(). * * cs8900_output() transfers a packet to the chip for transmission. * Function is called from cs8900if_output(). * * cs8900if_init() initializes the lwIP network interface, and * calls cs8900_init() to initialize the hardware. * Function is called from lwIP. * * cs8900if_input() calls cs8900_input() to get a received packet * and then forwards the packet to protocol(s) handler(s). * Function is called from cs8900_service(). * * cs8900if_output() resolves the hardware address, then * calls cs8900_output() to transfer the packet. * Function is called from lwIP. */ #include "lwip/opt.h" #include "lwip/def.h" #include "lwip/err.h" #include "lwip/mem.h" #include "lwip/pbuf.h" #include "lwip/stats.h" #include "lwip/sys.h" #include "netif/etharp.h" #include "netif/cs8900if.h" #include "bsp.h" /* ********************************************************************************************************* * global variables ********************************************************************************************************* */ OS_EVENT *CS8900_Service_Sem; extern OS_CPU_SR cpu_sr; /* ********************************************************************************************************* * Initialize the CS8900 Ethernet MAC/PHY and its device driver. * * Description : Initialize the CS8900 interface and then call the low level hardware initializing * function to setup the ethernet controller. * * Arguments : netif: The lwIP network interface data structure belonging to this device. * MAY be NULL as we do not support multiple devices yet. ********************************************************************************************************* */ err_t cs8900if_init(struct netif *netif) { struct cs8900if *cs8900if; cs8900if = mem_malloc(sizeof(struct cs8900if)); if (cs8900if == NULL) { LWIP_DEBUGF(NETIF_DEBUG, ("cs8900_input: out of memory for cs8900if\n")); return ERR_MEM; } // initialize lwip network interface netif->name[0] = IFNAME0; netif->name[1] = IFNAME1; // set the interface's mac address netif->hwaddr[0] = 0x00; netif->hwaddr[1] = 0x50; netif->hwaddr[2] = 0xc2; netif->hwaddr[3] = 0x25; netif->hwaddr[4] = 0x60; netif->hwaddr[5] = 0x0b; /* downward functions */ netif->output = cs8900if_output; netif->linkoutput = cs8900_output; // initialize cs8900 specific interface state data pointer netif->state = cs8900if; /* maximum transfer unit */ netif->mtu = 1500; /* broadcast capability */ netif->flags = NETIF_FLAG_BROADCAST; /* hardware address length */ netif->hwaddr_len = 6; // initially assume no ISQ event cs8900if->needs_service = 0; // set to 1 if polling method is used cs8900if->use_polling = 0; #if (CS8900_STATS > 0) // number of interrupt service routine calls cs8900if->interrupts = 0; cs8900if->missed = 0; cs8900if->dropped = 0; cs8900if->sentpackets = 0; cs8900if->sentbytes = 0; #endif // intialize the cs8900a chip return cs8900_init(); } /* ********************************************************************************************************* * cs8900_init() * * Description : Calls for a CS8900 hardware reset and then initializes the cs8900 registers * * Arguments : netif: The lwIP network interface data structure belonging to this device. * * Returns : return error codes * - ERR_OK: packet transferred to hardware * ********************************************************************************************************* */ static err_t cs8900_init(void) { CS8900_Service_Sem = OSSemCreate(0); /* semaphore to awaken the cs8900 service routine after an isr occurs */ while((cs8900_reset() & 0x8000) != 0x8000) { // if device is not ready...(initd should be set when ready) //todo add timeout OSTimeDly(10); } ppWrite(CS_PP_INTNUM, 0x0000U); //set the cs8900 interrupt to use to pin cs8900 pin 0, (connected to 9s12 micro using IRQ pin) ppWrite(CS_PP_RXCFG, 0x0301U); //receiver configuration register ppWrite(CS_PP_RXCTL, 0x050DU); //receiver control register ppWrite(CS_PP_BUFCFG, 0x0B05U); //bus configuration register ppWrite(CS_PP_BUSCTL, 0x1780U); //bus control register ppWrite(CS_PP_LINECTL, 0xD300U); //line control register ppWrite(CS_PP_IA1, 0x0050U); //mac address high bytes ppWrite(CS_PP_IA2, 0xC225U); //mac address middle bytes ppWrite(CS_PP_IA3, 0x600BU); //mac address low bytes INTCR = 0x40; //re-enable the irq pin now the the cs8900 is ready to go return ERR_OK; } /* ********************************************************************************************************* * Reset the CS8900A using a hardware reset * * Description : Pulls the CS8900's reset pin high to reset the ethernet controller. * * Arguments : None * Notes : Not User Accessible. ********************************************************************************************************* */ static INT16U cs8900_reset(void) { //default state for the signal bus (no inputs asserted) Signal_B = 0x06; /* pull reset pin and wait a bit */ Signal_B |= CS8900_RESET; OSTimeDly(10); /* release reset pin and wait a bit*/ Signal_B &= ~CS8900_RESET; OSTimeDly(10); //toggle SBHE to enter 16 bit mode (as specified in the datasheet) Signal_B |= CS8900_SBHE; //SBHE active OSTimeDly(10); Signal_B &= ~CS8900_SBHE; //SBHE inactive OSTimeDly(10); Signal_B |= CS8900_SBHE; //SBHE active OSTimeDly(10); Signal_B &= ~CS8900_SBHE; //SBHE inactive OSTimeDly(10); //return the value of the selftest register which tells us if the device has finished resetting return ppRead(CS_PP_SELFTEST); } /* ********************************************************************************************************* * CS8900 Interrupt Service Routine * * Description : function which gets called after the cs8900 asserts a hardware interrupt * this posts a semaphore to the OS in order to awaken ServiceCS8900 which * actually does the dirty work. The hardware interrupt pin is disabled here as well. * * Arguments : None ********************************************************************************************************* */ void CS8900_Interrupt(void) { INT8U err; INT16U isq_data; //hold the isq event register number INTCR = 0x00; //disable the irq pin! /* do { isq_data = IORead(CS8900_ISQ) >> 8; switch(isq_data & 0x3f) { case 0x00: //isq is empty. exit. break; case 0x04: //receieve event cs8900if_input(&netif); break; case 0x08: //transmit event break; case 0x0C: //buffer event break; case 0x10: //receiver miss event break; case 0x12: //transmit collision event break; default: //should never be here event. break; } }while(isq_data != 0); */ err = OSSemPost(CS8900_Service_Sem); //signal ServiceCS8900 to run. } /* ********************************************************************************************************* * CS8900 Service Routine * * Description : function which gets called after the cs8900 interrupt routine runs and posts a * semaphore to awaken this task and service the cs8900 ISQ and get data etc... * When done servicing the cs8900, the hardware interrupt pin is re-enabled. * * Arguments : None ********************************************************************************************************* */ void ServiceCS8900(void) { INT8U err; INT16U isq_data; //hold the isq event register number while(1) { OSSemPend(CS8900_Service_Sem, 0, &err); do { isq_data = IORead(CS8900_ISQ) >> 8; switch(isq_data & 0x3f) { case 0x00: //isq is empty. exit. break; case 0x04: //receieve event cs8900if_input(&netif); break; case 0x08: //transmit event break; case 0x0C: //buffer event break; case 0x10: //receiver miss event break; case 0x12: //transmit collision event break; default: //should never be here event. break; } }while(isq_data != 0); INTCR = 0x40; //re-enable interrupts } } /* ********************************************************************************************************* * Read a received packet from the CS8900. * * Description : This function should be called when a packet is received by the CS8900 * and is fully available to read. It moves the received packet to a pbuf * which is forwarded to the IP network layer or ARP module. It transmits * a resulting ARP reply or queued packet. * * Arguments : netif: The lwIP network interface to read from. * * Notes : Uses cs8900_input() to move the packet from the CS8900 to a * newly allocated pbuf. ********************************************************************************************************* */ void cs8900if_input(struct netif *netif) { struct cs8900if *cs8900if = netif->state; struct eth_hdr *ethhdr = NULL; struct pbuf *p = NULL, *q = NULL; /* move received packet into a new pbuf */ p = cs8900_input(netif); /* no packet could be read */ if (p == NULL) { /* silently ignore this */ return; } /* points to packet payload, which starts with an Ethernet header */ ethhdr = p->payload; q = NULL; switch (htons(ethhdr->type)) { /* IP packet? */ case ETHTYPE_IP: /* update ARP table, obtain first queued packet */ q = etharp_ip_input(netif, p); /* skip Ethernet header */ pbuf_header(p, -14); LWIP_DEBUGF(NETIF_DEBUG, ("cs8900_input: passing packet up to IP\n")); /* pass to network layer */ netif->input(p, netif); break; /* ARP packet? */ case ETHTYPE_ARP: /* pass p to ARP module, get ARP reply or ARP queued packet */ q = etharp_arp_input(netif, (struct eth_addr *)&netif->hwaddr, p); break; /* unsupported Ethernet packet type */ default: /* free pbuf */ pbuf_free(p); p = NULL; break; } /* send out the ARP reply or ARP queued packet */ if (q != NULL) { /* q pbuf has been succesfully sent? */ if (cs8900_output(netif, q) == ERR_OK) { pbuf_free(q); q = NULL; } else { /* TODO: re-queue packet in the ARP cache here (?) */ pbuf_free(q); q = NULL; } } } /* ********************************************************************************************************* * Move a received packet from the cs8900 into a new pbuf. * * Description : Must be called after reading an ISQ event containing the * "Receiver Event" register, before reading new ISQ events. * * This function copies a frame from the CS8900A. * * It is designed failsafe: * - It does not assume a frame is actually present. * - It checks for non-zero length * - It does not overflow the frame buffer * * * Arguments : netif: The lwIP network interface data structure belonging to this device. ********************************************************************************************************* */ static struct pbuf *cs8900_input(struct netif *netif) { struct pbuf *p = NULL, *q = NULL; u16_t len = 0; u16_t event_type; u16_t i; u16_t *ptr = NULL; // read RxStatus event_type = IORead(CS8900_RxTxDataPort); // correctly received frame, either broadcast or individual address? // TODO: maybe defer these conditions to cs8900_input() if ((event_type & 0x0001U/*RxOK*/) && (event_type & 0x000cU/*Broadcast | Individual*/)) { #if LWIP_SNMP > 0 // update number of received MAC-unicast and non-MAC-unicast packets if (event_type & 0x0004U/*Individual*/) { snmp_inc_ifinucastpkts(); } else { snmp_inc_ifinnucastpkts(); } #endif event_type = 0; // read RxLength len = IORead(CS8900_RxTxDataPort); len = len>>8 | len << 8; //the length is register content and therefore needs to be reversed. LWIP_DEBUGF(NETIF_DEBUG, ("cs8900_input: packet len %u\n", len)); snmp_add_ifinoctets(len); // positive length? if (len > 0) { // allocate a pbuf chain with total length 'len' p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); if (p != NULL) { for (q = p; q != 0; q = q->next) { LWIP_DEBUGF(NETIF_DEBUG, ("cs8900_input: pbuf @%p tot_len %u len %u\n", q, q->tot_len, q->len)); ptr = q->payload; // TODO: CHECK: what if q->len is odd? we don't use the last byte? for (i = 0; i < (q->len + 1) / 2; i++) { *ptr = IORead(CS8900_RxTxDataPort); ptr++; } } } // could not allocate a pbuf else { // skip received frame // TODO: maybe do not skip the frame at this point in time? ppWrite(CS_PP_RXCFG, 0x4301U/*SKIP*/); #if (CS8900_STATS > 0) ((struct cs8900if *)netif->state)->dropped++; #endif snmp_inc_ifindiscards(); len = 0; } } // length was zero else { } } return p; } /* ********************************************************************************************************* * Writing an IP packet (to be transmitted) to the CS8900. * * Description : Before writing a frame to the CS8900, the ARP module is asked to resolve the * Ethernet MAC address. The ARP module might undertake actions to resolve the * address first, and queue this packet for later transmission. * * Arguments : netif: The lwIP network interface data structure belonging to this device. * p pbuf: to be transmitted (or the first pbuf of a chained list of pbufs). * ipaddr: destination IP address. * * Returns : ERR_OK if the packet was sent or queued. There is no way to * find out if a packet really makes it onto the network link. * * Notes : Uses the function cs8900_input() that should handle the actual * reception of bytes from the network interface. ********************************************************************************************************* */ err_t cs8900if_output(struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr) { struct cs8900if *cs8900if = netif->state; err_t result; /* resolve the link destination hardware address */ p = etharp_output(netif, ipaddr, p); /* network hardware address obtained? */ if (p != NULL) { /* send out the packet */ result = cs8900_output(netif, p); p = NULL; } /* { p == NULL } */ else { /* we cannot tell if the packet was sent, the packet could have been queued */ /* on an ARP entry that was already pending. */ return ERR_OK; } return result; } /* ********************************************************************************************************* * cs8900_output() * * Description : Sends a packet to the cs8900 ethernet controller for transmisison * * Arguments : netif: The lwIP network interface data structure belonging to this device. * * Returns : return error codes * - ERR_OK: packet transferred to hardware * - ERR_CONN: no link or link failure * - ERR_IF: could not transfer to link (hardware buffer full?) * ********************************************************************************************************* */ static err_t cs8900_output(struct netif *netif, struct pbuf *p) { int tries = 0; err_t result; // exit if link has failed if (( ppRead(CS_PP_LINESTATUS) & 0x8000U/*LinkOK*/) != 0x8000U) return ERR_CONN; // no Ethernet link result = ERR_OK; /* issue 'transmit' command to CS8900 */ IOWrite(CS_IO_TXCMDW, 0xC900U);//send the write command to the txwrite register /* send length (in bytes) of packet to send, if its less than the ethernet minimum frame size, the cs8900 will pad automatically */ IOWrite(CS_IO_TXLEN, (p->tot_len<<8 | p->tot_len>>8)); //must be byte reversed! endian issue with the cs8900 auto swapping register data, // not ready for transmission and still within 100 retries? while (((ppRead(CS_PP_BUSSTATUS) & 0x0001U/*Rdy4TxNOW*/) != 0x0001) && (tries++ < 100)) { // throw away the last committed received frame (SKIP) ppWrite(CS_PP_RXCFG, 0x4301U); } // ready to transmit? if (ppRead(CS_PP_BUSSTATUS) & 0x0001U == 0x0001U) { unsigned long sent_bytes = 0; /* q traverses through linked list of pbuf's * This list MUST consist of a single packet ONLY */ struct pbuf *q; for (q = p; q != NULL; q = q->next) { u16_t i; u16_t *ptr = (u16_t *)q->payload; /* Send the data from the pbuf to the interface, one pbuf at a * time. The size of the data in each pbuf is kept in the ->len * variable. */ for (i = 0; i < q->len; i += 2) { /** TODO: this routine assumes 16-bit boundary pbufs... */ IOWrite(CS8900_RxTxDataPort, *ptr++); sent_bytes += 2; } } #if (CS8900_STATS > 0) ((struct cs8900if *)netif->state)->sentpackets++; ((struct cs8900if *)netif->state)->sentbytes += sent_bytes; #endif snmp_add_ifoutoctets(sent_bytes); } else { // { not ready to transmit!? } snmp_inc_ifoutdiscards(); /* return not connected */ result = ERR_IF; } return result; } /* ********************************************************************************************************* * ppRead() - reads cs8900 register content * * Description : Reads internal cs8900 packet page registers, and returns their content to the * calling function. * * Arguments : takes in an 8 bit packet page register address, and returns a 16 bit register content. ********************************************************************************************************* */ static INT16U ppRead(INT16U pp_addr) { INT16U pp_data; //data from the cs8900 pp register will be stored here until it can be returned (2 bytes). Data_DDR = 0xffff; //set the databus to output Addr_B = CS8900_PP_PTR_PORT; //load the address of the pp pointer register onto the address bus Data_B = pp_addr; //put the address of the internal cs8900 register we wish to read on to the databus Signal_B &= ~CS8900_WRITE; //write this data in to the pp pointer port in the cs8900. Signal_B |= CS8900_WRITE; //de-activate the write signal Data_DDR = 0x0000; //now, set the databus to input Addr_B = CS8900_PP_DATA_PORT; //put the address of the pp data port on to the address bus (this is the register where the cs8900 will put the data we requested just above, all we have to do is retrieve it now) Signal_B &= ~CS8900_READ; //assert the read signal. This tells the cs8900 to put the data from the pp data register on to the data bus pp_data = Data_B; //store the data thats on the address bus into pp_data Signal_B |= CS8900_READ; //de-activate the read signal return pp_data; //return the data from the cs8900 pp register we just read to the calling function. } /* ********************************************************************************************************* * ppWrite() - writes cs8900 register content * * Description : Writes 16 bit data values in to the cs8900 packet page registers. * * Arguments : Takes in a 16 bit packet page register address, and a 16 bit data * value to be written to that address. Returns nothing. ********************************************************************************************************* */ static void ppWrite(INT16U pp_addr, INT16U pp_data) { Data_DDR = 0xffff; //set the databus to output. Addr_B = CS8900_PP_PTR_PORT; //load the address of the pp pointer register onto the address bus Data_B = pp_addr; //put the address of the internal cs8900 register we wish to read on to the databus Signal_B &= ~CS8900_WRITE; //write this data in to the pp pointer port in the cs8900. Signal_B |= CS8900_WRITE; //de-activate the write signal Addr_B = CS8900_PP_DATA_PORT; //put the pp data port address on the address bus. (we write the pointer to this register, then the cs8900 internally gets the data from the register and puts in in the pp data port register) Data_B = pp_data; //put the data that we desire to put into the pp register (pp_address) on the databus Signal_B &= ~CS8900_WRITE; //write this data in to the cs8900 pp data port Signal_B |= CS8900_WRITE; //de-activate the write signal, the cs8900 should now have pp_data written to the pp register pp_addr } /* ********************************************************************************************************* * IORead() - reads cs8900 IO mapped Registers - like the frame data port! - not a packet page function! * * Description : Reads a 16 bit word from the cs8900 IO data port. - THIS IS NOT A PACKET PAGE PROCEDURE. * * Arguments : Takes in an 8 bit IO register address and returns a 16 bit word from that address ********************************************************************************************************* */ static INT16U IORead(INT8U IO_Reg_Addr) { INT16U IO_Reg_Data; //store the returned data here until its returned to the calling function. Data_DDR = 0x0000; //set the databus to input Addr_B = IO_Reg_Addr; //put the IO register address we wish to read from on to the databus Signal_B &= ~CS8900_READ; //assert the read signal. This tells the cs8900 to put the data from the specified address on to the databus. IO_Reg_Data = Data_B; //save the returned data from the cs8900 in IO_Data until its returned to the calling function Signal_B |= CS8900_READ; //de-activate the read signal. return IO_Reg_Data; //return the 16 bit word from the cs8900 IO Data Register. } /* ********************************************************************************************************* * IOWrite() - writes cs8900 IO mapped register like the frame data port! - not a packet page function! * * Description : Writes 16 bit data values in to the cs8900 packet page registers. * * Arguments : Takes in an 8 bit IO register address, and a 16 bit data * value to be written to that address. Returns nothing. ********************************************************************************************************* */ static void IOWrite(INT8U IO_Reg_Addr, INT16U IO_Reg_Data) { Data_DDR = 0xffff; //Set Data bus to OUTPUT Data_B = IO_Reg_Data; //put the data we wish to write to the IO mapped register on the Data Bus Addr_B = IO_Reg_Addr; //put the address of the IO mapped register we wish to write to on the address bus Signal_B &= ~CS8900_WRITE; //write this data in to the cs8900 IO Register OSTimeDly(1); Signal_B |= CS8900_WRITE; //de-activate the write signal }