NetBSD Problem Report #44624

From www@NetBSD.org  Tue Feb 22 19:41:02 2011
Return-Path: <www@NetBSD.org>
Received: from mail.netbsd.org (mail.netbsd.org [204.152.190.11])
	by www.NetBSD.org (Postfix) with ESMTP id CA11263B880
	for <gnats-bugs@gnats.NetBSD.org>; Tue, 22 Feb 2011 19:41:02 +0000 (UTC)
Message-Id: <20110222194100.3B81563B842@www.NetBSD.org>
Date: Tue, 22 Feb 2011 19:41:00 +0000 (UTC)
From: paul@xpg.dk
Reply-To: paul@xpg.dk
To: gnats-bugs@NetBSD.org
Subject: Improved DM9000 Ethernet Driver
X-Send-Pr-Version: www-1.0

>Number:         44624
>Category:       kern
>Synopsis:       Improved DM9000 Ethernet Driver
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    kern-bug-people
>State:          closed
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Tue Feb 22 19:45:00 +0000 2011
>Closed-Date:    Sat Dec 29 06:07:52 +0000 2018
>Last-Modified:  Sat Dec 29 06:07:52 +0000 2018
>Originator:     Paul Fleischer
>Release:        
>Organization:
>Environment:
>Description:
The current DM9000 ethernet driver is somewhat rough. The attached patch improves it in the following areas:

- Proper multicast support
- Carrier detection, and proper media handling
- Restructure code to make it easier to implement 8 and 32-bit transfers to DM9000 chip.
- Various cleanups
>How-To-Repeat:

>Fix:
--- dm9000var.h-1.1	2011-02-22 20:36:08.000000000 +0100
+++ dm9000var.h	2011-02-19 19:38:23.000000000 +0100
@@ -63,7 +63,7 @@
 #ifndef _DEV_IC_DM9000VAR_H_
 #define _DEV_IC_DM9000VAR_H_

-#include <sys/mutex.h>
+#include <sys/callout.h>

 #define DM9000_MODE_8BIT 2
 #define DM9000_MODE_16BIT 0
@@ -75,6 +75,9 @@
 	struct ethercom sc_ethercom;	/* Ethernet common data */
 	struct ifmedia	sc_media;	/* Media control structures */

+	uint		sc_media_active;
+	uint		sc_media_status;
+
 	bus_space_tag_t		sc_iot;
 	bus_space_handle_t	sc_ioh;
 	void		*sc_ih;
@@ -85,7 +88,7 @@
 	uint16_t	sc_vendor_id;
 	uint16_t	sc_product_id;

-	uint16_t	io_mode;
+	uint8_t		sc_data_width;

 	uint8_t		sc_enaddr[ETHER_ADDR_LEN];

@@ -94,6 +97,13 @@
 					   for transmission. */
 	uint16_t	txready_length;

+	int (*sc_pkt_write)(struct dme_softc*, struct mbuf *);
+	int (*sc_pkt_read)(struct dme_softc*, struct ifnet *, struct mbuf **);
+
+	callout_t	sc_link_callout;
+
+	bool		sc_phy_initialized;
+
 #ifdef DIAGNOSTIC
 	bool		sc_inside_interrupt;
 #endif
@@ -104,6 +114,9 @@
 int	dme_detach(struct dme_softc *);
 int	dme_intr(void *);

+/* Helper method used by sc_pkt_read */
+struct mbuf* dme_alloc_receive_buffer(struct ifnet *, unsigned int);
+
 /* Inline memory access methods */
 static inline uint8_t
 dme_read(struct dme_softc *sc, int reg)
--- dm9000reg.h-1.1	2011-02-22 20:35:56.000000000 +0100
+++ dm9000reg.h	2011-02-19 19:38:23.000000000 +0100
@@ -195,6 +195,10 @@
 #define DM9000_PHY_PHYID1	0x02
 #define DM9000_PHY_PHYID2 	0x03
 #define DM9000_PHY_ANAR 	0x04
+#define  DM9000_PHY_ANAR_10_HDX	 (1<<5)
+#define  DM9000_PHY_ANAR_10_FDX  (1<<6)
+#define  DM9000_PHY_ANAR_TX_HDX  (1<<7)
+#define  DM9000_PHY_ANAR_TX_FDX  (1<<8)
 #define DM9000_PHY_ANLPAR	0x05
 #define DM9000_PHY_ANER 	0x06
 #define DM9000_PHY_DSCR 	0x16
--- dm9000.c-1.2	2011-02-22 20:35:41.000000000 +0100
+++ dm9000.c	2011-02-19 19:38:23.000000000 +0100
@@ -89,6 +89,7 @@
 #include <sys/cdefs.h>

 #include <sys/param.h>
+#include <sys/kernel.h>
 #include <sys/systm.h>
 #include <sys/mbuf.h>
 #include <sys/syslog.h>
