[rtems-libbsd commit] if_stmac: Add driver for STM32H7 ethernet module

Sebastian Huber sebh at rtems.org
Tue Oct 27 05:32:36 UTC 2020


Module:    rtems-libbsd
Branch:    6-freebsd-12
Commit:    1189f7147a5e23fe61a2f3b99dd627b390d374f9
Changeset: http://git.rtems.org/rtems-libbsd/commit/?id=1189f7147a5e23fe61a2f3b99dd627b390d374f9

Author:    Sebastian Huber <sebastian.huber at embedded-brains.de>
Date:      Mon Mar 23 14:26:20 2020 +0100

if_stmac: Add driver for STM32H7 ethernet module

Update #3910.

---

 libbsd.py                            |    1 +
 rtemsbsd/include/bsp/nexus-devices.h |    5 +
 rtemsbsd/sys/dev/stmac/if_stmac.c    | 1008 ++++++++++++++++++++++++++++++++++
 3 files changed, 1014 insertions(+)

diff --git a/libbsd.py b/libbsd.py
index 24f42df..7848ec3 100644
--- a/libbsd.py
+++ b/libbsd.py
@@ -225,6 +225,7 @@ class rtems(builder.Module):
                 'sys/dev/ffec/if_ffec_mpc8xx.c',
                 'sys/dev/input/touchscreen/tsc_lpc32xx.c',
                 'sys/dev/smc/if_smc_nexus.c',
+                'sys/dev/stmac/if_stmac.c',
                 'sys/dev/tsec/if_tsec_nexus.c',
                 'sys/dev/usb/controller/ehci_mpc83xx.c',
                 'sys/dev/usb/controller/ohci_lpc32xx.c',
diff --git a/rtemsbsd/include/bsp/nexus-devices.h b/rtemsbsd/include/bsp/nexus-devices.h
index 125ac0c..9a10cba 100644
--- a/rtemsbsd/include/bsp/nexus-devices.h
+++ b/rtemsbsd/include/bsp/nexus-devices.h
@@ -150,6 +150,11 @@ SYSINIT_DRIVER_REFERENCE(usbus, ohci);
 RTEMS_BSD_DRIVER_USB;
 RTEMS_BSD_DRIVER_USB_MASS;
 
+#elif defined(LIBBSP_ARM_STM32H7_BSP_H)
+
+RTEMS_BSD_DEFINE_NEXUS_DEVICE(stmac, 0, 0, NULL);
+SYSINIT_DRIVER_REFERENCE(ukphy, miibus);
+
 #elif defined(LIBBSP_I386_PC386_BSP_H)
 
 RTEMS_BSD_DRIVER_PC_LEGACY;
