NetBSD/sys/dev/ppbus/if_plip.c

942 lines
22 KiB
C
Raw Normal View History

/* $NetBSD: if_plip.c,v 1.30 2018/06/26 06:48:02 msaitoh Exp $ */
2004-01-21 03:33:37 +03:00
/*-
* Copyright (c) 1997 Poul-Henning Kamp
2004-02-11 00:55:38 +03:00
* Copyright (c) 2003, 2004 Gary Thorpe <gathorpe@users.sourceforge.net>
* All rights reserved.
*
* 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 AUTHOR 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 AUTHOR 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.
*
* From Id: lpt.c,v 1.55.2.1 1996/11/12 09:08:38 phk Exp
* FreeBSD: src/sys/dev/ppbus/if_plip.c,v 1.19.2.1 2000/05/24 00:20:57 n_hibma Exp
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_plip.c,v 1.30 2018/06/26 06:48:02 msaitoh Exp $");
/*
* Parallel port TCP/IP interfaces added. I looked at the driver from
* MACH but this is a complete rewrite, and btw. incompatible, and it
* should perform better too. I have never run the MACH driver though.
*
* This driver sends two bytes (0x08, 0x00) in front of each packet,
* to allow us to distinguish another format later.
*
2018-06-25 16:28:12 +03:00
* Now added a Linux/Crynwr compatibility mode which is enabled using
* IF_LINK0 - Tim Wilkinson.
*
* TODO:
* Make HDLC/PPP mode, use IF_LLC1 to enable.
*
* Connect the two computers using a Laplink parallel cable to use this
* feature:
*
* +----------------------------------------+
* |A-name A-End B-End Descr. Port/Bit |
* +----------------------------------------+
* |DATA0 2 15 Data 0/0x01 |
* |-ERROR 15 2 1/0x08 |
* +----------------------------------------+
* |DATA1 3 13 Data 0/0x02 |
* |+SLCT 13 3 1/0x10 |
* +----------------------------------------+
* |DATA2 4 12 Data 0/0x04 |
* |+PE 12 4 1/0x20 |
* +----------------------------------------+
* |DATA3 5 10 Strobe 0/0x08 |
* |-ACK 10 5 1/0x40 |
* +----------------------------------------+
* |DATA4 6 11 Data 0/0x10 |
* |BUSY 11 6 1/~0x80 |
* +----------------------------------------+
* |GND 18-25 18-25 GND - |
* +----------------------------------------+
*
* Expect transfer-rates up to 75 kbyte/sec.
*
* If GCC could correctly grok
2005-12-25 01:59:39 +03:00
* register int port __asm("edx")
* the code would be cleaner
*
* Poul-Henning Kamp <phk@freebsd.org>
*/
/*
* Update for ppbus, PLIP support only - Nicolas Souchu
2005-02-27 03:26:58 +03:00
*/
#include "opt_inet.h"
#include "opt_plip.h"
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/types.h>
#include <sys/device.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/netisr.h>
#include <sys/time.h>
#include <net/bpf.h>
#ifdef INET
#include <netinet/in_var.h>
/* #include <netinet/in.h> */
#else
#error Cannot config lp/plip without inet
#endif
#include <dev/ppbus/ppbus_base.h>
#include <dev/ppbus/ppbus_device.h>
#include <dev/ppbus/ppbus_io.h>
#include <dev/ppbus/ppbus_var.h>
#include <machine/types.h>
#include <sys/intr.h>
#ifndef LPMTU /* MTU for the lp# interfaces */
#define LPMTU 1500
#endif
#ifndef LPMAXSPIN1 /* DELAY factor for the lp# interfaces */
#define LPMAXSPIN1 8000 /* Spinning for remote intr to happen */
#endif
#ifndef LPMAXSPIN2 /* DELAY factor for the lp# interfaces */
#define LPMAXSPIN2 500 /* Spinning for remote handshake to happen */
#endif
#ifndef LPMAXERRS /* Max errors before !RUNNING */
#define LPMAXERRS 100
#endif
#ifndef LPMAXRTRY
2005-02-27 03:26:58 +03:00
#define LPMAXRTRY 100 /* If channel busy, retry LPMAXRTRY
consecutive times */
#endif
#define CLPIPHDRLEN 14 /* We send dummy ethernet addresses (two) + packet type in front of packet */
#define CLPIP_SHAKE 0x80 /* This bit toggles between nibble reception */
#define MLPIPHDRLEN CLPIPHDRLEN
#define LPIPHDRLEN 2 /* We send 0x08, 0x00 in front of packet */
#define LPIP_SHAKE 0x40 /* This bit toggles between nibble reception */
#if !defined(MLPIPHDRLEN) || LPIPHDRLEN > MLPIPHDRLEN
#define MLPIPHDRLEN LPIPHDRLEN
#endif
#define LPIPTBLSIZE 256 /* Size of octet translation table */
#define LP_PRINTF if (lpflag) printf
#ifdef PLIP_DEBUG
static int volatile lpflag = 1;
#else
static int volatile lpflag = 0;
#endif
/* Tx/Rsv tables for the lp interface */
static u_char *txmith;
#define txmitl (txmith+(1*LPIPTBLSIZE))
#define trecvh (txmith+(2*LPIPTBLSIZE))
#define trecvl (txmith+(3*LPIPTBLSIZE))
static u_char *ctxmith;
#define ctxmitl (ctxmith+(1*LPIPTBLSIZE))
#define ctrecvh (ctxmith+(2*LPIPTBLSIZE))
#define ctrecvl (ctxmith+(3*LPIPTBLSIZE))
static uint16_t lp_count = 0;
/* Autoconf functions */
static int lp_probe(device_t, cfdata_t, void *);
static void lp_attach(device_t, device_t, void *);
static int lp_detach(device_t, int);
/* Soft config data */
struct lp_softc {
struct ppbus_device_softc ppbus_dev;
struct ifnet sc_if;
u_char *sc_ifbuf;
unsigned short sc_iferrs;
unsigned short sc_xmit_rtry;
u_int8_t sc_dev_ok; /* Zero means ok */
};
/* Autoconf structure */
CFATTACH_DECL_NEW(plip, sizeof(struct lp_softc), lp_probe, lp_attach, lp_detach,
NULL);
/* Functions for the lp interface */
static void lpinittables(void);
static void lpfreetables(void);
static int lpioctl(struct ifnet *, u_long, void *);
static int lpoutput(struct ifnet *, struct mbuf *, const struct sockaddr *,
2018-06-25 16:28:12 +03:00
const struct rtentry *);
static void lpstart(struct ifnet *);
static void lp_intr(void *);
static int
lp_probe(device_t parent, cfdata_t match, void *aux)
{
struct ppbus_attach_args * args = aux;
2005-02-27 03:26:58 +03:00
/* Fail if ppbus is not interrupt capable */
2018-06-25 16:28:12 +03:00
if (args->capabilities & PPBUS_HAS_INTR)
return 1;
2005-02-27 03:26:58 +03:00
printf("%s(%s): not an interrupt-driven port.\n", __func__,
2008-04-08 11:35:35 +04:00
device_xname(parent));
return 0;
}
2005-02-27 03:26:58 +03:00
static void
lp_attach(device_t parent, device_t self, void *aux)
{
2006-03-29 21:23:56 +04:00
struct lp_softc * lp = device_private(self);
struct ifnet * ifp = &lp->sc_if;
lp->ppbus_dev.sc_dev = self;
lp->sc_dev_ok = 0;
lp->sc_ifbuf = NULL;
lp->sc_iferrs = 0;
lp->sc_xmit_rtry = 0;
2006-03-29 21:23:56 +04:00
ifp->if_softc = lp;
2008-04-08 11:35:35 +04:00
strlcpy(ifp->if_xname, device_xname(self), IFNAMSIZ);
ifp->if_mtu = LPMTU;
ifp->if_flags = IFF_SIMPLEX | IFF_POINTOPOINT | IFF_MULTICAST;
ifp->if_ioctl = lpioctl;
ifp->if_output = lpoutput;
ifp->if_start = lpstart;
ifp->if_type = IFT_PARA;
ifp->if_hdrlen = 0;
ifp->if_addrlen = 0;
ifp->if_dlt = DLT_NULL;
IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
IFQ_SET_READY(&ifp->if_snd);
if_attach(ifp);
if_alloc_sadl(ifp);
bpf_attach(ifp, DLT_NULL, sizeof(u_int32_t));
2018-06-25 16:28:12 +03:00
if (lp_count++ == 0)
lpinittables();
printf("\n");
}
static int
lp_detach(device_t self, int flags)
{
int error = 0;
2006-03-29 21:23:56 +04:00
struct lp_softc * lp = device_private(self);
device_t ppbus = device_parent(self);
2005-02-27 03:26:58 +03:00
2018-06-25 16:28:12 +03:00
if (lp->sc_dev_ok) {
if (!(flags & DETACH_QUIET))
LP_PRINTF("%s(%s): device not properly attached! "
2005-02-27 03:26:58 +03:00
"Skipping detach....\n", __func__,
2008-04-08 11:35:35 +04:00
device_xname(self));
return error;
}
/* If interface is up, bring it down and release ppbus */
2018-06-25 16:28:12 +03:00
if (lp->sc_if.if_flags & IFF_RUNNING) {
ppbus_wctr(ppbus, 0x00);
if_detach(&lp->sc_if);
error = ppbus_remove_handler(ppbus, lp_intr);
2018-06-25 16:28:12 +03:00
if (error) {
if (!(flags & DETACH_QUIET))
LP_PRINTF("%s(%s): unable to remove interrupt "
2005-02-27 03:26:58 +03:00
"callback.\n", __func__,
2008-04-08 11:35:35 +04:00
device_xname(self));
2018-06-25 16:28:12 +03:00
if (!(flags & DETACH_FORCE))
return error;
}
error = ppbus_release_bus(ppbus, self, 0, 0);
2018-06-25 16:28:12 +03:00
if (error) {
if (!(flags & DETACH_QUIET))
2005-02-27 03:26:58 +03:00
LP_PRINTF("%s(%s): error releasing bus %s.\n",
2008-04-08 11:35:35 +04:00
__func__, device_xname(self),
device_xname(ppbus));
2018-06-25 16:28:12 +03:00
if (!(flags & DETACH_FORCE))
return error;
}
}
2018-06-25 16:28:12 +03:00
if (lp->sc_ifbuf)
free(lp->sc_ifbuf, M_DEVBUF);
2018-06-25 16:28:12 +03:00
if (--lp_count == 0)
lpfreetables();
return error;
}
/*
* Build the translation tables for the LPIP (BSD unix) protocol.
* We don't want to calculate these nasties in our tight loop, so we
* precalculate them when we initialize.
*/
2005-02-27 03:26:58 +03:00
static void
2018-06-25 16:28:12 +03:00
lpinittables(void)
{
int i;
if (!txmith)
txmith = malloc(4*LPIPTBLSIZE, M_DEVBUF, M_WAITOK);
if (!ctxmith)
ctxmith = malloc(4*LPIPTBLSIZE, M_DEVBUF, M_WAITOK);
2018-06-25 16:28:12 +03:00
for (i = 0; i < LPIPTBLSIZE; i++) {
ctxmith[i] = (i & 0xF0) >> 4;
ctxmitl[i] = 0x10 | (i & 0x0F);
ctrecvh[i] = (i & 0x78) << 1;
ctrecvl[i] = (i & 0x78) >> 3;
}
2018-06-25 16:28:12 +03:00
for (i = 0; i < LPIPTBLSIZE; i++) {
txmith[i] = ((i & 0x80) >> 3) | ((i & 0x70) >> 4) | 0x08;
txmitl[i] = ((i & 0x08) << 1) | (i & 0x07);
trecvh[i] = ((~i) & 0x80) | ((i & 0x38) << 1);
trecvl[i] = (((~i) & 0x80) >> 4) | ((i & 0x38) >> 3);
}
}
/* Free translation tables */
2005-02-27 03:26:58 +03:00
static void
2018-06-25 16:28:12 +03:00
lpfreetables(void)
{
if (txmith)
free(txmith, M_DEVBUF);
if (ctxmith)
free(ctxmith, M_DEVBUF);
txmith = ctxmith = NULL;
}
2005-02-27 03:26:58 +03:00
/* Process an ioctl request. */
static int
*** Summary *** When a link-layer address changes (e.g., ifconfig ex0 link 02:de:ad:be:ef:02 active), send a gratuitous ARP and/or a Neighbor Advertisement to update the network-/link-layer address bindings on our LAN peers. Refuse a change of ethernet address to the address 00:00:00:00:00:00 or to any multicast/broadcast address. (Thanks matt@.) Reorder ifnet ioctl operations so that driver ioctls may inherit the functions of their "class"---ether_ioctl(), fddi_ioctl(), et cetera---and the class ioctls may inherit from the generic ioctl, ifioctl_common(), but both driver- and class-ioctls may override the generic behavior. Make network drivers share more code. Distinguish a "factory" link-layer address from others for the purposes of both protecting that address from deletion and computing EUI64. Return consistent, appropriate error codes from network drivers. Improve readability. KNF. *** Details *** In if_attach(), always initialize the interface ioctl routine, ifnet->if_ioctl, if the driver has not already initialized it. Delete if_ioctl == NULL tests everywhere else, because it cannot happen. In the ioctl routines of network interfaces, inherit common ioctl behaviors by calling either ifioctl_common() or whichever ioctl routine is appropriate for the class of interface---e.g., ether_ioctl() for ethernets. Stop (ab)using SIOCSIFADDR and start to use SIOCINITIFADDR. In the user->kernel interface, SIOCSIFADDR's argument was an ifreq, but on the protocol->ifnet interface, SIOCSIFADDR's argument was an ifaddr. That was confusing, and it would work against me as I make it possible for a network interface to overload most ioctls. On the protocol->ifnet interface, replace SIOCSIFADDR with SIOCINITIFADDR. In ifioctl(), return EPERM if userland tries to invoke SIOCINITIFADDR. In ifioctl(), give the interface the first shot at handling most interface ioctls, and give the protocol the second shot, instead of the other way around. Finally, let compatibility code (COMPAT_OSOCK) take a shot. Pull device initialization out of switch statements under SIOCINITIFADDR. For example, pull ..._init() out of any switch statement that looks like this: switch (...->sa_family) { case ...: ..._init(); ... break; ... default: ..._init(); ... break; } Rewrite many if-else clauses that handle all permutations of IFF_UP and IFF_RUNNING to use a switch statement, switch (x & (IFF_UP|IFF_RUNNING)) { case 0: ... break; case IFF_RUNNING: ... break; case IFF_UP: ... break; case IFF_UP|IFF_RUNNING: ... break; } unifdef lots of code containing #ifdef FreeBSD, #ifdef NetBSD, and #ifdef SIOCSIFMTU, especially in fwip(4) and in ndis(4). In ipw(4), remove an if_set_sadl() call that is out of place. In nfe(4), reuse the jumbo MTU logic in ether_ioctl(). Let ethernets register a callback for setting h/w state such as promiscuous mode and the multicast filter in accord with a change in the if_flags: ether_set_ifflags_cb() registers a callback that returns ENETRESET if the caller should reset the ethernet by calling if_init(), 0 on success, != 0 on failure. Pull common code from ex(4), gem(4), nfe(4), sip(4), tlp(4), vge(4) into ether_ioctl(), and register if_flags callbacks for those drivers. Return ENOTTY instead of EINVAL for inappropriate ioctls. In zyd(4), use ENXIO instead of ENOTTY to indicate that the device is not any longer attached. Add to if_set_sadl() a boolean 'factory' argument that indicates whether a link-layer address was assigned by the factory or some other source. In a comment, recommend using the factory address for generating an EUI64, and update in6_get_hw_ifid() to prefer a factory address to any other link-layer address. Add a routing message, RTM_LLINFO_UPD, that tells protocols to update the binding of network-layer addresses to link-layer addresses. Implement this message in IPv4 and IPv6 by sending a gratuitous ARP or a neighbor advertisement, respectively. Generate RTM_LLINFO_UPD messages on a change of an interface's link-layer address. In ether_ioctl(), do not let SIOCALIFADDR set a link-layer address that is broadcast/multicast or equal to 00:00:00:00:00:00. Make ether_ioctl() call ifioctl_common() to handle ioctls that it does not understand. In gif(4), initialize if_softc and use it, instead of assuming that the gif_softc and ifp overlap. Let ifioctl_common() handle SIOCGIFADDR. Sprinkle rtcache_invariants(), which checks on DIAGNOSTIC kernels that certain invariants on a struct route are satisfied. In agr(4), rewrite agr_ioctl_filter() to be a bit more explicit about the ioctls that we do not allow on an agr(4) member interface. bzero -> memset. Delete unnecessary casts to void *. Use sockaddr_in_init() and sockaddr_in6_init(). Compare pointers with NULL instead of "testing truth". Replace some instances of (type *)0 with NULL. Change some K&R prototypes to ANSI C, and join lines.
2008-11-07 03:20:01 +03:00
lpioctl(struct ifnet *ifp, u_long cmd, void *data)
{
struct lp_softc * sc = ifp->if_softc;
device_t dev = sc->ppbus_dev.sc_dev;
device_t ppbus = device_parent(dev);
struct ifaddr * ifa = (struct ifaddr *)data;
struct ifreq * ifr = (struct ifreq *)data;
u_char * ptr;
int error, s;
error = 0;
s = splnet();
2018-06-25 16:28:12 +03:00
if (sc->sc_dev_ok) {
2005-02-27 03:26:58 +03:00
LP_PRINTF("%s(%s): device not properly attached!", __func__,
2008-04-08 11:35:35 +04:00
device_xname(dev));
error = ENODEV;
goto end;
}
2005-02-27 03:26:58 +03:00
switch (cmd) {
case SIOCSIFDSTADDR:
if (ifa->ifa_addr->sa_family != AF_INET)
error = EAFNOSUPPORT;
break;
2005-02-27 03:26:58 +03:00
*** Summary *** When a link-layer address changes (e.g., ifconfig ex0 link 02:de:ad:be:ef:02 active), send a gratuitous ARP and/or a Neighbor Advertisement to update the network-/link-layer address bindings on our LAN peers. Refuse a change of ethernet address to the address 00:00:00:00:00:00 or to any multicast/broadcast address. (Thanks matt@.) Reorder ifnet ioctl operations so that driver ioctls may inherit the functions of their "class"---ether_ioctl(), fddi_ioctl(), et cetera---and the class ioctls may inherit from the generic ioctl, ifioctl_common(), but both driver- and class-ioctls may override the generic behavior. Make network drivers share more code. Distinguish a "factory" link-layer address from others for the purposes of both protecting that address from deletion and computing EUI64. Return consistent, appropriate error codes from network drivers. Improve readability. KNF. *** Details *** In if_attach(), always initialize the interface ioctl routine, ifnet->if_ioctl, if the driver has not already initialized it. Delete if_ioctl == NULL tests everywhere else, because it cannot happen. In the ioctl routines of network interfaces, inherit common ioctl behaviors by calling either ifioctl_common() or whichever ioctl routine is appropriate for the class of interface---e.g., ether_ioctl() for ethernets. Stop (ab)using SIOCSIFADDR and start to use SIOCINITIFADDR. In the user->kernel interface, SIOCSIFADDR's argument was an ifreq, but on the protocol->ifnet interface, SIOCSIFADDR's argument was an ifaddr. That was confusing, and it would work against me as I make it possible for a network interface to overload most ioctls. On the protocol->ifnet interface, replace SIOCSIFADDR with SIOCINITIFADDR. In ifioctl(), return EPERM if userland tries to invoke SIOCINITIFADDR. In ifioctl(), give the interface the first shot at handling most interface ioctls, and give the protocol the second shot, instead of the other way around. Finally, let compatibility code (COMPAT_OSOCK) take a shot. Pull device initialization out of switch statements under SIOCINITIFADDR. For example, pull ..._init() out of any switch statement that looks like this: switch (...->sa_family) { case ...: ..._init(); ... break; ... default: ..._init(); ... break; } Rewrite many if-else clauses that handle all permutations of IFF_UP and IFF_RUNNING to use a switch statement, switch (x & (IFF_UP|IFF_RUNNING)) { case 0: ... break; case IFF_RUNNING: ... break; case IFF_UP: ... break; case IFF_UP|IFF_RUNNING: ... break; } unifdef lots of code containing #ifdef FreeBSD, #ifdef NetBSD, and #ifdef SIOCSIFMTU, especially in fwip(4) and in ndis(4). In ipw(4), remove an if_set_sadl() call that is out of place. In nfe(4), reuse the jumbo MTU logic in ether_ioctl(). Let ethernets register a callback for setting h/w state such as promiscuous mode and the multicast filter in accord with a change in the if_flags: ether_set_ifflags_cb() registers a callback that returns ENETRESET if the caller should reset the ethernet by calling if_init(), 0 on success, != 0 on failure. Pull common code from ex(4), gem(4), nfe(4), sip(4), tlp(4), vge(4) into ether_ioctl(), and register if_flags callbacks for those drivers. Return ENOTTY instead of EINVAL for inappropriate ioctls. In zyd(4), use ENXIO instead of ENOTTY to indicate that the device is not any longer attached. Add to if_set_sadl() a boolean 'factory' argument that indicates whether a link-layer address was assigned by the factory or some other source. In a comment, recommend using the factory address for generating an EUI64, and update in6_get_hw_ifid() to prefer a factory address to any other link-layer address. Add a routing message, RTM_LLINFO_UPD, that tells protocols to update the binding of network-layer addresses to link-layer addresses. Implement this message in IPv4 and IPv6 by sending a gratuitous ARP or a neighbor advertisement, respectively. Generate RTM_LLINFO_UPD messages on a change of an interface's link-layer address. In ether_ioctl(), do not let SIOCALIFADDR set a link-layer address that is broadcast/multicast or equal to 00:00:00:00:00:00. Make ether_ioctl() call ifioctl_common() to handle ioctls that it does not understand. In gif(4), initialize if_softc and use it, instead of assuming that the gif_softc and ifp overlap. Let ifioctl_common() handle SIOCGIFADDR. Sprinkle rtcache_invariants(), which checks on DIAGNOSTIC kernels that certain invariants on a struct route are satisfied. In agr(4), rewrite agr_ioctl_filter() to be a bit more explicit about the ioctls that we do not allow on an agr(4) member interface. bzero -> memset. Delete unnecessary casts to void *. Use sockaddr_in_init() and sockaddr_in6_init(). Compare pointers with NULL instead of "testing truth". Replace some instances of (type *)0 with NULL. Change some K&R prototypes to ANSI C, and join lines.
2008-11-07 03:20:01 +03:00
case SIOCINITIFADDR:
if (ifa->ifa_addr->sa_family != AF_INET) {
error = EAFNOSUPPORT;
break;
}
ifp->if_flags |= IFF_UP;
/* FALLTHROUGH */
case SIOCSIFFLAGS:
*** Summary *** When a link-layer address changes (e.g., ifconfig ex0 link 02:de:ad:be:ef:02 active), send a gratuitous ARP and/or a Neighbor Advertisement to update the network-/link-layer address bindings on our LAN peers. Refuse a change of ethernet address to the address 00:00:00:00:00:00 or to any multicast/broadcast address. (Thanks matt@.) Reorder ifnet ioctl operations so that driver ioctls may inherit the functions of their "class"---ether_ioctl(), fddi_ioctl(), et cetera---and the class ioctls may inherit from the generic ioctl, ifioctl_common(), but both driver- and class-ioctls may override the generic behavior. Make network drivers share more code. Distinguish a "factory" link-layer address from others for the purposes of both protecting that address from deletion and computing EUI64. Return consistent, appropriate error codes from network drivers. Improve readability. KNF. *** Details *** In if_attach(), always initialize the interface ioctl routine, ifnet->if_ioctl, if the driver has not already initialized it. Delete if_ioctl == NULL tests everywhere else, because it cannot happen. In the ioctl routines of network interfaces, inherit common ioctl behaviors by calling either ifioctl_common() or whichever ioctl routine is appropriate for the class of interface---e.g., ether_ioctl() for ethernets. Stop (ab)using SIOCSIFADDR and start to use SIOCINITIFADDR. In the user->kernel interface, SIOCSIFADDR's argument was an ifreq, but on the protocol->ifnet interface, SIOCSIFADDR's argument was an ifaddr. That was confusing, and it would work against me as I make it possible for a network interface to overload most ioctls. On the protocol->ifnet interface, replace SIOCSIFADDR with SIOCINITIFADDR. In ifioctl(), return EPERM if userland tries to invoke SIOCINITIFADDR. In ifioctl(), give the interface the first shot at handling most interface ioctls, and give the protocol the second shot, instead of the other way around. Finally, let compatibility code (COMPAT_OSOCK) take a shot. Pull device initialization out of switch statements under SIOCINITIFADDR. For example, pull ..._init() out of any switch statement that looks like this: switch (...->sa_family) { case ...: ..._init(); ... break; ... default: ..._init(); ... break; } Rewrite many if-else clauses that handle all permutations of IFF_UP and IFF_RUNNING to use a switch statement, switch (x & (IFF_UP|IFF_RUNNING)) { case 0: ... break; case IFF_RUNNING: ... break; case IFF_UP: ... break; case IFF_UP|IFF_RUNNING: ... break; } unifdef lots of code containing #ifdef FreeBSD, #ifdef NetBSD, and #ifdef SIOCSIFMTU, especially in fwip(4) and in ndis(4). In ipw(4), remove an if_set_sadl() call that is out of place. In nfe(4), reuse the jumbo MTU logic in ether_ioctl(). Let ethernets register a callback for setting h/w state such as promiscuous mode and the multicast filter in accord with a change in the if_flags: ether_set_ifflags_cb() registers a callback that returns ENETRESET if the caller should reset the ethernet by calling if_init(), 0 on success, != 0 on failure. Pull common code from ex(4), gem(4), nfe(4), sip(4), tlp(4), vge(4) into ether_ioctl(), and register if_flags callbacks for those drivers. Return ENOTTY instead of EINVAL for inappropriate ioctls. In zyd(4), use ENXIO instead of ENOTTY to indicate that the device is not any longer attached. Add to if_set_sadl() a boolean 'factory' argument that indicates whether a link-layer address was assigned by the factory or some other source. In a comment, recommend using the factory address for generating an EUI64, and update in6_get_hw_ifid() to prefer a factory address to any other link-layer address. Add a routing message, RTM_LLINFO_UPD, that tells protocols to update the binding of network-layer addresses to link-layer addresses. Implement this message in IPv4 and IPv6 by sending a gratuitous ARP or a neighbor advertisement, respectively. Generate RTM_LLINFO_UPD messages on a change of an interface's link-layer address. In ether_ioctl(), do not let SIOCALIFADDR set a link-layer address that is broadcast/multicast or equal to 00:00:00:00:00:00. Make ether_ioctl() call ifioctl_common() to handle ioctls that it does not understand. In gif(4), initialize if_softc and use it, instead of assuming that the gif_softc and ifp overlap. Let ifioctl_common() handle SIOCGIFADDR. Sprinkle rtcache_invariants(), which checks on DIAGNOSTIC kernels that certain invariants on a struct route are satisfied. In agr(4), rewrite agr_ioctl_filter() to be a bit more explicit about the ioctls that we do not allow on an agr(4) member interface. bzero -> memset. Delete unnecessary casts to void *. Use sockaddr_in_init() and sockaddr_in6_init(). Compare pointers with NULL instead of "testing truth". Replace some instances of (type *)0 with NULL. Change some K&R prototypes to ANSI C, and join lines.
2008-11-07 03:20:01 +03:00
if ((error = ifioctl_common(ifp, cmd, data)) != 0)
break;
2018-06-25 16:28:12 +03:00
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) == IFF_UP) {
if ((error = ppbus_request_bus(ppbus, dev, 0, 0)))
break;
error = ppbus_set_mode(ppbus, PPBUS_COMPATIBLE, 0);
2018-06-25 16:28:12 +03:00
if (error)
break;
2005-02-27 03:26:58 +03:00
error = ppbus_add_handler(ppbus, lp_intr, dev);
2018-06-25 16:28:12 +03:00
if (error) {
LP_PRINTF("%s(%s): unable to register interrupt"
2005-02-27 03:26:58 +03:00
" callback.\n", __func__,
2008-04-08 11:35:35 +04:00
device_xname(dev));
ppbus_release_bus(ppbus, dev, 0, 0);
break;
}
/* Allocate a buffer if necessary */
2018-06-25 16:28:12 +03:00
if (sc->sc_ifbuf == NULL) {
2005-02-27 03:26:58 +03:00
sc->sc_ifbuf = malloc(sc->sc_if.if_mtu +
MLPIPHDRLEN, M_DEVBUF, M_NOWAIT);
if (!sc->sc_ifbuf) {
error = ENOBUFS;
ppbus_release_bus(ppbus, dev, 0, 0);
break;
}
}
ppbus_wctr(ppbus, IRQENABLE);
ifp->if_flags |= IFF_RUNNING;
}
2018-06-25 16:28:12 +03:00
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) == IFF_RUNNING) {
ppbus_remove_handler(ppbus, lp_intr);
error = ppbus_release_bus(ppbus, dev, 0, 0);
ifp->if_flags &= ~IFF_RUNNING;
}
/* Go quiescent */
ppbus_wdtr(ppbus, 0);
break;
case SIOCSIFMTU:
2018-06-25 16:28:12 +03:00
if (sc->sc_if.if_mtu == ifr->ifr_mtu)
break;
ptr = sc->sc_ifbuf;
2005-02-27 03:26:58 +03:00
sc->sc_ifbuf = malloc(ifr->ifr_mtu+MLPIPHDRLEN, M_DEVBUF,
M_NOWAIT);
if (!sc->sc_ifbuf) {
sc->sc_ifbuf = ptr;
error = ENOBUFS;
break;
}
2018-06-25 16:28:12 +03:00
if (ptr)
free(ptr,M_DEVBUF);
/*FALLTHROUGH*/
case SIOCGIFMTU:
2008-04-15 23:03:26 +04:00
if ((error = ifioctl_common(ifp, cmd, data)) == ENETRESET)
error = 0;
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
if (ifr == NULL) {
error = EAFNOSUPPORT; /* XXX */
break;
}
switch (ifreq_getaddr(cmd, ifr)->sa_family) {
case AF_INET:
break;
default:
2017-06-25 15:27:13 +03:00
splx(s);
return EAFNOSUPPORT;
}
break;
case SIOCGIFMEDIA:
/*
* No ifmedia support at this stage; maybe use it
* in future for eg. protocol selection.
*/
default:
LP_PRINTF("LP:ioctl(0x%lx)\n", cmd);
*** Summary *** When a link-layer address changes (e.g., ifconfig ex0 link 02:de:ad:be:ef:02 active), send a gratuitous ARP and/or a Neighbor Advertisement to update the network-/link-layer address bindings on our LAN peers. Refuse a change of ethernet address to the address 00:00:00:00:00:00 or to any multicast/broadcast address. (Thanks matt@.) Reorder ifnet ioctl operations so that driver ioctls may inherit the functions of their "class"---ether_ioctl(), fddi_ioctl(), et cetera---and the class ioctls may inherit from the generic ioctl, ifioctl_common(), but both driver- and class-ioctls may override the generic behavior. Make network drivers share more code. Distinguish a "factory" link-layer address from others for the purposes of both protecting that address from deletion and computing EUI64. Return consistent, appropriate error codes from network drivers. Improve readability. KNF. *** Details *** In if_attach(), always initialize the interface ioctl routine, ifnet->if_ioctl, if the driver has not already initialized it. Delete if_ioctl == NULL tests everywhere else, because it cannot happen. In the ioctl routines of network interfaces, inherit common ioctl behaviors by calling either ifioctl_common() or whichever ioctl routine is appropriate for the class of interface---e.g., ether_ioctl() for ethernets. Stop (ab)using SIOCSIFADDR and start to use SIOCINITIFADDR. In the user->kernel interface, SIOCSIFADDR's argument was an ifreq, but on the protocol->ifnet interface, SIOCSIFADDR's argument was an ifaddr. That was confusing, and it would work against me as I make it possible for a network interface to overload most ioctls. On the protocol->ifnet interface, replace SIOCSIFADDR with SIOCINITIFADDR. In ifioctl(), return EPERM if userland tries to invoke SIOCINITIFADDR. In ifioctl(), give the interface the first shot at handling most interface ioctls, and give the protocol the second shot, instead of the other way around. Finally, let compatibility code (COMPAT_OSOCK) take a shot. Pull device initialization out of switch statements under SIOCINITIFADDR. For example, pull ..._init() out of any switch statement that looks like this: switch (...->sa_family) { case ...: ..._init(); ... break; ... default: ..._init(); ... break; } Rewrite many if-else clauses that handle all permutations of IFF_UP and IFF_RUNNING to use a switch statement, switch (x & (IFF_UP|IFF_RUNNING)) { case 0: ... break; case IFF_RUNNING: ... break; case IFF_UP: ... break; case IFF_UP|IFF_RUNNING: ... break; } unifdef lots of code containing #ifdef FreeBSD, #ifdef NetBSD, and #ifdef SIOCSIFMTU, especially in fwip(4) and in ndis(4). In ipw(4), remove an if_set_sadl() call that is out of place. In nfe(4), reuse the jumbo MTU logic in ether_ioctl(). Let ethernets register a callback for setting h/w state such as promiscuous mode and the multicast filter in accord with a change in the if_flags: ether_set_ifflags_cb() registers a callback that returns ENETRESET if the caller should reset the ethernet by calling if_init(), 0 on success, != 0 on failure. Pull common code from ex(4), gem(4), nfe(4), sip(4), tlp(4), vge(4) into ether_ioctl(), and register if_flags callbacks for those drivers. Return ENOTTY instead of EINVAL for inappropriate ioctls. In zyd(4), use ENXIO instead of ENOTTY to indicate that the device is not any longer attached. Add to if_set_sadl() a boolean 'factory' argument that indicates whether a link-layer address was assigned by the factory or some other source. In a comment, recommend using the factory address for generating an EUI64, and update in6_get_hw_ifid() to prefer a factory address to any other link-layer address. Add a routing message, RTM_LLINFO_UPD, that tells protocols to update the binding of network-layer addresses to link-layer addresses. Implement this message in IPv4 and IPv6 by sending a gratuitous ARP or a neighbor advertisement, respectively. Generate RTM_LLINFO_UPD messages on a change of an interface's link-layer address. In ether_ioctl(), do not let SIOCALIFADDR set a link-layer address that is broadcast/multicast or equal to 00:00:00:00:00:00. Make ether_ioctl() call ifioctl_common() to handle ioctls that it does not understand. In gif(4), initialize if_softc and use it, instead of assuming that the gif_softc and ifp overlap. Let ifioctl_common() handle SIOCGIFADDR. Sprinkle rtcache_invariants(), which checks on DIAGNOSTIC kernels that certain invariants on a struct route are satisfied. In agr(4), rewrite agr_ioctl_filter() to be a bit more explicit about the ioctls that we do not allow on an agr(4) member interface. bzero -> memset. Delete unnecessary casts to void *. Use sockaddr_in_init() and sockaddr_in6_init(). Compare pointers with NULL instead of "testing truth". Replace some instances of (type *)0 with NULL. Change some K&R prototypes to ANSI C, and join lines.
2008-11-07 03:20:01 +03:00
error = ifioctl_common(ifp, cmd, data);
}
end:
splx(s);
2005-02-27 03:26:58 +03:00
return error;
}
static inline int
2018-06-25 16:28:12 +03:00
clpoutbyte(u_char byte, int spin, device_t ppbus)
{
int s = spin;
ppbus_wdtr(ppbus, ctxmitl[byte]);
while (ppbus_rstr(ppbus) & CLPIP_SHAKE) {
if (--s == 0) {
return 1;
}
}
s = spin;
ppbus_wdtr(ppbus, ctxmith[byte]);
while (!(ppbus_rstr(ppbus) & CLPIP_SHAKE)) {
if (--s == 0) {
return 1;
}
}
return 0;
}
static inline int
2018-06-25 16:28:12 +03:00
clpinbyte(int spin, device_t ppbus)
{
u_char c, cl;
int s = spin;
2018-06-25 16:28:12 +03:00
while (ppbus_rstr(ppbus) & CLPIP_SHAKE) {
if (!--s) {
return -1;
}
}
cl = ppbus_rstr(ppbus);
ppbus_wdtr(ppbus, 0x10);
s = spin;
2018-06-25 16:28:12 +03:00
while (!(ppbus_rstr(ppbus) & CLPIP_SHAKE)) {
if (!--s) {
return -1;
}
}
c = ppbus_rstr(ppbus);
ppbus_wdtr(ppbus, 0x00);
return (ctrecvl[cl] | ctrecvh[c]);
}
static void
lptap(struct ifnet *ifp, struct mbuf *m, u_int direction)
{
/*
* Send a packet through bpf. We need to prepend the address family
* as a four byte field. Cons up a dummy header to pacify bpf. This
* is safe because bpf will only read from the mbuf (i.e., it won't
* try to free it or keep a pointer to it).
*/
u_int32_t af = AF_INET;
struct mbuf m0;
2005-02-27 03:26:58 +03:00
m0.m_next = m;
m0.m_len = sizeof(u_int32_t);
m0.m_data = (char *)&af;
bpf_mtap(ifp, &m0, direction);
}
/* Soft interrupt handler called by hardware interrupt handler */
static void
2018-06-25 16:28:12 +03:00
lp_intr(void *arg)
{
device_t dev = (device_t)arg;
device_t ppbus = device_parent(dev);
struct lp_softc * sc = device_private(dev);
struct ifnet * ifp = &sc->sc_if;
struct mbuf *top;
int len, s, j;
u_char *bp;
u_char c, cl;
s = splnet();
/* Do nothing if device not properly attached */
2018-06-25 16:28:12 +03:00
if (sc->sc_dev_ok) {
2005-02-27 03:26:58 +03:00
LP_PRINTF("%s(%s): device not properly attached!", __func__,
2008-04-08 11:35:35 +04:00
device_xname(dev));
goto done;
}
/* Do nothing if interface is not up */
2018-06-25 16:28:12 +03:00
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
goto done;
/* If other side is no longer transmitting, do nothing */
2018-06-25 16:28:12 +03:00
if (!(ppbus_rstr(ppbus) & LPIP_SHAKE))
goto done;
/* Disable interrupts until we finish */
ppbus_wctr(ppbus, ~IRQENABLE);
top = NULL;
bp = sc->sc_ifbuf;
/* Linux/crynwyr protocol receiving */
2018-06-25 16:28:12 +03:00
if (ifp->if_flags & IFF_LINK0) {
/* Ack. the request */
ppbus_wdtr(ppbus, 0x01);
/* Get the packet length */
j = clpinbyte(LPMAXSPIN2, ppbus);
2018-06-25 16:28:12 +03:00
if (j == -1)
goto err;
len = j;
j = clpinbyte(LPMAXSPIN2, ppbus);
2018-06-25 16:28:12 +03:00
if (j == -1)
goto err;
len = len + (j << 8);
2018-06-25 16:28:12 +03:00
if (len > ifp->if_mtu + MLPIPHDRLEN)
goto err;
2018-06-25 16:28:12 +03:00
while (len--) {
j = clpinbyte(LPMAXSPIN2, ppbus);
if (j == -1) {
goto err;
}
*bp++ = j;
}
/* Get and ignore checksum */
j = clpinbyte(LPMAXSPIN2, ppbus);
2018-06-25 16:28:12 +03:00
if (j == -1) {
goto err;
}
/* Return to idle state */
ppbus_wdtr(ppbus, 0);
len = bp - sc->sc_ifbuf;
if (len <= CLPIPHDRLEN)
goto err;
len -= CLPIPHDRLEN;
top = m_devget(sc->sc_ifbuf + CLPIPHDRLEN, len, 0, ifp, NULL);
}
/* FreeBSD protocol receiving */
else {
len = ifp->if_mtu + LPIPHDRLEN;
2018-06-25 16:28:12 +03:00
while (len--) {
cl = ppbus_rstr(ppbus);
ppbus_wdtr(ppbus, 0x08);
j = LPMAXSPIN2;
2018-06-25 16:28:12 +03:00
while ((ppbus_rstr(ppbus) & LPIP_SHAKE)) {
if (!--j)
goto err;
}
c = ppbus_rstr(ppbus);
ppbus_wdtr(ppbus, 0);
*bp++= trecvh[cl] | trecvl[c];
j = LPMAXSPIN2;
2018-06-25 16:28:12 +03:00
while (!((cl = ppbus_rstr(ppbus)) & LPIP_SHAKE)) {
if (cl != c &&
2005-02-27 03:26:58 +03:00
(((cl = ppbus_rstr(ppbus)) ^ 0xb8) &
0xf8) == (c & 0xf8))
goto end;
2018-06-25 16:28:12 +03:00
if (!--j)
goto err;
}
}
end:
len = bp - sc->sc_ifbuf;
2018-06-25 16:28:12 +03:00
if (len <= LPIPHDRLEN)
goto err;
len -= LPIPHDRLEN;
top = m_devget(sc->sc_ifbuf + LPIPHDRLEN, len, 0, ifp, NULL);
}
if (top == NULL) {
ifp->if_iqdrops++;
goto err;
}
if (ifp->if_bpf) {
lptap(ifp, top, BPF_D_IN);
}
if (__predict_false(!pktq_enqueue(ip_pktq, top, 0))) {
ifp->if_iqdrops++;
m_freem(top);
goto err;
}
ifp->if_ipackets++;
ifp->if_ibytes += len;
sc->sc_iferrs = 0;
goto done;
err:
/* Return to idle state */
ppbus_wdtr(ppbus, 0);
ifp->if_ierrors++;
sc->sc_iferrs++;
LP_PRINTF("R");
2005-02-27 03:26:58 +03:00
/* Disable interface if there are too many errors */
2018-06-25 16:28:12 +03:00
if (sc->sc_iferrs > LPMAXERRS) {
2008-04-08 11:35:35 +04:00
aprint_error_dev(dev, "Too many consecutive errors, going off-line.\n");
ppbus_wctr(ppbus, ~IRQENABLE);
if_down(ifp);
sc->sc_iferrs = 0;
}
done:
/* Re-enable interrupts */
ppbus_wctr(ppbus, IRQENABLE);
/* If interface is not active, send some packets */
2018-06-25 16:28:12 +03:00
if ((ifp->if_flags & IFF_OACTIVE) == 0)
lpstart(ifp);
splx(s);
return;
}
static inline int
lpoutbyte(u_char byte, int spin, device_t ppbus)
{
int s = spin;
ppbus_wdtr(ppbus, txmith[byte]);
2018-06-25 16:28:12 +03:00
while (!(ppbus_rstr(ppbus) & LPIP_SHAKE)) {
if (--s == 0)
return 1;
}
s = spin;
ppbus_wdtr(ppbus, txmitl[byte]);
2018-06-25 16:28:12 +03:00
while (ppbus_rstr(ppbus) & LPIP_SHAKE) {
if (--s == 0)
return 1;
}
return 0;
}
/* Queue a packet for delivery */
static int
lpoutput(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst,
2018-06-25 16:28:12 +03:00
const struct rtentry *rt)
{
struct lp_softc * sc = ifp->if_softc;
device_t dev = sc->ppbus_dev.sc_dev;
device_t ppbus = device_parent(dev);
int err;
int s;
s = splnet();
2018-06-25 16:28:12 +03:00
if (sc->sc_dev_ok) {
2005-02-27 03:26:58 +03:00
LP_PRINTF("%s(%s): device not properly attached!", __func__,
2008-04-08 11:35:35 +04:00
device_xname(dev));
err = ENODEV;
goto endoutput;
}
2005-02-27 03:26:58 +03:00
2018-06-25 16:28:12 +03:00
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
err = ENETDOWN;
goto endoutput;
}
2005-02-27 03:26:58 +03:00
/* Only support INET */
2018-06-25 16:28:12 +03:00
if (dst->sa_family != AF_INET) {
LP_PRINTF("%s: af%d not supported\n", ifp->if_xname,
dst->sa_family);
ifp->if_noproto++;
err = EAFNOSUPPORT;
goto endoutput;
}
2005-02-27 03:26:58 +03:00
IFQ_CLASSIFY(&ifp->if_snd, m, dst->sa_family);
IFQ_ENQUEUE(&ifp->if_snd, m, err);
2018-06-25 16:28:12 +03:00
if (err == 0) {
if ((ifp->if_flags & IFF_OACTIVE) == 0)
lpstart(ifp);
2018-06-25 16:28:12 +03:00
} else {
ifp->if_oerrors++;
sc->sc_iferrs++;
LP_PRINTF("Q");
2005-02-27 03:26:58 +03:00
/* Disable interface if there are too many errors */
2018-06-25 16:28:12 +03:00
if (sc->sc_iferrs > LPMAXERRS) {
2008-04-08 11:35:35 +04:00
aprint_error_dev(dev, "Too many errors, going off-line.\n");
ppbus_wctr(ppbus, ~IRQENABLE);
if_down(ifp);
sc->sc_iferrs = 0;
}
}
endoutput:
2018-06-25 16:28:12 +03:00
if ((err != 0) && (err != ENOBUFS))
m_freem(m);
splx(s);
return err;
}
/* Send routine: send packets over PLIP cable. Call at splnet(). */
void
2005-02-27 03:26:58 +03:00
lpstart(struct ifnet * ifp)
{
struct lp_softc * lp = ifp->if_softc;
device_t dev = lp->ppbus_dev.sc_dev;
device_t ppbus = device_parent(dev);
struct mbuf * mm;
struct mbuf * m;
u_char * cp;
int err, i, len, spin, count;
u_char str, chksum;
2018-06-25 16:28:12 +03:00
if (lp->sc_dev_ok) {
2005-02-27 03:26:58 +03:00
LP_PRINTF("%s(%s): device not properly attached!", __func__,
2008-04-08 11:35:35 +04:00
device_xname(dev));
return;
}
2005-02-27 03:26:58 +03:00
2018-06-25 16:28:12 +03:00
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
return;
}
ifp->if_flags |= IFF_OACTIVE;
/* Go quiescent */
ppbus_wdtr(ppbus, 0);
/* Output loop */
2018-06-25 16:28:12 +03:00
for (;;) {
/* Check if there are packets to send */
2018-06-25 16:28:12 +03:00
if (IFQ_IS_EMPTY(&ifp->if_snd)) {
goto final;
}
/* Try to send a packet, dequeue it later if successful */
IFQ_POLL(&ifp->if_snd, m);
2018-06-25 16:28:12 +03:00
if (m == NULL)
goto final;
str = ppbus_rstr(ppbus);
/* Wait until other side is not transmitting */
2018-06-25 16:28:12 +03:00
if ((str & LPIP_SHAKE) ||
((ifp->if_flags & IFF_LINK0) && !(str & CLPIP_SHAKE))) {
LP_PRINTF("&");
2018-06-25 16:28:12 +03:00
if (++lp->sc_xmit_rtry > LPMAXRTRY) {
2008-04-08 11:35:35 +04:00
aprint_error_dev(dev, "Too many retries while channel "
"busy, going off-line.\n");
ppbus_wctr(ppbus, ~IRQENABLE);
if_down(ifp);
lp->sc_xmit_rtry = 0;
}
goto final;
}
lp->sc_xmit_rtry = 0;
/* Disable interrupt generation */
ppbus_wctr(ppbus, ~IRQENABLE);
2005-02-27 03:26:58 +03:00
err = 1;
/* Output packet for Linux/crynwyr compatible protocol */
2018-06-25 16:28:12 +03:00
if (ifp->if_flags & IFF_LINK0) {
/* Calculate packet length */
count = 14; /* Ethernet header len */
2018-06-25 16:28:12 +03:00
for (mm = m; mm; mm = mm->m_next) {
count += mm->m_len;
}
/* Alert other end to pending packet */
spin = LPMAXSPIN1;
ppbus_wdtr(ppbus, 0x08);
2018-06-25 16:28:12 +03:00
while ((ppbus_rstr(ppbus) & 0x08) == 0) {
if (--spin == 0) {
goto nend;
}
}
2018-06-25 16:28:12 +03:00
if (clpoutbyte(count & 0xFF, LPMAXSPIN1, ppbus))
goto nend;
2018-06-25 16:28:12 +03:00
if (clpoutbyte((count >> 8) & 0xFF, LPMAXSPIN1, ppbus))
goto nend;
/* Send dummy ethernet header */
chksum = 0;
2018-06-25 16:28:12 +03:00
for (i = 0; i < 12; i++) {
if (clpoutbyte(i, LPMAXSPIN1, ppbus))
goto nend;
chksum += i;
}
2018-06-25 16:28:12 +03:00
if (clpoutbyte(0x08, LPMAXSPIN1, ppbus))
goto nend;
2018-06-25 16:28:12 +03:00
if (clpoutbyte(0x00, LPMAXSPIN1, ppbus))
goto nend;
chksum += 0x08 + 0x00; /* Add into checksum */
mm = m;
do {
cp = mtod(mm, u_char *);
len = mm->m_len;
2018-06-25 16:28:12 +03:00
while (len--) {
if (clpoutbyte(*cp, LPMAXSPIN2, ppbus))
goto nend;
chksum += *cp++;
}
} while ((mm = mm->m_next));
/* Send checksum */
2018-06-25 16:28:12 +03:00
if (clpoutbyte(chksum, LPMAXSPIN2, ppbus))
goto nend;
/* No errors */
err = 0;
/* Go quiescent */
ppbus_wdtr(ppbus, 0);
}
/* Output packet for FreeBSD compatible protocol */
else {
/* We need a sensible value if we abort */
cp = NULL;
2018-06-25 16:28:12 +03:00
if (lpoutbyte(0x08, LPMAXSPIN1, ppbus))
goto end;
2018-06-25 16:28:12 +03:00
if (lpoutbyte(0x00, LPMAXSPIN2, ppbus))
goto end;
mm = m;
do {
cp = mtod(mm,u_char *);
len = mm->m_len;
2018-06-25 16:28:12 +03:00
while (len--)
if (lpoutbyte(*cp++, LPMAXSPIN2, ppbus))
goto end;
} while ((mm = mm->m_next));
/* no errors were encountered */
err = 0;
end:
2018-06-25 16:28:12 +03:00
if (cp)
ppbus_wdtr(ppbus, txmitl[*(--cp)] ^ 0x17);
else
ppbus_wdtr(ppbus, txmitl['\0'] ^ 0x17);
}
nend:
/* Re-enable interrupt generation */
ppbus_wctr(ppbus, IRQENABLE);
2018-06-25 16:28:12 +03:00
if (err) {
/* Go quiescent */
ppbus_wdtr(ppbus, 0);
2005-02-27 03:26:58 +03:00
ifp->if_oerrors++;
lp->sc_iferrs++;
LP_PRINTF("X");
2005-02-27 03:26:58 +03:00
/* Disable interface if there are too many errors */
2018-06-25 16:28:12 +03:00
if (lp->sc_iferrs > LPMAXERRS) {
2008-04-08 11:35:35 +04:00
aprint_error_dev(dev, "Too many errors, going off-line.\n");
ppbus_wctr(ppbus, ~IRQENABLE);
if_down(ifp);
lp->sc_iferrs = 0;
goto final;
}
2018-06-25 16:28:12 +03:00
} else {
/* Dequeue packet on success */
IFQ_DEQUEUE(&ifp->if_snd, m);
if(ifp->if_bpf)
lptap(ifp, m, BPF_D_OUT);
ifp->if_opackets++;
ifp->if_obytes += m->m_pkthdr.len;
m_freem(m);
}
}
final:
ifp->if_flags &= ~IFF_OACTIVE;
return;
}