@@ -117,7 +118,7 @@

 #if 1
 #undef DM9000_DEBUG
-#undef  DM9000_TX_DEBUG
+#undef DM9000_TX_DEBUG
 #undef DM9000_TX_DATA_DEBUG
 #undef DM9000_RX_DEBUG
 #undef  DM9000_RX_DATA_DEBUG
@@ -159,9 +160,13 @@
 #define TX_DATA_DPRINTF(s) do {} while (/*CONSTCOND*/0)
 #endif

-
+/*** Internal PHY functions ***/
 uint16_t dme_phy_read(struct dme_softc *sc, int reg);
-void dme_phy_write(struct dme_softc *sc, int reg, uint16_t value);
+void	dme_phy_write(struct dme_softc *sc, int reg, uint16_t value);
+void	dme_phy_init(struct dme_softc *sc);
+void	dme_phy_reset(struct dme_softc *sc);
+void	dme_phy_update_media(struct dme_softc *sc);
+void	dme_phy_check_link(void *arg);

 /*** Methods registered in struct ifnet ***/
 void	dme_start_output(struct ifnet *ifp);
@@ -186,6 +191,17 @@
 /* Software Initialize/Reset of the DM9000 */
 void    dme_reset(struct dme_softc *sc);

+/* Configure multicast filter */
+void	dme_set_addr_filter(struct dme_softc *sc);
+
+/* Set media */
+int	dme_set_media(struct dme_softc *sc, int media);
+
+/* Read/write packet data from/to DM9000 IC in various transfer sizes */
+int	dme_pkt_read_2(struct dme_softc *sc, struct ifnet *ifp, struct mbuf **outBuf);
+int	dme_pkt_write_2(struct dme_softc *sc, struct mbuf *bufChain);
+/* TODO: Implement 8 and 32 bit read/write functions */
+
 uint16_t
 dme_phy_read(struct dme_softc *sc, int reg)
 {
@@ -199,9 +215,6 @@
 	/* Wait until access to PHY has completed */
 	while (dme_read(sc, DM9000_EPCR) & DM9000_EPCR_ERRE);

-	/* XXX: The delay is probably not necessary as we just busy-waited */
-	delay(200);
-
 	/* Reset ERPRR-bit */
 	dme_write(sc, DM9000_EPCR, DM9000_EPCR_EPOS_PHY);

@@ -228,19 +241,163 @@
 	/* Wait until access to PHY has completed */
 	while(dme_read(sc, DM9000_EPCR) & DM9000_EPCR_ERRE);

-
-	/* XXX: The delay is probably not necessary as we just busy-waited */
-	delay(200);
-
 	/* Reset ERPRR-bit */
 	dme_write(sc, DM9000_EPCR, DM9000_EPCR_EPOS_PHY);
 }

