summaryrefslogtreecommitdiffstats
path: root/bsps/powerpc/psim/net/if_sim.c
diff options
context:
space:
mode:
Diffstat (limited to 'bsps/powerpc/psim/net/if_sim.c')
-rw-r--r--bsps/powerpc/psim/net/if_sim.c504
1 files changed, 504 insertions, 0 deletions
diff --git a/bsps/powerpc/psim/net/if_sim.c b/bsps/powerpc/psim/net/if_sim.c
new file mode 100644
index 0000000000..3bb1ec362d
--- /dev/null
+++ b/bsps/powerpc/psim/net/if_sim.c
@@ -0,0 +1,504 @@
+/* Trivial driver for PSIM's emulated ethernet device 'hw_ethtap'.
+ *
+ * NOTE: At the time of this writing (2009/1) 'hw_ethtap' requires
+ * a patched version of PSIM -- the vanilla version does not
+ * implement this device.
+ * Also, support for this device is currently only available
+ * on a linux host.
+ *
+ * Author/Copyright: Till Straumann <Till.Straumann@TU-Berlin.de>
+ *
+ * LICENSE
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution or at
+ * http://www.rtems.org/license/LICENSE.
+ *
+ */
+
+#include <bsp.h>
+#include <rtems.h>
+#include <bsp/irq.h>
+#include <psim.h>
+#include <libcpu/io.h>
+#include <inttypes.h>
+
+
+#ifndef KERNEL
+#define KERNEL
+#endif
+#ifndef _KERNEL
+#define _KERNEL
+#endif
+
+#include <rtems/rtems_bsdnet.h>
+#include <sys/param.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <stdio.h>
+
+#define IFSIM_SLOTS 1
+
+#define DRVNAME "if_sim"
+
+#define IFSIM_TX_BUF_REG 1
+#define IFSIM_TX_STA_REG 2
+
+#define IFSIM_RX_BUF_REG 4
+#define IFSIM_RX_STA_REG 5
+
+#define IFSIM_RX_CNT_MSK 0xffff
+#define IFSIM_RX_STA_DONE (1<<31)
+
+#define IFSIM_RX_ERR_NOSPC (1<<20) /* buffer too small */
+#define IFSIM_RX_ERR_DMA (1<<21) /* DMA error */
+#define IFSIM_RX_ERR_RD (1<<23) /* file read error */
+
+#define IFSIM_TX_STA_LST (1<<16)
+#define IFSIM_TX_STA_DONE (1<<31)
+
+#define IFSIM_IEN_REG 6
+#define IFSIM_IRQ_REG 7
+
+#define IFSIM_RX_IRQ (1<<0)
+
+#define IFSIM_MACA0_REG 8
+#define IFSIM_MACA1_REG 9
+
+#define IFSIM_CSR_REG 10
+#define IFSIM_CSR_PROM (1<<0)
+/* Enable CRC generation/checking */
+#define IFSIM_CSR_CRCEN (1<<1)
+
+
+#define RX_BUF_ALIGNMENT 1
+#define ETH_RX_OFFSET 0
+
+/*
+ * Align 'p' up to a multiple of 'a' which must be
+ * a power of two. Result is cast to (uintptr_t)
+ */
+#define ALIGNTO(p,a) ((((uintptr_t)(p)) + (a) - 1) & ~((a)-1))
+
+
+typedef volatile unsigned reg_t;
+
+struct ifsim_private {
+ reg_t *base;
+ void *rbuf;
+ unsigned rx_cserrs;
+ unsigned rx_err_nospc;
+ unsigned rx_err_dma;
+ unsigned rx_err_rd;
+ unsigned rx_nobufs;
+ unsigned rx_irqs;
+};
+
+struct ifsim_softc {
+ struct arpcom arpcom;
+ struct ifsim_private pvt;
+};
+
+struct ifsim_softc theIfSims[IFSIM_SLOTS] = {{{{0}}} };
+
+rtems_id ifsim_tid = 0;
+
+static __inline__ uint32_t
+ifsim_in(struct ifsim_softc *sc, unsigned regno)
+{
+ return in_be32((volatile uint32_t *) (sc->pvt.base + regno));
+}
+
+static __inline__ void
+ifsim_out(struct ifsim_softc *sc, unsigned regno, uint32_t v)
+{
+ out_be32((volatile uint32_t *) (sc->pvt.base + regno), v);
+}
+
+static void *
+alloc_mbuf_rx(int *psz, uintptr_t *paddr)
+{
+struct mbuf *m;
+unsigned long l,o;
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if ( !m )
+ return 0;
+ MCLGET(m, M_DONTWAIT);
+ if ( ! (m->m_flags & M_EXT) ) {
+ m_freem(m);
+ return 0;
+ }
+
+ o = mtod(m, unsigned long);
+ l = ALIGNTO(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;
+ *psz = m->m_len;
+ *paddr = mtod(m, unsigned long);
+
+ return (void*) m;
+}
+
+static int
+get_rxbuf(struct ifsim_softc *sc)
+{
+int sz;
+uintptr_t addr;
+void *nbuf;
+
+ nbuf = alloc_mbuf_rx(&sz, &addr);
+
+ if ( nbuf ) {
+ sc->pvt.rbuf = nbuf;
+ ifsim_out(sc, IFSIM_RX_BUF_REG, addr);
+ ifsim_out(sc, IFSIM_RX_STA_REG, sz);
+ return 0;
+ }
+ return -1;
+}
+
+/* set/clear promiscuous mode */
+static void
+ifsim_upd_promisc(struct ifsim_softc *sc)
+{
+uint32_t ncsr, csr;
+
+ ncsr = csr = ifsim_in(sc, IFSIM_CSR_REG);
+
+ if ( sc->arpcom.ac_if.if_flags & IFF_PROMISC )
+ ncsr |= IFSIM_CSR_PROM;
+ else
+ ncsr &= ~IFSIM_CSR_PROM;
+
+ if ( ncsr != csr )
+ ifsim_out(sc, IFSIM_CSR_REG, ncsr);
+}
+
+static void
+ifsim_init(void *arg)
+{
+struct ifsim_softc *sc = arg;
+struct ifnet *ifp = &sc->arpcom.ac_if;
+
+ if ( 0 == get_rxbuf(sc) ) {
+ ifsim_upd_promisc(sc);
+ ifp->if_flags |= IFF_RUNNING;
+ }
+}
+
+static void
+ifsim_start(struct ifnet *ifp)
+{
+struct ifsim_softc *sc = ifp->if_softc;
+struct mbuf *m, *mh, *m1;
+
+ while ( ifp->if_snd.ifq_head ) {
+ IF_DEQUEUE( &ifp->if_snd, mh );
+ for ( m=mh, m1 = m->m_next ; m1 ; m1 = m1->m_next ) {
+ ifsim_out(sc, IFSIM_TX_BUF_REG, mtod(m, uint32_t));
+ ifsim_out(sc, IFSIM_TX_STA_REG, m->m_len);
+ /* dummy-busywait; the emulated hardware DMAs our
+ * data away 'immediately' i.e., this loop is
+ * never executed
+ */
+ while ( ! (IFSIM_TX_STA_DONE & ifsim_in(sc, IFSIM_TX_STA_REG)) )
+ /* Loop */;
+ m = m1;
+ }
+ ifsim_out(sc, IFSIM_TX_BUF_REG, mtod(m, uint32_t));
+ ifsim_out(sc, IFSIM_TX_STA_REG, m->m_len | IFSIM_TX_STA_LST);
+
+ /* dummy-busywait; the emulated hardware DMAs our
+ * data away 'immediately' i.e., this loop is
+ * never executed
+ */
+ while ( ! (IFSIM_TX_STA_DONE & ifsim_in(sc, IFSIM_TX_STA_REG)) )
+ /* Loop */;
+
+ m_freem(mh);
+ }
+}
+
+static int
+ifsim_ioctl(struct ifnet *ifp, ioctl_command_t cmd, caddr_t data)
+{
+struct ifsim_softc *sc = ifp->if_softc;
+int rval = 0;
+int f;
+
+ switch (cmd) {
+
+ case SIOCSIFFLAGS:
+ f = ifp->if_flags;
+ if ( f & IFF_UP ) {
+ if ( ! (f & IFF_RUNNING) ) {
+ ifsim_init(sc);
+ } else {
+ ifsim_upd_promisc(sc);
+ }
+ /* FIXME: handle other flags here */
+ } else {
+ if ( f & IFF_RUNNING ) {
+ printk("WARNING: bringing "DRVNAME" down not really implemented\n");
+ ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
+ }
+ }
+
+ break;
+
+ case SIO_RTEMS_SHOW_STATS:
+ printf("RX bad chksum : %u\n", sc->pvt.rx_cserrs);
+ printf("RX no space : %u\n", sc->pvt.rx_err_nospc);
+ printf("RX bad DMA : %u\n", sc->pvt.rx_err_dma);
+ printf("RX read errors : %u\n", sc->pvt.rx_err_rd);
+ printf("rx_nobufs : %u\n", sc->pvt.rx_nobufs);
+ printf("rx_irqs : %u\n", sc->pvt.rx_irqs);
+ break;
+
+ default:
+ rval = ether_ioctl(ifp, cmd, data);
+ break;
+ }
+
+ return rval;
+}
+
+static void ifsim_irq_on(const rtems_irq_connect_data *pd)
+{
+struct ifsim_softc *sc = pd->handle;
+ ifsim_out(sc, IFSIM_IEN_REG, IFSIM_RX_IRQ);
+}
+
+static void ifsim_irq_off(const rtems_irq_connect_data *pd)
+{
+struct ifsim_softc *sc = pd->handle;
+ ifsim_out(sc, IFSIM_IEN_REG, 0);
+}
+
+static int ifsim_irq_ison(const rtems_irq_connect_data *pd)
+{
+struct ifsim_softc *sc = pd->handle;
+ return ifsim_in(sc, IFSIM_IEN_REG) & IFSIM_RX_IRQ ? 1 : 0;
+}
+
+static void
+ifsim_isr(void *arg)
+{
+struct ifsim_softc *sc = arg;
+
+ sc->pvt.rx_irqs++;
+#ifdef IRQ_DEBUG
+ printk("ISR happened\n");
+#endif
+
+ ifsim_out(sc, IFSIM_IEN_REG, 0);
+ rtems_bsdnet_event_send(ifsim_tid, (1<<(sc-theIfSims)));
+}
+
+static void
+ifsim_task(void *arg)
+{
+struct ifsim_softc *sc;
+uint32_t sta;
+struct ifnet *ifp;
+unsigned len;
+rtems_event_set evs;
+
+ while (1) {
+
+ rtems_bsdnet_event_receive(
+ ((1<<IFSIM_SLOTS)-1),
+ RTEMS_WAIT | RTEMS_EVENT_ANY,
+ RTEMS_NO_TIMEOUT,
+ &evs);
+
+ evs &= ((1<<IFSIM_SLOTS)-1);
+
+#ifdef IRQ_DEBUG
+ printk("Task got evs %u\n", evs);
+#endif
+
+ for ( sc = theIfSims; evs; evs>>=1, sc++ ) {
+
+ if ( ! ( evs & 1 ) )
+ continue;
+
+ ifp = &sc->arpcom.ac_if;
+
+ while ( ifsim_in(sc, IFSIM_IRQ_REG) & IFSIM_RX_IRQ ) {
+ struct mbuf *m = sc->pvt.rbuf;
+
+ sta = ifsim_in(sc, IFSIM_RX_STA_REG);
+
+
+ if ( (sta & IFSIM_RX_STA_DONE) ) {
+
+ if ( (ifp->if_flags & IFF_RUNNING) ) {
+ if ( 0 == get_rxbuf(sc) ) {
+ /* enqueue packet */
+ struct ether_header *eh;
+ uint32_t crc_net, crc;
+ int crc_len;
+
+ crc_len = (IFSIM_CSR_CRCEN & ifsim_in(sc, IFSIM_CSR_REG)) ? sizeof(crc_net) : 0;
+
+ len = (sta & IFSIM_RX_CNT_MSK) - crc_len;
+
+ eh = (struct ether_header*)(mtod(m,unsigned long) + ETH_RX_OFFSET);
+
+ m->m_len = m->m_pkthdr.len = len - sizeof(struct ether_header) - ETH_RX_OFFSET;
+ m->m_data += sizeof(struct ether_header) + ETH_RX_OFFSET;
+ m->m_pkthdr.rcvif = ifp;
+
+#ifdef DEBUG_WO_BSDNET
+ {
+ int i;
+ for ( i=0; i<len + crc_len; ) {
+ printk("%02X ",((char*)eh)[i]);
+ if ( 0 == (++i & 0xf) )
+ fputc('\n',stdout);
+ }
+ if ( i & 0xf )
+ fputc('\n', stdout);
+ printk("*****\n");
+ }
+#endif
+
+ if ( crc_len
+ && (memcpy(&crc_net, (char*)eh + len, crc_len),
+ (crc = (ether_crc32_le((uint8_t *)eh, len) ^ 0xffffffff)) != crc_net) ) {
+ printk("CSUM: me 0x%08" PRIx32 ", them 0x%08" PRIx32 "\n", crc, crc_net);
+ sc->pvt.rx_cserrs++;
+ } else {
+
+ ifp->if_ipackets++;
+ ifp->if_ibytes += len;
+
+#ifdef DEBUG_WO_BSDNET
+ m_freem(m);
+#else
+ ether_input(ifp, eh, m);
+#endif
+
+ m = 0;
+ }
+
+ } else {
+ /* throw away and reuse buffer */
+ sc->pvt.rx_nobufs++;
+ }
+ }
+ } else {
+ /* throw away and reuse buffer */
+ if ( (sta & IFSIM_RX_ERR_NOSPC) )
+ sc->pvt.rx_err_nospc++;
+ if ( (sta & IFSIM_RX_ERR_DMA) )
+ sc->pvt.rx_err_dma++;
+ if ( (sta & IFSIM_RX_ERR_RD) )
+ sc->pvt.rx_err_rd++;
+ }
+
+ if ( m ) {
+ /* reuse */
+ ifsim_out(sc, IFSIM_RX_STA_REG, m->m_pkthdr.len);
+ }
+ }
+ /* re-enable interrupt */
+ ifsim_out(sc, IFSIM_IEN_REG, IFSIM_RX_IRQ);
+ }
+ }
+}
+
+int
+rtems_ifsim_attach(struct rtems_bsdnet_ifconfig *ifcfg, int attaching)
+{
+char *unitName;
+int unit;
+struct ifsim_softc *sc;
+struct ifnet *ifp;
+uint32_t v;
+rtems_irq_connect_data ihdl;
+
+ if ( !attaching )
+ return -1;
+
+ unit = rtems_bsdnet_parse_driver_name(ifcfg, &unitName);
+
+ if ( unit <= 0 || unit > IFSIM_SLOTS ) {
+ printk(DRVNAME": Bad unit number %i; must be 1..%i\n", unit, IFSIM_SLOTS);
+ return 1;
+ }
+
+ sc = &theIfSims[unit-1];
+ ifp = &sc->arpcom.ac_if;
+
+ memset(&sc->pvt, 0, sizeof(sc->pvt));
+
+ sc->pvt.base = (reg_t*)ifcfg->port;
+
+ if ( 0 == sc->pvt.base )
+ sc->pvt.base = (reg_t*)PSIM.Ethtap;
+
+
+ sc->pvt.rbuf = 0;
+
+ if ( !ifsim_tid ) {
+ ifsim_tid = rtems_bsdnet_newproc("IFSM", 10000, ifsim_task, 0);
+ }
+
+ ihdl.name = ifcfg->irno;
+ ihdl.hdl = ifsim_isr;
+ ihdl.handle = sc;
+ ihdl.on = ifsim_irq_on;
+ ihdl.off = ifsim_irq_off;
+ ihdl.isOn = ifsim_irq_ison;
+
+ if ( ! BSP_install_rtems_irq_handler(&ihdl) ) {
+ printk(DRVNAME"_attach(): installing ISR failed!\n");
+ return -1;
+ }
+
+ if ( ifcfg->hardware_address ) {
+ memcpy(sc->arpcom.ac_enaddr, ifcfg->hardware_address, ETHER_ADDR_LEN);
+ } else {
+ v = ifsim_in(sc, IFSIM_MACA0_REG);
+ memcpy(sc->arpcom.ac_enaddr, &v, 4);
+ v = ifsim_in(sc, IFSIM_MACA1_REG);
+ memcpy(sc->arpcom.ac_enaddr+4, &v, 2);
+ }
+
+ ifp->if_softc = sc;
+ ifp->if_unit = unit;
+ ifp->if_name = unitName;
+
+ ifp->if_mtu = ifcfg->mtu ? ifcfg->mtu : ETHERMTU;
+
+ ifp->if_init = ifsim_init;
+ ifp->if_ioctl = ifsim_ioctl;
+ ifp->if_start = ifsim_start;
+ ifp->if_output = ether_output;
+
+ ifp->if_watchdog = 0;
+ ifp->if_timer = 0;
+
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX;
+
+ ifp->if_snd.ifq_maxlen = ifqmaxlen;
+
+ if_attach(ifp);
+ ether_ifattach(ifp);
+
+ return 0;
+}