Add the lgue(4) driver for USB CDC ethernet.
authorSascha Wildner <saw@online.de>
Mon, 25 Jul 2011 20:55:15 +0000 (22:55 +0200)
committerSascha Wildner <saw@online.de>
Mon, 25 Jul 2011 20:55:38 +0000 (22:55 +0200)
Submitted-by: Yellow Rabbit <yrabbit@sdf.lonestar.org>
share/man/man4/Makefile
share/man/man4/lgue.4 [new file with mode: 0644]
sys/bus/usb/usbcdc.h
sys/conf/files
sys/config/LINT
sys/config/LINT64
sys/dev/netif/Makefile
sys/dev/netif/lgue/Makefile [new file with mode: 0644]
sys/dev/netif/lgue/if_lgue.c [new file with mode: 0644]
sys/dev/netif/lgue/if_lgue.h [new file with mode: 0644]

index 488cdfa..1baebe5 100644 (file)
@@ -141,6 +141,7 @@ MAN=        aac.4 \
        ktr.4 \
        kue.4 \
        lge.4 \
+       lgue.4 \
        lm.4 \
        lo.4 \
        lp.4 \
diff --git a/share/man/man4/lgue.4 b/share/man/man4/lgue.4
new file mode 100644 (file)
index 0000000..34f0626
--- /dev/null
@@ -0,0 +1,62 @@
+.\"
+.\" Copyright (c) 2011
+.\"    The DragonFly Project.  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.
+.\" 3. Neither the name of The DragonFly Project nor the names of its
+.\"    contributors may be used to endorse or promote products derived
+.\"    from this software without specific, prior written permission.
+.\"
+.\" 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 HOLDERS 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.
+.\"
+.Dd July 25, 2011
+.Dt LGUE 4
+.Os
+.Sh NAME
+.Nm lgue
+.Nd USB CDC ethernet
+.Sh SYNOPSIS
+.Cd "device uhci"
+.Cd "device ohci"
+.Cd "device usb"
+.Cd "device lgue"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for USB CDC (communication device class) ethernet.
+It currently supports the
+.Tn LG P-500
+smartphone.
+.Pp
+For more information on configuring this device, see
+.Xr ifconfig 8 .
+.Sh SEE ALSO
+.Xr arp 4 ,
+.Xr netintro 4 ,
+.Xr ng_ether 4 ,
+.Xr ifconfig 8
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Dx 2.11 .
index 08302b2..423fc81 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * $NetBSD: usbcdc.h,v 1.6 2000/04/27 15:26:50 augustss Exp $
  * $FreeBSD: src/sys/dev/usb/usbcdc.h,v 1.10 2003/01/09 04:24:28 imp Exp $
- * $DragonFly: src/sys/bus/usb/usbcdc.h,v 1.3 2003/12/30 01:01:44 dillon Exp $
  */
 
 /*
@@ -54,6 +53,7 @@
 #define UDESCSUB_CDC_CS                7 /* Country Selection */
 #define UDESCSUB_CDC_TOM       8 /* Telephone Operational Modes */
 #define UDESCSUB_CDC_USBT      9 /* USB Terminal */