+void
+dme_phy_init(struct dme_softc *sc)
+{
+	u_int ifm_media = sc->sc_media.ifm_media;
+	uint32_t bmcr, anar;
+
+	bmcr = dme_phy_read(sc, DM9000_PHY_BMCR);
+	anar = dme_phy_read(sc, DM9000_PHY_ANAR);
+
+	anar = anar & ~DM9000_PHY_ANAR_10_HDX
+		& ~DM9000_PHY_ANAR_10_FDX
+		& ~DM9000_PHY_ANAR_TX_HDX
+		& ~DM9000_PHY_ANAR_TX_FDX;
+
+	switch (IFM_SUBTYPE(ifm_media)) {
+	case IFM_AUTO:
+		bmcr |= DM9000_PHY_BMCR_AUTO_NEG_EN;
+		anar |= DM9000_PHY_ANAR_10_HDX |
+			DM9000_PHY_ANAR_10_FDX |
+			DM9000_PHY_ANAR_TX_HDX |
+			DM9000_PHY_ANAR_TX_FDX;
+		break;
+	case IFM_10_T:
+		//bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
+		bmcr &= ~DM9000_PHY_BMCR_SPEED_SELECT;
+		if (ifm_media & IFM_FDX)
+			anar |= DM9000_PHY_ANAR_10_FDX;
+		else
+			anar |= DM9000_PHY_ANAR_10_HDX;
+		break;
+	case IFM_100_TX:
+		//bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
+		bmcr |= DM9000_PHY_BMCR_SPEED_SELECT;
+		if (ifm_media & IFM_FDX)
+			anar |= DM9000_PHY_ANAR_TX_FDX;
+		else
+			anar |= DM9000_PHY_ANAR_TX_HDX;
+
+		break;
+	}
+
+	if(ifm_media & IFM_FDX) {
+		bmcr |= DM9000_PHY_BMCR_DUPLEX_MODE;
+	} else {
+		bmcr &= ~DM9000_PHY_BMCR_DUPLEX_MODE;
+	}
+
+	dme_phy_write(sc, DM9000_PHY_BMCR, bmcr);
+	dme_phy_write(sc, DM9000_PHY_ANAR, anar);
+}
+
+void
+dme_phy_reset(struct dme_softc *sc)
+{
+	uint32_t reg;
+
+	/* PHY Reset */
+	dme_phy_write(sc, DM9000_PHY_BMCR, DM9000_PHY_BMCR_RESET);
+
+	reg = dme_read(sc, DM9000_GPCR);
+	dme_write(sc, DM9000_GPCR, reg & ~DM9000_GPCR_GPIO0_OUT);
+	reg = dme_read(sc, DM9000_GPR);
+	dme_write(sc, DM9000_GPR, reg | DM9000_GPR_PHY_PWROFF);
+
+	dme_phy_init(sc);
+
+	reg = dme_read(sc, DM9000_GPR);
+	dme_write(sc, DM9000_GPR, reg & ~DM9000_GPR_PHY_PWROFF);
+	reg = dme_read(sc, DM9000_GPCR);
+	dme_write(sc, DM9000_GPCR, reg | DM9000_GPCR_GPIO0_OUT);
+
+	dme_phy_update_media(sc);
+}
+
+void
+dme_phy_update_media(struct dme_softc *sc)
+{
+	u_int ifm_media = sc->sc_media.ifm_media;
+	uint32_t reg;
+
+	if (IFM_SUBTYPE(ifm_media) == IFM_AUTO) {
+		/* If auto-negotiation is used, ensures that it is completed
+		 before trying to extract any media information. */
+		reg = dme_phy_read(sc, DM9000_PHY_BMSR);
+		if ((reg & DM9000_PHY_BMSR_AUTO_NEG_AB) == 0) {
+			/* Auto-negotation not possible, therefore there is no
+			   reason to try obtain any media information. */
+			return;
+		}
+
+		/* Then loop until the negotiation is completed. */
+		while ((reg & DM9000_PHY_BMSR_AUTO_NEG_COM) == 0) {
+			/* TODO: Bail out after a finite number of attempts
+			 in case something goes wrong. */
+			preempt();
+			reg = dme_phy_read(sc, DM9000_PHY_BMSR);
+		}
+	}
+
+
+	sc->sc_media_active = IFM_ETHER;
+	reg = dme_phy_read(sc, DM9000_PHY_BMCR);
+
+	if (reg & DM9000_PHY_BMCR_SPEED_SELECT) {
+		sc->sc_media_active |= IFM_100_TX;
+	} else {
+		sc->sc_media_active |= IFM_10_T;
+	}
+
+	if (reg & DM9000_PHY_BMCR_DUPLEX_MODE) {
+		sc->sc_media_active |= IFM_FDX;
+	}
+}
+
+void
+dme_phy_check_link(void *arg)
+{
+	struct dme_softc *sc = arg;
+	uint32_t reg;
+
+	reg = dme_read(sc, DM9000_NSR) & DM9000_NSR_LINKST;
+
+	if( reg )
+		reg = IFM_ETHER | IFM_AVALID | IFM_ACTIVE;
+	else {
+		reg = IFM_ETHER | IFM_AVALID;
+		sc->sc_media_active = IFM_NONE;
+	}
+
+	if ( (sc->sc_media_status != reg) && (reg & IFM_ACTIVE)) {
+		dme_phy_reset(sc);
+	}
+
+	sc->sc_media_status = reg;
+
+	callout_schedule(&sc->sc_link_callout, mstohz(2000));
+}
+
+int
+dme_set_media(struct dme_softc *sc, int media)
+{
+	sc->sc_media.ifm_media = media;
+	dme_phy_reset(sc);
+
+	return 0;
+}
+
 int
 dme_attach(struct dme_softc *sc, uint8_t *enaddr)
 {
-	struct ifnet *ifp = &sc->sc_ethercom.ec_if;
-	uint8_t b[2];
+	struct ifnet	*ifp = &sc->sc_ethercom.ec_if;
+	uint8_t		b[2];
+	uint16_t	io_mode;

 	dme_read_c(sc, DM9000_VID0, b, 2);
 #if BYTE_ORDER == BIG_ENDIAN
@@ -260,16 +417,6 @@
 		    sc->sc_product_id);
 	}

-#if 0
-	{
-		/* Force 10Mbps to test dme_phy_write */
-		uint16_t bmcr;
-		bmcr = dme_phy_read(sc, DM9000_PHY_BMCR);
-		bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
-		bmcr &= ~DM9000_PHY_BMCR_SPEED_SELECT; /* select 100Mbps */
-		dme_phy_write(sc, DM9000_PHY_BMCR, bmcr);
-	}
-#endif
 	/* Initialize ifnet structure. */
 	strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
 	ifp->if_softc = sc;
