xref: /f-stack/freebsd/mips/cavium/if_octm.c (revision 22ce4aff)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2010-2011 Juli Mallett <[email protected]>
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 /*
32  * Cavium Octeon management port Ethernet devices.
33  */
34 
35 #include "opt_inet.h"
36 
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/bus.h>
40 #include <sys/endian.h>
41 #include <sys/kernel.h>
42 #include <sys/mbuf.h>
43 #include <sys/lock.h>
44 #include <sys/module.h>
45 #include <sys/mutex.h>
46 #include <sys/rman.h>
47 #include <sys/socket.h>
48 #include <sys/sockio.h>
49 #include <sys/sysctl.h>
50 
51 #include <net/bpf.h>
52 #include <net/ethernet.h>
53 #include <net/if.h>
54 #include <net/if_dl.h>
55 #include <net/if_media.h>
56 #include <net/if_types.h>
57 #include <net/if_var.h>
58 #include <net/if_vlan_var.h>
59 
60 #ifdef INET
61 #include <netinet/in.h>
62 #include <netinet/if_ether.h>
63 #endif
64 
65 #include <contrib/octeon-sdk/cvmx.h>
66 #include <mips/cavium/octeon_irq.h>
67 #include <contrib/octeon-sdk/cvmx-mgmt-port.h>
68 
69 struct octm_softc {
70 	struct ifnet *sc_ifp;
71 	device_t sc_dev;
72 	unsigned sc_port;
73 	int sc_flags;
74 	struct ifmedia sc_ifmedia;
75 	struct resource *sc_intr;
76 	void *sc_intr_cookie;
77 };
78 
79 static void	octm_identify(driver_t *, device_t);
80 static int	octm_probe(device_t);
81 static int	octm_attach(device_t);
82 static int	octm_detach(device_t);
83 static int	octm_shutdown(device_t);
84 
85 static void	octm_init(void *);
86 static int	octm_transmit(struct ifnet *, struct mbuf *);
87 
88 static int	octm_medchange(struct ifnet *);
89 static void	octm_medstat(struct ifnet *, struct ifmediareq *);
90 
91 static int	octm_ioctl(struct ifnet *, u_long, caddr_t);
92 
93 static void	octm_rx_intr(void *);
94 
95 static device_method_t octm_methods[] = {
96 	/* Device interface */
97 	DEVMETHOD(device_identify,	octm_identify),
98 	DEVMETHOD(device_probe,		octm_probe),
99 	DEVMETHOD(device_attach,	octm_attach),
100 	DEVMETHOD(device_detach,	octm_detach),
101 	DEVMETHOD(device_shutdown,	octm_shutdown),
102 	{ 0, 0 }
103 };
104 
105 static driver_t octm_driver = {
106 	"octm",
107 	octm_methods,
108 	sizeof (struct octm_softc),
109 };
110 
111 static devclass_t octm_devclass;
112 
113 DRIVER_MODULE(octm, ciu, octm_driver, octm_devclass, 0, 0);
114 
115 static void
octm_identify(driver_t * drv,device_t parent)116 octm_identify(driver_t *drv, device_t parent)
117 {
118 	unsigned i;
119 
120 	if (!octeon_has_feature(OCTEON_FEATURE_MGMT_PORT))
121 		return;
122 
123 	for (i = 0; i < CVMX_MGMT_PORT_NUM_PORTS; i++)
124 		BUS_ADD_CHILD(parent, 0, "octm", i);
125 }
126 
127 static int
octm_probe(device_t dev)128 octm_probe(device_t dev)
129 {
130 	cvmx_mgmt_port_result_t result;
131 
132 	result = cvmx_mgmt_port_initialize(device_get_unit(dev));
133 	switch (result) {
134 	case CVMX_MGMT_PORT_SUCCESS:
135 		break;
136 	case CVMX_MGMT_PORT_NO_MEMORY:
137 		return (ENOBUFS);
138 	case CVMX_MGMT_PORT_INVALID_PARAM:
139 		return (ENXIO);
140 	case CVMX_MGMT_PORT_INIT_ERROR:
141 		return (EIO);
142 	}
143 
144 	device_set_desc(dev, "Cavium Octeon Management Ethernet");
145 
146 	return (0);
147 }
148 
149 static int
octm_attach(device_t dev)150 octm_attach(device_t dev)
151 {
152 	struct ifnet *ifp;
153 	struct octm_softc *sc;
154 	cvmx_mixx_irhwm_t mixx_irhwm;
155 	cvmx_mixx_intena_t mixx_intena;
156 	uint64_t mac;
157 	int error;
158 	int irq;
159 	int rid;
160 
161 	sc = device_get_softc(dev);
162 	sc->sc_dev = dev;
163 	sc->sc_port = device_get_unit(dev);
164 
165 	switch (sc->sc_port) {
166 	case 0:
167 		irq = OCTEON_IRQ_MII;
168 		break;
169 	case 1:
170 		irq = OCTEON_IRQ_MII1;
171 		break;
172 	default:
173 		device_printf(dev, "unsupported management port %u.\n", sc->sc_port);
174 		return (ENXIO);
175 	}
176 
177 	/*
178 	 * Set MAC address for this management port.
179 	 */
180 	mac = 0;
181 	memcpy((u_int8_t *)&mac + 2, cvmx_sysinfo_get()->mac_addr_base, 6);
182 	mac += sc->sc_port;
183 
184 	cvmx_mgmt_port_set_mac(sc->sc_port, mac);
185 
186 	/* No watermark for input ring.  */
187 	mixx_irhwm.u64 = 0;
188 	cvmx_write_csr(CVMX_MIXX_IRHWM(sc->sc_port), mixx_irhwm.u64);
189 
190 	/* Enable input ring interrupts.  */
191 	mixx_intena.u64 = 0;
192 	mixx_intena.s.ithena = 1;
193 	cvmx_write_csr(CVMX_MIXX_INTENA(sc->sc_port), mixx_intena.u64);
194 
195 	/* Allocate and establish interrupt.  */
196 	rid = 0;
197 	sc->sc_intr = bus_alloc_resource(sc->sc_dev, SYS_RES_IRQ, &rid,
198 	    irq, irq, 1, RF_ACTIVE);
199 	if (sc->sc_intr == NULL) {
200 		device_printf(dev, "unable to allocate IRQ.\n");
201 		return (ENXIO);
202 	}
203 
204 	error = bus_setup_intr(sc->sc_dev, sc->sc_intr, INTR_TYPE_NET, NULL,
205 	    octm_rx_intr, sc, &sc->sc_intr_cookie);
206 	if (error != 0) {
207 		device_printf(dev, "unable to setup interrupt.\n");
208 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_intr);
209 		return (ENXIO);
210 	}
211 
212 	bus_describe_intr(sc->sc_dev, sc->sc_intr, sc->sc_intr_cookie, "rx");
213 
214 	/* XXX Possibly should enable TX interrupts.  */
215 
216 	ifp = if_alloc(IFT_ETHER);
217 	if (ifp == NULL) {
218 		device_printf(dev, "cannot allocate ifnet.\n");
219 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_intr);
220 		return (ENOMEM);
221 	}
222 
223 	if_initname(ifp, device_get_name(dev), device_get_unit(dev));
224 	ifp->if_mtu = ETHERMTU;
225 	ifp->if_init = octm_init;
226 	ifp->if_softc = sc;
227 	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_ALLMULTI;
228 	ifp->if_ioctl = octm_ioctl;
229 
230 	sc->sc_ifp = ifp;
231 	sc->sc_flags = ifp->if_flags;
232 
233 	ifmedia_init(&sc->sc_ifmedia, 0, octm_medchange, octm_medstat);
234 
235 	ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_AUTO, 0, NULL);
236 	ifmedia_set(&sc->sc_ifmedia, IFM_ETHER | IFM_AUTO);
237 
238 	ether_ifattach(ifp, (const u_int8_t *)&mac + 2);
239 
240 	ifp->if_transmit = octm_transmit;
241 
242 	ifp->if_hdrlen = sizeof(struct ether_vlan_header);
243 	ifp->if_capabilities = IFCAP_VLAN_MTU;
244 	ifp->if_capenable = ifp->if_capabilities;
245 
246 	IFQ_SET_MAXLEN(&ifp->if_snd, CVMX_MGMT_PORT_NUM_TX_BUFFERS);
247 	ifp->if_snd.ifq_drv_maxlen = CVMX_MGMT_PORT_NUM_TX_BUFFERS;
248 	IFQ_SET_READY(&ifp->if_snd);
249 
250 	return (bus_generic_attach(dev));
251 }
252 
253 static int
octm_detach(device_t dev)254 octm_detach(device_t dev)
255 {
256 	struct octm_softc *sc;
257 	cvmx_mgmt_port_result_t result;
258 
259 	sc = device_get_softc(dev);
260 
261 	result = cvmx_mgmt_port_initialize(sc->sc_port);
262 	switch (result) {
263 	case CVMX_MGMT_PORT_SUCCESS:
264 		break;
265 	case CVMX_MGMT_PORT_NO_MEMORY:
266 		return (ENOBUFS);
267 	case CVMX_MGMT_PORT_INVALID_PARAM:
268 		return (ENXIO);
269 	case CVMX_MGMT_PORT_INIT_ERROR:
270 		return (EIO);
271 	}
272 
273 	bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_intr);
274 	/* XXX Incomplete.  */
275 
276 	return (0);
277 }
278 
279 static int
octm_shutdown(device_t dev)280 octm_shutdown(device_t dev)
281 {
282 	return (octm_detach(dev));
283 }
284 
285 static void
octm_init(void * arg)286 octm_init(void *arg)
287 {
288 	struct ifnet *ifp;
289 	struct octm_softc *sc;
290 	cvmx_mgmt_port_netdevice_flags_t flags;
291 	uint64_t mac;
292 
293 	sc = arg;
294 	ifp = sc->sc_ifp;
295 
296 	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
297 		cvmx_mgmt_port_disable(sc->sc_port);
298 
299 		ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
300 	}
301 
302 	/*
303 	 * NB:
304 	 * MAC must be set before allmulti and promisc below, as
305 	 * cvmx_mgmt_port_set_mac will always enable the CAM, and turning on
306 	 * promiscuous mode only works with the CAM disabled.
307 	 */
308 	mac = 0;
309 	memcpy((u_int8_t *)&mac + 2, IF_LLADDR(ifp), 6);
310 	cvmx_mgmt_port_set_mac(sc->sc_port, mac);
311 
312 	/*
313 	 * This is done unconditionally, rather than only if sc_flags have
314 	 * changed because of set_mac's effect on the CAM noted above.
315 	 */
316 	flags = 0;
317 	if ((ifp->if_flags & IFF_ALLMULTI) != 0)
318 		flags |= CVMX_IFF_ALLMULTI;
319 	if ((ifp->if_flags & IFF_PROMISC) != 0)
320 		flags |= CVMX_IFF_PROMISC;
321 	cvmx_mgmt_port_set_multicast_list(sc->sc_port, flags);
322 
323 	/* XXX link state?  */
324 
325 	if ((ifp->if_flags & IFF_UP) != 0)
326 		cvmx_mgmt_port_enable(sc->sc_port);
327 
328 	ifp->if_drv_flags |= IFF_DRV_RUNNING;
329 	ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
330 }
331 
332 static int
octm_transmit(struct ifnet * ifp,struct mbuf * m)333 octm_transmit(struct ifnet *ifp, struct mbuf *m)
334 {
335 	struct octm_softc *sc;
336 	cvmx_mgmt_port_result_t result;
337 
338 	sc = ifp->if_softc;
339 
340 	if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) !=
341 	    IFF_DRV_RUNNING) {
342 		m_freem(m);
343 		return (0);
344 	}
345 
346 	result = cvmx_mgmt_port_sendm(sc->sc_port, m);
347 
348 	if (result == CVMX_MGMT_PORT_SUCCESS) {
349 		ETHER_BPF_MTAP(ifp, m);
350 
351 		if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
352 		if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len);
353 	} else
354 		if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
355 
356 	m_freem(m);
357 
358 	switch (result) {
359 	case CVMX_MGMT_PORT_SUCCESS:
360 		return (0);
361 	case CVMX_MGMT_PORT_NO_MEMORY:
362 		return (ENOBUFS);
363 	case CVMX_MGMT_PORT_INVALID_PARAM:
364 		return (ENXIO);
365 	case CVMX_MGMT_PORT_INIT_ERROR:
366 		return (EIO);
367 	default:
368 		return (EDOOFUS);
369 	}
370 }
371 
372 static int
octm_medchange(struct ifnet * ifp)373 octm_medchange(struct ifnet *ifp)
374 {
375 	return (ENOTSUP);
376 }
377 
378 static void
octm_medstat(struct ifnet * ifp,struct ifmediareq * ifm)379 octm_medstat(struct ifnet *ifp, struct ifmediareq *ifm)
380 {
381 	struct octm_softc *sc;
382 	cvmx_helper_link_info_t link_info;
383 
384 	sc = ifp->if_softc;
385 
386 	ifm->ifm_status = IFM_AVALID;
387 	ifm->ifm_active = IFT_ETHER;
388 
389 	link_info = cvmx_mgmt_port_link_get(sc->sc_port);
390 	if (!link_info.s.link_up)
391 		return;
392 
393 	ifm->ifm_status |= IFM_ACTIVE;
394 
395 	switch (link_info.s.speed) {
396 	case 10:
397 		ifm->ifm_active |= IFM_10_T;
398 		break;
399 	case 100:
400 		ifm->ifm_active |= IFM_100_TX;
401 		break;
402 	case 1000:
403 		ifm->ifm_active |= IFM_1000_T;
404 		break;
405 	case 10000:
406 		ifm->ifm_active |= IFM_10G_T;
407 		break;
408 	}
409 
410 	if (link_info.s.full_duplex)
411 		ifm->ifm_active |= IFM_FDX;
412 	else
413 		ifm->ifm_active |= IFM_HDX;
414 }
415 
416 static int
octm_ioctl(struct ifnet * ifp,u_long cmd,caddr_t data)417 octm_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
418 {
419 	struct octm_softc *sc;
420 	struct ifreq *ifr;
421 #ifdef INET
422 	struct ifaddr *ifa;
423 #endif
424 	int error;
425 
426 	sc = ifp->if_softc;
427 	ifr = (struct ifreq *)data;
428 #ifdef INET
429 	ifa = (struct ifaddr *)data;
430 #endif
431 
432 	switch (cmd) {
433 	case SIOCSIFADDR:
434 #ifdef INET
435 		/*
436 		 * Avoid reinitialization unless it's necessary.
437 		 */
438 		if (ifa->ifa_addr->sa_family == AF_INET) {
439 			ifp->if_flags |= IFF_UP;
440 			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
441 				octm_init(sc);
442 			arp_ifinit(ifp, ifa);
443 
444 			return (0);
445 		}
446 #endif
447 		error = ether_ioctl(ifp, cmd, data);
448 		if (error != 0)
449 			return (error);
450 		return (0);
451 
452 	case SIOCSIFFLAGS:
453 		if (ifp->if_flags == sc->sc_flags)
454 			return (0);
455 		if ((ifp->if_flags & IFF_UP) != 0) {
456 			octm_init(sc);
457 		} else {
458 			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
459 				cvmx_mgmt_port_disable(sc->sc_port);
460 
461 				ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
462 			}
463 		}
464 		sc->sc_flags = ifp->if_flags;
465 		return (0);
466 
467 	case SIOCSIFCAP:
468 		/*
469 		 * Just change the capabilities in software, currently none
470 		 * require reprogramming hardware, they just toggle whether we
471 		 * make use of already-present facilities in software.
472 		 */
473 		ifp->if_capenable = ifr->ifr_reqcap;
474 		return (0);
475 
476 	case SIOCSIFMTU:
477 		cvmx_mgmt_port_set_max_packet_size(sc->sc_port, ifr->ifr_mtu + ifp->if_hdrlen);
478 		return (0);
479 
480 	case SIOCSIFMEDIA:
481 	case SIOCGIFMEDIA:
482 		error = ifmedia_ioctl(ifp, ifr, &sc->sc_ifmedia, cmd);
483 		if (error != 0)
484 			return (error);
485 		return (0);
486 
487 	default:
488 		error = ether_ioctl(ifp, cmd, data);
489 		if (error != 0)
490 			return (error);
491 		return (0);
492 	}
493 }
494 
495 static void
octm_rx_intr(void * arg)496 octm_rx_intr(void *arg)
497 {
498 	struct octm_softc *sc = arg;
499 	cvmx_mixx_isr_t mixx_isr;
500 	int len;
501 
502 	mixx_isr.u64 = cvmx_read_csr(CVMX_MIXX_ISR(sc->sc_port));
503 	if (!mixx_isr.s.irthresh) {
504 		device_printf(sc->sc_dev, "stray interrupt.\n");
505 		return;
506 	}
507 
508 	for (;;) {
509 		struct mbuf *m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
510 		if (m == NULL) {
511 			device_printf(sc->sc_dev, "no memory for receive mbuf.\n");
512 			return;
513 		}
514 
515 		len = cvmx_mgmt_port_receive(sc->sc_port, MCLBYTES, m->m_data);
516 		if (len > 0) {
517 			m->m_pkthdr.rcvif = sc->sc_ifp;
518 			m->m_pkthdr.len = m->m_len = len;
519 
520 			if_inc_counter(sc->sc_ifp, IFCOUNTER_IPACKETS, 1);
521 
522 			(*sc->sc_ifp->if_input)(sc->sc_ifp, m);
523 
524 			continue;
525 		}
526 
527 		m_freem(m);
528 
529 		if (len == 0)
530 			break;
531 
532 		if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1);
533 	}
534 
535 	/* Acknowledge interrupts.  */
536 	cvmx_write_csr(CVMX_MIXX_ISR(sc->sc_port), mixx_isr.u64);
537 	cvmx_read_csr(CVMX_MIXX_ISR(sc->sc_port));
538 }
539