/*
* Copyright (c) 2019 Matthew R. Green
* 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 ``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 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.
*/
/*
* Common code shared between USB network drivers.
*/
struct usbnet_private {
/*
* - unp_miilock protects the MII / media data and tick scheduling.
* - unp_rxlock protects the rx path and its data
* - unp_txlock protects the tx path and its data
*
* the lock ordering is:
* ifnet lock -> unp_miilock
* -> unp_rxlock
* -> unp_txlock
* -> unp_mcastlock
*/
kmutex_t unp_miilock;
kmutex_t unp_rxlock;
kmutex_t unp_txlock;
/*
* usbnet_rxeof() is designed to be the done callback for rx completion.
* it provides generic setup and finalisation, calls a different usbnet
* rx_loop callback in the middle, which can use usbnet_enqueue() to
* enqueue a packet for higher levels (or usbnet_input() if previously
* using if_input() path.)
*/
void
usbnet_enqueue(struct usbnet * const un, uint8_t *buf, size_t buflen,
int csum_flags, uint32_t csum_data, int mbuf_flags)
{
USBNETHIST_FUNC();
struct ifnet * const ifp = usbnet_ifp(un);
struct usbnet_private * const unp __unused = un->un_pri;
struct mbuf *m;
if (usbnet_isdying(un) ||
status == USBD_INVAL || status == USBD_NOT_STARTED ||
status == USBD_CANCELLED) {
USBNETHIST_CALLARGS("%jd: uni %#jx dying %#jx status %#jx",
unp->unp_number, (uintptr_t)uni,
usbnet_isdying(un), status);
return;
}
if (status != USBD_NORMAL_COMPLETION) {
if (usbd_ratecheck(&unp->unp_intr_notice)) {
device_printf(un->un_dev, "usb error on intr: %s\n",
usbd_errstr(status));
}
if (status == USBD_STALLED)
usbd_clear_endpoint_stall_async(unp->unp_ep[USBNET_ENDPT_INTR]);
USBNETHIST_CALLARGS("%jd: not normal status %#jx",
unp->unp_number, status, 0, 0);
return;
}
/* Open RX and TX pipes. */
err = usbnet_ep_open_pipes(un);
if (err) {
aprint_error_dev(un->un_dev, "open rx/tx pipes failed: %s\n",
usbd_errstr(err));
error = EIO;
goto out;
}
/* Init RX ring. */
if (usbnet_rx_list_init(un)) {
aprint_error_dev(un->un_dev, "rx list init failed\n");
error = ENOBUFS;
goto out;
}
/* Init TX ring. */
if (usbnet_tx_list_init(un)) {
aprint_error_dev(un->un_dev, "tx list init failed\n");
error = ENOBUFS;
goto out;
}
/* Indicate we are up and running. */
KASSERTMSG(IFNET_LOCKED(ifp), "%s", ifp->if_xname);
ifp->if_flags |= IFF_RUNNING;
/*
* If the hardware has a multicast filter, program it and then
* allow updates to it while we're running.
*/
if (un->un_ops->uno_mcast) {
mutex_enter(&unp->unp_mcastlock);
KASSERTMSG(!unp->unp_mcastactive, "%s", ifp->if_xname);
unp->unp_if_flags = ifp->if_flags;
(*un->un_ops->uno_mcast)(ifp);
unp->unp_mcastactive = true;
mutex_exit(&unp->unp_mcastlock);
}
/* Start up the receive pipe(s). */
usbnet_rx_start_pipes(un);
/* Kick off the watchdog/stats/mii tick. */
mutex_enter(&unp->unp_miilock);
unp->unp_stopped = false;
callout_schedule(&unp->unp_stat_ch, hz);
mutex_exit(&unp->unp_miilock);
out:
if (error) {
usbnet_rx_list_fini(un);
usbnet_tx_list_fini(un);
usbnet_ep_close_pipes(un);
}
/*
* For devices without any media autodetection, treat success
* here as an active link.
*/
if (un->un_ops->uno_statchg == NULL) {
mutex_enter(&unp->unp_miilock);
usbnet_set_link(un, error == 0);
mutex_exit(&unp->unp_miilock);
}
return error;
}
/* MII management. */
static int
usbnet_mii_readreg(device_t dev, int phy, int reg, uint16_t *val)
{
USBNETHIST_FUNC();
struct usbnet * const un = device_private(dev);
int err;
/* MII layer ensures miilock is held. */
usbnet_isowned_mii(un);
static int
usbnet_mii_writereg(device_t dev, int phy, int reg, uint16_t val)
{
USBNETHIST_FUNC();
struct usbnet * const un = device_private(dev);
int err;
/* MII layer ensures miilock is held. */
usbnet_isowned_mii(un);
/*
* If any user-settable flags have changed other than
* IFF_DEBUG, just reset the interface.
*/
if ((changed & ~(IFF_CANTCHANGE | IFF_DEBUG)) != 0)
return ENETRESET;
/*
* Otherwise, cache the flags change so we can read the flags
* under unp_mcastlock for multicast updates in SIOCADDMULTI or
* SIOCDELMULTI without IFNET_LOCK.
*/
mutex_enter(&unp->unp_mcastlock);
unp->unp_if_flags = ifp->if_flags;
mutex_exit(&unp->unp_mcastlock);
/*
* If we're switching on or off promiscuous mode, reprogram the
* hardware multicast filter now.
*
* XXX Actually, reset the interface, because some usbnet
* drivers (e.g., aue(4)) initialize the hardware differently
* in uno_init depending on IFF_PROMISC. But some (again,
* aue(4)) _also_ need to know whether IFF_PROMISC is set in
* uno_mcast and do something different with it there. Maybe
* the logic can be unified, but it will require an audit and
* testing of all the usbnet drivers.
*/
if (changed & IFF_PROMISC)
return ENETRESET;
static int
usbnet_if_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
USBNETHIST_FUNC();
struct usbnet * const un = ifp->if_softc;
struct usbnet_private * const unp __unused = un->un_pri;
int error;
USBNETHIST_CALLARGSN(11, "%jd: enter %#jx data %#jx",
unp->unp_number, cmd, (uintptr_t)data, 0);
if (un->un_ops->uno_override_ioctl)
return uno_override_ioctl(un, ifp, cmd, data);
error = ether_ioctl(ifp, cmd, data);
if (error == ENETRESET) {
switch (cmd) {
case SIOCADDMULTI:
case SIOCDELMULTI:
/*
* If there's a hardware multicast filter, and
* it has been programmed by usbnet_init_rx_tx
* and is active, update it now. Otherwise,
* drop the update on the floor -- it will be
* observed by usbnet_init_rx_tx next time we
* bring the interface up.
*/
if (un->un_ops->uno_mcast) {
mutex_enter(&unp->unp_mcastlock);
if (unp->unp_mcastactive)
(*un->un_ops->uno_mcast)(ifp);
mutex_exit(&unp->unp_mcastlock);
}
error = 0;
break;
default:
error = uno_ioctl(un, ifp, cmd, data);
}
}
return error;
}
/*
* Generic stop network function:
* - mark as stopping
* - call DD routine to stop the device
* - turn off running, timer, statchg callout, link
* - stop transfers
* - free RX and TX resources
* - close pipes
*
* usbnet_if_stop() is for the if_stop handler.
*/
static void
usbnet_stop(struct usbnet *un, struct ifnet *ifp, int disable)
{
struct usbnet_private * const unp = un->un_pri;
struct mii_data * const mii = usbnet_mii(un);
/*
* Stop the timer first, then the task -- if the timer was
* already firing, we stop the task or wait for it complete
* only after it last fired. Setting unp_stopped prevents the
* timer task from being scheduled again.
*/
callout_halt(&unp->unp_stat_ch, NULL);
usb_rem_task_wait(un->un_udev, &unp->unp_ticktask, USB_TASKQ_DRIVER,
NULL);
/*
* Now that we have stopped calling mii_tick, bring the MII
* state machine down.
*/
if (mii) {
mutex_enter(&unp->unp_miilock);
mii_down(mii);
mutex_exit(&unp->unp_miilock);
}
/* Stop transfers. */
usbnet_ep_stop_pipes(un);
/*
* Now that the software is quiescent, ask the driver to stop
* the hardware. The driver's uno_stop routine now has
* exclusive access to any registers that might previously have
* been used by to ifmedia, mii, or ioctl callbacks.
*
* Don't bother if the device is being detached, though -- if
* it's been unplugged then there's no point in trying to touch
* the registers.
*/
if (!usbnet_isdying(un))
uno_stop(un, ifp, disable);
/*
* If we're already stopped, nothing to do.
*
* XXX This should be an assertion, but it may require some
* analysis -- and possibly some tweaking -- of sys/net to
* ensure.
*/
if ((ifp->if_flags & IFF_RUNNING) == 0)
return;
usbnet_stop(un, ifp, disable);
}
/*
* Generic tick task function.
*
* usbnet_tick() is triggered from a callout, and triggers a call to
* usbnet_tick_task() from the usb_task subsystem.
*/
static void
usbnet_tick(void *arg)
{
USBNETHIST_FUNC();
struct usbnet * const un = arg;
struct usbnet_private * const unp = un->un_pri;
/*
* Prevent anyone from bringing the interface back up once
* we're detaching.
*/
if (usbnet_isdying(un))
return EIO;
/*
* If we're already running, stop the interface first -- we're
* reinitializing it.
*
* XXX Grody for sys/net to call if_init to reinitialize. This
* should be an assertion, not a branch, but it will require
* some tweaking of sys/net to avoid. See also the comment in
* usbnet_ifflags_cb about if_init vs uno_mcast on reinitialize.
*/
if (ifp->if_flags & IFF_RUNNING)
usbnet_stop(un, ifp, /*disable*/1/*XXX???*/);
KASSERTMSG((ifp->if_flags & IFF_RUNNING) == 0, "%s", ifp->if_xname);
error = uno_init(un, ifp);
if (error)
return error;
error = usbnet_init_rx_tx(un);
if (error)
return error;
/*
* usbnet_attach() and usbnet_attach_ifp() perform setup of the relevant
* 'usbnet'. The first is enough to enable device access (eg, endpoints
* are connected and commands can be sent), and the second connects the
* device to the system networking.
*
* Always call usbnet_detach(), even if usbnet_attach_ifp() is skipped.
* Also usable as driver detach directly.
*
* To skip ethernet configuration (eg, point-to-point), make sure that
* the un_eaddr[] is fully zero.
*/
/*
* Prevent new activity. After we stop the interface, it
* cannot be brought back up.
*/
atomic_store_relaxed(&unp->unp_dying, true);
/*
* If we're still running on the network, stop and wait for all
* asynchronous activity to finish.
*
* If usbnet_attach_ifp never ran, IFNET_LOCK won't work, but
* no activity is possible, so just skip this part.
*/
if (unp->unp_ifp_attached) {
IFNET_LOCK(ifp);
if (ifp->if_flags & IFF_RUNNING) {
usbnet_if_stop(ifp, 1);
}
IFNET_UNLOCK(ifp);
}
/*
* The callout and tick task can't be scheduled anew at this
* point, and usbnet_if_stop has waited for them to complete.
*/
KASSERT(!callout_pending(&unp->unp_stat_ch));
KASSERT(!usb_task_pending(un->un_udev, &unp->unp_ticktask));
if (mii) {
mii_detach(mii, MII_PHY_ANY, MII_OFFSET_ANY);
ifmedia_fini(&mii->mii_media);
}
if (unp->unp_ifp_attached) {
if (!usbnet_empty_eaddr(un))
ether_ifdetach(ifp);
else
bpf_detach(ifp);
if_detach(ifp);
}
usbnet_ec(un)->ec_mii = NULL;
/*
* Notify userland that we're going away, if we arrived in the
* first place.
*/
if (unp->unp_ifp_attached) {
usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, un->un_udev,
un->un_dev);
}