@@ -278,17 +425,28 @@
 	ifp->if_ioctl = dme_ioctl;
 	ifp->if_stop = dme_stop;
 	ifp->if_watchdog = NULL;	/* no watchdog at this stage */
-	ifp->if_flags = IFF_SIMPLEX | IFF_NOTRAILERS |
-		IFF_BROADCAST; /* No multicast support for now */
+	ifp->if_flags = IFF_SIMPLEX | IFF_NOTRAILERS | IFF_BROADCAST |
+			IFF_MULTICAST;
 	IFQ_SET_READY(&ifp->if_snd);

 	/* Initialize ifmedia structures. */
 	ifmedia_init(&sc->sc_media, 0, dme_mediachange, dme_mediastatus);
-	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_100_TX, 0, NULL);
-	ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_100_TX);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_AUTO, 0, NULL);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_FDX, 0, NULL);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T, 0, NULL);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_100_TX|IFM_FDX, 0, NULL);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_100_TX, 0, NULL);
+
+	ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_AUTO);

 	if (enaddr != NULL)
 		memcpy(sc->sc_enaddr, enaddr, sizeof(sc->sc_enaddr));
+	/* TODO: Support an EEPROM attached to the DM9000 chip */
+
+	callout_init(&sc->sc_link_callout, 0);
+	callout_setfunc(&sc->sc_link_callout, dme_phy_check_link, sc);
+
+	sc->sc_media_status = 0;

 	/* Configure DM9000 with the MAC address */
 	dme_write_c(sc, DM9000_PAB0, sc->sc_enaddr, 6);
@@ -325,28 +483,34 @@
 	}
 #endif

-	sc->io_mode = (dme_read(sc, DM9000_ISR) &
+	io_mode = (dme_read(sc, DM9000_ISR) &
 	    DM9000_IOMODE_MASK) >> DM9000_IOMODE_SHIFT;
-	if (sc->io_mode != DM9000_MODE_16BIT )
+	if (io_mode != DM9000_MODE_16BIT )
 		panic("DM9000: Only 16-bit mode is supported!\n");
-#ifdef DM9000_DEBUG
-	printf("DM9000 Operation Mode: ");
-	switch( sc->io_mode) {
+
+	DPRINTF(("DM9000 Operation Mode: "));
+	switch( io_mode) {
 	case DM9000_MODE_16BIT:
-		printf("16-bit mode");
+		DPRINTF(("16-bit mode"));
+		sc->sc_data_width = 2;
+		sc->sc_pkt_write = dme_pkt_write_2;
+		sc->sc_pkt_read = dme_pkt_read_2;
 		break;
 	case DM9000_MODE_32BIT:
-		printf("32-bit mode");
+		DPRINTF(("32-bit mode"));
+		sc->sc_data_width = 4;
 		break;
 	case DM9000_MODE_8BIT:
-		printf("8-bit mode");
+		DPRINTF(("8-bit mode"));
+		sc->sc_data_width = 1;
 		break;
-	case 3:
-		printf("Invalid mode");
+	default:
+		DPRINTF(("Invalid mode"));
 		break;
 	}
-	printf("\n");
-#endif
+	DPRINTF(("\n"));
+
+	callout_schedule(&sc->sc_link_callout, mstohz(2000));

 	return 0;
 }
@@ -357,6 +521,9 @@
 	struct ifnet *ifp = &sc->sc_ethercom.ec_if;
 	uint8_t status;

+
+	DPRINTF(("dme_intr: Begin\n"));
+
 	/* Disable interrupts */
 	dme_write(sc, DM9000_IMR, DM9000_IMR_PAR );

@@ -412,6 +579,8 @@
 	dme_write(sc, DM9000_IMR, DM9000_IMR_PAR | DM9000_IMR_PRM |
 		 DM9000_IMR_PTM);

+	DPRINTF(("dme_intr: End\n"));
+
 	return 1;
 }

@@ -422,6 +591,8 @@

 	sc = ifp->if_softc;

+	DPRINTF(("dme_start_output: Begin\n"));
+
 	if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING) {
 		printf("No output\n");
 		return;
@@ -444,15 +615,15 @@
 		 */
 		ifp->if_flags |= IFF_OACTIVE;
 	}
+
+	DPRINTF(("dme_start_output: End\n"));
 }

 void
 dme_prepare(struct dme_softc *sc, struct ifnet *ifp)
 {
-	struct mbuf *buf;
 	struct mbuf *bufChain;
 	uint16_t length;
-	uint8_t *write_ptr;

 	TX_DPRINTF(("dme_prepare: Entering\n"));

@@ -471,106 +642,16 @@
 	if (ifp->if_bpf)
 		bpf_mtap(ifp, bufChain);

-
-	length = 0;
-
-	/* XXX: This support 16-bit I/O mode only. */
-	/* XXX: This code must be factored out, such that architecture
-	   dependant versions can be supplied */
-
-	int left_over_count = 0; /* Number of bytes from previous mbuf, which
-				    need to be written with the next.*/
-	uint16_t left_over_buf = 0;
-
 	/* Setup the DM9000 to accept the writes, and then write each buf in
 	   the chain. */

 	TX_DATA_DPRINTF(("dme_prepare: Writing data: "));
 	bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->dme_io, DM9000_MWCMD);
