URL
https://opencores.org/ocsvn/openrisc_me/openrisc_me/trunk
Subversion Repositories openrisc_me
Compare Revisions
- This comparison shows the changes necessary to convert path
/openrisc/trunk/rtos/ecos-2.0/packages/io/usb/eth/slave/v2_0/src
- from Rev 27 to Rev 174
- ↔ Reverse comparison
Rev 27 → Rev 174
/usbsethdrv.c
0,0 → 1,616
//========================================================================== |
// |
// usbethdrv.c |
// |
// Network device driver for USB-ethernet devices. |
// |
//========================================================================== |
//####ECOSGPLCOPYRIGHTBEGIN#### |
// ------------------------------------------- |
// This file is part of eCos, the Embedded Configurable Operating System. |
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
// |
// eCos is free software; you can redistribute it and/or modify it under |
// the terms of the GNU General Public License as published by the Free |
// Software Foundation; either version 2 or (at your option) any later version. |
// |
// eCos is distributed in the hope that it will be useful, but WITHOUT ANY |
// WARRANTY; without even the implied warranty of MERCHANTABILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// You should have received a copy of the GNU General Public License along |
// with eCos; if not, write to the Free Software Foundation, Inc., |
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
// |
// As a special exception, if other files instantiate templates or use macros |
// or inline functions from this file, or you compile this file and link it |
// with other works to produce a work based on this file, this file does not |
// by itself cause the resulting work to be covered by the GNU General Public |
// License. However the source code for this file must still be made available |
// in accordance with section (3) of the GNU General Public License. |
// |
// This exception does not invalidate any other reasons why a work based on |
// this file might be covered by the GNU General Public License. |
// |
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
// at http://sources.redhat.com/ecos/ecos-license/ |
// ------------------------------------------- |
//####ECOSGPLCOPYRIGHTEND#### |
//========================================================================== |
//#####DESCRIPTIONBEGIN#### |
// |
// Author(s): bartv |
// Contributors: bartv |
// Date: 2000-10-04 |
// |
//####DESCRIPTIONEND#### |
//========================================================================== |
|
#include <cyg/infra/cyg_type.h> |
#include <cyg/hal/hal_arch.h> |
#include <cyg/infra/diag.h> |
#include <cyg/hal/drv_api.h> |
|
#define __ECOS 1 |
#include <cyg/io/eth/netdev.h> |
#include <cyg/io/eth/eth_drv.h> |
#include <cyg/io/eth/eth_drv_stats.h> |
|
#include <pkgconf/io_usb_slave_eth.h> |
#include <cyg/io/usb/usbs_eth.h> |
|
// ---------------------------------------------------------------------------- |
// The network driver data structure. |
ETH_DRV_SC(usbs_eth_sc0, |
(void*) &usbs_eth0, |
CYGDAT_USBS_ETHDRV_NAME, |
usbs_ethdrv_start, |
usbs_ethdrv_stop, |
usbs_ethdrv_ioctl, |
usbs_ethdrv_can_send, |
usbs_ethdrv_send, |
usbs_ethdrv_recv, |
usbs_ethdrv_deliver, |
usbs_ethdrv_poll, |
usbs_ethdrv_intvector); |
|
NETDEVTAB_ENTRY(usbs_eth_netdev0, |
"usbs_eth0", |
usbs_ethdrv_init, |
&usbs_eth_sc0); |
|
// ---------------------------------------------------------------------------- |
// Statics gathering. The following macro can be used to increment a |
// statistic without having to use a #ifdef for the statistics |
// configuration option everywhere. |
#ifdef CYGFUN_USBS_ETHDRV_STATISTICS |
# define INCR_STAT(a) \ |
CYG_MACRO_START \ |
(a) += 1; \ |
CYG_MACRO_END |
#else |
# define INCR_STAT(a) CYG_EMPTY_STATEMENT |
#endif |
|
// Various constants related to SNMP statistics. It is not clear |
// what these are all for. |
#ifdef CYGFUN_USBS_ETHDRV_STATISTICS |
# define CYGDAT_USBS_ETHDRV_DESCRIPTION "eCos USB ethernet device" |
#endif |
// ---------------------------------------------------------------------------- |
// Utility functions. |
// |
// The TCP/IP stack works in terms of scatter/gather buffers. USB tends to |
// involve DMA operations so it is more convenient to work in terms of |
// 1514 byte flat buffers. Actually, the first two bytes of the buffer |
// are used to hold the ethernet frame size to work around restrictions |
// with certain hardware implementations of USB that may be unable to |
// transfer certain packet sizes. |
|
static bool |
scatter(unsigned char* buf, struct eth_drv_sg* sg, int sg_len) |
{ |
unsigned int size; |
|
size = buf[0] | (buf[1] << 8); |
buf++; buf++; |
|
CYG_ASSERT((size >= CYGNUM_USBS_ETH_MIN_FRAME_SIZE) && (size <= CYGNUM_USBS_ETH_MAX_FRAME_SIZE),\ |
"ethernet frame size limits must be observed"); |
|
while ((size > 0) && (sg_len > 0)) { |
if (size > sg->len) { |
memcpy((void*) sg->buf, buf, sg->len); |
buf += sg->len; |
size -= sg->len; |
sg++; |
sg_len--; |
} else { |
memcpy((void*) sg->buf, buf, size); |
size = 0; |
} |
} |
|
return 0 == size; |
} |
|
static bool |
gather(unsigned char* buf, unsigned int size, struct eth_drv_sg* sg, int sg_len) |
{ |
unsigned int left = size; |
unsigned char* base = buf; |
|
buf++; buf++; |
while ((left > 0) && (sg_len > 0)) { |
if (left > sg->len) { |
memcpy(buf, (void*) sg->buf, sg->len); |
buf += sg->len; |
left -= sg->len; |
sg++; |
sg_len--; |
} else { |
memcpy(buf, (void*) sg->buf, left); |
left = 0; |
} |
} |
size = size - left; |
base[0] = size & 0x00FF; |
base[1] = (size >> 8) & 0x00FF; |
|
return 0 == left; |
} |
|
|
// ---------------------------------------------------------------------------- |
// usbs_ethdrv_init() |
// |
// This function is called during system initialization to decide |
// whether or not this particular network device is usable. For |
// USB-ethernet this is problematical, the device is only really |
// usable once both sides have come up. The typical sequence |
// of events is something like: |
// |
// 1) the eCos peripheral is powered up. Static constructors are |
// run resulting in basic initialization. |
// |
// 2) the eCos TCP/IP stack initialization happens. Roughly in |
// parallel the eCos USB slave side is initialized as well, |
// i.e. enumeration data is supplied to control endpoints, |
// endpoints are associated with application classes, and so |
// on. The relative order of TCP/IP and USB initialization is |
// not particularly important. |
// |
// It is the TCP/IP stack's initialization code that will |
// invoke usbs_eth_init(). |
// |
// 3) host-side USB detects that the eCos peripheral has been |
// connected or powered up. It goes through the enumeration |
// process and will end up loading a host-side network driver. |
// This connects to the eCos-side USB ethernet code to |
// e.g. obtain the MAC address. |
// |
// 4) when the host-side is ready, the eCos side can be brought up. |
// The required call is (sc->funs->eth_drv->init)(sc, enaddr) |
// |
// In practice it is easier for now to invoke the init() function |
// immediately. There are not going to be any incoming packets |
// until the host is ready, and can_send() can just return false |
// for the time being. |
// |
// Invoked in: thread context only |
// ---------------------------------------------------------------------------- |
|
static bool |
usbs_ethdrv_init(struct cyg_netdevtab_entry* ndp) |
{ |
struct eth_drv_sc* sc = (struct eth_drv_sc*)(ndp->device_instance); |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
|
(*sc->funs->eth_drv->init)(sc, eth->ecos_MAC); |
return true; |
} |
|
// ---------------------------------------------------------------------------- |
// The receive process that is used to transfer a received ethernet |
// packet into the stack. The calling sequence is somewhat convoluted. |
// It started off as: |
// |
// 1) Ethernet hw ISR invoked by hardware, schedules its own |
// hw_dsr(), and blocks further interrupts in the ethernet chip |
// 2) hw_dsr() calls generic eth_drv_dsr() from io/eth common package |
// 3) eth_drv_dsr() interacts with the TCP/IP stack and allocates mbufs |
// (typically, the TCP/IP stack might not be in use) |
// 4) eth_drv_dsr() calls usbs_eth_recv() to transfer the data to mbufs |
// 5) eth_drv_dsr() returns to hw_dsr() which reenables interrupts |
// 6) hw_dsr() completes and everything can proceed. |
// |
// The problem with this is that the whole ethernet packet gets copied |
// inside a DSR, affecting dispatch latency (but not interrupt latency). |
// This is bad. Hence there is an alternative route involving a separate |
// thread in the TCP/IP stack. |
// |
// 1) Ethernet hw ISR runs as before, scheduling hw_dsr() |
// 2) hw_dsr() calls up into eth_drv_dsr() |
// 3) eth_drv_dsr() wakes up a thread inside the TCP/IP stack |
// 4) eth_drv_dsr() returns to hw_dsr(), which performs no further |
// processing. Ethernet chip interrupts remain disabled. |
// 5) The TCP/IP thread ends up calling hw_deliver(). This should take |
// care of any pending activity. For every buffered packet there should |
// be a call to a generic recv() function which then goes back into |
// the driver-specific recv() function. |
// |
// The advantage is that ethernet packet copying now happens at thread |
// level rather than DSR level so thread priorities can be used to |
// schedule things. |
// |
// USB-ethernet does not interact directly with any hardware, instead |
// it just passes information to lower levels of USB code. The reception |
// process is started by usbs_ethdrv_start() when the TCP/IP stack brings |
// up the interface. |
// |
// When the USB transfer has completed a callback will be invoked, at |
// DSR level. Assuming the transfer went ok, the callback will invoke |
// eth_drv_dsr() to inform the higher level code. |
// |
// The deliver function can check the state of the buffer |
// and go through the sc->funs->eth_drv->recv()/recv() sequence |
// to transfer the data into the stack. |
// |
// usbs_ethdrv_recv() does a scatter from the internal buffer into the |
// mbuf, thus freeing up the buffer. This allows it to start another |
// receive, |
// |
// Synchronisation involves the scheduler lock because the recv |
// callback is invoked inside a DSR. |
|
static void usbs_ethdrv_halted_callback(void*, int); |
|
static void |
usbs_ethdrv_recv_callback(usbs_eth* eth, void* callback_data, int size) |
{ |
cyg_bool resubmit = true; |
|
struct eth_drv_sc* sc = (struct eth_drv_sc*) callback_data; |
CYG_ASSERT( eth == (usbs_eth*)(sc->driver_private), "USB and TCP/IP worlds need to be consistent"); |
|
INCR_STAT(eth->interrupts); |
if (!eth->ecos_up) { |
// This message should just be discarded since the eCos TCP/IP |
// stack is not expecting anything from this interface. |
// Reception will resume when the interface comes back up. |
eth->rx_active = false; |
resubmit = false; |
} else if (size < 0) { |
// An error has occurred. The likely possibilities are: |
// -EPIPE: connection to the host has been broken |
// -EAGAIN: the endpoint is haltedn |
// -EMSGSIZE: bogus message from host |
// -EIO: other |
|
if (-EAGAIN == size) { |
// EAGAIN should be handled by waiting for the endpoint to be reset. |
resubmit = false; |
usbs_start_rx_endpoint_wait(eth->rx_endpoint, &usbs_ethdrv_halted_callback, (void*) sc); |
} else if (-EMSGSIZE == size) { |
// Do nothing for now |
} else { |
// EPIPE should be resubmitted, the usbseth.c will use the |
// pending rx support. EIO could mean anything. |
} |
} else if (0 == size) { |
// The endpoint is no longer halted. Just do the resubmit at |
// the end. |
} else { |
// A packet has been received. Now do a size sanity check |
// based on the first two bytes. |
int real_size = eth->rx_bufptr[0] + (eth->rx_bufptr[1] << 8); |
if (real_size < CYGNUM_USBS_ETH_MIN_FRAME_SIZE) { |
INCR_STAT(eth->rx_short_frames); |
} else if (real_size > CYGNUM_USBS_ETH_MAX_FRAME_SIZE) { |
INCR_STAT(eth->rx_too_long_frames); |
} else { |
// The packet appears to be valid. Inform higher level |
// code and mark the buffer as in use. |
resubmit = false; |
eth->rx_buffer_full = true; |
eth->rx_active = false; |
eth_drv_dsr(0, 0, (cyg_addrword_t) sc); |
} |
} |
|
if (resubmit) { |
eth->rx_active = true; |
usbs_eth_start_rx(eth, eth->rx_bufptr, &usbs_ethdrv_recv_callback, callback_data); |
} |
} |
|
// Another callback, used to wait while an endpoint is halted. |
static void |
usbs_ethdrv_halted_callback(void* callback_data, int size) |
{ |
struct eth_drv_sc* sc = (struct eth_drv_sc*) callback_data; |
usbs_ethdrv_recv_callback((usbs_eth*) sc->driver_private, callback_data, 0); |
} |
|
// Start a receive operation. It is not possible to abort an existing |
// rx operation, so a valid sequence of events is: start, rx ongoing, |
// stop, restart. The rx_active field is used to keep track of whether |
// or not there is still a receive in progress. The receive callback |
// will just discard incoming data if the eCos stack is not currently |
// running. |
static void |
usbs_ethdrv_start_recv(struct eth_drv_sc* sc, usbs_eth* eth) |
{ |
cyg_drv_dsr_lock(); |
if (!eth->rx_active) { |
eth->rx_active = true; |
usbs_eth_start_rx(eth, eth->rx_bufptr, &usbs_ethdrv_recv_callback, (void*) sc); |
} |
cyg_drv_dsr_unlock(); |
} |
|
// This is invoked from the delivery thread when a valid buffer |
// has been received. The buffer should be scattered into the |
// supplied list, then another receive should be started. |
|
static void |
usbs_ethdrv_recv(struct eth_drv_sc* sc, |
struct eth_drv_sg* sg_list, int sg_len) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
|
CYG_ASSERT( eth->rx_buffer_full, "This function should only be called when there is a buffer available"); |
(void) scatter(eth->rx_bufptr, sg_list, sg_len); |
eth->rx_buffer_full = false; |
eth->rx_active = true; |
usbs_eth_start_rx(eth, eth->rx_bufptr, &usbs_ethdrv_recv_callback, (void*) sc); |
} |
|
// ---------------------------------------------------------------------------- |
// Now for the transmit process. |
// |
// When an application thread writes down a socket the data gets moved |
// into mbufs, and then passed to the appropriate device driver - which |
// may or may not be able to process it immediately. There is also a |
// timeout thread within the TCP/IP to handle retransmits etc. |
// |
// The stack will start by calling usbs_ethdrv_can_send() to determine |
// whether or not the driver can accept the packet. For the purposes |
// of the USB-ethernet driver this is true provided both host |
// and target are up and there is a spare buffer available. |
// |
// If the usbs_eth_can_send() returns true then there will be a call |
// to usbs_ethdrv_send(). This gathers the data into a single |
// buffer. If there is no transmit in progress yet then one is started. |
// |
// At some point the packet will have been transmitted and a callback |
// gets invoked. This needs to call eth_drv_dsr(), waking up the |
// delivery thread. The deliver() function can then check which |
// transmissions have completed and inform the higher level code |
// via sc->funs->eth_drv->tx_done(). The buffer can be re-used at |
// that point. |
|
static void |
usbs_ethdrv_send_callback(usbs_eth* eth, void* callback_data, int size) |
{ |
struct eth_drv_sc* sc = (struct eth_drv_sc*) callback_data; |
CYG_ASSERT( eth == (usbs_eth*)(sc->driver_private), "USB and TCP/IP worlds need to be consistent"); |
|
INCR_STAT(eth->interrupts); |
|
// There are a variety of possible error codes. -EAGAIN indicates |
// that the endpoint is stalled. -EPIPE indicates that the |
// connection to the host has been lost. These are not really |
// particularly interesting. Whatever happens the buffer |
// must be cleared and higher-level code informed so that |
// the mbufs can be released. |
if (size > 0) { |
INCR_STAT(eth->tx_count); |
} |
eth->tx_done = true; |
eth_drv_dsr(0, 0, (cyg_addrword_t) sc); |
} |
|
// Is it possible to send an ethernet frame? This requires |
// an empty buffer, i.e. there should be no existing |
// transmit in progress. It also requires that the host |
// is connected and that the endpoint is not currently halted. |
static int |
usbs_ethdrv_can_send(struct eth_drv_sc* sc) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
return eth->host_up && !eth->tx_buffer_full && !eth->tx_endpoint->halted; |
} |
|
// Actually start a packet transmission. This means collecting |
// all the data into a single buffer and then invoking the |
// lower-level code. The latter may discard the packet immediately |
// if the MAC is not appropriate: it would be more efficient to |
// catch that here, especially for large packets, but the check |
// has to happen inside the lower-level code anyway in case |
// that is being invoked directly rather than via the driver. |
// |
// There is a possible recursion problem, |
// send->start_tx->tx_done->can_send->send, which is guarded |
// against using the tx_in_send flag. |
|
static void |
usbs_ethdrv_send(struct eth_drv_sc* sc, |
struct eth_drv_sg* sgl_list, int sg_len, int total_len, |
unsigned long key) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
|
CYG_ASSERT( 0 == eth->tx_in_send, "send() should not be invoked recursively"); |
CYG_ASSERT( total_len <= CYGNUM_USBS_ETH_MAX_FRAME_SIZE, "ethernet maximum frame size should be observed"); |
CYG_ASSERT( CYGNUM_USBS_ETH_MIN_FRAME_SIZE <= total_len, "ethernet minimum frame size should be observed"); |
|
eth->tx_in_send = true; |
CYG_ASSERT( !eth->tx_buffer_full, "the transmit buffer should be empty"); |
gather(eth->tx_buffer, CYGNUM_USBS_ETH_MAX_FRAME_SIZE, sgl_list, sg_len); |
eth->tx_buffer_full = true; |
eth->tx_done = false; |
eth->tx_key = key; |
usbs_eth_start_tx(eth, eth->tx_buffer, &usbs_ethdrv_send_callback, (void*) sc); |
eth->tx_in_send = false; |
} |
|
// ---------------------------------------------------------------------------- |
// Deliver needs to take into account both receive and transmit buffers. |
|
static void |
usbs_ethdrv_deliver(struct eth_drv_sc* sc) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
|
if (eth->rx_buffer_full) { |
int size = eth->rx_bufptr[0] + (eth->rx_bufptr[1] << 8); |
(*sc->funs->eth_drv->recv)(sc, size); |
} |
if (eth->tx_done) { |
unsigned long key = eth->tx_key; |
eth->tx_buffer_full = false; |
eth->tx_done = false; |
(*sc->funs->eth_drv->tx_done)(sc, key, 1); |
} |
} |
|
// ---------------------------------------------------------------------------- |
// usbs_ethdrv_start() |
// |
// This gets called by the TCP/IP stack later on during |
// initialization, when the stack is ready to send and receive |
// packets. It may get called multiple times while the stack |
// is running, with different flags values. |
// |
// As far as transmits are concerned, nothing needs to be done. If no |
// transmit is in progress then everything is fine anyway. If a |
// transmit is already in progress then it must be allowed to complete |
// via the usual route. Receives should however be restarted, the |
// start function has appropriate safeguards. |
// |
// Invoked in: thread context only |
// ---------------------------------------------------------------------------- |
|
static void |
usbs_ethdrv_start(struct eth_drv_sc* sc, unsigned char* enaddr, int flags) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
if (!eth->ecos_up) { |
eth->ecos_up = true; |
usbs_ethdrv_start_recv(sc, eth); |
} |
} |
|
// ---------------------------------------------------------------------------- |
// usbs_ethdrv_stop() |
// |
// Similarly this gets called by the TCP/IP stack to bring the network |
// interface down. Nothing should happen for any packets currently |
// being transmitted or received, that would cause confusion everywhere. |
// The receive callback checks the ecos_up flag and does the right |
// thing. The TCP/IP stack should not call can_send() after taking |
// the interface down so no new transmits will be initiated. |
// |
// Invoked in: thread context only |
// ---------------------------------------------------------------------------- |
|
static void |
usbs_ethdrv_stop(struct eth_drv_sc* sc) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
eth->ecos_up = false; |
} |
|
// ---------------------------------------------------------------------------- |
// usbs_eth_ioctl() |
// |
// The operations to worry about here are: |
// |
// SET_MAC_ADDRESS,via the SIOCSIFHWADDR ioctl |
// |
// GET_IF_STATS and GET_IF_STATS_UD, to report gathered statistics. |
// |
// Invoked in: thread context only |
// ---------------------------------------------------------------------------- |
|
static int |
usbs_ethdrv_ioctl(struct eth_drv_sc* sc, unsigned long key, void* data, int data_length) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
int result = EINVAL; |
|
switch(key) { |
case ETH_DRV_SET_MAC_ADDRESS: |
{ |
if (6 == data_length) { |
memcpy(eth->ecos_MAC, data, 6); |
result = 0; |
} |
} |
break; |
#if defined(CYGFUN_USBS_ETHDRV_STATISTICS) && defined(ETH_DRV_GET_IF_STATS_UD) |
case ETH_DRV_GET_IF_STATS_UD: |
case ETH_DRV_GET_IF_STATS: |
{ |
static unsigned char my_chipset[] = { 0, 0 }; |
struct ether_drv_stats *p = (struct ether_drv_stats*) data; |
int i; |
strcpy(p->description, CYGDAT_USBS_ETHDRV_DESCRIPTION); |
for ( i = 0; i < SNMP_CHIPSET_LEN; i++ ) { |
if ( 0 == (p->snmp_chipset[i] = my_chipset[i]) ) { |
break; |
} |
} |
p->duplex = 3; // 3 == duplex |
p->operational = (eth->host_up && eth->ecos_up) ? 3 : 2; // 3 == up, 2 == down |
p->speed = 10 * 1000000; |
p->supports_dot3 = 1; |
p->rx_too_long_frames = eth->rx_too_long_frames; |
p->rx_short_frames = eth->rx_short_frames; |
p->interrupts = eth->interrupts; |
p->rx_count = eth->rx_count; |
p->tx_count = eth->tx_count; |
p->tx_queue_len = 1; |
} |
break; |
#endif |
|
default: |
break; |
} |
|
return result; |
} |
|
|
// ---------------------------------------------------------------------------- |
// usbs_ethdrv_poll() |
// |
// On real ethernet hardware this is used by RedBoot once the |
// application has started running, so that the network device can be |
// used for debugging purposes as well as for the application's own |
// needs. The lower-level USB device may supply a poll function as well. |
// ---------------------------------------------------------------------------- |
static void |
usbs_ethdrv_poll(struct eth_drv_sc* sc) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
(*eth->control_endpoint->poll_fn)(eth->control_endpoint); |
} |
|
// ---------------------------------------------------------------------------- |
// usbs_ethdrv_intvector() |
// |
// See usbs_eth_poll(). |
// ---------------------------------------------------------------------------- |
|
static int |
usbs_ethdrv_intvector(struct eth_drv_sc* sc) |
{ |
usbs_eth* eth = (usbs_eth*)(sc->driver_private); |
return eth->control_endpoint->interrupt_vector; |
} |
|
|
/usbseth.c
0,0 → 1,321
//========================================================================== |
// |
// usbseth.c |
// |
// Support for USB-ethernet devices, slave-side. |
// |
//========================================================================== |
//####ECOSGPLCOPYRIGHTBEGIN#### |
// ------------------------------------------- |
// This file is part of eCos, the Embedded Configurable Operating System. |
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
// |
// eCos is free software; you can redistribute it and/or modify it under |
// the terms of the GNU General Public License as published by the Free |
// Software Foundation; either version 2 or (at your option) any later version. |
// |
// eCos is distributed in the hope that it will be useful, but WITHOUT ANY |
// WARRANTY; without even the implied warranty of MERCHANTABILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// You should have received a copy of the GNU General Public License along |
// with eCos; if not, write to the Free Software Foundation, Inc., |
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
// |
// As a special exception, if other files instantiate templates or use macros |
// or inline functions from this file, or you compile this file and link it |
// with other works to produce a work based on this file, this file does not |
// by itself cause the resulting work to be covered by the GNU General Public |
// License. However the source code for this file must still be made available |
// in accordance with section (3) of the GNU General Public License. |
// |
// This exception does not invalidate any other reasons why a work based on |
// this file might be covered by the GNU General Public License. |
// |
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
// at http://sources.redhat.com/ecos/ecos-license/ |
// ------------------------------------------- |
//####ECOSGPLCOPYRIGHTEND#### |
//========================================================================== |
//#####DESCRIPTIONBEGIN#### |
// |
// Author(s): bartv |
// Contributors: bartv |
// Date: 2000-10-04 |
// |
//####DESCRIPTIONEND#### |
// |
//========================================================================== |
|
#include <cyg/infra/cyg_type.h> |
#include <cyg/infra/cyg_ass.h> |
#include <cyg/infra/cyg_trac.h> |
#include <cyg/infra/diag.h> |
#include <cyg/hal/hal_arch.h> |
#include <cyg/infra/diag.h> |
#include <cyg/hal/drv_api.h> |
|
#include <pkgconf/io_usb_slave_eth.h> |
|
#define __ECOS 1 |
#include <cyg/io/usb/usbs_eth.h> |
|
#ifdef CYGPKG_USBS_ETHDRV |
#include <cyg/io/eth/netdev.h> |
#include <cyg/io/eth/eth_drv.h> |
#endif |
|
// ---------------------------------------------------------------------------- |
// Static data. |
// |
// usbs_eth0 contains the per-device data, both the low-level data |
// such as which endpoints to use and the network-driver data such as |
// SNMP statistics. If this package is loaded then the assumption |
// is that there should be at least one USB-ethernet device. Additional |
// ones can be instantiated in application code if necessary. A call |
// to usbs_eth_init() is required for initialization. |
usbs_eth usbs_eth0; |
|
// ---------------------------------------------------------------------------- |
// Initialization. This should be called explicitly by application code |
// at an appropriate point in the system startup. |
void |
usbs_eth_init(usbs_eth* eth, usbs_control_endpoint* ctrl, usbs_rx_endpoint* rx, usbs_tx_endpoint* tx, unsigned char* mac) |
{ |
eth->control_endpoint = ctrl; |
eth->rx_endpoint = rx; |
eth->tx_endpoint = tx; |
eth->host_up = false; |
eth->host_promiscuous = false; |
memcpy(eth->host_MAC, mac, 6); |
eth->rx_pending_buf = (unsigned char*) 0; |
|
// Install default handlers for some messages. Higher level code |
// may override this. |
ctrl->state_change_fn = &usbs_eth_state_change_handler; |
ctrl->state_change_data = (void*) eth; |
ctrl->class_control_fn = &usbs_eth_class_control_handler; |
ctrl->class_control_data = (void*) eth; |
|
#ifdef CYGPKG_USBS_ETHDRV |
eth->ecos_up = false; |
eth->rx_active = false; |
# ifdef CYGFUN_USBS_ETHDRV_STATISTICS |
eth->interrupts = 0; |
eth->tx_count = 0; |
eth->rx_count = 0; |
# endif |
# ifndef HAL_DCACHE_LINE_SIZE |
eth->rx_bufptr = eth->rx_buffer; |
# else |
# endif |
eth->rx_bufptr = (unsigned char*) ((((cyg_uint32)eth->rx_buffer) + HAL_DCACHE_LINE_SIZE - 1) |
& ~(HAL_DCACHE_LINE_SIZE - 1)); |
eth->rx_buffer_full = false; |
eth->tx_in_send = false; |
eth->tx_buffer_full = false; |
eth->tx_done = false; |
#endif |
} |
|
|
// ---------------------------------------------------------------------------- |
// Generic transmit and receive operations. These can be called |
// explicitly by application code, or implicitly via the eCos ethernet |
// device driver code in usbsethdrv.c. These two modes of operation |
// should not be mixed since the routines do not perform any |
// synchronization themselves, instead they rely on higher level code. |
|
// Packet transmission. The exported function is usbs_eth_start_tx(), |
// which can be invoked from thread context or DSR context. The |
// supplied buffer must already be in a form that can be transmitted |
// directly out of the USB endpoint with no further processing |
// (although it is necessary to extract the size information from the |
// buffer). |
// |
// When the underlying USB transfer has completed the USB code will invoke |
// usbs_eth_tx_callback(), usually in DSR context although possibly in |
// thread context depending on the specific USB implementation. The |
// underlying USB driver may have had to do some padding so the amount |
// transferred may be slightly greater than requested. |
|
static void |
usbs_eth_tx_callback(void* usbs_callback_arg, int size) |
{ |
usbs_eth* eth = (usbs_eth*) usbs_callback_arg; |
CYG_ASSERT( (size < 0) || (size >= CYGNUM_USBS_ETH_MINTU), "returned size must be valid."); |
(*eth->tx_callback_fn)(eth, eth->tx_callback_arg, size); |
} |
|
void |
usbs_eth_start_tx(usbs_eth* eth, unsigned char* buf, void (*callback_fn)(usbs_eth*, void*, int), void* callback_arg) |
{ |
int size; |
cyg_bool address_ok = false; |
static const unsigned char broadcast_mac[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; |
|
size = buf[0] + (buf[1] << 8); |
CYG_ASSERT( (size < 0) || ((size >= CYGNUM_USBS_ETH_MIN_FRAME_SIZE) && (size <= CYGNUM_USBS_ETH_MAX_FRAME_SIZE)), \ |
"ethernet frame size constraints must be observed"); |
|
if ((0 == memcmp(buf + 2, eth->host_MAC, 6)) || |
(0 == memcmp(buf + 2, broadcast_mac, 6))) { |
address_ok = true; |
} |
|
// The following checks involve data that can change as a result |
// of control operations, so it is necessary to synchronize with |
// those. The control operations will typically run at DSR level |
// so a DSR lock has to be used. |
|
cyg_drv_dsr_lock(); |
if (eth->host_up && (address_ok || eth->host_promiscuous)) { |
|
eth->tx_callback_fn = callback_fn; |
eth->tx_callback_arg = callback_arg; |
eth->tx_endpoint->buffer = buf; |
eth->tx_endpoint->buffer_size = size + 2; |
eth->tx_endpoint->complete_fn = &usbs_eth_tx_callback; |
eth->tx_endpoint->complete_data = (void*) eth; |
(*(eth->tx_endpoint->start_tx_fn))(eth->tx_endpoint); |
|
} else { |
// Packets not intended for the host can be discarded quietly. |
// A broken connection needs to be reported. |
(*callback_fn)(eth, callback_arg, eth->host_up ? size : -EPIPE); |
} |
cyg_drv_dsr_unlock(); |
} |
|
// Packet reception. This simply involves starting a transfer for |
// up to the maximum ethernet frame size. The lower-level USB code |
// will detect the end of the transfer. The exported function is |
// usbs_eth_start_rx(). |
static void |
usbs_eth_rx_callback(void* usbs_callback_arg, int size) |
{ |
usbs_eth* eth = (usbs_eth*) usbs_callback_arg; |
|
CYG_ASSERT( (size <= 0) || ((size >= CYGNUM_USBS_ETH_MINTU) && (size <= CYGNUM_USBS_ETH_MAXTU)), \ |
"ethernet frame size constraints must be observed"); |
|
(*eth->rx_callback_fn)(eth, eth->rx_callback_arg, size); |
} |
|
void |
usbs_eth_start_rx(usbs_eth* eth, unsigned char* buf, void (*callback_fn)(usbs_eth*, void*, int), void* callback_arg) |
{ |
eth->rx_callback_fn = callback_fn; |
eth->rx_callback_arg = callback_arg; |
|
cyg_drv_dsr_lock(); |
if (eth->host_up) { |
eth->rx_endpoint->buffer = buf; |
eth->rx_endpoint->buffer_size = CYGNUM_USBS_ETH_RXSIZE; |
eth->rx_endpoint->complete_fn = &usbs_eth_rx_callback; |
eth->rx_endpoint->complete_data = (void*) eth; |
(*(eth->rx_endpoint->start_rx_fn))(eth->rx_endpoint); |
} else { |
CYG_ASSERT( (void*) 0 == eth->rx_pending_buf, "No RX operation should be in progress"); |
eth->rx_pending_buf = buf; |
} |
cyg_drv_dsr_unlock(); |
} |
|
// ---------------------------------------------------------------------------- |
// Control operations. The host may send two types of application-specific |
// control messages, one to get the MAC address and one to enable/disable |
// promiscuous mode on the host side. This callback will typically be invoked |
// in DSR context. |
|
// These constants need to be shared somehow with the driver in ../host/, |
// but if some variant of that driver becomes part of the Linux kernel |
// then its sources must be self-contained with no dependencies on |
// eCos sources or headers. Hence a duplicate definition for now. |
#define USBS_ETH_CONTROL_GET_MAC_ADDRESS 0x01 |
#define USBS_ETH_CONTROL_SET_PROMISCUOUS_MODE 0x02 |
|
usbs_control_return |
usbs_eth_class_control_handler(usbs_control_endpoint* endpoint, void* callback_data) |
{ |
usbs_control_return result = USBS_CONTROL_RETURN_STALL; |
|
usbs_eth* eth = (usbs_eth*) callback_data; |
usb_devreq* devreq = (usb_devreq*) endpoint->control_buffer; |
int size = (devreq->length_hi << 8) + devreq->length_lo; |
|
CYG_ASSERT(endpoint == eth->control_endpoint, "USB ethernet control messages correctly routed"); |
|
if (USBS_ETH_CONTROL_GET_MAC_ADDRESS == devreq->request) { |
// This should be an IN operation for at least six bytes. |
if ((size >= 6) && |
(USB_DEVREQ_DIRECTION_IN == (devreq->type & USB_DEVREQ_DIRECTION_MASK))) { |
|
endpoint->buffer = eth->host_MAC; |
endpoint->buffer_size = 6; |
result = USBS_CONTROL_RETURN_HANDLED; |
} |
// Otherwise drop through with a return value of STALL |
|
} else if (USBS_ETH_CONTROL_SET_PROMISCUOUS_MODE == devreq->request) { |
// The length should be 0, no more data is expected by either side. |
if (0 == size) { |
// The new promiscuity mode is encoded in value_lo; |
eth->host_promiscuous = devreq->value_lo; |
result = USBS_CONTROL_RETURN_HANDLED; |
} |
} |
|
return result; |
} |
|
// State changes. As far as the ethernet code is concerned, if there |
// is a change to CONFIGURED state then the device has come up, |
// otherwise if there is a change from CONFIGURED state it has gone |
// down. All other state changes are irrelevant. |
void |
usbs_eth_state_change_handler(usbs_control_endpoint* endpoint, void* callback_data, usbs_state_change change, int old_state) |
{ |
usbs_eth* eth = (usbs_eth*) callback_data; |
CYG_ASSERT(endpoint == eth->control_endpoint, "USB ethernet state changes correctly routed"); |
|
if (USBS_STATE_CHANGE_CONFIGURED == change) { |
if (USBS_STATE_CONFIGURED != old_state) { |
usbs_eth_enable(eth); |
} |
} else if ((USBS_STATE_CHANGE_RESUMED == change) && (USBS_STATE_CONFIGURED == (USBS_STATE_MASK & old_state))) { |
usbs_eth_enable(eth); |
} else if (eth->host_up) { |
usbs_eth_disable(eth); |
} |
} |
|
// Disabling the ethernet device means clearing the host_up flag. |
// This will block future transmits and receives but not any |
// that are currently underway. |
void |
usbs_eth_disable(usbs_eth* eth) |
{ |
eth->host_up = false; |
} |
|
// Enabling the ethernet device means setting the host_up flag and |
// possibly activating a pending rx operation. |
void |
usbs_eth_enable(usbs_eth* eth) |
{ |
if (!eth->host_up) { |
eth->host_up = true; |
eth->host_promiscuous = false; |
if ((void*) 0 != eth->rx_pending_buf) { |
eth->rx_endpoint->buffer = eth->rx_pending_buf; |
eth->rx_endpoint->buffer_size = CYGNUM_USBS_ETH_RXSIZE; |
eth->rx_endpoint->complete_fn = &usbs_eth_rx_callback; |
eth->rx_endpoint->complete_data = (void*) eth; |
eth->rx_pending_buf = (void*) 0; |
(*(eth->rx_endpoint->start_rx_fn))(eth->rx_endpoint); |
} |
} |
} |
|