diff --git a/rtemsbsd/sys/dev/stmac/if_stmac.c b/rtemsbsd/sys/dev/stmac/if_stmac.c
new file mode 100644
index 0000000..614c51b
--- /dev/null
+++ b/rtemsbsd/sys/dev/stmac/if_stmac.c
@@ -0,0 +1,1008 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/*
+ * Copyright (C) 2020 embedded brains Gmb_h (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <machine/rtems-bsd-kernel-space.h>
+
+#include <bsp.h>
+
+#ifdef LIBBSP_ARM_STM32H7_BSP_H
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/bus.h>
+#include <sys/mbuf.h>
+#include <sys/malloc.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <net/if.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+#include <net/if_var.h>
+
+#include <machine/bus.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <rtems/bsd/local/miibus_if.h>
+
+#include <stm32h7xx_hal.h>
+
+#include <rtems/bsd/bsd.h>
+#include <rtems/irq-extension.h>
+#include <rtems/score/armv7m.h>
+
+#define	RX_DESC_COUNT		64
+#define	TX_DESC_COUNT		256
+
+#define	STMAC_LOCK(sc) mtx_lock(&(sc)->mtx)
+#define	STMAC_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
+
+#define	STMAC_TXLOCK(sc) mtx_lock(&(sc)->tx_mtx)
+#define	STMAC_TXUNLOCK(sc) mtx_unlock(&(sc)->tx_mtx)
+
+struct stmac_desc {
+	__IO uint32_t	DESC0;
+	__IO uint32_t	DESC1;
+	__IO uint32_t	DESC2;
+	__IO uint32_t	DESC3;
+	struct mbuf	*m;
+};
+
+struct stmac_softc {
+	ETH_HandleTypeDef	heth;
+	uint8_t			mac_addr[6];
+	struct ifnet		*ifp;
+	struct mtx		mtx;
+	device_t		miibus;
+	struct mtx		mii_mtx;
+	struct mii_data		*mii_softc;
+	u_int			mii_media_active;
+	u_int			mii_media_status;
+	struct callout		tick_callout;
+	int			if_flags;
+
+	/* RX */
+	struct stmac_desc	*rx_desc_ring;
+	uint32_t		rx_idx;
+	bool			rx_do_alloc;
+
+	/* TX */
+	struct mtx		tx_mtx;
+	struct stmac_desc	*tx_desc_ring;
+	uint32_t		tx_idx_head;
+	uint32_t		tx_idx_tail;
+};
+
+static void stmac_init_locked(struct stmac_softc *);
+static void stmac_stop_locked(struct stmac_softc *);
+
+struct mbuf *
+stmac_new_mbuf(struct ifnet *ifp)
+{
+	struct mbuf *m;
+
+	m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+	if (m != NULL) {
+		m->m_data = mtod(m, char *) + ETHER_ALIGN;
+		m->m_pkthdr.rcvif = ifp;
+		rtems_cache_invalidate_multiple_data_lines(m->m_data, m->m_len);
+	}
+
+	return m;
+}
+
+static void
+stmac_tick(void *arg)
+{
+	struct stmac_softc *sc;
+	struct ifnet *ifp;
+
+	sc = arg;
+	ifp = sc->ifp;
+
+	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
+		return;
+	}
+
+	mii_tick(sc->mii_softc);
+	callout_reset(&sc->tick_callout, hz, stmac_tick, sc);
+}
+
+static uint32_t
+stmac_rx_desc3(uint32_t idx)
+{
+
+	if (idx % 16 != 0) {
+		return (ETH_DMARXNDESCRF_OWN | ETH_DMARXNDESCRF_BUF1V);
+	}
+
+	return (ETH_DMARXNDESCRF_OWN | ETH_DMARXNDESCRF_BUF1V |
+	    ETH_DMARXNDESCRF_IOC);
+}
+
+static void
+stmac_rx_setup_desc(struct stmac_softc *sc)
+{
+	uint32_t idx;
+	bool do_alloc;
+	ETH_TypeDef *regs;
+
+	do_alloc = sc->rx_do_alloc;
+	sc->rx_do_alloc = false;
+	sc->rx_idx = 0;
+
+	for (idx = 0; idx < RX_DESC_COUNT; ++idx) {
+		struct stmac_desc *desc;
+		struct mbuf *m;
+
+		desc = &sc->rx_desc_ring[idx];
+
+		if (do_alloc) {
+			m = stmac_new_mbuf(sc->ifp);
+			BSD_ASSERT(m != NULL);
+			desc->m = m;
+		} else {
+			m = desc->m;
+		}
+
+		WRITE_REG(desc->DESC0, mtod(m, uint32_t));
+		WRITE_REG(desc->DESC1, 0x0);
+		WRITE_REG(desc->DESC2, 0x0);
+		WRITE_REG(desc->DESC3, stmac_rx_desc3(idx));
+	}
+
+	regs = sc->heth.Instance;
+	WRITE_REG(regs->DMACRDRLR, RX_DESC_COUNT -1);
+	WRITE_REG(regs->DMACRDLAR, (uint32_t)&sc->rx_desc_ring[0]);
+	WRITE_REG(regs->DMACRDTPR,
+	    (uint32_t)&sc->rx_desc_ring[RX_DESC_COUNT - 1]);
+}
+
+static void
+stmac_tx_setup_desc(struct stmac_softc *sc)
+{
+	uint32_t idx;
+	ETH_TypeDef *regs;
+
+	sc->tx_idx_head = 0;
+	sc->tx_idx_tail = 0;
+
+	for (idx = 0; idx < TX_DESC_COUNT; ++idx) {
+		struct stmac_desc *desc;
+
+		desc = &sc->tx_desc_ring[idx];
+		WRITE_REG(desc->DESC0, 0x0);
+		WRITE_REG(desc->DESC1, 0x0);
+		WRITE_REG(desc->DESC2, 0x0);
+		WRITE_REG(desc->DESC3, 0x0);
+		desc->m = NULL;
+	}
+
+	regs = sc->heth.Instance;
+	WRITE_REG(regs->DMACTDRLR, TX_DESC_COUNT -1);
+	WRITE_REG(regs->DMACTDLAR, (uint32_t)&sc->tx_desc_ring[0]);
+	WRITE_REG(regs->DMACTDTPR, (uint32_t)&sc->tx_desc_ring[0]);
+}
+
+static void
+stmac_init_locked(struct stmac_softc *sc)
+{
+	struct ifnet *ifp;
+	ETH_TypeDef *regs;
+
+	ifp = sc->ifp;
+
+	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
+		return;
+	}
+
+	ifp->if_drv_flags |= IFF_DRV_RUNNING;
+
+	stmac_rx_setup_desc(sc);
+	stmac_tx_setup_desc(sc);
+
+	HAL_ETH_Start(&sc->heth);
+	regs = sc->heth.Instance;
+
+	/* Enable interrupts */
+	SET_BIT(regs->DMACIER, ETH_DMACIER_NIE | ETH_DMACIER_RIE |
+	    ETH_DMACIER_FBEE | ETH_DMACIER_AIE);
+
+	mii_mediachg(sc->mii_softc);
+	callout_reset(&sc->tick_callout, hz, stmac_tick, sc);
+}
+
+static void
+stmac_tx_reclaim_all(struct stmac_softc *sc)
+{
+	uint32_t idx;
+
+	for (idx = 0; idx < TX_DESC_COUNT; ++idx) {
+		struct stmac_desc *desc;
+		struct mbuf *m;
+
+		desc = &sc->tx_desc_ring[idx];
+		m = desc->m;
+		desc->m = NULL;
+		m_freem(m);
+	}
+}
+
+static void
+stmac_stop_locked(struct stmac_softc *sc)
+{
+	struct ifnet *ifp;
+	ETH_TypeDef *regs;
+	rtems_interrupt_server_entry server_entry;
+	rtems_status_code status;
+	rtems_name name;
+	uint64_t t0;
+
+	ifp = sc->ifp;
+
+	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
+		return;
+	}
+
+	ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
+	regs = sc->heth.Instance;
+
+	/* Disable interrupts */
+	WRITE_REG(regs->DMACIER, 0);
+
+	/* Disable the DMA transmission and reception */
+	CLEAR_BIT(regs->DMACTCR, ETH_DMACTCR_ST);
+	CLEAR_BIT(regs->DMACRCR, ETH_DMACRCR_SR);
+
+	/* Wait for DMA done */
+	t0 = rtems_clock_get_uptime_nanoseconds();
+	while ((regs->MTLTQDR & (ETH_MTLTQDR_TXQSTS |
+	    ETH_MTLTQDR_TRCSTS)) != 0 ||
+	    (regs->MTLRQDR & (ETH_MTLRQDR_RXQSTS |
+	    ETH_MTLRQDR_PRXQ)) != 0) {
+		if (rtems_clock_get_uptime_nanoseconds() - t0 > 500000) {
+			break;
+		}
+	}
+
+	/* Disable the MAC reception and transmission */
+	CLEAR_BIT(regs->MACCR, ETH_MACCR_RE | ETH_MACCR_TE);
+
+	/* Make sure no receive interrupt is in progress */
+	status = rtems_object_get_classic_name(rtems_task_self(), &name);
+	BSD_ASSERT(status == RTEMS_SUCCESSFUL);
+	if (name != rtems_build_name('I', 'R', 'Q', 'S')) {
+		status = rtems_interrupt_server_entry_initialize(
+		    RTEMS_INTERRUPT_SERVER_DEFAULT, &server_entry);
+		BSD_ASSERT(status == RTEMS_SUCCESSFUL);
+		rtems_interrupt_server_entry_destroy(&server_entry);
+	}
+
+	stmac_tx_reclaim_all(sc);
+}
+
+static uint8_t
+stmac_bitreverse(uint8_t x)
+{
+	static const uint8_t nibbletab[] = {
+	    0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
+	};
+
+	return ((nibbletab[x & 15] << 4) | nibbletab[x >> 4]);
+}
+
+static uint64_t
+stmac_hash_bit(const uint8_t *eaddr)
+{
+	uint32_t crc;
+
+	/*
+	 * Thanks to the excellent documentation from STM it was easy to figure
+	 * this out.
+	 */
+	crc = 0;
+	crc = crc32_raw(eaddr, ETHER_ADDR_LEN, ~crc);
+	crc = stmac_bitreverse((uint8_t)~crc);
+	return ((uint64_t)1 << (crc >> 2));
+}
+
+static void
+stmac_rx_setup_filter(struct stmac_softc *sc)
+{
+	struct ifmultiaddr *ifma;
+	struct ifnet *ifp;
+	ETH_TypeDef *regs;
+	uint32_t macpfr;
+	uint64_t machtr;
+	const uint8_t *eaddr;
+
+	ifp = sc->ifp;
+
+	macpfr = ETH_MACPFR_HMC;
+	if ((ifp->if_flags & IFF_PROMISC) != 0) {
+		macpfr |= ETH_MACPFR_PR;
+	}
+
+	machtr = 0;
+	if ((ifp->if_flags & IFF_ALLMULTI) != 0) {
+		machtr = ~machtr;
+	} else {
+		if_maddr_rlock(ifp);
+		CK_STAILQ_FOREACH(ifma, &sc->ifp->if_multiaddrs, ifma_link) {
+			if (ifma->ifma_addr->sa_family != AF_LINK) {
+				continue;
+			}
+
+			eaddr = LLADDR((struct sockaddr_dl *)ifma->ifma_addr);
+			machtr |= stmac_hash_bit(eaddr);
+		}
+		if_maddr_runlock(ifp);
+	}
+
+	regs = sc->heth.Instance;
+	eaddr = IF_LLADDR(ifp);
+	regs->MACA0LR = eaddr[0] | (eaddr[1] << 8) | (eaddr[2] << 16) |
+	    (eaddr[3] << 24);
+	regs->MACA0HR = eaddr[4] | (eaddr[5] << 8);
+	regs->MACHT0R = (uint32_t)machtr;
+	regs->MACHT1R = (uint32_t)(machtr >> 32);
+	regs->MACPFR = macpfr;
+}
+
+static int
+stmac_ioctl(struct ifnet *ifp, ioctl_command_t cmd, caddr_t data)
+{
+	struct stmac_softc *sc;
+	int error;
+	struct mii_data *mii;
+
+	sc = ifp->if_softc;
+
+	error = 0;
+	switch (cmd) {
+	case SIOCSIFFLAGS:
+		STMAC_LOCK(sc);
+		if ((ifp->if_flags & IFF_UP) != 0) {
+			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
+				if ((ifp->if_flags ^ sc->if_flags) &
+				    (IFF_PROMISC | IFF_ALLMULTI))
+					stmac_rx_setup_filter(sc);
+			} else {
+				stmac_init_locked(sc);
+			}
+		} else {
+			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
+				STMAC_TXLOCK(sc);
+				stmac_stop_locked(sc);
+				STMAC_TXUNLOCK(sc);
+			}
+		}
+		sc->if_flags = ifp->if_flags;
+		STMAC_UNLOCK(sc);
+		break;
+	case SIOCADDMULTI:
+	case SIOCDELMULTI:
+		STMAC_LOCK(sc);
+		if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
+			stmac_rx_setup_filter(sc);
+		}
+		STMAC_UNLOCK(sc);
+		break;
+	case SIOCSIFMEDIA:
+	case SIOCGIFMEDIA:
+		mii = sc->mii_softc;
+		if (mii != NULL) {
+			error = ifmedia_ioctl(ifp, (struct ifreq *)data, &mii->mii_media, cmd);
+		} else {
+			error = ether_ioctl(ifp, cmd, data);
+		}
+		break;
+	default:
+		error = ether_ioctl(ifp, cmd, data);
+		break;
+	}
+
+	return (error);
+}
+
+static void
+stmac_init(void *arg)
+{
+	struct stmac_softc *sc;
+
+	sc = arg;
+	STMAC_LOCK(sc);
+	STMAC_TXLOCK(sc);
+	stmac_init_locked(sc);
+	STMAC_TXUNLOCK(sc);
+	STMAC_UNLOCK(sc);
+}
+
+static void
+stmac_media_status(struct ifnet *ifp, struct ifmediareq *ifmr_p)
+{
+	struct stmac_softc *sc;
+	struct mii_data *mii;
+
+	sc = ifp->if_softc;
+
+	STMAC_LOCK(sc);
+	mii = sc->mii_softc;
+	if (mii != NULL) {
+		mii_pollstat(mii);
+		ifmr_p->ifm_active = mii->mii_media_active;
+		ifmr_p->ifm_status = mii->mii_media_status;
+	}
+	STMAC_UNLOCK(sc);
+}
+
+static int
+stmac_media_change(struct ifnet *ifp)
+{
+	struct stmac_softc *sc;
+	int error;
+
+	sc = ifp->if_softc;
+
+	STMAC_LOCK(sc);
+	if (sc->mii_softc != NULL) {
+		error = mii_mediachg(sc->mii_softc);
+	} else {
+		error = ENXIO;
+	}
+	STMAC_UNLOCK(sc);
+	return (error);
+}
+
+static int
+stmac_probe(device_t dev)
+{
+
+	device_set_desc(dev, "STM MAC");
+	return (BUS_PROBE_DEFAULT);
+}
+
+static void
+stmac_rx_checksum(struct mbuf *m, uint32_t desc1)
+{
+
+	if ((desc1 & (ETH_DMARXNDESCWBF_IPCE | ETH_DMARXNDESCWBF_IPCB |
+	    ETH_DMARXNDESCWBF_IPHE)) == 0) {
+		uint32_t pt;
+
+		pt = desc1 & ETH_DMARXNDESCWBF_PT;
+		if (pt == ETH_DMARXNDESCWBF_PT_UDP ||
+		    pt == ETH_DMARXNDESCWBF_PT_TCP) {
+			m->m_pkthdr.csum_flags |=
+			    CSUM_IP_CHECKED |
+			    CSUM_IP_VALID |
+			    CSUM_DATA_VALID |
+			    CSUM_PSEUDO_HDR;
+			m->m_pkthdr.csum_data = 0xffff;
+		} else if ((desc1 & ETH_DMARXNDESCWBF_IPV4) != 0) {
+			m->m_pkthdr.csum_flags |=
+			    CSUM_IP_CHECKED |
+			    CSUM_IP_VALID;
+			m->m_pkthdr.csum_data = 0xffff;
+		}
+	}
+}
+
+static void
+stmac_rx_interrupt(struct stmac_softc *sc, ETH_TypeDef *regs)
+{
+	struct ifnet *ifp;
+	uint32_t idx;
+	struct stmac_desc *desc_ring;
+	struct stmac_desc *desc;
+
+	ifp = sc->ifp;
+	desc_ring = sc->rx_desc_ring;
+	idx = sc->rx_idx;
+	desc = &desc_ring[idx];
+
+	while (true) {
+		uint32_t desc3;
+		struct mbuf *m;
+
+		/*
+		 * Make sure the interrupt is cleared and no longer pending
+		 * before we read the descriptor status.
+		 */
+		regs->DMACSR = ETH_DMACSR_RI | ETH_DMACSR_NIS;
+		regs->DMACSR;
+		_ARMV7M_NVIC_Clear_pending(ETH_IRQn);
+
+		desc3 = desc->DESC3;
+		if ((desc3 & ETH_DMARXNDESCRF_OWN) != 0) {
+			break;
+		}
+
+		m = stmac_new_mbuf(ifp);
+		if (m != NULL) {
+			uint32_t mask;
+			uint32_t set;
+			uint32_t len;
+
+			mask = ETH_DMARXNDESCWBF_CTXT | ETH_DMARXNDESCWBF_FD |
+			    ETH_DMARXNDESCWBF_LD | ETH_DMARXNDESCWBF_ES;
+			set = ETH_DMARXNDESCWBF_FD | ETH_DMARXNDESCWBF_LD;
+			len = desc3 & ETH_DMARXNDESCWBF_PL;
+			if ((desc3 & mask) == set && len > ETHER_CRC_LEN) {
+				struct mbuf *rx;
+
+				rx = desc->m;
+				stmac_rx_checksum(rx, desc->DESC1);
+				rx->m_len = len;
+				rx->m_pkthdr.len = len;
+				(*ifp->if_input)(ifp, rx);
+			} else {
+				if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+				m_freem(m);
+				m = desc->m;
+			}
+		} else {
+			if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+			m = desc->m;
+		}
+
+		WRITE_REG(desc->DESC0, mtod(m, uint32_t));
+		WRITE_REG(desc->DESC1, 0x0);
+		WRITE_REG(desc->DESC2, 0x0);
+		WRITE_REG(desc->DESC3, stmac_rx_desc3(idx));
+		desc->m = m;
+
+		_ARM_Data_synchronization_barrier();
+		WRITE_REG(regs->DMACRDTPR, (uint32_t)desc);
+
+		idx = (idx + 1) % RX_DESC_COUNT;
+		desc = &desc_ring[idx];
+	}
+
+	sc->rx_idx = idx;
+}
+
+static void
+stmac_reset(struct stmac_softc *sc)
+{
+	HAL_StatusTypeDef hal_status;
+	ETH_TypeDef *regs;
+
+	hal_status = HAL_ETH_Init(&sc->heth);
+	BSD_ASSERT(hal_status == HAL_OK);
+
+	regs = sc->heth.Instance;
+
+	/* Set descriptor skip length according to struct stmac_desc */
+	MODIFY_REG(regs->DMACCR, ETH_DMACCR_DSL, ETH_DMACCR_DSL_32BIT);
+
+	/*
+	 * FIXME: Just use a random value.  It is not clear which clock is used
+	 * here.
+	 */
+	regs->DMACRIWTR = 0x80;
+}
+
+static void
+stmac_interrupt(void *arg)
+{
+	struct stmac_softc *sc;
+	ETH_TypeDef *regs;
+	uint32_t dmacsr;
+
+	sc = arg;
+	regs = sc->heth.Instance;
+	dmacsr = regs->DMACSR;
+
+	/* This is almost always a receive interrupt */
+	stmac_rx_interrupt(sc, regs);
+
+	if (__predict_false((dmacsr & ETH_DMACSR_FBE) != 0)) {
+		regs->DMACSR = ETH_DMACSR_FBE | ETH_DMACSR_AIS;
+
+		STMAC_LOCK(sc);
+		STMAC_TXLOCK(sc);
+		stmac_stop_locked(sc);
+		stmac_reset(sc);
+		stmac_init_locked(sc);
+		STMAC_TXUNLOCK(sc);
+		STMAC_UNLOCK(sc);
+	}
+}
+
+static void
+stmac_tx_reclaim(struct stmac_softc *sc, struct ifnet *ifp)
+{
+	uint32_t head_idx;
+	uint32_t tail_idx;
+	struct stmac_desc *desc_ring;
+
+	head_idx = sc->tx_idx_head;
+	tail_idx = sc->tx_idx_tail;
+	desc_ring = sc->tx_desc_ring;
+
+	while (head_idx != tail_idx) {
+		struct stmac_desc *tail_desc;
+		uint32_t desc3;
+
+		tail_desc = &desc_ring[tail_idx];
+
+		desc3 = tail_desc->DESC3;
+		if ((desc3 & ETH_DMATXNDESCWBF_OWN) != 0) {
+			break;
+		}
+
+		if ((desc3 & ETH_DMATXNDESCWBF_LD) != 0) {
+			struct mbuf *m;
+			ift_counter cnt;
+
+			if ((desc3 & ETH_DMATXNDESCWBF_ES) == 0) {
+				cnt = IFCOUNTER_OPACKETS;
+			} else {
+				cnt = IFCOUNTER_OERRORS;
+			}
+
+			if_inc_counter(ifp, cnt, 1);
+
+			m = tail_desc->m;
+			tail_desc->m = NULL;
+			m_freem(m);
+		}
+
+		tail_idx = (tail_idx + 1) % TX_DESC_COUNT;
+	}
+
+	sc->tx_idx_tail = tail_idx;
+}
+
+static void
+stmac_cache_flush(uintptr_t begin, uintptr_t size)
+{
+	uintptr_t end;
+	uintptr_t mask;
+
+	/* Align begin and end of the data to a cache line */
+	end = begin + size;
+	mask = CPU_CACHE_LINE_BYTES - 1;
+	begin &= ~mask;
+	end = (end + mask) & ~mask;
+	rtems_cache_flush_multiple_data_lines((void *)begin, end - begin);
+}
+
+static int
+stmac_tx_enqueue(struct stmac_softc *sc, struct ifnet *ifp, struct mbuf *m)
+{
+	uint32_t head_idx;
+	uint32_t tail_idx;
+	uint32_t capacity;
+	uint32_t new_head_idx;
+	uint32_t idx;
+	struct stmac_desc *desc_ring;
+	struct stmac_desc *desc;
+	ETH_TypeDef *regs;
+	uint32_t bufs;
+	uint32_t desc2;
+	uint32_t desc3;
+	struct mbuf *n;
+	int csum_flags;
+
+	head_idx = sc->tx_idx_head;
+	tail_idx = sc->tx_idx_tail;
+	capacity = 2 * ((tail_idx - head_idx - 1) % TX_DESC_COUNT);
+
+	idx = head_idx;
+	desc_ring = sc->tx_desc_ring;
+	desc2 = 0;
+	bufs = 0;
+	n = m;
+
+	do {
+		uintptr_t size;
+
+		desc = &desc_ring[idx];
+
+		size = (uintptr_t)n->m_len;
+		if (__predict_true(size > 0)) {
+			uintptr_t begin;
+
+			++bufs;
+			if (__predict_false(bufs > capacity)) {
+				return (ENOBUFS);
+			}
+
+			begin = mtod(n, uintptr_t);
+
+			if (bufs % 2 == 1) {
+				desc->DESC0 = begin;
+				desc2 = size;
+			} else {
+				desc->DESC1 = begin;
+				desc->DESC2 = (size << 16) | desc2;
+				idx = (idx + 1) % TX_DESC_COUNT;
+			}
+
+			stmac_cache_flush(begin, size);
+		}
+
+		n = n->m_next;
+	} while (n != NULL);
+
+	if (bufs % 2 == 1) {
+		desc->DESC1 = 0;
+		desc->DESC2 = desc2;
+		idx = (idx + 1) % TX_DESC_COUNT;
+	}
+
+	new_head_idx = idx;
+	sc->tx_idx_head = new_head_idx;
+
+	idx = (idx - 1) % TX_DESC_COUNT;
+	desc = &desc_ring[idx];
+	desc->m = m;
+
+	desc3 = ETH_DMATXNDESCRF_OWN | ETH_DMATXNDESCRF_LD | m->m_pkthdr.len;
+	csum_flags = m->m_pkthdr.csum_flags;
+	if ((csum_flags & (CSUM_TCP | CSUM_UDP | CSUM_TCP_IPV6 |
+	    CSUM_UDP_IPV6)) != 0) {
+		desc3 |= ETH_DMATXNDESCRF_CIC_IPHDR_PAYLOAD_INSERT_PHDR_CALC;
+	} else if ((csum_flags & CSUM_IP) != 0) {
+		desc3 |= ETH_DMATXNDESCRF_CIC_IPHDR_INSERT;
+	}
+
+	while (idx != head_idx) {
+		desc->DESC3 = desc3;
+		desc3 &= ~ETH_DMATXNDESCRF_LD;
+
+		idx = (idx - 1) % TX_DESC_COUNT;
+		desc = &desc_ring[idx];
+	}
+
+	desc->DESC3 = ETH_DMATXNDESCRF_FD | desc3;
+	_ARM_Data_synchronization_barrier();
+	regs = sc->heth.Instance;
+	WRITE_REG(regs->DMACTDTPR, (uint32_t)&desc_ring[new_head_idx]);
+	return (0);
+}
+
+static int
+stmac_transmit(struct ifnet *ifp, struct mbuf *m)
+{
+	struct stmac_softc *sc;
+	int error;
+
+	sc = ifp->if_softc;
+	STMAC_TXLOCK(sc);
+
+	error = stmac_tx_enqueue(sc, ifp, m);
+	stmac_tx_reclaim(sc, ifp);
+
+	if (__predict_false(error != 0)) {
+		error = stmac_tx_enqueue(sc, ifp, m);
+		if (error != 0) {
+			m_freem(m);
+			if_inc_counter(ifp, IFCOUNTER_OQDROPS, 1);
+		}
+	}
+
+	STMAC_TXUNLOCK(sc);
+	return (error);
+}
+
+static int
+stmac_transmit_no_link(struct ifnet *ifp, struct mbuf *m)
+{
+
+	(void)ifp;
+	(void)m;
+	return (ENETDOWN);
+}
+
+static void
+stmac_qflush(struct ifnet *ifp)
+{
+
+	(void)ifp;
+}
+
+static int
+stmac_attach(device_t dev)
+{
+	struct stmac_softc *sc;
+	struct ifnet *ifp;
+	int error;
+	struct stmac_desc *descs;
+	ETH_TypeDef *regs;
+	rtems_status_code status;
+
+	sc = device_get_softc(dev);
+
+	sc->rx_do_alloc = true;
+	mtx_init(&sc->mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF);
+	mtx_init(&sc->tx_mtx, "stmac tx", MTX_NETWORK_LOCK, MTX_DEF);
+	callout_init_mtx(&sc->tick_callout, &sc->mtx, 0);
+
+	sc->ifp = ifp = if_alloc(IFT_ETHER);
+	ifp->if_softc = sc;
+
+	regs = ETH;
+	sc->heth.Instance = regs;
+
+	descs = rtems_cache_coherent_allocate(sizeof(*descs) *
+	    (RX_DESC_COUNT + TX_DESC_COUNT), 4, 0);
+	BSD_ASSERT(descs != NULL);
+
+	sc->tx_desc_ring = &descs[0];
+	sc->rx_desc_ring = &descs[TX_DESC_COUNT];
+
+	rtems_bsd_get_mac_address(device_get_name(dev), device_get_unit(dev),
+	    &sc->mac_addr[0]);
+
+	sc->heth.Init.MACAddr = &sc->mac_addr[0];
+	sc->heth.Init.MediaInterface = HAL_ETH_RMII_MODE;
+	sc->heth.Init.RxBuffLen = 1524;
+
+	stmac_reset(sc);
+
+	if_initname(ifp, "stm", device_get_unit(dev));
+	ifp->if_flags = IFF_SIMPLEX | IFF_MULTICAST | IFF_BROADCAST;
+	ifp->if_capabilities |= IFCAP_HWCSUM | IFCAP_HWCSUM_IPV6;
+	ifp->if_capenable = ifp->if_capabilities;
+	ifp->if_hwassist = CSUM_IP | CSUM_TCP | CSUM_UDP | CSUM_TCP_IPV6 | CSUM_UDP_IPV6;
+	ifp->if_transmit = stmac_transmit_no_link;
+	ifp->if_qflush = stmac_qflush;
+	ifp->if_ioctl = stmac_ioctl;
+	ifp->if_init = stmac_init;
+	IFQ_SET_MAXLEN(&ifp->if_snd, TX_DESC_COUNT - 1);
+	ifp->if_snd.ifq_drv_maxlen = TX_DESC_COUNT - 1;
+	IFQ_SET_READY(&ifp->if_snd);
+
+	error = mii_attach(dev, &sc->miibus, ifp, stmac_media_change,
+	    stmac_media_status, BMSR_DEFCAPMASK, MII_PHY_ANY,
+	    MII_OFFSET_ANY, 0);
+	if (error == 0) {
+		sc->mii_softc = device_get_softc(sc->miibus);
+	}
+
+	ether_ifattach(ifp, &sc->heth.Init.MACAddr[0]);
+
+	status = rtems_interrupt_server_handler_install(
+	    RTEMS_INTERRUPT_SERVER_DEFAULT, ETH_IRQn, "stmac",
+	    RTEMS_INTERRUPT_SHARED, stmac_interrupt, sc);
+	BSD_ASSERT(status == RTEMS_SUCCESSFUL);
+	return (0);
+}
+
+static int
+stmac_miibus_read(device_t dev, int phy, int reg)
+{
+	struct stmac_softc *sc;
+	uint32_t val;
+	HAL_StatusTypeDef hal_status;
+
+	sc = device_get_softc(dev);
+	hal_status = HAL_ETH_ReadPHYRegister(&sc->heth, (uint32_t)phy,
+	    (uint32_t)reg,  &val);
+	if (hal_status != HAL_OK) {
+		return (-1);
+	}
+
+	return ((int)val);
+}
+
+static int
+stmac_miibus_write(device_t dev, int phy, int reg, int val)
+{
+	struct stmac_softc *sc;
+	HAL_StatusTypeDef hal_status;
+
+	sc = device_get_softc(dev);
+	hal_status = HAL_ETH_WritePHYRegister(&sc->heth, (uint32_t)phy,
+	    (uint32_t)reg, (uint32_t)val);
+	if (hal_status != HAL_OK) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+static void
+stmac_miibus_statchg(device_t dev)
+{
+	struct stmac_softc *sc;
+	struct mii_data *mii;
+	u_int active;
+	u_int status;
+	ETH_TypeDef *regs;
+	uint32_t maccr;
+
+	sc = device_get_softc(dev);
+	mii = device_get_softc(sc->miibus);
+	active = mii->mii_media_active;
+	status = mii->mii_media_status;
+
+	if (sc->mii_media_active == active && sc->mii_media_status == status) {
+		return;
+	}
+
+	sc->mii_media_active = active;
+	sc->mii_media_status = status;
+
+	regs = sc->heth.Instance;
+	maccr = regs->MACCR;
+
+	if ((status & IFM_ACTIVE) != 0) {
+		if_settransmitfn(sc->ifp, stmac_transmit);
+
+		if ((IFM_OPTIONS(active) & IFM_FDX) != 0) {
+			maccr |= ETH_MACCR_DM;
+		} else {
+			maccr &= ~ETH_MACCR_DM;
+		}
+
+		if (IFM_SUBTYPE(active) != IFM_10_T) {
+			maccr |= ETH_MACCR_FES;
+		} else {
+			maccr &= ~ETH_MACCR_FES;
+		}
+	} else {
+		maccr |= ETH_MACCR_DM | ETH_MACCR_FES;
+	}
+
+	regs->MACCR = maccr;
+}
+
+static device_method_t stmac_methods[] = {
+	DEVMETHOD(device_probe, stmac_probe),
+	DEVMETHOD(device_attach, stmac_attach),
+	DEVMETHOD(miibus_readreg, stmac_miibus_read),
+	DEVMETHOD(miibus_writereg, stmac_miibus_write),
+	DEVMETHOD(miibus_statchg, stmac_miibus_statchg),
+	DEVMETHOD_END
+};
+
+driver_t stmac_driver = {
+	"stmac",
+	stmac_methods,
+	sizeof(struct stmac_softc)
+};
+
+static devclass_t stmac_devclass;
+
+DRIVER_MODULE(stmac, nexus, stmac_driver, stmac_devclass, 0, 0);
+DRIVER_MODULE(miibus, stmac, miibus_driver, miibus_devclass, 0, 0);
+
+MODULE_DEPEND(stmac, ether, 1, 1, 1);
+MODULE_DEPEND(stmac, miibus, 1, 1, 1);
+
+#endif /* LIBBSP_ARM_STM32H7_BSP_H */



More information about the vc mailing list