-	for (buf = bufChain; buf != NULL; buf = buf->m_next) {
-		int to_write = buf->m_len;
-
-		length += to_write;
-
-		write_ptr = buf->m_data;
-		while (to_write > 0 ||
-		       (buf->m_next == NULL && left_over_count > 0)
-		       ) {
-			if (left_over_count > 0) {
-				uint8_t b = 0;
-				DPRINTF(("dme_prepare: "
-					"Writing left over byte\n"));
-
-				if (to_write > 0) {
-					b = *write_ptr;
-					to_write--;
-					write_ptr++;
-
-					DPRINTF(("Took single byte\n"));
-				} else {
-					DPRINTF(("Leftover in last run\n"));
-					length++;
-				}
-
-				/* Does shift direction depend on endianess? */
-				left_over_buf = left_over_buf | (b << 8);
-
-				bus_space_write_2(sc->sc_iot, sc->sc_ioh,
-						  sc->dme_data, left_over_buf);
-				TX_DATA_DPRINTF(("%02X ", left_over_buf));
-				left_over_count = 0;
-			} else if ((long)write_ptr % 2 != 0) {
-				/* Misaligned data */
-				DPRINTF(("dme_prepare: "
-					"Detected misaligned data\n"));
-				left_over_buf = *write_ptr;
-				left_over_count = 1;
-				write_ptr++;
-				to_write--;
-			} else {
-				int i;
-				uint16_t *dptr = (uint16_t*)write_ptr;
-
-				/* A block of aligned data. */
-				for(i = 0; i < to_write/2; i++) {
-					/* buf will be half-word aligned
-					 * all the time
-					 */
-					bus_space_write_2(sc->sc_iot,
-					    sc->sc_ioh, sc->dme_data, *dptr);
-					TX_DATA_DPRINTF(("%02X %02X ",
-					    *dptr & 0xFF, (*dptr>>8) & 0xFF));
-					dptr++;
-				}
-
-				write_ptr += i*2;
-				if (to_write % 2 != 0) {
-					DPRINTF(("dme_prepare: "
-						"to_write %% 2: %d\n",
-						to_write % 2));
-					left_over_count = 1;
-					/* XXX: Does this depend on
-					 * the endianess?
-					 */
-					left_over_buf = *write_ptr;
-					
-					write_ptr++;
-					to_write--;
-					DPRINTF(("dme_prepare: "
-						"to_write (after): %d\n",
-						to_write));
-					DPRINTF(("dme_prepare: i*2: %d\n",
-						i*2));
-				}
-				to_write -= i*2;
-			}
-		} /* while(...) */
-	} /* for(...) */
-
+	length = sc->sc_pkt_write(sc, bufChain);
 	TX_DATA_DPRINTF(("\n"));

-	if (length % 2 == 1) {
-		panic("dme_prepare: length is not a word-length");
+	if (length % sc->sc_data_width != 0) {
+		panic("dme_prepare: length is not compatible with IO_MODE");
 	}

 	sc->txready_length = length;
@@ -622,6 +703,14 @@
 		break;
 	default:
 		error = ether_ioctl(ifp, cmd, data);
+		if (error == ENETRESET) {
+			if (ifp->if_flags && IFF_RUNNING) {
+				/* Address list has changed, reconfigure
+				   filter */
+				dme_set_addr_filter(sc);
+			}
+			error = 0;
+		}
 		break;
 	}

@@ -647,22 +736,18 @@
 int
 dme_mediachange(struct ifnet *ifp)
 {
-	/* TODO: Make this function do something useful. */
-	return 0;
+	struct dme_softc *sc = ifp->if_softc;
+
+	return dme_set_media(sc, sc->sc_media.ifm_cur->ifm_media);
 }

 void
 dme_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
 {
-	/* TODO: Make this function do something useful. */
 	struct dme_softc *sc = ifp->if_softc;
-	ifmr->ifm_active = sc->sc_media.ifm_cur->ifm_media;

-	if (ifp->if_flags & IFF_UP) {
-		ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE;
-	} else {
-		ifmr->ifm_status = 0;
-	}
+	ifmr->ifm_active = sc->sc_media_active;
+	ifmr->ifm_status = sc->sc_media_status;
 }

 void
