From de0badf2c3aea5328936e583c842f58e80e56d62 Mon Sep 17 00:00:00 2001 From: Heinz Junkes Date: Sun, 6 Mar 2022 18:01:30 +0100 Subject: [PATCH] if_mve: Add MV643XX Ethernet driver Update #4345. --- buildset/default.ini | 1 + libbsd.py | 19 + rtemsbsd/include/bsp/mv643xx_eth.h | 397 +++++ rtemsbsd/include/bsp/nexus-devices.h | 5 + rtemsbsd/sys/dev/mve/if_mve.c | 2389 ++++++++++++++++++++++++++ rtemsbsd/sys/dev/mve/if_mve_nexus.c | 935 ++++++++++ 6 files changed, 3746 insertions(+) create mode 100644 rtemsbsd/include/bsp/mv643xx_eth.h create mode 100644 rtemsbsd/sys/dev/mve/if_mve.c create mode 100644 rtemsbsd/sys/dev/mve/if_mve_nexus.c diff --git a/buildset/default.ini b/buildset/default.ini index 454cc74e..5f9b81a5 100644 --- a/buildset/default.ini +++ b/buildset/default.ini @@ -42,6 +42,7 @@ evdev = on fdt = on fs_nfs = on fs_nfsclient = on +if_mve = on imx = on in_cksum = on mdnsresponder = on diff --git a/libbsd.py b/libbsd.py index 7593e9ba..ce5ed995 100644 --- a/libbsd.py +++ b/libbsd.py @@ -816,6 +816,24 @@ class evdev(builder.Module): mm.generator['source']() ) +# +# MV643XX Ethernet driver +# +class if_mve(builder.Module): + + def __init__(self, manager): + super(if_mve, self).__init__(manager, type(self).__name__) + + def generate(self): + mm = self.manager + self.addRTEMSKernelSourceFiles( + [ + 'sys/dev/mve/if_mve.c', + 'sys/dev/mve/if_mve_nexus.c', + ], + mm.generator['source']() + ) + # # USB # @@ -5551,6 +5569,7 @@ def load(mm): mm.addModule(mmc_ti(mm)) mm.addModule(dev_input(mm)) mm.addModule(evdev(mm)) + mm.addModule(if_mve(mm)) mm.addModule(dev_usb(mm)) mm.addModule(dev_usb_controller(mm)) diff --git a/rtemsbsd/include/bsp/mv643xx_eth.h b/rtemsbsd/include/bsp/mv643xx_eth.h new file mode 100644 index 00000000..7d122cd6 --- /dev/null +++ b/rtemsbsd/include/bsp/mv643xx_eth.h @@ -0,0 +1,397 @@ +/* Acknowledgement: + * + * Valuable information for developing this driver was obtained + * from the linux open-source driver mv643xx_eth.c which was written + * by the following people and organizations: + * + * Matthew Dharm + * rabeeh@galileo.co.il + * PMC-Sierra, Inc., Manish Lachwani + * Ralf Baechle + * MontaVista Software, Inc., Dale Farnsworth + * Steven J. Hill / + * + * Note however, that in spite of the identical name of this file + * (and some of the symbols used herein) this file provides a + * new implementation and is the original work by the author. + */ + +/* + * Authorship + * ---------- + * This software (mv643xx ethernet driver for RTEMS) was + * created by Till Straumann , 2005-2007, + * Stanford Linear Accelerator Center, Stanford University. + * + * Acknowledgement of sponsorship + * ------------------------------ + * The 'mv643xx ethernet driver for RTEMS' was produced by + * the Stanford Linear Accelerator Center, Stanford University, + * under Contract DE-AC03-76SFO0515 with the Department of Energy. + * + * Government disclaimer of liability + * ---------------------------------- + * Neither the United States nor the United States Department of Energy, + * nor any of their employees, makes any warranty, express or implied, or + * assumes any legal liability or responsibility for the accuracy, + * completeness, or usefulness of any data, apparatus, product, or process + * disclosed, or represents that its use would not infringe privately owned + * rights. + * + * Stanford disclaimer of liability + * -------------------------------- + * Stanford University makes no representations or warranties, express or + * implied, nor assumes any liability for the use of this software. + * + * Stanford disclaimer of copyright + * -------------------------------- + * Stanford University, owner of the copyright, hereby disclaims its + * copyright and all other rights in this software. Hence, anyone may + * freely use it for any purpose without restriction. + * + * Maintenance of notices + * ---------------------- + * In the interest of clarity regarding the origin and status of this + * SLAC software, this and all the preceding Stanford University notices + * are to remain affixed to any copy or derivative of this software made + * or distributed by the recipient and are to be affixed to any copy of + * software made or distributed by the recipient that contains a copy or + * derivative of this software. + * + * ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03 + */ + +#ifndef MV643XX_LOWLEVEL_DRIVER_H +#define MV643XX_LOWLEVEL_DRIVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define MV643XXETH_NUM_DRIVER_SLOTS 2 + +struct mveth_private; + +/* Create interface. + * Allocates resources for descriptor rings and sets up the driver software structure. + * + * Arguments: + * unit: + * interface # (1..2). The interface must not be attached to BSD. + * + * driver_tid: + * optional driver task-id (can be retrieved with BSP_mve_get_tid()). + * Not used by the low-level driver. + * + * isr(isr_arg): + * user ISR. + * + * void (*cleanup_txbuf)(void *user_buf, void *cleanup_txbuf_arg, int error_on_tx_occurred): + * Pointer to user-supplied callback to release a buffer that had been sent + * by BSP_mve_send_buf() earlier. The callback is passed 'cleanup_txbuf_arg' + * and a flag indicating whether the send had been successful. + * The driver no longer accesses 'user_buf' after invoking this callback. + * CONTEXT: This callback is executed either by BSP_mve_swipe_tx() or + * BSP_mve_send_buf(), BSP_mve_init_hw(), BSP_mve_stop_hw() (the latter + * ones calling BSP_mve_swipe_tx()). + * void *cleanup_txbuf_arg: + * Closure argument that is passed on to 'cleanup_txbuf()' callback; + * + * void *(*alloc_rxbuf)(int *p_size, unsigned long *p_data_addr), + * Pointer to user-supplied callback to allocate a buffer for subsequent + * insertion into the RX ring by the driver. + * RETURNS: opaque handle to the buffer (which may be a more complex object + * such as an 'mbuf'). The handle is not used by the driver directly + * but passed back to the 'consume_rxbuf()' callback. + * Size of the available data area and pointer to buffer's data area + * in '*psize' and '*p_data_area', respectively. + * If no buffer is available, this routine should return NULL in which + * case the driver drops the last packet and re-uses the last buffer + * instead of handing it out to 'consume_rxbuf()'. + * CONTEXT: Called when initializing the RX ring (BSP_mve_init_hw()) or when + * swiping it (BSP_mve_swipe_rx()). + * + * + * void (*consume_rxbuf)(void *user_buf, void *consume_rxbuf_arg, int len); + * Pointer to user-supplied callback to pass a received buffer back to + * the user. The driver no longer accesses the buffer after invoking this + * callback (with 'len'>0, see below). 'user_buf' is the buffer handle + * previously generated by 'alloc_rxbuf()'. + * The callback is passed 'cleanup_rxbuf_arg' and a 'len' + * argument giving the number of bytes that were received. + * 'len' may be <=0 in which case the 'user_buf' argument is NULL. + * 'len' == 0 means that the last 'alloc_rxbuf()' had failed, + * 'len' < 0 indicates a receiver error. In both cases, the last packet + * was dropped/missed and the last buffer will be re-used by the driver. + * NOTE: the data are 'prefixed' with two bytes, i.e., the ethernet packet header + * is stored at offset 2 in the buffer's data area. Also, the FCS (4 bytes) + * is appended. 'len' accounts for both. + * CONTEXT: Called from BSP_mve_swipe_rx(). + * void *cleanup_rxbuf_arg: + * Closure argument that is passed on to 'consume_rxbuf()' callback; + * + * rx_ring_size, tx_ring_size: + * How many big to make the RX and TX descriptor rings. Note that the sizes + * may be 0 in which case a reasonable default will be used. + * If either ring size is < 0 then the RX or TX will be disabled. + * Note that it is illegal in this case to use BSP_mve_swipe_rx() or + * BSP_mve_swipe_tx(), respectively. + * + * irq_mask: + * Interrupts to enable. OR of flags from above. + * + */ + +/* Direct assignment of MVE flags to user API relies on irqs and x-irqs not overlapping */ +#define MV643XX_ETH_IRQ_RX_DONE (1<<2) +#define MV643XX_ETH_EXT_IRQ_TX_DONE (1<<0) +#define MV643XX_ETH_EXT_IRQ_LINK_CHG (1<<16) + +struct mveth_private * +BSP_mve_create( + int unit, + rtems_id tid, + void (*isr)(void*isr_arg), + void *isr_arg, + void (*cleanup_txbuf)(void *user_buf, void *closure, int error_on_tx_occurred), + void *cleanup_txbuf_arg, + void *(*alloc_rxbuf)(int *p_size, uintptr_t *p_data_addr), + void (*consume_rxbuf)(void *user_buf, void *closure, int len), + void *consume_rxbuf_arg, + int rx_ring_size, + int tx_ring_size, + int irq_mask +); + +/* + * Clear multicast hash filter. No multicast frames are accepted + * after executing this routine (unless the hardware was initialized + * in 'promiscuous' mode). + */ +void +BSP_mve_mcast_filter_clear(struct mveth_private *mp); + +/* + * Program multicast filter to accept all multicast frames + */ +void +BSP_mve_mcast_filter_accept_all(struct mveth_private *mp); + +/* + * Add a MAC address to the multicast filter. + * Existing entries are not changed but note that + * the filter is imperfect, i.e., multiple MAC addresses + * may alias to a single filter entry. Hence software + * filtering must still be performed. + * + * If a higher-level driver implements IP multicasting + * then multiple IP addresses may alias to the same MAC + * address. This driver maintains a 'reference-count' + * which is incremented every time the same MAC-address + * is passed to this routine; the address is only removed + * from the filter if BSP_mve_mcast_filter_accept_del() + * is called the same number of times (or by BSP_mve_mcast_filter_clear). + */ +void +BSP_mve_mcast_filter_accept_add(struct mveth_private *mp, unsigned char *enaddr); + +/* + * Remove a MAC address from the multicast filter. + * This routine decrements the reference count of the given + * MAC-address and removes it from the filter once the + * count reaches zero. + */ +void +BSP_mve_mcast_filter_accept_del(struct mveth_private *mp, unsigned char *enaddr); + + +/* Enable/disable promiscuous mode */ +void +BSP_mve_promisc_set(struct mveth_private *mp, int promisc); + +/* calls BSP_mve_stop_hw(), releases all resources and marks the interface + * as unused. + * RETURNS 0 on success, nonzero on failure. + * NOTE: the handle MUST NOT be used after successful execution of this + * routine. + */ +int +BSP_mve_detach(struct mveth_private *mp); + +/* Enqueue a buffer chain for transmission. + * + * RETURN: #bytes sent or -1 if there are not enough descriptors + * -2 is returned if the caller should attempt to + * repackage the chain into a smaller one. + * + * Comments: software cache-flushing incurs a penalty if the + * packet cannot be queued since it is flushed anyways. + * The algorithm is slightly more efficient in the normal + * case, though. + */ + +typedef struct MveEthBufIter { + void *hdl; /* opaque handle for the iterator implementor */ + void *data; /* data to be sent */ + size_t len; /* data size (in octets) */ + void *uptr; /* user-pointer to go into the descriptor; + note: cleanup_txbuf is only called for desriptors + where this field is non-NULL (for historical + reasons) */ +} MveEthBufIter; + +typedef MveEthBufIter *(*MveEthBufIterNext)(MveEthBufIter*); + +int +BSP_mve_send_buf_chain(struct mveth_private *mp, MveEthBufIterNext next, MveEthBufIter *it); + + +/* Legacy entry point to send a header + a buffer */ +int +BSP_mve_send_buf_raw(struct mveth_private *mp, void *head_p, int h_len, void *data_p, int d_len); + +/* Descriptor scavenger; cleanup the TX ring, passing all buffers + * that have been sent to the cleanup_tx() callback. + * This routine is called from BSP_mve_send_buf(), BSP_mve_init_hw(), + * BSP_mve_stop_hw(). + * + * RETURNS: number of buffers processed. + */ +int +BSP_mve_swipe_tx(struct mveth_private *mp); + +/* Retrieve all received buffers from the RX ring, replacing them + * by fresh ones (obtained from the alloc_rxbuf() callback). The + * received buffers are passed to consume_rxbuf(). + * + * RETURNS: number of buffers processed. + */ +int +BSP_mve_swipe_rx(struct mveth_private *mp); + +/* read ethernet address from hw to buffer */ +void +BSP_mve_read_eaddr(struct mveth_private *mp, unsigned char *oeaddr); + +/* Interrupt related routines */ + +/* Note: the BSP_mve_enable/disable/ack_irqs() entry points + * are deprecated. + * The newer API where the user passes a mask allows + * for more selective control. + */ + +/* Enable all supported interrupts at device */ +void +BSP_mve_enable_irqs(struct mveth_private *mp); + +/* Disable all supported interrupts at device */ +void +BSP_mve_disable_irqs(struct mveth_private *mp); + +/* Acknowledge (and clear) all supported interrupts. + * RETURNS: interrupts that were raised. + */ +uint32_t +BSP_mve_ack_irqs(struct mveth_private *mp); + +/* Enable interrupts included in 'mask' (leaving + * already enabled interrupts on). If the mask + * includes bits that were not passed to the 'setup' + * routine then the behavior is undefined. + */ +void +BSP_mve_enable_irq_mask(struct mveth_private *mp, uint32_t irq_mask); + +/* Disable interrupts included in 'mask' (leaving + * other ones that are currently enabled on). If the + * mask includes bits that were not passed to the 'setup' + * routine then the behavior is undefined. + * + * RETURNS: Bitmask of interrupts that were enabled upon entry + * into this routine. This can be used to restore the + * previous state. + */ +uint32_t +BSP_mve_disable_irq_mask(struct mveth_private *mp, uint32_t irq_mask); + +/* Acknowledge and clear selected interrupts. + * + * RETURNS: All pending interrupts. + * + * NOTE: Only pending interrupts contained in 'mask' + * are cleared. Others are left pending. + * + * This routine can be used to check for pending + * interrupts (pass mask == 0) or to clear all + * interrupts (pass mask == -1). + */ +uint32_t +BSP_mve_ack_irq_mask(struct mveth_private *mp, uint32_t mask); + +/* Retrieve the driver daemon TID that was passed to + * BSP_mve_setup(). + */ + +rtems_id +BSP_mve_get_tid(struct mveth_private *mp); + +/* Dump statistics to file (stdout if NULL) + * + * NOTE: this routine is not thread safe + */ +void +BSP_mve_dump_stats(struct mveth_private *mp, FILE *f); + +#define MV643XX_MEDIA_LINK (1<<0) +#define MV643XX_MEDIA_FD (1<<1) +#define MV643XX_MEDIA_10 (1<<8) +#define MV643XX_MEDIA_100 (2<<8) +#define MV643XX_MEDIA_1000 (3<<8) +#define MV643XX_MEDIA_SPEED_MSK (0xff00) + +/* + * Initialize interface hardware + * + * 'mp' handle obtained by from BSP_mve_setup(). + * 'promisc' whether to set promiscuous flag. + * 'enaddr' pointer to six bytes with MAC address. Read + * from the device if NULL. + * 'speed' current speed and link status as read from the PHY. + * + * Note: Multicast filters are cleared by this routine. + * However, in promiscuous mode the mcast filters + * are programmed to accept all multicast frames. + */ +void +BSP_mve_init_hw(struct mveth_private *mp, int promisc, unsigned char *enaddr, int speed); + +/* + * Update the serial port to a new speed (e.g., result of a PHY IRQ) + */ +void +BSP_mve_update_serial_port(struct mveth_private *mp, int speed); + +/* + * Shutdown hardware and clean out the rings + */ +void +BSP_mve_stop_hw(struct mveth_private *mp); + +unsigned +BSP_mve_mii_read(struct mveth_private *mp, unsigned addr); + +unsigned +BSP_mve_mii_write(struct mveth_private *mp, unsigned addr, unsigned v); + +unsigned +BSP_mve_mii_read_early(int port, unsigned addr); + +unsigned +BSP_mve_mii_write_early(int port, unsigned addr, unsigned v); + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/rtemsbsd/include/bsp/nexus-devices.h b/rtemsbsd/include/bsp/nexus-devices.h index 46df17b4..d39edf22 100644 --- a/rtemsbsd/include/bsp/nexus-devices.h +++ b/rtemsbsd/include/bsp/nexus-devices.h @@ -266,6 +266,11 @@ SYSINIT_DRIVER_REFERENCE(ukphy, miibus); RTEMS_BSD_DEFINE_NEXUS_DEVICE(fec, 0, 0, NULL); SYSINIT_DRIVER_REFERENCE(ukphy, miibus); +#elif defined(LIBBSP_BEATNIK_BSP_H) + +RTEMS_BSD_DEFINE_NEXUS_DEVICE(mve, 0, 0, NULL); +SYSINIT_DRIVER_REFERENCE(ukphy, miibus); + #elif defined(LIBBSP_POWERPC_MOTOROLA_POWERPC_BSP_H) RTEMS_BSD_DRIVER_PC_LEGACY; diff --git a/rtemsbsd/sys/dev/mve/if_mve.c b/rtemsbsd/sys/dev/mve/if_mve.c new file mode 100644 index 00000000..517484ee --- /dev/null +++ b/rtemsbsd/sys/dev/mve/if_mve.c @@ -0,0 +1,2389 @@ +/* RTEMS driver for the mv643xx gigabit ethernet chip */ + +/* Acknowledgement: + * + * Valuable information for developing this driver was obtained + * from the linux open-source driver mv643xx_eth.c which was written + * by the following people and organizations: + * + * Matthew Dharm + * rabeeh@galileo.co.il + * PMC-Sierra, Inc., Manish Lachwani + * Ralf Baechle + * MontaVista Software, Inc., Dale Farnsworth + * Steven J. Hill / + * + * Note however, that in spite of the identical name of this file + * (and some of the symbols used herein) this file provides a + * new implementation and is the original work by the author. + */ + +/* + * Authorship + * ---------- + * This software (mv643xx ethernet driver for RTEMS) was + * created by Till Straumann , 2005-2007, + * Stanford Linear Accelerator Center, Stanford University. + * + * Acknowledgement of sponsorship + * ------------------------------ + * The 'mv643xx ethernet driver for RTEMS' was produced by + * the Stanford Linear Accelerator Center, Stanford University, + * under Contract DE-AC03-76SFO0515 with the Department of Energy. + * + * Government disclaimer of liability + * ---------------------------------- + * Neither the United States nor the United States Department of Energy, + * nor any of their employees, makes any warranty, express or implied, or + * assumes any legal liability or responsibility for the accuracy, + * completeness, or usefulness of any data, apparatus, product, or process + * disclosed, or represents that its use would not infringe privately owned + * rights. + * + * Stanford disclaimer of liability + * -------------------------------- + * Stanford University makes no representations or warranties, express or + * implied, nor assumes any liability for the use of this software. + * + * Stanford disclaimer of copyright + * -------------------------------- + * Stanford University, owner of the copyright, hereby disclaims its + * copyright and all other rights in this software. Hence, anyone may + * freely use it for any purpose without restriction. + * + * Maintenance of notices + * ---------------------- + * In the interest of clarity regarding the origin and status of this + * SLAC software, this and all the preceding Stanford University notices + * are to remain affixed to any copy or derivative of this software made + * or distributed by the recipient and are to be affixed to any copy of + * software made or distributed by the recipient that contains a copy or + * derivative of this software. + * + * ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03 + */ + +/* + * NOTE: Some register (e.g., the SMI register) are SHARED among the + * three devices. Concurrent access protection is provided by + * the global networking semaphore. + * If other drivers are running on a subset of IFs then proper + * locking of all shared registers must be implemented! + * + * Some things I learned about this hardware can be found + * further down... + */ + +/* +#ifndef KERNEL +#define KERNEL +#endif +#ifndef _KERNEL +#define _KERNEL +#endif +*/ + +#include + +#ifdef LIBBSP_BEATNIK_BSP_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* CONFIGURABLE PARAMETERS */ + +/* Enable Hardware Snooping; if this is disabled (undefined), + * cache coherency is maintained by software. + */ +#undef ENABLE_HW_SNOOPING + +/* Compile-time debugging features */ + +/* Enable paranoia assertions and checks; reduce # of descriptors to minimum for stressing */ +#define MVETH_TESTING + +/* Enable debugging messages and some support routines (dump rings etc.) */ +#undef MVETH_DEBUG + +#define TX_NUM_TAG_SLOTS 1 /* leave room for tag; must not be 0 */ + +/* This is REAL; chip reads from 64-bit down-aligned buffer + * if the buffer size is < 8 !!! for buffer sizes 8 and upwards + * alignment is not an issue. This was verified using the + * 'mve_smallbuf_test.c' + */ +#define ENABLE_TX_WORKAROUND_8_BYTE_PROBLEM + +/* Chip register configuration values */ +#define MVETH_PORT_CONFIG_VAL (0 \ + | MV643XX_ETH_DFLT_RX_Q(0) \ + | MV643XX_ETH_DFLT_RX_ARP_Q(0) \ + | MV643XX_ETH_DFLT_RX_TCP_Q(0) \ + | MV643XX_ETH_DFLT_RX_UDP_Q(0) \ + | MV643XX_ETH_DFLT_RX_BPDU_Q(0) \ + ) + + +#define MVETH_PORT_XTEND_CONFIG_VAL 0 + +#ifdef OLDCONFIGVAL +#define MVETH_SERIAL_CTRL_CONFIG_VAL (0 \ + | MV643XX_ETH_FORCE_LINK_PASS \ + | MV643XX_ETH_DISABLE_AUTO_NEG_FOR_FLOWCTL \ + | MV643XX_ETH_ADVERTISE_SYMMETRIC_FLOWCTL \ + | MV643XX_ETH_BIT9_UNKNOWN \ + | MV643XX_ETH_FORCE_LINK_FAIL_DISABLE \ + | MV643XX_ETH_SC_MAX_RX_1552 \ + | MV643XX_ETH_SET_FULL_DUPLEX \ + | MV643XX_ETH_ENBL_FLOWCTL_TX_RX_IN_FD \ + ) +#endif +/* If we enable autoneg (duplex, speed, ...) then it seems + * that the chip automatically updates link settings + * (correct link settings are reflected in PORT_STATUS_R). + * However, when we disable aneg in the PHY then things + * can get messed up and the port doesn't work anymore. + * => we follow the linux driver in disabling all aneg + * in the serial config reg. and manually updating the + * speed & duplex bits when the phy link status changes. + * FIXME: don't know what to do about pause/flow-ctrl. + * It is best to just use ANEG anyways!!! + */ +#define MVETH_SERIAL_CTRL_CONFIG_VAL (0 \ + | MV643XX_ETH_DISABLE_AUTO_NEG_FOR_DUPLEX \ + | MV643XX_ETH_DISABLE_AUTO_NEG_FOR_FLOWCTL \ + | MV643XX_ETH_ADVERTISE_SYMMETRIC_FLOWCTL \ + | MV643XX_ETH_BIT9_UNKNOWN \ + | MV643XX_ETH_FORCE_LINK_FAIL_DISABLE \ + | MV643XX_ETH_DISABLE_AUTO_NEG_SPEED_GMII \ + | MV643XX_ETH_SC_MAX_RX_1552 \ + ) + +#define MVETH_SERIAL_CTRL_CONFIG_MSK (0 \ + | MV643XX_ETH_SERIAL_PORT_ENBL \ + | MV643XX_ETH_FORCE_LINK_PASS \ + | MV643XX_ETH_SC_MAX_RX_MASK \ + ) + + +#ifdef __PPC__ +#define MVETH_SDMA_CONFIG_VAL (0 \ + | MV643XX_ETH_RX_BURST_SZ_4_64BIT \ + | MV643XX_ETH_TX_BURST_SZ_4_64BIT \ + ) +#else +#define MVETH_SDMA_CONFIG_VAL (0 \ + | MV643XX_ETH_RX_BURST_SZ_16_64BIT \ + | MV643XX_ETH_TX_BURST_SZ_16_64BIT \ + ) +#endif + +/* minimal frame size we accept */ +#define MVETH_MIN_FRAMSZ_CONFIG_VAL 40 + +/* END OF CONFIGURABLE SECTION */ + +/* + * Here's stuff I learned about this chip: + * + * + * RX interrupt flags: + * + * broadcast packet RX: 0x00000005 + * last buf: 0x00000c05 + * overrun: 0x00000c00 + * unicast packet RX: 0x00000005 + * bad CRC received: 0x00000005 + * + * clearing 0x00000004 -> clears 0x00000001 + * clearing 0x00000400 -> clears 0x00000800 + * + * --> 0x0801 are probably some sort of summary bits. + * + * TX interrupt flags: + * + * broadcast packet in 1 buf: xcause: 0x00000001 (cause 0x00080000) + * into disconn. link: " " + * + * in some cases, I observed xcause: 0x00000101 (reason for 0x100 unknown + * but the linux driver accepts it also). + * + * + * Here a few more ugly things about this piece of hardware I learned + * (painfully, painfully; spending many many hours & nights :-() + * + * a) Especially in the case of 'chained' descriptors, the DMA keeps + * clobbering 'cmd_sts' long after it cleared the OWNership flag!!! + * Only after the whole chain is processed (OWN cleared on the + * last descriptor) it is safe to change cmd_sts. + * However, in the case of hardware snooping I found that the + * last descriptor in chain has its cmd_sts still clobbered *after* + * checking ownership!, I.e., + * if ( ! OWN & cmd_sts ) { + * cmd_sts = 0; + * } + * --> sometimes, cmd_sts is STILL != 0 here + * + * b) Sometimes, the OWNership flag is *not cleared*. + * + * c) Weird things happen if the chip finds a descriptor with 'OWN' + * still set (i.e., not properly loaded), i.e., corrupted packets + * are sent [with OK checksum since the chip calculates it]. + * + * Combine a+b+c and we end up with a real mess. + * + * The fact that the chip doesn't reliably reset OWN and that OTOH, + * it can't be reliably reset by the driver and still, the chip needs + * it for proper communication doesn't make things easy... + * + * Here the basic workarounds: + * + * - In addition to check OWN, the scavenger compares the "currently + * served desc" register to the descriptor it tries to recover and + * ignores OWN if they do not match. Hope this is OK. + * Otherwise, we could scan the list of used descriptors and proceed + * recycling descriptors if we find a !OWNed one behind the target... + * + * - Always keep an empty slot around to mark the end of the list of + * jobs. The driver clears the descriptor ahead when enqueueing a new + * packet. + */ + +#define DRVNAME "mve" +#define MAX_NUM_SLOTS 3 + +#if MV643XXETH_NUM_DRIVER_SLOTS > MAX_NUM_SLOTS +#error "mv643xxeth: only MAX_NUM_SLOTS supported" +#endif + +#ifdef NDEBUG +#error "Driver uses assert() statements with side-effects; MUST NOT define NDEBUG" +#endif + +#ifdef MVETH_DEBUG +#define STATIC +#else +#define STATIC static +#endif + +#define TX_AVAILABLE_RING_SIZE(mp) ((mp)->xbuf_count - (TX_NUM_TAG_SLOTS)) + +/* macros for ring alignment; proper alignment is a hardware req; . */ + +#ifdef ENABLE_HW_SNOOPING + +#define RING_ALIGNMENT 16 +/* rx buffers must be 64-bit aligned (chip requirement) */ +#define RX_BUF_ALIGNMENT 8 + +#else /* ENABLE_HW_SNOOPING */ + +/* Software cache management */ + +#ifndef __PPC__ +#error "Dont' know how to deal with cache on this CPU architecture" +#endif + +/* Ring entries are 32 bytes; coherency-critical chunks are 16 -> software coherency + * management works for cache line sizes of 16 and 32 bytes only. If the line size + * is bigger, the descriptors could be padded... + */ +#if PPC_CACHE_ALIGMENT != 16 && PPC_CACHE_ALIGNMENT != 32 +#error "Cache line size must be 16 or 32" +#else +#define RING_ALIGNMENT PPC_CACHE_ALIGNMENT +#define RX_BUF_ALIGNMENT PPC_CACHE_ALIGNMENT +#endif + +#endif /* ENABLE_HW_SNOOPING */ + + +/* HELPER MACROS */ + +/* Align base to alignment 'a' */ +#define MV643XX_ALIGN(b, a) ((((uint32_t)(b)) + (a)-1) & (~((a)-1))) + +#define NOOP() do {} while(0) + +/* Function like macros */ +#define MV_READ(off) \ + ld_le32((volatile uint32_t *)(BSP_MV64x60_BASE + (off))) +#define MV_WRITE(off, data) \ + st_le32((volatile uint32_t *)(BSP_MV64x60_BASE + (off)), ((unsigned)data)) + + +/* ENET window mapped 1:1 to CPU addresses by our BSP/MotLoad + * -- if this is changed, we should think about caching the 'next' and 'buf' pointers. + */ +#define CPUADDR2ENET(a) ((Dma_addr_t)(a)) +#define ENET2CPUADDR(a) (a) + +#if 1 /* Whether to automatically try to reclaim descriptors when enqueueing new packets */ +#define MVETH_CLEAN_ON_SEND(mp) (BSP_mve_swipe_tx(mp)) +#else +#define MVETH_CLEAN_ON_SEND(mp) (-1) +#endif + +#define NEXT_TXD(d) (d)->next +#define NEXT_RXD(d) (d)->next + +/* REGISTER AND DESCRIPTOR OFFSET AND BIT DEFINITIONS */ + +/* Descriptor Definitions */ +/* Rx descriptor */ +#define RDESC_ERROR (1<< 0) /* Error summary */ + +/* Error code (bit 1&2) is only valid if summary bit is set */ +#define RDESC_CRC_ERROR ( 1) +#define RDESC_OVERRUN_ERROR ( 3) +#define RDESC_MAX_FRAMELENGTH_ERROR ( 5) +#define RDESC_RESOURCE_ERROR ( 7) + +#define RDESC_LAST (1<<26) /* Last Descriptor */ +#define RDESC_FRST (1<<27) /* First Descriptor */ +#define RDESC_INT_ENA (1<<29) /* Enable Interrupts */ +#define RDESC_DMA_OWNED (1<<31) + +/* Tx descriptor */ +#define TDESC_ERROR (1<< 0) /* Error summary */ +#define TDESC_ZERO_PAD (1<<19) +#define TDESC_LAST (1<<20) /* Last Descriptor */ +#define TDESC_FRST (1<<21) /* First Descriptor */ +#define TDESC_GEN_CRC (1<<22) +#define TDESC_INT_ENA (1<<23) /* Enable Interrupts */ +#define TDESC_DMA_OWNED (1<<31) + + + +/* Register Definitions */ +#define MV643XX_ETH_PHY_ADDR_R (0x2000) +#define MV643XX_ETH_SMI_R (0x2004) +#define MV643XX_ETH_SMI_BUSY (1<<28) +#define MV643XX_ETH_SMI_VALID (1<<27) +#define MV643XX_ETH_SMI_OP_WR (0<<26) +#define MV643XX_ETH_SMI_OP_RD (1<<26) + +#define MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(port) (0x2448 + ((port)<<10)) +#define MV643XX_ETH_TX_START(queue) (0x0001<<(queue)) +#define MV643XX_ETH_TX_STOP(queue) (0x0100<<(queue)) +#define MV643XX_ETH_TX_START_M(queues) ((queues)&0xff) +#define MV643XX_ETH_TX_STOP_M(queues) (((queues)&0xff)<<8) +#define MV643XX_ETH_TX_STOP_ALL (0xff00) +#define MV643XX_ETH_TX_ANY_RUNNING (0x00ff) + +#define MV643XX_ETH_RECEIVE_QUEUE_COMMAND_R(port) (0x2680 + ((port)<<10)) +#define MV643XX_ETH_RX_START(queue) (0x0001<<(queue)) +#define MV643XX_ETH_RX_STOP(queue) (0x0100<<(queue)) +#define MV643XX_ETH_RX_STOP_ALL (0xff00) +#define MV643XX_ETH_RX_ANY_RUNNING (0x00ff) + +#define MV643XX_ETH_CURRENT_SERVED_TX_DESC(port) (0x2684 + ((port)<<10)) + +/* The chip puts the ethernet header at offset 2 into the buffer so + * that the payload is aligned + */ +#define ETH_RX_OFFSET 2 +#define ETH_CRC_LEN 4 /* strip FCS at end of packet */ + + +#define MV643XX_ETH_INTERRUPT_CAUSE_R(port) (0x2460 + ((port)<<10)) +/* not fully understood; RX seems to raise 0x0005 or 0x0c05 if last buffer is filled and 0x0c00 + * if there are no buffers + */ +#define MV643XX_ETH_ALL_IRQS (0x07ffffff) +#define MV643XX_ETH_KNOWN_IRQS (0x00080c07) +#define MV643XX_ETH_IRQ_EXT_ENA (1<<1) +/* defined in public header +#define MV643XX_ETH_IRQ_RX_DONE (1<<2) + */ +#define MV643XX_ETH_IRQ_RX_NO_DESC (1<<10) +#define MV643XX_ETH_TX_Q_N_END(n) (1<<((n)+19)) +/* We just use queue 0 */ +#define MV643XX_ETH_TX_Q_END MV643XX_ETH_TX_Q_N_END(0) + +#define MV643XX_ETH_INTERRUPT_EXTEND_CAUSE_R(port) (0x2464 + ((port)<<10)) +/* not fully understood; TX seems to raise 0x0001 and link change is 0x00010000 + * if there are no buffers + */ +#define MV643XX_ETH_ALL_EXT_IRQS (0x0011ffff) +/* Recent (2013) linux driver mentions both bits 0x00110000 as 'link change' causes */ +#define MV643XX_ETH_KNOWN_EXT_IRQS (0x00110101) +/* TX queues 0..7 */ +#define MV643XX_ETH_EXT_IRQ_TXN_DONE(n) (1<<(n)) +/* We just use queue 0 */ +/* defined in public header +#define MV643XX_ETH_EXT_IRQ_TX_DONE MV643XX_ETH_EXT_IRQ_TXN_DONE(0) +#define MV643XX_ETH_EXT_IRQ_LINK_CHG (1<<16) + */ +#define MV643XX_ETH_INTERRUPT_ENBL_R(port) (0x2468 + ((port)<<10)) +#define MV643XX_ETH_INTERRUPT_EXTEND_ENBL_R(port) (0x246c + ((port)<<10)) + +/* port configuration */ +#define MV643XX_ETH_PORT_CONFIG_R(port) (0x2400 + ((port)<<10)) +#define MV643XX_ETH_UNICAST_PROMISC_MODE (1<<0) +#define MV643XX_ETH_DFLT_RX_Q(q) ((q)<<1) +#define MV643XX_ETH_DFLT_RX_ARP_Q(q) ((q)<<4) +#define MV643XX_ETH_REJ_BCAST_IF_NOT_IP_OR_ARP (1<<7) +#define MV643XX_ETH_REJ_BCAST_IF_IP (1<<8) +#define MV643XX_ETH_REJ_BCAST_IF_ARP (1<<9) +#define MV643XX_ETH_TX_AM_NO_UPDATE_ERR_SUMMARY (1<<12) +#define MV643XX_ETH_CAPTURE_TCP_FRAMES_ENBL (1<<14) +#define MV643XX_ETH_CAPTURE_UDP_FRAMES_ENBL (1<<15) +#define MV643XX_ETH_DFLT_RX_TCP_Q(q) ((q)<<16) +#define MV643XX_ETH_DFLT_RX_UDP_Q(q) ((q)<<19) +#define MV643XX_ETH_DFLT_RX_BPDU_Q(q) ((q)<<22) + + + +#define MV643XX_ETH_PORT_CONFIG_XTEND_R(port) (0x2404 + ((port)<<10)) +#define MV643XX_ETH_CLASSIFY_ENBL (1<<0) +#define MV643XX_ETH_SPAN_BPDU_PACKETS_AS_NORMAL (0<<1) +#define MV643XX_ETH_SPAN_BPDU_PACKETS_2_Q7 (1<<1) +#define MV643XX_ETH_PARTITION_DISBL (0<<2) +#define MV643XX_ETH_PARTITION_ENBL (1<<2) + +#define MV643XX_ETH_SDMA_CONFIG_R(port) (0x241c + ((port)<<10)) +#define MV643XX_ETH_SDMA_RIFB (1<<0) +#define MV643XX_ETH_RX_BURST_SZ_1_64BIT (0<<1) +#define MV643XX_ETH_RX_BURST_SZ_2_64BIT (1<<1) +#define MV643XX_ETH_RX_BURST_SZ_4_64BIT (2<<1) +#define MV643XX_ETH_RX_BURST_SZ_8_64BIT (3<<1) +#define MV643XX_ETH_RX_BURST_SZ_16_64BIT (4<<1) +#define MV643XX_ETH_SMDA_BLM_RX_NO_SWAP (1<<4) +#define MV643XX_ETH_SMDA_BLM_TX_NO_SWAP (1<<5) +#define MV643XX_ETH_SMDA_DESC_BYTE_SWAP (1<<6) +#define MV643XX_ETH_TX_BURST_SZ_1_64BIT (0<<22) +#define MV643XX_ETH_TX_BURST_SZ_2_64BIT (1<<22) +#define MV643XX_ETH_TX_BURST_SZ_4_64BIT (2<<22) +#define MV643XX_ETH_TX_BURST_SZ_8_64BIT (3<<22) +#define MV643XX_ETH_TX_BURST_SZ_16_64BIT (4<<22) + +#define MV643XX_ETH_RX_MIN_FRAME_SIZE_R(port) (0x247c + ((port)<<10)) + + +#define MV643XX_ETH_SERIAL_CONTROL_R(port) (0x243c + ((port)<<10)) +#define MV643XX_ETH_SERIAL_PORT_ENBL (1<<0) /* Enable serial port */ +#define MV643XX_ETH_FORCE_LINK_PASS (1<<1) +#define MV643XX_ETH_DISABLE_AUTO_NEG_FOR_DUPLEX (1<<2) +#define MV643XX_ETH_DISABLE_AUTO_NEG_FOR_FLOWCTL (1<<3) +#define MV643XX_ETH_ADVERTISE_SYMMETRIC_FLOWCTL (1<<4) +#define MV643XX_ETH_FORCE_FC_MODE_TX_PAUSE_DIS (1<<5) +#define MV643XX_ETH_FORCE_BP_MODE_JAM_TX (1<<7) +#define MV643XX_ETH_FORCE_BP_MODE_JAM_TX_ON_RX_ERR (1<<8) +#define MV643XX_ETH_BIT9_UNKNOWN (1<<9) /* unknown purpose; linux sets this */ +#define MV643XX_ETH_FORCE_LINK_FAIL_DISABLE (1<<10) +#define MV643XX_ETH_RETRANSMIT_FOREVER (1<<11) /* limit to 16 attempts if clear */ +#define MV643XX_ETH_DISABLE_AUTO_NEG_SPEED_GMII (1<<13) +#define MV643XX_ETH_DTE_ADV_1 (1<<14) +#define MV643XX_ETH_AUTO_NEG_BYPASS_ENBL (1<<15) +#define MV643XX_ETH_RESTART_AUTO_NEG (1<<16) +#define MV643XX_ETH_SC_MAX_RX_1518 (0<<17) /* Limit RX packet size */ +#define MV643XX_ETH_SC_MAX_RX_1522 (1<<17) /* Limit RX packet size */ +#define MV643XX_ETH_SC_MAX_RX_1552 (2<<17) /* Limit RX packet size */ +#define MV643XX_ETH_SC_MAX_RX_9022 (3<<17) /* Limit RX packet size */ +#define MV643XX_ETH_SC_MAX_RX_9192 (4<<17) /* Limit RX packet size */ +#define MV643XX_ETH_SC_MAX_RX_9700 (5<<17) /* Limit RX packet size */ +#define MV643XX_ETH_SC_MAX_RX_MASK (7<<17) /* bitmask */ +#define MV643XX_ETH_SET_EXT_LOOPBACK (1<<20) +#define MV643XX_ETH_SET_FULL_DUPLEX (1<<21) +#define MV643XX_ETH_ENBL_FLOWCTL_TX_RX_IN_FD (1<<22) /* enable flow ctrl on rx and tx in full-duplex */ +#define MV643XX_ETH_SET_GMII_SPEED_1000 (1<<23) /* 10/100 if clear */ +#define MV643XX_ETH_SET_MII_SPEED_100 (1<<24) /* 10 if clear */ + +#define MV643XX_ETH_PORT_STATUS_R(port) (0x2444 + ((port)<<10)) + +#define MV643XX_ETH_PORT_STATUS_MODE_10_BIT (1<<0) +#define MV643XX_ETH_PORT_STATUS_LINK_UP (1<<1) +#define MV643XX_ETH_PORT_STATUS_FDX (1<<2) +#define MV643XX_ETH_PORT_STATUS_FC (1<<3) +#define MV643XX_ETH_PORT_STATUS_1000 (1<<4) +#define MV643XX_ETH_PORT_STATUS_100 (1<<5) +/* PSR bit 6 unknown */ +#define MV643XX_ETH_PORT_STATUS_TX_IN_PROGRESS (1<<7) +#define MV643XX_ETH_PORT_STATUS_ANEG_BYPASSED (1<<8) +#define MV643XX_ETH_PORT_STATUS_PARTITION (1<<9) +#define MV643XX_ETH_PORT_STATUS_TX_FIFO_EMPTY (1<<10) + +#define MV643XX_ETH_MIB_COUNTERS(port) (0x3000 + ((port)<<7)) +#define MV643XX_ETH_NUM_MIB_COUNTERS 32 + +#define MV643XX_ETH_MIB_GOOD_OCTS_RCVD_LO (0) +#define MV643XX_ETH_MIB_GOOD_OCTS_RCVD_HI (1<<2) +#define MV643XX_ETH_MIB_BAD_OCTS_RCVD (2<<2) +#define MV643XX_ETH_MIB_INTERNAL_MAC_TX_ERR (3<<2) +#define MV643XX_ETH_MIB_GOOD_FRAMES_RCVD (4<<2) +#define MV643XX_ETH_MIB_BAD_FRAMES_RCVD (5<<2) +#define MV643XX_ETH_MIB_BCAST_FRAMES_RCVD (6<<2) +#define MV643XX_ETH_MIB_MCAST_FRAMES_RCVD (7<<2) +#define MV643XX_ETH_MIB_FRAMES_64_OCTS (8<<2) +#define MV643XX_ETH_MIB_FRAMES_65_127_OCTS (9<<2) +#define MV643XX_ETH_MIB_FRAMES_128_255_OCTS (10<<2) +#define MV643XX_ETH_MIB_FRAMES_256_511_OCTS (11<<2) +#define MV643XX_ETH_MIB_FRAMES_512_1023_OCTS (12<<2) +#define MV643XX_ETH_MIB_FRAMES_1024_MAX_OCTS (13<<2) +#define MV643XX_ETH_MIB_GOOD_OCTS_SENT_LO (14<<2) +#define MV643XX_ETH_MIB_GOOD_OCTS_SENT_HI (15<<2) +#define MV643XX_ETH_MIB_GOOD_FRAMES_SENT (16<<2) +#define MV643XX_ETH_MIB_EXCESSIVE_COLL (17<<2) +#define MV643XX_ETH_MIB_MCAST_FRAMES_SENT (18<<2) +#define MV643XX_ETH_MIB_BCAST_FRAMES_SENT (19<<2) +#define MV643XX_ETH_MIB_UNREC_MAC_CTRL_RCVD (20<<2) +#define MV643XX_ETH_MIB_FC_SENT (21<<2) +#define MV643XX_ETH_MIB_GOOD_FC_RCVD (22<<2) +#define MV643XX_ETH_MIB_BAD_FC_RCVD (23<<2) +#define MV643XX_ETH_MIB_UNDERSIZE_RCVD (24<<2) +#define MV643XX_ETH_MIB_FRAGMENTS_RCVD (25<<2) +#define MV643XX_ETH_MIB_OVERSIZE_RCVD (26<<2) +#define MV643XX_ETH_MIB_JABBER_RCVD (27<<2) +#define MV643XX_ETH_MIB_MAC_RX_ERR (28<<2) +#define MV643XX_ETH_MIB_BAD_CRC_EVENT (29<<2) +#define MV643XX_ETH_MIB_COLL (30<<2) +#define MV643XX_ETH_MIB_LATE_COLL (31<<2) + +#define MV643XX_ETH_DA_FILTER_SPECL_MCAST_TBL(port) (0x3400+((port)<<10)) +#define MV643XX_ETH_DA_FILTER_OTHER_MCAST_TBL(port) (0x3500+((port)<<10)) +#define MV643XX_ETH_DA_FILTER_UNICAST_TBL(port) (0x3600+((port)<<10)) +#define MV643XX_ETH_NUM_MCAST_ENTRIES 64 +#define MV643XX_ETH_NUM_UNICAST_ENTRIES 4 + +#define MV643XX_ETH_BAR_0 (0x2200) +#define MV643XX_ETH_SIZE_R_0 (0x2204) +#define MV643XX_ETH_BAR_1 (0x2208) +#define MV643XX_ETH_SIZE_R_1 (0x220c) +#define MV643XX_ETH_BAR_2 (0x2210) +#define MV643XX_ETH_SIZE_R_2 (0x2214) +#define MV643XX_ETH_BAR_3 (0x2218) +#define MV643XX_ETH_SIZE_R_3 (0x221c) +#define MV643XX_ETH_BAR_4 (0x2220) +#define MV643XX_ETH_SIZE_R_4 (0x2224) +#define MV643XX_ETH_BAR_5 (0x2228) +#define MV643XX_ETH_SIZE_R_5 (0x222c) +#define MV643XX_ETH_NUM_BARS 6 + +/* Bits in the BAR reg to program cache snooping */ +#define MV64360_ENET2MEM_SNOOP_NONE 0x0000 +#define MV64360_ENET2MEM_SNOOP_WT 0x1000 +#define MV64360_ENET2MEM_SNOOP_WB 0x2000 +#define MV64360_ENET2MEM_SNOOP_MSK 0x3000 + + +#define MV643XX_ETH_BAR_ENBL_R (0x2290) +#define MV643XX_ETH_BAR_DISABLE(bar) (1<<(bar)) +#define MV643XX_ETH_BAR_DISBL_ALL 0x3f + +#define MV643XX_ETH_RX_Q0_CURRENT_DESC_PTR(port) (0x260c+((port)<<10)) +#define MV643XX_ETH_RX_Q1_CURRENT_DESC_PTR(port) (0x261c+((port)<<10)) +#define MV643XX_ETH_RX_Q2_CURRENT_DESC_PTR(port) (0x262c+((port)<<10)) +#define MV643XX_ETH_RX_Q3_CURRENT_DESC_PTR(port) (0x263c+((port)<<10)) +#define MV643XX_ETH_RX_Q4_CURRENT_DESC_PTR(port) (0x264c+((port)<<10)) +#define MV643XX_ETH_RX_Q5_CURRENT_DESC_PTR(port) (0x265c+((port)<<10)) +#define MV643XX_ETH_RX_Q6_CURRENT_DESC_PTR(port) (0x266c+((port)<<10)) +#define MV643XX_ETH_RX_Q7_CURRENT_DESC_PTR(port) (0x267c+((port)<<10)) + +#define MV643XX_ETH_TX_Q0_CURRENT_DESC_PTR(port) (0x26c0+((port)<<10)) +#define MV643XX_ETH_TX_Q1_CURRENT_DESC_PTR(port) (0x26c4+((port)<<10)) +#define MV643XX_ETH_TX_Q2_CURRENT_DESC_PTR(port) (0x26c8+((port)<<10)) +#define MV643XX_ETH_TX_Q3_CURRENT_DESC_PTR(port) (0x26cc+((port)<<10)) +#define MV643XX_ETH_TX_Q4_CURRENT_DESC_PTR(port) (0x26d0+((port)<<10)) +#define MV643XX_ETH_TX_Q5_CURRENT_DESC_PTR(port) (0x26d4+((port)<<10)) +#define MV643XX_ETH_TX_Q6_CURRENT_DESC_PTR(port) (0x26d8+((port)<<10)) +#define MV643XX_ETH_TX_Q7_CURRENT_DESC_PTR(port) (0x26dc+((port)<<10)) + +#define MV643XX_ETH_MAC_ADDR_LO(port) (0x2414+((port)<<10)) +#define MV643XX_ETH_MAC_ADDR_HI(port) (0x2418+((port)<<10)) + +/* TYPE DEFINITIONS */ + +/* just to make the purpose explicit; vars of this + * type may need CPU-dependent address translation, + * endian conversion etc. + */ +typedef uint32_t Dma_addr_t; + +typedef volatile struct mveth_rx_desc { +#ifndef __BIG_ENDIAN__ +#error "descriptor declaration not implemented for little endian machines" +#endif + uint16_t byte_cnt; + uint16_t buf_size; + uint32_t cmd_sts; /* control and status */ + Dma_addr_t next_desc_ptr; /* next descriptor (as seen from DMA) */ + Dma_addr_t buf_ptr; + /* fields below here are not used by the chip */ + void *u_buf; /* user buffer */ + volatile struct mveth_rx_desc *next; /* next descriptor (CPU address; next_desc_ptr is a DMA address) */ + uint32_t pad[2]; +} __attribute__(( aligned(RING_ALIGNMENT) )) MvEthRxDescRec, *MvEthRxDesc; + +typedef volatile struct mveth_tx_desc { +#ifndef __BIG_ENDIAN__ +#error "descriptor declaration not implemented for little endian machines" +#endif + uint16_t byte_cnt; + uint16_t l4i_chk; + uint32_t cmd_sts; /* control and status */ + Dma_addr_t next_desc_ptr; /* next descriptor (as seen from DMA) */ + Dma_addr_t buf_ptr; + /* fields below here are not used by the chip */ + uint32_t workaround[2]; /* use this space to work around the 8byte problem (is this real?) */ + void *u_buf; /* user buffer */ + volatile struct mveth_tx_desc *next; /* next descriptor (CPU address; next_desc_ptr is a DMA address) */ +} __attribute__(( aligned(RING_ALIGNMENT) )) MvEthTxDescRec, *MvEthTxDesc; + +/* Assume there are never more then 64k aliasing entries */ +typedef uint16_t Mc_Refcnt[MV643XX_ETH_NUM_MCAST_ENTRIES*4]; + +/* driver private data and bsdnet interface structure */ +struct mveth_private { + MvEthRxDesc rx_ring; /* pointers to aligned ring area */ + MvEthTxDesc tx_ring; /* pointers to aligned ring area */ + MvEthRxDesc ring_area; /* allocated ring area */ + int rbuf_count, xbuf_count; /* saved ring sizes from ifconfig */ + int port_num; + int phy; + MvEthRxDesc d_rx_t; /* tail of the RX ring; next received packet */ + MvEthTxDesc d_tx_t, d_tx_h; + uint32_t rx_desc_dma, tx_desc_dma; /* ring address as seen by DMA; (1:1 on this BSP) */ + int avail; + void (*isr)(void*); + void *isr_arg; + /* Callbacks to handle buffers */ + void (*cleanup_txbuf)(void*, void*, int); /* callback to cleanup TX buffer */ + void *cleanup_txbuf_arg; + void *(*alloc_rxbuf)(int *psize, uintptr_t *paddr); /* allocate RX buffer */ + void (*consume_rxbuf)(void*, void*, int); /* callback to consume RX buffer */ + void *consume_rxbuf_arg; + rtems_id tid; + uint32_t irq_mask; /* IRQs we use */ + uint32_t xirq_mask; + int promisc; + struct { + unsigned irqs; + unsigned maxchain; + unsigned repack; + unsigned packet; + unsigned idrops; /* no counter in core code */ + struct { + uint64_t good_octs_rcvd; /* 64-bit */ + uint32_t bad_octs_rcvd; + uint32_t internal_mac_tx_err; + uint32_t good_frames_rcvd; + uint32_t bad_frames_rcvd; + uint32_t bcast_frames_rcvd; + uint32_t mcast_frames_rcvd; + uint32_t frames_64_octs; + uint32_t frames_65_127_octs; + uint32_t frames_128_255_octs; + uint32_t frames_256_511_octs; + uint32_t frames_512_1023_octs; + uint32_t frames_1024_max_octs; + uint64_t good_octs_sent; /* 64-bit */ + uint32_t good_frames_sent; + uint32_t excessive_coll; + uint32_t mcast_frames_sent; + uint32_t bcast_frames_sent; + uint32_t unrec_mac_ctrl_rcvd; + uint32_t fc_sent; + uint32_t good_fc_rcvd; + uint32_t bad_fc_rcvd; + uint32_t undersize_rcvd; + uint32_t fragments_rcvd; + uint32_t oversize_rcvd; + uint32_t jabber_rcvd; + uint32_t mac_rx_err; + uint32_t bad_crc_event; + uint32_t coll; + uint32_t late_coll; + } mib; + } stats; + struct { + Mc_Refcnt specl, other; + } mc_refcnt; +}; + +/* GLOBAL VARIABLES */ + +/* Format strings for statistics messages */ +static const char *mibfmt[] = { + " GOOD_OCTS_RCVD: %"PRIu64"\n", + 0, + " BAD_OCTS_RCVD: %"PRIu32"\n", + " INTERNAL_MAC_TX_ERR: %"PRIu32"\n", + " GOOD_FRAMES_RCVD: %"PRIu32"\n", + " BAD_FRAMES_RCVD: %"PRIu32"\n", + " BCAST_FRAMES_RCVD: %"PRIu32"\n", + " MCAST_FRAMES_RCVD: %"PRIu32"\n", + " FRAMES_64_OCTS: %"PRIu32"\n", + " FRAMES_65_127_OCTS: %"PRIu32"\n", + " FRAMES_128_255_OCTS: %"PRIu32"\n", + " FRAMES_256_511_OCTS: %"PRIu32"\n", + " FRAMES_512_1023_OCTS:%"PRIu32"\n", + " FRAMES_1024_MAX_OCTS:%"PRIu32"\n", + " GOOD_OCTS_SENT: %"PRIu64"\n", + 0, + " GOOD_FRAMES_SENT: %"PRIu32"\n", + " EXCESSIVE_COLL: %"PRIu32"\n", + " MCAST_FRAMES_SENT: %"PRIu32"\n", + " BCAST_FRAMES_SENT: %"PRIu32"\n", + " UNREC_MAC_CTRL_RCVD: %"PRIu32"\n", + " FC_SENT: %"PRIu32"\n", + " GOOD_FC_RCVD: %"PRIu32"\n", + " BAD_FC_RCVD: %"PRIu32"\n", + " UNDERSIZE_RCVD: %"PRIu32"\n", + " FRAGMENTS_RCVD: %"PRIu32"\n", + " OVERSIZE_RCVD: %"PRIu32"\n", + " JABBER_RCVD: %"PRIu32"\n", + " MAC_RX_ERR: %"PRIu32"\n", + " BAD_CRC_EVENT: %"PRIu32"\n", + " COLL: %"PRIu32"\n", + " LATE_COLL: %"PRIu32"\n", +}; + +/* Interrupt Handler Connection */ + +/* forward decls + implementation for IRQ API funcs */ + +STATIC int +mveth_init_rx_desc_ring(struct mveth_private *mp); + +STATIC int +mveth_init_tx_desc_ring(struct mveth_private *mp); + +int +BSP_mve_dring_nonsync(struct mveth_private *mp); + +static void mveth_isr(rtems_irq_hdl_param unit); +static void noop(const rtems_irq_connect_data *unused) {} +static int noop1(const rtems_irq_connect_data *unused) { return 0; } + +static rtems_irq_connect_data irq_data[MAX_NUM_SLOTS] = { + { + BSP_IRQ_ETH0, + 0, + (rtems_irq_hdl_param)0, + noop, + noop, + noop1 + }, + { + BSP_IRQ_ETH1, + 0, + (rtems_irq_hdl_param)1, + noop, + noop, + noop1 + }, + { + BSP_IRQ_ETH2, + 0, + (rtems_irq_hdl_param)2, + noop, + noop, + noop1 + }, +}; + +/* LOW LEVEL SUPPORT ROUTINES */ + +/* Software Cache Coherency */ +#ifndef ENABLE_HW_SNOOPING +#ifndef __PPC__ +#error "Software cache coherency maintenance is not implemented for your CPU architecture" +#endif + +static inline unsigned INVAL_DESC(volatile void *d) +{ +typedef const char cache_line[PPC_CACHE_ALIGNMENT]; + asm volatile("dcbi 0, %1":"=m"(*(cache_line*)d):"r"(d)); + return (unsigned)d; /* so this can be used in comma expression */ +} + +static inline void FLUSH_DESC(volatile void *d) +{ +typedef const char cache_line[PPC_CACHE_ALIGNMENT]; + asm volatile("dcbf 0, %0"::"r"(d),"m"(*(cache_line*)d)); +} + +static inline void FLUSH_BARRIER(void) +{ + asm volatile("eieio"); +} + +/* RX buffers are always cache-line aligned + * ASSUMPTIONS: + * - 'addr' is cache aligned + * - len is a multiple >0 of cache lines + */ +static inline void INVAL_BUF(register uintptr_t addr, register int len) +{ +typedef char maxbuf[2048]; /* more than an ethernet packet */ + do { + len -= RX_BUF_ALIGNMENT; + asm volatile("dcbi %0, %1"::"b"(addr),"r"(len)); + } while (len > 0); + asm volatile("":"=m"(*(maxbuf*)addr)); +} + +/* Flushing TX buffers is a little bit trickier; we don't really know their + * alignment but *assume* adjacent addresses are covering 'ordinary' memory + * so that flushing them does no harm! + */ +static inline void FLUSH_BUF(register uintptr_t addr, register int len) +{ + asm volatile("":::"memory"); + len = MV643XX_ALIGN(len, RX_BUF_ALIGNMENT); + do { + asm volatile("dcbf %0, %1"::"b"(addr),"r"(len)); + len -= RX_BUF_ALIGNMENT; + } while ( len >= 0 ); +} + +#else /* hardware snooping enabled */ + +/* inline this to silence compiler warnings */ +static inline int INVAL_DESC(volatile void *d) +{ return 0; } + +#define FLUSH_DESC(d) NOOP() +#define INVAL_BUF(b,l) NOOP() +#define FLUSH_BUF(b,l) NOOP() +#define FLUSH_BARRIER() NOOP() + +#endif /* cache coherency support */ + +/* Synchronize memory access */ +#ifdef __PPC__ +static inline void membarrier(void) +{ + asm volatile("sync":::"memory"); +} +#else +#error "memory barrier instruction not defined (yet) for this CPU" +#endif + +/* Enable and disable interrupts at the device */ +static inline void +mveth_enable_irqs(struct mveth_private *mp, uint32_t mask) +{ +rtems_interrupt_level l; +uint32_t val; + rtems_interrupt_disable(l); + + val = MV_READ(MV643XX_ETH_INTERRUPT_ENBL_R(mp->port_num)); + val = (val | mask | MV643XX_ETH_IRQ_EXT_ENA) & mp->irq_mask; + + MV_WRITE(MV643XX_ETH_INTERRUPT_ENBL_R(mp->port_num), val); + + val = MV_READ(MV643XX_ETH_INTERRUPT_EXTEND_ENBL_R(mp->port_num)); + val = (val | mask) & mp->xirq_mask; + MV_WRITE(MV643XX_ETH_INTERRUPT_EXTEND_ENBL_R(mp->port_num), val); + + rtems_interrupt_enable(l); +} + +static inline uint32_t +mveth_disable_irqs(struct mveth_private *mp, uint32_t mask) +{ +rtems_interrupt_level l; +uint32_t val,xval,tmp; + rtems_interrupt_disable(l); + + val = MV_READ(MV643XX_ETH_INTERRUPT_ENBL_R(mp->port_num)); + tmp = ( (val & ~mask) | MV643XX_ETH_IRQ_EXT_ENA ) & mp->irq_mask; + MV_WRITE(MV643XX_ETH_INTERRUPT_ENBL_R(mp->port_num), tmp); + + xval = MV_READ(MV643XX_ETH_INTERRUPT_EXTEND_ENBL_R(mp->port_num)); + tmp = (xval & ~mask) & mp->xirq_mask; + MV_WRITE(MV643XX_ETH_INTERRUPT_EXTEND_ENBL_R(mp->port_num), tmp); + + rtems_interrupt_enable(l); + + return (val | xval); +} + +/* This should be safe even w/o turning off interrupts if multiple + * threads ack different bits in the cause register (and ignore + * other ones) since writing 'ones' into the cause register doesn't + * 'stick'. + */ + +static inline uint32_t +mveth_ack_irqs(struct mveth_private *mp, uint32_t mask) +{ +register uint32_t x,xe,p; +register uint32_t rval; + + p = mp->port_num; + /* Get cause */ + x = MV_READ(MV643XX_ETH_INTERRUPT_CAUSE_R(p)); + + /* Ack interrupts filtering the ones we're interested in */ + + /* Note: EXT_IRQ bit clears by itself if EXT interrupts are cleared */ + MV_WRITE(MV643XX_ETH_INTERRUPT_CAUSE_R(p), ~ (x & mp->irq_mask & mask)); + + /* linux driver tests 1<<1 as a summary bit for extended interrupts; + * the mv64360 seems to use 1<<19 for that purpose; for the moment, + * I just check both. + * Update: link status irq (1<<16 in xe) doesn't set (1<<19) in x! + */ + if ( 1 /* x & 2 */ ) + { + xe = MV_READ(MV643XX_ETH_INTERRUPT_EXTEND_CAUSE_R(p)); + + MV_WRITE(MV643XX_ETH_INTERRUPT_EXTEND_CAUSE_R(p), ~ (xe & mp->xirq_mask & mask)); + } else { + xe = 0; + } +#ifdef MVETH_TESTING + if ( ((x & MV643XX_ETH_ALL_IRQS) & ~MV643XX_ETH_KNOWN_IRQS) + || ((xe & MV643XX_ETH_ALL_EXT_IRQS) & ~MV643XX_ETH_KNOWN_EXT_IRQS) ) { + fprintf(stderr, "Unknown IRQs detected; leaving all disabled for debugging:\n"); + fprintf(stderr, "Cause reg was 0x%08x, ext cause 0x%08x\n", x, xe); +/* + mp->irq_mask = 0; + mp->xirq_mask = 0; +*/ + } +#endif + /* luckily, the extended and 'normal' interrupts we use don't overlap so + * we can just OR them into a single word + */ + rval = (xe & mp->xirq_mask) | (x & mp->irq_mask); + +#ifdef MVETH_DEBUG + printk(DRVNAME"%i: mveth_ack_irqs 0x%08x\n", rval); +#endif + return rval; +} + +static void mveth_isr(rtems_irq_hdl_param arg) +{ +struct mveth_private *mp = (struct mveth_private*) arg; + +#ifdef MVETH_DEBUG + printk(DRVNAME": mveth_isr\n"); +#endif + mp->stats.irqs++; + mp->isr(mp->isr_arg); +} + +static void +mveth_clear_mib_counters(struct mveth_private *mp) +{ +register int i; +register uint32_t b; + /* reading the counters resets them */ + b = MV643XX_ETH_MIB_COUNTERS(mp->port_num); + for (i=0; i< MV643XX_ETH_NUM_MIB_COUNTERS; i++, b+=4) + (void)MV_READ(b); +} + +/* Reading a MIB register also clears it. Hence we read the lo + * register first, then the hi one. Correct reading is guaranteed since + * the 'lo' register cannot overflow after it is read since it had + * been reset to 0. + */ +static unsigned long long +read_long_mib_counter(int port_num, int idx) +{ +unsigned long lo; +unsigned long long hi; + lo = MV_READ(MV643XX_ETH_MIB_COUNTERS(port_num)+(idx<<2)); + idx++; + hi = MV_READ(MV643XX_ETH_MIB_COUNTERS(port_num)+(idx<<2)); + return (hi<<32) | lo; +} + +static inline unsigned long +read_mib_counter(int port_num, int idx) +{ + return MV_READ(MV643XX_ETH_MIB_COUNTERS(port_num)+(idx<<2)); +} + + +/* write ethernet address from buffer to hardware (need to change unicast filter after this) */ +static void +mveth_write_eaddr(struct mveth_private *mp, unsigned char *eaddr) +{ +int i; +uint32_t x; + + /* build hi word */ + for (i=4,x=0; i; i--, eaddr++) { + x = (x<<8) | *eaddr; + } + MV_WRITE(MV643XX_ETH_MAC_ADDR_HI(mp->port_num), x); + + /* build lo word */ + for (i=2,x=0; i; i--, eaddr++) { + x = (x<<8) | *eaddr; + } + MV_WRITE(MV643XX_ETH_MAC_ADDR_LO(mp->port_num), x); +} + +static inline int +port2phy(int port) +{ + port &= 0x1f; + /* during early init we may not know the phy and we are given a port number instead! */ + return ( (MV_READ(MV643XX_ETH_PHY_ADDR_R) >> (5*port)) & 0x1f ); +} + +/* PHY/MII Interface + * + * Read/write a PHY register; + * + * NOTE: The SMI register is shared among the three devices. + * Protection is provided by the global networking semaphore. + * If non-bsd drivers are running on a subset of IFs proper + * locking of all shared registers must be implemented! + */ +static unsigned +do_mii_read(int phy, unsigned addr) +{ +unsigned v; +unsigned wc = 0; + + addr &= 0x1f; + + /* wait until not busy */ + do { + v = MV_READ(MV643XX_ETH_SMI_R); + wc++; + } while ( MV643XX_ETH_SMI_BUSY & v ); + + MV_WRITE(MV643XX_ETH_SMI_R, (addr <<21 ) | (phy<<16) | MV643XX_ETH_SMI_OP_RD ); + + do { + v = MV_READ(MV643XX_ETH_SMI_R); + wc++; + } while ( MV643XX_ETH_SMI_BUSY & v ); + + if (wc>0xffff) + wc = 0xffff; + return (wc<<16) | (v & 0xffff); +} + +unsigned +BSP_mve_mii_read(struct mveth_private *mp, unsigned addr) +{ +unsigned rval = do_mii_read(mp->phy, addr); +#ifdef MVETH_DEBUG + printk(DRVNAME": BSP_mve_mii_read(%d): 0x%08x\n", addr, rval); +#endif + return rval; +} + +unsigned +BSP_mve_mii_read_early(int port, unsigned addr) +{ + return do_mii_read(port2phy(port), addr); +} + +static unsigned +do_mii_write(int phy, unsigned addr, unsigned v) +{ +unsigned wc = 0; + + addr &= 0x1f; + v &= 0xffff; + + /* busywait is ugly but not preventing ISRs or high priority tasks from + * preempting us + */ + + /* wait until not busy */ + while ( MV643XX_ETH_SMI_BUSY & MV_READ(MV643XX_ETH_SMI_R) ) + wc++ /* wait */; + + MV_WRITE(MV643XX_ETH_SMI_R, (addr <<21 ) | (phy<<16) | MV643XX_ETH_SMI_OP_WR | v ); + + return wc; +} + +unsigned +BSP_mve_mii_write(struct mveth_private *mp, unsigned addr, unsigned v) +{ +#ifdef MVETH_DEBUG + printk(DRVNAME": BSP_mve_mii_write(%d): 0x%08x\n", addr, v); +#endif + return do_mii_write( mp->phy, addr, v ); +} + +unsigned +BSP_mve_mii_write_early(int port, unsigned addr, unsigned v) +{ + return do_mii_write(port2phy(port), addr, v); +} + + +/* MID-LAYER SUPPORT ROUTINES */ + +/* Start TX if descriptors are exhausted */ +static __inline__ void +mveth_start_tx(struct mveth_private *mp) +{ +uint32_t running; + if ( mp->avail <= 0 ) { + running = MV_READ(MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(mp->port_num)); + if ( ! (running & MV643XX_ETH_TX_START(0)) ) { + MV_WRITE(MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(mp->port_num), MV643XX_ETH_TX_START(0)); + } + } +} + +/* Stop TX and wait for the command queues to stop and the fifo to drain */ +static uint32_t +mveth_stop_tx(int port) +{ +uint32_t active_q; + + active_q = (MV_READ(MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(port)) & MV643XX_ETH_TX_ANY_RUNNING); + + if ( active_q ) { + /* Halt TX and wait for activity to stop */ + MV_WRITE(MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(port), MV643XX_ETH_TX_STOP_ALL); + while ( MV643XX_ETH_TX_ANY_RUNNING & MV_READ(MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(port)) ) + /* poll-wait */; + /* Wait for Tx FIFO to drain */ + while ( ! (MV643XX_ETH_PORT_STATUS_R(port) & MV643XX_ETH_PORT_STATUS_TX_FIFO_EMPTY) ) + /* poll-wait */; + } + + return active_q; +} + +void +BSP_mve_promisc_set(struct mveth_private *mp, int promisc) +{ +uint32_t v; + + v = MV_READ(MV643XX_ETH_PORT_CONFIG_R(mp->port_num)); + if ( (mp->promisc = promisc) ) + v |= MV643XX_ETH_UNICAST_PROMISC_MODE; + else + v &= ~MV643XX_ETH_UNICAST_PROMISC_MODE; + MV_WRITE(MV643XX_ETH_PORT_CONFIG_R(mp->port_num), v); +} + +/* update serial port settings from current link status */ + +void +BSP_mve_mcast_filter_clear(struct mveth_private *mp) +{ +int i; +register uint32_t s,o; +uint32_t v = mp->promisc ? 0x01010101 : 0x00000000; + s = MV643XX_ETH_DA_FILTER_SPECL_MCAST_TBL(mp->port_num); + o = MV643XX_ETH_DA_FILTER_OTHER_MCAST_TBL(mp->port_num); + for (i=0; imc_refcnt.specl)/sizeof(mp->mc_refcnt.specl[0]); i++) { + mp->mc_refcnt.specl[i] = 0; + mp->mc_refcnt.other[i] = 0; + } +} + +void +BSP_mve_mcast_filter_accept_all(struct mveth_private *mp) +{ +int i; +register uint32_t s,o; + s = MV643XX_ETH_DA_FILTER_SPECL_MCAST_TBL(mp->port_num); + o = MV643XX_ETH_DA_FILTER_OTHER_MCAST_TBL(mp->port_num); + for (i=0; imc_refcnt.specl)/sizeof(mp->mc_refcnt.specl[0]); i++) { + mp->mc_refcnt.specl[i]++; + mp->mc_refcnt.other[i]++; + } + } +} + +static void add_entry(uint32_t off, uint8_t hash, Mc_Refcnt *refcnt) +{ +uint32_t val; +uint32_t slot = hash & 0xfc; + + if ( 0 == (*refcnt)[hash]++ ) { + val = MV_READ(off+slot) | ( 1 << ((hash&3)<<3) ); + MV_WRITE(off+slot, val); + } +} + +static void del_entry(uint32_t off, uint8_t hash, Mc_Refcnt *refcnt) +{ +uint32_t val; +uint32_t slot = hash & 0xfc; + + if ( (*refcnt)[hash] > 0 && 0 == --(*refcnt)[hash] ) { + val = MV_READ(off+slot) & ~( 1 << ((hash&3)<<3) ); + MV_WRITE(off+slot, val); + } +} + +void +BSP_mve_mcast_filter_accept_add(struct mveth_private *mp, unsigned char *enaddr) +{ +uint32_t hash; +static const char spec[]={0x01,0x00,0x5e,0x00,0x00}; +static const char bcst[]={0xff,0xff,0xff,0xff,0xff,0xff}; +uint32_t tabl; +Mc_Refcnt *refcnt; + + if ( ! (0x01 & enaddr[0]) ) { + /* not a multicast address; ignore */ + return; + } + + if ( 0 == memcmp(enaddr, bcst, sizeof(bcst)) ) { + /* broadcast address; ignore */ + return; + } + + if ( 0 == memcmp(enaddr, spec, sizeof(spec)) ) { + hash = enaddr[5]; + tabl = MV643XX_ETH_DA_FILTER_SPECL_MCAST_TBL(mp->port_num); + refcnt = &mp->mc_refcnt.specl; + } else { + uint32_t test, mask; + int i; + /* algorithm used by linux driver */ + for ( hash=0, i=0; i<6; i++ ) { + hash = (hash ^ enaddr[i]) << 8; + for ( test=0x8000, mask=0x8380; test>0x0080; test>>=1, mask>>=1 ) { + if ( hash & test ) + hash ^= mask; + } + } + tabl = MV643XX_ETH_DA_FILTER_OTHER_MCAST_TBL(mp->port_num); + refcnt = &mp->mc_refcnt.other; + } + add_entry(tabl, hash, refcnt); +} + +void +BSP_mve_mcast_filter_accept_del(struct mveth_private *mp, unsigned char *enaddr) +{ +uint32_t hash; +static const char spec[]={0x01,0x00,0x5e,0x00,0x00}; +static const char bcst[]={0xff,0xff,0xff,0xff,0xff,0xff}; +uint32_t tabl; +Mc_Refcnt *refcnt; + + if ( ! (0x01 & enaddr[0]) ) { + /* not a multicast address; ignore */ + return; + } + + if ( 0 == memcmp(enaddr, bcst, sizeof(bcst)) ) { + /* broadcast address; ignore */ + return; + } + + if ( 0 == memcmp(enaddr, spec, sizeof(spec)) ) { + hash = enaddr[5]; + tabl = MV643XX_ETH_DA_FILTER_SPECL_MCAST_TBL(mp->port_num); + refcnt = &mp->mc_refcnt.specl; + } else { + uint32_t test, mask; + int i; + /* algorithm used by linux driver */ + for ( hash=0, i=0; i<6; i++ ) { + hash = (hash ^ enaddr[i]) << 8; + for ( test=0x8000, mask=0x8380; test>0x0080; test>>=1, mask>>=1 ) { + if ( hash & test ) + hash ^= mask; + } + } + tabl = MV643XX_ETH_DA_FILTER_OTHER_MCAST_TBL(mp->port_num); + refcnt = &mp->mc_refcnt.other; + } + del_entry(tabl, hash, refcnt); +} + +/* Clear all address filters (multi- and unicast) */ +static void +mveth_clear_addr_filters(struct mveth_private *mp) +{ +register int i; +register uint32_t u; + u = MV643XX_ETH_DA_FILTER_UNICAST_TBL(mp->port_num); + for (i=0; iport_num) + slot); + if ( accept ) { + val |= 0x01 << bit; + } else { + val &= 0x0e << bit; + } + MV_WRITE(MV643XX_ETH_DA_FILTER_UNICAST_TBL(mp->port_num) + slot, val); +} + +#if defined( ENABLE_TX_WORKAROUND_8_BYTE_PROBLEM ) && 0 +/* Currently unused; small unaligned buffers seem to be rare + * so we just use memcpy()... + */ + +/* memcpy for 0..7 bytes; arranged so that gcc + * optimizes for powerpc... + */ + +static inline void memcpy8(void *to, void *fr, unsigned x) +{ +register uint8_t *d = to, *s = fro; + + d+=l; s+=l; + if ( l & 1 ) { + *--d=*--s; + } + if ( l & 2 ) { + /* pre-decrementing causes gcc to use auto-decrementing + * PPC instructions (lhzu rx, -2(ry)) + */ + d-=2; s-=2; + /* use memcpy; don't cast to short -- accessing + * misaligned data as short is not portable + * (but it works on PPC). + */ + __builtin_memcpy(d,s,2); + } + if ( l & 4 ) { + d-=4; s-=4; + /* see above */ + __builtin_memcpy(d,s,4); + } +} +#endif + +static int +mveth_assign_desc_raw(MvEthTxDesc d, void *buf, int len, void *uptr, unsigned long extra) +{ +int rval = (d->byte_cnt = len); + +#ifdef MVETH_TESTING + assert( !d->u_buf ); + assert( len ); +#endif + + /* set CRC on all descriptors; seems to be necessary */ + d->cmd_sts = extra | (TDESC_GEN_CRC | TDESC_ZERO_PAD); + +#ifdef ENABLE_TX_WORKAROUND_8_BYTE_PROBLEM + /* The buffer must be 64bit aligned if the payload is <8 (??) */ + if ( rval < 8 && ( ((uintptr_t)buf) & 7) ) { + d->buf_ptr = CPUADDR2ENET( d->workaround ); + memcpy((void*)d->workaround, buf, rval); + } else +#endif + { + d->buf_ptr = CPUADDR2ENET( (unsigned long)buf ); + } + d->u_buf = uptr; + d->l4i_chk = 0; + return rval; +} + +/* + * Ring Initialization + * + * ENDIAN ASSUMPTION: DMA engine matches CPU endianness (???) + * + * Linux driver discriminates __LITTLE and __BIG endian for re-arranging + * the u16 fields in the descriptor structs. However, no endian conversion + * is done on the individual fields (SDMA byte swapping is disabled on LE). + */ + +STATIC int +mveth_init_rx_desc_ring(struct mveth_private *mp) +{ +int i,sz; +MvEthRxDesc d; +uintptr_t baddr; + + memset((void*)mp->rx_ring, 0, sizeof(*mp->rx_ring)*mp->rbuf_count); + + mp->rx_desc_dma = CPUADDR2ENET(mp->rx_ring); + + for ( i=0, d = mp->rx_ring; irbuf_count; i++, d++ ) { + d->u_buf = mp->alloc_rxbuf(&sz, &baddr); + assert( d->u_buf ); + +#ifndef ENABLE_HW_SNOOPING + /* could reduce the area to max. ethernet packet size */ + INVAL_BUF(baddr, sz); +#endif + + d->buf_size = sz; + d->byte_cnt = 0; + d->cmd_sts = RDESC_DMA_OWNED | RDESC_INT_ENA; + d->next = mp->rx_ring + (i+1) % mp->rbuf_count; + + d->buf_ptr = CPUADDR2ENET( baddr ); + d->next_desc_ptr = CPUADDR2ENET(d->next); + FLUSH_DESC(d); + } + FLUSH_BARRIER(); + + mp->d_rx_t = mp->rx_ring; + + /* point the chip to the start of the ring */ + MV_WRITE(MV643XX_ETH_RX_Q0_CURRENT_DESC_PTR(mp->port_num),mp->rx_desc_dma); + + + return i; +} + +STATIC int +mveth_init_tx_desc_ring(struct mveth_private *mp) +{ +int i; +MvEthTxDesc d; + + memset((void*)mp->tx_ring, 0, sizeof(*mp->tx_ring)*mp->xbuf_count); + + /* DMA and CPU live in the same address space (rtems) */ + mp->tx_desc_dma = CPUADDR2ENET(mp->tx_ring); + mp->avail = TX_AVAILABLE_RING_SIZE(mp); + + for ( i=0, d=mp->tx_ring; ixbuf_count; i++,d++ ) { + d->l4i_chk = 0; + d->byte_cnt = 0; + d->cmd_sts = 0; + d->buf_ptr = 0; + + d->next = mp->tx_ring + (i+1) % mp->xbuf_count; + d->next_desc_ptr = CPUADDR2ENET(d->next); + FLUSH_DESC(d); + } + FLUSH_BARRIER(); + + mp->d_tx_h = mp->d_tx_t = mp->tx_ring; + + /* point the chip to the start of the ring */ + MV_WRITE(MV643XX_ETH_TX_Q0_CURRENT_DESC_PTR(mp->port_num),mp->tx_desc_dma); + + return i; +} + +/* PUBLIC LOW-LEVEL DRIVER ACCESS */ + +struct mveth_private * +BSP_mve_create( + int unit, + rtems_id tid, + void (*isr)(void*isr_arg), + void *isr_arg, + void (*cleanup_txbuf)(void *user_buf, void *closure, int error_on_tx_occurred), + void *cleanup_txbuf_arg, + void *(*alloc_rxbuf)(int *p_size, uintptr_t *p_data_addr), + void (*consume_rxbuf)(void *user_buf, void *closure, int len), + void *consume_rxbuf_arg, + int rx_ring_size, + int tx_ring_size, + int irq_mask +) +{ +struct mveth_private *mp; +int InstallISRSuccessful; + + if ( unit <= 0 || unit > MV643XXETH_NUM_DRIVER_SLOTS ) { + printk(DRVNAME": Bad unit number %i; must be 1..%i\n", unit, MV643XXETH_NUM_DRIVER_SLOTS); + return 0; + } + + if ( rx_ring_size < 0 && tx_ring_size < 0 ) + return 0; + + if ( MV_64360 != BSP_getDiscoveryVersion(0) ) { + printk(DRVNAME": not mv64360 chip\n"); + return 0; + } + + if ( tx_ring_size < 1 ) { + printk(DRVNAME": tx ring size must not be zero (networking configuration issue?)\n"); + return 0; + } + + if ( rx_ring_size < 1 ) { + printk(DRVNAME": rx ring size must not be zero (networking configuration issue?)\n"); + return 0; + } + + mp = calloc( 1, sizeof *mp ); + + mp->port_num = unit-1; + mp->phy = port2phy(mp->port_num); + + mp->tid = tid; + mp->isr = isr; + mp->isr_arg = isr_arg; + + mp->cleanup_txbuf = cleanup_txbuf; + mp->cleanup_txbuf_arg = cleanup_txbuf_arg; + mp->alloc_rxbuf = alloc_rxbuf; + mp->consume_rxbuf = consume_rxbuf; + mp->consume_rxbuf_arg = consume_rxbuf_arg; + + mp->rbuf_count = rx_ring_size; + mp->xbuf_count = tx_ring_size; + + if ( mp->xbuf_count > 0 ) + mp->xbuf_count += TX_NUM_TAG_SLOTS; + + if ( mp->rbuf_count < 0 ) + mp->rbuf_count = 0; + if ( mp->xbuf_count < 0 ) + mp->xbuf_count = 0; + + /* allocate ring area; add 1 entry -- room for alignment */ + assert( !mp->ring_area ); + mp->ring_area = malloc( sizeof(*mp->ring_area) * (mp->rbuf_count + mp->xbuf_count + 1) ); + assert( mp->ring_area ); + + BSP_mve_stop_hw(mp); + + if ( irq_mask ) { + irq_data[mp->port_num].hdl = mveth_isr; + irq_data[mp->port_num].handle = (rtems_irq_hdl_param)mp; + InstallISRSuccessful = BSP_install_rtems_irq_handler( &irq_data[mp->port_num] ); + assert( InstallISRSuccessful ); + } + + if ( rx_ring_size < 0 ) + irq_mask &= ~ MV643XX_ETH_IRQ_RX_DONE; + if ( tx_ring_size < 0 ) + irq_mask &= ~ MV643XX_ETH_EXT_IRQ_TX_DONE; + + mp->irq_mask = (irq_mask & MV643XX_ETH_IRQ_RX_DONE); + if ( (irq_mask &= (MV643XX_ETH_EXT_IRQ_TX_DONE | MV643XX_ETH_EXT_IRQ_LINK_CHG)) ) { + mp->irq_mask |= MV643XX_ETH_IRQ_EXT_ENA; + mp->xirq_mask = irq_mask; + } else { + mp->xirq_mask = 0; + } + + return mp; +} + +void +BSP_mve_update_serial_port(struct mveth_private *mp, int media) +{ +int port = mp->port_num; +uint32_t old, new; + +#ifdef MVETH_DEBUG + printk(DRVNAME": Entering BSP_mve_update_serial_port()\n"); +#endif + + new = old = MV_READ(MV643XX_ETH_SERIAL_CONTROL_R(port)); + + /* mask speed and duplex settings */ + new &= ~( MV643XX_ETH_SET_GMII_SPEED_1000 + | MV643XX_ETH_SET_MII_SPEED_100 + | MV643XX_ETH_SET_FULL_DUPLEX ); + + if ( (MV643XX_MEDIA_FD & media) ) + new |= MV643XX_ETH_SET_FULL_DUPLEX; + + switch ( (media & MV643XX_MEDIA_SPEED_MSK) ) { + default: /* treat as 10 */ + break; + case MV643XX_MEDIA_100: + new |= MV643XX_ETH_SET_MII_SPEED_100; + break; + case MV643XX_MEDIA_1000: + new |= MV643XX_ETH_SET_GMII_SPEED_1000; + break; + } + + + if ( new != old ) { + if ( ! (MV643XX_ETH_SERIAL_PORT_ENBL & new) ) { + /* just write */ + MV_WRITE(MV643XX_ETH_SERIAL_CONTROL_R(port), new); + } else { + uint32_t were_running; + + were_running = mveth_stop_tx(port); + + old &= ~MV643XX_ETH_SERIAL_PORT_ENBL; + MV_WRITE(MV643XX_ETH_SERIAL_CONTROL_R(port), old); + MV_WRITE(MV643XX_ETH_SERIAL_CONTROL_R(port), new); + /* linux driver writes twice... */ + MV_WRITE(MV643XX_ETH_SERIAL_CONTROL_R(port), new); + + if ( were_running ) { + MV_WRITE(MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(mp->port_num), MV643XX_ETH_TX_START(0)); + } + } + } + /* If TX stalled because there was no buffer then whack it */ + mveth_start_tx(mp); +} + +rtems_id +BSP_mve_get_tid(struct mveth_private *mp) +{ + return mp->tid; +} + +int +BSP_mve_detach(struct mveth_private *mp) +{ + BSP_mve_stop_hw(mp); + if ( mp->irq_mask || mp->xirq_mask ) { + if ( !BSP_remove_rtems_irq_handler( &irq_data[mp->port_num] ) ) + return -1; + } + free( (void*)mp->ring_area ); + memset(mp, 0, sizeof(*mp)); + __asm__ __volatile__("":::"memory"); + return 0; +} + +/* MAIN RX-TX ROUTINES + * + * BSP_mve_swipe_tx(): descriptor scavenger; releases mbufs + * BSP_mve_send_buf(): xfer mbufs from IF to chip + * BSP_mve_swipe_rx(): enqueue received mbufs to interface + * allocate new ones and yield them to the + * chip. + */ + +/* clean up the TX ring freeing up buffers */ +int +BSP_mve_swipe_tx(struct mveth_private *mp) +{ +int rval = 0; +register MvEthTxDesc d; + + for ( d = mp->d_tx_t; d->buf_ptr; d = NEXT_TXD(d) ) { + + INVAL_DESC(d); + + if ( (TDESC_DMA_OWNED & d->cmd_sts) + && (uint32_t)d == MV_READ(MV643XX_ETH_CURRENT_SERVED_TX_DESC(mp->port_num)) ) + break; + + /* d->u_buf is only set on the last descriptor in a chain; + * we only count errors in the last descriptor; + */ + if ( d->u_buf ) { + mp->cleanup_txbuf(d->u_buf, mp->cleanup_txbuf_arg, (d->cmd_sts & TDESC_ERROR) ? 1 : 0); + d->u_buf = 0; + } + + d->buf_ptr = 0; + + rval++; + } + mp->d_tx_t = d; + mp->avail += rval; + + return mp->avail; +} + +int +BSP_mve_send_buf_chain(struct mveth_private *mp, MveEthBufIterNext next, MveEthBufIter *it) +{ +int rval; +register MvEthTxDesc l,d,h; +int nmbs; +MveEthBufIter head = *it; + + rval = 0; + + /* if no descriptor is available; try to wipe the queue */ + if ( (mp->avail < 1) && MVETH_CLEAN_ON_SEND(mp)<=0 ) { + /* Maybe TX is stalled and needs to be restarted */ + mveth_start_tx(mp); + return -1; + } + + h = mp->d_tx_h; + +#ifdef MVETH_TESTING + assert( !h->buf_ptr ); + assert( !h->u_buf ); +#endif + + /* Don't use the first descriptor yet because BSP_mve_swipe_tx() + * needs mp->d_tx_h->buf_ptr == NULL as a marker. Hence, we + * start with the second mbuf and fill the first descriptor + * last. + */ + + l = h; + d = NEXT_TXD(h); + + mp->avail--; + + nmbs = 1; + while ( (it = next(it)) ) { + if ( 0 == it->len ) + continue; /* skip empty mbufs */ + + nmbs++; + + if ( mp->avail < 1 && MVETH_CLEAN_ON_SEND(mp)<=0 ) { + /* Maybe TX was stalled - try to restart */ + mveth_start_tx(mp); + + /* not enough descriptors; cleanup... + * the first slot was never used, so we start + * at mp->d_tx_h->next; + */ + for ( l = NEXT_TXD(h); l!=d; l=NEXT_TXD(l) ) { +#ifdef MVETH_TESTING + assert( l->u_buf == 0 ); +#endif + l->buf_ptr = 0; + l->cmd_sts = 0; + mp->avail++; + } + mp->avail++; + if ( nmbs > TX_AVAILABLE_RING_SIZE(mp) ) { + /* this chain will never fit into the ring */ + if ( nmbs > mp->stats.maxchain ) + mp->stats.maxchain = nmbs; + mp->stats.repack++; + /* caller may reorganize chain */ + return -2; + } + return -1; + } + + mp->avail--; + +#ifdef MVETH_TESTING + assert( d != h ); + assert( !d->buf_ptr ); +#endif + + /* fill this slot */ + rval += mveth_assign_desc_raw(d, it->data, it->len, it->uptr, TDESC_DMA_OWNED); + + FLUSH_BUF( (uint32_t)it->data, it->len ); + + l = d; + d = NEXT_TXD(d); + + FLUSH_DESC(l); + } + + /* fill first slot - don't release to DMA yet */ + rval += mveth_assign_desc_raw(h, head.data, head.len, head.uptr, TDESC_FRST); + + + FLUSH_BUF((uint32_t)head.data, head.len); + + + /* tag last slot; this covers the case where 1st==last */ + l->cmd_sts |= TDESC_LAST | TDESC_INT_ENA; + + FLUSH_DESC(l); + + /* Tag end; make sure chip doesn't try to read ahead of here! */ + l->next->cmd_sts = 0; + FLUSH_DESC(l->next); + + membarrier(); + + /* turn over the whole chain by flipping ownership of the first desc */ + h->cmd_sts |= TDESC_DMA_OWNED; + + FLUSH_DESC(h); + + membarrier(); + + /* notify the device */ + MV_WRITE(MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(mp->port_num), MV643XX_ETH_TX_START(0)); + + /* Update softc */ + mp->stats.packet++; + if ( nmbs > mp->stats.maxchain ) + mp->stats.maxchain = nmbs; + + /* remember new head */ + mp->d_tx_h = d; + + return rval; /* #bytes sent */ +} + +int +BSP_mve_send_buf_raw( + struct mveth_private *mp, + void *head_p, + int h_len, + void *data_p, + int d_len) +{ +int rval; +register MvEthTxDesc l,d,h; +int needed; +void *frst_buf; +int frst_len; +void *uarg; + + rval = 0; + +#ifdef MVETH_TESTING + assert(head_p || data_p); +#endif + + needed = head_p && data_p ? 2 : 1; + + /* if no descriptor is available; try to wipe the queue */ + if ( ( mp->avail < needed ) + && ( MVETH_CLEAN_ON_SEND(mp) <= 0 || mp->avail < needed ) ) { + /* Maybe TX was stalled and needs a restart */ + mveth_start_tx(mp); + return -1; + } + + h = mp->d_tx_h; + +#ifdef MVETH_TESTING + assert( !h->buf_ptr ); + assert( !h->u_buf ); +#endif + + /* find the 'first' user buffer */ + if ( (frst_buf = head_p) && (h_len > 0) ) { + frst_len = h_len; + } else { + frst_buf = data_p; + frst_len = d_len; + } + + uarg = (head_p && ! h_len) ? head_p : frst_buf; + + /* Legacy: if h_len == 0 but head_p is not then use that for the user arg */ + + /* Don't use the first descriptor yet because BSP_mve_swipe_tx() + * needs mp->d_tx_h->buf_ptr == NULL as a marker. Hence, we + * start with the second (optional) slot and fill the first + * descriptor last. + */ + + l = h; + d = NEXT_TXD(h); + + mp->avail--; + + if ( needed > 1 ) { + mp->avail--; +#ifdef MVETH_TESTING + assert( d != h ); + assert( !d->buf_ptr ); +#endif + rval += mveth_assign_desc_raw(d, data_p, d_len, 0, TDESC_DMA_OWNED); + FLUSH_BUF( (uint32_t)data_p, d_len ); + d->u_buf = data_p; + + l = d; + d = NEXT_TXD(d); + + FLUSH_DESC(l); + } + + /* fill first slot with raw buffer - don't release to DMA yet */ + rval += mveth_assign_desc_raw(h, frst_buf, frst_len, 0, TDESC_FRST); + + FLUSH_BUF( (uint32_t)frst_buf, frst_len); + + /* tag last slot; this covers the case where 1st==last */ + l->cmd_sts |= TDESC_LAST | TDESC_INT_ENA; + + /* first buffer of 'chain' goes into last desc */ + l->u_buf = uarg; + + FLUSH_DESC(l); + + /* Tag end; make sure chip doesn't try to read ahead of here! */ + l->next->cmd_sts = 0; + FLUSH_DESC(l->next); + + membarrier(); + + /* turn over the whole chain by flipping ownership of the first desc */ + h->cmd_sts |= TDESC_DMA_OWNED; + + FLUSH_DESC(h); + + membarrier(); + + /* notify the device */ + MV_WRITE(MV643XX_ETH_TRANSMIT_QUEUE_COMMAND_R(mp->port_num), MV643XX_ETH_TX_START(0)); + + /* Update softc */ + mp->stats.packet++; + if ( needed > mp->stats.maxchain ) + mp->stats.maxchain = needed; + + /* remember new head */ + mp->d_tx_h = d; + + return rval; /* #bytes sent */ +} + +/* send received buffers upwards and replace them + * with freshly allocated ones; + * ASSUMPTION: buffer length NEVER changes and is set + * when the ring is initialized. + * TS 20060727: not sure if this assumption is still necessary - I believe it isn't. + */ + +int +BSP_mve_swipe_rx(struct mveth_private *mp) +{ +int rval = 0, err; +register MvEthRxDesc d; +void *newbuf; +int sz; +uintptr_t baddr; + + for ( d = mp->d_rx_t; ! (INVAL_DESC(d), (RDESC_DMA_OWNED & d->cmd_sts)); d=NEXT_RXD(d) ) { + +#ifdef MVETH_TESTING + assert(d->u_buf); +#endif + + err = (RDESC_ERROR & d->cmd_sts); + + if ( err || !(newbuf = mp->alloc_rxbuf(&sz, &baddr)) ) { + /* drop packet and recycle buffer */ + newbuf = d->u_buf; + mp->stats.idrops++; + } else { +#ifdef MVETH_TESTING + assert( d->byte_cnt > 0 ); +#endif + mp->consume_rxbuf(d->u_buf, mp->consume_rxbuf_arg, d->byte_cnt); + +#ifndef ENABLE_HW_SNOOPING + /* could reduce the area to max. ethernet packet size */ + INVAL_BUF(baddr, sz); +#endif + d->u_buf = newbuf; + d->buf_ptr = CPUADDR2ENET(baddr); + d->buf_size = sz; + FLUSH_DESC(d); + } + + membarrier(); + + d->cmd_sts = RDESC_DMA_OWNED | RDESC_INT_ENA; + + FLUSH_DESC(d); + + rval++; + } + MV_WRITE(MV643XX_ETH_RECEIVE_QUEUE_COMMAND_R(mp->port_num), MV643XX_ETH_RX_START(0)); + mp->d_rx_t = d; + return rval; +} + +/* Stop hardware and clean out the rings */ +void +BSP_mve_stop_hw(struct mveth_private *mp) +{ +MvEthTxDesc d; +MvEthRxDesc r; +int i; + + mveth_disable_irqs(mp, -1); + + mveth_stop_tx(mp->port_num); + + /* cleanup TX rings */ + if (mp->d_tx_t) { /* maybe ring isn't initialized yet */ + for ( i=0, d=mp->tx_ring; ixbuf_count; i++, d++ ) { + /* should be safe to clear ownership */ + d->cmd_sts &= ~TDESC_DMA_OWNED; + FLUSH_DESC(d); + } + FLUSH_BARRIER(); + + BSP_mve_swipe_tx(mp); + +#ifdef MVETH_TESTING + assert( mp->d_tx_h == mp->d_tx_t ); + for ( i=0, d=mp->tx_ring; ixbuf_count; i++, d++ ) { + assert( !d->buf_ptr ); + } +#endif + } + + MV_WRITE(MV643XX_ETH_RECEIVE_QUEUE_COMMAND_R(mp->port_num), MV643XX_ETH_RX_STOP_ALL); + while ( MV643XX_ETH_RX_ANY_RUNNING & MV_READ(MV643XX_ETH_RECEIVE_QUEUE_COMMAND_R(mp->port_num)) ) + /* poll-wait */; + + /* stop serial port */ + MV_WRITE(MV643XX_ETH_SERIAL_CONTROL_R(mp->port_num), + MV_READ(MV643XX_ETH_SERIAL_CONTROL_R(mp->port_num)) + & ~( MV643XX_ETH_SERIAL_PORT_ENBL | MV643XX_ETH_FORCE_LINK_FAIL_DISABLE | MV643XX_ETH_FORCE_LINK_PASS) + ); + + /* clear pending interrupts */ + MV_WRITE(MV643XX_ETH_INTERRUPT_CAUSE_R(mp->port_num), 0); + MV_WRITE(MV643XX_ETH_INTERRUPT_EXTEND_CAUSE_R(mp->port_num), 0); + + /* cleanup RX rings */ + if ( mp->rx_ring ) { + for ( i=0, r=mp->rx_ring; irbuf_count; i++, r++ ) { + /* should be OK to clear ownership flag */ + r->cmd_sts = 0; + FLUSH_DESC(r); + mp->consume_rxbuf(r->u_buf, mp->consume_rxbuf_arg, 0); + r->u_buf = 0; + } + FLUSH_BARRIER(); + } + + +} + +uint32_t mveth_serial_ctrl_config_val = MVETH_SERIAL_CTRL_CONFIG_VAL; + +/* Fire up the low-level driver + * + * - make sure hardware is halted + * - enable cache snooping + * - clear address filters + * - clear mib counters + * - reset phy + * - initialize (or reinitialize) descriptor rings + * - check that the firmware has set up a reasonable mac address. + * - generate unicast filter entry for our mac address + * - write register config values to the chip + * - start hardware (serial port and SDMA) + */ + +void +BSP_mve_init_hw(struct mveth_private *mp, int promisc, unsigned char *enaddr, int media) +{ +int i; +uint32_t v; +static int inited = 0; + +#ifdef MVETH_DEBUG + printk(DRVNAME"%i: Entering BSP_mve_init_hw()\n", mp->port_num+1); +#endif + + /* since enable/disable IRQ routine only operate on select bitsets + * we must make sure everything is masked initially. + */ + MV_WRITE(MV643XX_ETH_INTERRUPT_ENBL_R(mp->port_num), 0); + MV_WRITE(MV643XX_ETH_INTERRUPT_EXTEND_ENBL_R(mp->port_num), 0); + + BSP_mve_stop_hw(mp); + + memset(&mp->stats, 0, sizeof(mp->stats)); + + mp->promisc = promisc; + + /* MotLoad has cache snooping disabled on the ENET2MEM windows. + * Some comments in (linux) indicate that there are errata + * which cause problems which would be a real bummer. + * We try it anyways... + */ + if ( !inited ) { + unsigned long disbl, bar; + inited = 1; /* FIXME: non-thread safe lazy init */ + disbl = MV_READ(MV643XX_ETH_BAR_ENBL_R); + /* disable all 6 windows */ + MV_WRITE(MV643XX_ETH_BAR_ENBL_R, MV643XX_ETH_BAR_DISBL_ALL); + /* set WB snooping on enabled bars */ + for ( i=0; irbuf_count > 0 ) { + mp->rx_ring = (MvEthRxDesc)MV643XX_ALIGN(mp->ring_area, RING_ALIGNMENT); + mveth_init_rx_desc_ring(mp); + } + + if ( mp->xbuf_count > 0 ) { + mp->tx_ring = (MvEthTxDesc)mp->rx_ring + mp->rbuf_count; + mveth_init_tx_desc_ring(mp); + } + + if ( enaddr ) { + /* set ethernet address from arpcom struct */ +#ifdef MVETH_DEBUG + printk(DRVNAME"%i: Writing MAC addr ", mp->port_num+1); + for (i=5; i>=0; i--) { + printk("%02X%c", enaddr[i], i?':':'\n'); + } +#endif + mveth_write_eaddr(mp, enaddr); + } + + /* set mac address and unicast filter */ + + { + uint32_t machi, maclo; + maclo = MV_READ(MV643XX_ETH_MAC_ADDR_LO(mp->port_num)); + machi = MV_READ(MV643XX_ETH_MAC_ADDR_HI(mp->port_num)); + /* ASSUME: firmware has set the mac address for us + * - if assertion fails, we have to do more work... + */ + assert( maclo && machi && maclo != 0xffffffff && machi != 0xffffffff ); + mveth_ucfilter(mp, maclo&0xff, 1/* accept */); + } + + /* port, serial and sdma configuration */ + v = MVETH_PORT_CONFIG_VAL; + if ( promisc ) { + /* multicast filters were already set up to + * accept everything (mveth_clear_addr_filters()) + */ + v |= MV643XX_ETH_UNICAST_PROMISC_MODE; + } else { + v &= ~MV643XX_ETH_UNICAST_PROMISC_MODE; + } + MV_WRITE(MV643XX_ETH_PORT_CONFIG_R(mp->port_num), + v); + MV_WRITE(MV643XX_ETH_PORT_CONFIG_XTEND_R(mp->port_num), + MVETH_PORT_XTEND_CONFIG_VAL); + + v = MV_READ(MV643XX_ETH_SERIAL_CONTROL_R(mp->port_num)); + v &= ~(MVETH_SERIAL_CTRL_CONFIG_MSK); + v |= mveth_serial_ctrl_config_val; + MV_WRITE(MV643XX_ETH_SERIAL_CONTROL_R(mp->port_num), v); + + if ( (MV643XX_MEDIA_LINK & media) ) { + BSP_mve_update_serial_port(mp, media); + } + + /* enable serial port */ + v = MV_READ(MV643XX_ETH_SERIAL_CONTROL_R(mp->port_num)); + MV_WRITE(MV643XX_ETH_SERIAL_CONTROL_R(mp->port_num), + v | MV643XX_ETH_SERIAL_PORT_ENBL); + +#ifndef __BIG_ENDIAN__ +#error "byte swapping needs to be disabled for little endian machines" +#endif + MV_WRITE(MV643XX_ETH_SDMA_CONFIG_R(mp->port_num), MVETH_SDMA_CONFIG_VAL); + + /* allow short frames */ + MV_WRITE(MV643XX_ETH_RX_MIN_FRAME_SIZE_R(mp->port_num), MVETH_MIN_FRAMSZ_CONFIG_VAL); + + MV_WRITE(MV643XX_ETH_INTERRUPT_CAUSE_R(mp->port_num), 0); + MV_WRITE(MV643XX_ETH_INTERRUPT_EXTEND_CAUSE_R(mp->port_num), 0); + /* TODO: set irq coalescing */ + + /* enable Rx */ + if ( mp->rbuf_count > 0 ) { + MV_WRITE(MV643XX_ETH_RECEIVE_QUEUE_COMMAND_R(mp->port_num), MV643XX_ETH_RX_START(0)); + } + + mveth_enable_irqs(mp, -1); + +#ifdef MVETH_DEBUG + printk(DRVNAME"%i: Leaving BSP_mve_init_hw()\n", mp->port_num+1); +#endif +} + +/* read ethernet address from hw to buffer */ +void +BSP_mve_read_eaddr(struct mveth_private *mp, unsigned char *oeaddr) +{ +int i; +uint32_t x; +unsigned char buf[6], *eaddr; + + eaddr = oeaddr ? oeaddr : buf; + + eaddr += 5; + x = MV_READ(MV643XX_ETH_MAC_ADDR_LO(mp->port_num)); + + /* lo word */ + for (i=2; i; i--, eaddr--) { + *eaddr = (unsigned char)(x & 0xff); + x>>=8; + } + + x = MV_READ(MV643XX_ETH_MAC_ADDR_HI(mp->port_num)); + /* hi word */ + for (i=4; i; i--, eaddr--) { + *eaddr = (unsigned char)(x & 0xff); + x>>=8; + } + + if ( !oeaddr ) { + printf("%02X",buf[0]); + for (i=1; iport_num; +int idx; +uint32_t v; + + if ( !f ) + f = stdout; + + fprintf(f, DRVNAME"%i Statistics:\n", mp->port_num + 1); + fprintf(f, " # IRQS: %i\n", mp->stats.irqs); + fprintf(f, " Max. mbuf chain length: %i\n", mp->stats.maxchain); + fprintf(f, " # repacketed: %i\n", mp->stats.repack); + fprintf(f, " # packets: %i\n", mp->stats.packet); + fprintf(f, " # buffer alloc failed: %i\n", mp->stats.idrops); + fprintf(f, "MIB Counters:\n"); + for ( idx = MV643XX_ETH_MIB_GOOD_OCTS_RCVD_LO>>2; + idx < MV643XX_ETH_NUM_MIB_COUNTERS; + idx++ ) { + switch ( idx ) { + case MV643XX_ETH_MIB_GOOD_OCTS_RCVD_LO>>2: + mp->stats.mib.good_octs_rcvd += read_long_mib_counter(p, idx); + fprintf(f, mibfmt[idx], mp->stats.mib.good_octs_rcvd); + idx++; + break; + + case MV643XX_ETH_MIB_GOOD_OCTS_SENT_LO>>2: + mp->stats.mib.good_octs_sent += read_long_mib_counter(p, idx); + fprintf(f, mibfmt[idx], mp->stats.mib.good_octs_sent); + idx++; + break; + + default: + v = ((uint32_t*)&mp->stats.mib)[idx] += read_mib_counter(p, idx); + fprintf(f, mibfmt[idx], v); + break; + } + } + fprintf(f, "\n"); +} + +#ifdef MVETH_DEBUG +/* Display/dump descriptor rings */ + +/* These low-level routines need to be synchronized with + * any Tx/Rx threads! + */ +int +BSP_mve_dring_nonsync(struct mveth_private *mp) +{ +int i; +if (1) { +MvEthRxDesc pr; +printf("RX:\n"); + + for (i=0, pr=mp->rx_ring; irbuf_count; i++, pr++) { + printf("cnt: 0x%04x, size: 0x%04x, stat: 0x%08x, next: 0x%08x, buf: 0x%08x\n", + pr->byte_cnt, pr->buf_size, pr->cmd_sts, (uint32_t)pr->next_desc_ptr, pr->buf_ptr); + } +} +if (1) { +MvEthTxDesc pt; +printf("TX:\n"); + for (i=0, pt=mp->tx_ring; ixbuf_count; i++, pt++) { + printf("cnt: 0x%04x, stat: 0x%08x, next: 0x%08x, buf: 0x%08x, mb: 0x%08x\n", + pt->byte_cnt, pt->cmd_sts, (uint32_t)pt->next_desc_ptr, pt->buf_ptr, + (uint32_t)pt->u_buf); + } +} + return 0; +} +#endif + +#endif /* LIBBSP_BEATNIK_BSP_H */ diff --git a/rtemsbsd/sys/dev/mve/if_mve_nexus.c b/rtemsbsd/sys/dev/mve/if_mve_nexus.c new file mode 100644 index 00000000..be9433da --- /dev/null +++ b/rtemsbsd/sys/dev/mve/if_mve_nexus.c @@ -0,0 +1,935 @@ +/* RTEMS driver for the mv643xx gigabit ethernet chip */ + +/* Acknowledgement: + * + * Valuable information for developing this driver was obtained + * from the linux open-source driver mv643xx_eth.c which was written + * by the following people and organizations: + * + * Matthew Dharm + * rabeeh@galileo.co.il + * PMC-Sierra, Inc., Manish Lachwani + * Ralf Baechle + * MontaVista Software, Inc., Dale Farnsworth + * Steven J. Hill / + * + * Note however, that in spite of the identical name of this file + * (and some of the symbols used herein) this file provides a + * new implementation and is the original work by the author. + */ + +/* + * Authorship + * ---------- + * This software (mv643xx ethernet driver for RTEMS) was + * created by Till Straumann , 2005-2007, + * Stanford Linear Accelerator Center, Stanford University. + * + * Acknowledgement of sponsorship + * ------------------------------ + * The 'mv643xx ethernet driver for RTEMS' was produced by + * the Stanford Linear Accelerator Center, Stanford University, + * under Contract DE-AC03-76SFO0515 with the Department of Energy. + * + * Government disclaimer of liability + * ---------------------------------- + * Neither the United States nor the United States Department of Energy, + * nor any of their employees, makes any warranty, express or implied, or + * assumes any legal liability or responsibility for the accuracy, + * completeness, or usefulness of any data, apparatus, product, or process + * disclosed, or represents that its use would not infringe privately owned + * rights. + * + * Stanford disclaimer of liability + * -------------------------------- + * Stanford University makes no representations or warranties, express or + * implied, nor assumes any liability for the use of this software. + * + * Stanford disclaimer of copyright + * -------------------------------- + * Stanford University, owner of the copyright, hereby disclaims its + * copyright and all other rights in this software. Hence, anyone may + * freely use it for any purpose without restriction. + * + * Maintenance of notices + * ---------------------- + * In the interest of clarity regarding the origin and status of this + * SLAC software, this and all the preceding Stanford University notices + * are to remain affixed to any copy or derivative of this software made + * or distributed by the recipient and are to be affixed to any copy of + * software made or distributed by the recipient that contains a copy or + * derivative of this software. + * + * ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03 + */ + +/* Nexus port by Till Straumann, , 3/2021 */ + +#include +#include + +#ifdef LIBBSP_BEATNIK_BSP_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "mv63xx_nexus" + +#undef MVETH_DEBUG + +/* Define default ring sizes */ + +#undef MVETH_TESTING + +#ifdef MVETH_TESTING +/* hard and small defaults */ +#define MV643XX_RX_RING_SIZE 2 +#define MV643XX_TX_QUEUE_SIZE 4 +#define MV643XX_BD_PER_PACKET 1 +#define TX_LOWWATER 1 + +#else /* MVETH_TESTING */ + +#define MV643XX_RX_RING_SIZE 40 /* attached buffers are always 2k clusters, i.e., this + * driver - with a configured ring size of 40 - constantly + * locks 80k of cluster memory - your app config better + * provides enough space! + */ +#define MV643XX_TX_QUEUE_SIZE 40 +#define MV643XX_BD_PER_PACKET 10 +#define TX_LOWWATER (4*(MV643XX_BD_PER_PACKET)) +#endif /* MVETH_TESTING */ + +/* NOTE: tx ring size MUST be > max. # of fragments / mbufs in a chain; + * I observed chains of >17 entries regularly! + */ +#define MV643XX_TX_RING_SIZE ((MV643XX_TX_QUEUE_SIZE) * (MV643XX_BD_PER_PACKET)) + +/* The chip puts the ethernet header at offset 2 into the buffer so + * that the payload is aligned + */ +#define ETH_RX_OFFSET 2 +#define ETH_CRC_LEN 4 /* strip FCS at end of packet */ + +#ifndef __PPC__ +#error "Dont' know how to deal with cache on this CPU architecture" +#endif + +/* Ring entries are 32 bytes; coherency-critical chunks are 16 -> software coherency + * management works for cache line sizes of 16 and 32 bytes only. If the line size + * is bigger, the descriptors could be padded... + */ +#if !defined(PPC_CACHE_ALIGNMENT) +#error "PPC_CACHE_ALIGNMENT not defined" +#elif PPC_CACHE_ALIGMENT != 16 && PPC_CACHE_ALIGNMENT != 32 +#error "Cache line size must be 16 or 32" +#else +#define RX_BUF_ALIGNMENT PPC_CACHE_ALIGNMENT +#endif + +/* HELPER MACROS */ + +/* Align base to alignment 'a' */ +#define MV643XX_ALIGN(b, a) ((((uint32_t)(b)) + (a)-1) & (~((a)-1))) + + +#define IRQ_EVENT RTEMS_EVENT_0 +#define TX_EVENT RTEMS_EVENT_1 + +/* Hacks -- FIXME */ +rtems_id +rtems_bsdnet_newproc (char *name, int stacksize, void(*entry)(void *), void *arg); +#define SIO_RTEMS_SHOW_STATS _IO('i', 250) + +#define MVE643XX_DUMMY_PHY 0 /* phy is defined by low-level driver */ + +struct mve_enet_softc { + device_t dev; + struct ifnet *ifp; + device_t miibus; + struct mii_data *mii_softc; + struct mveth_private *mp; + struct mtx mtx; + struct callout wdCallout; + rtems_id daemonTid; + int oif_flags; +}; + +static struct mve_enet_softc * ifaces[MV643XXETH_NUM_DRIVER_SLOTS] = { 0 }; + +typedef struct MveMbufIter { + MveEthBufIter it; + struct mbuf *next; + struct mbuf *head; +} MveMbufIter; + +/* Forward Declarations */ +struct mve_enet_softc; + +static void +mve_media_status(struct ifnet *ifp, struct ifmediareq *ifmr); + +static int +mve_media_change(struct ifnet *ifp); + +static void +mve_set_filters(struct ifnet *ifp); + +static int +xlateMediaFlags(const struct mii_data *mid); + +static void +mve_ack_link_change(struct mve_enet_softc *sc); + +static __inline__ void +mve_send_event(struct mve_enet_softc *sc, rtems_event_set ev) +{ +rtems_status_code st; + if ( RTEMS_SUCCESSFUL != (st = rtems_event_send(sc->daemonTid, ev)) ) { + printk(DRVNAME": rtems_event_send returned 0x%08x (TID: 0x%08x, sc: 0x%08x)\n", st, sc->daemonTid, sc); + rtems_panic(DRVNAME": rtems_event_send() failed!\n"); + } +} + +static __inline__ void +mve_lock(struct mve_enet_softc *sc, const char *from) +{ + mtx_lock( & sc->mtx ); +/*printk("L V %s\n", from);*/ +} + +static __inline__ void +mve_unlock(struct mve_enet_softc *sc, const char *from) +{ +/*printk("L ^ %s\n", from);*/ + mtx_unlock( & sc->mtx ); +} + +static int +mve_probe(device_t dev) +{ + int unit = device_get_unit(dev); + int err; + +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_probe (entering)\n"); +#endif + + if ( unit >= 0 && unit < MV643XXETH_NUM_DRIVER_SLOTS ) { + err = BUS_PROBE_DEFAULT; + } else { + err = ENXIO; + } + + return err; +} + +/* + * starting at 'm' scan the buffer chain until we + * find a non-empty buffer (which we return) + */ +static __inline__ struct mbuf * +skipEmpty(struct mbuf *m) +{ + while ( m && ( 0 == m->m_len ) ) { + m = m->m_next; + } + return m; +} + +/* + * Record a buffer's info in the low-leve driver 'iterator' struct. + * Also scan ahead to find the next non-empty buffer (store it in + * the iterator's 'next' field). This info is needed because we + * want to know if 'this' buffer is the last (non-empty!) one + * in a chain. + * + * On entry 'it->next' identifies 'this' buffer and on return + * 'it->next' points to the next non-empty buffer. + */ +static MveEthBufIter * +nextBuf(MveEthBufIter *arg) +{ +MveMbufIter *it = (MveMbufIter*)arg; +struct mbuf *m; + /* If 'this' buffer is non-null */ + if ( (m = it->next) ) { + /* find next non-empty buffer */ + it->next = skipEmpty( m->m_next ); + /* record 'this' buffer's info */ + it->it.data = mtod(m, void*); + it->it.len = m->m_len; + /* if there is a non-empty buffer after 'this' uptr is NULL + * if this is tha last buffer in a chain then record the + * head of the chain in the uptr (for eventual cleanup + * by release_tx_mbuf()). + */ + it->it.uptr = it->next ? 0 : it->head; + return (MveEthBufIter*)it; + } + return 0; +} + +/* + * Initialize the iterator struct + */ +static MveEthBufIter * +initIter(MveMbufIter *it, struct mbuf *m) +{ + /* record the head of the chain */ + it->head = m; + /* initialize 'next' field to the first non-empty buffer. + * This may be NULL if the chain is entirely empty but + * that is handled correctly. + */ + it->next = skipEmpty( m ); + /* Fill with first buf info */ + return nextBuf( &it->it ); +} + +static int +mve_send_mbuf( struct mve_enet_softc *sc, struct mbuf *m_head ) +{ +MveMbufIter iter; +int rval; + + if ( ! m_head ) { + return 0; + } + + if ( ! initIter( &iter, m_head ) ) { + /* completely empty chain */ + m_freem( m_head ); + return 0; + } + + rval = BSP_mve_send_buf_chain( sc->mp, nextBuf, &iter.it ); + + return rval; +} + +static void +mve_isr(void *closure) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*)closure; +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_isr; posting event to %x\n", sc->daemonTid); +#endif + BSP_mve_disable_irqs( sc->mp ); + mve_send_event( sc, IRQ_EVENT ); +} + +static void +mve_stop(struct mve_enet_softc *sc) +{ + BSP_mve_stop_hw( sc->mp ); + /* clear IF flags */ + if_setdrvflagbits(sc->ifp, 0, (IFF_DRV_OACTIVE | IFF_DRV_RUNNING)); +} + +static void +mve_set_filters(struct ifnet *ifp) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) if_getsoftc( ifp ); +int iff = if_getflags(ifp); +struct ifmultiaddr *ifma; +unsigned char *lladdr; + + BSP_mve_promisc_set( sc->mp, !!(iff & IFF_PROMISC)); + + if ( iff & (IFF_PROMISC | IFF_ALLMULTI) ) { + BSP_mve_mcast_filter_accept_all(sc->mp); + } else { + BSP_mve_mcast_filter_clear( sc->mp ); + + if_maddr_rlock( ifp ); + + CK_STAILQ_FOREACH( ifma, &ifp->if_multiaddrs, ifma_link ) { + + if ( ifma->ifma_addr->sa_family != AF_LINK ) { + continue; + } + + lladdr = LLADDR((struct sockaddr_dl *) ifma->ifma_addr); + + BSP_mve_mcast_filter_accept_add( sc->mp, lladdr ); + + } + + if_maddr_runlock( ifp ); + } +} + +/* Daemon task does all the 'interrupt' work */ +static void +mve_daemon(void *arg) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) arg; +struct ifnet *ifp = sc->ifp; +rtems_event_set evs; +struct mbuf *m; +int avail; +int sndStat; +uint32_t irqstat; + +#ifdef MVETH_DEBUG + printk(DRVNAME": bsdnet mveth_daemon started\n"); +#endif + + mve_lock( sc, "daemon" ); + + for (;;) { + + mve_unlock( sc, "daemon" ); + if ( RTEMS_SUCCESSFUL != rtems_event_receive( (IRQ_EVENT | TX_EVENT), (RTEMS_WAIT | RTEMS_EVENT_ANY), RTEMS_NO_TIMEOUT, &evs ) ) { + rtems_panic(DRVNAME": rtems_event_receive() failed!\n"); + } + mve_lock( sc, "daemon" ); + +#ifdef MVETH_DEBUG + printk(DRVNAME": bsdnet mveth_daemon event received 0x%x\n", evs); +#endif + + if ( !(if_getflags(ifp) & IFF_UP) ) { + mve_stop(sc); + /* clear flag */ + if_setdrvflagbits(sc->ifp, 0, IFF_DRV_RUNNING); + continue; + } + + if ( ! (if_getdrvflags(ifp) & IFF_DRV_RUNNING) ) { + /* event could have been pending at the time hw was stopped; + * just ignore... + */ + continue; + } + + if ( (evs & IRQ_EVENT) ) { + irqstat = BSP_mve_ack_irqs(sc->mp); + } else { + irqstat = 0; + } + + if ( (MV643XX_ETH_EXT_IRQ_LINK_CHG & irqstat) && sc->mii_softc ) { + /* phy status changed */ + mii_pollstat( sc->mii_softc ); + } + + /* free tx chain and send */ + if ( (evs & TX_EVENT) || (MV643XX_ETH_EXT_IRQ_TX_DONE & irqstat) ) { + while ( (avail = BSP_mve_swipe_tx( sc->mp )) > TX_LOWWATER ) { + IF_DEQUEUE( &ifp->if_snd, m ); + if ( ! m ) { + /* clear active bit */ + if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); + break; + } + sndStat = mve_send_mbuf( sc, m ); + if ( sndStat < 0 ) { + /* maybe not enough space right now; requeue and wait for next IRQ */ + IF_PREPEND( &ifp->if_snd, m ); + break; + } + } + } + if ( (MV643XX_ETH_IRQ_RX_DONE & irqstat) ) { + BSP_mve_swipe_rx(sc->mp); + } + + BSP_mve_enable_irqs(sc->mp); + } + + mve_unlock( sc, "daemon (xit)" ); +} + +static void +release_tx_mbuf(void *user_buf, void *closure, int error_on_tx_occurred) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*)closure; +struct ifnet *ifp = sc->ifp; +struct mbuf *mb = (struct mbuf*)user_buf; + + if ( error_on_tx_occurred ) { + if_inc_counter( ifp, IFCOUNTER_OERRORS, 1 ); + } else { + if_inc_counter( ifp, IFCOUNTER_OPACKETS, 1 ); + if_inc_counter( ifp, IFCOUNTER_OBYTES, mb->m_pkthdr.len ); + } + m_freem(mb); +} + +static void * +alloc_rx_mbuf(int *p_size, uintptr_t *p_data) +{ +struct mbuf *m; +unsigned long l,o; + + m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); + + if ( m ) { + + o = mtod(m, unsigned long); + l = MV643XX_ALIGN(o, RX_BUF_ALIGNMENT) - o; + + /* align start of buffer */ + m->m_data += l; + + /* reduced length */ + l = MCLBYTES - l; + + m->m_len = m->m_pkthdr.len = l; + *p_size = m->m_len; + *p_data = mtod(m, uintptr_t); + } + + return (void*) m; +} + + +static void +consume_rx_mbuf(void *user_buf, void *closure, int len) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*)closure; +struct ifnet *ifp = sc->ifp; +struct mbuf *m = (struct mbuf*)user_buf; + + if ( len <= 0 ) { + if_inc_counter( ifp, IFCOUNTER_IQDROPS, 1 ); + if ( len < 0 ) { + if_inc_counter( ifp, IFCOUNTER_IERRORS, 1 ); + } + m_freem(m); + } else { + m->m_len = m->m_pkthdr.len = len - ETH_RX_OFFSET - ETH_CRC_LEN; + m->m_data += ETH_RX_OFFSET; + m->m_pkthdr.rcvif = ifp; + + if_inc_counter( ifp, IFCOUNTER_IPACKETS, 1 ); + if_inc_counter( ifp, IFCOUNTER_IBYTES, m->m_pkthdr.len ); + + if (0) { + /* Low-level debugging */ + int i; + for (i=0; im_len; i++) { + if ( !(i&15) ) + printk("\n"); + printk("0x%02x ",mtod(m,char*)[i]); + } + printk("\n"); + } + + mve_unlock( sc, "rx_cleanup" ); + (*ifp->if_input)(ifp, m); + mve_lock( sc, "rx_cleanup" ); + } +} + +/* Translate IFFLAGS to low-level driver representation */ +static int +xlateMediaFlags(const struct mii_data *mid) +{ +int lowLevelFlags = 0; +int msk = IFM_AVALID | IFM_ACTIVE; + + if ( (mid->mii_media_status & msk) == msk ) { + lowLevelFlags |= MV643XX_MEDIA_LINK; + + if ( IFM_OPTIONS( mid->mii_media_active ) & IFM_FDX ) { + lowLevelFlags |= MV643XX_MEDIA_FD; + } + + switch ( IFM_ETHER_SUBTYPE_GET( mid->mii_media_active ) ) { + default: +#ifdef MVETH_DEBUG + printk(DRVNAME"xlateMediaFlags: UNKNOWN SPEED\n"); +#endif + break; /* UNKNOWN -- FIXME */ + case IFM_10_T: +#ifdef MVETH_DEBUG + printk(DRVNAME"xlateMediaFlags: 10baseT\n"); +#endif + lowLevelFlags |= MV643XX_MEDIA_10; + break; + case IFM_100_TX: +#ifdef MVETH_DEBUG + printk(DRVNAME"xlateMediaFlags: 100baseT\n"); +#endif + lowLevelFlags |= MV643XX_MEDIA_100; + break; + case IFM_1000_T: +#ifdef MVETH_DEBUG + printk(DRVNAME"xlateMediaFlags: 1000baseT\n"); +#endif + lowLevelFlags |= MV643XX_MEDIA_1000; + break; + } + } else { +#ifdef MVETH_DEBUG + printk(DRVNAME"xlateMediaFlags: NO LINK\n"); +#endif + } + return lowLevelFlags; +} + +static void +mve_init(void *arg) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*)arg; +struct ifnet *ifp = sc->ifp; +int lowLevelMediaStatus = 0; +int promisc; + +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_init (entering)\n"); +#endif + + if ( sc->mii_softc ) { + mii_pollstat( sc->mii_softc ); + lowLevelMediaStatus = xlateMediaFlags( sc->mii_softc ); + if ( (lowLevelMediaStatus & MV643XX_MEDIA_LINK) ) { + if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); + } else { + if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0); + } + } + + promisc = !! (if_getdrvflags(ifp) & IFF_PROMISC); + + BSP_mve_init_hw(sc->mp, promisc, if_getlladdr(ifp), lowLevelMediaStatus); + + /* if promiscuous then there is no need to change */ + if ( ! promisc ) { + mve_set_filters(ifp); + } + + + if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0); +} + +static void +mve_start(struct ifnet *ifp) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) if_getsoftc( ifp ); + mve_lock( sc, "mve_start" ); + if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0); + mve_unlock( sc, "mve_start" ); + mve_send_event( sc, TX_EVENT ); +} + +static int +mve_ioctl(struct ifnet *ifp, ioctl_command_t cmd, caddr_t data) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) if_getsoftc( ifp ); +struct ifreq *ifr = (struct ifreq *)data; +int err = 0; +int f, df; + +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_ioctl (entering)\n"); +#endif + + mve_lock( sc, "mve_ioctl" ); + + switch ( cmd ) { + case SIOCSIFFLAGS: + f = if_getflags( ifp ); + df = if_getdrvflags( ifp ); + if ( (f & IFF_UP) ) { + if ( ! ( df & IFF_DRV_RUNNING ) ) { + mve_init( (void*)sc ); + } else { + if ( (f & IFF_PROMISC) != (sc->oif_flags & IFF_PROMISC) ) { + mve_set_filters(ifp); + } + /* FIXME: other flag changes are ignored/unimplemented */ + } + } else { + if ( df & IFF_DRV_RUNNING ) { + mve_stop(sc); + } + } + sc->oif_flags = f; + break; + + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + if ( sc->mii_softc ) { + err = ifmedia_ioctl( ifp, ifr, &sc->mii_softc->mii_media, cmd ); + } else { + err = EINVAL; + } + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + if ( if_getdrvflags( ifp ) & IFF_DRV_RUNNING ) { + mve_set_filters(ifp); + } + break; + + case SIO_RTEMS_SHOW_STATS: + BSP_mve_dump_stats(sc->mp, stdout); + break; + + default: + err = ether_ioctl(ifp, cmd, data); + break; + } + + mve_unlock( sc, "mve_ioctl" ); + + return err; +} + +/* SIO RTEMS_SHOW_STATS is too cumbersome to use -- for debugging, provide direct hack */ +int +mv643xx_nexus_dump_stats(int unit, FILE *f) +{ + if ( unit < 0 || unit >= MV643XXETH_NUM_DRIVER_SLOTS || ! ifaces[unit] ) + return -EINVAL; + if ( ! f ) + f = stdout; + BSP_mve_dump_stats(ifaces[unit]->mp, f); + return 0; +} + +/* + * Used to update speed settings in the hardware + * when the phy setup changes. + * + * ASSUME: caller holds lock + */ +static void +mve_ack_link_change(struct mve_enet_softc *sc) +{ +struct mii_data *mii = sc->mii_softc; +int lowLevelMediaStatus; + + if ( !mii ) + return; + + lowLevelMediaStatus = xlateMediaFlags( mii ); + + if ( (lowLevelMediaStatus & MV643XX_MEDIA_LINK) ) { + BSP_mve_update_serial_port( sc->mp, lowLevelMediaStatus ); + if_setdrvflagbits( sc->ifp, 0, IFF_DRV_OACTIVE ); + mve_start( sc->ifp ); + } else { + if_setdrvflagbits( sc->ifp, IFF_DRV_OACTIVE, 0 ); + } +} + +/* Callback from ifmedia_ioctl() + * + * Caller probably holds the lock already but + * since it is recursive we may as well make sure + * in case there are other possible execution paths. + */ +static int +mve_media_change(struct ifnet *ifp) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) if_getsoftc( ifp ); +struct mii_data *mii = sc->mii_softc; +int err; + +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_media_change\n"); +#endif + + if ( ! mii ) { + return ENXIO; + } + + err = mii_mediachg( mii ); + + return err; +} + +static void +mve_media_status(struct ifnet *ifp, struct ifmediareq *ifmr) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) if_getsoftc( ifp ); +struct mii_data *mii = sc->mii_softc; + +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_media_status\n"); +#endif + + if ( mii ) { + mii_pollstat( mii ); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + } +} + +static int +mve_attach(device_t dev) +{ +struct mve_enet_softc *sc; +struct ifnet *ifp; +uint8_t hwaddr[ETHER_ADDR_LEN]; +struct mveth_private *mp; +int unit = device_get_unit(dev); +int tx_ring_size = MV643XX_TX_RING_SIZE; +int rx_ring_size = MV643XX_RX_RING_SIZE; +int tx_q_size = MV643XX_TX_QUEUE_SIZE; + + sc = device_get_softc( dev ); + sc->dev = dev; + sc->ifp = ifp = if_alloc(IFT_ETHER); + sc->daemonTid = 0; + sc->mii_softc = 0; + +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_attach (entering)\n"); +#endif + + mtx_init( &sc->mtx, device_get_nameunit( sc->dev ), MTX_NETWORK_LOCK, MTX_RECURSE ); + callout_init_mtx( &sc->wdCallout, &sc->mtx, 0 ); + + if_setsoftc ( ifp, sc ); + if_initname ( ifp, device_get_name(dev), unit); + if_setinitfn ( ifp, mve_init ); + if_setioctlfn ( ifp, mve_ioctl ); + if_setstartfn ( ifp, mve_start ); + if_setflags ( ifp, (IFF_BROADCAST | IFF_MULTICAST | IFF_SIMPLEX) ); + sc->oif_flags = if_getflags( ifp ); + + if_setsendqlen ( ifp, tx_q_size ); + if_setsendqready( ifp ); + + mp = BSP_mve_create( + unit + 1, /* low-level driver' unit numbers are 1-based */ + 0, + mve_isr, (void*)sc, + release_tx_mbuf, (void*)sc, + alloc_rx_mbuf, + consume_rx_mbuf, (void*)sc, + rx_ring_size, + tx_ring_size, + ( MV643XX_ETH_IRQ_RX_DONE + | MV643XX_ETH_EXT_IRQ_TX_DONE + | MV643XX_ETH_EXT_IRQ_LINK_CHG)); + + if ( ! mp ) { + rtems_panic("Unable to create mv643xx low-level driver"); + } + + sc->mp = mp; + + BSP_mve_read_eaddr( mp, hwaddr ); + + if ( 0 == mii_attach( sc->dev, + &sc->miibus, + ifp, + mve_media_change, + mve_media_status, + BMSR_DEFCAPMASK, + MVE643XX_DUMMY_PHY, + MII_OFFSET_ANY, + 0 ) ) { + sc->mii_softc = device_get_softc( sc->miibus ); + } + + sc->daemonTid = rtems_bsdnet_newproc("MVE", 4096, mve_daemon, (void*)sc); + + ether_ifattach( ifp, hwaddr ); + +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_attach (leaving)\n"); +#endif + + ifaces[unit] = sc; + + return 0; +} + +static int +mve_miibus_read_reg(device_t dev, int phy, int reg) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) device_get_softc(dev); + + /* low-level driver knows what phy to use; ignore arg */ + return (int) BSP_mve_mii_read( sc->mp, reg ); +} + +static int +mve_miibus_write_reg(device_t dev, int phy, int reg, int val) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) device_get_softc(dev); + + /* low-level driver knows what phy to use; ignore arg */ + BSP_mve_mii_write( sc->mp, reg, val ); + return 0; +} + +static void +mve_miibus_statchg(device_t dev) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) device_get_softc(dev); +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_miibus_statchg\n"); +#endif + /* assume this ends up being called either from the ioctl or the driver + * task -- either of which holds the lock. + */ + mve_ack_link_change( sc ); +} + +static void +mve_miibus_linkchg(device_t dev) +{ +struct mve_enet_softc *sc = (struct mve_enet_softc*) device_get_softc(dev); +#ifdef MVETH_DEBUG + printk(DRVNAME": mve_miibus_linkchg\n"); +#endif + /* assume this ends up being called either from the ioctl or the driver + * task -- either of which holds the lock. + */ + mve_ack_link_change( sc ); +} + + +static device_method_t mve_methods[] = { + DEVMETHOD(device_probe, mve_probe ), + DEVMETHOD(device_attach, mve_attach), + + DEVMETHOD(miibus_readreg, mve_miibus_read_reg ), + DEVMETHOD(miibus_writereg, mve_miibus_write_reg), + DEVMETHOD(miibus_statchg , mve_miibus_statchg ), + DEVMETHOD(miibus_linkchg , mve_miibus_linkchg ), + + DEVMETHOD_END +}; + +static driver_t mve_nexus_driver = { + "mve", + mve_methods, + sizeof( struct mve_enet_softc ) +}; + +static devclass_t mve_devclass; + +DRIVER_MODULE(mve, nexus, mve_nexus_driver, mve_devclass, 0, 0); +DRIVER_MODULE(miibus, mve, miibus_driver, miibus_devclass, 0, 0); + +MODULE_DEPEND(mve, nexus, 1, 1, 1); +MODULE_DEPEND(mve, ether, 1, 1, 1); + +#endif /* LIBBSP_BEATNIK_BSP_H */