+#define UDESCSUB_CDC_ETHERNET 15 /* Ethernet Adapter */
 
 typedef struct {
        uByte           bLength;
@@ -91,6 +91,17 @@ typedef struct {
        uByte           bSlaveInterface[1];
 } usb_cdc_union_descriptor_t;
 
+typedef struct {
+       uByte bLength;
+       uByte bDescriptorType;
+       uByte bDescriptorSubType;
+       uByte iMACAddress;
+       uDWord dwEthernetStatistics;
+       uWord wMaxSegmentSize;
+       uWord wNumberMCFilters;
+       uByte bNumberPowerFilters;
+} usb_cdc_ethernet_descriptor_t;
+
 #define UCDC_SEND_ENCAPSULATED_COMMAND 0x00
 #define UCDC_GET_ENCAPSULATED_RESPONSE 0x01
 #define UCDC_SET_COMM_FEATURE          0x02
index 3bc4c7d..09fb925 100644 (file)
@@ -1779,6 +1779,7 @@ dev/netif/aue/if_aue.c            optional aue
 dev/netif/axe/if_axe.c         optional axe
 dev/netif/cue/if_cue.c         optional cue
 dev/netif/kue/if_kue.c         optional kue
+dev/netif/lgue/if_lgue.c       optional lgue
 dev/netif/rue/if_rue.c         optional rue
 dev/netif/lnc/am7990.c         optional lnc
 dev/netif/lnc/am79900.c                optional lnc
index 413f86d..2308d9a 100644 (file)
@@ -2540,6 +2540,9 @@ device            cue
 # and 2104USB, and the Corega USB-T.
 device         kue
 #
+# USB CDC ethernet. Supports the LG P-500 smartphone.
+device         lgue
+#
 # RealTek 8150 based USB ethernet device:
 #   Melco LUA-KTX
 #   GREEN HOUSE GH-USB100B
index 6fb3943..8ccfae8 100644 (file)
@@ -2315,6 +2315,9 @@ device            cue
 # and 2104USB, and the Corega USB-T.
 device         kue
 #
+# USB CDC ethernet. Supports the LG P-500 smartphone.
+device         lgue
+#
 # RealTek 8150 based USB ethernet device:
 #   Melco LUA-KTX
 #   GREEN HOUSE GH-USB100B
index 9e51736..2774de2 100644 (file)
@@ -1,9 +1,6 @@
-# $DragonFly: src/sys/dev/netif/Makefile,v 1.34 2008/07/26 14:26:30 sephe Exp $
-#
-
 SUBDIR= an age alc ale ar ath aue axe bce bfe bge \
        cue dc ed em ep et e1000 fwe \
-       fxp ic iwi iwn jme kue lge lnc mii_layer my msk mxge nfe nge pcn \
+       fxp ic iwi iwn jme kue lge lgue lnc mii_layer my msk mxge nfe nge pcn \
        ral re rl rue sbni sbsh sf sis sk sln sr ste stge ti tl tx txp \
        vge vr vx wb wi wpi xe xl ig_hal emx ae
 
diff --git a/sys/dev/netif/lgue/Makefile b/sys/dev/netif/lgue/Makefile
new file mode 100644 (file)
index 0000000..7779f95
--- /dev/null
@@ -0,0 +1,10 @@
+# XXX Some notes
+
+# Declare Name of kernel module
+KMOD   = if_lgue
+
+# Enumerate Source files for kernel module
+SRCS   = if_lgue.c opt_usb.h device_if.h bus_if.h
+
+# Include kernel module makefile
+.include <bsd.kmod.mk>
diff --git a/sys/dev/netif/lgue/if_lgue.c b/sys/dev/netif/lgue/if_lgue.c
new file mode 100644 (file)
index 0000000..70611b2
--- /dev/null
@@ -0,0 +1,904 @@
+/*
+ * LG-P500 Smartphone
+ * Written by Yellow Rabbit <yrabbit@sdf.lonestar.org>
+ */
+
+/*
+ * XXX
+ * USB:
+ * Takes two interfaces.
+ * IN and OUT endpoints on the data interface (altsetting).
+ * Interrupt endpoint on the control interface.
+ *
+ * NET:
+ * Transfer frames without modification (AS IS).
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/bus.h>
+
+#include <net/if.h>
+#include <net/ifq_var.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/bpf.h>
+
+#include <bus/usb/usb.h>
+#include <bus/usb/usbcdc.h>
+#include <bus/usb/usbdi.h>
+#include <bus/usb/usbdi_util.h>
+#include <bus/usb/usbdivar.h>
+#include <bus/usb/usb_ethersubr.h>
+
+#include "if_lgue.h"
+
+/*
+ * Supported device vendors/products
+ */
+static struct usb_devno lgue_devs[] = {
+       { USB_DEVICE(0x01004, 0x61a2) } /* LG P500 */
+};
+
+static int lgue_match(device_t);
+static int lgue_attach(device_t);
+static int lgue_detach(device_t);
+
+static void lgue_start(struct ifnet *);
+static void lgue_stop(struct lgue_softc *);
+static void lgue_init(void *);
+static void lgue_watchdog(struct ifnet *);
+static int lgue_ioctl(struct ifnet *, u_long, caddr_t, struct ucred *);
+
+static int lgue_encap(struct lgue_softc *, struct mbuf *);
+static int lgue_start_transfer(struct lgue_softc *);
+
+static void lgue_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status);
+
+static void lgue_start_ipifunc(void *);
+static void lgue_start_schedule(struct ifnet *);
+
+static int lgue_newbuf(struct lgue_softc *, int, struct mbuf **);
+static void lgue_rxstart(struct ifnet *);
+static void lgue_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status);
+
+static void lgue_intrstart(struct ifnet *);
+static void lgue_intreof(usbd_xfer_handle, usbd_private_handle, usbd_status);
+
+static int lgue_get_data_iface_no(usbd_device_handle,
+              usb_interface_descriptor_t *);
+
+static int lgue_getmac(struct lgue_softc *, void *);
+static int lgue_getmtu(struct lgue_softc *);
+
+static int hex(char);
+
+static device_method_t lgue_methods[] = {
+       DEVMETHOD(device_probe,  lgue_match),
+       DEVMETHOD(device_attach, lgue_attach),
+       DEVMETHOD(device_detach, lgue_detach),
+
+       { 0, 0 }
+};
+
+static driver_t lgue_driver = {
+       "lgue",
+       lgue_methods,
+       sizeof(struct lgue_softc)
+};
+
+static devclass_t lgue_devclass;
+
+DECLARE_DUMMY_MODULE(if_lgue);
+DRIVER_MODULE(lgue, uhub, lgue_driver, lgue_devclass, usbd_driver_load, 0);
+MODULE_DEPEND(lgue, usb, 1, 1, 1);
+
+/*
+ * Probe chip
+ */
+static int
+lgue_match(device_t dev)
+{
+       struct usb_attach_arg *uaa;
+       usb_interface_descriptor_t *id;
+
+       uaa = device_get_ivars(dev);
+       if (uaa->iface == NULL)
+               return(UMATCH_NONE);
+
+       if (usb_lookup(lgue_devs, uaa->vendor, uaa->product) != NULL) {
+               id = usbd_get_interface_descriptor(uaa->iface);
+               if (id != NULL &&
+                   id->bInterfaceClass == UICLASS_CDC &&
+                   id->bInterfaceSubClass == UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL)
+                       return(UMATCH_VENDOR_PRODUCT);
+       }
+       return(UMATCH_NONE);
+}
+
+/*
+ * Attach the interface.
+ */
+static int
+lgue_attach(device_t dev)
+{
+       struct lgue_softc *sc;
+       struct usb_attach_arg *uaa;
+       struct ifnet *ifp;
+       usb_interface_descriptor_t *id;
+       usb_endpoint_descriptor_t *ed;
+       int i;
+       u_char eaddr[ETHER_ADDR_LEN];
+       usbd_status err;
+
+       sc = device_get_softc(dev);
+       uaa = device_get_ivars(dev);
+
+       sc->lgue_ctl_iface = uaa->iface;
+       sc->lgue_udev = uaa->device;
+
+       /* It has only config but in case... */
+       if (usbd_set_config_no(sc->lgue_udev, LGUE_CONFIG_NO, 0)) {
+               device_printf(dev, "setting config no %d failed\n",
+                   LGUE_CONFIG_NO);
+               return(ENXIO);
+       }
+
+       /* Get control and data intefaces */
+       id = usbd_get_interface_descriptor(uaa->iface);
+       sc->lgue_ctl_iface_no = id->bInterfaceNumber;
+       sc->lgue_data_iface_no = lgue_get_data_iface_no(sc->lgue_udev, id);
+
+       if (sc->lgue_data_iface_no == -1) {
+               device_printf(dev, "no data interface number\n");
+               goto bad;
+       }
+
+       /* Claim data interface */
+       for (i = 0; i < uaa->nifaces; ++i) {
+               if (uaa->ifaces[i] != NULL) {
+                       id = usbd_get_interface_descriptor(uaa->ifaces[i]);
+                       if (id != NULL &&
+                           id->bInterfaceNumber == sc->lgue_data_iface_no) {
+                               err = usbd_set_interface(uaa->ifaces[i],
+                                   LGUE_ALTERNATE_SETTING);
+                               if ( err != USBD_NORMAL_COMPLETION) {
+                                       device_printf(dev,
+                                           "no alternate data interface. err:%s\n",
+                                           usbd_errstr(err));
+                                       goto bad;
+                               }
+                               sc->lgue_data_iface = uaa->ifaces[i];
+                               uaa->ifaces[i] = NULL;
+                       }
+               }
+       }
+       if (sc->lgue_data_iface == NULL) {
+               device_printf(dev, "no data interface\n");
+               goto bad;
+       }
+
+       /* Find data interface endpoints */
+       id = usbd_get_interface_descriptor(sc->lgue_data_iface);
+       sc->lgue_ed[LGUE_ENDPT_RX] = sc->lgue_ed[LGUE_ENDPT_TX] = -1;
+       for (i = 0; i < id->bNumEndpoints; ++i) {
+               ed = usbd_interface2endpoint_descriptor(sc->lgue_data_iface, i);
+               if (!ed) {
+                       device_printf(dev,
+                           "couldn't get endpoint descriptor %d\n", i);
+                       goto bad;
+               }
+               if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+                               UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+                       sc->lgue_ed[LGUE_ENDPT_RX] = ed->bEndpointAddress;
+               } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
+                   UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+                       sc->lgue_ed[LGUE_ENDPT_TX] = ed->bEndpointAddress;
+               }
+       }
+
+       if (sc->lgue_ed[LGUE_ENDPT_RX] == -1) {
+               device_printf(dev, "couldn't find data bilk in\n");
+               goto bad;
+       }
+       if (sc->lgue_ed[LGUE_ENDPT_TX] == -1) {
+               device_printf(dev, "couldn't find data bilk out\n");
+               goto bad;
+       }
+
+       /* Find control interface endpoint */
+       id = usbd_get_interface_descriptor(sc->lgue_ctl_iface);
+       sc->lgue_ed[LGUE_ENDPT_INTR] = -1;
+       for (i = 0; i < id->bNumEndpoints; ++i) {
+               ed = usbd_interface2endpoint_descriptor(sc->lgue_ctl_iface, i);
+               if (!ed) {
+                       device_printf(dev,
+                           "couldn't get endpoint descriptor %d\n", i);
+                       goto bad;
+               }
+               if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+                   UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
+                       sc->lgue_ed[LGUE_ENDPT_INTR] = ed->bEndpointAddress;
+               }
+       }
+
+       if (sc->lgue_ed[LGUE_ENDPT_INTR] == -1) {
+               device_printf(dev, "couldn't find interrupt bilk in\n");
+               goto bad;
+       }
+
+       /* Create interface */
+       ifp = &sc->lgue_arpcom.ac_if;
+       ifp->if_softc = sc;
+       if_initname(ifp, device_get_name(dev), device_get_unit(dev));
+       lgue_getmac(sc, eaddr);
+
+       ifp->if_mtu = lgue_getmtu(sc);
+       ifp->if_data.ifi_mtu = ifp->if_mtu;
+       ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+       ifp->if_baudrate = 10000000;
+       ifp->if_ioctl = lgue_ioctl;
+       ifp->if_start = lgue_start;
+       ifp->if_watchdog = lgue_watchdog;
+       ifp->if_init = lgue_init;
+       ifq_set_maxlen(&ifp->if_snd, IFQ_MAXLEN);
+       ifq_set_ready(&ifp->if_snd);
+
+       /* Call attach routine */
+       ether_ifattach(ifp, eaddr, NULL);
+       usb_register_netisr();
+       sc->lgue_dying = 0;
+       return(0);
+
+bad:
+       return(ENXIO);
+}
+
+/*
+ * Device detached.
+ */
+static int
+lgue_detach(device_t dev)
+{
+       struct lgue_softc *sc;
+       struct ifnet *ifp;
+
+       sc = device_get_softc(dev);
+       ifp = &sc->lgue_arpcom.ac_if;
+       ether_ifdetach(ifp);
+
+       if (sc->lgue_ep[LGUE_ENDPT_TX] != NULL)
+               usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_TX]);
+       if (sc->lgue_ep[LGUE_ENDPT_RX] != NULL)
+               usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_RX]);
+       if (sc->lgue_ep[LGUE_ENDPT_INTR] != NULL)
+               usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_INTR]);
+       return(0);
+}
+
+/*
+ * Find data interface.
+ */
+int
+lgue_get_data_iface_no(usbd_device_handle dev, usb_interface_descriptor_t *id)
+{
+       const usb_cdc_union_descriptor_t *cud;
+
+       cud = (const usb_cdc_union_descriptor_t *)usb_find_desc_if(dev,
+           UDESC_CS_INTERFACE, UDESCSUB_CDC_UNION, id);
+       return(cud ? cud->bSlaveInterface[0] : -1);
+}
+
+/*
+ * Get hard max mtu
+ */
+static int
+lgue_getmtu(struct lgue_softc *sc)
+{
+       const usb_cdc_ethernet_descriptor_t *ced;
+       usb_interface_descriptor_t *id;
+
+       id = usbd_get_interface_descriptor(sc->lgue_ctl_iface);
+       if (id == NULL) goto bad;
+       ced = (const usb_cdc_ethernet_descriptor_t *)usb_find_desc_if(sc->lgue_udev,
+           UDESC_CS_INTERFACE, UDESCSUB_CDC_ETHERNET, id);
+       if (ced == NULL)
+               goto bad;
+       return(UGETW(ced->wMaxSegmentSize));
+bad:
+       kprintf("Bad mtu:%d\n", UGETW(ced->wMaxSegmentSize));
+       return(ETHERMTU);
+}
+
+/*
+ * Get mac address
+ */
+static int
+lgue_getmac(struct lgue_softc *sc, void *buf)
+{
+       const usb_cdc_ethernet_descriptor_t *ced;
+       usb_interface_descriptor_t *id;
+       char sbuf[ETHER_ADDR_LEN * 2 + 1];
+       usbd_status err;
+       int i;
+
+       id = usbd_get_interface_descriptor(sc->lgue_ctl_iface);
+       if (id == NULL) goto bad;
+       ced = (const usb_cdc_ethernet_descriptor_t *)usb_find_desc_if(sc->lgue_udev,
+           UDESC_CS_INTERFACE, UDESCSUB_CDC_ETHERNET, id);
+       if (ced == NULL) goto bad;
+
+       err = usbd_get_string(sc->lgue_udev, ced->iMACAddress, sbuf);
+       if(err) {
+               kprintf("Read MAC address failed\n");
+               goto bad;
+       }
+
+       for (i = 0; i < ETHER_ADDR_LEN; ++i) {
+               ((uByte *)buf)[i] = (hex(sbuf[i * 2]) << 4) + hex(sbuf[(i * 2) + 1]);
+       }
+       return(0);
+bad:
+       return(-1);
+}
+
+/*
+ * Listen INTR pipe
+ */
+static void
+lgue_intrstart(struct ifnet *ifp)
+{
+       struct lgue_softc *sc;
+
+       sc = ifp->if_softc;
+       usbd_setup_xfer(sc->lgue_intr_xfer, sc->lgue_ep[LGUE_ENDPT_INTR], sc,
+           sc->lgue_intr_buf, LGUE_BUFSZ,
+           USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, lgue_intreof);
+       usbd_transfer(sc->lgue_intr_xfer);
+}
+
+/*
+ * INTR arrived
+ */
+static void
+lgue_intreof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
+{
+       struct ifnet *ifp;
+       struct lgue_softc *sc;
+
+       sc = priv;
+       if (sc->lgue_dying)
+               return;
+
+       ifp = &sc->lgue_arpcom.ac_if;
+       lwkt_serialize_enter(ifp->if_serializer);
+       if (status != USBD_NORMAL_COMPLETION) {
+               if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
+                       lwkt_serialize_exit(ifp->if_serializer);
+                       return;
+               if (status == USBD_STALLED)
+                       usbd_clear_endpoint_stall(sc->lgue_ep[LGUE_ENDPT_INTR]);
+                       lwkt_serialize_exit(ifp->if_serializer);
+               return;
+       }
+       lgue_intrstart(ifp);
+       lwkt_serialize_exit(ifp->if_serializer);
+}
+
+/*
+ * Encap packet & send
+ */
+static int
+lgue_encap(struct lgue_softc *sc, struct mbuf *m)
+{
+       struct ifnet *ifp;
+       struct lgue_queue_entry *entry;
+
+       ifp = &sc->lgue_arpcom.ac_if;
+       entry = kmalloc(sizeof(struct lgue_queue_entry), M_USBDEV , M_NOWAIT);
+       if (entry == NULL) {
+               if_printf(ifp, "no memory for internal queue entry\n");
+               return(ENOBUFS);
+       }
+       entry->entry_mbuf = m;
+
+       /* Put packet into internal queue tail */
+       STAILQ_INSERT_TAIL(&sc->lgue_tx_queue, entry, entry_next);
+       return(0);
+}
+
+/*
+ * Start transfer from internal queue
+ */
+static int
+lgue_start_transfer(struct lgue_softc *sc) {
+       usbd_status err;
+       struct lgue_queue_entry *entry;
+       struct ifnet *ifp;
+
+       if (STAILQ_EMPTY(&sc->lgue_tx_queue))
+               return(0);
+
+       ifp = &sc->lgue_arpcom.ac_if;
+       entry = STAILQ_FIRST(&sc->lgue_tx_queue);
+       STAILQ_REMOVE_HEAD(&sc->lgue_tx_queue, entry_next);
+
+       m_copydata(entry->entry_mbuf, 0, entry->entry_mbuf->m_pkthdr.len,
+           sc->lgue_tx_buf);
+
+       /* Transmit */
+       usbd_setup_xfer(sc->lgue_tx_xfer, sc->lgue_ep[LGUE_ENDPT_TX], sc,
+           sc->lgue_tx_buf, entry->entry_mbuf->m_pkthdr.len,
+           USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, lgue_txeof);
+       err = usbd_transfer(sc->lgue_tx_xfer);
+       if (err != USBD_IN_PROGRESS) {
+               m_freem(entry->entry_mbuf);
+               kfree(entry, M_USBDEV);
+               lgue_stop(sc);
+               ifp->if_flags &= ~IFF_OACTIVE;
+               return(EIO);
+       }
+
+       m_freem(entry->entry_mbuf);
+       kfree(entry, M_USBDEV);
+
+       sc->lgue_tx_cnt++;
+       ifp->if_flags |= IFF_OACTIVE;
+       ifp->if_timer = 5;
+       return(0);
+}
+
+/*
+ * Start call
+ */
+static void
+lgue_start_ipifunc(void *arg)
+{
+       struct ifnet *ifp;
+       struct lwkt_msg *lmsg;
+
+       ifp = arg;
+       lmsg = &ifp->if_start_nmsg[mycpuid].lmsg;
+       crit_enter();
+       if (lmsg->ms_flags & MSGF_DONE)
+               lwkt_sendmsg(ifnet_portfn(mycpuid), lmsg);
+       crit_exit();
+}
+
+/*
+ * Schedule start call
+ */
+static void
+lgue_start_schedule(struct ifnet *ifp)
+{
+#ifdef SMP
+       int cpu;
+
+       cpu = ifp->if_start_cpuid(ifp);
+       if (cpu != mycpuid)
+               lwkt_send_ipiq(globaldata_find(cpu), lgue_start_ipifunc, ifp);
+       else
+#endif
+               lgue_start_ipifunc(ifp);
+}
+
+/*
+ * End of sending
+ */
+static void
+lgue_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
+{
+       struct ifnet *ifp;
+       struct lgue_softc *sc;
+       usbd_status err;
+
+       sc = priv;
+       if (sc->lgue_dying)
+               return;
+
+       ifp = &sc->lgue_arpcom.ac_if;
+
+       if (status != USBD_NORMAL_COMPLETION) {
+               if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
+                       return;
+               if (status == USBD_STALLED)
+                       usbd_clear_endpoint_stall(sc->lgue_ep[LGUE_ENDPT_TX]);
+               return;
+       }
+       usbd_get_xfer_status(sc->lgue_tx_xfer, NULL, NULL, NULL,&err);
+       if (err)
+               ifp->if_oerrors++;
+       else
+               ifp->if_opackets++;
+
+       if (!STAILQ_EMPTY(&sc->lgue_tx_queue)) {
+               lgue_start_schedule(ifp);
+       }
+
+       ifp->if_timer = 0;
+       ifp->if_flags &= ~IFF_OACTIVE;
+}
+
+/*
+ * Start transfer
+ */
+static void
+lgue_start(struct ifnet *ifp)
+{
+       struct lgue_softc *sc;
+       struct mbuf *m_head;
+
+       sc = ifp->if_softc;
+       if (sc->lgue_dying)
+               return;
+
+       if (ifp->if_flags & IFF_OACTIVE) {
+               return;
+       }
+
+       /* To internal queue */
+       while ((m_head = ifq_dequeue(&ifp->if_snd, NULL)) != NULL) {
+               if (lgue_encap(sc, m_head)) {
+                       m_freem(m_head);
+                       break;
+               }
+               /* Filter */
+               BPF_MTAP(ifp, m_head);
+       }
+
+       lgue_start_transfer(sc);
+}
+
+/*
+ * Stop
+ */
+static void
+lgue_stop(struct lgue_softc *sc)
+{
+       struct ifnet *ifp;
+       usbd_status err;
+       struct lgue_queue_entry *entry;
+
+       if (sc->lgue_dying)
+               return;
+       sc->lgue_dying = 1;
+
+       ifp = &sc->lgue_arpcom.ac_if;
+
+       /* Stop transfers */
+       if (sc->lgue_ep[LGUE_ENDPT_TX] != NULL) {
+               err = usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_TX]);
+               if (err) {
+                       if_printf(ifp, "abort tx pipe failed:%s\n",
+                           usbd_errstr(err));
+               }
+               err = usbd_close_pipe(sc->lgue_ep[LGUE_ENDPT_TX]);
+               if (err) {
+                       if_printf(ifp, "close tx pipe failed:%s\n",
+                           usbd_errstr(err));
+               }
+       }
+       if (sc->lgue_ep[LGUE_ENDPT_RX] != NULL) {
+               err = usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_RX]);
+               if (err) {
+                       if_printf(ifp, "abort rx pipe failed:%s\n",
+                           usbd_errstr(err));
+               }
+               err = usbd_close_pipe(sc->lgue_ep[LGUE_ENDPT_RX]);
+               if (err) {
+                       if_printf(ifp, "close rx pipe failed:%s\n",
+                           usbd_errstr(err));
+               }
+       }
+       if (sc->lgue_ep[LGUE_ENDPT_INTR] != NULL) {
+               err = usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_INTR]);
+               if (err) {
+                       if_printf(ifp, "abort intr pipe failed:%s\n",
+                           usbd_errstr(err));
+               }
+               err = usbd_close_pipe(sc->lgue_ep[LGUE_ENDPT_INTR]);
+               if (err) {
+                       if_printf(ifp, "close intr pipe failed:%s\n",
+                           usbd_errstr(err));
+               }
+       }
+
+       /* Free tx buffers */
+       if (sc->lgue_tx_buf != NULL) {
+               kfree(sc->lgue_tx_buf, M_USBDEV);
+               sc->lgue_tx_buf = NULL;
+       }
+       if (sc->lgue_tx_xfer != NULL) {
+               usbd_free_xfer(sc->lgue_tx_xfer);
+               sc->lgue_tx_xfer = NULL;
+       }
+
+       /* Free rx buffers */
+       if (sc->lgue_rx_buf != NULL) {
+               kfree(sc->lgue_rx_buf, M_USBDEV);
+               sc->lgue_rx_buf = NULL;
+       }
+       if (sc->lgue_rx_xfer != NULL) {
+               usbd_free_xfer(sc->lgue_rx_xfer);
+               sc->lgue_rx_xfer = NULL;
+       }
+
+       /* Free intr buffer */
+       if (sc->lgue_intr_buf != NULL) {
+               kfree(sc->lgue_intr_buf, M_USBDEV);
+               sc->lgue_intr_buf = NULL;
+       }
+       if (sc->lgue_intr_xfer != NULL) {
+               usbd_free_xfer(sc->lgue_intr_xfer);
+               sc->lgue_intr_xfer = NULL;
+       }
+
+       /* Clear internal queue */
+       while (!STAILQ_EMPTY(&sc->lgue_tx_queue)) {
+               entry = STAILQ_FIRST(&sc->lgue_tx_queue);
+               STAILQ_REMOVE_HEAD(&sc->lgue_tx_queue, entry_next);
+               m_freem(entry->entry_mbuf);
+               kfree(entry, M_USBDEV);
+       }
+
+       ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
+}
+
+/*
+ * Init
+ */
+static void
+lgue_init(void *xsc)
+{
+       struct lgue_softc *sc;
+       struct ifnet *ifp;
+       usbd_status err;
+
+       sc = xsc;
+       ifp = &sc->lgue_arpcom.ac_if;
+
+       if (ifp->if_flags & IFF_RUNNING)
+               return;
+
+       /* Create RX and TX bufs */
+       if (sc->lgue_tx_xfer == NULL) {
+               sc->lgue_tx_xfer = usbd_alloc_xfer(sc->lgue_udev);
+               if (sc->lgue_tx_xfer == NULL) {
+                       if_printf(ifp, "tx buffer allocate failed\n");
+                       return;
+               }
+       }
+       sc->lgue_tx_buf = kmalloc(LGUE_BUFSZ, M_USBDEV, M_WAITOK);
+       if (sc->lgue_tx_buf == NULL) {
+               if_printf(ifp, "tx internal buffer allocate failed\n");
+               return;
+       }
+
+       if (sc->lgue_rx_xfer == NULL) {
+               sc->lgue_rx_xfer = usbd_alloc_xfer(sc->lgue_udev);
+               if (sc->lgue_rx_xfer == NULL) {
+                       if_printf(ifp, "rx buffer allocate failed\n");
+                       return;
+               }
+       }
+       sc->lgue_rx_buf = kmalloc(LGUE_BUFSZ, M_USBDEV, M_WAITOK);
+       if (sc->lgue_rx_buf == NULL) {
+               if_printf(ifp, "rx internal buffer allocate failed\n");
+               return;
+       }
+
+       /* Create INTR buf */
+       if (sc->lgue_intr_xfer == NULL) {
+               sc->lgue_intr_xfer = usbd_alloc_xfer(sc->lgue_udev);
+               if (sc->lgue_intr_xfer == NULL) {
+                       if_printf(ifp, "intr buffer allocate failed\n");
+                       return;
+               }
+       }
+       sc->lgue_intr_buf = kmalloc(LGUE_BUFSZ, M_USBDEV, M_WAITOK);
+
+       /* Open RX and TX pipes. */
+       err = usbd_open_pipe(sc->lgue_data_iface, sc->lgue_ed[LGUE_ENDPT_RX],
+           USBD_EXCLUSIVE_USE, &sc->lgue_ep[LGUE_ENDPT_RX]);
+       if (err) {
+               if_printf(ifp, "open RX pipe failed: %s\n", usbd_errstr(err));
+               return;
+       }
+       err = usbd_open_pipe(sc->lgue_data_iface, sc->lgue_ed[LGUE_ENDPT_TX],
+           USBD_EXCLUSIVE_USE, &sc->lgue_ep[LGUE_ENDPT_TX]);
+       if (err) {
+               if_printf(ifp, "open TX pipe failed: %s\n", usbd_errstr(err));
+               return;
+       }
+       /* Open INTR pipe. */
+       err = usbd_open_pipe(sc->lgue_ctl_iface, sc->lgue_ed[LGUE_ENDPT_INTR],
+           USBD_EXCLUSIVE_USE, &sc->lgue_ep[LGUE_ENDPT_INTR]);
+       if (err) {
+               if_printf(ifp, "open INTR pipe failed: %s\n", usbd_errstr(err));
+               return;
+       }
+
+       /* Create internal queue */
+       STAILQ_INIT(&sc->lgue_tx_queue);
+
+       ifp->if_flags |= IFF_RUNNING;
+       ifp->if_flags &= ~IFF_OACTIVE;
+
+       sc->lgue_dying = 0;
+
+       lgue_rxstart(ifp);
+       lgue_intrstart(ifp);
+}
+
+/*
+ * New mbuf
+ */
+static int
+lgue_newbuf(struct lgue_softc *sc, int len, struct mbuf **m_buf)
+{
+       struct ifnet *ifp;
+
+       ifp = &sc->lgue_arpcom.ac_if;
+       *m_buf = NULL;
+
+       /* Allocate mbuf */
+       *m_buf = m_getcl(MB_DONTWAIT, MT_DATA, MT_HEADER);
+       if (*m_buf == NULL) {
+               if_printf(ifp, " no memory for rx buffer --- packet dropped!\n");
+               return(ENOBUFS);
+       }
+       (*m_buf)->m_len = (*m_buf)->m_pkthdr.len = MCLBYTES;
+       m_adj(*m_buf, ETHER_ALIGN);
+       return(0);
+}
+
+/*
+ * Start read
+ */
+static void
+lgue_rxstart(struct ifnet *ifp)
+{
+       struct lgue_softc *sc;
+
+       sc = ifp->if_softc;
+       if (sc->lgue_dying)
+               return;
+
+       usbd_setup_xfer(sc->lgue_rx_xfer, sc->lgue_ep[LGUE_ENDPT_RX], sc,
+           sc->lgue_rx_buf, LGUE_BUFSZ,
+           USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, lgue_rxeof);
+       usbd_transfer(sc->lgue_rx_xfer);
+}
+
+/*
+ * A frame has been uploaded: pass the resulting mbuf up to
+ * the higher level protocols.
+ */
+static void
+lgue_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
+{
+       struct lgue_softc       *sc;
+       struct mbuf             *m;
+       struct ifnet    *ifp;
+       int                     total_len;
+
+       sc = priv;
+       if (sc->lgue_dying)
+               return;
+
+       ifp = &sc->lgue_arpcom.ac_if;
+
+       total_len = 0;
+
+       if (!(ifp->if_flags & IFF_RUNNING))
+               return;
+
+       if (status != USBD_NORMAL_COMPLETION) {
+               if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
+                       return;
+               if (usbd_ratecheck(&sc->lgue_rx_notice)) {
+                       if_printf(ifp, "usb error on rx:%s\n",
+                           usbd_errstr(status));
+               }
+               if (status == USBD_STALLED)
+                       usbd_clear_endpoint_stall(sc->lgue_ep[LGUE_ENDPT_RX]);
+               goto done;
+       }
+
+       usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL);
+
+       if (total_len < sizeof(struct ether_header)) {
+               ifp->if_ierrors++;
+               goto done;
+       }
+
+       if (lgue_newbuf(sc, total_len, &m) == ENOBUFS) {
+               ifp->if_ierrors++;
+               return;
+       }
+
+       ifp->if_ipackets++;
+       m_copyback(m, 0, total_len, sc->lgue_rx_buf);
+       m->m_pkthdr.rcvif = ifp;
+       m->m_pkthdr.len = m->m_len = total_len;
+
+       usb_ether_input(m);
+       lgue_rxstart(ifp);
+       return;
+done:
+       usbd_setup_xfer(sc->lgue_rx_xfer, sc->lgue_ep[LGUE_ENDPT_RX], sc,
+           sc->lgue_rx_buf, LGUE_BUFSZ,
+           USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, lgue_rxeof);
+       usbd_transfer(sc->lgue_rx_xfer);
+}
+
+/*
+ * Control
+ */
+static int
+lgue_ioctl(struct ifnet *ifp, u_long command, caddr_t data, struct ucred *cr)
+{
+       struct lgue_softc *sc;
+       struct ifreq *ifr;
+       int err;
+
+       err = 0;
+       ifr = (struct ifreq *)data;
+       sc = ifp->if_softc;
+
+       switch(command) {
+       case SIOCSIFFLAGS:
+               if (ifp->if_flags & IFF_UP) {
+                       if (!(ifp->if_flags & IFF_RUNNING))
+                               lgue_init(sc);
+               } else if (ifp->if_flags & IFF_RUNNING) {
+                       lgue_stop(sc);
+               }
+               sc->lgue_if_flags = ifp->if_flags;
+               err = 0;
+               break;
+#if 0
+       case SIOCADDMULTI:
+       case SIOCDELMULTI:
+               err = 0;
+               break;
+#endif
+       case SIOCSIFMTU:
+               ifp->if_mtu = ifr->ifr_mtu;
+               break;
+       default:
+               err = ether_ioctl(ifp, command, data);
+               break;
+       }
+       return(err);
+}
+
+/*
+ * Watchdog
+ */
+static void
+lgue_watchdog(struct ifnet *ifp)
+{
+       struct lgue_softc *sc;
+
+       sc = ifp->if_softc;
+       ifp->if_oerrors++;
+
+       if (!ifq_is_empty(&ifp->if_snd))
+               lgue_start_schedule(ifp);
+}
+
+/*
+ * Hex -> bin
+ */
+static int
+hex(char ch)
+{
+       if ((ch >= 'a') && (ch <= 'f')) return (ch-'a'+10);
+       if ((ch >= '0') && (ch <= '9')) return (ch-'0');
+       if ((ch >= 'A') && (ch <= 'F')) return (ch-'A'+10);
+       return (-1);
+}
diff --git a/sys/dev/netif/lgue/if_lgue.h b/sys/dev/netif/lgue/if_lgue.h
new file mode 100644 (file)
index 0000000..8c1b632
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Definitions for LG USB CDC
+ */
+
+#define LGUE_CONFIG_NO         1
+
+#define LGUE_ALTERNATE_SETTING 1
+
+#define LGUE_ENDPT_RX          0x0
+#define LGUE_ENDPT_TX          0x1
+#define LGUE_ENDPT_INTR                0x2
+#define LGUE_ENDPT_MAX         0x3
+
+#define LGUE_BUFSZ             1600
+
+/*
+ * Internal queue entry
+ */
+struct lgue_queue_entry {
+       /*
+       int entry_len;
+       char entry_tx_buf[LGUE_BUFSZ];
+       */
+       struct mbuf *entry_mbuf;
+       STAILQ_ENTRY(lgue_queue_entry) entry_next;
+};
+
+
+struct lgue_softc {
+       struct arpcom lgue_arpcom;
+       usbd_device_handle              lgue_udev;
+       int lgue_ed[LGUE_ENDPT_MAX];
+       usbd_pipe_handle lgue_ep[LGUE_ENDPT_MAX];
+
+       int lgue_ctl_iface_no;
+       usbd_interface_handle lgue_ctl_iface; /* control interface */
+       int lgue_data_iface_no;
+       usbd_interface_handle lgue_data_iface; /* data interface */
+
+       char lgue_dying;
+       int lgue_if_flags;
+
+       int lgue_tx_cnt;
+       usbd_xfer_handle lgue_tx_xfer;
+       char *lgue_tx_buf;
+
+       int lgue_rx_cnt;
+       usbd_xfer_handle lgue_rx_xfer;
+       struct timeval lgue_rx_notice;
+       char *lgue_rx_buf;
+
+       usbd_xfer_handle lgue_intr_xfer;
+       char *lgue_intr_buf;
+
+       /* Internal queue */
+       STAILQ_HEAD(, lgue_queue_entry) lgue_tx_queue;
+};