@@ -704,69 +789,14 @@
 		ready = bus_space_read_1(sc->sc_iot, sc->sc_ioh, sc->dme_data);
 		ready &= 0x03;	/* we only want bits 1:0 */
 		if (ready == 0x01) {
-			uint8_t rx_status;
-
-			uint16_t data;
-			uint16_t frame_length;
-			uint16_t i;
-			struct mbuf *m;
-			uint16_t *buf;
-			int pad;
-
-			/* TODO: Add support for 8-bit and
-			 *  32-bit transfer modes.
-			 */
+			uint8_t		rx_status;
+			struct mbuf	*m;

 			/* Read with address increment. */
 			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
-			    sc->dme_io, DM9000_MRCMD);
-			data = bus_space_read_2(sc->sc_iot, sc->sc_ioh,
-			    sc->dme_data);
-
-			rx_status = data & 0xFF;
-			frame_length = bus_space_read_2(sc->sc_iot,
-			    sc->sc_ioh, sc->dme_data);
-
-			RX_DPRINTF(("dme_receive: "
-				"rx_statux: 0x%x, frame_length: %d\n",
-				rx_status, frame_length));
-
-
-			MGETHDR(m, M_DONTWAIT, MT_DATA);
-			m->m_pkthdr.rcvif = ifp;
-			/* Ensure that we always allocate an even number of
-			 * bytes in order to avoid writing beyond the buffer
-			 */
-			m->m_pkthdr.len = frame_length + (frame_length % 2);
-			pad = ALIGN(sizeof(struct ether_header)) -
-			    sizeof(struct ether_header);
-			/* All our frames have the CRC attached */
-			m->m_flags |= M_HASFCS;
-			if (m->m_pkthdr.len + pad > MHLEN )
-				MCLGET(m, M_DONTWAIT);
-
-			m->m_data += pad;
-			m->m_len = frame_length + (frame_length % 2);
-			buf = mtod(m, uint16_t*);
-
-			RX_DPRINTF(("dme_receive: "));
-
-			for(i=0; i< frame_length; i+=2 ) {
-				data = bus_space_read_2(sc->sc_iot,
-				    sc->sc_ioh, sc->dme_data);
-				if ( (frame_length % 2 != 0) &&
-				    (i == frame_length-1) ) {
-					data = data & 0xff;
-					RX_DPRINTF((" L "));
-				}
-				*buf = data;
-				buf++;
-				RX_DATA_DPRINTF(("%02X %02X ", data & 0xff,
-					    (data>>8) & 0xff));
-			}
+					  sc->dme_io, DM9000_MRCMD);

