1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2008-2012 Juli Mallett <jmallett@FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 * $FreeBSD$
29 */
30
31#include "opt_inet.h"
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/bus.h>
36#include <sys/endian.h>
37#include <sys/kernel.h>
38#include <sys/mbuf.h>
39#include <sys/lock.h>
40#include <sys/module.h>
41#include <sys/mutex.h>
42#include <sys/rman.h>
43#include <sys/socket.h>
44#include <sys/sockio.h>
45#include <sys/sysctl.h>
46
47#include <net/bpf.h>
48#include <net/ethernet.h>
49#include <net/if.h>
50#include <net/if_dl.h>
51#include <net/if_media.h>
52#include <net/if_types.h>
53#include <net/if_var.h>
54#include <net/if_vlan_var.h>
55
56#ifdef INET
57#include <netinet/in.h>
58#include <netinet/if_ether.h>
59#endif
60
61#include <machine/cpuregs.h>
62
63#include <dev/gxemul/ether/gxreg.h>
64
65struct gx_softc {
66	struct ifnet *sc_ifp;
67	device_t sc_dev;
68	unsigned sc_port;
69	int sc_flags;
70	struct ifmedia sc_ifmedia;
71	struct resource *sc_intr;
72	void *sc_intr_cookie;
73	struct mtx sc_mtx;
74};
75
76#define	GXEMUL_ETHER_LOCK(sc)	mtx_lock(&(sc)->sc_mtx)
77#define	GXEMUL_ETHER_UNLOCK(sc)	mtx_unlock(&(sc)->sc_mtx)
78
79static void	gx_identify(driver_t *, device_t);
80static int	gx_probe(device_t);
81static int	gx_attach(device_t);
82static int	gx_detach(device_t);
83static int	gx_shutdown(device_t);
84
85static void	gx_init(void *);
86static int	gx_transmit(struct ifnet *, struct mbuf *);
87
88static int	gx_medchange(struct ifnet *);
89static void	gx_medstat(struct ifnet *, struct ifmediareq *);
90
91static int	gx_ioctl(struct ifnet *, u_long, caddr_t);
92
93static void	gx_rx_intr(void *);
94
95static device_method_t gx_methods[] = {
96	/* Device interface */
97	DEVMETHOD(device_identify,	gx_identify),
98	DEVMETHOD(device_probe,		gx_probe),
99	DEVMETHOD(device_attach,	gx_attach),
100	DEVMETHOD(device_detach,	gx_detach),
101	DEVMETHOD(device_shutdown,	gx_shutdown),
102
103	{ 0, 0 }
104};
105
106static driver_t gx_driver = {
107	"gx",
108	gx_methods,
109	sizeof (struct gx_softc),
110};
111
112static devclass_t gx_devclass;
113
114DRIVER_MODULE(gx, nexus, gx_driver, gx_devclass, 0, 0);
115
116static void
117gx_identify(driver_t *drv, device_t parent)
118{
119	BUS_ADD_CHILD(parent, 0, "gx", 0);
120}
121
122static int
123gx_probe(device_t dev)
124{
125	if (device_get_unit(dev) != 0)
126		return (ENXIO);
127
128	device_set_desc(dev, "GXemul test Ethernet");
129
130	return (BUS_PROBE_NOWILDCARD);
131}
132
133static int
134gx_attach(device_t dev)
135{
136	struct ifnet *ifp;
137	struct gx_softc *sc;
138	uint8_t mac[6];
139	int error;
140	int rid;
141
142	sc = device_get_softc(dev);
143	sc->sc_dev = dev;
144	sc->sc_port = device_get_unit(dev);
145
146	/* Read MAC address.  */
147	GXEMUL_ETHER_DEV_WRITE(GXEMUL_ETHER_DEV_MAC, (uintptr_t)mac);
148
149	/* Allocate and establish interrupt.  */
150	rid = 0;
151	sc->sc_intr = bus_alloc_resource(sc->sc_dev, SYS_RES_IRQ, &rid,
152	    GXEMUL_ETHER_DEV_IRQ - 2, GXEMUL_ETHER_DEV_IRQ - 2, 1, RF_ACTIVE);
153	if (sc->sc_intr == NULL) {
154		device_printf(dev, "unable to allocate IRQ.\n");
155		return (ENXIO);
156	}
157
158	error = bus_setup_intr(sc->sc_dev, sc->sc_intr, INTR_TYPE_NET, NULL,
159	    gx_rx_intr, sc, &sc->sc_intr_cookie);
160	if (error != 0) {
161		device_printf(dev, "unable to setup interrupt.\n");
162		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_intr);
163		return (ENXIO);
164	}
165
166	bus_describe_intr(sc->sc_dev, sc->sc_intr, sc->sc_intr_cookie, "rx");
167
168	ifp = if_alloc(IFT_ETHER);
169	if (ifp == NULL) {
170		device_printf(dev, "cannot allocate ifnet.\n");
171		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_intr);
172		return (ENOMEM);
173	}
174
175	if_initname(ifp, device_get_name(dev), device_get_unit(dev));
176	ifp->if_mtu = ETHERMTU;
177	ifp->if_init = gx_init;
178	ifp->if_softc = sc;
179	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_ALLMULTI;
180	ifp->if_ioctl = gx_ioctl;
181
182	sc->sc_ifp = ifp;
183	sc->sc_flags = ifp->if_flags;
184
185	ifmedia_init(&sc->sc_ifmedia, 0, gx_medchange, gx_medstat);
186
187	ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_AUTO, 0, NULL);
188	ifmedia_set(&sc->sc_ifmedia, IFM_ETHER | IFM_AUTO);
189
190	mtx_init(&sc->sc_mtx, "GXemul Ethernet", NULL, MTX_DEF);
191
192	ether_ifattach(ifp, mac);
193
194	ifp->if_transmit = gx_transmit;
195
196	return (bus_generic_attach(dev));
197}
198
199static int
200gx_detach(device_t dev)
201{
202	struct gx_softc *sc;
203
204	sc = device_get_softc(dev);
205
206	bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_intr);
207	/* XXX Incomplete.  */
208
209	return (0);
210}
211
212static int
213gx_shutdown(device_t dev)
214{
215	return (gx_detach(dev));
216}
217
218static void
219gx_init(void *arg)
220{
221	struct ifnet *ifp;
222	struct gx_softc *sc;
223
224	sc = arg;
225	ifp = sc->sc_ifp;
226
227	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0)
228		ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
229
230	ifp->if_drv_flags |= IFF_DRV_RUNNING;
231}
232
233static int
234gx_transmit(struct ifnet *ifp, struct mbuf *m)
235{
236	struct gx_softc *sc;
237
238	sc = ifp->if_softc;
239
240	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != IFF_DRV_RUNNING) {
241		m_freem(m);
242		return (0);
243	}
244
245	GXEMUL_ETHER_LOCK(sc);
246	GXEMUL_ETHER_DEV_WRITE(GXEMUL_ETHER_DEV_LENGTH, m->m_pkthdr.len);
247	m_copydata(m, 0, m->m_pkthdr.len, (void *)(uintptr_t)GXEMUL_ETHER_DEV_FUNCTION(GXEMUL_ETHER_DEV_BUFFER));
248	GXEMUL_ETHER_DEV_WRITE(GXEMUL_ETHER_DEV_COMMAND, GXEMUL_ETHER_DEV_COMMAND_TX);
249	GXEMUL_ETHER_UNLOCK(sc);
250
251	ETHER_BPF_MTAP(ifp, m);
252
253	if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
254	if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len);
255
256	m_freem(m);
257
258	return (0);
259}
260
261static int
262gx_medchange(struct ifnet *ifp)
263{
264	return (ENOTSUP);
265}
266
267static void
268gx_medstat(struct ifnet *ifp, struct ifmediareq *ifm)
269{
270	struct gx_softc *sc;
271
272	sc = ifp->if_softc;
273
274	/* Lie amazingly.  */
275	ifm->ifm_status = IFM_AVALID | IFM_ACTIVE;
276	ifm->ifm_active = IFT_ETHER | IFM_1000_T | IFM_FDX;
277}
278
279static int
280gx_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
281{
282	struct gx_softc *sc;
283	struct ifreq *ifr;
284#ifdef INET
285	struct ifaddr *ifa;
286#endif
287	int error;
288
289	sc = ifp->if_softc;
290	ifr = (struct ifreq *)data;
291#ifdef INET
292	ifa = (struct ifaddr *)data;
293#endif
294
295	switch (cmd) {
296	case SIOCSIFADDR:
297#ifdef INET
298		/*
299		 * Avoid reinitialization unless it's necessary.
300		 */
301		if (ifa->ifa_addr->sa_family == AF_INET) {
302			ifp->if_flags |= IFF_UP;
303			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
304				gx_init(sc);
305			arp_ifinit(ifp, ifa);
306
307			return (0);
308		}
309#endif
310		error = ether_ioctl(ifp, cmd, data);
311		if (error != 0)
312			return (error);
313		return (0);
314
315	case SIOCSIFFLAGS:
316		if (ifp->if_flags == sc->sc_flags)
317			return (0);
318		if ((ifp->if_flags & IFF_UP) != 0) {
319			gx_init(sc);
320		} else {
321			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
322				ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
323			}
324		}
325		sc->sc_flags = ifp->if_flags;
326		return (0);
327
328	case SIOCSIFMTU:
329		if (ifr->ifr_mtu + ifp->if_hdrlen > GXEMUL_ETHER_DEV_MTU)
330			return (ENOTSUP);
331		return (0);
332
333	case SIOCSIFMEDIA:
334	case SIOCGIFMEDIA:
335		error = ifmedia_ioctl(ifp, ifr, &sc->sc_ifmedia, cmd);
336		if (error != 0)
337			return (error);
338		return (0);
339
340	default:
341		error = ether_ioctl(ifp, cmd, data);
342		if (error != 0)
343			return (error);
344		return (0);
345	}
346}
347
348static void
349gx_rx_intr(void *arg)
350{
351	struct gx_softc *sc = arg;
352
353	GXEMUL_ETHER_LOCK(sc);
354	for (;;) {
355		uint64_t status, length;
356		struct mbuf *m;
357
358		/*
359		 * XXX
360		 * Limit number of packets received at once?
361		 */
362		status = GXEMUL_ETHER_DEV_READ(GXEMUL_ETHER_DEV_STATUS);
363		if (status == GXEMUL_ETHER_DEV_STATUS_RX_MORE) {
364			GXEMUL_ETHER_DEV_WRITE(GXEMUL_ETHER_DEV_COMMAND, GXEMUL_ETHER_DEV_COMMAND_RX);
365			continue;
366		}
367		if (status != GXEMUL_ETHER_DEV_STATUS_RX_OK)
368			break;
369		length = GXEMUL_ETHER_DEV_READ(GXEMUL_ETHER_DEV_LENGTH);
370		if (length > MCLBYTES - ETHER_ALIGN) {
371			if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1);
372			continue;
373		}
374
375		m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
376		if (m == NULL) {
377			device_printf(sc->sc_dev, "no memory for receive mbuf.\n");
378			if_inc_counter(sc->sc_ifp, IFCOUNTER_IQDROPS, 1);
379			GXEMUL_ETHER_UNLOCK(sc);
380			return;
381		}
382
383		/* Align incoming frame so IP headers are aligned.  */
384		m->m_data += ETHER_ALIGN;
385
386		memcpy(m->m_data, (const void *)(uintptr_t)GXEMUL_ETHER_DEV_FUNCTION(GXEMUL_ETHER_DEV_BUFFER), length);
387
388		m->m_pkthdr.rcvif = sc->sc_ifp;
389		m->m_pkthdr.len = m->m_len = length;
390
391		if_inc_counter(sc->sc_ifp, IFCOUNTER_IPACKETS, 1);
392
393		GXEMUL_ETHER_UNLOCK(sc);
394
395		(*sc->sc_ifp->if_input)(sc->sc_ifp, m);
396
397		GXEMUL_ETHER_LOCK(sc);
398	}
399	GXEMUL_ETHER_UNLOCK(sc);
400}
401