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