-			RX_DATA_DPRINTF(("\n"));
-			RX_DPRINTF(("Read %d bytes\n", i));
+			rx_status = sc->sc_pkt_read(sc, ifp, &m);

 			if (rx_status & (DM9000_RSR_CE | DM9000_RSR_PLE)) {
 				/* Error while receiving the packet,
@@ -797,28 +827,46 @@
 {
 	uint8_t var;

-	/* Enable PHY */
-	var = dme_read(sc, DM9000_GPCR);
-	dme_write(sc, DM9000_GPCR, var | DM9000_GPCR_GPIO0_OUT);
-	var = dme_read(sc, DM9000_GPR);
-	dme_write(sc, DM9000_GPR, var & ~DM9000_GPR_PHY_PWROFF);
+	/* We only re-initialized the PHY in this function the first time it is
+	   called. */
+	if( !sc->sc_phy_initialized) {
+		/* PHY Reset */
+		dme_phy_write(sc, DM9000_PHY_BMCR, DM9000_PHY_BMCR_RESET);
+
+		/* PHY Power Down */
+		var = dme_read(sc, DM9000_GPR);
+		dme_write(sc, DM9000_GPR, var | DM9000_GPR_PHY_PWROFF);
+	}

-	/* Reset the DM9000 twice, as describe din section 5.2 of the
-	 * Application Notes
+	/* Reset the DM9000 twice, as described in section 2 of the Programming
+	   Guide.
+	   The PHY is initialized and enabled between those two resets.
 	 */
+
+	/* Software Reset*/
 	dme_write(sc, DM9000_NCR,
 	    DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
-	
-	delay(20);
-	dme_write(sc, DM9000_NCR, 0x0);
-	dme_write(sc, DM9000_NCR,
-	    DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
-	
 	delay(20);
 	dme_write(sc, DM9000_NCR, 0x0);

+	if( !sc->sc_phy_initialized) {
+		/* PHY Initialization */
+		dme_phy_init(sc);
+
+		/* PHY Enable */
+		var = dme_read(sc, DM9000_GPR);
+		dme_write(sc, DM9000_GPR, var & ~DM9000_GPR_PHY_PWROFF);
+		var = dme_read(sc, DM9000_GPCR);
+		dme_write(sc, DM9000_GPCR, var | DM9000_GPCR_GPIO0_OUT);
+
+		dme_write(sc, DM9000_NCR,
+			  DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
+		delay(20);
+		dme_write(sc, DM9000_NCR, 0x0);
+	}
+
 	/* Select internal PHY, no wakeup event, no collosion mode,
-	 * normal loopback mode, and no full duplex mode
+	 * normal loopback mode.
 	 */
 	dme_write(sc, DM9000_NCR, DM9000_NCR_LBK_NORMAL );

@@ -831,9 +879,243 @@
 	dme_write(sc, DM9000_IMR,
 	    DM9000_IMR_PAR | DM9000_IMR_PRM | DM9000_IMR_PTM);

-	/* Enable RX without watchdog */
-	dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN | DM9000_RCR_WTDIS);
+	/* Setup multicast address filter, and enable RX. */
+	dme_set_addr_filter(sc);
+
+	/* Obtain media information from PHY */
+	dme_phy_update_media(sc);

 	sc->txbusy = 0;
 	sc->txready = 0;
+	sc->sc_phy_initialized = 1;
+}
+
+void
+dme_set_addr_filter(struct dme_softc *sc)
+{
+	struct ether_multi	*enm;
+	struct ether_multistep	step;
+	struct ethercom		*ec;
+	struct ifnet		*ifp;
+	uint16_t		af[4];
+	int			i;
+
+	ec = &sc->sc_ethercom;
+	ifp = &ec->ec_if;
+
+	if (ifp->if_flags & IFF_PROMISC) {
+		dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN  |
+					  DM9000_RCR_WTDIS |
+					  DM9000_RCR_PRMSC);
+		ifp->if_flags |= IFF_ALLMULTI;
+		return;
+	}
+
+	af[0] = af[1] = af[2] = af[3] = 0x0000;
+	ifp->if_flags &= ~IFF_ALLMULTI;
+
+	ETHER_FIRST_MULTI(step, ec, enm);
+	while (enm != NULL) {
+		uint16_t hash;
+		if (memcpy(enm->enm_addrlo, enm->enm_addrhi,
+		    sizeof(enm->enm_addrlo))) {
+			/*
+	                 * We must listen to a range of multicast addresses.
+	                 * For now, just accept all multicasts, rather than
+	                 * trying to set only those filter bits needed to match
+	                 * the range.  (At this time, the only use of address
+	                 * ranges is for IP multicast routing, for which the
+	                 * range is big enough to require all bits set.)
+	                 */
+			ifp->if_flags |= IFF_ALLMULTI;
+			af[0] = af[1] = af[2] = af[3] = 0xffff;
+			break;
+		} else {
+			hash = ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN) & 0x3F;
+			af[(uint16_t)(hash>>4)] |= (uint16_t)(1 << (hash % 16));
+			ETHER_NEXT_MULTI(step, enm);
+		}
+	}
+
+	/* Write the multicast address filter */
+	for(i=0; i<4; i++) {
+		dme_write(sc, DM9000_MAB0+i*2, af[i] & 0xFF);
+		dme_write(sc, DM9000_MAB0+i*2+1, (af[i] >> 8) & 0xFF);
+	}
+
+	/* Setup RX controls */
+	dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN | DM9000_RCR_WTDIS);
+}
+
+int
+dme_pkt_write_2(struct dme_softc *sc, struct mbuf *bufChain)
+{
+	int left_over_count = 0; /* Number of bytes from previous mbuf, which
+				    need to be written with the next.*/
+	uint16_t left_over_buf = 0;
+	int length = 0;
+	struct mbuf *buf;
+	uint8_t *write_ptr;
+
+	/* We expect that the DM9000 has been setup to accept writes before
+	   this function is called. */
+
+	for (buf = bufChain; buf != NULL; buf = buf->m_next) {
+		int to_write = buf->m_len;
+
+		length += to_write;
+
+		write_ptr = buf->m_data;
+		while (to_write > 0 ||
+		       (buf->m_next == NULL && left_over_count > 0)
+		       ) {
+			if (left_over_count > 0) {
+				uint8_t b = 0;
+				DPRINTF(("dme_pkt_write_16: "
+					 "Writing left over byte\n"));
+
+				if (to_write > 0) {
+					b = *write_ptr;
+					to_write--;
+					write_ptr++;
+
+					DPRINTF(("Took single byte\n"));
+				} else {
+					DPRINTF(("Leftover in last run\n"));
+					length++;
+				}
+
+				/* Does shift direction depend on endianess? */
+				left_over_buf = left_over_buf | (b << 8);
+
+				bus_space_write_2(sc->sc_iot, sc->sc_ioh,
+						  sc->dme_data, left_over_buf);
+				TX_DATA_DPRINTF(("%02X ", left_over_buf));
+				left_over_count = 0;
+			} else if ((long)write_ptr % 2 != 0) {
+				/* Misaligned data */
+				DPRINTF(("dme_pkt_write_16: "
+					 "Detected misaligned data\n"));
+				left_over_buf = *write_ptr;
+				left_over_count = 1;
+				write_ptr++;
+				to_write--;
+			} else {
+				int i;
+				uint16_t *dptr = (uint16_t*)write_ptr;
+
+				/* A block of aligned data. */
+				for(i = 0; i < to_write/2; i++) {
+					/* buf will be half-word aligned
+					 * all the time
+					 */
+					bus_space_write_2(sc->sc_iot,
+							  sc->sc_ioh, sc->dme_data, *dptr);
+					TX_DATA_DPRINTF(("%02X %02X ",
+							 *dptr & 0xFF, (*dptr>>8) & 0xFF));
+					dptr++;
+				}
+
+				write_ptr += i*2;
+				if (to_write % 2 != 0) {
+					DPRINTF(("dme_pkt_write_16: "
+						 "to_write %% 2: %d\n",
+						 to_write % 2));
+					left_over_count = 1;
+					/* XXX: Does this depend on
+					 * the endianess?
+					 */
+					left_over_buf = *write_ptr;
+
+					write_ptr++;
+					to_write--;
+					DPRINTF(("dme_pkt_write_16: "
+						 "to_write (after): %d\n",
+						 to_write));
+					DPRINTF(("dme_pkt_write_16: i*2: %d\n",
+						 i*2));
+				}
+				to_write -= i*2;
+			}
+		} /* while(...) */
+	} /* for(...) */
+
+	return length;
+}
+
+int
+dme_pkt_read_2(struct dme_softc *sc, struct ifnet *ifp, struct mbuf **outBuf)
+{
+	uint8_t rx_status;
+	struct mbuf *m;
+	uint16_t data;
+	uint16_t frame_length;
+	uint16_t i;
+	uint16_t *buf;
+
+	data = bus_space_read_2(sc->sc_iot, sc->sc_ioh,
+				sc->dme_data);
+
+	rx_status = data & 0xFF;
+	frame_length = bus_space_read_2(sc->sc_iot,
+					sc->sc_ioh, sc->dme_data);
+	if (frame_length > ETHER_MAX_LEN) {
+		panic("Something is rotten");
+	}
+	RX_DPRINTF(("dme_receive: "
+		    "rx_statux: 0x%x, frame_length: %d\n",
+		    rx_status, frame_length));
+
+
+	m = dme_alloc_receive_buffer(ifp, frame_length);
+
+	buf = mtod(m, uint16_t*);
+
+	RX_DPRINTF(("dme_receive: "));
+
+	for(i=0; i< frame_length; i+=2 ) {
+		data = bus_space_read_2(sc->sc_iot,
+					sc->sc_ioh, sc->dme_data);
+		if ( (frame_length % 2 != 0) &&
+		     (i == frame_length-1) ) {
+			data = data & 0xff;
+			RX_DPRINTF((" L "));
+		}
+		*buf = data;
+		buf++;
+		RX_DATA_DPRINTF(("%02X %02X ", data & 0xff,
+				 (data>>8) & 0xff));
+	}
+
+	RX_DATA_DPRINTF(("\n"));
+	RX_DPRINTF(("Read %d bytes\n", i));
+
+	*outBuf = m;
+	return rx_status;
+}
+
+struct mbuf*
+dme_alloc_receive_buffer(struct ifnet *ifp, unsigned int frame_length)
+{
+	struct dme_softc *sc = ifp->if_softc;
+	struct mbuf *m;
+	int pad;
+
+	MGETHDR(m, M_DONTWAIT, MT_DATA);
+	m->m_pkthdr.rcvif = ifp;
+	/* Ensure that we always allocate an even number of
+	 * bytes in order to avoid writing beyond the buffer
+	 */
+	m->m_pkthdr.len = frame_length + (frame_length % sc->sc_data_width);
+	pad = ALIGN(sizeof(struct ether_header)) -
+		sizeof(struct ether_header);
+	/* All our frames have the CRC attached */
+	m->m_flags |= M_HASFCS;
+	if (m->m_pkthdr.len + pad > MHLEN )
+		MCLGET(m, M_DONTWAIT);
+
+	m->m_data += pad;
+	m->m_len = frame_length + (frame_length % sc->sc_data_width);
+
+	return m;
 }

>Release-Note:

>Audit-Trail:

State-Changed-From-To: open->closed
State-Changed-By: maya@NetBSD.org
State-Changed-When: Sat, 29 Dec 2018 06:07:52 +0000
State-Changed-Why:
Applied in 2012 by nisimura (with some light changes, which I assume might be from a slightly different version)
https://mail-index.netbsd.org/source-changes/2012/01/28/msg031089.html

Thanks for the patch.
Let us know if something had gone wrong in the application (I asume you posted a slightly different version on a mailing list too, or had some discussion followup there.)


>Unformatted:

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.43 2018/01/16 07:36:43 maya Exp $
$NetBSD: gnats_config.sh,v 1.9 2014/08/02 14:16:04 spz Exp $
Copyright © 1994-2017 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.