/*-
* Copyright (c) 2017 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Taylor R. Campbell.
*
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/* Get endpoint descriptor 0. Make sure it's bulk-in. */
ed = usbd_interface2endpoint_descriptor(uiaa->uiaa_iface, 0);
if (ed == NULL) {
aprint_error_dev(sc->sc_dev, "failed to read endpoint 0\n");
return;
}
if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN ||
UE_GET_XFERTYPE(ed->bmAttributes) != UE_BULK) {
aprint_error_dev(sc->sc_dev, "invalid endpoint\n");
return;
}
/* Remember the maximum packet size. */
sc->sc_maxpktsize = UGETW(ed->wMaxPacketSize);
/* Open an exclusive MP-safe pipe for endpoint 0. */
status = usbd_open_pipe(uiaa->uiaa_iface, ed->bEndpointAddress,
USBD_EXCLUSIVE_USE|USBD_MPSAFE, &sc->sc_pipe);
if (status) {
aprint_error_dev(sc->sc_dev, "failed to open pipe: %d\n",
status);
return;
}
/* Create an xfer of maximum packet size on the pipe. */
status = usbd_create_xfer(sc->sc_pipe, sc->sc_maxpktsize,
0, 0, &sc->sc_xfer);
if (status) {
aprint_error_dev(sc->sc_dev, "failed to create xfer: %d\n",
status);
return;
}
if (!pmf_device_register(self, NULL, NULL))
aprint_error_dev(sc->sc_dev, "failed to register power handler"
"\n");
/* Success! We are ready to run. */
sc->sc_attached = true;
rndsource_setcb(&sc->sc_rnd, ualea_get, sc);
rnd_attach_source(&sc->sc_rnd, device_xname(self), RND_TYPE_RNG,
RND_FLAG_COLLECT_VALUE|RND_FLAG_HASCB);
}
static int
ualea_detach(device_t self, int flags)
{
struct ualea_softc *sc = device_private(self);
/* Prevent new use of xfer. */
if (sc->sc_attached)
rnd_detach_source(&sc->sc_rnd);
/* Prevent xfer from rescheduling itself, if still pending. */
mutex_enter(&sc->sc_lock);
sc->sc_needed = 0;
mutex_exit(&sc->sc_lock);
/* Cancel pending xfer. */
if (sc->sc_pipe)
usbd_abort_pipe(sc->sc_pipe);
KASSERT(!sc->sc_inflight);
/* All users have drained. Tear it all down. */
if (sc->sc_xfer)
usbd_destroy_xfer(sc->sc_xfer);
if (sc->sc_pipe)
usbd_close_pipe(sc->sc_pipe);
mutex_destroy(&sc->sc_lock);
/* Do nothing if we need nothing. */
if (sc->sc_needed == 0)
return;
/* Setup the xfer to call ualea_xfer_done with sc. */
usbd_setup_xfer(sc->sc_xfer, sc, usbd_get_buffer(sc->sc_xfer),
sc->sc_maxpktsize, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT,
ualea_xfer_done);
/* Issue xfer or complain if we can't. */
status = usbd_transfer(sc->sc_xfer);
KASSERT(status != USBD_NORMAL_COMPLETION); /* asynchronous xfer */
if (status != USBD_IN_PROGRESS) {
device_printf(sc->sc_dev, "failed to issue xfer: %s\n",
usbd_errstr(status));
/* We failed -- let someone else have a go. */
return;
}
/* Mark xfer in-flight. */
sc->sc_inflight = true;
}
/*
* If the transfer failed, give up -- forget what we need and
* don't reschedule ourselves.
*/
if (status) {
device_printf(sc->sc_dev, "xfer failed: %s\n",
usbd_errstr(status));
mutex_enter(&sc->sc_lock);
sc->sc_needed = 0;
sc->sc_inflight = false;
mutex_exit(&sc->sc_lock);
return;
}
/*
* Enter the data, debit what we contributed from what we need,
* mark the xfer as done, and reschedule the xfer if we still
* need more.
*
* Must enter the data under the lock so it happens atomically
* with updating sc_needed -- otherwise we might hang needing
* entropy and not scheduling xfer. Must not touch pkt after
* clearing sc_inflight and possibly rescheduling the xfer.
*/
mutex_enter(&sc->sc_lock);
rnd_add_data(&sc->sc_rnd, pkt, pktsize, NBBY*pktsize);
sc->sc_needed -= MIN(sc->sc_needed, pktsize);
sc->sc_inflight = false;
ualea_xfer(sc);
mutex_exit(&sc->sc_lock);
}
MODULE(MODULE_CLASS_DRIVER, ualea, NULL);
#ifdef _MODULE
#include "ioconf.c"
#endif
static int
ualea_modcmd(modcmd_t cmd, void *aux)
{
int error = 0;