diff --git a/lib/libusb20/Makefile b/lib/libusb20/Makefile new file mode 100644 index 000000000000..0c0f18f1bfec --- /dev/null +++ b/lib/libusb20/Makefile @@ -0,0 +1,24 @@ +# +# $FreeBSD$ +# +# Makefile for the FreeBSD specific LibUSB 2.0 +# + +LIB= usb20 +SHLIB_MAJOR= 1 +SHLIB_MINOR= 0 +SRCS= libusb20.c +SRCS+= libusb20_desc.c +SRCS+= libusb20_ugen20.c +SRCS+= libusb20_compat01.c +SRCS+= libusb20_compat10.c +INCS+= libusb20.h +INCS+= libusb20_desc.h +INCS+= libusb20_compat01.h +INCS+= libusb20_compat10.h +MAN= libusb20.3 +MKLINT= no +NOGCCERROR= + +.include + diff --git a/lib/libusb20/libusb20.3 b/lib/libusb20/libusb20.3 new file mode 100644 index 000000000000..975bc637ae68 --- /dev/null +++ b/lib/libusb20/libusb20.3 @@ -0,0 +1,893 @@ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky +.\" +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd Oct 23, 2008 +.Dt LIBUSB20 3 +.Os +.Sh NAME +.Nm libusb20 +. +.Nd "USB access library" +. +. +.Sh LIBRARY +. +. +USB access library (libusb20 -lusb20) +. +. +. +.Sh SYNOPSIS +. +. +.In libusb20.h +. +. +.Sh DESCRIPTION +. +The +.Nm +library implements functions to be able to easily access and control +USB through the USB file system interface. +. +. +.Sh USB TRANSFER OPERATIONS +. +.Pp +. +.Fn libusb20_tr_close +This function will release all kernel resources associated with an USB +.Fa xfer . +. +This function returns zero upon success. +. +Non-zero return values indicate a LIBUSB20_ERROR value. +. +.Pp +. +.Fn libusb20_tr_open +This function will allocate kernel resources like +.Fa MaxBufSize +and +.Fa MaxFrameCount +associated with an USB +.Fa xfer +and bind the transfer to the specified +.Fa ep_no . +. +This function returns zero upon success. +. +Non-zero return values indicate a LIBUSB20_ERROR value. +. +.Pp +. +.Fn libusb20_tr_get_pointer +This function will return a pointer to the allocated USB transfer according to the +.Fa pdev +and +.Fa tr_index +arguments. +. +This function returns NULL in case of failure. +. +.Pp +. +.Fn libusb20_tr_get_time_complete +This function will return the completion time of an USB transfer in +millisecond units. This function is most useful for isochronous USB +transfers when doing echo cancelling. +. +.Pp +. +.Fn libusb20_tr_get_actual_frames +This function will return the actual number of USB frames after an USB +transfer completed. A value of zero means that no data was transferred. +. +.Pp +. +.Fn libusb20_tr_get_actual_length +This function will return the sum of the actual length for all +transferred USB frames for the given USB transfer. +. +.Pp +. +.Fn libusb20_tr_get_max_frames +This function will return the maximum number of USB frames that were +allocated when an USB transfer was setup for the given USB transfer. +. +.Pp +. +.Fn libusb20_tr_get_max_packet_length +This function will return the maximum packet length in bytes +associated with the given USB transfer. +. +The packet length can be used round up buffer sizes so that short USB +packets are avoided for proxy buffers. +. +. +.Pp +. +.Fn libusb20_tr_get_max_total_length +This function will return the maximum value for the length sum of all +USB frames associated with an USB transfer. +. +.Pp +. +.Fn libusb20_tr_get_status +This function will return the status of an USB transfer. +. +Status values are defined by a set of LIBUSB20_TRANSFER_XXX enums. +. +.Pp +. +.Fn libusb20_tr_pending +This function will return non-zero if the given USB transfer is +pending for completion. +. +Else this function returns zero. +. +.Pp +. +.Fn libusb20_tr_callback_wrapper +This is an internal function used to wrap asynchronous USB callbacks. +. +.Pp +. +.Fn libusb20_tr_clear_stall_sync +This is an internal function used to synchronously clear the stall on +the given USB transfer. +. +Please see the USB specification for more information on stall +clearing. +. +If the given USB transfer is pending when this function is called, the +USB transfer will complete with an error after that this function has +been called. +. +.Pp +. +.Fn libusb20_tr_drain +This function will stop the given USB transfer and will not return +until the USB transfer has been stopped in hardware. +. +.Pp +. +.Fn libusb20_tr_set_buffer +This function is used to set the +.Fa buffer +pointer for the given USB transfer and +.Fa fr_index . +. +Typically the frame index is zero. +. +. +.Pp +. +.Fn libusb20_tr_set_callback +This function is used to set the USB callback for asynchronous USB +transfers. +. +The callback type is defined by libusb20_tr_callback_t. +. +.Pp +. +.Fn libusb20_tr_set_flags +This function is used to set various USB flags for the given USB transfer. +.Bl -tag +.It LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK +Report a short frame as error. +.It LIBUSB20_TRANSFER_MULTI_SHORT_NOT_OK +Multiple short frames are not allowed. +.It LIBUSB20_TRANSFER_FORCE_SHORT +All transmitted frames are short terminated. +.It LIBUSB20_TRANSFER_DO_CLEAR_STALL +Will do a clear-stall before starting the transfer. +.El +. +.Pp +. +.Fn libusb20_tr_set_length +This function sets the length of a given USB transfer and frame index. +. +.Pp +. +.Fn libusb20_tr_set_priv_sc0 +This function sets private driver pointer number zero. +. +.Pp +. +.Fn libusb20_tr_set_priv_sc1 +This function sets private driver pointer number one. +. +.Pp +. +.Fn libusb20_tr_set_timeout +This function sets the timeout for the given USB transfer. +. +A timeout value of zero means no timeout. +. +The timeout is given in milliseconds. +. +.Pp +. +.Fn libusb20_tr_set_total_frames +This function sets the total number of frames that should be executed when the USB transfer is submitted. +. +The total number of USB frames must be less than the maximum number of USB frames associated with the given USB transfer. +. +.Pp +. +.Fn libusb20_tr_setup_bulk +This function is a helper function for setting up a single frame USB BULK transfer. +. +.Pp +. +.Fn libusb20_tr_setup_control +This function is a helper function for setting up a single or dual +frame USB CONTROL transfer depending on the control transfer length. +. +.Pp +. +.Fn libusb20_tr_setup_intr +This function is a helper function for setting up a single frame USB INTERRUPT transfer. +. +.Pp +. +.Fn libusb20_tr_setup_isoc +This function is a helper function for setting up a multi frame USB ISOCHRONOUS transfer. +. +.Pp +. +.Fn libusb20_tr_start +This function will get the USB transfer started, if not already +started. +. +This function will not get the transfer queued in hardware. +. +This function is non-blocking. +. +.Pp +. +.Fn libusb20_tr_stop +This function will get the USB transfer stopped, if not already stopped. +. +This function is non-blocking, which means that the actual stop can +happen after the return of this function. +. +.Pp +. +.Fn libusb20_tr_submit +This function will get the USB transfer queued in hardware. +. +. +.Pp +. +.Fn libusb20_tr_get_priv_sc0 +This function returns private driver pointer number zero associated +with an USB transfer. +. +. +.Pp +. +.Fn libusb20_tr_get_priv_sc1 +This function returns private driver pointer number one associated +with an USB transfer. +. +. +.Sh USB DEVICE OPERATIONS +. +.Pp +. +.Fn libusb20_dev_get_backend_name +This function returns a zero terminated string describing the backend used. +. +.Pp +. +.Fn libusb20_dev_get_desc +This function returns a zero terminated string describing the given USB device. +. +.Pp +. +.Fn libusb20_dev_claim_interface +This function will try to claim the given USB interface given by +.Fa iface_index . +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_close +This function will close the given USB device. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_detach_kernel_driver +This function will try to detach the kernel driver for the USB interface given by +.Fa iface_index . +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_set_config_index +This function will try to set the configuration index on an USB +device. +. +The first configuration index is zero. +. +The un-configure index is 255. +. +This function returns zero on success else a LIBUSB20_ERROR value is returned. +. +.Pp +. +.Fn libusb20_dev_get_debug +This function returns the debug level of an USB device. +. +.Pp +. +.Fn libusb20_dev_get_fd +This function returns the file descriptor of the given USB device. +. +A negative value is returned when no file descriptor is present. +. +The file descriptor can be used for polling purposes. +. +.Pp +. +.Fn libusb20_dev_kernel_driver_active +This function returns a non-zero value if a kernel driver is active on +the given USB interface. +. +Else zero is returned. +. +.Pp +. +.Fn libusb20_dev_open +This function opens an USB device so that setting up USB transfers +becomes possible. +. +The number of USB transfers can be zero which means only control +transfers are allowed. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +A return value of LIBUSB20_ERROR_BUSY means that the device is already +opened. +. +.Pp +. +.Fn libusb20_dev_process +This function is called to sync kernel USB transfers with userland USB +transfers. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned typically indicating that the given USB device has been +detached. +. +.Pp +. +.Fn libusb20_dev_release_interface +This function will try to release a claimed USB interface for the specified USB device. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_request_sync +This function will perform a synchronous control request on the given +USB device. +. +Before this call will succeed the USB device must be opened. +. +.Fa setup +is a pointer to a decoded and host endian SETUP packet. +.Fa data +is a pointer to a data transfer buffer associated with the control transaction. This argument can be NULL. +.Fa pactlen +is a pointer to a variable that will hold the actual transfer length after the control transaction is complete. +.Fa timeout +is the transaction timeout given in milliseconds. +A timeout of zero means no timeout. +.Fa flags +is used to specify transaction flags, for example LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_req_string_sync +This function will synchronously request an USB string by language ID +and string index into the given buffer limited by a maximum length. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_req_string_simple_sync +This function will synchronously request an USB string using the +default language ID and convert the string into ASCII before storing +the string into the given buffer limited by a maximum length which +includes the terminating zero. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +. +.Pp +. +.Fn libusb20_dev_reset +This function will try to BUS reset the given USB device and restore +the last set USB configuration. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_set_power_mode +This function sets the power mode of the USB device. +. +Valid power modes: +.Bl -tag +.It LIBUSB20_POWER_OFF +.It LIBUSB20_POWER_ON +.It LIBUSB20_POWER_SAVE +.It LIBUSB20_POWER_SUSPEND +.It LIBUSB20_POWER_RESUME +.El +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_get_power_mode +This function returns the currently selected power mode for the given +USB device. +. +.Pp +. +.Fn libusb20_dev_set_alt_index +This function will try to set the given alternate index for the given +USB interface index. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_set_owner +This function will set the ownership of the given USB device. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_set_perm +This function will set the permissions of the given USB device. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_set_iface_owner +This function will set the ownership of the given USB interface. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_set_iface_perm +This function will set the permissions of the given USB interface. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_get_owner +This function will retrieve the current USB device ownership. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_get_perm +This function will retrieve the current USB device permissions. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_get_iface_owner +This function will retrieve the current USB interface ownership for +the given USB interface. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_get_iface_perm +This function will retrieve the current USB interface permissions for +the given USB interface. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_dev_get_device_desc +This function returns a pointer to the decoded and host endian version +of the device descriptor. +. +The USB device need not be opened when calling this function. +. +.Pp +. +.Fn libusb20_dev_alloc_config +This function will read out and decode the USB config descriptor for +the given USB device and config index. This function returns a pointer +to the decoded configuration which must eventually be passed to +free(). NULL is returned in case of failure. +. +.Pp +. +.Fn libusb20_dev_alloc(void) +This is an internal function to allocate a new USB device. +. +.Pp +. +.Fn libusb20_dev_get_address +This function returns the internal and not necessarily the real +hardware address of the given USB device. +. +.Pp +. +.Fn libusb20_dev_get_bus_number +This function return the internal bus number which the given USB +device belongs to. +. +.Pp +. +.Fn libusb20_dev_get_mode +This function returns the current operation mode of the USB entity. +. +Valid return values are: +.Bl -tag +.It LIBUSB20_MODE_HOST +.It LIBUSB20_MODE_DEVICE +.El +. +.Pp +. +.Fn libusb20_dev_get_speed +This function returns the current speed of the given USB device. +. +.Bl -tag +.It LIBUSB20_SPEED_UNKNOWN +.It LIBUSB20_SPEED_LOW +.It LIBUSB20_SPEED_FULL +.It LIBUSB20_SPEED_HIGH +.It LIBUSB20_SPEED_VARIABLE +.It LIBUSB20_SPEED_SUPER +.El +. +.Pp +. +.Fn libusb20_dev_get_config_index +This function returns the currently select config index for the given +USB device. +. +.Pp +. +.Fn libusb20_dev_free +This function will free the given USB device and all associated USB +transfers. +. +.Pp +. +.Fn libusb20_dev_set_debug +This function will set the debug level for the given USB device. +. +.Pp +. +.Fn libusb20_dev_wait_process +This function will wait until a pending USB transfer has completed on +the given USB device. +. +A timeout value can be specified which is passed on to the +.Xr 2 poll +function. +. +.Sh USB BUS OPERATIONS +. +.Fn libusb20_bus_set_owner +This function will set the ownership for the given USB bus. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_bus_set_perm +This function will set the permissions for the given USB bus. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_bus_get_owner +This function will retrieve the ownership for the given USB bus. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_bus_get_perm +This function will retrieve the permissions for the given USB bus. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +. +.Sh USB BACKEND OPERATIONS +. +.Fn libusb20_be_get_dev_quirk +This function will return the device quirk according to +.Fa index +into the libusb20_quirk structure pointed to by +.Fa pq . +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +If the given quirk does not exist LIBUSB20_ERROR_NOT_FOUND is +returned. +. +.Pp +. +.Fn libusb20_be_get_quirk_name +This function will return the quirk name according to +.Fa index +into the libusb20_quirk structure pointed to by +.Fa pq . +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +If the given quirk does not exist LIBUSB20_ERROR_NOT_FOUND is +returned. +. +.Pp +. +.Fn libusb20_be_add_dev_quirk +This function will add the libusb20_quirk structure pointed to by the +.Fa pq +argument into the device quirk list. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +If the given quirk cannot be added LIBUSB20_ERROR_NO_MEM is +returned. +. +.Pp +. +.Fn libusb20_be_remove_dev_quirk +This function will remove the quirk matching the libusb20_quirk structure pointed to by the +.Fa pq +argument from the device quirk list. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +If the given quirk does not exist LIBUSB20_ERROR_NOT_FOUND is +returned. +. +.Pp +. +.Fn libusb20_be_set_owner +This function will set the ownership for the given backend. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_be_set_perm +This function will set the permissions for the given backend. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_be_get_owner +This function will retrieve the ownership of the given backend. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_be_get_perm +This function will retrieve the permissions of the given backend. +. +. +This function returns zero on success else a LIBUSB20_ERROR value is +returned. +. +.Pp +. +.Fn libusb20_be_alloc +This is an internal function to allocate a USB backend. +. +.Pp +.Fn libusb20_be_alloc_default +.Fn libusb20_be_alloc_freebsd +.Fn libusb20_be_alloc_linux +These functions are used to allocate a specific USB backend or the +operating system default USB backend. Allocating a backend is a way to +scan for currently present USB devices. +. +.Pp +. +.Fn libusb20_be_device_foreach +This function is used to iterate USB devices present in a USB backend. +. +The starting value of +.Fa pdev +is NULL. +. +This function returns the next USB device in the list. +. +If NULL is returned the end of the USB device list has been reached. +. +.Pp +. +.Fn libusb20_be_dequeue_device +This function will dequeue the given USB device pointer from the +backend USB device list. +. +Dequeued USB devices will not be freed when the backend is freed. +. +.Pp +. +.Fn libusb20_be_enqueue_device +This function will enqueue the given USB device pointer in the backend USB device list. +. +Enqueued USB devices will get freed when the backend is freed. +. +.Pp +. +.Fn libusb20_be_free +This function will free the given backend and all USB devices in its device list. +. +. +.Sh USB DESCRIPTOR PARSING +. +.Fn libusb20_me_get_1 +This function will return a byte at the given byte offset of a message +entity. +. +This function is safe against invalid offsets. +. +.Pp +. +.Fn libusb20_me_get_2 +This function will return a little endian 16-bit value at the given byte offset of a message +entity. +. +This function is safe against invalid offsets. +. +.Pp +. +.Fn libusb20_me_encode +This function will encode a so-called *DECODED structure into binary +format. +. +The total encoded length that will fit in the given buffer is +returned. +. +If the buffer pointer is NULL no data will be written to the buffer +location. +. +.Pp +. +.Fn libusb20_me_decode +This function will decode a binary structure into a so-called *DECODED +structure. +. +The total decoded length is returned. +. +The buffer pointer cannot be NULL. +. +. +.Sh LIBUSB VERSION 0.1 COMPATIBILITY +. +.Fn usb_open +.Fn usb_close +.Fn usb_get_string +.Fn usb_get_string_simple +.Fn usb_get_descriptor_by_endpoint +.Fn usb_get_descriptor +.Fn usb_parse_descriptor +.Fn usb_parse_configuration +.Fn usb_destroy_configuration +.Fn usb_fetch_and_parse_descriptors +.Fn usb_bulk_write +.Fn usb_bulk_read +.Fn usb_interrupt_write +.Fn usb_interrupt_read +.Fn usb_control_msg +.Fn usb_set_configuration +.Fn usb_claim_interface +.Fn usb_release_interface +.Fn usb_set_altinterface +.Fn usb_resetep +.Fn usb_clear_halt +.Fn usb_reset +.Fn usb_strerror +.Fn usb_init +.Fn usb_set_debug +.Fn usb_find_busses +.Fn usb_find_devices +.Fn usb_device +.Fn usb_get_busses +These functions are compliant with LibUSB version 0.1.12. +. +.Sh FILES +. +. +/dev/usb +.Sh SEE ALSO +.Xr usb2_core 4 , +.Xr usbconfig 8 +. +. +.Sh HISTORY +. +. +Some parts of the +.Nm +API derives from the libusb project at sourceforge. diff --git a/lib/libusb20/libusb20.c b/lib/libusb20/libusb20.c new file mode 100644 index 000000000000..432cd993b438 --- /dev/null +++ b/lib/libusb20/libusb20.c @@ -0,0 +1,1245 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "libusb20.h" +#include "libusb20_desc.h" +#include "libusb20_int.h" + +static int +dummy_int(void) +{ + return (LIBUSB20_ERROR_NOT_SUPPORTED); +} + +static void +dummy_void(void) +{ + return; +} + +static void +dummy_callback(struct libusb20_transfer *xfer) +{ + ; /* style fix */ + switch (libusb20_tr_get_status(xfer)) { + case LIBUSB20_TRANSFER_START: + libusb20_tr_submit(xfer); + break; + default: + /* complete or error */ + break; + } + return; +} + +#define dummy_get_config_desc_full (void *)dummy_int +#define dummy_get_config_index (void *)dummy_int +#define dummy_set_config_index (void *)dummy_int +#define dummy_claim_interface (void *)dummy_int +#define dummy_release_interface (void *)dummy_int +#define dummy_set_alt_index (void *)dummy_int +#define dummy_reset_device (void *)dummy_int +#define dummy_set_power_mode (void *)dummy_int +#define dummy_get_power_mode (void *)dummy_int +#define dummy_kernel_driver_active (void *)dummy_int +#define dummy_detach_kernel_driver (void *)dummy_int +#define dummy_do_request_sync (void *)dummy_int +#define dummy_tr_open (void *)dummy_int +#define dummy_tr_close (void *)dummy_int +#define dummy_tr_clear_stall_sync (void *)dummy_int +#define dummy_process (void *)dummy_int + +#define dummy_tr_submit (void *)dummy_void +#define dummy_tr_cancel_async (void *)dummy_void + +static const struct libusb20_device_methods libusb20_dummy_methods = { + LIBUSB20_DEVICE(LIBUSB20_DECLARE, dummy) +}; + +void +libusb20_tr_callback_wrapper(struct libusb20_transfer *xfer) +{ + ; /* style fix */ + +repeat: + + if (!xfer->is_pending) { + xfer->status = LIBUSB20_TRANSFER_START; + } else { + xfer->is_pending = 0; + } + + (xfer->callback) (xfer); + + if (xfer->is_restart) { + xfer->is_restart = 0; + goto repeat; + } + if (xfer->is_draining && + (!xfer->is_pending)) { + xfer->is_draining = 0; + xfer->status = LIBUSB20_TRANSFER_DRAINED; + (xfer->callback) (xfer); + } + return; +} + +int +libusb20_tr_close(struct libusb20_transfer *xfer) +{ + int error; + + if (!xfer->is_opened) { + return (LIBUSB20_ERROR_OTHER); + } + error = (xfer->pdev->methods->tr_close) (xfer); + + if (xfer->pLength) { + free(xfer->pLength); + } + if (xfer->ppBuffer) { + free(xfer->ppBuffer); + } + /* clear some fields */ + xfer->is_opened = 0; + xfer->maxFrames = 0; + xfer->maxTotalLength = 0; + xfer->maxPacketLen = 0; + return (error); +} + +int +libusb20_tr_open(struct libusb20_transfer *xfer, uint32_t MaxBufSize, + uint32_t MaxFrameCount, uint8_t ep_no) +{ + uint32_t size; + int error; + + if (xfer->is_opened) { + return (LIBUSB20_ERROR_BUSY); + } + if (MaxFrameCount == 0) { + return (LIBUSB20_ERROR_INVALID_PARAM); + } + xfer->maxFrames = MaxFrameCount; + + size = MaxFrameCount * sizeof(xfer->pLength[0]); + xfer->pLength = malloc(size); + if (xfer->pLength == NULL) { + return (LIBUSB20_ERROR_NO_MEM); + } + memset(xfer->pLength, 0, size); + + size = MaxFrameCount * sizeof(xfer->ppBuffer[0]); + xfer->ppBuffer = malloc(size); + if (xfer->ppBuffer == NULL) { + free(xfer->pLength); + return (LIBUSB20_ERROR_NO_MEM); + } + memset(xfer->ppBuffer, 0, size); + + error = (xfer->pdev->methods->tr_open) (xfer, MaxBufSize, + MaxFrameCount, ep_no); + + if (error) { + free(xfer->ppBuffer); + free(xfer->pLength); + } else { + xfer->is_opened = 1; + } + return (error); +} + +struct libusb20_transfer * +libusb20_tr_get_pointer(struct libusb20_device *pdev, uint16_t trIndex) +{ + if (trIndex >= pdev->nTransfer) { + return (NULL); + } + return (pdev->pTransfer + trIndex); +} + +uint32_t +libusb20_tr_get_actual_frames(struct libusb20_transfer *xfer) +{ + return (xfer->aFrames); +} + +uint16_t +libusb20_tr_get_time_complete(struct libusb20_transfer *xfer) +{ + return (xfer->timeComplete); +} + +uint32_t +libusb20_tr_get_actual_length(struct libusb20_transfer *xfer) +{ + uint32_t x; + uint32_t actlen = 0; + + for (x = 0; x != xfer->aFrames; x++) { + actlen += xfer->pLength[x]; + } + return (actlen); +} + +uint32_t +libusb20_tr_get_max_frames(struct libusb20_transfer *xfer) +{ + return (xfer->maxFrames); +} + +uint32_t +libusb20_tr_get_max_packet_length(struct libusb20_transfer *xfer) +{ + /* + * Special Case NOTE: If the packet multiplier is non-zero for + * High Speed USB, the value returned is equal to + * "wMaxPacketSize * multiplier" ! + */ + return (xfer->maxPacketLen); +} + +uint32_t +libusb20_tr_get_max_total_length(struct libusb20_transfer *xfer) +{ + return (xfer->maxTotalLength); +} + +uint8_t +libusb20_tr_get_status(struct libusb20_transfer *xfer) +{ + return (xfer->status); +} + +uint8_t +libusb20_tr_pending(struct libusb20_transfer *xfer) +{ + return (xfer->is_pending); +} + +void * +libusb20_tr_get_priv_sc0(struct libusb20_transfer *xfer) +{ + return (xfer->priv_sc0); +} + +void * +libusb20_tr_get_priv_sc1(struct libusb20_transfer *xfer) +{ + return (xfer->priv_sc1); +} + +void +libusb20_tr_stop(struct libusb20_transfer *xfer) +{ + if (!xfer->is_pending) { + /* transfer not pending */ + return; + } + if (xfer->is_cancel) { + /* already cancelling */ + return; + } + xfer->is_cancel = 1; /* we are cancelling */ + + (xfer->pdev->methods->tr_cancel_async) (xfer); + return; +} + +void +libusb20_tr_drain(struct libusb20_transfer *xfer) +{ + /* make sure that we are cancelling */ + libusb20_tr_stop(xfer); + + if (xfer->is_pending) { + xfer->is_draining = 1; + } + return; +} + +void +libusb20_tr_clear_stall_sync(struct libusb20_transfer *xfer) +{ + (xfer->pdev->methods->tr_clear_stall_sync) (xfer); + return; +} + +void +libusb20_tr_set_buffer(struct libusb20_transfer *xfer, void *buffer, uint16_t frIndex) +{ + xfer->ppBuffer[frIndex] = buffer; + return; +} + +void +libusb20_tr_set_callback(struct libusb20_transfer *xfer, libusb20_tr_callback_t *cb) +{ + xfer->callback = cb; + return; +} + +void +libusb20_tr_set_flags(struct libusb20_transfer *xfer, uint8_t flags) +{ + xfer->flags = flags; + return; +} + +void +libusb20_tr_set_length(struct libusb20_transfer *xfer, uint32_t length, uint16_t frIndex) +{ + xfer->pLength[frIndex] = length; + return; +} + +void +libusb20_tr_set_priv_sc0(struct libusb20_transfer *xfer, void *sc0) +{ + xfer->priv_sc0 = sc0; + return; +} + +void +libusb20_tr_set_priv_sc1(struct libusb20_transfer *xfer, void *sc1) +{ + xfer->priv_sc1 = sc1; + return; +} + +void +libusb20_tr_set_timeout(struct libusb20_transfer *xfer, uint32_t timeout) +{ + xfer->timeout = timeout; + return; +} + +void +libusb20_tr_set_total_frames(struct libusb20_transfer *xfer, uint32_t nFrames) +{ + if (nFrames > xfer->maxFrames) { + /* should not happen */ + nFrames = xfer->maxFrames; + } + xfer->nFrames = nFrames; + return; +} + +void +libusb20_tr_setup_bulk(struct libusb20_transfer *xfer, void *pBuf, uint32_t length, uint32_t timeout) +{ + xfer->ppBuffer[0] = pBuf; + xfer->pLength[0] = length; + xfer->timeout = timeout; + xfer->nFrames = 1; + return; +} + +void +libusb20_tr_setup_control(struct libusb20_transfer *xfer, void *psetup, void *pBuf, uint32_t timeout) +{ + uint16_t len; + + xfer->ppBuffer[0] = psetup; + xfer->pLength[0] = 8; /* fixed */ + xfer->timeout = timeout; + + len = ((uint8_t *)psetup)[6] | (((uint8_t *)psetup)[7] << 8); + + if (len != 0) { + xfer->nFrames = 2; + xfer->ppBuffer[1] = pBuf; + xfer->pLength[1] = len; + } else { + xfer->nFrames = 1; + } + return; +} + +void +libusb20_tr_setup_intr(struct libusb20_transfer *xfer, void *pBuf, uint32_t length, uint32_t timeout) +{ + xfer->ppBuffer[0] = pBuf; + xfer->pLength[0] = length; + xfer->timeout = timeout; + xfer->nFrames = 1; + return; +} + +void +libusb20_tr_setup_isoc(struct libusb20_transfer *xfer, void *pBuf, uint32_t length, uint16_t frIndex) +{ + if (frIndex >= xfer->maxFrames) { + /* should not happen */ + return; + } + xfer->ppBuffer[frIndex] = pBuf; + xfer->pLength[frIndex] = length; + return; +} + +void +libusb20_tr_submit(struct libusb20_transfer *xfer) +{ + if (xfer->is_pending) { + /* should not happen */ + return; + } + xfer->is_pending = 1; /* we are pending */ + xfer->is_cancel = 0; /* not cancelling */ + xfer->is_restart = 0; /* not restarting */ + + (xfer->pdev->methods->tr_submit) (xfer); + return; +} + +void +libusb20_tr_start(struct libusb20_transfer *xfer) +{ + if (xfer->is_pending) { + if (xfer->is_cancel) { + /* cancelling - restart */ + xfer->is_restart = 1; + } + /* transfer not pending */ + return; + } + /* get into the callback */ + libusb20_tr_callback_wrapper(xfer); + return; +} + +/* USB device operations */ + +int +libusb20_dev_claim_interface(struct libusb20_device *pdev, uint8_t ifaceIndex) +{ + int error; + + if (ifaceIndex >= 32) { + error = LIBUSB20_ERROR_INVALID_PARAM; + } else if (pdev->claimed_interfaces & (1 << ifaceIndex)) { + error = LIBUSB20_ERROR_NOT_FOUND; + } else { + error = (pdev->methods->claim_interface) (pdev, ifaceIndex); + } + if (!error) { + pdev->claimed_interfaces |= (1 << ifaceIndex); + } + return (error); +} + +int +libusb20_dev_close(struct libusb20_device *pdev) +{ + struct libusb20_transfer *xfer; + uint16_t x; + int error = 0; + + if (!pdev->is_opened) { + return (LIBUSB20_ERROR_OTHER); + } + for (x = 0; x != pdev->nTransfer; x++) { + xfer = pdev->pTransfer + x; + + libusb20_tr_drain(xfer); + } + + if (pdev->pTransfer != NULL) { + free(pdev->pTransfer); + pdev->pTransfer = NULL; + } + error = (pdev->beMethods->close_device) (pdev); + + pdev->methods = &libusb20_dummy_methods; + + pdev->is_opened = 0; + + return (error); +} + +int +libusb20_dev_detach_kernel_driver(struct libusb20_device *pdev, uint8_t ifaceIndex) +{ + int error; + + error = (pdev->methods->detach_kernel_driver) (pdev, ifaceIndex); + return (error); +} + +struct LIBUSB20_DEVICE_DESC_DECODED * +libusb20_dev_get_device_desc(struct libusb20_device *pdev) +{ + return (&(pdev->ddesc)); +} + +int +libusb20_dev_get_fd(struct libusb20_device *pdev) +{ + return (pdev->file); +} + +int +libusb20_dev_kernel_driver_active(struct libusb20_device *pdev, uint8_t ifaceIndex) +{ + int error; + + error = (pdev->methods->kernel_driver_active) (pdev, ifaceIndex); + return (error); +} + +int +libusb20_dev_open(struct libusb20_device *pdev, uint16_t nTransferMax) +{ + struct libusb20_transfer *xfer; + uint32_t size; + uint16_t x; + int error; + + if (pdev->is_opened) { + return (LIBUSB20_ERROR_BUSY); + } + if (nTransferMax >= 256) { + return (LIBUSB20_ERROR_INVALID_PARAM); + } else if (nTransferMax != 0) { + size = sizeof(pdev->pTransfer[0]) * nTransferMax; + pdev->pTransfer = malloc(size); + if (pdev->pTransfer == NULL) { + return (LIBUSB20_ERROR_NO_MEM); + } + memset(pdev->pTransfer, 0, size); + } + /* initialise all transfers */ + for (x = 0; x != nTransferMax; x++) { + + xfer = pdev->pTransfer + x; + + xfer->pdev = pdev; + xfer->trIndex = x; + xfer->callback = &dummy_callback; + } + + error = (pdev->beMethods->open_device) (pdev, nTransferMax); + + if (error) { + if (pdev->pTransfer != NULL) { + free(pdev->pTransfer); + pdev->pTransfer = NULL; + } + pdev->file = -1; + pdev->file_ctrl = -1; + pdev->nTransfer = 0; + } else { + pdev->is_opened = 1; + pdev->nTransfer = nTransferMax; + } + return (error); +} + +int +libusb20_dev_release_interface(struct libusb20_device *pdev, uint8_t ifaceIndex) +{ + int error; + + if (ifaceIndex >= 32) { + error = LIBUSB20_ERROR_INVALID_PARAM; + } else if (!(pdev->claimed_interfaces & (1 << ifaceIndex))) { + error = LIBUSB20_ERROR_NOT_FOUND; + } else { + error = (pdev->methods->release_interface) (pdev, ifaceIndex); + } + if (!error) { + pdev->claimed_interfaces &= ~(1 << ifaceIndex); + } + return (error); +} + +int +libusb20_dev_reset(struct libusb20_device *pdev) +{ + int error; + + error = (pdev->methods->reset_device) (pdev); + return (error); +} + +int +libusb20_dev_set_power_mode(struct libusb20_device *pdev, uint8_t power_mode) +{ + int error; + + error = (pdev->methods->set_power_mode) (pdev, power_mode); + return (error); +} + +uint8_t +libusb20_dev_get_power_mode(struct libusb20_device *pdev) +{ + int error; + uint8_t power_mode; + + error = (pdev->methods->get_power_mode) (pdev, &power_mode); + if (error) + power_mode = LIBUSB20_POWER_ON; /* fake power mode */ + return (power_mode); +} + +int +libusb20_dev_set_alt_index(struct libusb20_device *pdev, uint8_t ifaceIndex, uint8_t altIndex) +{ + int error; + + error = (pdev->methods->set_alt_index) (pdev, ifaceIndex, altIndex); + return (error); +} + +int +libusb20_dev_set_config_index(struct libusb20_device *pdev, uint8_t configIndex) +{ + int error; + + error = (pdev->methods->set_config_index) (pdev, configIndex); + return (error); +} + +int +libusb20_dev_request_sync(struct libusb20_device *pdev, + struct LIBUSB20_CONTROL_SETUP_DECODED *setup, void *data, + uint16_t *pactlen, uint32_t timeout, uint8_t flags) +{ + int error; + + error = (pdev->methods->do_request_sync) (pdev, + setup, data, pactlen, timeout, flags); + return (error); +} + +int +libusb20_dev_req_string_sync(struct libusb20_device *pdev, + uint8_t index, uint16_t langid, void *ptr, uint16_t len) +{ + struct LIBUSB20_CONTROL_SETUP_DECODED req; + int error; + + if (len < 4) { + /* invalid length */ + return (LIBUSB20_ERROR_INVALID_PARAM); + } + LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &req); + + /* + * We need to read the USB string in two steps else some USB + * devices will complain. + */ + req.bmRequestType = + LIBUSB20_REQUEST_TYPE_STANDARD | + LIBUSB20_RECIPIENT_DEVICE | + LIBUSB20_ENDPOINT_IN; + req.bRequest = LIBUSB20_REQUEST_GET_DESCRIPTOR; + req.wValue = (LIBUSB20_DT_STRING << 8) | index; + req.wIndex = langid; + req.wLength = 4; /* bytes */ + + error = libusb20_dev_request_sync(pdev, &req, + ptr, NULL, 1000, LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK); + if (error) { + return (error); + } + req.wLength = *(uint8_t *)ptr; /* bytes */ + if (req.wLength > len) { + /* partial string read */ + req.wLength = len; + } + error = libusb20_dev_request_sync(pdev, &req, + ptr, NULL, 1000, LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK); + + if (error) { + return (error); + } + if (((uint8_t *)ptr)[1] != LIBUSB20_DT_STRING) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); /* success */ +} + +int +libusb20_dev_req_string_simple_sync(struct libusb20_device *pdev, + uint8_t index, void *ptr, uint16_t len) +{ + char *buf; + int error; + uint16_t langid; + uint16_t n; + uint16_t i; + uint16_t c; + uint8_t temp[255]; + uint8_t swap; + + /* the following code derives from the FreeBSD USB kernel */ + + if ((len < 1) || (ptr == NULL)) { + /* too short buffer */ + return (LIBUSB20_ERROR_INVALID_PARAM); + } + /* + * Make sure that there is sensible contents in the buffer in case + * of an error: + */ + *(uint8_t *)ptr = 0; + + error = libusb20_dev_req_string_sync(pdev, + 0, 0, temp, sizeof(temp)); + if (error < 0) + return (error); + + langid = temp[2] | (temp[3] << 8); + + error = libusb20_dev_req_string_sync(pdev, index, + langid, temp, sizeof(temp)); + if (error < 0) + return (error); + + if (temp[0] < 2) { + /* string length is too short */ + return (LIBUSB20_ERROR_OTHER); + } + /* reserve one byte for terminating zero */ + len--; + + /* find maximum length */ + n = (temp[0] / 2) - 1; + if (n > len) { + n = len; + } + /* reset swap state */ + swap = 3; + + /* setup output buffer pointer */ + buf = ptr; + + /* convert and filter */ + for (i = 0; (i != n); i++) { + c = temp[(2 * i) + 2] | (temp[(2 * i) + 3] << 8); + + /* convert from Unicode, handle buggy strings */ + if (((c & 0xff00) == 0) && (swap & 1)) { + /* Little Endian, default */ + *buf = c; + swap = 1; + } else if (((c & 0x00ff) == 0) && (swap & 2)) { + /* Big Endian */ + *buf = c >> 8; + swap = 2; + } else { + *buf = '.'; + } + /* + * Filter by default - we don't allow greater and less than + * signs because they might confuse the dmesg printouts! + */ + if ((*buf == '<') || (*buf == '>') || (!isprint(*buf))) { + *buf = '.'; + } + buf++; + } + *buf = 0; /* zero terminate string */ + + return (0); +} + +struct libusb20_config * +libusb20_dev_alloc_config(struct libusb20_device *pdev, uint8_t configIndex) +{ + struct libusb20_config *retval = NULL; + uint8_t *ptr; + uint16_t len; + uint8_t do_close; + int error; + + if (!pdev->is_opened) { + error = libusb20_dev_open(pdev, 0); + if (error) { + return (NULL); + } + do_close = 1; + } else { + do_close = 0; + } + error = (pdev->methods->get_config_desc_full) (pdev, + &ptr, &len, configIndex); + + if (error) { + goto done; + } + /* parse new config descriptor */ + retval = libusb20_parse_config_desc(ptr); + + /* free config descriptor */ + free(ptr); + +done: + if (do_close) { + error = libusb20_dev_close(pdev); + } + return (retval); +} + +struct libusb20_device * +libusb20_dev_alloc(void) +{ + struct libusb20_device *pdev; + + pdev = malloc(sizeof(*pdev)); + if (pdev == NULL) { + return (NULL); + } + memset(pdev, 0, sizeof(*pdev)); + + pdev->file = -1; + pdev->file_ctrl = -1; + pdev->methods = &libusb20_dummy_methods; + return (pdev); +} + +uint8_t +libusb20_dev_get_config_index(struct libusb20_device *pdev) +{ + int error; + uint8_t index; + uint8_t do_close; + + if (!pdev->is_opened) { + error = libusb20_dev_open(pdev, 0); + if (error == 0) { + do_close = 1; + } else { + do_close = 0; + } + } else { + do_close = 0; + } + + error = (pdev->methods->get_config_index) (pdev, &index); + if (error) { + index = 0 - 1; /* current config index */ + } + if (do_close) { + if (libusb20_dev_close(pdev)) { + /* ignore */ + } + } + return (index); +} + +uint8_t +libusb20_dev_get_mode(struct libusb20_device *pdev) +{ + return (pdev->usb_mode); +} + +uint8_t +libusb20_dev_get_speed(struct libusb20_device *pdev) +{ + return (pdev->usb_speed); +} + +/* if this function returns an error, the device is gone */ +int +libusb20_dev_process(struct libusb20_device *pdev) +{ + int error; + + error = (pdev->methods->process) (pdev); + return (error); +} + +void +libusb20_dev_wait_process(struct libusb20_device *pdev, int timeout) +{ + struct pollfd pfd[2]; + + if (!pdev->is_opened) { + return; + } + pfd[0].fd = pdev->file; + pfd[0].events = (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM); + pfd[0].revents = 0; + pfd[1].fd = 0; /* standard input */ + pfd[1].events = (POLLIN | POLLRDNORM); + pfd[1].revents = 0; + + if (poll(pfd, 2, timeout)) { + /* ignore any error */ + } + return; +} + +void +libusb20_dev_free(struct libusb20_device *pdev) +{ + if (pdev == NULL) { + /* be NULL safe */ + return; + } + if (pdev->is_opened) { + if (libusb20_dev_close(pdev)) { + /* ignore any errors */ + } + } + free(pdev); + return; +} + +const char * +libusb20_dev_get_backend_name(struct libusb20_device *pdev) +{ + return ((pdev->beMethods->get_backend_name) ()); +} + +const char * +libusb20_dev_get_desc(struct libusb20_device *pdev) +{ + return (pdev->usb_desc); +} + +void +libusb20_dev_set_debug(struct libusb20_device *pdev, int debug) +{ + pdev->debug = debug; + return; +} + +int +libusb20_dev_get_debug(struct libusb20_device *pdev) +{ + return (pdev->debug); +} + +uint8_t +libusb20_dev_get_address(struct libusb20_device *pdev) +{ + return (pdev->device_address); +} + +uint8_t +libusb20_dev_get_bus_number(struct libusb20_device *pdev) +{ + return (pdev->bus_number); +} + +int +libusb20_dev_set_owner(struct libusb20_device *pdev, uid_t user, gid_t group) +{ + return ((pdev->beMethods->dev_set_owner) (pdev, user, group)); +} + +int +libusb20_dev_set_perm(struct libusb20_device *pdev, mode_t mode) +{ + return ((pdev->beMethods->dev_set_perm) (pdev, mode)); +} + +int +libusb20_dev_set_iface_owner(struct libusb20_device *pdev, uint8_t iface_index, uid_t user, gid_t group) +{ + return ((pdev->beMethods->dev_set_iface_owner) (pdev, iface_index, user, group)); +} + +int +libusb20_dev_set_iface_perm(struct libusb20_device *pdev, uint8_t iface_index, mode_t mode) +{ + return ((pdev->beMethods->dev_set_iface_perm) (pdev, iface_index, mode)); +} + +int +libusb20_dev_get_owner(struct libusb20_device *pdev, uid_t *user, gid_t *group) +{ + uid_t a; + gid_t b; + + if (user == NULL) + user = &a; + if (group == NULL) + group = &b; + + return ((pdev->beMethods->dev_get_owner) (pdev, user, group)); +} + +int +libusb20_dev_get_perm(struct libusb20_device *pdev, mode_t *mode) +{ + mode_t a; + + if (mode == NULL) + mode = &a; + return ((pdev->beMethods->dev_get_perm) (pdev, mode)); +} + +int +libusb20_dev_get_iface_owner(struct libusb20_device *pdev, uint8_t iface_index, uid_t *user, gid_t *group) +{ + uid_t a; + gid_t b; + + if (user == NULL) + user = &a; + if (group == NULL) + group = &b; + + return ((pdev->beMethods->dev_get_iface_owner) (pdev, iface_index, user, group)); +} + +int +libusb20_dev_get_iface_perm(struct libusb20_device *pdev, uint8_t iface_index, mode_t *mode) +{ + mode_t a; + + if (mode == NULL) + mode = &a; + return ((pdev->beMethods->dev_get_iface_perm) (pdev, iface_index, mode)); +} + +/* USB bus operations */ + +int +libusb20_bus_set_owner(struct libusb20_backend *pbe, uint8_t bus, uid_t user, gid_t group) +{ + return ((pbe->methods->bus_set_owner) (pbe, bus, user, group)); +} + +int +libusb20_bus_set_perm(struct libusb20_backend *pbe, uint8_t bus, mode_t mode) +{ + return ((pbe->methods->bus_set_perm) (pbe, bus, mode)); +} + +int +libusb20_bus_get_owner(struct libusb20_backend *pbe, uint8_t bus, uid_t *user, gid_t *group) +{ + uid_t a; + gid_t b; + + if (user == NULL) + user = &a; + if (group == NULL) + group = &b; + return ((pbe->methods->bus_get_owner) (pbe, bus, user, group)); +} + +int +libusb20_bus_get_perm(struct libusb20_backend *pbe, uint8_t bus, mode_t *mode) +{ + mode_t a; + + if (mode == NULL) + mode = &a; + return ((pbe->methods->bus_get_perm) (pbe, bus, mode)); +} + +/* USB backend operations */ + +int +libusb20_be_get_dev_quirk(struct libusb20_backend *pbe, + uint16_t index, struct libusb20_quirk *pq) +{ + return ((pbe->methods->root_get_dev_quirk) (pbe, index, pq)); +} + +int +libusb20_be_get_quirk_name(struct libusb20_backend *pbe, + uint16_t index, struct libusb20_quirk *pq) +{ + return ((pbe->methods->root_get_quirk_name) (pbe, index, pq)); +} + +int +libusb20_be_add_dev_quirk(struct libusb20_backend *pbe, + struct libusb20_quirk *pq) +{ + return ((pbe->methods->root_add_dev_quirk) (pbe, pq)); +} + +int +libusb20_be_remove_dev_quirk(struct libusb20_backend *pbe, + struct libusb20_quirk *pq) +{ + return ((pbe->methods->root_remove_dev_quirk) (pbe, pq)); +} + +int +libusb20_be_set_owner(struct libusb20_backend *pbe, uid_t user, gid_t group) +{ + return ((pbe->methods->root_set_owner) (pbe, user, group)); +} + +int +libusb20_be_set_perm(struct libusb20_backend *pbe, mode_t mode) +{ + return ((pbe->methods->root_set_perm) (pbe, mode)); +} + +int +libusb20_be_get_owner(struct libusb20_backend *pbe, uid_t *user, gid_t *group) +{ + uid_t a; + gid_t b; + + if (user == NULL) + user = &a; + if (group == NULL) + group = &b; + return ((pbe->methods->root_get_owner) (pbe, user, group)); +} + +int +libusb20_be_get_perm(struct libusb20_backend *pbe, mode_t *mode) +{ + mode_t a; + + if (mode == NULL) + mode = &a; + return ((pbe->methods->root_get_perm) (pbe, mode)); +} + +struct libusb20_device * +libusb20_be_device_foreach(struct libusb20_backend *pbe, struct libusb20_device *pdev) +{ + if (pbe == NULL) { + pdev = NULL; + } else if (pdev == NULL) { + pdev = TAILQ_FIRST(&(pbe->usb_devs)); + } else { + pdev = TAILQ_NEXT(pdev, dev_entry); + } + return (pdev); +} + +struct libusb20_backend * +libusb20_be_alloc(const struct libusb20_backend_methods *methods) +{ + struct libusb20_backend *pbe; + + pbe = malloc(sizeof(*pbe)); + if (pbe == NULL) { + return (NULL); + } + memset(pbe, 0, sizeof(*pbe)); + + TAILQ_INIT(&(pbe->usb_devs)); + + pbe->methods = methods; /* set backend methods */ + + /* do the initial device scan */ + if (pbe->methods->init_backend) { + (pbe->methods->init_backend) (pbe); + } + return (pbe); +} + +struct libusb20_backend * +libusb20_be_alloc_linux(void) +{ + struct libusb20_backend *pbe; + +#ifdef __linux__ + pbe = libusb20_be_alloc(&libusb20_linux_backend); +#else + pbe = NULL; +#endif + return (pbe); +} + +struct libusb20_backend * +libusb20_be_alloc_ugen20(void) +{ + struct libusb20_backend *pbe; + +#ifdef __FreeBSD__ + pbe = libusb20_be_alloc(&libusb20_ugen20_backend); +#else + pbe = NULL; +#endif + return (pbe); +} + +struct libusb20_backend * +libusb20_be_alloc_default(void) +{ + struct libusb20_backend *pbe; + + pbe = libusb20_be_alloc_linux(); + if (pbe) { + return (pbe); + } + pbe = libusb20_be_alloc_ugen20(); + if (pbe) { + return (pbe); + } + return (NULL); /* no backend found */ +} + +void +libusb20_be_free(struct libusb20_backend *pbe) +{ + struct libusb20_device *pdev; + + if (pbe == NULL) { + /* be NULL safe */ + return; + } + while ((pdev = libusb20_be_device_foreach(pbe, NULL))) { + libusb20_be_dequeue_device(pbe, pdev); + libusb20_dev_free(pdev); + } + if (pbe->methods->exit_backend) { + (pbe->methods->exit_backend) (pbe); + } + return; +} + +void +libusb20_be_enqueue_device(struct libusb20_backend *pbe, struct libusb20_device *pdev) +{ + pdev->beMethods = pbe->methods; /* copy backend methods */ + TAILQ_INSERT_TAIL(&(pbe->usb_devs), pdev, dev_entry); + return; +} + +void +libusb20_be_dequeue_device(struct libusb20_backend *pbe, + struct libusb20_device *pdev) +{ + TAILQ_REMOVE(&(pbe->usb_devs), pdev, dev_entry); + return; +} diff --git a/lib/libusb20/libusb20.h b/lib/libusb20/libusb20.h new file mode 100644 index 000000000000..7fb8e68bae67 --- /dev/null +++ b/lib/libusb20/libusb20.h @@ -0,0 +1,313 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2007-2008 Daniel Drake. All rights reserved. + * Copyright (c) 2001 Johannes Erdfelt. 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. + */ + +#ifndef _LIBUSB20_H_ +#define _LIBUSB20_H_ + +#include +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +}; /* style */ + +#endif + +/** \ingroup misc + * Error codes. Most libusb20 functions return 0 on success or one of + * these codes on failure. + */ +enum libusb20_error { + /** Success (no error) */ + LIBUSB20_SUCCESS = 0, + + /** Input/output error */ + LIBUSB20_ERROR_IO = -1, + + /** Invalid parameter */ + LIBUSB20_ERROR_INVALID_PARAM = -2, + + /** Access denied (insufficient permissions) */ + LIBUSB20_ERROR_ACCESS = -3, + + /** No such device (it may have been disconnected) */ + LIBUSB20_ERROR_NO_DEVICE = -4, + + /** Entity not found */ + LIBUSB20_ERROR_NOT_FOUND = -5, + + /** Resource busy */ + LIBUSB20_ERROR_BUSY = -6, + + /** Operation timed out */ + LIBUSB20_ERROR_TIMEOUT = -7, + + /** Overflow */ + LIBUSB20_ERROR_OVERFLOW = -8, + + /** Pipe error */ + LIBUSB20_ERROR_PIPE = -9, + + /** System call interrupted (perhaps due to signal) */ + LIBUSB20_ERROR_INTERRUPTED = -10, + + /** Insufficient memory */ + LIBUSB20_ERROR_NO_MEM = -11, + + /** Operation not supported or unimplemented on this platform */ + LIBUSB20_ERROR_NOT_SUPPORTED = -12, + + /** Other error */ + LIBUSB20_ERROR_OTHER = -99, +}; + +/** \ingroup asyncio + * libusb20_tr_get_status() values */ +enum libusb20_transfer_status { + /** Transfer completed without error. Note that this does not + * indicate that the entire amount of requested data was + * transferred. */ + LIBUSB20_TRANSFER_COMPLETED, + + /** Callback code to start transfer */ + LIBUSB20_TRANSFER_START, + + /** Drain complete callback code */ + LIBUSB20_TRANSFER_DRAINED, + + /** Transfer failed */ + LIBUSB20_TRANSFER_ERROR, + + /** Transfer timed out */ + LIBUSB20_TRANSFER_TIMED_OUT, + + /** Transfer was cancelled */ + LIBUSB20_TRANSFER_CANCELLED, + + /** For bulk/interrupt endpoints: halt condition detected + * (endpoint stalled). For control endpoints: control request + * not supported. */ + LIBUSB20_TRANSFER_STALL, + + /** Device was disconnected */ + LIBUSB20_TRANSFER_NO_DEVICE, + + /** Device sent more data than requested */ + LIBUSB20_TRANSFER_OVERFLOW, +}; + +/** \ingroup asyncio + * libusb20_tr_set_flags() values */ +enum libusb20_transfer_flags { + /** Report a short frame as error */ + LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK = 0x0001, + + /** Multiple short frames are not allowed */ + LIBUSB20_TRANSFER_MULTI_SHORT_NOT_OK = 0x0002, + + /** All transmitted frames are short terminated */ + LIBUSB20_TRANSFER_FORCE_SHORT = 0x0004, + + /** Will do a clear-stall before xfer */ + LIBUSB20_TRANSFER_DO_CLEAR_STALL = 0x0008, +}; + +/** \ingroup misc + * libusb20_dev_get_mode() values + */ +enum libusb20_device_mode { + LIBUSB20_MODE_HOST, /* default */ + LIBUSB20_MODE_DEVICE, +}; + +/** \ingroup misc + * libusb20_dev_get_speed() values + */ +enum { + LIBUSB20_SPEED_UNKNOWN, /* default */ + LIBUSB20_SPEED_LOW, + LIBUSB20_SPEED_FULL, + LIBUSB20_SPEED_HIGH, + LIBUSB20_SPEED_VARIABLE, + LIBUSB20_SPEED_SUPER, +}; + +/** \ingroup misc + * libusb20_dev_set_power() values + */ +enum { + LIBUSB20_POWER_OFF, + LIBUSB20_POWER_ON, + LIBUSB20_POWER_SAVE, + LIBUSB20_POWER_SUSPEND, + LIBUSB20_POWER_RESUME, +}; + +struct libusb20_transfer; +struct libusb20_backend; +struct libusb20_backend_methods; +struct libusb20_device; +struct libusb20_device_methods; +struct libusb20_config; +struct LIBUSB20_CONTROL_SETUP_DECODED; +struct LIBUSB20_DEVICE_DESC_DECODED; + +typedef void (libusb20_tr_callback_t)(struct libusb20_transfer *xfer); + +struct libusb20_quirk { + uint16_t vid; /* vendor ID */ + uint16_t pid; /* product ID */ + uint16_t bcdDeviceLow; /* low revision value, inclusive */ + uint16_t bcdDeviceHigh; /* high revision value, inclusive */ + uint16_t reserved[2]; /* for the future */ + /* quirk name, UQ_XXX, including terminating zero */ + char quirkname[64 - 12]; +}; + +/* USB transfer operations */ + +int libusb20_tr_close(struct libusb20_transfer *xfer); +int libusb20_tr_open(struct libusb20_transfer *xfer, uint32_t pMaxBufSize, uint32_t MaxFrameCount, uint8_t ep_no); +struct libusb20_transfer *libusb20_tr_get_pointer(struct libusb20_device *pdev, uint16_t tr_index); +uint16_t libusb20_tr_get_time_complete(struct libusb20_transfer *xfer); +uint32_t libusb20_tr_get_actual_frames(struct libusb20_transfer *xfer); +uint32_t libusb20_tr_get_actual_length(struct libusb20_transfer *xfer); +uint32_t libusb20_tr_get_max_frames(struct libusb20_transfer *xfer); +uint32_t libusb20_tr_get_max_packet_length(struct libusb20_transfer *xfer); +uint32_t libusb20_tr_get_max_total_length(struct libusb20_transfer *xfer); +uint8_t libusb20_tr_get_status(struct libusb20_transfer *xfer); +uint8_t libusb20_tr_pending(struct libusb20_transfer *xfer); +void libusb20_tr_callback_wrapper(struct libusb20_transfer *xfer); +void libusb20_tr_clear_stall_sync(struct libusb20_transfer *xfer); +void libusb20_tr_drain(struct libusb20_transfer *xfer); +void libusb20_tr_set_buffer(struct libusb20_transfer *xfer, void *buffer, uint16_t fr_index); +void libusb20_tr_set_callback(struct libusb20_transfer *xfer, libusb20_tr_callback_t *cb); +void libusb20_tr_set_flags(struct libusb20_transfer *xfer, uint8_t flags); +void libusb20_tr_set_length(struct libusb20_transfer *xfer, uint32_t length, uint16_t fr_index); +void libusb20_tr_set_priv_sc0(struct libusb20_transfer *xfer, void *sc0); +void libusb20_tr_set_priv_sc1(struct libusb20_transfer *xfer, void *sc1); +void libusb20_tr_set_timeout(struct libusb20_transfer *xfer, uint32_t timeout); +void libusb20_tr_set_total_frames(struct libusb20_transfer *xfer, uint32_t nFrames); +void libusb20_tr_setup_bulk(struct libusb20_transfer *xfer, void *pbuf, uint32_t length, uint32_t timeout); +void libusb20_tr_setup_control(struct libusb20_transfer *xfer, void *psetup, void *pbuf, uint32_t timeout); +void libusb20_tr_setup_intr(struct libusb20_transfer *xfer, void *pbuf, uint32_t length, uint32_t timeout); +void libusb20_tr_setup_isoc(struct libusb20_transfer *xfer, void *pbuf, uint32_t length, uint16_t fr_index); +void libusb20_tr_start(struct libusb20_transfer *xfer); +void libusb20_tr_stop(struct libusb20_transfer *xfer); +void libusb20_tr_submit(struct libusb20_transfer *xfer); +void *libusb20_tr_get_priv_sc0(struct libusb20_transfer *xfer); +void *libusb20_tr_get_priv_sc1(struct libusb20_transfer *xfer); + + +/* USB device operations */ + +const char *libusb20_dev_get_backend_name(struct libusb20_device *pdev); +const char *libusb20_dev_get_desc(struct libusb20_device *pdev); +int libusb20_dev_claim_interface(struct libusb20_device *pdev, uint8_t iface_index); +int libusb20_dev_close(struct libusb20_device *pdev); +int libusb20_dev_detach_kernel_driver(struct libusb20_device *pdev, uint8_t iface_index); +int libusb20_dev_set_config_index(struct libusb20_device *pdev, uint8_t configIndex); +int libusb20_dev_get_debug(struct libusb20_device *pdev); +int libusb20_dev_get_fd(struct libusb20_device *pdev); +int libusb20_dev_kernel_driver_active(struct libusb20_device *pdev, uint8_t iface_index); +int libusb20_dev_open(struct libusb20_device *pdev, uint16_t transfer_max); +int libusb20_dev_process(struct libusb20_device *pdev); +int libusb20_dev_release_interface(struct libusb20_device *pdev, uint8_t iface_index); +int libusb20_dev_request_sync(struct libusb20_device *pdev, struct LIBUSB20_CONTROL_SETUP_DECODED *setup, void *data, uint16_t *pactlen, uint32_t timeout, uint8_t flags); +int libusb20_dev_req_string_sync(struct libusb20_device *pdev, uint8_t index, uint16_t langid, void *ptr, uint16_t len); +int libusb20_dev_req_string_simple_sync(struct libusb20_device *pdev, uint8_t index, void *ptr, uint16_t len); +int libusb20_dev_reset(struct libusb20_device *pdev); +int libusb20_dev_set_power_mode(struct libusb20_device *pdev, uint8_t power_mode); +uint8_t libusb20_dev_get_power_mode(struct libusb20_device *pdev); +int libusb20_dev_set_alt_index(struct libusb20_device *pdev, uint8_t iface_index, uint8_t alt_index); +int libusb20_dev_set_owner(struct libusb20_device *pdev, uid_t user, gid_t group); +int libusb20_dev_set_perm(struct libusb20_device *pdev, mode_t mode); +int libusb20_dev_set_iface_owner(struct libusb20_device *pdev, uint8_t iface_index, uid_t user, gid_t group); +int libusb20_dev_set_iface_perm(struct libusb20_device *pdev, uint8_t iface_index, mode_t mode); +int libusb20_dev_get_owner(struct libusb20_device *pdev, uid_t *user, gid_t *group); +int libusb20_dev_get_perm(struct libusb20_device *pdev, mode_t *mode); +int libusb20_dev_get_iface_owner(struct libusb20_device *pdev, uint8_t iface_index, uid_t *user, gid_t *group); +int libusb20_dev_get_iface_perm(struct libusb20_device *pdev, uint8_t iface_index, mode_t *mode); + +struct LIBUSB20_DEVICE_DESC_DECODED *libusb20_dev_get_device_desc(struct libusb20_device *pdev); +struct libusb20_config *libusb20_dev_alloc_config(struct libusb20_device *pdev, uint8_t config_index); +struct libusb20_device *libusb20_dev_alloc(void); +uint8_t libusb20_dev_get_address(struct libusb20_device *pdev); +uint8_t libusb20_dev_get_bus_number(struct libusb20_device *pdev); +uint8_t libusb20_dev_get_mode(struct libusb20_device *pdev); +uint8_t libusb20_dev_get_speed(struct libusb20_device *pdev); +uint8_t libusb20_dev_get_config_index(struct libusb20_device *pdev); +void libusb20_dev_free(struct libusb20_device *pdev); +void libusb20_dev_set_debug(struct libusb20_device *pdev, int debug); +void libusb20_dev_wait_process(struct libusb20_device *pdev, int timeout); + +/* USB bus operations */ + +int libusb20_bus_set_owner(struct libusb20_backend *pbe, uint8_t bus, uid_t user, gid_t group); +int libusb20_bus_set_perm(struct libusb20_backend *pbe, uint8_t bus, mode_t mode); +int libusb20_bus_get_owner(struct libusb20_backend *pbe, uint8_t bus, uid_t *user, gid_t *group); +int libusb20_bus_get_perm(struct libusb20_backend *pbe, uint8_t bus, mode_t *mode); + +/* USB global operations */ + +int libusb20_be_get_dev_quirk(struct libusb20_backend *pbe, uint16_t index, struct libusb20_quirk *pq); +int libusb20_be_get_quirk_name(struct libusb20_backend *pbe, uint16_t index, struct libusb20_quirk *pq); +int libusb20_be_add_dev_quirk(struct libusb20_backend *pbe, struct libusb20_quirk *pq); +int libusb20_be_remove_dev_quirk(struct libusb20_backend *pbe, struct libusb20_quirk *pq); +int libusb20_be_set_owner(struct libusb20_backend *be, uid_t user, gid_t group); +int libusb20_be_set_perm(struct libusb20_backend *be, mode_t mode); +int libusb20_be_get_owner(struct libusb20_backend *be, uid_t *user, gid_t *group); +int libusb20_be_get_perm(struct libusb20_backend *be, mode_t *mode); + +/* USB backend operations */ + +struct libusb20_backend *libusb20_be_alloc(const struct libusb20_backend_methods *methods); +struct libusb20_backend *libusb20_be_alloc_default(void); +struct libusb20_backend *libusb20_be_alloc_freebsd(void); +struct libusb20_backend *libusb20_be_alloc_linux(void); +struct libusb20_device *libusb20_be_device_foreach(struct libusb20_backend *pbe, struct libusb20_device *pdev); +void libusb20_be_dequeue_device(struct libusb20_backend *pbe, struct libusb20_device *pdev); +void libusb20_be_enqueue_device(struct libusb20_backend *pbe, struct libusb20_device *pdev); +void libusb20_be_free(struct libusb20_backend *pbe); + +#if 0 +{ /* style */ +#endif +#ifdef __cplusplus +} + +#endif + +#endif /* _LIBUSB20_H_ */ diff --git a/lib/libusb20/libusb20_compat01.c b/lib/libusb20/libusb20_compat01.c new file mode 100644 index 000000000000..e9d9ebe12a16 --- /dev/null +++ b/lib/libusb20/libusb20_compat01.c @@ -0,0 +1,902 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * This file contains the emulation layer for LibUSB v0.1 from sourceforge. + */ + +#include + +#include +#include + +#include "libusb20.h" +#include "libusb20_desc.h" +#include "libusb20_int.h" +#include "libusb20_compat01.h" + +/* + * The two following macros were taken from the original LibUSB v0.1 + * for sake of compatibility: + */ +#define LIST_ADD(begin, ent) \ + do { \ + if (begin) { \ + ent->next = begin; \ + ent->next->prev = ent; \ + } else { \ + ent->next = NULL; \ + } \ + ent->prev = NULL; \ + begin = ent; \ + } while(0) + +#define LIST_DEL(begin, ent) \ + do { \ + if (ent->prev) { \ + ent->prev->next = ent->next; \ + } else { \ + begin = ent->next; \ + } \ + if (ent->next) { \ + ent->next->prev = ent->prev; \ + } \ + ent->prev = NULL; \ + ent->next = NULL; \ + } while (0) + +struct usb_bus *usb_busses = NULL; + +static struct usb_bus usb_global_bus = { + .dirname = {"/dev/usb"}, + .root_dev = NULL, + .devices = NULL, +}; + +static struct libusb20_backend *usb_backend = NULL; + +struct usb_parse_state { + + struct { + struct libusb20_endpoint *currep; + struct libusb20_interface *currifc; + struct libusb20_config *currcfg; + struct libusb20_me_struct *currextra; + } a; + + struct { + struct usb_config_descriptor *currcfg; + struct usb_interface_descriptor *currifc; + struct usb_endpoint_descriptor *currep; + struct usb_interface *currifcw; + uint8_t *currextra; + } b; + + uint8_t preparse; +}; + +static uint8_t +usb_get_first_claimed_interface(usb_dev_handle * dev) +{ + struct libusb20_device *pdev = (void *)dev; + uint32_t x; + uint8_t y; + + x = pdev->claimed_interfaces; + + for (y = 0; y != 32; y++) { + if (x & (1 << y)) + break; + } + + if (y == 32) + y = 0xFF; /* dummy */ + + return (y); +} + +static struct libusb20_transfer * +usb_get_transfer_by_ep_no(usb_dev_handle * dev, uint8_t ep_no) +{ + struct libusb20_device *pdev = (void *)dev; + struct libusb20_transfer *xfer; + int err; + uint32_t bufsize; + uint8_t x; + uint8_t speed; + + x = (ep_no & LIBUSB20_ENDPOINT_ADDRESS_MASK) * 2; + + if (ep_no & LIBUSB20_ENDPOINT_DIR_MASK) { + /* this is a IN endpoint */ + x |= 1; + } + speed = libusb20_dev_get_speed(pdev); + + /* select a sensible buffer size */ + if (speed == LIBUSB20_SPEED_LOW) { + bufsize = 256; + } else if (speed == LIBUSB20_SPEED_FULL) { + bufsize = 4096; + } else { + bufsize = 16384; + } + + xfer = libusb20_tr_get_pointer(pdev, x); + + if (xfer == NULL) + return (xfer); + + err = libusb20_tr_open(xfer, bufsize, 1, ep_no); + if (err == LIBUSB20_ERROR_BUSY) { + /* already opened */ + return (xfer); + } else if (err) { + return (NULL); + } + /* success */ + return (xfer); +} + +usb_dev_handle * +usb_open(struct usb_device *dev) +{ + int err; + + err = libusb20_dev_open(dev->dev, 16 * 2); + if (err == LIBUSB20_ERROR_BUSY) { + /* + * Workaround buggy USB applications which open the USB + * device multiple times: + */ + return (dev->dev); + } + if (err) + return (NULL); + + return (dev->dev); +} + +int +usb_close(usb_dev_handle * dev) +{ + int err; + + err = libusb20_dev_close((void *)dev); + + if (err) + return (-1); + + return (0); +} + +int +usb_get_string(usb_dev_handle * dev, int index, + int langid, char *buf, size_t buflen) +{ + int err; + + err = libusb20_dev_req_string_sync((void *)dev, + index, langid, buf, buflen); + + if (err) + return (-1); + + return (0); +} + +int +usb_get_string_simple(usb_dev_handle * dev, int index, + char *buf, size_t buflen) +{ + int err; + + err = libusb20_dev_req_string_simple_sync((void *)dev, + index, buf, buflen); + + if (err) + return (-1); + + return (strlen(buf)); +} + +int +usb_get_descriptor_by_endpoint(usb_dev_handle * udev, int ep, uint8_t type, + uint8_t index, void *buf, int size) +{ + memset(buf, 0, size); + + return (usb_control_msg(udev, ep | USB_ENDPOINT_IN, + USB_REQ_GET_DESCRIPTOR, (type << 8) + index, 0, + buf, size, 1000)); +} + +int +usb_get_descriptor(usb_dev_handle * udev, uint8_t type, uint8_t index, + void *buf, int size) +{ + memset(buf, 0, size); + + return (usb_control_msg(udev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, + (type << 8) + index, 0, buf, size, 1000)); +} + +int +usb_parse_descriptor(uint8_t *source, char *description, void *dest) +{ + uint8_t *sp = source; + uint8_t *dp = dest; + uint16_t w; + uint32_t d; + char *cp; + + for (cp = description; *cp; cp++) { + switch (*cp) { + case 'b': /* 8-bit byte */ + *dp++ = *sp++; + break; + /* + * 16-bit word, convert from little endian to CPU + */ + case 'w': + w = (sp[1] << 8) | sp[0]; + sp += 2; + /* Align to word boundary */ + dp += ((dp - (uint8_t *)0) & 1); + *((uint16_t *)dp) = w; + dp += 2; + break; + /* + * 32-bit dword, convert from little endian to CPU + */ + case 'd': + d = (sp[3] << 24) | (sp[2] << 16) | + (sp[1] << 8) | sp[0]; + sp += 4; + /* Align to word boundary */ + dp += ((dp - (uint8_t *)0) & 1); + /* Align to double word boundary */ + dp += ((dp - (uint8_t *)0) & 2); + *((uint32_t *)dp) = d; + dp += 4; + break; + } + } + return (sp - source); +} + +static void +usb_parse_extra(struct usb_parse_state *ps, uint8_t **pptr, int *plen) +{ + void *ptr; + uint16_t len; + + ptr = ps->a.currextra->ptr; + len = ps->a.currextra->len; + + if (ps->preparse == 0) { + memcpy(ps->b.currextra, ptr, len); + *pptr = ps->b.currextra; + *plen = len; + } + ps->b.currextra += len; + return; +} + +static void +usb_parse_endpoint(struct usb_parse_state *ps) +{ + struct usb_endpoint_descriptor *bep; + struct libusb20_endpoint *aep; + + aep = ps->a.currep; + bep = ps->b.currep++; + + if (ps->preparse == 0) { + /* copy descriptor fields */ + bep->bLength = aep->desc.bLength; + bep->bDescriptorType = aep->desc.bDescriptorType; + bep->bEndpointAddress = aep->desc.bEndpointAddress; + bep->bmAttributes = aep->desc.bmAttributes; + bep->wMaxPacketSize = aep->desc.wMaxPacketSize; + bep->bInterval = aep->desc.bInterval; + bep->bRefresh = aep->desc.bRefresh; + bep->bSynchAddress = aep->desc.bSynchAddress; + } + ps->a.currextra = &aep->extra; + usb_parse_extra(ps, &bep->extra, &bep->extralen); + return; +} + +static void +usb_parse_iface_sub(struct usb_parse_state *ps) +{ + struct libusb20_interface *aifc; + struct usb_interface_descriptor *bifc; + uint8_t x; + + aifc = ps->a.currifc; + bifc = ps->b.currifc++; + + if (ps->preparse == 0) { + /* copy descriptor fields */ + bifc->bLength = aifc->desc.bLength; + bifc->bDescriptorType = aifc->desc.bDescriptorType; + bifc->bInterfaceNumber = aifc->desc.bInterfaceNumber; + bifc->bAlternateSetting = aifc->desc.bAlternateSetting; + bifc->bNumEndpoints = aifc->num_endpoints; + bifc->bInterfaceClass = aifc->desc.bInterfaceClass; + bifc->bInterfaceSubClass = aifc->desc.bInterfaceSubClass; + bifc->bInterfaceProtocol = aifc->desc.bInterfaceProtocol; + bifc->iInterface = aifc->desc.iInterface; + bifc->endpoint = ps->b.currep; + } + for (x = 0; x != aifc->num_endpoints; x++) { + ps->a.currep = aifc->endpoints + x; + usb_parse_endpoint(ps); + } + + ps->a.currextra = &aifc->extra; + usb_parse_extra(ps, &bifc->extra, &bifc->extralen); + return; +} + +static void +usb_parse_iface(struct usb_parse_state *ps) +{ + struct libusb20_interface *aifc; + struct usb_interface *bifc; + uint8_t x; + + aifc = ps->a.currifc; + bifc = ps->b.currifcw++; + + if (ps->preparse == 0) { + /* initialise interface wrapper */ + bifc->altsetting = ps->b.currifc; + bifc->num_altsetting = aifc->num_altsetting + 1; + } + usb_parse_iface_sub(ps); + + for (x = 0; x != aifc->num_altsetting; x++) { + ps->a.currifc = aifc->altsetting + x; + usb_parse_iface_sub(ps); + } + return; +} + +static void +usb_parse_config(struct usb_parse_state *ps) +{ + struct libusb20_config *acfg; + struct usb_config_descriptor *bcfg; + uint8_t x; + + acfg = ps->a.currcfg; + bcfg = ps->b.currcfg; + + if (ps->preparse == 0) { + /* initialise config wrapper */ + bcfg->bLength = acfg->desc.bLength; + bcfg->bDescriptorType = acfg->desc.bDescriptorType; + bcfg->wTotalLength = acfg->desc.wTotalLength; + bcfg->bNumInterfaces = acfg->num_interface; + bcfg->bConfigurationValue = acfg->desc.bConfigurationValue; + bcfg->iConfiguration = acfg->desc.iConfiguration; + bcfg->bmAttributes = acfg->desc.bmAttributes; + bcfg->MaxPower = acfg->desc.bMaxPower; + bcfg->interface = ps->b.currifcw; + } + for (x = 0; x != acfg->num_interface; x++) { + ps->a.currifc = acfg->interface + x; + usb_parse_iface(ps); + } + + ps->a.currextra = &acfg->extra; + usb_parse_extra(ps, &bcfg->extra, &bcfg->extralen); + return; +} + +int +usb_parse_configuration(struct usb_config_descriptor *config, + uint8_t *buffer) +{ + struct usb_parse_state ps; + uint8_t *ptr; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + + if ((buffer == NULL) || (config == NULL)) { + return (-1); + } + memset(&ps, 0, sizeof(ps)); + + ps.a.currcfg = libusb20_parse_config_desc(buffer); + ps.b.currcfg = config; + if (ps.a.currcfg == NULL) { + /* could not parse config or out of memory */ + return (-1); + } + /* do the pre-parse */ + ps.preparse = 1; + usb_parse_config(&ps); + + a = ((uint8_t *)(ps.b.currifcw) - ((uint8_t *)0)); + b = ((uint8_t *)(ps.b.currifc) - ((uint8_t *)0)); + c = ((uint8_t *)(ps.b.currep) - ((uint8_t *)0)); + d = ((uint8_t *)(ps.b.currextra) - ((uint8_t *)0)); + + /* allocate memory for our configuration */ + ptr = malloc(a + b + c + d); + + /* "currifcw" must be first, hence this pointer is freed */ + ps.b.currifcw = (void *)(ptr); + ps.b.currifc = (void *)(ptr + a); + ps.b.currep = (void *)(ptr + a + b); + ps.b.currextra = (void *)(ptr + a + b + c); + + /* generate a libusb v0.1 compatible structure */ + ps.preparse = 0; + usb_parse_config(&ps); + + /* free config structure */ + free(ps.a.currcfg); + + return (0); /* success */ +} + +void +usb_destroy_configuration(struct usb_device *dev) +{ + uint8_t c; + + if (dev->config == NULL) { + return; + } + for (c = 0; c != dev->descriptor.bNumConfigurations; c++) { + struct usb_config_descriptor *cf = &dev->config[c]; + + if (cf->interface != NULL) { + free(cf->interface); + cf->interface = NULL; + } + } + + free(dev->config); + dev->config = NULL; + return; +} + +void +usb_fetch_and_parse_descriptors(usb_dev_handle * udev) +{ + struct usb_device *dev; + struct libusb20_device *pdev; + uint8_t *ptr; + int error; + uint32_t size; + uint16_t len; + uint8_t x; + + if (udev == NULL) { + /* be NULL safe */ + return; + } + dev = usb_device(udev); + pdev = (void *)udev; + + if (dev->descriptor.bNumConfigurations == 0) { + /* invalid device */ + return; + } + size = dev->descriptor.bNumConfigurations * + sizeof(struct usb_config_descriptor); + + dev->config = malloc(size); + if (dev->config == NULL) { + /* out of memory */ + return; + } + memset(dev->config, 0, size); + + for (x = 0; x != dev->descriptor.bNumConfigurations; x++) { + + error = (pdev->methods->get_config_desc_full) ( + pdev, &ptr, &len, x); + + if (error) { + usb_destroy_configuration(dev); + return; + } + usb_parse_configuration(dev->config + x, ptr); + + /* free config buffer */ + free(ptr); + } + return; +} + +static int +usb_std_io(usb_dev_handle * dev, int ep, char *bytes, int size, + int timeout, int is_intr) +{ + struct libusb20_transfer *xfer; + uint32_t temp; + uint32_t maxsize; + uint32_t actlen; + char *oldbytes; + + xfer = usb_get_transfer_by_ep_no(dev, ep); + if (xfer == NULL) + return (-1); + + if (libusb20_tr_pending(xfer)) { + /* there is already a transfer ongoing */ + return (-1); + } + maxsize = libusb20_tr_get_max_total_length(xfer); + oldbytes = bytes; + + /* + * We allow transferring zero bytes which is the same + * equivalent to a zero length USB packet. + */ + do { + + temp = size; + if (temp > maxsize) { + /* find maximum possible length */ + temp = maxsize; + } + if (is_intr) + libusb20_tr_setup_intr(xfer, bytes, temp, timeout); + else + libusb20_tr_setup_bulk(xfer, bytes, temp, timeout); + + libusb20_tr_start(xfer); + + while (1) { + + if (libusb20_dev_process((void *)dev) != 0) { + /* device detached */ + return (-1); + } + if (libusb20_tr_pending(xfer) == 0) { + /* transfer complete */ + break; + } + /* wait for USB event from kernel */ + libusb20_dev_wait_process((void *)dev, -1); + } + + if (libusb20_tr_get_status(xfer)) { + /* transfer error */ + return (-1); + } + actlen = libusb20_tr_get_actual_length(xfer); + + bytes += actlen; + size -= actlen; + + if (actlen != temp) { + /* short transfer */ + break; + } + } while (size > 0); + + return (bytes - oldbytes); +} + +int +usb_bulk_write(usb_dev_handle * dev, int ep, char *bytes, + int size, int timeout) +{ + return (usb_std_io(dev, ep, bytes, size, timeout, 0)); +} + +int +usb_bulk_read(usb_dev_handle * dev, int ep, char *bytes, + int size, int timeout) +{ + return (usb_std_io(dev, ep, bytes, size, timeout, 0)); +} + +int +usb_interrupt_write(usb_dev_handle * dev, int ep, char *bytes, + int size, int timeout) +{ + return (usb_std_io(dev, ep, bytes, size, timeout, 1)); +} + +int +usb_interrupt_read(usb_dev_handle * dev, int ep, char *bytes, + int size, int timeout) +{ + return (usb_std_io(dev, ep, bytes, size, timeout, 1)); +} + +int +usb_control_msg(usb_dev_handle * dev, int requesttype, int request, + int value, int index, char *bytes, int size, int timeout) +{ + struct LIBUSB20_CONTROL_SETUP_DECODED req; + int err; + uint16_t actlen; + + LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &req); + + req.bmRequestType = requesttype; + req.bRequest = request; + req.wValue = value; + req.wIndex = index; + req.wLength = size; + + err = libusb20_dev_request_sync((void *)dev, &req, bytes, + &actlen, timeout, 0); + + if (err) + return (-1); + + return (actlen); +} + +int +usb_set_configuration(usb_dev_handle * dev, int configuration) +{ + int err; + + err = libusb20_dev_set_config_index((void *)dev, configuration); + + if (err) + return (-1); + + return (0); +} + +int +usb_claim_interface(usb_dev_handle * dev, int interface) +{ + int err; + + err = libusb20_dev_claim_interface((void *)dev, interface); + + if (err) + return (-1); + + return (0); +} + +int +usb_release_interface(usb_dev_handle * dev, int interface) +{ + int err; + + err = libusb20_dev_release_interface((void *)dev, interface); + + if (err) + return (-1); + + return (0); +} + +int +usb_set_altinterface(usb_dev_handle * dev, int alternate) +{ + int err; + uint8_t iface; + + iface = usb_get_first_claimed_interface(dev); + + err = libusb20_dev_set_alt_index((void *)dev, iface, alternate); + + if (err) + return (-1); + + return (0); +} + +int +usb_resetep(usb_dev_handle * dev, unsigned int ep) +{ + /* emulate an endpoint reset through clear-STALL */ + return (usb_clear_halt(dev, ep)); +} + +int +usb_clear_halt(usb_dev_handle * dev, unsigned int ep) +{ + struct libusb20_transfer *xfer; + + xfer = usb_get_transfer_by_ep_no(dev, ep); + if (xfer == NULL) + return (-1); + + libusb20_tr_clear_stall_sync(xfer); + + return (0); +} + +int +usb_reset(usb_dev_handle * dev) +{ + int err; + + err = libusb20_dev_reset((void *)dev); + + if (err) + return (-1); + + return (0); +} + +char * +usb_strerror(void) +{ + /* TODO */ + return ("Unknown error"); +} + +void +usb_init(void) +{ + /* nothing to do */ + return; +} + +void +usb_set_debug(int level) +{ + /* use kernel UGEN debugging if you need to see what is going on */ + return; +} + +int +usb_find_busses(void) +{ + usb_busses = &usb_global_bus; + return (0); +} + +int +usb_find_devices(void) +{ + struct libusb20_device *pdev; + struct usb_device *udev; + struct LIBUSB20_DEVICE_DESC_DECODED *ddesc; + struct libusb20_backend *pold; + int err; + + /* cleanup after last device search */ + + pold = usb_backend; + + pdev = NULL; + while ((pdev = libusb20_be_device_foreach(pold, pdev))) { + if (!pdev->is_opened) { + /* + * if the device has not been opened we free the + * device data + */ + udev = pdev->priv01Data; + libusb20_be_dequeue_device(pold, pdev); + libusb20_dev_free(pdev); + if (udev != NULL) { + LIST_DEL(usb_global_bus.devices, udev); + free(udev); + } + pdev = NULL; /* restart search */ + } + } + + /* do a new backend device search */ + + usb_backend = libusb20_be_alloc_default(); + if (usb_backend == NULL) { + usb_backend = pold; /* restore */ + return (-1); + } + /* iterate all devices */ + + pdev = NULL; + while ((pdev = libusb20_be_device_foreach(usb_backend, pdev))) { + udev = malloc(sizeof(*udev)); + if (udev == NULL) + break; + + memset(udev, 0, sizeof(*udev)); + + udev->bus = &usb_global_bus; + + snprintf(udev->filename, sizeof(udev->filename), + "/dev/ugen%u.%u", + libusb20_dev_get_bus_number(pdev), + libusb20_dev_get_address(pdev)); + + ddesc = libusb20_dev_get_device_desc(pdev); + + udev->descriptor.bLength = sizeof(udev->descriptor); + udev->descriptor.bDescriptorType = ddesc->bDescriptorType; + udev->descriptor.bcdUSB = ddesc->bcdUSB; + udev->descriptor.bDeviceClass = ddesc->bDeviceClass; + udev->descriptor.bDeviceSubClass = ddesc->bDeviceSubClass; + udev->descriptor.bDeviceProtocol = ddesc->bDeviceProtocol; + udev->descriptor.bMaxPacketSize0 = ddesc->bMaxPacketSize0; + udev->descriptor.idVendor = ddesc->idVendor; + udev->descriptor.idProduct = ddesc->idProduct; + udev->descriptor.bcdDevice = ddesc->bcdDevice; + udev->descriptor.iManufacturer = ddesc->iManufacturer; + udev->descriptor.iProduct = ddesc->iProduct; + udev->descriptor.iSerialNumber = ddesc->iSerialNumber; + udev->descriptor.bNumConfigurations = + ddesc->bNumConfigurations; + if (udev->descriptor.bNumConfigurations > USB_MAXCONFIG) { + /* truncate number of configurations */ + udev->descriptor.bNumConfigurations = USB_MAXCONFIG; + } + /* link together the two structures */ + udev->dev = pdev; + pdev->priv01Data = udev; + + err = libusb20_dev_open(pdev, 0); + if (err == 0) { + /* XXX get all config descriptors by default */ + usb_fetch_and_parse_descriptors((void *)pdev); + libusb20_dev_close(pdev); + } + LIST_ADD(usb_global_bus.devices, udev); + } + + /* move old devices over to the new USB backend */ + + while ((pdev = libusb20_be_device_foreach(pold, pdev))) { + libusb20_be_dequeue_device(pold, pdev); + libusb20_be_enqueue_device(usb_backend, pdev); + } + + /* free old backend, if any */ + + libusb20_be_free(pold); + + return (0); /* success */ +} + +struct usb_device * +usb_device(usb_dev_handle * dev) +{ + struct libusb20_device *pdev; + + pdev = (void *)dev; + + return (pdev->priv01Data); +} + +struct usb_bus * +usb_get_busses(void) +{ + return (usb_busses); +} diff --git a/lib/libusb20/libusb20_compat01.h b/lib/libusb20/libusb20_compat01.h new file mode 100644 index 000000000000..333793d5e996 --- /dev/null +++ b/lib/libusb20/libusb20_compat01.h @@ -0,0 +1,310 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _LIBUSB20_COMPAT_01_H_ +#define _LIBUSB20_COMPAT_01_H_ + +#include +#include +#include +#include + +/* USB interface class codes */ + +#define USB_CLASS_PER_INTERFACE 0 +#define USB_CLASS_AUDIO 1 +#define USB_CLASS_COMM 2 +#define USB_CLASS_HID 3 +#define USB_CLASS_PRINTER 7 +#define USB_CLASS_PTP 6 +#define USB_CLASS_MASS_STORAGE 8 +#define USB_CLASS_HUB 9 +#define USB_CLASS_DATA 10 +#define USB_CLASS_VENDOR_SPEC 0xff + +/* USB descriptor types */ + +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 + +#define USB_DT_HID 0x21 +#define USB_DT_REPORT 0x22 +#define USB_DT_PHYSICAL 0x23 +#define USB_DT_HUB 0x29 + +/* USB descriptor type sizes */ + +#define USB_DT_DEVICE_SIZE 18 +#define USB_DT_CONFIG_SIZE 9 +#define USB_DT_INTERFACE_SIZE 9 +#define USB_DT_ENDPOINT_SIZE 7 +#define USB_DT_ENDPOINT_AUDIO_SIZE 9 +#define USB_DT_HUB_NONVAR_SIZE 7 + +/* USB descriptor header */ +struct usb_descriptor_header { + uint8_t bLength; + uint8_t bDescriptorType; +}; + +/* USB string descriptor */ +struct usb_string_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wData[1]; +}; + +/* USB HID descriptor */ +struct usb_hid_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHID; + uint8_t bCountryCode; + uint8_t bNumDescriptors; + /* uint8_t bReportDescriptorType; */ + /* uint16_t wDescriptorLength; */ + /* ... */ +}; + +/* USB endpoint descriptor */ +#define USB_MAXENDPOINTS 32 +struct usb_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; +#define USB_ENDPOINT_ADDRESS_MASK 0x0f +#define USB_ENDPOINT_DIR_MASK 0x80 + uint8_t bmAttributes; +#define USB_ENDPOINT_TYPE_MASK 0x03 +#define USB_ENDPOINT_TYPE_CONTROL 0 +#define USB_ENDPOINT_TYPE_ISOCHRONOUS 1 +#define USB_ENDPOINT_TYPE_BULK 2 +#define USB_ENDPOINT_TYPE_INTERRUPT 3 + uint16_t wMaxPacketSize; + uint8_t bInterval; + uint8_t bRefresh; + uint8_t bSynchAddress; + + uint8_t *extra; /* Extra descriptors */ + int extralen; +}; + +/* USB interface descriptor */ +#define USB_MAXINTERFACES 32 +struct usb_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + + struct usb_endpoint_descriptor *endpoint; + + uint8_t *extra; /* Extra descriptors */ + int extralen; +}; + +#define USB_MAXALTSETTING 128 /* Hard limit */ +struct usb_interface { + struct usb_interface_descriptor *altsetting; + + int num_altsetting; +}; + +/* USB configuration descriptor */ +#define USB_MAXCONFIG 8 +struct usb_config_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t MaxPower; + + struct usb_interface *interface; + + uint8_t *extra; /* Extra descriptors */ + int extralen; +}; + +/* USB device descriptor */ +struct usb_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +}; + +/* USB setup packet */ +struct usb_ctrl_setup { + uint8_t bRequestType; +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) +#define USB_ENDPOINT_IN 0x80 +#define USB_ENDPOINT_OUT 0x00 + uint8_t bRequest; +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +}; + +/* Error codes */ +#define USB_ERROR_BEGIN 500000 + +/* Byte swapping */ +#define USB_LE16_TO_CPU(x) le16toh(x) + +/* Data types */ +struct usb_device; +struct usb_bus; + +/* + * To maintain compatibility with applications already built with libusb, + * we must only add entries to the end of this structure. NEVER delete or + * move members and only change types if you really know what you're doing. + */ +struct usb_device { + struct usb_device *next; + struct usb_device *prev; + + char filename[PATH_MAX + 1]; + + struct usb_bus *bus; + + struct usb_device_descriptor descriptor; + struct usb_config_descriptor *config; + + void *dev; + + uint8_t devnum; + + uint8_t num_children; + struct usb_device **children; +}; + +struct usb_bus { + struct usb_bus *next; + struct usb_bus *prev; + + char dirname[PATH_MAX + 1]; + + struct usb_device *devices; + uint32_t location; + + struct usb_device *root_dev; +}; + +struct usb_dev_handle; +typedef struct usb_dev_handle usb_dev_handle; + +/* Variables */ +extern struct usb_bus *usb_busses; + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +} /* style */ + +#endif + +/* Function prototypes from "libusb20_compat01.c" */ + +usb_dev_handle *usb_open(struct usb_device *dev); +int usb_close(usb_dev_handle * dev); +int usb_get_string(usb_dev_handle * dev, int index, int langid, char *buf, size_t buflen); +int usb_get_string_simple(usb_dev_handle * dev, int index, char *buf, size_t buflen); +int usb_get_descriptor_by_endpoint(usb_dev_handle * udev, int ep, uint8_t type, uint8_t index, void *buf, int size); +int usb_get_descriptor(usb_dev_handle * udev, uint8_t type, uint8_t index, void *buf, int size); +int usb_parse_descriptor(uint8_t *source, char *description, void *dest); +int usb_parse_configuration(struct usb_config_descriptor *config, uint8_t *buffer); +void usb_destroy_configuration(struct usb_device *dev); +void usb_fetch_and_parse_descriptors(usb_dev_handle * udev); +int usb_bulk_write(usb_dev_handle * dev, int ep, char *bytes, int size, int timeout); +int usb_bulk_read(usb_dev_handle * dev, int ep, char *bytes, int size, int timeout); +int usb_interrupt_write(usb_dev_handle * dev, int ep, char *bytes, int size, int timeout); +int usb_interrupt_read(usb_dev_handle * dev, int ep, char *bytes, int size, int timeout); +int usb_control_msg(usb_dev_handle * dev, int requesttype, int request, int value, int index, char *bytes, int size, int timeout); +int usb_set_configuration(usb_dev_handle * dev, int configuration); +int usb_claim_interface(usb_dev_handle * dev, int interface); +int usb_release_interface(usb_dev_handle * dev, int interface); +int usb_set_altinterface(usb_dev_handle * dev, int alternate); +int usb_resetep(usb_dev_handle * dev, unsigned int ep); +int usb_clear_halt(usb_dev_handle * dev, unsigned int ep); +int usb_reset(usb_dev_handle * dev); +char *usb_strerror(void); +void usb_init(void); +void usb_set_debug(int level); +int usb_find_busses(void); +int usb_find_devices(void); +struct usb_device *usb_device(usb_dev_handle * dev); +struct usb_bus *usb_get_busses(void); + +#if 0 +{ /* style */ +#endif +#ifdef __cplusplus +} + +#endif + +#endif /* _LIBUSB20_COMPAT01_H_ */ diff --git a/lib/libusb20/libusb20_compat10.c b/lib/libusb20/libusb20_compat10.c new file mode 100644 index 000000000000..36244850fbda --- /dev/null +++ b/lib/libusb20/libusb20_compat10.c @@ -0,0 +1,29 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * This file contains the emulation layer for LibUSB v1.0 from sourceforge. + */ diff --git a/lib/libusb20/libusb20_compat10.h b/lib/libusb20/libusb20_compat10.h new file mode 100644 index 000000000000..d98895fa25e8 --- /dev/null +++ b/lib/libusb20/libusb20_compat10.h @@ -0,0 +1,25 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ diff --git a/lib/libusb20/libusb20_desc.c b/lib/libusb20/libusb20_desc.c new file mode 100644 index 000000000000..fbbd96fc980f --- /dev/null +++ b/lib/libusb20/libusb20_desc.c @@ -0,0 +1,771 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include + +#include "libusb20.h" +#include "libusb20_desc.h" +#include "libusb20_int.h" + +static const uint32_t libusb20_me_encode_empty[2]; /* dummy */ + +LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_DEVICE_DESC); +LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_ENDPOINT_DESC); +LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_INTERFACE_DESC); +LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_CONFIG_DESC); +LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_CONTROL_SETUP); + +/*------------------------------------------------------------------------* + * libusb20_parse_config_desc + * + * Return values: + * NULL: Out of memory. + * Else: A valid config structure pointer which must be passed to "free()" + *------------------------------------------------------------------------*/ +struct libusb20_config * +libusb20_parse_config_desc(const void *config_desc) +{ + struct libusb20_config *lub_config; + struct libusb20_interface *lub_interface; + struct libusb20_interface *lub_alt_interface; + struct libusb20_interface *last_if; + struct libusb20_endpoint *lub_endpoint; + struct libusb20_endpoint *last_ep; + + struct libusb20_me_struct pcdesc; + const uint8_t *ptr; + uint32_t size; + uint16_t niface_no_alt; + uint16_t niface; + uint16_t nendpoint; + uint8_t iface_no; + + ptr = config_desc; + if (ptr[1] != LIBUSB20_DT_CONFIG) { + return (NULL); /* not config descriptor */ + } + /* + * The first "bInterfaceNumber" should never have the value 0xff. + * Then it is corrupt. + */ + niface_no_alt = 0; + nendpoint = 0; + niface = 0; + iface_no = 0 - 1; + ptr = NULL; + + /* get "wTotalLength" and setup "pcdesc" */ + pcdesc.ptr = LIBUSB20_ADD_BYTES(config_desc, 0); + pcdesc.len = + ((uint8_t *)config_desc)[2] | + (((uint8_t *)config_desc)[3] << 8); + pcdesc.type = LIBUSB20_ME_IS_RAW; + + /* descriptor pre-scan */ + while ((ptr = libusb20_desc_foreach(&pcdesc, ptr))) { + if (ptr[1] == LIBUSB20_DT_ENDPOINT) { + nendpoint++; + } else if ((ptr[1] == LIBUSB20_DT_INTERFACE) && (ptr[0] >= 4)) { + niface++; + /* check "bInterfaceNumber" */ + if (ptr[2] != iface_no) { + iface_no = ptr[2]; + niface_no_alt++; + } + } + } + + /* sanity checking */ + if (niface >= 256) { + return (NULL); /* corrupt */ + } + if (nendpoint >= 256) { + return (NULL); /* corrupt */ + } + size = sizeof(*lub_config) + + (niface * sizeof(*lub_interface)) + + (nendpoint * sizeof(*lub_endpoint)) + + pcdesc.len; + + lub_config = malloc(size); + if (lub_config == NULL) { + return (NULL); /* out of memory */ + } + lub_interface = (void *)(lub_config + 1); + lub_alt_interface = (void *)(lub_interface + niface_no_alt); + lub_endpoint = (void *)(lub_interface + niface); + + /* + * Make a copy of the config descriptor, so that the caller can free + * the inital config descriptor pointer! + */ + ptr = (void *)(lub_endpoint + nendpoint); + memcpy(LIBUSB20_ADD_BYTES(ptr, 0), config_desc, pcdesc.len); + pcdesc.ptr = LIBUSB20_ADD_BYTES(ptr, 0); + config_desc = LIBUSB20_ADD_BYTES(ptr, 0); + + /* init config structure */ + + ptr = config_desc; + + LIBUSB20_INIT(LIBUSB20_CONFIG_DESC, &lub_config->desc); + + if (libusb20_me_decode(ptr, ptr[0], &lub_config->desc)) { + /* ignore */ + } + lub_config->num_interface = 0; + lub_config->interface = lub_interface; + lub_config->extra.ptr = LIBUSB20_ADD_BYTES(ptr, ptr[0]); + lub_config->extra.len = -ptr[0]; + lub_config->extra.type = LIBUSB20_ME_IS_RAW; + + /* reset states */ + niface = 0; + iface_no = 0 - 1; + ptr = NULL; + lub_interface--; + lub_endpoint--; + last_if = NULL; + last_ep = NULL; + + /* descriptor pre-scan */ + while ((ptr = libusb20_desc_foreach(&pcdesc, ptr))) { + if (ptr[1] == LIBUSB20_DT_ENDPOINT) { + if (last_if) { + lub_endpoint++; + last_ep = lub_endpoint; + last_if->num_endpoints++; + + LIBUSB20_INIT(LIBUSB20_ENDPOINT_DESC, &last_ep->desc); + + if (libusb20_me_decode(ptr, ptr[0], &last_ep->desc)) { + /* ignore */ + } + last_ep->extra.ptr = LIBUSB20_ADD_BYTES(ptr, ptr[0]); + last_ep->extra.len = 0; + last_ep->extra.type = LIBUSB20_ME_IS_RAW; + } else { + lub_config->extra.len += ptr[0]; + } + + } else if ((ptr[1] == LIBUSB20_DT_INTERFACE) && (ptr[0] >= 4)) { + if (ptr[2] != iface_no) { + /* new interface */ + iface_no = ptr[2]; + lub_interface++; + lub_config->num_interface++; + last_if = lub_interface; + niface++; + } else { + /* one more alternate setting */ + lub_interface->num_altsetting++; + last_if = lub_alt_interface; + lub_alt_interface++; + } + + LIBUSB20_INIT(LIBUSB20_INTERFACE_DESC, &last_if->desc); + + if (libusb20_me_decode(ptr, ptr[0], &last_if->desc)) { + /* ignore */ + } + /* + * Sometimes USB devices have corrupt interface + * descriptors and we need to overwrite the provided + * interface number! + */ + last_if->desc.bInterfaceNumber = niface - 1; + last_if->extra.ptr = LIBUSB20_ADD_BYTES(ptr, ptr[0]); + last_if->extra.len = 0; + last_if->extra.type = LIBUSB20_ME_IS_RAW; + last_if->endpoints = lub_endpoint + 1; + last_if->altsetting = lub_alt_interface; + last_if->num_altsetting = 0; + last_if->num_endpoints = 0; + last_ep = NULL; + } else { + /* unknown descriptor */ + if (last_if) { + if (last_ep) { + last_ep->extra.len += ptr[0]; + } else { + last_if->extra.len += ptr[0]; + } + } else { + lub_config->extra.len += ptr[0]; + } + } + } + return (lub_config); +} + +/*------------------------------------------------------------------------* + * libusb20_desc_foreach + * + * Safe traversal of USB descriptors. + * + * Return values: + * NULL: End of descriptors + * Else: Pointer to next descriptor + *------------------------------------------------------------------------*/ +const uint8_t * +libusb20_desc_foreach(const struct libusb20_me_struct *pdesc, + const uint8_t *psubdesc) +{ + void *end; + + if (pdesc == NULL) { + return (NULL); + } + end = LIBUSB20_ADD_BYTES(pdesc->ptr, pdesc->len); + + if (psubdesc == NULL) { + psubdesc = LIBUSB20_ADD_BYTES(pdesc->ptr, 0); + } else { + psubdesc = LIBUSB20_ADD_BYTES(psubdesc, psubdesc[0]); + } + return (((((void *)psubdesc) >= ((void *)(pdesc->ptr))) && + (((void *)psubdesc) < end) && + (LIBUSB20_ADD_BYTES(psubdesc, psubdesc[0]) >= ((void *)(pdesc->ptr))) && + (LIBUSB20_ADD_BYTES(psubdesc, psubdesc[0]) <= end) && + (psubdesc[0] >= 3)) ? psubdesc : NULL); +} + +/*------------------------------------------------------------------------* + * libusb20_me_get_1 - safety wrapper to read out one byte + *------------------------------------------------------------------------*/ +uint8_t +libusb20_me_get_1(const struct libusb20_me_struct *ie, uint16_t offset) +{ + if (offset < ie->len) { + return (*((uint8_t *)LIBUSB20_ADD_BYTES(ie->ptr, offset))); + } + return (0); +} + +/*------------------------------------------------------------------------* + * libusb20_me_get_2 - safety wrapper to read out one word + *------------------------------------------------------------------------*/ +uint16_t +libusb20_me_get_2(const struct libusb20_me_struct *ie, uint16_t offset) +{ + return (libusb20_me_get_1(ie, offset) | + (libusb20_me_get_1(ie, offset + 1) << 8)); +} + +/*------------------------------------------------------------------------* + * libusb20_me_encode - encode a message structure + * + * Description of parameters: + * "len" - maximum length of output buffer + * "ptr" - pointer to output buffer. If NULL, no data will be written + * "pd" - source structure + * + * Return values: + * 0..65535 - Number of bytes used, limited by the "len" input parameter. + *------------------------------------------------------------------------*/ +uint16_t +libusb20_me_encode(void *ptr, uint16_t len, const void *pd) +{ + const uint8_t *pf; /* pointer to format data */ + uint8_t *buf; /* pointer to output buffer */ + + uint32_t pd_offset; /* decoded structure offset */ + uint16_t len_old; /* old length */ + uint16_t pd_count; /* decoded element count */ + uint8_t me; /* message element */ + + /* initialise */ + + len_old = len; + buf = ptr; + pd_offset = sizeof(void *); + pf = (*((struct libusb20_me_format **)pd))->format; + + /* scan */ + + while (1) { + + /* get information element */ + + me = (pf[0]) & LIBUSB20_ME_MASK; + pd_count = pf[1] | (pf[2] << 8); + pf += 3; + + /* encode the message element */ + + switch (me) { + case LIBUSB20_ME_INT8: + while (pd_count--) { + uint8_t temp; + + if (len < 1) /* overflow */ + goto done; + if (buf) { + temp = *((const uint8_t *) + LIBUSB20_ADD_BYTES(pd, pd_offset)); + buf[0] = temp; + buf += 1; + } + pd_offset += 1; + len -= 1; + } + break; + + case LIBUSB20_ME_INT16: + pd_offset = -((-pd_offset) & ~1); /* align */ + while (pd_count--) { + uint16_t temp; + + if (len < 2) /* overflow */ + goto done; + + if (buf) { + temp = *((const uint16_t *) + LIBUSB20_ADD_BYTES(pd, pd_offset)); + buf[1] = (temp >> 8) & 0xFF; + buf[0] = temp & 0xFF; + buf += 2; + } + pd_offset += 2; + len -= 2; + } + break; + + case LIBUSB20_ME_INT32: + pd_offset = -((-pd_offset) & ~3); /* align */ + while (pd_count--) { + uint32_t temp; + + if (len < 4) /* overflow */ + goto done; + if (buf) { + temp = *((const uint32_t *) + LIBUSB20_ADD_BYTES(pd, pd_offset)); + buf[3] = (temp >> 24) & 0xFF; + buf[2] = (temp >> 16) & 0xFF; + buf[1] = (temp >> 8) & 0xFF; + buf[0] = temp & 0xFF; + buf += 4; + } + pd_offset += 4; + len -= 4; + } + break; + + case LIBUSB20_ME_INT64: + pd_offset = -((-pd_offset) & ~7); /* align */ + while (pd_count--) { + uint64_t temp; + + if (len < 8) /* overflow */ + goto done; + if (buf) { + + temp = *((const uint64_t *) + LIBUSB20_ADD_BYTES(pd, pd_offset)); + buf[7] = (temp >> 56) & 0xFF; + buf[6] = (temp >> 48) & 0xFF; + buf[5] = (temp >> 40) & 0xFF; + buf[4] = (temp >> 32) & 0xFF; + buf[3] = (temp >> 24) & 0xFF; + buf[2] = (temp >> 16) & 0xFF; + buf[1] = (temp >> 8) & 0xFF; + buf[0] = temp & 0xFF; + buf += 8; + } + pd_offset += 8; + len -= 8; + } + break; + + case LIBUSB20_ME_STRUCT: + pd_offset = -((-pd_offset) & + ~(LIBUSB20_ME_STRUCT_ALIGN - 1)); /* align */ + while (pd_count--) { + void *src_ptr; + uint16_t src_len; + struct libusb20_me_struct *ps; + + ps = LIBUSB20_ADD_BYTES(pd, pd_offset); + + switch (ps->type) { + case LIBUSB20_ME_IS_RAW: + src_len = ps->len; + src_ptr = ps->ptr; + break; + + case LIBUSB20_ME_IS_ENCODED: + if (ps->len == 0) { + /* + * Length is encoded + * in the data itself + * and should be + * correct: + */ + ps->len = 0 - 1; + } + src_len = libusb20_me_get_1(pd, 0); + src_ptr = LIBUSB20_ADD_BYTES(ps->ptr, 1); + if (src_len == 0xFF) { + /* length is escaped */ + src_len = libusb20_me_get_2(pd, 1); + src_ptr = + LIBUSB20_ADD_BYTES(ps->ptr, 3); + } + break; + + case LIBUSB20_ME_IS_DECODED: + /* reserve 3 length bytes */ + src_len = libusb20_me_encode(NULL, + 0 - 1 - 3, ps->ptr); + src_ptr = NULL; + break; + + default: /* empty structure */ + src_len = 0; + src_ptr = NULL; + break; + } + + if (src_len > 0xFE) { + if (src_len > (uint16_t)(0 - 1 - 3)) + /* overflow */ + goto done; + + if (len < (src_len + 3)) + /* overflow */ + goto done; + + if (buf) { + buf[0] = 0xFF; + buf[1] = (src_len & 0xFF); + buf[2] = (src_len >> 8) & 0xFF; + buf += 3; + } + len -= (src_len + 3); + } else { + if (len < (src_len + 1)) + /* overflow */ + goto done; + + if (buf) { + buf[0] = (src_len & 0xFF); + buf += 1; + } + len -= (src_len + 1); + } + + /* check for buffer and non-zero length */ + + if (buf && src_len) { + if (ps->type == LIBUSB20_ME_IS_DECODED) { + /* + * Repeat encode + * procedure - we have + * room for the + * complete structure: + */ + uint16_t dummy; + + dummy = libusb20_me_encode(buf, + 0 - 1 - 3, ps->ptr); + } else { + bcopy(src_ptr, buf, src_len); + } + buf += src_len; + } + pd_offset += sizeof(struct libusb20_me_struct); + } + break; + + default: + goto done; + } + } +done: + return (len_old - len); +} + +/*------------------------------------------------------------------------* + * libusb20_me_decode - decode a message into a decoded structure + * + * Description of parameters: + * "ptr" - message pointer + * "len" - message length + * "pd" - pointer to decoded structure + * + * Returns: + * "0..65535" - number of bytes decoded, limited by "len" + *------------------------------------------------------------------------*/ +uint16_t +libusb20_me_decode(const void *ptr, uint16_t len, void *pd) +{ + const uint8_t *pf; /* pointer to format data */ + const uint8_t *buf; /* pointer to input buffer */ + + uint32_t pd_offset; /* decoded structure offset */ + uint16_t len_old; /* old length */ + uint16_t pd_count; /* decoded element count */ + uint8_t me; /* message element */ + + /* initialise */ + + len_old = len; + buf = ptr; + pd_offset = sizeof(void *); + pf = (*((struct libusb20_me_format **)pd))->format; + + /* scan */ + + while (1) { + + /* get information element */ + + me = (pf[0]) & LIBUSB20_ME_MASK; + pd_count = pf[1] | (pf[2] << 8); + pf += 3; + + /* decode the message element by type */ + + switch (me) { + case LIBUSB20_ME_INT8: + while (pd_count--) { + uint8_t temp; + + if (len < 1) { + len = 0; + temp = 0; + } else { + len -= 1; + temp = buf[0]; + buf++; + } + *((uint8_t *)LIBUSB20_ADD_BYTES(pd, + pd_offset)) = temp; + pd_offset += 1; + } + break; + + case LIBUSB20_ME_INT16: + pd_offset = -((-pd_offset) & ~1); /* align */ + while (pd_count--) { + uint16_t temp; + + if (len < 2) { + len = 0; + temp = 0; + } else { + len -= 2; + temp = buf[1] << 8; + temp |= buf[0]; + buf += 2; + } + *((uint16_t *)LIBUSB20_ADD_BYTES(pd, + pd_offset)) = temp; + pd_offset += 2; + } + break; + + case LIBUSB20_ME_INT32: + pd_offset = -((-pd_offset) & ~3); /* align */ + while (pd_count--) { + uint32_t temp; + + if (len < 4) { + len = 0; + temp = 0; + } else { + len -= 4; + temp = buf[3] << 24; + temp |= buf[2] << 16; + temp |= buf[1] << 8; + temp |= buf[0]; + buf += 4; + } + + *((uint32_t *)LIBUSB20_ADD_BYTES(pd, + pd_offset)) = temp; + pd_offset += 4; + } + break; + + case LIBUSB20_ME_INT64: + pd_offset = -((-pd_offset) & ~7); /* align */ + while (pd_count--) { + uint64_t temp; + + if (len < 8) { + len = 0; + temp = 0; + } else { + len -= 8; + temp = ((uint64_t)buf[7]) << 56; + temp |= ((uint64_t)buf[6]) << 48; + temp |= ((uint64_t)buf[5]) << 40; + temp |= ((uint64_t)buf[4]) << 32; + temp |= buf[3] << 24; + temp |= buf[2] << 16; + temp |= buf[1] << 8; + temp |= buf[0]; + buf += 8; + } + + *((uint64_t *)LIBUSB20_ADD_BYTES(pd, + pd_offset)) = temp; + pd_offset += 8; + } + break; + + case LIBUSB20_ME_STRUCT: + pd_offset = -((-pd_offset) & + ~(LIBUSB20_ME_STRUCT_ALIGN - 1)); /* align */ + while (pd_count--) { + uint16_t temp; + uint16_t dummy; + struct libusb20_me_struct *ps; + + ps = LIBUSB20_ADD_BYTES(pd, pd_offset); + + if (ps->type == LIBUSB20_ME_IS_ENCODED) { + /* + * Pre-store a de-constified + * pointer to the raw + * structure: + */ + ps->ptr = LIBUSB20_ADD_BYTES(buf, 0); + + /* + * Get the correct number of + * length bytes: + */ + if (len != 0) { + if (buf[0] == 0xFF) { + ps->len = 3; + } else { + ps->len = 1; + } + } else { + ps->len = 0; + } + } + /* get the structure length */ + + if (len != 0) { + if (buf[0] == 0xFF) { + if (len < 3) { + len = 0; + temp = 0; + } else { + len -= 3; + temp = buf[1] | + (buf[2] << 8); + buf += 3; + } + } else { + len -= 1; + temp = buf[0]; + buf += 1; + } + } else { + len = 0; + temp = 0; + } + /* check for invalid length */ + + if (temp > len) { + len = 0; + temp = 0; + } + /* check wanted structure type */ + + switch (ps->type) { + case LIBUSB20_ME_IS_ENCODED: + /* check for zero length */ + if (temp == 0) { + /* + * The pointer must + * be valid: + */ + ps->ptr = LIBUSB20_ADD_BYTES( + libusb20_me_encode_empty, 0); + ps->len = 1; + } else { + ps->len += temp; + } + break; + + case LIBUSB20_ME_IS_RAW: + /* update length and pointer */ + ps->len = temp; + ps->ptr = LIBUSB20_ADD_BYTES(buf, 0); + break; + + case LIBUSB20_ME_IS_EMPTY: + case LIBUSB20_ME_IS_DECODED: + /* check for non-zero length */ + if (temp != 0) { + /* update type */ + ps->type = LIBUSB20_ME_IS_DECODED; + ps->len = 0; + /* + * Recursivly decode + * the next structure + */ + dummy = libusb20_me_decode(buf, + temp, ps->ptr); + } else { + /* update type */ + ps->type = LIBUSB20_ME_IS_EMPTY; + ps->len = 0; + } + break; + + default: + /* + * nothing to do - should + * not happen + */ + ps->ptr = NULL; + ps->len = 0; + break; + } + buf += temp; + len -= temp; + pd_offset += sizeof(struct libusb20_me_struct); + } + break; + + default: + goto done; + } + } +done: + return (len_old - len); +} diff --git a/lib/libusb20/libusb20_desc.h b/lib/libusb20/libusb20_desc.h new file mode 100644 index 000000000000..76bcca8fd991 --- /dev/null +++ b/lib/libusb20/libusb20_desc.h @@ -0,0 +1,534 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2007-2008 Daniel Drake. All rights reserved. + * Copyright (c) 2001 Johannes Erdfelt. 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. + */ + +/* + * NOTE: This file contains the definition of some standard USB + * structures. All structures which name ends by *DECODED use host byte + * order. + */ + +/* + * NOTE: This file uses a lot of macros. If you want to see what the + * macros become when they are expanded then run the following + * commands from your shell: + * + * cpp libusb20_desc.h > temp.h + * indent temp.h + * less temp.h + */ + +#ifndef _LIBUSB20_DESC_H_ +#define _LIBUSB20_DESC_H_ + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +}; /* style */ + +#endif +/* basic macros */ + +#define LIBUSB20__NOT(...) __VA_ARGS__ +#define LIBUSB20_NOT(arg) LIBUSB20__NOT(LIBUSB20_YES arg(() LIBUSB20_NO)) +#define LIBUSB20_YES(...) __VA_ARGS__ +#define LIBUSB20_NO(...) +#define LIBUSB20_END(...) __VA_ARGS__ +#define LIBUSB20_MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define LIBUSB20_MIN(a,b) (((a) < (b)) ? (a) : (b)) + +#define LIBUSB20_ADD_BYTES(ptr,off) \ + ((void *)(((const uint8_t *)(ptr)) + (off))) + +/* basic message elements */ +enum { + LIBUSB20_ME_INT8, + LIBUSB20_ME_INT16, + LIBUSB20_ME_INT32, + LIBUSB20_ME_INT64, + LIBUSB20_ME_STRUCT, + LIBUSB20_ME_MAX, /* used to indicate end */ +}; + +/* basic message element modifiers */ +enum { + LIBUSB20_ME_IS_UNSIGNED = 0x00, + LIBUSB20_ME_IS_SIGNED = 0x80, + LIBUSB20_ME_MASK = 0x7F, +}; + +enum { + LIBUSB20_ME_IS_RAW, /* structure excludes length field + * (hardcoded value) */ + LIBUSB20_ME_IS_ENCODED, /* structure includes length field */ + LIBUSB20_ME_IS_EMPTY, /* no structure */ + LIBUSB20_ME_IS_DECODED, /* structure is recursive */ +}; + +/* basic helper structures and macros */ + +#define LIBUSB20_ME_STRUCT_ALIGN sizeof(void *) + +struct libusb20_me_struct { + void *ptr; /* data pointer */ + uint16_t len; /* defaults to zero */ + uint16_t type; /* defaults to LIBUSB20_ME_IS_EMPTY */ +} __aligned(LIBUSB20_ME_STRUCT_ALIGN); + +struct libusb20_me_format { + const uint8_t *format; /* always set */ + const char *desc; /* optionally set */ + const char *fields; /* optionally set */ +}; + +#define LIBUSB20_ME_STRUCT(n, field, arg, ismeta) \ + ismeta ( LIBUSB20_ME_STRUCT, 1, 0, ) \ + LIBUSB20_NOT(ismeta) ( struct libusb20_me_struct field; ) + +#define LIBUSB20_ME_STRUCT_ARRAY(n, field, arg, ismeta) \ + ismeta ( LIBUSB20_ME_STRUCT , (arg) & 0xFF, \ + ((arg) / 0x100) & 0xFF, ) \ + LIBUSB20_NOT(ismeta) ( struct libusb20_me_struct field [arg]; ) + +#define LIBUSB20_ME_INTEGER(n, field, ismeta, un, u, bits, a, size) \ + ismeta ( LIBUSB20_ME_INT##bits | \ + LIBUSB20_ME_IS_##un##SIGNED , \ + (size) & 0xFF, ((size) / 0x100) & 0xFF, ) \ + LIBUSB20_NOT(ismeta) ( u##int##bits##_t \ + __aligned((bits) / 8) field a; ) + +#define LIBUSB20_ME_UINT8_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta, UN, u, 8, , 1) + +#define LIBUSB20_ME_UINT8_ARRAY_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta, UN, u, 8, [arg], arg) + +#define LIBUSB20_ME_SINT8_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta,,, 8, , 1) + +#define LIBUSB20_ME_SINT8_ARRAY_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta,,, 8, [arg], arg) + +#define LIBUSB20_ME_UINT16_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta, UN, u, 16, , 1) + +#define LIBUSB20_ME_UINT16_ARRAY_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta, UN, u, 16, [arg], arg) + +#define LIBUSB20_ME_SINT16_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta,,, 16, , 1) + +#define LIBUSB20_ME_SINT16_ARRAY_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta,,, 16, [arg], arg) + +#define LIBUSB20_ME_UINT32_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta, UN, u, 32, , 1) + +#define LIBUSB20_ME_UINT32_ARRAY_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta, UN, u, 32, [arg], arg) + +#define LIBUSB20_ME_SINT32_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta,,, 32, , 1) + +#define LIBUSB20_ME_SINT32_ARRAY_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta,,, 32, [arg], arg) + +#define LIBUSB20_ME_UINT64_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta, UN, u, 64, , 1) + +#define LIBUSB20_ME_UINT64_ARRAY_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta, UN, u, 64, [arg], arg) + +#define LIBUSB20_ME_SINT64_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta,,, 64, , 1) + +#define LIBUSB20_ME_SINT64_ARRAY_T(n, field, arg, ismeta) \ + LIBUSB20_ME_INTEGER(n, field, ismeta,,, 64, [arg], arg) + +#define LIBUSB20_MAKE_DECODED_FIELD(n, type, field, arg) \ + LIBUSB20_ME_##type (n, field, arg, LIBUSB20_NO) + +#define LIBUSB20_MAKE_STRUCT(name) \ + extern const struct libusb20_me_format \ + name##_FORMAT[1]; \ + struct name##_DECODED { \ + const struct libusb20_me_format *name##_FORMAT; \ + name (LIBUSB20_MAKE_DECODED_FIELD,) \ + } + +#define LIBUSB20_MAKE_STRUCT_FORMAT(name) \ + const struct libusb20_me_format \ + name##_FORMAT[1] = {{ \ + .format = LIBUSB20_MAKE_FORMAT(name), \ + .desc = #name, \ + .fields = NULL, \ + }} + +#define LIBUSB20_MAKE_FORMAT_SUB(n, type, field, arg) \ + LIBUSB20_ME_##type (n, field, arg, LIBUSB20_YES) + +#define LIBUSB20_MAKE_FORMAT(what) (const uint8_t []) \ + { what (LIBUSB20_MAKE_FORMAT_SUB, ) LIBUSB20_ME_MAX, 0, 0 } + +#define LIBUSB20_INIT(what, ptr) do { \ + memset(ptr, 0, sizeof(*(ptr))); \ + (ptr)->what##_FORMAT = what##_FORMAT; \ +} while (0) + +#define LIBUSB20_DEVICE_DESC(m,n) \ + m(n, UINT8_T, bLength, ) \ + m(n, UINT8_T, bDescriptorType, ) \ + m(n, UINT16_T, bcdUSB, ) \ + m(n, UINT8_T, bDeviceClass, ) \ + m(n, UINT8_T, bDeviceSubClass, ) \ + m(n, UINT8_T, bDeviceProtocol, ) \ + m(n, UINT8_T, bMaxPacketSize0, ) \ + m(n, UINT16_T, idVendor, ) \ + m(n, UINT16_T, idProduct, ) \ + m(n, UINT16_T, bcdDevice, ) \ + m(n, UINT8_T, iManufacturer, ) \ + m(n, UINT8_T, iProduct, ) \ + m(n, UINT8_T, iSerialNumber, ) \ + m(n, UINT8_T, bNumConfigurations, ) \ + +LIBUSB20_MAKE_STRUCT(LIBUSB20_DEVICE_DESC); + +#define LIBUSB20_ENDPOINT_DESC(m,n) \ + m(n, UINT8_T, bLength, ) \ + m(n, UINT8_T, bDescriptorType, ) \ + m(n, UINT8_T, bEndpointAddress, ) \ + m(n, UINT8_T, bmAttributes, ) \ + m(n, UINT16_T, wMaxPacketSize, ) \ + m(n, UINT8_T, bInterval, ) \ + m(n, UINT8_T, bRefresh, ) \ + m(n, UINT8_T, bSynchAddress, ) \ + +LIBUSB20_MAKE_STRUCT(LIBUSB20_ENDPOINT_DESC); + +#define LIBUSB20_INTERFACE_DESC(m,n) \ + m(n, UINT8_T, bLength, ) \ + m(n, UINT8_T, bDescriptorType, ) \ + m(n, UINT8_T, bInterfaceNumber, ) \ + m(n, UINT8_T, bAlternateSetting, ) \ + m(n, UINT8_T, bNumEndpoints, ) \ + m(n, UINT8_T, bInterfaceClass, ) \ + m(n, UINT8_T, bInterfaceSubClass, ) \ + m(n, UINT8_T, bInterfaceProtocol, ) \ + m(n, UINT8_T, iInterface, ) \ + +LIBUSB20_MAKE_STRUCT(LIBUSB20_INTERFACE_DESC); + +#define LIBUSB20_CONFIG_DESC(m,n) \ + m(n, UINT8_T, bLength, ) \ + m(n, UINT8_T, bDescriptorType, ) \ + m(n, UINT16_T, wTotalLength, ) \ + m(n, UINT8_T, bNumInterfaces, ) \ + m(n, UINT8_T, bConfigurationValue, ) \ + m(n, UINT8_T, iConfiguration, ) \ + m(n, UINT8_T, bmAttributes, ) \ + m(n, UINT8_T, bMaxPower, ) \ + +LIBUSB20_MAKE_STRUCT(LIBUSB20_CONFIG_DESC); + +#define LIBUSB20_CONTROL_SETUP(m,n) \ + m(n, UINT8_T, bmRequestType, ) \ + m(n, UINT8_T, bRequest, ) \ + m(n, UINT16_T, wValue, ) \ + m(n, UINT16_T, wIndex, ) \ + m(n, UINT16_T, wLength, ) \ + +LIBUSB20_MAKE_STRUCT(LIBUSB20_CONTROL_SETUP); + +/* standard USB stuff */ + +/** \ingroup desc + * Device and/or Interface Class codes */ +enum libusb20_class_code { + /** In the context of a \ref LIBUSB20_DEVICE_DESC "device + * descriptor", this bDeviceClass value indicates that each + * interface specifies its own class information and all + * interfaces operate independently. + */ + LIBUSB20_CLASS_PER_INTERFACE = 0, + + /** Audio class */ + LIBUSB20_CLASS_AUDIO = 1, + + /** Communications class */ + LIBUSB20_CLASS_COMM = 2, + + /** Human Interface Device class */ + LIBUSB20_CLASS_HID = 3, + + /** Printer dclass */ + LIBUSB20_CLASS_PRINTER = 7, + + /** Picture transfer protocol class */ + LIBUSB20_CLASS_PTP = 6, + + /** Mass storage class */ + LIBUSB20_CLASS_MASS_STORAGE = 8, + + /** Hub class */ + LIBUSB20_CLASS_HUB = 9, + + /** Data class */ + LIBUSB20_CLASS_DATA = 10, + + /** Class is vendor-specific */ + LIBUSB20_CLASS_VENDOR_SPEC = 0xff, +}; + +/** \ingroup desc + * Descriptor types as defined by the USB specification. */ +enum libusb20_descriptor_type { + /** Device descriptor. See LIBUSB20_DEVICE_DESC. */ + LIBUSB20_DT_DEVICE = 0x01, + + /** Configuration descriptor. See LIBUSB20_CONFIG_DESC. */ + LIBUSB20_DT_CONFIG = 0x02, + + /** String descriptor */ + LIBUSB20_DT_STRING = 0x03, + + /** Interface descriptor. See LIBUSB20_INTERFACE_DESC. */ + LIBUSB20_DT_INTERFACE = 0x04, + + /** Endpoint descriptor. See LIBUSB20_ENDPOINT_DESC. */ + LIBUSB20_DT_ENDPOINT = 0x05, + + /** HID descriptor */ + LIBUSB20_DT_HID = 0x21, + + /** HID report descriptor */ + LIBUSB20_DT_REPORT = 0x22, + + /** Physical descriptor */ + LIBUSB20_DT_PHYSICAL = 0x23, + + /** Hub descriptor */ + LIBUSB20_DT_HUB = 0x29, +}; + +/* Descriptor sizes per descriptor type */ +#define LIBUSB20_DT_DEVICE_SIZE 18 +#define LIBUSB20_DT_CONFIG_SIZE 9 +#define LIBUSB20_DT_INTERFACE_SIZE 9 +#define LIBUSB20_DT_ENDPOINT_SIZE 7 +#define LIBUSB20_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define LIBUSB20_DT_HUB_NONVAR_SIZE 7 + +#define LIBUSB20_ENDPOINT_ADDRESS_MASK 0x0f /* in bEndpointAddress */ +#define LIBUSB20_ENDPOINT_DIR_MASK 0x80 + +/** \ingroup desc + * Endpoint direction. Values for bit 7 of the + * \ref LIBUSB20_ENDPOINT_DESC::bEndpointAddress "endpoint address" scheme. + */ +enum libusb20_endpoint_direction { + /** In: device-to-host */ + LIBUSB20_ENDPOINT_IN = 0x80, + + /** Out: host-to-device */ + LIBUSB20_ENDPOINT_OUT = 0x00, +}; + +#define LIBUSB20_TRANSFER_TYPE_MASK 0x03 /* in bmAttributes */ + +/** \ingroup desc + * Endpoint transfer type. Values for bits 0:1 of the + * \ref LIBUSB20_ENDPOINT_DESC::bmAttributes "endpoint attributes" field. + */ +enum libusb20_transfer_type { + /** Control endpoint */ + LIBUSB20_TRANSFER_TYPE_CONTROL = 0, + + /** Isochronous endpoint */ + LIBUSB20_TRANSFER_TYPE_ISOCHRONOUS = 1, + + /** Bulk endpoint */ + LIBUSB20_TRANSFER_TYPE_BULK = 2, + + /** Interrupt endpoint */ + LIBUSB20_TRANSFER_TYPE_INTERRUPT = 3, +}; + +/** \ingroup misc + * Standard requests, as defined in table 9-3 of the USB2 specifications */ +enum libusb20_standard_request { + /** Request status of the specific recipient */ + LIBUSB20_REQUEST_GET_STATUS = 0x00, + + /** Clear or disable a specific feature */ + LIBUSB20_REQUEST_CLEAR_FEATURE = 0x01, + + /* 0x02 is reserved */ + + /** Set or enable a specific feature */ + LIBUSB20_REQUEST_SET_FEATURE = 0x03, + + /* 0x04 is reserved */ + + /** Set device address for all future accesses */ + LIBUSB20_REQUEST_SET_ADDRESS = 0x05, + + /** Get the specified descriptor */ + LIBUSB20_REQUEST_GET_DESCRIPTOR = 0x06, + + /** Used to update existing descriptors or add new descriptors */ + LIBUSB20_REQUEST_SET_DESCRIPTOR = 0x07, + + /** Get the current device configuration value */ + LIBUSB20_REQUEST_GET_CONFIGURATION = 0x08, + + /** Set device configuration */ + LIBUSB20_REQUEST_SET_CONFIGURATION = 0x09, + + /** Return the selected alternate setting for the specified + * interface */ + LIBUSB20_REQUEST_GET_INTERFACE = 0x0A, + + /** Select an alternate interface for the specified interface */ + LIBUSB20_REQUEST_SET_INTERFACE = 0x0B, + + /** Set then report an endpoint's synchronization frame */ + LIBUSB20_REQUEST_SYNCH_FRAME = 0x0C, +}; + +/** \ingroup misc + * Request type bits of the + * \ref libusb20_control_setup::bmRequestType "bmRequestType" field in + * control transfers. */ +enum libusb20_request_type { + /** Standard */ + LIBUSB20_REQUEST_TYPE_STANDARD = (0x00 << 5), + + /** Class */ + LIBUSB20_REQUEST_TYPE_CLASS = (0x01 << 5), + + /** Vendor */ + LIBUSB20_REQUEST_TYPE_VENDOR = (0x02 << 5), + + /** Reserved */ + LIBUSB20_REQUEST_TYPE_RESERVED = (0x03 << 5), +}; + +/** \ingroup misc + * Recipient bits of the + * \ref libusb20_control_setup::bmRequestType "bmRequestType" field in + * control transfers. Values 4 through 31 are reserved. */ +enum libusb20_request_recipient { + /** Device */ + LIBUSB20_RECIPIENT_DEVICE = 0x00, + + /** Interface */ + LIBUSB20_RECIPIENT_INTERFACE = 0x01, + + /** Endpoint */ + LIBUSB20_RECIPIENT_ENDPOINT = 0x02, + + /** Other */ + LIBUSB20_RECIPIENT_OTHER = 0x03, +}; + +#define LIBUSB20_ISO_SYNC_TYPE_MASK 0x0C + +/** \ingroup desc + * Synchronization type for isochronous endpoints. Values for bits 2:3 + * of the \ref LIBUSB20_ENDPOINT_DESC::bmAttributes "bmAttributes" + * field in LIBUSB20_ENDPOINT_DESC. + */ +enum libusb20_iso_sync_type { + /** No synchronization */ + LIBUSB20_ISO_SYNC_TYPE_NONE = 0, + + /** Asynchronous */ + LIBUSB20_ISO_SYNC_TYPE_ASYNC = 1, + + /** Adaptive */ + LIBUSB20_ISO_SYNC_TYPE_ADAPTIVE = 2, + + /** Synchronous */ + LIBUSB20_ISO_SYNC_TYPE_SYNC = 3, +}; + +#define LIBUSB20_ISO_USAGE_TYPE_MASK 0x30 + +/** \ingroup desc + * Usage type for isochronous endpoints. Values for bits 4:5 of the + * \ref LIBUSB20_ENDPOINT_DESC::bmAttributes "bmAttributes" field in + * LIBUSB20_ENDPOINT_DESC. + */ +enum libusb20_iso_usage_type { + /** Data endpoint */ + LIBUSB20_ISO_USAGE_TYPE_DATA = 0, + + /** Feedback endpoint */ + LIBUSB20_ISO_USAGE_TYPE_FEEDBACK = 1, + + /** Implicit feedback Data endpoint */ + LIBUSB20_ISO_USAGE_TYPE_IMPLICIT = 2, +}; + +struct libusb20_endpoint { + struct LIBUSB20_ENDPOINT_DESC_DECODED desc; + struct libusb20_me_struct extra; +} __aligned(sizeof(void *)); + +struct libusb20_interface { + struct LIBUSB20_INTERFACE_DESC_DECODED desc; + struct libusb20_me_struct extra; + struct libusb20_interface *altsetting; + struct libusb20_endpoint *endpoints; + uint8_t num_altsetting; + uint8_t num_endpoints; +} __aligned(sizeof(void *)); + +struct libusb20_config { + struct LIBUSB20_CONFIG_DESC_DECODED desc; + struct libusb20_me_struct extra; + struct libusb20_interface *interface; + uint8_t num_interface; +} __aligned(sizeof(void *)); + +uint8_t libusb20_me_get_1(const struct libusb20_me_struct *ie, uint16_t offset); +uint16_t libusb20_me_get_2(const struct libusb20_me_struct *ie, uint16_t offset); +uint16_t libusb20_me_encode(void *ptr, uint16_t len, const void *pd); +uint16_t libusb20_me_decode(const void *ptr, uint16_t len, void *pd); +const uint8_t *libusb20_desc_foreach(const struct libusb20_me_struct *pdesc, const uint8_t *psubdesc); +struct libusb20_config *libusb20_parse_config_desc(const void *config_desc); + +#if 0 +{ /* style */ +#endif +#ifdef __cplusplus +} + +#endif + +#endif /* _LIBUSB20_DESC_H_ */ diff --git a/lib/libusb20/libusb20_int.h b/lib/libusb20/libusb20_int.h new file mode 100644 index 000000000000..6c849b9396b5 --- /dev/null +++ b/lib/libusb20/libusb20_int.h @@ -0,0 +1,252 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * This file describes internal structures. + */ + +#ifndef _LIBUSB20_INT_H_ +#define _LIBUSB20_INT_H_ + +struct libusb20_device; +struct libusb20_backend; +struct libusb20_transfer; +struct libusb20_quirk; + +union libusb20_session_data { + unsigned long session_data; + struct timespec tv; + uint32_t plugtime; +}; + +/* USB backend specific */ +typedef const char *(libusb20_get_backend_name_t)(void); +typedef int (libusb20_root_get_dev_quirk_t)(struct libusb20_backend *pbe, uint16_t index, struct libusb20_quirk *pq); +typedef int (libusb20_root_get_quirk_name_t)(struct libusb20_backend *pbe, uint16_t index, struct libusb20_quirk *pq); +typedef int (libusb20_root_add_dev_quirk_t)(struct libusb20_backend *pbe, struct libusb20_quirk *pq); +typedef int (libusb20_root_remove_dev_quirk_t)(struct libusb20_backend *pbe, struct libusb20_quirk *pq); +typedef int (libusb20_bus_get_owner_t)(struct libusb20_backend *pbe, uint8_t bus, uid_t *user, gid_t *group); +typedef int (libusb20_bus_get_perm_t)(struct libusb20_backend *pbe, uint8_t bus, mode_t *mode); +typedef int (libusb20_bus_set_owner_t)(struct libusb20_backend *pbe, uint8_t bus, uid_t user, gid_t group); +typedef int (libusb20_bus_set_perm_t)(struct libusb20_backend *pbe, uint8_t bus, mode_t mode); +typedef int (libusb20_close_device_t)(struct libusb20_device *pdev); +typedef int (libusb20_dev_get_iface_owner_t)(struct libusb20_device *pdev, uint8_t iface_index, uid_t *user, gid_t *group); +typedef int (libusb20_dev_get_iface_perm_t)(struct libusb20_device *pdev, uint8_t iface_index, mode_t *mode); +typedef int (libusb20_dev_get_owner_t)(struct libusb20_device *pdev, uid_t *user, gid_t *group); +typedef int (libusb20_dev_get_perm_t)(struct libusb20_device *pdev, mode_t *mode); +typedef int (libusb20_dev_set_iface_owner_t)(struct libusb20_device *pdev, uint8_t iface_index, uid_t user, gid_t group); +typedef int (libusb20_dev_set_iface_perm_t)(struct libusb20_device *pdev, uint8_t iface_index, mode_t mode); +typedef int (libusb20_dev_set_owner_t)(struct libusb20_device *pdev, uid_t user, gid_t group); +typedef int (libusb20_dev_set_perm_t)(struct libusb20_device *pdev, mode_t mode); +typedef int (libusb20_init_backend_t)(struct libusb20_backend *pbe); +typedef int (libusb20_open_device_t)(struct libusb20_device *pdev, uint16_t transfer_count_max); +typedef int (libusb20_root_get_owner_t)(struct libusb20_backend *pbe, uid_t *user, gid_t *group); +typedef int (libusb20_root_get_perm_t)(struct libusb20_backend *pbe, mode_t *mode); +typedef int (libusb20_root_set_owner_t)(struct libusb20_backend *pbe, uid_t user, gid_t group); +typedef int (libusb20_root_set_perm_t)(struct libusb20_backend *pbe, mode_t mode); +typedef void (libusb20_exit_backend_t)(struct libusb20_backend *pbe); + +#define LIBUSB20_DEFINE(n,field) \ + libusb20_##field##_t *field; + +#define LIBUSB20_DECLARE(n,field) \ + /* .field = */ n##_##field, + +#define LIBUSB20_BACKEND(m,n) \ + /* description of this backend */ \ + m(n, get_backend_name) \ + /* optional backend methods */ \ + m(n, init_backend) \ + m(n, exit_backend) \ + m(n, bus_set_owner) \ + m(n, bus_get_owner) \ + m(n, bus_set_perm) \ + m(n, bus_get_perm) \ + m(n, dev_get_iface_owner) \ + m(n, dev_get_iface_perm) \ + m(n, dev_get_owner) \ + m(n, dev_get_perm) \ + m(n, dev_set_iface_owner) \ + m(n, dev_set_iface_perm) \ + m(n, dev_set_owner) \ + m(n, dev_set_perm) \ + m(n, root_get_dev_quirk) \ + m(n, root_get_quirk_name) \ + m(n, root_add_dev_quirk) \ + m(n, root_remove_dev_quirk) \ + m(n, root_set_owner) \ + m(n, root_get_owner) \ + m(n, root_set_perm) \ + m(n, root_get_perm) \ + /* mandatory device methods */ \ + m(n, open_device) \ + m(n, close_device) \ + +struct libusb20_backend_methods { + LIBUSB20_BACKEND(LIBUSB20_DEFINE,) +}; + +/* USB dummy methods */ +typedef int (libusb20_dummy_int_t)(void); +typedef void (libusb20_dummy_void_t)(void); + +/* USB device specific */ +typedef int (libusb20_claim_interface_t)(struct libusb20_device *pdev, uint8_t iface_index); +typedef int (libusb20_detach_kernel_driver_t)(struct libusb20_device *pdev, uint8_t iface_index); +typedef int (libusb20_do_request_sync_t)(struct libusb20_device *pdev, struct LIBUSB20_CONTROL_SETUP_DECODED *setup, void *data, uint16_t *pactlen, uint32_t timeout, uint8_t flags); +typedef int (libusb20_get_config_desc_full_t)(struct libusb20_device *pdev, uint8_t **ppbuf, uint16_t *plen, uint8_t index); +typedef int (libusb20_get_config_index_t)(struct libusb20_device *pdev, uint8_t *pindex); +typedef int (libusb20_kernel_driver_active_t)(struct libusb20_device *pdev, uint8_t iface_index); +typedef int (libusb20_process_t)(struct libusb20_device *pdev); +typedef int (libusb20_release_interface_t)(struct libusb20_device *pdev, uint8_t iface_index); +typedef int (libusb20_reset_device_t)(struct libusb20_device *pdev); +typedef int (libusb20_set_power_mode_t)(struct libusb20_device *pdev, uint8_t power_mode); +typedef int (libusb20_get_power_mode_t)(struct libusb20_device *pdev, uint8_t *power_mode); +typedef int (libusb20_set_alt_index_t)(struct libusb20_device *pdev, uint8_t iface_index, uint8_t alt_index); +typedef int (libusb20_set_config_index_t)(struct libusb20_device *pdev, uint8_t index); + +/* USB transfer specific */ +typedef int (libusb20_tr_open_t)(struct libusb20_transfer *xfer, uint32_t MaxBufSize, uint32_t MaxFrameCount, uint8_t ep_no); +typedef int (libusb20_tr_close_t)(struct libusb20_transfer *xfer); +typedef int (libusb20_tr_clear_stall_sync_t)(struct libusb20_transfer *xfer); +typedef void (libusb20_tr_submit_t)(struct libusb20_transfer *xfer); +typedef void (libusb20_tr_cancel_async_t)(struct libusb20_transfer *xfer); + +#define LIBUSB20_DEVICE(m,n) \ + m(n, claim_interface) \ + m(n, detach_kernel_driver) \ + m(n, do_request_sync) \ + m(n, get_config_desc_full) \ + m(n, get_config_index) \ + m(n, kernel_driver_active) \ + m(n, process) \ + m(n, release_interface) \ + m(n, reset_device) \ + m(n, set_power_mode) \ + m(n, get_power_mode) \ + m(n, set_alt_index) \ + m(n, set_config_index) \ + m(n, tr_cancel_async) \ + m(n, tr_clear_stall_sync) \ + m(n, tr_close) \ + m(n, tr_open) \ + m(n, tr_submit) \ + +struct libusb20_device_methods { + LIBUSB20_DEVICE(LIBUSB20_DEFINE,) +}; + +struct libusb20_backend { + TAILQ_HEAD(, libusb20_device) usb_devs; + const struct libusb20_backend_methods *methods; +}; + +struct libusb20_transfer { + struct libusb20_device *pdev; /* the USB device we belong to */ + libusb20_tr_callback_t *callback; + void *priv_sc0; /* private client data */ + void *priv_sc1; /* private client data */ + /* + * Pointer to a list of buffer pointers: + */ + void **ppBuffer; + /* + * Pointer to frame lengths, which are updated to actual length + * after the USB transfer completes: + */ + uint32_t *pLength; + uint32_t maxTotalLength; + uint32_t maxFrames; /* total number of frames */ + uint32_t nFrames; /* total number of frames */ + uint32_t aFrames; /* actual number of frames */ + uint32_t timeout; + /* isochronous completion time in milliseconds */ + uint16_t timeComplete; + uint16_t trIndex; + uint16_t maxPacketLen; + uint8_t flags; /* see LIBUSB20_TRANSFER_XXX */ + uint8_t status; /* see LIBUSB20_TRANSFER_XXX */ + uint8_t is_opened; + uint8_t is_pending; + uint8_t is_cancel; + uint8_t is_draining; + uint8_t is_restart; +}; + +struct libusb20_device { + + /* device descriptor */ + struct LIBUSB20_DEVICE_DESC_DECODED ddesc; + + /* device timestamp */ + union libusb20_session_data session_data; + + /* our device entry */ + TAILQ_ENTRY(libusb20_device) dev_entry; + + /* device methods */ + const struct libusb20_device_methods *methods; + + /* backend methods */ + const struct libusb20_backend_methods *beMethods; + + /* list of USB transfers */ + struct libusb20_transfer *pTransfer; + + /* private backend data */ + void *privBeData; + + /* libUSB v0.1 compat data */ + void *priv01Data; + + /* claimed interfaces */ + uint32_t claimed_interfaces; + + /* device file handle */ + int file; + + /* device file handle (control transfers only) */ + int file_ctrl; + + /* debugging level */ + int debug; + + /* number of USB transfers */ + uint16_t nTransfer; + + uint8_t bus_number; + uint8_t device_address; + uint8_t usb_mode; + uint8_t usb_speed; + uint8_t is_opened; + + char usb_desc[96]; +}; + +extern const struct libusb20_backend_methods libusb20_ugen20_backend; +extern const struct libusb20_backend_methods libusb20_linux_backend; + +#endif /* _LIBUSB20_INT_H_ */ diff --git a/lib/libusb20/libusb20_ugen20.c b/lib/libusb20/libusb20_ugen20.c new file mode 100644 index 000000000000..ffbd861de5c2 --- /dev/null +++ b/lib/libusb20/libusb20_ugen20.c @@ -0,0 +1,1077 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libusb20.h" +#include "libusb20_desc.h" +#include "libusb20_int.h" + +#include +#include +#include +#include +#include + +static libusb20_init_backend_t ugen20_init_backend; +static libusb20_open_device_t ugen20_open_device; +static libusb20_close_device_t ugen20_close_device; +static libusb20_get_backend_name_t ugen20_get_backend_name; +static libusb20_exit_backend_t ugen20_exit_backend; +static libusb20_bus_set_owner_t ugen20_bus_set_owner; +static libusb20_bus_get_owner_t ugen20_bus_get_owner; +static libusb20_bus_set_perm_t ugen20_bus_set_perm; +static libusb20_bus_get_perm_t ugen20_bus_get_perm; +static libusb20_dev_get_iface_owner_t ugen20_dev_get_iface_owner; +static libusb20_dev_get_iface_perm_t ugen20_dev_get_iface_perm; +static libusb20_dev_get_owner_t ugen20_dev_get_owner; +static libusb20_dev_get_perm_t ugen20_dev_get_perm; +static libusb20_dev_set_iface_owner_t ugen20_dev_set_iface_owner; +static libusb20_dev_set_iface_perm_t ugen20_dev_set_iface_perm; +static libusb20_dev_set_owner_t ugen20_dev_set_owner; +static libusb20_dev_set_perm_t ugen20_dev_set_perm; +static libusb20_root_get_dev_quirk_t ugen20_root_get_dev_quirk; +static libusb20_root_get_quirk_name_t ugen20_root_get_quirk_name; +static libusb20_root_add_dev_quirk_t ugen20_root_add_dev_quirk; +static libusb20_root_remove_dev_quirk_t ugen20_root_remove_dev_quirk; +static libusb20_root_set_owner_t ugen20_root_set_owner; +static libusb20_root_get_owner_t ugen20_root_get_owner; +static libusb20_root_set_perm_t ugen20_root_set_perm; +static libusb20_root_get_perm_t ugen20_root_get_perm; + +const struct libusb20_backend_methods libusb20_ugen20_backend = { + LIBUSB20_BACKEND(LIBUSB20_DECLARE, ugen20) +}; + +/* USB device specific */ +static libusb20_get_config_desc_full_t ugen20_get_config_desc_full; +static libusb20_get_config_index_t ugen20_get_config_index; +static libusb20_set_config_index_t ugen20_set_config_index; +static libusb20_claim_interface_t ugen20_claim_interface; +static libusb20_release_interface_t ugen20_release_interface; +static libusb20_set_alt_index_t ugen20_set_alt_index; +static libusb20_reset_device_t ugen20_reset_device; +static libusb20_set_power_mode_t ugen20_set_power_mode; +static libusb20_get_power_mode_t ugen20_get_power_mode; +static libusb20_kernel_driver_active_t ugen20_kernel_driver_active; +static libusb20_detach_kernel_driver_t ugen20_detach_kernel_driver; +static libusb20_do_request_sync_t ugen20_do_request_sync; +static libusb20_process_t ugen20_process; + +/* USB transfer specific */ +static libusb20_tr_open_t ugen20_tr_open; +static libusb20_tr_close_t ugen20_tr_close; +static libusb20_tr_clear_stall_sync_t ugen20_tr_clear_stall_sync; +static libusb20_tr_submit_t ugen20_tr_submit; +static libusb20_tr_cancel_async_t ugen20_tr_cancel_async; + +static const struct libusb20_device_methods libusb20_ugen20_device_methods = { + LIBUSB20_DEVICE(LIBUSB20_DECLARE, ugen20) +}; + +static const char * +ugen20_get_backend_name(void) +{ + return ("FreeBSD UGEN 2.0"); +} + +static uint32_t +ugen20_path_convert_one(const char **pp) +{ + const char *ptr; + uint32_t temp = 0; + + ptr = *pp; + + while ((*ptr >= '0') && (*ptr <= '9')) { + temp *= 10; + temp += (*ptr - '0'); + if (temp >= 1000000) { + /* catch overflow early */ + return (0 - 1); + } + ptr++; + } + + if (*ptr == '.') { + /* skip dot */ + ptr++; + } + *pp = ptr; + + return (temp); +} + +static int +ugen20_enumerate(struct libusb20_device *pdev, const char *id) +{ + const char *tmp = id; + struct usb2_device_descriptor ddesc; + struct usb2_device_info devinfo; + uint32_t plugtime; + char buf[64]; + int f; + int error; + + pdev->bus_number = ugen20_path_convert_one(&tmp); + pdev->device_address = ugen20_path_convert_one(&tmp); + + snprintf(buf, sizeof(buf), "/dev/ugen%u.%u", + pdev->bus_number, pdev->device_address); + + f = open(buf, O_RDWR); + if (f < 0) { + return (LIBUSB20_ERROR_OTHER); + } + if (ioctl(f, USB_GET_PLUGTIME, &plugtime)) { + error = LIBUSB20_ERROR_OTHER; + goto done; + } + /* store when the device was plugged */ + pdev->session_data.plugtime = plugtime; + + if (ioctl(f, USB_GET_DEVICE_DESC, &ddesc)) { + error = LIBUSB20_ERROR_OTHER; + goto done; + } + LIBUSB20_INIT(LIBUSB20_DEVICE_DESC, &(pdev->ddesc)); + + libusb20_me_decode(&ddesc, sizeof(ddesc), &(pdev->ddesc)); + + if (pdev->ddesc.bNumConfigurations == 0) { + error = LIBUSB20_ERROR_OTHER; + goto done; + } else if (pdev->ddesc.bNumConfigurations >= 8) { + error = LIBUSB20_ERROR_OTHER; + goto done; + } + if (ioctl(f, USB_GET_DEVICEINFO, &devinfo)) { + error = LIBUSB20_ERROR_OTHER; + goto done; + } + switch (devinfo.udi_mode) { + case USB_MODE_DEVICE: + pdev->usb_mode = LIBUSB20_MODE_DEVICE; + break; + default: + pdev->usb_mode = LIBUSB20_MODE_HOST; + break; + } + + switch (devinfo.udi_speed) { + case USB_SPEED_LOW: + pdev->usb_speed = LIBUSB20_SPEED_LOW; + break; + case USB_SPEED_FULL: + pdev->usb_speed = LIBUSB20_SPEED_FULL; + break; + case USB_SPEED_HIGH: + pdev->usb_speed = LIBUSB20_SPEED_HIGH; + break; + case USB_SPEED_VARIABLE: + pdev->usb_speed = LIBUSB20_SPEED_VARIABLE; + break; + case USB_SPEED_SUPER: + pdev->usb_speed = LIBUSB20_SPEED_SUPER; + break; + default: + pdev->usb_speed = LIBUSB20_SPEED_UNKNOWN; + break; + } + + /* generate a nice description for printout */ + + snprintf(pdev->usb_desc, sizeof(pdev->usb_desc), + "ugen%u.%u: <%s %s> at usbus%u", pdev->bus_number, + pdev->device_address, devinfo.udi_product, + devinfo.udi_vendor, pdev->bus_number); + + error = 0; +done: + close(f); + return (error); +} + +struct ugen20_urd_state { + struct usb2_read_dir urd; + uint32_t nparsed; + int f; + uint8_t *ptr; + const char *src; + const char *dst; + uint8_t buf[256]; + uint8_t dummy_zero[1]; +}; + +static int +ugen20_readdir(struct ugen20_urd_state *st) +{ + ; /* style fix */ +repeat: + if (st->ptr == NULL) { + st->urd.urd_startentry += st->nparsed; + st->urd.urd_data = st->buf; + st->urd.urd_maxlen = sizeof(st->buf); + st->nparsed = 0; + + if (ioctl(st->f, USB_READ_DIR, &st->urd)) { + return (EINVAL); + } + st->ptr = st->buf; + } + if (st->ptr[0] == 0) { + if (st->nparsed) { + st->ptr = NULL; + goto repeat; + } else { + return (ENXIO); + } + } + st->src = (void *)(st->ptr + 1); + st->dst = st->src + strlen(st->src) + 1; + st->ptr = st->ptr + st->ptr[0]; + st->nparsed++; + + if ((st->ptr < st->buf) || + (st->ptr > st->dummy_zero)) { + /* invalid entry */ + return (EINVAL); + } + return (0); +} + +static int +ugen20_init_backend(struct libusb20_backend *pbe) +{ + struct ugen20_urd_state state; + struct libusb20_device *pdev; + + memset(&state, 0, sizeof(state)); + + state.f = open("/dev/usb", O_RDONLY); + if (state.f < 0) + return (LIBUSB20_ERROR_OTHER); + + while (ugen20_readdir(&state) == 0) { + + if ((state.src[0] != 'u') || + (state.src[1] != 'g') || + (state.src[2] != 'e') || + (state.src[3] != 'n')) { + continue; + } + pdev = libusb20_dev_alloc(); + if (pdev == NULL) { + continue; + } + if (ugen20_enumerate(pdev, state.src + 4)) { + libusb20_dev_free(pdev); + continue; + } + /* put the device on the backend list */ + libusb20_be_enqueue_device(pbe, pdev); + } + close(state.f); + return (0); /* success */ +} + +static int +ugen20_open_device(struct libusb20_device *pdev, uint16_t nMaxTransfer) +{ + struct usb2_fs_endpoint *pfse = NULL; + struct usb2_fs_init fs_init = { /* zero */ }; + uint32_t size; + uint32_t plugtime; + char buf[64]; + int f; + int g; + int error; + + snprintf(buf, sizeof(buf), "/dev/ugen%u.%u", + pdev->bus_number, pdev->device_address); + + /* + * We need two file handles, one for the control endpoint and one + * for BULK, INTERRUPT and ISOCHRONOUS transactions due to optimised + * kernel locking. + */ + g = open(buf, O_RDWR); + if (g < 0) { + return (LIBUSB20_ERROR_NO_DEVICE); + } + f = open(buf, O_RDWR); + if (f < 0) { + close(g); + return (LIBUSB20_ERROR_NO_DEVICE); + } + if (ioctl(f, USB_GET_PLUGTIME, &plugtime)) { + error = LIBUSB20_ERROR_OTHER; + goto done; + } + /* check that the correct device is still plugged */ + if (pdev->session_data.plugtime != plugtime) { + error = LIBUSB20_ERROR_NO_DEVICE; + goto done; + } + if (nMaxTransfer != 0) { + + size = nMaxTransfer * sizeof(*pfse); + + pfse = malloc(size); + if (!pfse) { + error = LIBUSB20_ERROR_NO_MEM; + goto done; + } + memset(pfse, 0, size); + + fs_init.pEndpoints = pfse; + fs_init.ep_index_max = nMaxTransfer; + + if (ioctl(f, USB_FS_INIT, &fs_init)) { + error = LIBUSB20_ERROR_OTHER; + goto done; + } + } + /* set methods */ + pdev->methods = &libusb20_ugen20_device_methods; + pdev->privBeData = pfse; + pdev->file = f; + pdev->file_ctrl = g; + error = 0; +done: + if (error) { + if (pfse) { + free(pfse); + } + close(f); + close(g); + } + return (error); +} + +static int +ugen20_close_device(struct libusb20_device *pdev) +{ + struct usb2_fs_uninit fs_uninit = { /* zero */ }; + int error = 0; + + if (pdev->privBeData) { + if (ioctl(pdev->file, USB_FS_UNINIT, &fs_uninit)) { + error = LIBUSB20_ERROR_OTHER; + } + free(pdev->privBeData); + } + pdev->nTransfer = 0; + pdev->privBeData = NULL; + close(pdev->file); + close(pdev->file_ctrl); + pdev->file = -1; + pdev->file_ctrl = -1; + return (error); +} + +static void +ugen20_exit_backend(struct libusb20_backend *pbe) +{ + return; /* nothing to do */ +} + +static int +ugen20_get_config_desc_full(struct libusb20_device *pdev, + uint8_t **ppbuf, uint16_t *plen, uint8_t index) +{ + struct usb2_gen_descriptor gen_desc = { /* zero */ }; + struct usb2_config_descriptor cdesc; + uint8_t *ptr; + uint16_t len; + int error; + + gen_desc.ugd_data = &cdesc; + gen_desc.ugd_maxlen = sizeof(cdesc); + gen_desc.ugd_config_index = index; + + error = ioctl(pdev->file_ctrl, USB_GET_FULL_DESC, &gen_desc); + if (error) { + return (LIBUSB20_ERROR_OTHER); + } + len = UGETW(cdesc.wTotalLength); + if (len < sizeof(cdesc)) { + /* corrupt descriptor */ + return (LIBUSB20_ERROR_OTHER); + } + ptr = malloc(len); + if (!ptr) { + return (LIBUSB20_ERROR_NO_MEM); + } + gen_desc.ugd_data = ptr; + gen_desc.ugd_maxlen = len; + + error = ioctl(pdev->file_ctrl, USB_GET_FULL_DESC, &gen_desc); + if (error) { + free(ptr); + return (LIBUSB20_ERROR_OTHER); + } + /* make sure that the device doesn't fool us */ + memcpy(ptr, &cdesc, sizeof(cdesc)); + + *ppbuf = ptr; + *plen = len; + + return (0); /* success */ +} + +static int +ugen20_get_config_index(struct libusb20_device *pdev, uint8_t *pindex) +{ + int temp; + + if (ioctl(pdev->file_ctrl, USB_GET_CONFIG, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + *pindex = temp; + + return (0); +} + +static int +ugen20_set_config_index(struct libusb20_device *pdev, uint8_t index) +{ + int temp = index; + + if (ioctl(pdev->file_ctrl, USB_SET_CONFIG, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); +} + +static int +ugen20_claim_interface(struct libusb20_device *pdev, uint8_t iface_index) +{ + int temp = iface_index; + + if (ioctl(pdev->file_ctrl, USB_CLAIM_INTERFACE, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); +} + +static int +ugen20_release_interface(struct libusb20_device *pdev, uint8_t iface_index) +{ + int temp = iface_index; + + if (ioctl(pdev->file_ctrl, USB_RELEASE_INTERFACE, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); +} + +static int +ugen20_set_alt_index(struct libusb20_device *pdev, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb2_alt_interface alt_iface = { /* zero */ }; + + alt_iface.uai_interface_index = iface_index; + alt_iface.uai_alt_index = alt_index; + + if (ioctl(pdev->file_ctrl, USB_SET_ALTINTERFACE, &alt_iface)) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); +} + +static int +ugen20_reset_device(struct libusb20_device *pdev) +{ + int temp = 0; + + if (ioctl(pdev->file_ctrl, USB_DEVICEENUMERATE, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); +} + +static int +ugen20_set_power_mode(struct libusb20_device *pdev, uint8_t power_mode) +{ + int temp; + + switch (power_mode) { + case LIBUSB20_POWER_OFF: + temp = USB_POWER_MODE_OFF; + break; + case LIBUSB20_POWER_ON: + temp = USB_POWER_MODE_ON; + break; + case LIBUSB20_POWER_SAVE: + temp = USB_POWER_MODE_SAVE; + break; + case LIBUSB20_POWER_SUSPEND: + temp = USB_POWER_MODE_SUSPEND; + break; + case LIBUSB20_POWER_RESUME: + temp = USB_POWER_MODE_RESUME; + break; + default: + return (LIBUSB20_ERROR_INVALID_PARAM); + } + if (ioctl(pdev->file_ctrl, USB_SET_POWER_MODE, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); +} + +static int +ugen20_get_power_mode(struct libusb20_device *pdev, uint8_t *power_mode) +{ + int temp; + + if (ioctl(pdev->file_ctrl, USB_GET_POWER_MODE, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + switch (temp) { + case USB_POWER_MODE_OFF: + temp = LIBUSB20_POWER_OFF; + break; + case USB_POWER_MODE_ON: + temp = LIBUSB20_POWER_ON; + break; + case USB_POWER_MODE_SAVE: + temp = LIBUSB20_POWER_SAVE; + break; + case USB_POWER_MODE_SUSPEND: + temp = LIBUSB20_POWER_SUSPEND; + break; + case USB_POWER_MODE_RESUME: + temp = LIBUSB20_POWER_RESUME; + break; + default: + temp = LIBUSB20_POWER_ON; + break; + } + *power_mode = temp; + return (0); /* success */ +} + +static int +ugen20_kernel_driver_active(struct libusb20_device *pdev, + uint8_t iface_index) +{ + int temp = iface_index; + + if (ioctl(pdev->file_ctrl, USB_IFACE_DRIVER_ACTIVE, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); /* kernel driver is active */ +} + +static int +ugen20_detach_kernel_driver(struct libusb20_device *pdev, + uint8_t iface_index) +{ + int temp = iface_index; + + if (ioctl(pdev->file_ctrl, USB_IFACE_DRIVER_DETACH, &temp)) { + return (LIBUSB20_ERROR_OTHER); + } + return (0); /* kernel driver is active */ +} + +static int +ugen20_do_request_sync(struct libusb20_device *pdev, + struct LIBUSB20_CONTROL_SETUP_DECODED *setup, + void *data, uint16_t *pactlen, uint32_t timeout, uint8_t flags) +{ + struct usb2_ctl_request req = { /* zero */ }; + + req.ucr_data = data; + if (!(flags & LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK)) { + req.ucr_flags |= USB_SHORT_XFER_OK; + } + if (libusb20_me_encode(&req.ucr_request, + sizeof(req.ucr_request), setup)) { + /* ignore */ + } + if (ioctl(pdev->file_ctrl, USB_DO_REQUEST, &req)) { + return (LIBUSB20_ERROR_OTHER); + } + if (pactlen) { + /* get actual length */ + *pactlen = req.ucr_actlen; + } + return (0); /* kernel driver is active */ +} + +static int +ugen20_process(struct libusb20_device *pdev) +{ + struct usb2_fs_complete temp; + struct usb2_fs_endpoint *fsep; + struct libusb20_transfer *xfer; + + while (1) { + + if (ioctl(pdev->file, USB_FS_COMPLETE, &temp)) { + if (errno == EBUSY) { + break; + } else { + /* device detached */ + return (LIBUSB20_ERROR_OTHER); + } + } + fsep = pdev->privBeData; + xfer = pdev->pTransfer; + fsep += temp.ep_index; + xfer += temp.ep_index; + + /* update transfer status */ + + if (fsep->status == 0) { + xfer->aFrames = fsep->aFrames; + xfer->timeComplete = fsep->isoc_time_complete; + xfer->status = LIBUSB20_TRANSFER_COMPLETED; + } else if (fsep->status == USB_ERR_CANCELLED) { + xfer->aFrames = 0; + xfer->timeComplete = 0; + xfer->status = LIBUSB20_TRANSFER_CANCELLED; + } else if (fsep->status == USB_ERR_STALLED) { + xfer->aFrames = 0; + xfer->timeComplete = 0; + xfer->status = LIBUSB20_TRANSFER_STALL; + } else if (fsep->status == USB_ERR_TIMEOUT) { + xfer->aFrames = 0; + xfer->timeComplete = 0; + xfer->status = LIBUSB20_TRANSFER_TIMED_OUT; + } else { + xfer->aFrames = 0; + xfer->timeComplete = 0; + xfer->status = LIBUSB20_TRANSFER_ERROR; + } + libusb20_tr_callback_wrapper(xfer); + } + return (0); /* done */ +} + +static int +ugen20_tr_open(struct libusb20_transfer *xfer, uint32_t MaxBufSize, + uint32_t MaxFrameCount, uint8_t ep_no) +{ + struct usb2_fs_open temp = { /* zero */ }; + struct usb2_fs_endpoint *fsep; + + fsep = xfer->pdev->privBeData; + fsep += xfer->trIndex; + + temp.max_bufsize = MaxBufSize; + temp.max_frames = MaxFrameCount; + temp.ep_index = xfer->trIndex; + temp.ep_no = ep_no; + + if (ioctl(xfer->pdev->file, USB_FS_OPEN, &temp)) { + return (LIBUSB20_ERROR_INVALID_PARAM); + } + /* maximums might have changed - update */ + xfer->maxFrames = temp.max_frames; + + /* "max_bufsize" should be multiple of "max_packet_length" */ + xfer->maxTotalLength = temp.max_bufsize; + xfer->maxPacketLen = temp.max_packet_length; + + /* setup buffer and length lists */ + fsep->ppBuffer = xfer->ppBuffer;/* zero copy */ + fsep->pLength = xfer->pLength; /* zero copy */ + + return (0); /* success */ +} + +static int +ugen20_tr_close(struct libusb20_transfer *xfer) +{ + struct usb2_fs_close temp = { /* zero */ }; + + temp.ep_index = xfer->trIndex; + + if (ioctl(xfer->pdev->file, USB_FS_CLOSE, &temp)) { + return (LIBUSB20_ERROR_INVALID_PARAM); + } + return (0); /* success */ +} + +static int +ugen20_tr_clear_stall_sync(struct libusb20_transfer *xfer) +{ + struct usb2_fs_clear_stall_sync temp = { /* zero */ }; + + /* if the transfer is active, an error will be returned */ + + temp.ep_index = xfer->trIndex; + + if (ioctl(xfer->pdev->file, USB_FS_CLEAR_STALL_SYNC, &temp)) { + return (LIBUSB20_ERROR_INVALID_PARAM); + } + return (0); /* success */ +} + +static void +ugen20_tr_submit(struct libusb20_transfer *xfer) +{ + struct usb2_fs_start temp = { /* zero */ }; + struct usb2_fs_endpoint *fsep; + + fsep = xfer->pdev->privBeData; + fsep += xfer->trIndex; + + fsep->nFrames = xfer->nFrames; + fsep->flags = 0; + if (!(xfer->flags & LIBUSB20_TRANSFER_SINGLE_SHORT_NOT_OK)) { + fsep->flags |= USB_FS_FLAG_SINGLE_SHORT_OK; + } + if (!(xfer->flags & LIBUSB20_TRANSFER_MULTI_SHORT_NOT_OK)) { + fsep->flags |= USB_FS_FLAG_MULTI_SHORT_OK; + } + if (xfer->flags & LIBUSB20_TRANSFER_FORCE_SHORT) { + fsep->flags |= USB_FS_FLAG_FORCE_SHORT; + } + if (xfer->flags & LIBUSB20_TRANSFER_DO_CLEAR_STALL) { + fsep->flags |= USB_FS_FLAG_CLEAR_STALL; + } + fsep->timeout = xfer->timeout; + + temp.ep_index = xfer->trIndex; + + if (ioctl(xfer->pdev->file, USB_FS_START, &temp)) { + /* ignore any errors - should never happen */ + } + return; /* success */ +} + +static void +ugen20_tr_cancel_async(struct libusb20_transfer *xfer) +{ + struct usb2_fs_stop temp = { /* zero */ }; + + temp.ep_index = xfer->trIndex; + + if (ioctl(xfer->pdev->file, USB_FS_STOP, &temp)) { + /* ignore any errors - should never happen */ + } + return; +} + +static int +ugen20_be_ioctl(uint32_t cmd, void *data) +{ + int f; + int err; + + f = open("/dev/usb", O_RDONLY); + if (f < 0) + return (LIBUSB20_ERROR_OTHER); + err = ioctl(f, cmd, data); + if (err == -1) { + if (errno == EPERM) { + err = LIBUSB20_ERROR_ACCESS; + } else { + err = LIBUSB20_ERROR_OTHER; + } + } + close(f); + return (err); +} + +static int +ugen20_be_do_perm(uint32_t get_cmd, uint32_t set_cmd, uint8_t bus, + uint8_t dev, uint8_t iface, uid_t *uid, + gid_t *gid, mode_t *mode) +{ + struct usb2_dev_perm perm = { /* zero */ }; + int err; + + perm.bus_index = bus; + perm.dev_index = dev; + perm.iface_index = iface; + + err = ugen20_be_ioctl(get_cmd, &perm); + if (err) + return (err); + + if (set_cmd == 0) { + if (uid) + *uid = perm.user_id; + if (gid) + *gid = perm.group_id; + if (mode) + *mode = perm.mode; + return (0); + } + if (uid) + perm.user_id = *uid; + if (gid) + perm.group_id = *gid; + if (mode) + perm.mode = *mode; + + return (ugen20_be_ioctl(set_cmd, &perm)); +} + +static int +ugen20_bus_set_owner(struct libusb20_backend *pbe, + uint8_t bus, uid_t user, gid_t group) +{ + return (ugen20_be_do_perm(USB_GET_BUS_PERM, USB_SET_BUS_PERM, + bus, 0, 0, &user, &group, NULL)); +} + +static int +ugen20_bus_get_owner(struct libusb20_backend *pbe, uint8_t bus, + uid_t *user, gid_t *group) +{ + return (ugen20_be_do_perm(USB_GET_BUS_PERM, 0, + bus, 0, 0, user, group, NULL)); +} + +static int +ugen20_bus_set_perm(struct libusb20_backend *pbe, + uint8_t bus, mode_t mode) +{ + return (ugen20_be_do_perm(USB_GET_BUS_PERM, USB_SET_BUS_PERM, + bus, 0, 0, NULL, NULL, &mode)); +} + +static int +ugen20_bus_get_perm(struct libusb20_backend *pbe, + uint8_t bus, mode_t *mode) +{ + return (ugen20_be_do_perm(USB_GET_BUS_PERM, 0, + bus, 0, 0, NULL, NULL, mode)); +} + +static int +ugen20_dev_get_iface_owner(struct libusb20_device *pdev, + uint8_t iface_index, uid_t *user, gid_t *group) +{ + return (ugen20_be_do_perm(USB_GET_IFACE_PERM, 0, + pdev->bus_number, pdev->device_address, iface_index, + user, group, NULL)); +} + +static int +ugen20_dev_get_iface_perm(struct libusb20_device *pdev, + uint8_t iface_index, mode_t *mode) +{ + return (ugen20_be_do_perm(USB_GET_IFACE_PERM, 0, + pdev->bus_number, pdev->device_address, iface_index, + NULL, NULL, mode)); +} + +static int +ugen20_dev_get_owner(struct libusb20_device *pdev, + uid_t *user, gid_t *group) +{ + return (ugen20_be_do_perm(USB_GET_DEVICE_PERM, 0, + pdev->bus_number, pdev->device_address, 0, + user, group, NULL)); +} + +static int +ugen20_dev_get_perm(struct libusb20_device *pdev, mode_t *mode) +{ + return (ugen20_be_do_perm(USB_GET_DEVICE_PERM, 0, + pdev->bus_number, pdev->device_address, 0, + NULL, NULL, mode)); +} + +static int +ugen20_dev_set_iface_owner(struct libusb20_device *pdev, + uint8_t iface_index, uid_t user, gid_t group) +{ + return (ugen20_be_do_perm(USB_GET_IFACE_PERM, USB_SET_IFACE_PERM, + pdev->bus_number, pdev->device_address, iface_index, + &user, &group, NULL)); +} + +static int +ugen20_dev_set_iface_perm(struct libusb20_device *pdev, + uint8_t iface_index, mode_t mode) +{ + return (ugen20_be_do_perm(USB_GET_IFACE_PERM, USB_SET_IFACE_PERM, + pdev->bus_number, pdev->device_address, iface_index, + NULL, NULL, &mode)); +} + +static int +ugen20_root_get_dev_quirk(struct libusb20_backend *pbe, + uint16_t index, struct libusb20_quirk *pq) +{ + struct usb2_gen_quirk q; + int err; + + memset(&q, 0, sizeof(q)); + + q.index = index; + + err = ugen20_be_ioctl(USB_DEV_QUIRK_GET, &q); + + if (err) { + if (errno == EINVAL) { + return (LIBUSB20_ERROR_NOT_FOUND); + } + } else { + pq->vid = q.vid; + pq->pid = q.pid; + pq->bcdDeviceLow = q.bcdDeviceLow; + pq->bcdDeviceHigh = q.bcdDeviceHigh; + strlcpy(pq->quirkname, q.quirkname, sizeof(pq->quirkname)); + } + return (err); +} + +static int +ugen20_root_get_quirk_name(struct libusb20_backend *pbe, uint16_t index, + struct libusb20_quirk *pq) +{ + struct usb2_gen_quirk q; + int err; + + memset(&q, 0, sizeof(q)); + + q.index = index; + + err = ugen20_be_ioctl(USB_QUIRK_NAME_GET, &q); + + if (err) { + if (errno == EINVAL) { + return (LIBUSB20_ERROR_NOT_FOUND); + } + } else { + strlcpy(pq->quirkname, q.quirkname, sizeof(pq->quirkname)); + } + return (err); +} + +static int +ugen20_root_add_dev_quirk(struct libusb20_backend *pbe, + struct libusb20_quirk *pq) +{ + struct usb2_gen_quirk q; + int err; + + memset(&q, 0, sizeof(q)); + + q.vid = pq->vid; + q.pid = pq->pid; + q.bcdDeviceLow = pq->bcdDeviceLow; + q.bcdDeviceHigh = pq->bcdDeviceHigh; + strlcpy(q.quirkname, pq->quirkname, sizeof(q.quirkname)); + + err = ugen20_be_ioctl(USB_DEV_QUIRK_ADD, &q); + if (err) { + if (errno == ENOMEM) { + return (LIBUSB20_ERROR_NO_MEM); + } + } + return (err); +} + +static int +ugen20_root_remove_dev_quirk(struct libusb20_backend *pbe, + struct libusb20_quirk *pq) +{ + struct usb2_gen_quirk q; + int err; + + memset(&q, 0, sizeof(q)); + + q.vid = pq->vid; + q.pid = pq->pid; + q.bcdDeviceLow = pq->bcdDeviceLow; + q.bcdDeviceHigh = pq->bcdDeviceHigh; + strlcpy(q.quirkname, pq->quirkname, sizeof(q.quirkname)); + + err = ugen20_be_ioctl(USB_DEV_QUIRK_REMOVE, &q); + if (err) { + if (errno == EINVAL) { + return (LIBUSB20_ERROR_NOT_FOUND); + } + } + return (err); +} + +static int +ugen20_dev_set_owner(struct libusb20_device *pdev, + uid_t user, gid_t group) +{ + return (ugen20_be_do_perm(USB_GET_DEVICE_PERM, USB_SET_DEVICE_PERM, + pdev->bus_number, pdev->device_address, 0, + &user, &group, NULL)); +} + +static int +ugen20_dev_set_perm(struct libusb20_device *pdev, mode_t mode) +{ + return (ugen20_be_do_perm(USB_GET_DEVICE_PERM, USB_SET_DEVICE_PERM, + pdev->bus_number, pdev->device_address, 0, + NULL, NULL, &mode)); +} + +static int +ugen20_root_set_owner(struct libusb20_backend *pbe, + uid_t user, gid_t group) +{ + return (ugen20_be_do_perm(USB_GET_ROOT_PERM, USB_SET_ROOT_PERM, 0, 0, 0, + &user, &group, NULL)); +} + +static int +ugen20_root_get_owner(struct libusb20_backend *pbe, uid_t *user, gid_t *group) +{ + return (ugen20_be_do_perm(USB_GET_ROOT_PERM, 0, 0, 0, 0, + user, group, NULL)); +} + +static int +ugen20_root_set_perm(struct libusb20_backend *pbe, mode_t mode) +{ + return (ugen20_be_do_perm(USB_GET_ROOT_PERM, USB_SET_ROOT_PERM, 0, 0, 0, + NULL, NULL, &mode)); +} + +static int +ugen20_root_get_perm(struct libusb20_backend *pbe, mode_t *mode) +{ + return (ugen20_be_do_perm(USB_GET_ROOT_PERM, 0, 0, 0, 0, + NULL, NULL, mode)); +} diff --git a/share/man/man4/usb2_bluetooth.4 b/share/man/man4/usb2_bluetooth.4 new file mode 100644 index 000000000000..8b2c0fc5167a --- /dev/null +++ b/share/man/man4/usb2_bluetooth.4 @@ -0,0 +1,65 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_BLUETOOTH 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_bluetooth +. +.Nd "USB bluetooth container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_bluetooth" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_bluetooth_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB bluetooth drivers. +. +When you plug an USB bluetooth device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_controller.4 b/share/man/man4/usb2_controller.4 new file mode 100644 index 000000000000..998d0dccaf78 --- /dev/null +++ b/share/man/man4/usb2_controller.4 @@ -0,0 +1,65 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_CONTROLLER 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_controller +. +.Nd "USB controller container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_controller" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_controller_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB Host and Device side +controller drivers. +. +When you plug an USB controller the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_core.4 b/share/man/man4/usb2_core.4 new file mode 100644 index 000000000000..318b4fc4125a --- /dev/null +++ b/share/man/man4/usb2_core.4 @@ -0,0 +1,630 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 20, 2008 +.Dt USB2_CORE 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_core +. +.Nd "USB core functions" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_core" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_core_load="YES" +.Ed +. +.Pp +Here is a list of commonly used functions: +.Pp +. +.Ft "usb2_error_t" +.Fo "usb2_transfer_setup" +.Fa "udev" +.Fa "ifaces" +.Fa "pxfer" +.Fa "setup_start" +.Fa "n_setup" +.Fa "priv_sc" +.Fa "priv_mtx" +.Fc +. +.Ft "void" +.Fo "usb2_transfer_unsetup" +.Fa "pxfer" +.Fa "n_setup" +.Fc +. +.Ft "void" +.Fo "usb2_transfer_start" +.Fa "xfer" +.Fc +. +.Ft "void" +.Fo "usb2_transfer_stop" +.Fa "xfer" +.Fc +. +.Ft "void" +.Fo "usb2_transfer_drain" +.Fa "xfer" +.Fc +. +. +.Sh DESCRIPTION +The +.Nm +module implements the core functionality of the USB standard and many +helper functions to make USB device driver programming easier and more +safe. +. +The +.Nm +module supports both USB Host and USB Device side mode! +. +.Sh USB TRANSFER MANAGEMENT FUNCTIONS +The USB standard defines four types of USB transfers. +. +Control transfers, Bulk transfers, Interrupt transfers and Isochronous +transfers. +. +All the transfer types are managed using the following five functions: +. +.Pp +. +.Fn usb2_transfer_setup +This function will allocate memory for and initialise an array of USB +transfers and all required DMA memory. +. +This function can sleep or block waiting for resources to become +available. +.Fa udev +is a pointer to "struct usb2_device". +.Fa ifaces +is an array of interface index numbers to use. See "if_index". +.Fa pxfer +is a pointer to an array of USB transfer pointers that are initialized +to NULL, and then pointed to allocated USB transfers. +.Fa setup_start +is a pointer to an array of USB config structures. +.Fa n_setup +is a number telling the USB system how many USB transfers should be +setup. +.Fa priv_sc +is the private softc pointer, which will be used to initialize +"xfer->priv_sc". +.Fa priv_mtx +is the private mutex protecting the transfer structure and the +softc. This pointer is used to initialize "xfer->priv_mtx". +This function returns +zero upon success. A non-zero return value indicates failure. +. +.Pp +. +.Fn usb2_transfer_unsetup +This function will release the given USB transfers and all allocated +resources associated with these USB transfers. +.Fa pxfer +is a pointer to an array of USB transfer pointers, that may be NULL, +that should be freed by the USB system. +.Fa n_setup +is a number telling the USB system how many USB transfers should be +unsetup. +. +This function can sleep waiting for USB transfers to complete. +. +This function is NULL safe with regard to the USB transfer structure +pointer. +. +It is not allowed to call this function from the USB transfer +callback. +. +.Pp +. +.Fn usb2_transfer_start +This function will start the USB transfer pointed to by +.Fa xfer, +if not already started. +. +This function is always non-blocking and must be called with the +so-called private USB mutex locked. +. +This function is NULL safe with regard to the USB transfer structure +pointer. +. +.Pp +. +.Fn usb2_transfer_stop +This function will stop the USB transfer pointed to by +.Fa xfer, +if not already stopped. +. +This function is always non-blocking and must be called with the +so-called private USB mutex locked. +. +This function can return before the USB callback has been called. +. +This function is NULL safe with regard to the USB transfer structure +pointer. +. +If the transfer was in progress, the callback will called with +"USB_ST_ERROR" and "xfer->error = USB_ERR_CANCELLED". +. +.Pp +. +.Fn usb2_transfer_drain +This function will stop an USB transfer, if not already stopped and +wait for any additional USB hardware operations to complete. +. +Buffers that are loaded into DMA using "usb2_set_frame_data()" can +safely be freed after that this function has returned. +. +This function can block the caller and will not return before the USB +callback has been called. +. +This function is NULL safe with regard to the USB transfer structure +pointer. +. +.Sh USB TRANSFER CALLBACK +. +The USB callback has three states. +. +USB_ST_SETUP, USB_ST_TRANSFERRED and USB_ST_ERROR. USB_ST_SETUP is the +initial state. +. +After the callback has been called with this state it will always be +called back at a later stage in one of the other two states. +. +In the USB_ST_ERROR state the "error" field of the USB transfer +structure is set to the error cause. +. +The USB callback should not restart the USB transfer in case the error +cause is USB_ERR_CANCELLED. +. +The USB callback is protected from recursion. +. +That means one can start and stop whatever transfer from the callback +of another transfer one desires. +. +Also the transfer that is currently called back. +. +Recursion is handled like this that when the callback that wants to +recurse returns it is called one more time. +. +. +.Pp +. +.Fn usb2_start_hardware +This function should only be called from within the USB callback and +is used to start the USB hardware. +. +Typical parameters that should be set in the USB transfer structure +before this function is called are "frlengths[]", "nframes" and +"frbuffers[]". +. +An USB transfer can have multiple frames consisting of one or more USB +packets making up an I/O vector for all USB transfer types. +. +After the USB transfer is complete "frlengths[]" is updated to the +actual USB transfer length for the given frame. +.Bd -literal -offset indent +void +usb2_default_callback(struct usb2_xfer *xfer) +{ + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + /* + * Setup xfer->frlengths[], xfer->nframes + * and write data to xfer->frbuffers[], if any + */ + usb2_start_hardware(xfer); + break; + + case USB_ST_TRANSFERRED: + /* + * Read data from xfer->frbuffers[], if any. + * "xfer->frlengths[]" should now have been + * updated to the actual length. + */ + break; + + default: /* Error */ + /* + * Print error message and clear stall + * for example. + */ + break; + } + /* + * Here it is safe to do something without the private + * USB mutex locked. + */ + return; +} +.Ed +. +.Sh USB CONTROL TRANSFERS +An USB control transfer has three parts. +. +First the SETUP packet, then DATA packet(s) and then a STATUS +packet. +. +The SETUP packet is always pointed to by "xfer->frbuffers[0]" and the +length is stored in "xfer->frlengths[0]" also if there should not be +sent any SETUP packet! If an USB control transfer has no DATA stage, +then "xfer->nframes" should be set to 1. +. +Else the default value is "xfer->nframes" equal to 2. +. +.Bd -literal -offset indent + +Example1: SETUP + STATUS + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + usb2_start_hardware(xfer); + +Example2: SETUP + DATA + STATUS + xfer->nframes = 2; + xfer->frlenghts[0] = 8; + xfer->frlenghts[1] = 1; + usb2_start_hardware(xfer); + +Example3: SETUP + DATA + STATUS - split +1st callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + usb2_start_hardware(xfer); + +2nd callback: + /* IMPORTANT: frbuffers[0] must still point at the setup packet! */ + xfer->nframes = 2; + xfer->frlenghts[0] = 0; + xfer->frlenghts[1] = 1; + usb2_start_hardware(xfer); + +Example4: SETUP + STATUS - split +1st callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + xfer->flags.manual_status = 1; + usb2_start_hardware(xfer); + +2nd callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 0; + xfer->flags.manual_status = 0; + usb2_start_hardware(xfer); + +.Ed +.Sh USB TRANSFER CONFIG +To simply the search for endpoints the +.Nm +module defines a USB config structure where it is possible to specify +the characteristics of the wanted endpoint. +.Bd -literal -offset indent + +struct usb2_config { + bufsize, + callback + direction, + endpoint, + frames, + index flags, + interval, + timeout, + type, +}; + +.Ed +. +.Pp +.Fa type +field selects the USB pipe type. +. +Valid values are: UE_INTERRUPT, UE_CONTROL, UE_BULK, +UE_ISOCHRONOUS. +. +The special value UE_BULK_INTR will select BULK and INTERRUPT pipes. +. +This field is mandatory. +. +.Pp +.Fa endpoint +field selects the USB endpoint number. +. +A value of 0xFF, "-1" or "UE_ADDR_ANY" will select the first matching +endpoint. +. +This field is mandatory. +. +.Pp +.Fa direction +field selects the USB endpoint direction. +. +A value of "UE_DIR_ANY" will select the first matching endpoint. +. +Else valid values are: "UE_DIR_IN" and "UE_DIR_OUT". +. +"UE_DIR_IN" and "UE_DIR_OUT" can be binary OR'ed by "UE_DIR_SID" which +means that the direction will be swapped in case of +USB_MODE_DEVICE. +. +Note that "UE_DIR_IN" refers to the data transfer direction of the +"IN" tokens and "UE_DIR_OUT" refers to the data transfer direction of +the "OUT" tokens. +. +This field is mandatory. +. +.Pp +.Fa interval +field selects the interrupt interval. +. +The value of this field is given in milliseconds and is independent of +device speed. +. +Depending on the endpoint type, this field has different meaning: +.Bl -tag +.It UE_INTERRUPT +"0" use the default interrupt interval based on endpoint descriptor. +"Else" use the given value for polling rate. +.It UE_ISOCHRONOUS +"0" use default. "Else" the value is ignored. +.It UE_BULK +.It UE_CONTROL +"0" no transfer pre-delay. "Else" a delay as given by this field in +milliseconds is inserted before the hardware is started when +"usb2_start_hardware()" is called. +.Pp +NOTE: The transfer timeout, if any, is started after that the +pre-delay has elapsed! +.El +. +.Pp +.Fa timeout +field, if non-zero, will set the transfer timeout in milliseconds. If +the "timeout" field is zero and the transfer type is ISOCHRONOUS a +timeout of 250ms will be used. +. +.Pp +.Fa frames +field sets the maximum number of frames. If zero is specified it will +yield the following results: +.Bl -tag +.It UE_BULK +xfer->nframes = 1; +.It UE_INTERRUPT +xfer->nframes = 1; +.It UE_CONTROL +xfer->nframes = 2; +.It UE_ISOCHRONOUS +Not allowed. Will cause an error. +.El +. +.Pp +.Fa ep_index +field allows you to give a number, in case more endpoints match the +description, that selects which matching "ep_index" should be used. +. +.Pp +.Fa if_index +field allows you to select which of the interface numbers in the +"ifaces" array parameter passed to "usb2_transfer_setup" that should +be used when setting up the given USB transfer. +. +.Pp +.Fa flags +field has type "struct usb2_xfer_flags" and allows one to set initial +flags an USB transfer. Valid flags are: +.Bl -tag +.It force_short_xfer +This flag forces the last transmitted USB packet to be short. A short +packet has a length of less than "xfer->max_packet_size", which +derives from "wMaxPacketSize". This flag can be changed during +operation. +.It short_xfer_ok +This flag allows the received transfer length, "xfer->actlen" to be +less than "xfer->sumlen" upon completion of a transfer. This flag can +be changed during operation. +.It pipe_bof +This flag causes a failing USB transfer to remain first in the PIPE +queue except in the case of "xfer->error" equal to +"USB_ERR_CANCELLED". No other USB transfers in the affected PIPE queue +will be started until either: +.Bl -tag +.It 1 +The failing USB transfer is stopped using "usb2_transfer_stop()". +.It 2 +The failing USB transfer performs a successful transfer. +.El +The purpose of this flag is to avoid races when multiple transfers are +queued for execution on an USB endpoint, and the first executing +transfer fails leading to the need for clearing of stall for +example. +. +In this case this flag is used to prevent the following USB transfers +from being executed at the same time the clear-stall command is +executed on the USB control endpoint. +. +This flag can be changed during operation. +.Pp +"BOF" is short for "Block On Failure" +.Pp +NOTE: This flag should be set on all BULK and INTERRUPT USB transfers +which use an endpoint that can be shared between userland and kernel. +. +. +.It proxy_buffer +Setting this flag will cause that the total buffer size will be +rounded up to the nearest atomic hardware transfer size. +. +The maximum data length of any USB transfer is always stored in the +"xfer->max_data_length". +. +For control transfers the USB kernel will allocate additional space +for the 8-bytes of SETUP header. +. +These 8-bytes are not counted by the "xfer->max_data_length" +variable. +. +This flag can not be changed during operation. +. +. +.It ext_buffer +Setting this flag will cause that no data buffer will be +allocated. +. +Instead the USB client must supply a data buffer. +. +This flag can not be changed during operation. +. +. +.It manual_status +Setting this flag prevents an USB STATUS stage to be appended to the +end of the USB control transfer. +. +If no control data is transferred this flag must be cleared. +. +Else an error will be returned to the USB callback. +. +This flag is mostly useful for the USB device side. +. +This flag can be changed during operation. +. +. +.It no_pipe_ok +Setting this flag causes the USB_ERR_NO_PIPE error to be ignored. This +flag can not be changed during operation. +. +. +.It stall_pipe +.Bl -tag +.It Device Side Mode +Setting this flag will cause STALL pids to be sent to the endpoint +belonging to this transfer before the transfer is started. +. +The transfer is started at the moment the host issues a clear-stall +command on the STALL'ed endpoint. +. +This flag can be changed during operation. +.It Host Side Mode +Setting this flag will cause a clear-stall control request to be +executed on the endpoint before the USB transfer is started. +.El +.Pp +If this flag is changed outside the USB callback function you have to +use the "usb2_transfer_set_stall()" and "usb2_transfer_clear_stall()" +functions ! +. +.El +.Pp +.Fa bufsize +field sets the total buffer size in bytes. +. +If this field is zero, "wMaxPacketSize" will be used, multiplied by +the "frames" field if the transfer type is ISOCHRONOUS. +. +This is useful for setting up interrupt pipes. +. +This field is mandatory. +.Pp +NOTE: For control transfers "bufsize" includes the length of the +request structure. +. +.Pp +.Fa callback +pointer sets the USB callback. This field is mandatory. +. +. +.Sh USB LINUX COMPAT LAYER +The +.Nm +module supports the Linux USB API. +. +. +. +. +.Sh USB SECURITY MODEL +. +. +The +.Nm +module implements fine grained read and write access based on username +and group. +. +Access is granted at four levels: +. +.Bl -tag +.It Level 4 - USB interface +USB interfaces can be given individual access rights. +.It Level 3 - USB device +USB devices can be given individual access rights. +.It Level 2 - USB BUS +USB busses can be given individual access rights. +.It Level 1 - USB +USB as a whole can be given individual access rights. +.El +.Pp +The +.Nm +module will search for access rights starting at level 4 continuing +downwards to USB at level 1. +. +For critical applications you should be aware that the outgoing serial +BUS traffic will be broadcasted to all USB devices. +. +For absolute security USB devices that require different access rights +should not be placed on the same USB BUS or controller. +. +If connected to the same USB bus, it is possible that a USB device can +sniff and intercept the communication of another USB device. +. +Using USB HUBs will not solve this problem. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usbconfig 8 +.Sh STANDARDS +The +.Nm +module complies with the USB 2.0 standard. +.Sh HISTORY +The +.Nm +module has been inspired by the NetBSD USB stack initially written by +Lennart Augustsson. The +.Nm +module was written by +.An Hans Petter Selasky Aq hselasky@freebsd.org . diff --git a/share/man/man4/usb2_ethernet.4 b/share/man/man4/usb2_ethernet.4 new file mode 100644 index 000000000000..f951677c70f9 --- /dev/null +++ b/share/man/man4/usb2_ethernet.4 @@ -0,0 +1,65 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_ETHERNET 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_ethernet +. +.Nd "USB ethernet container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_ethernet" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_ethernet_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB ethernet drivers. +. +When you plug an USB ethernet device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_image.4 b/share/man/man4/usb2_image.4 new file mode 100644 index 000000000000..30c9ef60dd7b --- /dev/null +++ b/share/man/man4/usb2_image.4 @@ -0,0 +1,65 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_IMAGE 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_image +. +.Nd "USB image container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_image" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_image_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB image scanner drivers. +. +When you plug an USB image scanner device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_input.4 b/share/man/man4/usb2_input.4 new file mode 100644 index 000000000000..cf486039cb75 --- /dev/null +++ b/share/man/man4/usb2_input.4 @@ -0,0 +1,66 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_INPUT 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_input +. +.Nd "USB input container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_input" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_input_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB input drivers. +. +When you plug an USB input device, like USB mouse, USB keyboard and USB +HID device, the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_misc.4 b/share/man/man4/usb2_misc.4 new file mode 100644 index 000000000000..e658f3158276 --- /dev/null +++ b/share/man/man4/usb2_misc.4 @@ -0,0 +1,66 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_MISC 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_misc +. +.Nd "USB miscellaneous container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_misc" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_misc_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for various USB drivers that does not fit +into into any other USB container module. +. +When you plug an USB miscellaneous device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_ndis.4 b/share/man/man4/usb2_ndis.4 new file mode 100644 index 000000000000..4173c1bdfd65 --- /dev/null +++ b/share/man/man4/usb2_ndis.4 @@ -0,0 +1,66 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_NDIS 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_ndis +. +.Nd "USB NDIS container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_ndis" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_ndis_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for various USB drivers that does not fit +into into any other USB container module. +. +When you plug an USB NDIS device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_quirk.4 b/share/man/man4/usb2_quirk.4 new file mode 100644 index 000000000000..55c5859991d5 --- /dev/null +++ b/share/man/man4/usb2_quirk.4 @@ -0,0 +1,64 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_QUIRK 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_quirk +. +.Nd "USB quirk container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_quirk" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_quirk_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB quirks. +. +USB quirks are workarounds for specific USB device problems. The +.Nm +module can be dynamically loaded and unloaded at any time. +. +. +. +.Sh SEE ALSO +.Xr usb2_core 4 +.Xr usb2_controller 4 diff --git a/share/man/man4/usb2_serial.4 b/share/man/man4/usb2_serial.4 new file mode 100644 index 000000000000..d3512cfbf2c3 --- /dev/null +++ b/share/man/man4/usb2_serial.4 @@ -0,0 +1,66 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_SERIAL 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_serial +. +.Nd "USB serial container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_serial" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_serial_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB serial and USB parallel port +drivers. +. +When you plug an USB serial or USB parallel port device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_sound.4 b/share/man/man4/usb2_sound.4 new file mode 100644 index 000000000000..1e70ceac145c --- /dev/null +++ b/share/man/man4/usb2_sound.4 @@ -0,0 +1,65 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_SOUND 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_sound +. +.Nd "USB sound container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_sound" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_sound_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB sound and USB MIDI drivers. +. +When you plug an USB sound or USB MIDI device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_storage.4 b/share/man/man4/usb2_storage.4 new file mode 100644 index 000000000000..9f85207e16ab --- /dev/null +++ b/share/man/man4/usb2_storage.4 @@ -0,0 +1,65 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_STORAGE 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_storage +. +.Nd "USB storage container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_storage" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_storage_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB storage drivers. +. +When you plug an USB storage device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/share/man/man4/usb2_template.4 b/share/man/man4/usb2_template.4 new file mode 100644 index 000000000000..06dc611d0298 --- /dev/null +++ b/share/man/man4/usb2_template.4 @@ -0,0 +1,84 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_TEMPLATE 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_template +. +.Nd "USB templates" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_template" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_template_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module implements various USB templates that are needed when +programming an USB device side driver. +. +A USB template consists of an USB device descriptor, one or more USB +configuration descriptors, one or more USB interface descriptors, one +or more USB endpoint descriptors, USB strings and additional USB +descriptors. +. +The USB template module currently has templates for USB Mass Storage, +USB CDC Ethernet and Message Transfer Protocol. +. +USB templates are currently selected using the "hw.usb2.template" +sysctl. +. +The "hw.usb2.template" value can be changed at any time, but will not +have any effect until the USB device has been re-enumerated. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 +.Sh STANDARDS +The +.Nm +module complies with the USB 2.0 standard. +.Sh HISTORY +The +.Nm +module was written by +.An Hans Petter Selasky Aq hselasky@freebsd.org . diff --git a/share/man/man4/usb2_wlan.4 b/share/man/man4/usb2_wlan.4 new file mode 100644 index 000000000000..73ff75f24fc6 --- /dev/null +++ b/share/man/man4/usb2_wlan.4 @@ -0,0 +1,65 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd September 21, 2008 +.Dt USB2_WLAN 4 +.Os +. +.Sh NAME +. +. +.Nm usb2_wlan +. +.Nd "USB WLAN container module" +. +. +.Sh SYNOPSIS +To compile this module into the kernel, place the following line in +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usb2_wlan" +.Ed +.Pp +To load the module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usb2_wlan_load="YES" +.Ed +. +.Sh DESCRIPTION +The +.Nm +module is a container module for all USB WLAN drivers. +. +When you plug an USB WLAN device the +.Nm +module will try to load the required driver for your device +automatically. +. +. +. +.Sh SEE ALSO +.Xr usb2_controller 4 +.Xr usb2_core 4 diff --git a/sys/conf/files b/sys/conf/files index af7f0b0a369e..9b67a7342c25 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1366,6 +1366,145 @@ dev/usb/uscanner.c optional uscanner dev/usb/uslcom.c optional uslcom dev/usb/uvisor.c optional uvisor dev/usb/uvscom.c optional uvscom +# +# USB2 controller drivers +# +dev/usb2/controller/at91dci.c optional usb2_core usb2_controller usb2_controller_at91dci +dev/usb2/controller/at91dci_atmelarm.c optional usb2_core usb2_controller usb2_controller_at91dci at91rm9200 +dev/usb2/controller/musb2_otg.c optional usb2_core usb2_controller usb2_controller_musb +dev/usb2/controller/musb2_otg_atmelarm.c optional usb2_core usb2_controller usb2_controller_musb at91rm9200 +dev/usb2/controller/ehci2.c optional usb2_core usb2_controller usb2_controller_ehci +dev/usb2/controller/ehci2_pci.c optional usb2_core usb2_controller usb2_controller_ehci pci +dev/usb2/controller/ohci2.c optional usb2_core usb2_controller usb2_controller_ohci +dev/usb2/controller/ohci2_atmelarm.c optional usb2_core usb2_controller usb2_controller_ohci at91rm9200 +dev/usb2/controller/ohci2_pci.c optional usb2_core usb2_controller usb2_controller_ohci pci +dev/usb2/controller/uhci2.c optional usb2_core usb2_controller usb2_controller_uhci +dev/usb2/controller/uhci2_pci.c optional usb2_core usb2_controller usb2_controller_uhci pci +dev/usb2/controller/uss820dci.c optional usb2_core usb2_controller usb2_controller_uss820dci +dev/usb2/controller/uss820dci_atmelarm.c optional usb2_core usb2_controller usb2_controller_uss820dci at91rm9200 +dev/usb2/controller/usb2_controller.c optional usb2_core usb2_controller +# +# USB2 storage drivers +# +dev/usb2/storage/ata-usb2.c optional usb2_core usb2_storage usb2_storage_ata +dev/usb2/storage/umass2.c optional usb2_core usb2_storage usb2_storage_mass +dev/usb2/storage/urio2.c optional usb2_core usb2_storage usb2_storage_rio +dev/usb2/storage/usb2_storage.c optional usb2_core usb2_storage +dev/usb2/storage/ustorage2_fs.c optional usb2_core usb2_storage usb2_storage_fs +# +# USB2 NDIS driver +# +dev/usb2/ndis/if_ndis_usb2.c optional usb2_core usb2_ndis +dev/usb2/ndis/usb2_ndis.c optional usb2_core usb2_ndis +# +# USB2 core +# +dev/usb2/core/usb2_busdma.c optional usb2_core +dev/usb2/core/usb2_compat_linux.c optional usb2_core +dev/usb2/core/usb2_config_td.c optional usb2_core +dev/usb2/core/usb2_core.c optional usb2_core +dev/usb2/core/usb2_debug.c optional usb2_core +dev/usb2/core/usb2_dev.c optional usb2_core +dev/usb2/core/usb2_device.c optional usb2_core +dev/usb2/core/usb2_dynamic.c optional usb2_core +dev/usb2/core/usb2_error.c optional usb2_core +dev/usb2/core/usb2_generic.c optional usb2_core +dev/usb2/core/usb2_handle_request.c optional usb2_core +dev/usb2/core/usb2_hid.c optional usb2_core +dev/usb2/core/usb2_hub.c optional usb2_core +dev/usb2/core/usb2_if.m optional usb2_core +dev/usb2/core/usb2_lookup.c optional usb2_core +dev/usb2/core/usb2_mbuf.c optional usb2_core +dev/usb2/core/usb2_msctest.c optional usb2_core +dev/usb2/core/usb2_parse.c optional usb2_core +dev/usb2/core/usb2_process.c optional usb2_core +dev/usb2/core/usb2_request.c optional usb2_core +dev/usb2/core/usb2_sw_transfer.c optional usb2_core +dev/usb2/core/usb2_transfer.c optional usb2_core +dev/usb2/core/usb2_util.c optional usb2_core +# +# USB2 ethernet drivers +# +dev/usb2/ethernet/if_aue2.c optional usb2_core usb2_ethernet usb2_ethernet_aue +dev/usb2/ethernet/if_axe2.c optional usb2_core usb2_ethernet usb2_ethernet_axe +dev/usb2/ethernet/if_cdce2.c optional usb2_core usb2_ethernet usb2_ethernet_cdce +dev/usb2/ethernet/if_cue2.c optional usb2_core usb2_ethernet usb2_ethernet_cue +dev/usb2/ethernet/if_kue2.c optional usb2_core usb2_ethernet usb2_ethernet_kue +dev/usb2/ethernet/if_rue2.c optional usb2_core usb2_ethernet usb2_ethernet_rue +dev/usb2/ethernet/if_udav2.c optional usb2_core usb2_ethernet usb2_ethernet_udav +dev/usb2/ethernet/usb2_ethernet.c optional usb2_core usb2_ethernet +# +# USB2 WLAN drivers +# +dev/usb2/wlan/if_rum2.c optional usb2_core usb2_wlan usb2_wlan_rum +dev/usb2/wlan/if_ural2.c optional usb2_core usb2_wlan usb2_wlan_ral +dev/usb2/wlan/if_zyd2.c optional usb2_core usb2_wlan usb2_wlan_zyd +dev/usb2/wlan/usb2_wlan.c optional usb2_core usb2_wlan +# +# USB2 serial and parallel port drivers +# +dev/usb2/serial/uark2.c optional usb2_core usb2_serial usb2_serial_ark +dev/usb2/serial/ubsa2.c optional usb2_core usb2_serial usb2_serial_bsa +dev/usb2/serial/ubser2.c optional usb2_core usb2_serial usb2_serial_bser +dev/usb2/serial/uchcom2.c optional usb2_core usb2_serial usb2_serial_chcom +dev/usb2/serial/ucycom2.c optional usb2_core usb2_serial usb2_serial_cycom +dev/usb2/serial/ufoma2.c optional usb2_core usb2_serial usb2_serial_foma +dev/usb2/serial/uftdi2.c optional usb2_core usb2_serial usb2_serial_ftdi +dev/usb2/serial/ugensa2.c optional usb2_core usb2_serial usb2_serial_gensa +dev/usb2/serial/uipaq2.c optional usb2_core usb2_serial usb2_serial_ipaq +dev/usb2/serial/ulpt2.c optional usb2_core usb2_serial usb2_serial_lpt +dev/usb2/serial/umct2.c optional usb2_core usb2_serial usb2_serial_mct +dev/usb2/serial/umodem2.c optional usb2_core usb2_serial usb2_serial_modem +dev/usb2/serial/umoscom2.c optional usb2_core usb2_serial usb2_serial_moscom +dev/usb2/serial/uplcom2.c optional usb2_core usb2_serial usb2_serial_plcom +dev/usb2/serial/usb2_serial.c optional usb2_core usb2_serial +dev/usb2/serial/uvisor2.c optional usb2_core usb2_serial usb2_serial_visor +dev/usb2/serial/uvscom2.c optional usb2_core usb2_serial usb2_serial_vscom +# +# USB2 bluetooth drivers +# +dev/usb2/bluetooth/usb2_bluetooth.c optional usb2_core usb2_bluetooth +dev/usb2/bluetooth/ng_ubt2.c optional usb2_core usb2_bluetooth usb2_bluetooth_ng +dev/usb2/bluetooth/ubtbcmfw2.c optional usb2_core usb2_bluetooth usb2_bluetooth_fw + +# +# USB2 misc drivers +# +dev/usb2/misc/usb2_misc.c optional usb2_core usb2_misc +dev/usb2/misc/ufm2.c optional usb2_core usb2_misc usb2_misc_fm +dev/usb2/misc/udbp2.c optional usb2_core usb2_misc usb2_misc_dbp +# +# USB2 input drivers +# +dev/usb2/input/uhid2.c optional usb2_core usb2_input usb2_input_hid +dev/usb2/input/ukbd2.c optional usb2_core usb2_input usb2_input_kbd +dev/usb2/input/ums2.c optional usb2_core usb2_input usb2_input_ms +dev/usb2/input/usb2_input.c optional usb2_core usb2_input +# +# USB2 quirks +# +dev/usb2/quirk/usb2_quirk.c optional usb2_core usb2_quirk +# +# USB2 templates +# +dev/usb2/template/usb2_template.c optional usb2_core usb2_template +dev/usb2/template/usb2_template_cdce.c optional usb2_core usb2_template +dev/usb2/template/usb2_template_msc.c optional usb2_core usb2_template +dev/usb2/template/usb2_template_mtp.c optional usb2_core usb2_template +# +# USB2 image drivers +# +dev/usb2/image/usb2_image.c optional usb2_core usb2_image +dev/usb2/image/uscanner2.c optional usb2_core usb2_image usb2_scanner +# +# USB2 sound and MIDI drivers +# +dev/usb2/sound/usb2_sound.c optional usb2_core usb2_sound +dev/usb2/sound/uaudio2.c optional usb2_core usb2_sound +dev/usb2/sound/uaudio2_pcm.c optional usb2_core usb2_sound +# +# USB2 END +# dev/utopia/idtphy.c optional utopia dev/utopia/suni.c optional utopia dev/utopia/utopia.c optional utopia diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index ffc0ffed1649..464330ae521e 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -570,13 +570,26 @@ chn_read(struct pcm_channel *c, struct uio *buf) void chn_intr(struct pcm_channel *c) { - CHN_LOCK(c); + uint8_t do_unlock; + if (CHN_LOCK_OWNED(c)) { + /* + * Allow sound drivers to call this function with + * "CHN_LOCK()" locked: + */ + do_unlock = 0; + } else { + do_unlock = 1; + CHN_LOCK(c); + } c->interrupts++; if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); - CHN_UNLOCK(c); + if (do_unlock) { + CHN_UNLOCK(c); + } + return; } u_int32_t diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h index 65175a38db74..8f9f8f662d40 100644 --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -258,11 +258,13 @@ int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); #endif #ifdef USING_MUTEX +#define CHN_LOCK_OWNED(c) mtx_owned((struct mtx *)((c)->lock)) #define CHN_LOCK(c) mtx_lock((struct mtx *)((c)->lock)) #define CHN_UNLOCK(c) mtx_unlock((struct mtx *)((c)->lock)) #define CHN_TRYLOCK(c) mtx_trylock((struct mtx *)((c)->lock)) #define CHN_LOCKASSERT(c) mtx_assert((struct mtx *)((c)->lock), MA_OWNED) #else +#define CHN_LOCK_OWNED(c) 0 #define CHN_LOCK(c) #define CHN_UNLOCK(c) #define CHN_TRYLOCK(c) diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c index c3ca4f0dea47..dd3b7bb3663f 100644 --- a/sys/dev/sound/pcm/mixer.c +++ b/sys/dev/sound/pcm/mixer.c @@ -589,7 +589,7 @@ mixer_delete(struct snd_mixer *m) KASSERT(m->type == MIXER_TYPE_SECONDARY, ("%s(): illegal mixer type=%d", __func__, m->type)); - snd_mtxlock(m->lock); + /* mixer uninit can sleep --hps */ MIXER_UNINIT(m); @@ -704,14 +704,24 @@ mixer_uninit(device_t dev) return EBUSY; } + /* destroy dev can sleep --hps */ + + snd_mtxunlock(m->lock); + pdev->si_drv1 = NULL; destroy_dev(pdev); + snd_mtxlock(m->lock); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) mixer_set(m, i, 0); mixer_setrecsrc(m, SOUND_MASK_MIC); + snd_mtxunlock(m->lock); + + /* mixer uninit can sleep --hps */ + MIXER_UNINIT(m); snd_mtxfree(m->lock); @@ -1280,3 +1290,16 @@ mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi) return (EINVAL); } + +/* + * Allow the sound driver to use the mixer lock to protect its mixer + * data: + */ +struct mtx * +mixer_get_lock(struct snd_mixer *m) +{ + if (m->lock == NULL) { + return (&Giant); + } + return (m->lock); +} diff --git a/sys/dev/sound/pcm/mixer.h b/sys/dev/sound/pcm/mixer.h index bcded4bd529b..d03338b198f6 100644 --- a/sys/dev/sound/pcm/mixer.h +++ b/sys/dev/sound/pcm/mixer.h @@ -56,6 +56,7 @@ void mix_setrealdev(struct snd_mixer *m, u_int32_t dev, u_int32_t realdev); u_int32_t mix_getparent(struct snd_mixer *m, u_int32_t dev); u_int32_t mix_getchild(struct snd_mixer *m, u_int32_t dev); void *mix_getdevinfo(struct snd_mixer *m); +struct mtx *mixer_get_lock(struct snd_mixer *m); extern int mixer_count; diff --git a/sys/dev/usb2/bluetooth/TODO.TXT b/sys/dev/usb2/bluetooth/TODO.TXT new file mode 100644 index 000000000000..b0d6695ca9db --- /dev/null +++ b/sys/dev/usb2/bluetooth/TODO.TXT @@ -0,0 +1,18 @@ +$Id: TODO,v 1.1 2002/11/24 19:46:56 max Exp $ +$FreeBSD$ + +1) SMP/Locking + + The code makes use of ng_send_fn() whenever possible. Just + need to verify and make sure i did it right + +2) Firmware upgrade + + According to Bluetooth spec device may present third interface + to perform firmware upgrade. 3Com USB Bluetooth dongle has + such interface. Need to implement set of Netgraph messages. + +3) Isochronous USB transfers (SCO data) + + Tried to fix isochrounous transfers, which are still disabled + by default. diff --git a/sys/dev/usb2/bluetooth/ng_ubt2.c b/sys/dev/usb2/bluetooth/ng_ubt2.c new file mode 100644 index 000000000000..1522ac9d2999 --- /dev/null +++ b/sys/dev/usb2/bluetooth/ng_ubt2.c @@ -0,0 +1,1774 @@ +/* + * ng_ubt.c + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * 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. + * + * $Id: ng_ubt.c,v 1.16 2003/10/10 19:15:06 max Exp $ + * $FreeBSD$ + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * USB methods + */ + +static device_probe_t ubt_probe; +static device_attach_t ubt_attach; +static device_detach_t ubt_detach; + +static devclass_t ubt_devclass; + +static device_method_t ubt_methods[] = { + DEVMETHOD(device_probe, ubt_probe), + DEVMETHOD(device_attach, ubt_attach), + DEVMETHOD(device_detach, ubt_detach), + {0, 0} +}; + +static driver_t ubt_driver = { + .name = "ubt", + .methods = ubt_methods, + .size = sizeof(struct ubt_softc), +}; + +/* + * Netgraph methods + */ + +static ng_constructor_t ng_ubt_constructor; +static ng_shutdown_t ng_ubt_shutdown; +static ng_newhook_t ng_ubt_newhook; +static ng_connect_t ng_ubt_connect; +static ng_disconnect_t ng_ubt_disconnect; +static ng_rcvmsg_t ng_ubt_rcvmsg; +static ng_rcvdata_t ng_ubt_rcvdata; + +/* Queue length */ +static const struct ng_parse_struct_field ng_ubt_node_qlen_type_fields[] = +{ + {"queue", &ng_parse_int32_type,}, + {"qlen", &ng_parse_int32_type,}, + {NULL,} +}; +static const struct ng_parse_type ng_ubt_node_qlen_type = { + &ng_parse_struct_type, + &ng_ubt_node_qlen_type_fields +}; + +/* Stat info */ +static const struct ng_parse_struct_field ng_ubt_node_stat_type_fields[] = +{ + {"pckts_recv", &ng_parse_uint32_type,}, + {"bytes_recv", &ng_parse_uint32_type,}, + {"pckts_sent", &ng_parse_uint32_type,}, + {"bytes_sent", &ng_parse_uint32_type,}, + {"oerrors", &ng_parse_uint32_type,}, + {"ierrors", &ng_parse_uint32_type,}, + {NULL,} +}; +static const struct ng_parse_type ng_ubt_node_stat_type = { + &ng_parse_struct_type, + &ng_ubt_node_stat_type_fields +}; + +/* Netgraph node command list */ +static const struct ng_cmdlist ng_ubt_cmdlist[] = { + { + NGM_UBT_COOKIE, + NGM_UBT_NODE_SET_DEBUG, + "set_debug", + &ng_parse_uint16_type, + NULL + }, + { + NGM_UBT_COOKIE, + NGM_UBT_NODE_GET_DEBUG, + "get_debug", + NULL, + &ng_parse_uint16_type + }, + { + NGM_UBT_COOKIE, + NGM_UBT_NODE_SET_QLEN, + "set_qlen", + &ng_ubt_node_qlen_type, + NULL + }, + { + NGM_UBT_COOKIE, + NGM_UBT_NODE_GET_QLEN, + "get_qlen", + &ng_ubt_node_qlen_type, + &ng_ubt_node_qlen_type + }, + { + NGM_UBT_COOKIE, + NGM_UBT_NODE_GET_STAT, + "get_stat", + NULL, + &ng_ubt_node_stat_type + }, + { + NGM_UBT_COOKIE, + NGM_UBT_NODE_RESET_STAT, + "reset_stat", + NULL, + NULL + }, + {0,} +}; + +/* Netgraph node type */ +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_UBT_NODE_TYPE, + .constructor = ng_ubt_constructor, + .rcvmsg = ng_ubt_rcvmsg, + .shutdown = ng_ubt_shutdown, + .newhook = ng_ubt_newhook, + .connect = ng_ubt_connect, + .rcvdata = ng_ubt_rcvdata, + .disconnect = ng_ubt_disconnect, + .cmdlist = ng_ubt_cmdlist +}; + +/* USB methods */ + +static usb2_callback_t ubt_ctrl_write_callback; +static usb2_callback_t ubt_intr_read_callback; +static usb2_callback_t ubt_intr_read_clear_stall_callback; +static usb2_callback_t ubt_bulk_read_callback; +static usb2_callback_t ubt_bulk_read_clear_stall_callback; +static usb2_callback_t ubt_bulk_write_callback; +static usb2_callback_t ubt_bulk_write_clear_stall_callback; +static usb2_callback_t ubt_isoc_read_callback; +static usb2_callback_t ubt_isoc_write_callback; + +static int ubt_modevent(module_t mod, int event, void *data); +static void ubt_intr_read_complete(node_p node, hook_p hook, void *arg1, int arg2); +static void ubt_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2); +static void ubt_isoc_read_complete(node_p node, hook_p hook, void *arg1, int arg2); + +/* USB config */ +static const struct usb2_config ubt_config_if_0[UBT_IF_0_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UBT_BULK_WRITE_BUFFER_SIZE, + .mh.flags = {.pipe_bof = 1,}, + .mh.callback = &ubt_bulk_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UBT_BULK_READ_BUFFER_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &ubt_bulk_read_callback, + }, + + [2] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0x110, /* bytes */ + .mh.callback = &ubt_intr_read_callback, + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = (sizeof(struct usb2_device_request) + UBT_CTRL_BUFFER_SIZE), + .mh.callback = &ubt_ctrl_write_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [4] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ubt_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ubt_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [6] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ubt_intr_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +/* USB config */ +static const struct usb2_config + ubt_config_if_1_full_speed[UBT_IF_1_N_TRANSFER] = { + + [0] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UBT_ISOC_NFRAMES, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ubt_isoc_read_callback, + }, + + [1] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UBT_ISOC_NFRAMES, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ubt_isoc_read_callback, + }, + + [2] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UBT_ISOC_NFRAMES, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ubt_isoc_write_callback, + }, + + [3] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UBT_ISOC_NFRAMES, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ubt_isoc_write_callback, + }, +}; + +/* USB config */ +static const struct usb2_config + ubt_config_if_1_high_speed[UBT_IF_1_N_TRANSFER] = { + + [0] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UBT_ISOC_NFRAMES * 8, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ubt_isoc_read_callback, + }, + + [1] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UBT_ISOC_NFRAMES * 8, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ubt_isoc_read_callback, + }, + + [2] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UBT_ISOC_NFRAMES * 8, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ubt_isoc_write_callback, + }, + + [3] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UBT_ISOC_NFRAMES * 8, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ubt_isoc_write_callback, + }, +}; + +/* + * Module + */ + +DRIVER_MODULE(ng_ubt, ushub, ubt_driver, ubt_devclass, ubt_modevent, 0); +MODULE_VERSION(ng_ubt, NG_BLUETOOTH_VERSION); +MODULE_DEPEND(ng_ubt, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); +MODULE_DEPEND(ng_ubt, usb2_bluetooth, 1, 1, 1); +MODULE_DEPEND(ng_ubt, usb2_core, 1, 1, 1); + +/**************************************************************************** + **************************************************************************** + ** USB specific + **************************************************************************** + ****************************************************************************/ + +/* + * Load/Unload the driver module + */ + +static int +ubt_modevent(module_t mod, int event, void *data) +{ + int error; + + switch (event) { + case MOD_LOAD: + error = ng_newtype(&typestruct); + if (error != 0) { + printf("%s: Could not register " + "Netgraph node type, error=%d\n", + NG_UBT_NODE_TYPE, error); + } + break; + + case MOD_UNLOAD: + error = ng_rmtype(&typestruct); + break; + + default: + error = EOPNOTSUPP; + break; + } + return (error); +} /* ubt_modevent */ + +/* + * If for some reason device should not be attached then put + * VendorID/ProductID pair into the list below. The format is + * as follows: + * + * { VENDOR_ID, PRODUCT_ID }, + * + * where VENDOR_ID and PRODUCT_ID are hex numbers. + */ +static const struct usb2_device_id ubt_ignore_devs[] = { + /* AVM USB Bluetooth-Adapter BlueFritz! v1.0 */ + {USB_VPI(USB_VENDOR_AVM, 0x2200, 0)}, +}; + +/* List of supported bluetooth devices */ +static const struct usb2_device_id ubt_devs[] = { + /* Generic Bluetooth class devices. */ + {USB_IFACE_CLASS(UDCLASS_WIRELESS), + USB_IFACE_SUBCLASS(UDSUBCLASS_RF), + USB_IFACE_PROTOCOL(UDPROTO_BLUETOOTH)}, + + /* AVM USB Bluetooth-Adapter BlueFritz! v2.0 */ + {USB_VPI(USB_VENDOR_AVM, 0x3800, 0)}, +}; + +/* + * Probe for a USB Bluetooth device + */ + +static int +ubt_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != 0) { + return (ENXIO); + } + if (usb2_lookup_id_by_uaa(ubt_ignore_devs, + sizeof(ubt_ignore_devs), uaa) == 0) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(ubt_devs, sizeof(ubt_devs), uaa)); +} + +/* + * Attach the device + */ + +static int +ubt_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ubt_softc *sc = device_get_softc(dev); + const struct usb2_config *isoc_setup; + struct usb2_endpoint_descriptor *ed; + uint16_t wMaxPacketSize; + uint8_t alt_index; + uint8_t iface_index; + uint8_t i; + uint8_t j; + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + /* + * Initialize device softc structure + */ + + /* state */ + sc->sc_debug = NG_UBT_WARN_LEVEL; + sc->sc_flags = 0; + NG_UBT_STAT_RESET(sc->sc_stat); + + /* control pipe */ + NG_BT_MBUFQ_INIT(&sc->sc_cmdq, UBT_DEFAULT_QLEN); + + /* bulk-out pipe */ + NG_BT_MBUFQ_INIT(&sc->sc_aclq, UBT_DEFAULT_QLEN); + + /* isoc-out pipe */ + NG_BT_MBUFQ_INIT(&sc->sc_scoq, + (usb2_get_speed(uaa->device) == USB_SPEED_HIGH) ? + (2 * UBT_ISOC_NFRAMES * 8) : + (2 * UBT_ISOC_NFRAMES)); + + /* isoc-in pipe */ + NG_BT_MBUFQ_INIT(&sc->sc_sciq, + (usb2_get_speed(uaa->device) == USB_SPEED_HIGH) ? + (2 * UBT_ISOC_NFRAMES * 8) : + (2 * UBT_ISOC_NFRAMES)); + + /* netgraph part */ + sc->sc_node = NULL; + sc->sc_hook = NULL; + + /* + * Configure Bluetooth USB device. Discover all required USB + * interfaces and endpoints. + * + * USB device must present two interfaces: + * 1) Interface 0 that has 3 endpoints + * 1) Interrupt endpoint to receive HCI events + * 2) Bulk IN endpoint to receive ACL data + * 3) Bulk OUT endpoint to send ACL data + * + * 2) Interface 1 then has 2 endpoints + * 1) Isochronous IN endpoint to receive SCO data + * 2) Isochronous OUT endpoint to send SCO data + * + * Interface 1 (with isochronous endpoints) has several alternate + * configurations with different packet size. + */ + + /* + * Interface 0 + */ + + mtx_init(&sc->sc_mtx, "ubt lock", NULL, MTX_DEF | MTX_RECURSE); + + iface_index = 0; + if (usb2_transfer_setup + (uaa->device, &iface_index, sc->sc_xfer_if_0, ubt_config_if_0, + UBT_IF_0_N_TRANSFER, sc, &sc->sc_mtx)) { + device_printf(dev, "Could not allocate transfers " + "for interface 0!\n"); + goto detach; + } + /* + * Interface 1 + * (search alternate settings, and find + * the descriptor with the largest + * wMaxPacketSize) + */ + isoc_setup = + ((usb2_get_speed(uaa->device) == USB_SPEED_HIGH) ? + ubt_config_if_1_high_speed : + ubt_config_if_1_full_speed); + + wMaxPacketSize = 0; + + /* search through all the descriptors looking for bidir mode */ + + alt_index = 0 - 1; + i = 0; + j = 0; + while (1) { + uint16_t temp; + + ed = usb2_find_edesc( + usb2_get_config_descriptor(uaa->device), 1, i, j); + if (ed == NULL) { + if (j == 0) { + /* end of interfaces */ + break; + } else { + /* next interface */ + j = 0; + i++; + continue; + } + } + temp = UGETW(ed->wMaxPacketSize); + if (temp > wMaxPacketSize) { + wMaxPacketSize = temp; + alt_index = i; + } + j++; + } + + if (usb2_set_alt_interface_index(uaa->device, 1, alt_index)) { + device_printf(dev, "Could not set alternate " + "setting %d for interface 1!\n", alt_index); + goto detach; + } + iface_index = 1; + if (usb2_transfer_setup + (uaa->device, &iface_index, sc->sc_xfer_if_1, + isoc_setup, UBT_IF_1_N_TRANSFER, sc, &sc->sc_mtx)) { + device_printf(dev, "Could not allocate transfers " + "for interface 1!\n"); + goto detach; + } + /* create Netgraph node */ + + if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) { + printf("%s: Could not create Netgraph node\n", + sc->sc_name); + sc->sc_node = NULL; + goto detach; + } + /* name node */ + + if (ng_name_node(sc->sc_node, sc->sc_name) != 0) { + printf("%s: Could not name Netgraph node\n", + sc->sc_name); + NG_NODE_UNREF(sc->sc_node); + sc->sc_node = NULL; + goto detach; + } + NG_NODE_SET_PRIVATE(sc->sc_node, sc); + NG_NODE_FORCE_WRITER(sc->sc_node); + + /* claim all interfaces on the device */ + + for (i = 1;; i++) { + + if (usb2_get_iface(uaa->device, i) == NULL) { + break; + } + usb2_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + } + + return (0); /* success */ + +detach: + ubt_detach(dev); + + return (ENXIO); +} + +/* + * Detach the device + */ + +int +ubt_detach(device_t dev) +{ + struct ubt_softc *sc = device_get_softc(dev); + + /* destroy Netgraph node */ + + if (sc->sc_node != NULL) { + NG_NODE_SET_PRIVATE(sc->sc_node, NULL); + ng_rmnode_self(sc->sc_node); + sc->sc_node = NULL; + } + /* free USB transfers, if any */ + + usb2_transfer_unsetup(sc->sc_xfer_if_0, UBT_IF_0_N_TRANSFER); + + usb2_transfer_unsetup(sc->sc_xfer_if_1, UBT_IF_1_N_TRANSFER); + + mtx_destroy(&sc->sc_mtx); + + /* destroy queues */ + + NG_BT_MBUFQ_DESTROY(&sc->sc_cmdq); + NG_BT_MBUFQ_DESTROY(&sc->sc_aclq); + NG_BT_MBUFQ_DESTROY(&sc->sc_scoq); + NG_BT_MBUFQ_DESTROY(&sc->sc_sciq); + + return (0); +} + +static void +ubt_ctrl_write_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + struct mbuf *m; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + + if (xfer->error) { + NG_UBT_STAT_OERROR(sc->sc_stat); + } else { + NG_UBT_STAT_BYTES_SENT(sc->sc_stat, xfer->actlen); + NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); + } + + case USB_ST_SETUP: + + /* get next mbuf, if any */ + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_cmdq, m); + + if (m == NULL) { + NG_UBT_INFO(sc, "HCI command queue is empty\n"); + return; + } + /* + * check HCI command frame size and + * copy it to USB transfer buffer: + */ + + if (m->m_pkthdr.len > UBT_CTRL_BUFFER_SIZE) { + panic("HCI command frame too big, size=%zd, len=%d\n", + UBT_CTRL_BUFFER_SIZE, m->m_pkthdr.len); + } + /* initialize a USB control request and then schedule it */ + + bzero(&req, sizeof(req)); + + req.bmRequestType = UBT_HCI_REQUEST; + USETW(req.wLength, m->m_pkthdr.len); + + NG_UBT_INFO(sc, "Sending control request, bmRequestType=0x%02x, " + "wLength=%d\n", req.bmRequestType, UGETW(req.wLength)); + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + usb2_m_copy_in(xfer->frbuffers + 1, 0, m, 0, m->m_pkthdr.len); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = m->m_pkthdr.len; + xfer->nframes = xfer->frlengths[1] ? 2 : 1; + + NG_FREE_M(m); + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + /* ignore */ + return; + } + goto tr_transferred; + } +} + +static void +ubt_intr_read_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + struct mbuf *m; + uint32_t max_len; + uint8_t *ptr; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* allocate a new mbuf */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + + if (m == NULL) { + goto tr_setup; + } + MCLGET(m, M_DONTWAIT); + + if (!(m->m_flags & M_EXT)) { + NG_FREE_M(m); + goto tr_setup; + } + if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { + *mtod(m, uint8_t *)= NG_HCI_EVENT_PKT; + m->m_pkthdr.len = m->m_len = 1; + } else { + m->m_pkthdr.len = m->m_len = 0; + } + + max_len = (MCLBYTES - m->m_len); + + if (xfer->actlen > max_len) { + xfer->actlen = max_len; + } + ptr = ((uint8_t *)(m->m_data)) + m->m_len; + + usb2_copy_out(xfer->frbuffers, 0, ptr, xfer->actlen); + + m->m_pkthdr.len += xfer->actlen; + m->m_len += xfer->actlen; + + NG_UBT_INFO(sc, "got %d bytes from interrupt " + "pipe\n", xfer->actlen); + + sc->sc_intr_buffer = m; + + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_intr_buffer) { + ng_send_fn(sc->sc_node, NULL, ubt_intr_read_complete, NULL, 0); + return; + } + if (sc->sc_flags & UBT_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer_if_0[6]); + return; + } + xfer->frlengths[0] = xfer->max_data_length; + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UBT_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer_if_0[6]); + } + return; + + } +} + +static void +ubt_intr_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_if_0[2]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UBT_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubt_intr_read_complete(node_p node, hook_p hook, void *arg1, int arg2) +{ + struct ubt_softc *sc = NG_NODE_PRIVATE(node); + struct mbuf *m; + ng_hci_event_pkt_t *hdr; + int error; + + if (sc == NULL) { + return; + } + mtx_lock(&sc->sc_mtx); + + m = sc->sc_intr_buffer; + + if (m) { + + sc->sc_intr_buffer = NULL; + + hdr = mtod(m, ng_hci_event_pkt_t *); + + if ((sc->sc_hook == NULL) || NG_HOOK_NOT_VALID(sc->sc_hook)) { + NG_UBT_INFO(sc, "No upstream hook\n"); + goto done; + } + NG_UBT_STAT_BYTES_RECV(sc->sc_stat, m->m_pkthdr.len); + + if (m->m_pkthdr.len < sizeof(*hdr)) { + NG_UBT_INFO(sc, "Packet too short\n"); + goto done; + } + if (hdr->length == (m->m_pkthdr.len - sizeof(*hdr))) { + NG_UBT_INFO(sc, "Got complete HCI event frame, " + "pktlen=%d, length=%d\n", + m->m_pkthdr.len, hdr->length); + + NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); + + NG_SEND_DATA_ONLY(error, sc->sc_hook, m); + + m = NULL; + + if (error != 0) { + NG_UBT_STAT_IERROR(sc->sc_stat); + } + } else { + NG_UBT_ERR(sc, "Invalid HCI event frame size, " + "length=%d, pktlen=%d\n", + hdr->length, m->m_pkthdr.len); + + NG_UBT_STAT_IERROR(sc->sc_stat); + } + } +done: + if (m) { + NG_FREE_M(m); + } + /* start USB transfer if not already started */ + + usb2_transfer_start(sc->sc_xfer_if_0[2]); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +ubt_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + struct mbuf *m; + uint32_t max_len; + uint8_t *ptr; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* allocate new mbuf */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + + if (m == NULL) { + goto tr_setup; + } + MCLGET(m, M_DONTWAIT); + + if (!(m->m_flags & M_EXT)) { + NG_FREE_M(m); + goto tr_setup; + } + if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { + *mtod(m, uint8_t *)= NG_HCI_ACL_DATA_PKT; + m->m_pkthdr.len = m->m_len = 1; + } else { + m->m_pkthdr.len = m->m_len = 0; + } + + max_len = (MCLBYTES - m->m_len); + + if (xfer->actlen > max_len) { + xfer->actlen = max_len; + } + ptr = ((uint8_t *)(m->m_data)) + m->m_len; + + usb2_copy_out(xfer->frbuffers, 0, ptr, xfer->actlen); + + m->m_pkthdr.len += xfer->actlen; + m->m_len += xfer->actlen; + + NG_UBT_INFO(sc, "got %d bytes from bulk-in " + "pipe\n", xfer->actlen); + + sc->sc_bulk_in_buffer = m; + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_bulk_in_buffer) { + ng_send_fn(sc->sc_node, NULL, ubt_bulk_read_complete, NULL, 0); + return; + } + if (sc->sc_flags & UBT_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer_if_0[5]); + return; + } + xfer->frlengths[0] = xfer->max_data_length; + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UBT_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer_if_0[5]); + } + return; + + } +} + +static void +ubt_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_if_0[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UBT_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubt_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2) +{ + struct ubt_softc *sc = NG_NODE_PRIVATE(node); + struct mbuf *m; + ng_hci_acldata_pkt_t *hdr; + uint16_t len; + int error; + + if (sc == NULL) { + return; + } + mtx_lock(&sc->sc_mtx); + + m = sc->sc_bulk_in_buffer; + + if (m) { + + sc->sc_bulk_in_buffer = NULL; + + hdr = mtod(m, ng_hci_acldata_pkt_t *); + + if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) { + NG_UBT_INFO(sc, "No upstream hook\n"); + goto done; + } + NG_UBT_STAT_BYTES_RECV(sc->sc_stat, m->m_pkthdr.len); + + if (m->m_pkthdr.len < sizeof(*hdr)) { + NG_UBT_INFO(sc, "Packet too short\n"); + goto done; + } + len = le16toh(hdr->length); + + if (len == (m->m_pkthdr.len - sizeof(*hdr))) { + NG_UBT_INFO(sc, "Got complete ACL data frame, " + "pktlen=%d, length=%d\n", + m->m_pkthdr.len, len); + + NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); + + NG_SEND_DATA_ONLY(error, sc->sc_hook, m); + + m = NULL; + + if (error != 0) { + NG_UBT_STAT_IERROR(sc->sc_stat); + } + } else { + NG_UBT_ERR(sc, "Invalid ACL frame size, " + "length=%d, pktlen=%d\n", + len, m->m_pkthdr.len); + + NG_UBT_STAT_IERROR(sc->sc_stat); + } + } +done: + if (m) { + NG_FREE_M(m); + } + /* start USB transfer if not already started */ + + usb2_transfer_start(sc->sc_xfer_if_0[1]); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +ubt_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + struct mbuf *m; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + NG_UBT_INFO(sc, "sent %d bytes to bulk-out " + "pipe\n", xfer->actlen); + NG_UBT_STAT_BYTES_SENT(sc->sc_stat, xfer->actlen); + NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); + + case USB_ST_SETUP: + if (sc->sc_flags & UBT_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer_if_0[4]); + return; + } + /* get next mbuf, if any */ + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_aclq, m); + + if (m == NULL) { + NG_UBT_INFO(sc, "ACL data queue is empty\n"); + return; + } + /* + * check ACL data frame size and + * copy it back to a linear USB + * transfer buffer: + */ + + if (m->m_pkthdr.len > UBT_BULK_WRITE_BUFFER_SIZE) { + panic("ACL data frame too big, size=%d, len=%d\n", + UBT_BULK_WRITE_BUFFER_SIZE, + m->m_pkthdr.len); + } + usb2_m_copy_in(xfer->frbuffers, 0, m, 0, m->m_pkthdr.len); + + NG_UBT_INFO(sc, "bulk-out transfer has been started, " + "len=%d\n", m->m_pkthdr.len); + + xfer->frlengths[0] = m->m_pkthdr.len; + + NG_FREE_M(m); + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + + NG_UBT_WARN(sc, "bulk-out transfer failed: %s\n", + usb2_errstr(xfer->error)); + + NG_UBT_STAT_OERROR(sc->sc_stat); + + /* try to clear stall first */ + sc->sc_flags |= UBT_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer_if_0[4]); + } + return; + + } +} + +static void +ubt_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_if_0[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UBT_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubt_isoc_read_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + ng_hci_scodata_pkt_t hdr; + struct mbuf *m; + uint8_t *ptr; + uint32_t max_len; + uint32_t n; + uint32_t offset; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + offset = 0; + + for (n = 0; n < xfer->nframes; n++) { + + if (xfer->frlengths[n] >= sizeof(hdr)) { + + usb2_copy_out(xfer->frbuffers, offset, + &hdr, sizeof(hdr)); + + if (hdr.length == (xfer->frlengths[n] - sizeof(hdr))) { + + NG_UBT_INFO(sc, "got complete SCO data " + "frame, length=%d\n", hdr.length); + + if (!NG_BT_MBUFQ_FULL(&sc->sc_sciq)) { + + /* allocate a new mbuf */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + + if (m == NULL) { + goto tr_setup; + } + MCLGET(m, M_DONTWAIT); + + if (!(m->m_flags & M_EXT)) { + NG_FREE_M(m); + goto tr_setup; + } + /* + * fix SCO data frame header + * if required + */ + + if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { + *mtod(m, uint8_t *)= NG_HCI_SCO_DATA_PKT; + m->m_pkthdr.len = m->m_len = 1; + } else { + m->m_pkthdr.len = m->m_len = 0; + } + + max_len = (MCLBYTES - m->m_len); + + if (xfer->frlengths[n] > max_len) { + xfer->frlengths[n] = max_len; + } + ptr = ((uint8_t *)(m->m_data)) + m->m_len; + + usb2_copy_out + (xfer->frbuffers, offset, + ptr, xfer->frlengths[n]); + + m->m_pkthdr.len += xfer->frlengths[n]; + m->m_len += xfer->frlengths[n]; + + NG_BT_MBUFQ_ENQUEUE(&sc->sc_sciq, m); + } + } + } + offset += xfer->max_frame_size; + } + + case USB_ST_SETUP: +tr_setup: + + if (NG_BT_MBUFQ_LEN(&sc->sc_sciq) > 0) { + ng_send_fn(sc->sc_node, NULL, ubt_isoc_read_complete, NULL, 0); + } + for (n = 0; n < xfer->nframes; n++) { + xfer->frlengths[n] = xfer->max_frame_size; + } + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + /* ignore */ + return; + } + goto tr_transferred; + } +} + +static void +ubt_isoc_read_complete(node_p node, hook_p hook, void *arg1, int arg2) +{ + ubt_softc_p sc = NG_NODE_PRIVATE(node); + struct mbuf *m; + int error; + + if (sc == NULL) { + return; + } + mtx_lock(&sc->sc_mtx); + + while (1) { + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_sciq, m); + + if (m == NULL) { + break; + } + if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) { + NG_UBT_INFO(sc, "No upstream hook\n"); + goto done; + } + NG_UBT_INFO(sc, "Got complete SCO data frame, " + "pktlen=%d bytes\n", m->m_pkthdr.len); + + NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); + NG_UBT_STAT_BYTES_RECV(sc->sc_stat, m->m_pkthdr.len); + + NG_SEND_DATA_ONLY(error, sc->sc_hook, m); + + m = NULL; + + if (error) { + NG_UBT_STAT_IERROR(sc->sc_stat); + } +done: + if (m) { + NG_FREE_M(m); + } + } + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +ubt_isoc_write_callback(struct usb2_xfer *xfer) +{ + struct ubt_softc *sc = xfer->priv_sc; + struct mbuf *m; + uint32_t n; + uint32_t len; + uint32_t offset; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (xfer->error) { + NG_UBT_STAT_OERROR(sc->sc_stat); + } else { + NG_UBT_STAT_BYTES_SENT(sc->sc_stat, xfer->actlen); + NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); + } + + case USB_ST_SETUP: + offset = 0; + + for (n = 0; n < xfer->nframes; n++) { + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m); + + if (m) { + len = min(xfer->max_frame_size, m->m_pkthdr.len); + + usb2_m_copy_in(xfer->frbuffers, offset, m, 0, len); + + NG_FREE_M(m); + + xfer->frlengths[n] = len; + offset += len; + } else { + xfer->frlengths[n] = 0; + } + } + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + /* ignore */ + return; + } + goto tr_transferred; + } +} + +/**************************************************************************** + **************************************************************************** + ** Netgraph specific + **************************************************************************** + ****************************************************************************/ + +/* + * Netgraph node constructor. + * Do not allow to create node of this type: + */ + +static int +ng_ubt_constructor(node_p node) +{ + return (EINVAL); +} + +/* + * Netgraph node destructor. + * Destroy node only when device has been detached: + */ + +static int +ng_ubt_shutdown(node_p node) +{ + struct ubt_softc *sc = NG_NODE_PRIVATE(node); + + /* Let old node go */ + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + + if (sc == NULL) { + goto done; + } + mtx_lock(&sc->sc_mtx); + + /* Create Netgraph node */ + if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) { + printf("%s: Could not create Netgraph node\n", + sc->sc_name); + sc->sc_node = NULL; + goto done; + } + /* Name node */ + if (ng_name_node(sc->sc_node, sc->sc_name) != 0) { + printf("%s: Could not name Netgraph node\n", + sc->sc_name); + NG_NODE_UNREF(sc->sc_node); + sc->sc_node = NULL; + goto done; + } + NG_NODE_SET_PRIVATE(sc->sc_node, sc); + NG_NODE_FORCE_WRITER(sc->sc_node); + +done: + if (sc) { + mtx_unlock(&sc->sc_mtx); + } + return (0); +} + +/* + * Create new hook. + * There can only be one. + */ + +static int +ng_ubt_newhook(node_p node, hook_p hook, char const *name) +{ + struct ubt_softc *sc = NG_NODE_PRIVATE(node); + int error = 0; + + if (strcmp(name, NG_UBT_HOOK) != 0) { + return (EINVAL); + } + mtx_lock(&sc->sc_mtx); + + if (sc->sc_hook != NULL) { + error = EISCONN; + } else { + sc->sc_hook = hook; + } + + mtx_unlock(&sc->sc_mtx); + + return (error); +} + +/* + * Connect hook. + * Start incoming USB transfers + */ + +static int +ng_ubt_connect(hook_p hook) +{ + struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + + mtx_lock(&sc->sc_mtx); + + sc->sc_flags |= (UBT_FLAG_READ_STALL | + UBT_FLAG_WRITE_STALL | + UBT_FLAG_INTR_STALL); + + /* start intr transfer */ + usb2_transfer_start(sc->sc_xfer_if_0[2]); + + /* start bulk-in transfer */ + usb2_transfer_start(sc->sc_xfer_if_0[1]); + + /* start bulk-out transfer */ + usb2_transfer_start(sc->sc_xfer_if_0[0]); + + /* start control-out transfer */ + usb2_transfer_start(sc->sc_xfer_if_0[3]); +#if 0 + XXX can enable this XXX + + /* start isoc-in transfer */ + usb2_transfer_start(sc->sc_xfer_if_1[0]); + + usb2_transfer_start(sc->sc_xfer_if_1[1]); + + /* start isoc-out transfer */ + usb2_transfer_start(sc->sc_xfer_if_1[2]); + usb2_transfer_start(sc->sc_xfer_if_1[3]); +#endif + + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +/* + * Disconnect hook + */ + +static int +ng_ubt_disconnect(hook_p hook) +{ + struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int error = 0; + + if (sc != NULL) { + + mtx_lock(&sc->sc_mtx); + + if (hook != sc->sc_hook) { + error = EINVAL; + } else { + + /* stop intr transfer */ + usb2_transfer_stop(sc->sc_xfer_if_0[2]); + usb2_transfer_stop(sc->sc_xfer_if_0[6]); + + /* stop bulk-in transfer */ + usb2_transfer_stop(sc->sc_xfer_if_0[1]); + usb2_transfer_stop(sc->sc_xfer_if_0[5]); + + /* stop bulk-out transfer */ + usb2_transfer_stop(sc->sc_xfer_if_0[0]); + usb2_transfer_stop(sc->sc_xfer_if_0[4]); + + /* stop control transfer */ + usb2_transfer_stop(sc->sc_xfer_if_0[3]); + + /* stop isoc-in transfer */ + usb2_transfer_stop(sc->sc_xfer_if_1[0]); + usb2_transfer_stop(sc->sc_xfer_if_1[1]); + + /* stop isoc-out transfer */ + usb2_transfer_stop(sc->sc_xfer_if_1[2]); + usb2_transfer_stop(sc->sc_xfer_if_1[3]); + + /* cleanup queues */ + NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq); + NG_BT_MBUFQ_DRAIN(&sc->sc_aclq); + NG_BT_MBUFQ_DRAIN(&sc->sc_scoq); + NG_BT_MBUFQ_DRAIN(&sc->sc_sciq); + + sc->sc_hook = NULL; + } + + mtx_unlock(&sc->sc_mtx); + } + return (error); +} + +/* + * Process control message + */ + +static int +ng_ubt_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct ubt_softc *sc = NG_NODE_PRIVATE(node); + struct ng_mesg *msg = NULL, *rsp = NULL; + struct ng_bt_mbufq *q = NULL; + int error = 0, queue, qlen; + + if (sc == NULL) { + NG_FREE_ITEM(item); + return (EHOSTDOWN); + } + mtx_lock(&sc->sc_mtx); + + NGI_GET_MSG(item, msg); + + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_STATUS: + NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + snprintf(rsp->data, NG_TEXTRESPONSE, + "Hook: %s\n" \ + "Flags: %#x\n" \ + "Debug: %d\n" \ + "CMD queue: [have:%d,max:%d]\n" \ + "ACL queue: [have:%d,max:%d]\n" \ + "SCO queue: [have:%d,max:%d]", + (sc->sc_hook != NULL) ? NG_UBT_HOOK : "", + sc->sc_flags, + sc->sc_debug, + NG_BT_MBUFQ_LEN(&sc->sc_cmdq), + sc->sc_cmdq.maxlen, + NG_BT_MBUFQ_LEN(&sc->sc_aclq), + sc->sc_aclq.maxlen, + NG_BT_MBUFQ_LEN(&sc->sc_scoq), + sc->sc_scoq.maxlen); + break; + + default: + error = EINVAL; + break; + } + break; + + case NGM_UBT_COOKIE: + switch (msg->header.cmd) { + case NGM_UBT_NODE_SET_DEBUG: + if (msg->header.arglen != sizeof(ng_ubt_node_debug_ep)) + error = EMSGSIZE; + else + sc->sc_debug = + *((ng_ubt_node_debug_ep *) (msg->data)); + break; + + case NGM_UBT_NODE_GET_DEBUG: + NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_debug_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else + *((ng_ubt_node_debug_ep *) (rsp->data)) = + sc->sc_debug; + break; + + case NGM_UBT_NODE_SET_QLEN: + if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) + error = EMSGSIZE; + else { + queue = ((ng_ubt_node_qlen_ep *) + (msg->data))->queue; + qlen = ((ng_ubt_node_qlen_ep *) + (msg->data))->qlen; + + if (qlen <= 0) { + error = EINVAL; + break; + } + switch (queue) { + case NGM_UBT_NODE_QUEUE_CMD: + q = &sc->sc_cmdq; + break; + + case NGM_UBT_NODE_QUEUE_ACL: + q = &sc->sc_aclq; + break; + + case NGM_UBT_NODE_QUEUE_SCO: + q = &sc->sc_scoq; + break; + + default: + q = NULL; + error = EINVAL; + break; + } + + if (q != NULL) { + q->maxlen = qlen; + } + } + break; + + case NGM_UBT_NODE_GET_QLEN: + if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) { + error = EMSGSIZE; + break; + } + queue = ((ng_ubt_node_qlen_ep *) (msg->data))->queue; + switch (queue) { + case NGM_UBT_NODE_QUEUE_CMD: + q = &sc->sc_cmdq; + break; + + case NGM_UBT_NODE_QUEUE_ACL: + q = &sc->sc_aclq; + break; + + case NGM_UBT_NODE_QUEUE_SCO: + q = &sc->sc_scoq; + break; + + default: + q = NULL; + error = EINVAL; + break; + } + + if (q != NULL) { + NG_MKRESPONSE(rsp, msg, + sizeof(ng_ubt_node_qlen_ep), M_NOWAIT); + if (rsp == NULL) { + error = ENOMEM; + break; + } + ((ng_ubt_node_qlen_ep *) (rsp->data))->queue = + queue; + ((ng_ubt_node_qlen_ep *) (rsp->data))->qlen = + q->maxlen; + } + break; + + case NGM_UBT_NODE_GET_STAT: + NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_stat_ep), + M_NOWAIT); + if (rsp == NULL) + error = ENOMEM; + else { + bcopy(&sc->sc_stat, rsp->data, + sizeof(ng_ubt_node_stat_ep)); + } + break; + + case NGM_UBT_NODE_RESET_STAT: + + NG_UBT_STAT_RESET(sc->sc_stat); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + + NG_RESPOND_MSG(error, node, item, rsp); + NG_FREE_MSG(msg); + + mtx_unlock(&sc->sc_mtx); + + return (error); +} + +/* + * Process data + */ + +static int +ng_ubt_rcvdata(hook_p hook, item_p item) +{ + struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct mbuf *m; + struct ng_bt_mbufq *q; + struct usb2_xfer *xfer; + int error = 0; + + if (sc == NULL) { + error = EHOSTDOWN; + goto done; + } + mtx_lock(&sc->sc_mtx); + + if (hook != sc->sc_hook) { + error = EINVAL; + goto done; + } + /* deatch mbuf and get HCI frame type */ + NGI_GET_M(item, m); + + /* process HCI frame */ + switch (*mtod(m, uint8_t *)) { /* XXX call m_pullup ? */ + case NG_HCI_CMD_PKT: + xfer = sc->sc_xfer_if_0[3]; + q = &sc->sc_cmdq; + break; + + case NG_HCI_ACL_DATA_PKT: + xfer = sc->sc_xfer_if_0[0]; + q = &sc->sc_aclq; + break; + + case NG_HCI_SCO_DATA_PKT: + xfer = NULL; + q = &sc->sc_scoq; + break; + + default: + NG_UBT_ERR(sc, "Dropping unsupported HCI frame, " + "type=0x%02x, pktlen=%d\n", + *mtod(m, uint8_t *), + m->m_pkthdr.len); + + NG_FREE_M(m); + error = EINVAL; + goto done; + } + + /* loose frame type, if required */ + if (!(sc->sc_flags & UBT_NEED_FRAME_TYPE)) { + m_adj(m, sizeof(uint8_t)); + } + if (NG_BT_MBUFQ_FULL(q)) { + NG_UBT_ERR(sc, "Dropping HCI frame 0x%02x, len=%d. " + "Queue full\n", *mtod(m, uint8_t *), + m->m_pkthdr.len); + NG_FREE_M(m); + } else { + NG_BT_MBUFQ_ENQUEUE(q, m); + } + + if (xfer) { + usb2_transfer_start(xfer); + } +done: + NG_FREE_ITEM(item); + + if (sc) { + mtx_unlock(&sc->sc_mtx); + } + return (error); +} diff --git a/sys/dev/usb2/bluetooth/ng_ubt2_var.h b/sys/dev/usb2/bluetooth/ng_ubt2_var.h new file mode 100644 index 000000000000..8fecc41be8a9 --- /dev/null +++ b/sys/dev/usb2/bluetooth/ng_ubt2_var.h @@ -0,0 +1,126 @@ +/* + * ng_ubt_var.h + */ + +/*- + * Copyright (c) 2001-2002 Maksim Yevmenkin + * 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. + * + * $Id: ng_ubt_var.h,v 1.2 2003/03/22 23:44:36 max Exp $ + * $FreeBSD$ + */ + +#ifndef _NG_UBT_VAR_H_ +#define _NG_UBT_VAR_H_ + +/* pullup wrapper */ +#define NG_UBT_M_PULLUP(m, s) \ + do { \ + if ((m)->m_len < (s)) \ + (m) = m_pullup((m), (s)); \ + if ((m) == NULL) { \ + NG_UBT_ALERT("%s: %s - m_pullup(%d) failed\n", \ + __func__, sc->sc_name, (s)); \ + } \ + } while (0) + +/* Debug printf's */ +#define NG_UBT_DEBUG(level, sc, fmt, ...) do { \ + if ((sc)->sc_debug >= (level)) { \ + printf("%s:%s:%d: " fmt, (sc)->sc_name, \ + __FUNCTION__, __LINE__,## __VA_ARGS__); \ + } \ +} while (0) + +#define NG_UBT_ALERT(...) NG_UBT_DEBUG(NG_UBT_ALERT_LEVEL, __VA_ARGS__) +#define NG_UBT_ERR(...) NG_UBT_DEBUG(NG_UBT_ERR_LEVEL, __VA_ARGS__) +#define NG_UBT_WARN(...) NG_UBT_DEBUG(NG_UBT_WARN_LEVEL, __VA_ARGS__) +#define NG_UBT_INFO(...) NG_UBT_DEBUG(NG_UBT_INFO_LEVEL, __VA_ARGS__) + +/* Bluetooth USB control request type */ +#define UBT_HCI_REQUEST 0x20 +#define UBT_DEFAULT_QLEN 12 + +/* Bluetooth USB defines */ +#define UBT_IF_0_N_TRANSFER 7 /* units */ +#define UBT_IF_1_N_TRANSFER 4 /* units */ +#define UBT_ISOC_NFRAMES 25 /* units */ + +/* USB device softc structure */ +struct ubt_softc { + /* State */ + ng_ubt_node_debug_ep sc_debug; /* debug level */ + uint32_t sc_flags; /* device flags */ +#define UBT_NEED_FRAME_TYPE (1 << 0)/* device required frame type */ +#define UBT_HAVE_FRAME_TYPE UBT_NEED_FRAME_TYPE +#define UBT_FLAG_READ_STALL (1 << 1)/* read transfer has stalled */ +#define UBT_FLAG_WRITE_STALL (1 << 2)/* write transfer has stalled */ +#define UBT_FLAG_INTR_STALL (1 << 3)/* interrupt transfer has stalled */ + + ng_ubt_node_stat_ep sc_stat; /* statistic */ +#define NG_UBT_STAT_PCKTS_SENT(s) (s).pckts_sent ++ +#define NG_UBT_STAT_BYTES_SENT(s, n) (s).bytes_sent += (n) +#define NG_UBT_STAT_PCKTS_RECV(s) (s).pckts_recv ++ +#define NG_UBT_STAT_BYTES_RECV(s, n) (s).bytes_recv += (n) +#define NG_UBT_STAT_OERROR(s) (s).oerrors ++ +#define NG_UBT_STAT_IERROR(s) (s).ierrors ++ +#define NG_UBT_STAT_RESET(s) bzero(&(s), sizeof((s))) + + uint8_t sc_name[16]; + + struct mtx sc_mtx; + + /* USB device specific */ + struct usb2_xfer *sc_xfer_if_0[UBT_IF_0_N_TRANSFER]; + struct usb2_xfer *sc_xfer_if_1[UBT_IF_1_N_TRANSFER]; + + /* Interrupt pipe (HCI events) */ + struct mbuf *sc_intr_buffer; /* interrupt buffer */ + + /* Control pipe (HCI commands) */ + struct ng_bt_mbufq sc_cmdq; /* HCI command queue */ +#define UBT_CTRL_BUFFER_SIZE (sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE) + + /* Bulk in pipe (ACL data) */ + struct mbuf *sc_bulk_in_buffer; /* bulk-in buffer */ + + /* Bulk out pipe (ACL data) */ + struct ng_bt_mbufq sc_aclq; /* ACL data queue */ +#define UBT_BULK_READ_BUFFER_SIZE (MCLBYTES-1) /* reserve one byte for ID-tag */ +#define UBT_BULK_WRITE_BUFFER_SIZE (MCLBYTES) + + /* Isoc. out pipe (ACL data) */ + struct ng_bt_mbufq sc_scoq; /* SCO data queue */ + + /* Isoc. in pipe (ACL data) */ + struct ng_bt_mbufq sc_sciq; /* SCO data queue */ + + /* Netgraph specific */ + node_p sc_node; /* pointer back to node */ + hook_p sc_hook; /* upstream hook */ +}; +typedef struct ubt_softc ubt_softc_t; +typedef struct ubt_softc *ubt_softc_p; + +#endif /* ndef _NG_UBT_VAR_H_ */ diff --git a/sys/dev/usb2/bluetooth/ubtbcmfw2.c b/sys/dev/usb2/bluetooth/ubtbcmfw2.c new file mode 100644 index 000000000000..440b5cda6566 --- /dev/null +++ b/sys/dev/usb2/bluetooth/ubtbcmfw2.c @@ -0,0 +1,448 @@ +/* + * ubtbcmfw.c + */ + +/*- + * Copyright (c) 2003 Maksim Yevmenkin + * 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. + * + * $Id: ubtbcmfw.c,v 1.3 2003/10/10 19:15:08 max Exp $ + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Download firmware to BCM2033. + */ + +#define UBTBCMFW_CONFIG_NO 1 /* Config number */ +#define UBTBCMFW_IFACE_IDX 0 /* Control interface */ +#define UBTBCMFW_T_MAX 4 /* units */ + +struct ubtbcmfw_softc { + struct usb2_fifo_sc sc_fifo; + struct mtx sc_mtx; + + device_t sc_dev; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[UBTBCMFW_T_MAX]; + + uint8_t sc_flags; +#define UBTBCMFW_FLAG_WRITE_STALL 0x01 +#define UBTBCMFW_FLAG_READ_STALL 0x02 +}; + +#define UBTBCMFW_BSIZE 1024 +#define UBTBCMFW_IFQ_MAXLEN 2 + +/* prototypes */ + +static device_probe_t ubtbcmfw_probe; +static device_attach_t ubtbcmfw_attach; +static device_detach_t ubtbcmfw_detach; + +static usb2_callback_t ubtbcmfw_write_callback; +static usb2_callback_t ubtbcmfw_write_clear_stall_callback; +static usb2_callback_t ubtbcmfw_read_callback; +static usb2_callback_t ubtbcmfw_read_clear_stall_callback; + +static usb2_fifo_close_t ubtbcmfw_close; +static usb2_fifo_cmd_t ubtbcmfw_start_read; +static usb2_fifo_cmd_t ubtbcmfw_start_write; +static usb2_fifo_cmd_t ubtbcmfw_stop_read; +static usb2_fifo_cmd_t ubtbcmfw_stop_write; +static usb2_fifo_ioctl_t ubtbcmfw_ioctl; +static usb2_fifo_open_t ubtbcmfw_open; + +static struct usb2_fifo_methods ubtbcmfw_fifo_methods = { + .f_close = &ubtbcmfw_close, + .f_ioctl = &ubtbcmfw_ioctl, + .f_open = &ubtbcmfw_open, + .f_start_read = &ubtbcmfw_start_read, + .f_start_write = &ubtbcmfw_start_write, + .f_stop_read = &ubtbcmfw_stop_read, + .f_stop_write = &ubtbcmfw_stop_write, + .basename[0] = "ubtbcmfw", + .basename[1] = "ubtbcmfw", + .basename[2] = "ubtbcmfw", + .postfix[0] = "", + .postfix[1] = ".1", + .postfix[2] = ".2", +}; + +static const struct usb2_config ubtbcmfw_config[UBTBCMFW_T_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = 0x02, /* fixed */ + .direction = UE_DIR_OUT, + .mh.bufsize = UBTBCMFW_BSIZE, + .mh.flags = {.pipe_bof = 1,.proxy_buffer = 1,}, + .mh.callback = &ubtbcmfw_write_callback, + }, + + [1] = { + .type = UE_INTERRUPT, + .endpoint = 0x01, /* fixed */ + .direction = UE_DIR_IN, + .mh.bufsize = UBTBCMFW_BSIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,}, + .mh.callback = &ubtbcmfw_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ubtbcmfw_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ubtbcmfw_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +/* + * Module + */ + +static devclass_t ubtbcmfw_devclass; + +static device_method_t ubtbcmfw_methods[] = { + DEVMETHOD(device_probe, ubtbcmfw_probe), + DEVMETHOD(device_attach, ubtbcmfw_attach), + DEVMETHOD(device_detach, ubtbcmfw_detach), + {0, 0} +}; + +static driver_t ubtbcmfw_driver = { + .name = "ubtbcmfw", + .methods = ubtbcmfw_methods, + .size = sizeof(struct ubtbcmfw_softc), +}; + +DRIVER_MODULE(ubtbcmfw, ushub, ubtbcmfw_driver, ubtbcmfw_devclass, NULL, 0); +MODULE_DEPEND(ubtbcmfw, usb2_bluetooth, 1, 1, 1); +MODULE_DEPEND(ubtbcmfw, usb2_core, 1, 1, 1); + +/* + * Probe for a USB Bluetooth device + */ + +static int +ubtbcmfw_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != 0) + return (ENXIO); + + /* Match the boot device. */ + if (uaa->info.idVendor == USB_VENDOR_BROADCOM && + uaa->info.idProduct == USB_PRODUCT_BROADCOM_BCM2033) + return (0); + + return (ENXIO); +} + +/* + * Attach the device + */ + +static int +ubtbcmfw_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ubtbcmfw_softc *sc = device_get_softc(dev); + int32_t err; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "ubtbcmfw lock", NULL, MTX_DEF | MTX_RECURSE); + + iface_index = UBTBCMFW_IFACE_IDX; + err = usb2_transfer_setup(uaa->device, + &iface_index, sc->sc_xfer, ubtbcmfw_config, + UBTBCMFW_T_MAX, sc, &sc->sc_mtx); + if (err) { + device_printf(dev, "allocating USB transfers " + "failed, err=%s\n", usb2_errstr(err)); + goto detach; + } + /* set interface permissions */ + usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + err = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &ubtbcmfw_fifo_methods, &sc->sc_fifo, + device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex); + if (err) { + goto detach; + } + return (0); /* success */ + +detach: + ubtbcmfw_detach(dev); + return (ENOMEM); /* failure */ +} + +/* + * Detach the device + */ + +static int +ubtbcmfw_detach(device_t dev) +{ + struct ubtbcmfw_softc *sc = device_get_softc(dev); + + usb2_fifo_detach(&sc->sc_fifo); + + usb2_transfer_unsetup(sc->sc_xfer, UBTBCMFW_T_MAX); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +ubtbcmfw_write_callback(struct usb2_xfer *xfer) +{ + struct ubtbcmfw_softc *sc = xfer->priv_sc; + struct usb2_fifo *f = sc->sc_fifo.fp[USB_FIFO_RX]; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: + if (sc->sc_flags & UBTBCMFW_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_fifo_get_data(f, xfer->frbuffers, 0, + UBTBCMFW_BSIZE, &actlen, 0)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UBTBCMFW_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + } +} + +static void +ubtbcmfw_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubtbcmfw_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UBTBCMFW_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubtbcmfw_read_callback(struct usb2_xfer *xfer) +{ + struct ubtbcmfw_softc *sc = xfer->priv_sc; + struct usb2_fifo *f = sc->sc_fifo.fp[USB_FIFO_RX]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_fifo_put_data(f, xfer->frbuffers, + 0, xfer->actlen, 1); + + case USB_ST_SETUP: + if (sc->sc_flags & UBTBCMFW_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + return; + } + if (usb2_fifo_put_bytes_max(f) != 0) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UBTBCMFW_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + } +} + +static void +ubtbcmfw_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubtbcmfw_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UBTBCMFW_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubtbcmfw_start_read(struct usb2_fifo *fifo) +{ + struct ubtbcmfw_softc *sc = fifo->priv_sc0; + + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +ubtbcmfw_stop_read(struct usb2_fifo *fifo) +{ + struct ubtbcmfw_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +ubtbcmfw_start_write(struct usb2_fifo *fifo) +{ + struct ubtbcmfw_softc *sc = fifo->priv_sc0; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +ubtbcmfw_stop_write(struct usb2_fifo *fifo) +{ + struct ubtbcmfw_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static int +ubtbcmfw_open(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct ubtbcmfw_softc *sc = fifo->priv_sc0; + + if (fflags & FREAD) { + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_xfer[1]->max_data_length, + UBTBCMFW_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + if (fflags & FWRITE) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + sc->sc_flags |= UBTBCMFW_FLAG_WRITE_STALL; + mtx_unlock(&sc->sc_mtx); + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_xfer[0]->max_data_length, + UBTBCMFW_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + return (0); +} + +static void +ubtbcmfw_close(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + if (fflags & (FREAD | FWRITE)) { + usb2_fifo_free_buffer(fifo); + } + return; +} + +static int +ubtbcmfw_ioctl(struct usb2_fifo *fifo, u_long cmd, void *data, + int fflags, struct thread *td) +{ + struct ubtbcmfw_softc *sc = fifo->priv_sc0; + int error = 0; + + switch (cmd) { + case USB_GET_DEVICE_DESC: + *(struct usb2_device_descriptor *)data = + *usb2_get_device_descriptor(sc->sc_udev); + break; + + default: + error = EINVAL; + break; + } + return (error); +} diff --git a/sys/dev/usb2/bluetooth/usb2_bluetooth.c b/sys/dev/usb2/bluetooth/usb2_bluetooth.c new file mode 100644 index 000000000000..a8c9f545c994 --- /dev/null +++ b/sys/dev/usb2/bluetooth/usb2_bluetooth.c @@ -0,0 +1,31 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_bluetooth, 1); +MODULE_DEPEND(usb2_bluetooth, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/bluetooth/usb2_bluetooth.h b/sys/dev/usb2/bluetooth/usb2_bluetooth.h new file mode 100644 index 000000000000..b4b1761506ed --- /dev/null +++ b/sys/dev/usb2/bluetooth/usb2_bluetooth.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_BLUETOOTH_H_ +#define _USB2_BLUETOOTH_H_ + +#endif /* _USB2_BLUETOOTH_H_ */ diff --git a/sys/dev/usb2/controller/at91dci.c b/sys/dev/usb2/controller/at91dci.c new file mode 100644 index 000000000000..d5c7508ec517 --- /dev/null +++ b/sys/dev/usb2/controller/at91dci.c @@ -0,0 +1,2547 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2007-2008 Hans Petter Selasky. 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. + */ + +/* + * This file contains the driver for the AT91 series USB Device + * Controller + */ + +/* + * Thanks to "David Brownell" for helping out regarding the hardware + * endpoint profiles. + */ + +/* + * NOTE: The "fifo_bank" is not reset in hardware when the endpoint is + * reset ! + * + * NOTE: When the chip detects BUS-reset it will also reset the + * endpoints, Function-address and more. + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR at91dcidebug +#define usb2_config_td_cc at91dci_config_copy +#define usb2_config_td_softc at91dci_softc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AT9100_DCI_BUS2SC(bus) \ + ((struct at91dci_softc *)(((uint8_t *)(bus)) - \ + USB_P2U(&(((struct at91dci_softc *)0)->sc_bus)))) + +#define AT9100_DCI_PC2SC(pc) \ + AT9100_DCI_BUS2SC((pc)->tag_parent->info->bus) + +#if USB_DEBUG +static int at91dcidebug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, at91dci, CTLFLAG_RW, 0, "USB at91dci"); +SYSCTL_INT(_hw_usb2_at91dci, OID_AUTO, debug, CTLFLAG_RW, + &at91dcidebug, 0, "at91dci debug level"); +#endif + +#define AT9100_DCI_INTR_ENDPT 1 + +/* prototypes */ + +struct usb2_bus_methods at91dci_bus_methods; +struct usb2_pipe_methods at91dci_device_bulk_methods; +struct usb2_pipe_methods at91dci_device_ctrl_methods; +struct usb2_pipe_methods at91dci_device_intr_methods; +struct usb2_pipe_methods at91dci_device_isoc_fs_methods; +struct usb2_pipe_methods at91dci_root_ctrl_methods; +struct usb2_pipe_methods at91dci_root_intr_methods; + +static at91dci_cmd_t at91dci_setup_rx; +static at91dci_cmd_t at91dci_data_rx; +static at91dci_cmd_t at91dci_data_tx; +static at91dci_cmd_t at91dci_data_tx_sync; +static void at91dci_device_done(struct usb2_xfer *xfer, usb2_error_t error); +static void at91dci_do_poll(struct usb2_bus *bus); +static void at91dci_root_ctrl_poll(struct at91dci_softc *sc); +static void at91dci_standard_done(struct usb2_xfer *xfer); + +static usb2_sw_transfer_func_t at91dci_root_intr_done; +static usb2_sw_transfer_func_t at91dci_root_ctrl_done; +static usb2_config_td_command_t at91dci_root_ctrl_task; + +/* + * NOTE: Some of the bits in the CSR register have inverse meaning so + * we need a helper macro when acknowledging events: + */ +#define AT91_CSR_ACK(csr, what) do { \ + (csr) &= ~((AT91_UDP_CSR_FORCESTALL| \ + AT91_UDP_CSR_TXPKTRDY| \ + AT91_UDP_CSR_RXBYTECNT) ^ (what));\ + (csr) |= ((AT91_UDP_CSR_RX_DATA_BK0| \ + AT91_UDP_CSR_RX_DATA_BK1| \ + AT91_UDP_CSR_TXCOMP| \ + AT91_UDP_CSR_RXSETUP| \ + AT91_UDP_CSR_STALLSENT) ^ (what)); \ +} while (0) + +/* + * Here is a list of what the chip supports. + * Probably it supports more than listed here! + */ +static const struct usb2_hw_ep_profile + at91dci_ep_profile[AT91_UDP_EP_MAX] = { + + [0] = { + .max_in_frame_size = 8, + .max_out_frame_size = 8, + .is_simplex = 1, + .support_control = 1, + }, + [1] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 1, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, + [2] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 1, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, + [3] = { + /* can also do BULK */ + .max_in_frame_size = 8, + .max_out_frame_size = 8, + .is_simplex = 1, + .support_interrupt = 1, + .support_in = 1, + .support_out = 1, + }, + [4] = { + .max_in_frame_size = 256, + .max_out_frame_size = 256, + .is_simplex = 1, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, + [5] = { + .max_in_frame_size = 256, + .max_out_frame_size = 256, + .is_simplex = 1, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, +}; + +static void +at91dci_get_hw_ep_profile(struct usb2_device *udev, + const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr) +{ + if (ep_addr < AT91_UDP_EP_MAX) { + *ppf = (at91dci_ep_profile + ep_addr); + } else { + *ppf = NULL; + } + return; +} + +static void +at91dci_clocks_on(struct at91dci_softc *sc) +{ + if (sc->sc_flags.clocks_off && + sc->sc_flags.port_powered) { + + DPRINTFN(5, "\n"); + + if (sc->sc_clocks_on) { + (sc->sc_clocks_on) (sc->sc_clocks_arg); + } + sc->sc_flags.clocks_off = 0; + + /* enable Transceiver */ + AT91_UDP_WRITE_4(sc, AT91_UDP_TXVC, 0); + } + return; +} + +static void +at91dci_clocks_off(struct at91dci_softc *sc) +{ + if (!sc->sc_flags.clocks_off) { + + DPRINTFN(5, "\n"); + + /* disable Transceiver */ + AT91_UDP_WRITE_4(sc, AT91_UDP_TXVC, AT91_UDP_TXVC_DIS); + + if (sc->sc_clocks_off) { + (sc->sc_clocks_off) (sc->sc_clocks_arg); + } + sc->sc_flags.clocks_off = 1; + } + return; +} + +static void +at91dci_pull_up(struct at91dci_softc *sc) +{ + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + (sc->sc_pull_up) (sc->sc_pull_arg); + } + return; +} + +static void +at91dci_pull_down(struct at91dci_softc *sc) +{ + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + (sc->sc_pull_down) (sc->sc_pull_arg); + } + return; +} + +static void +at91dci_wakeup_peer(struct at91dci_softc *sc) +{ + uint32_t temp; + + if (!(sc->sc_flags.status_suspend)) { + return; + } + temp = AT91_UDP_READ_4(sc, AT91_UDP_GSTATE); + + if (!(temp & AT91_UDP_GSTATE_ESR)) { + return; + } + AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, temp); + + return; +} + +static void +at91dci_rem_wakeup_set(struct usb2_device *udev, uint8_t is_on) +{ + struct at91dci_softc *sc; + uint32_t temp; + + DPRINTFN(5, "is_on=%u\n", is_on); + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + sc = AT9100_DCI_BUS2SC(udev->bus); + + temp = AT91_UDP_READ_4(sc, AT91_UDP_GSTATE); + + if (is_on) { + temp |= AT91_UDP_GSTATE_ESR; + } else { + temp &= ~AT91_UDP_GSTATE_ESR; + } + + AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, temp); + + return; +} + +static void +at91dci_set_address(struct at91dci_softc *sc, uint8_t addr) +{ + DPRINTFN(5, "addr=%d\n", addr); + + AT91_UDP_WRITE_4(sc, AT91_UDP_FADDR, addr | + AT91_UDP_FADDR_EN); + + return; +} + +static uint8_t +at91dci_setup_rx(struct at91dci_td *td) +{ + struct at91dci_softc *sc; + struct usb2_device_request req; + uint32_t csr; + uint32_t temp; + uint16_t count; + + /* read out FIFO status */ + csr = bus_space_read_4(td->io_tag, td->io_hdl, + td->status_reg); + + DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder); + + temp = csr; + temp &= (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1 | + AT91_UDP_CSR_STALLSENT | + AT91_UDP_CSR_RXSETUP | + AT91_UDP_CSR_TXCOMP); + + if (!(csr & AT91_UDP_CSR_RXSETUP)) { + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(5, "stalling\n"); + temp |= AT91_UDP_CSR_FORCESTALL; + td->did_stall = 1; + } + goto not_complete; + } + /* get the packet byte count */ + count = (csr & AT91_UDP_CSR_RXBYTECNT) >> 16; + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + /* receive data */ + bus_space_read_multi_1(td->io_tag, td->io_hdl, + td->fifo_reg, (void *)&req, sizeof(req)); + + /* copy data into real buffer */ + usb2_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* get pointer to softc */ + sc = AT9100_DCI_PC2SC(td->pc); + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + } else { + sc->sc_dv_addr = 0xFF; + } + + /* sneak peek the endpoint direction */ + if (req.bmRequestType & UE_DIR_IN) { + csr |= AT91_UDP_CSR_DIR; + } else { + csr &= ~AT91_UDP_CSR_DIR; + } + + /* write the direction of the control transfer */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + return (0); /* complete */ + +not_complete: + /* clear interrupts, if any */ + if (temp) { + DPRINTFN(5, "clearing 0x%08x\n", temp); + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + } + return (1); /* not complete */ + +} + +static uint8_t +at91dci_data_rx(struct at91dci_td *td) +{ + struct usb2_page_search buf_res; + uint32_t csr; + uint32_t temp; + uint16_t count; + uint8_t to; + uint8_t got_short; + + to = 2; /* don't loop forever! */ + got_short = 0; + + /* check if any of the FIFO banks have data */ +repeat: + /* read out FIFO status */ + csr = bus_space_read_4(td->io_tag, td->io_hdl, + td->status_reg); + + DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder); + + if (csr & AT91_UDP_CSR_RXSETUP) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + /* Make sure that "STALLSENT" gets cleared */ + temp = csr; + temp &= AT91_UDP_CSR_STALLSENT; + + /* check status */ + if (!(csr & (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1))) { + if (temp) { + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + } + return (1); /* not complete */ + } + /* get the packet byte count */ + count = (csr & AT91_UDP_CSR_RXBYTECNT) >> 16; + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + usb2_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* receive data */ + bus_space_read_multi_1(td->io_tag, td->io_hdl, + td->fifo_reg, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* clear status bits */ + if (td->support_multi_buffer) { + if (td->fifo_bank) { + td->fifo_bank = 0; + temp |= AT91_UDP_CSR_RX_DATA_BK1; + } else { + td->fifo_bank = 1; + temp |= AT91_UDP_CSR_RX_DATA_BK0; + } + } else { + temp |= (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1); + } + + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + + /* + * NOTE: We may have to delay a little bit before + * proceeding after clearing the DATA_BK bits. + */ + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +at91dci_data_tx(struct at91dci_td *td) +{ + struct usb2_page_search buf_res; + uint32_t csr; + uint32_t temp; + uint16_t count; + uint8_t to; + + to = 2; /* don't loop forever! */ + +repeat: + + /* read out FIFO status */ + csr = bus_space_read_4(td->io_tag, td->io_hdl, + td->status_reg); + + DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder); + + if (csr & AT91_UDP_CSR_RXSETUP) { + /* + * The current transfer was aborted + * by the USB Host + */ + td->error = 1; + return (0); /* complete */ + } + /* Make sure that "STALLSENT" gets cleared */ + temp = csr; + temp &= AT91_UDP_CSR_STALLSENT; + + if (csr & AT91_UDP_CSR_TXPKTRDY) { + if (temp) { + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + } + return (1); /* not complete */ + } else { + /* clear TXCOMP and set TXPKTRDY */ + temp |= (AT91_UDP_CSR_TXCOMP | + AT91_UDP_CSR_TXPKTRDY); + } + + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + while (count > 0) { + + usb2_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* transmit data */ + bus_space_write_multi_1(td->io_tag, td->io_hdl, + td->fifo_reg, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +at91dci_data_tx_sync(struct at91dci_td *td) +{ + struct at91dci_softc *sc; + uint32_t csr; + uint32_t temp; + +#if 0 +repeat: +#endif + + /* read out FIFO status */ + csr = bus_space_read_4(td->io_tag, td->io_hdl, + td->status_reg); + + DPRINTFN(5, "csr=0x%08x\n", csr); + + if (csr & AT91_UDP_CSR_RXSETUP) { + DPRINTFN(5, "faking complete\n"); + /* Race condition */ + return (0); /* complete */ + } + temp = csr; + temp &= (AT91_UDP_CSR_STALLSENT | + AT91_UDP_CSR_TXCOMP); + + /* check status */ + if (csr & AT91_UDP_CSR_TXPKTRDY) { + goto not_complete; + } + if (!(csr & AT91_UDP_CSR_TXCOMP)) { + goto not_complete; + } + sc = AT9100_DCI_PC2SC(td->pc); + if (sc->sc_dv_addr != 0xFF) { + /* + * The AT91 has a special requirement with regard to + * setting the address and that is to write the new + * address before clearing TXCOMP: + */ + at91dci_set_address(sc, sc->sc_dv_addr); + } + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + + return (0); /* complete */ + +not_complete: + if (temp) { + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + } + return (1); /* not complete */ +} + +static uint8_t +at91dci_xfer_do_fifo(struct usb2_xfer *xfer) +{ + struct at91dci_softc *sc; + struct at91dci_td *td; + uint8_t temp; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + temp = 0; + if (td->fifo_bank) + temp |= 1; + td = td->obj_next; + xfer->td_transfer_cache = td; + if (temp & 1) + td->fifo_bank = 1; + } + return (1); /* not complete */ + +done: + sc = xfer->usb2_sc; + temp = (xfer->endpoint & UE_ADDR); + + /* update FIFO bank flag and multi buffer */ + if (td->fifo_bank) { + sc->sc_ep_flags[temp].fifo_bank = 1; + } else { + sc->sc_ep_flags[temp].fifo_bank = 0; + } + + /* compute all actual lengths */ + + at91dci_standard_done(xfer); + + return (0); /* complete */ +} + +static void +at91dci_interrupt_poll(struct at91dci_softc *sc) +{ + struct usb2_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!at91dci_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } + return; +} + +static void +at91dci_vbus_interrupt(struct usb2_bus *bus, uint8_t is_on) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus); + + DPRINTFN(5, "vbus = %u\n", is_on); + + mtx_lock(&sc->sc_bus.mtx); + if (is_on) { + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + + usb2_sw_transfer(&sc->sc_root_intr, + &at91dci_root_intr_done); + } + } else { + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + + usb2_sw_transfer(&sc->sc_root_intr, + &at91dci_root_intr_done); + } + } + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +void +at91dci_interrupt(struct at91dci_softc *sc) +{ + uint32_t status; + + mtx_lock(&sc->sc_bus.mtx); + + status = AT91_UDP_READ_4(sc, AT91_UDP_ISR); + status &= AT91_UDP_INT_DEFAULT; + + if (!status) { + mtx_unlock(&sc->sc_bus.mtx); + return; + } + /* acknowledge interrupts */ + + AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, status); + + /* check for any bus state change interrupts */ + + if (status & AT91_UDP_INT_BUS) { + + DPRINTFN(5, "real bus interrupt 0x%08x\n", status); + + if (status & AT91_UDP_INT_END_BR) { + + /* set correct state */ + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* disable resume interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, + AT91_UDP_INT_RXRSM); + /* enable suspend interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, + AT91_UDP_INT_RXSUSP); + } + /* + * If RXRSM and RXSUSP is set at the same time we interpret + * that like RESUME. Resume is set when there is at least 3 + * milliseconds of inactivity on the USB BUS. + */ + if (status & AT91_UDP_INT_RXRSM) { + if (sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + + /* disable resume interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, + AT91_UDP_INT_RXRSM); + /* enable suspend interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, + AT91_UDP_INT_RXSUSP); + } + } else if (status & AT91_UDP_INT_RXSUSP) { + if (!sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + + /* disable suspend interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, + AT91_UDP_INT_RXSUSP); + + /* enable resume interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, + AT91_UDP_INT_RXRSM); + } + } + /* complete root HUB interrupt endpoint */ + + usb2_sw_transfer(&sc->sc_root_intr, + &at91dci_root_intr_done); + } + /* check for any endpoint interrupts */ + + if (status & AT91_UDP_INT_EPS) { + + DPRINTFN(5, "real endpoint interrupt 0x%08x\n", status); + + at91dci_interrupt_poll(sc); + } + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +at91dci_setup_standard_chain_sub(struct at91dci_std_temp *temp) +{ + struct at91dci_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->fifo_bank = 0; + td->error = 0; + td->did_stall = 0; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; + return; +} + +static void +at91dci_setup_standard_chain(struct usb2_xfer *xfer) +{ + struct at91dci_std_temp temp; + struct at91dci_softc *sc; + struct at91dci_td *td; + uint32_t x; + uint8_t ep_no; + uint8_t need_sync; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpoint), + xfer->sumlen, usb2_get_speed(xfer->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.offset = 0; + + sc = xfer->usb2_sc; + ep_no = (xfer->endpoint & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &at91dci_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + + at91dci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpoint & UE_DIR_IN) { + temp.func = &at91dci_data_tx; + need_sync = 1; + } else { + temp.func = &at91dci_data_rx; + need_sync = 0; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } else { + need_sync = 0; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + temp.setup_alt_next = 0; + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + at91dci_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + + /* check if we need to sync */ + if (need_sync && xfer->flags_int.control_xfr) { + + /* we need a SYNC point after TX */ + temp.func = &at91dci_data_tx_sync; + temp.len = 0; + temp.short_pkt = 0; + + at91dci_setup_standard_chain_sub(&temp); + } + /* check if we should append a status stage */ + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + if (xfer->endpoint & UE_DIR_IN) { + temp.func = &at91dci_data_rx; + need_sync = 0; + } else { + temp.func = &at91dci_data_tx; + need_sync = 1; + } + temp.len = 0; + temp.short_pkt = 0; + + at91dci_setup_standard_chain_sub(&temp); + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &at91dci_data_tx_sync; + temp.len = 0; + temp.short_pkt = 0; + + at91dci_setup_standard_chain_sub(&temp); + } + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; + + /* setup the correct fifo bank */ + if (sc->sc_ep_flags[ep_no].fifo_bank) { + td = xfer->td_transfer_first; + td->fifo_bank = 1; + } + return; +} + +static void +at91dci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + struct at91dci_softc *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + at91dci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +at91dci_start_standard_chain(struct usb2_xfer *xfer) +{ + DPRINTFN(9, "\n"); + + /* poll one time */ + if (at91dci_xfer_do_fifo(xfer)) { + + struct at91dci_softc *sc = xfer->usb2_sc; + uint8_t ep_no = xfer->endpoint & UE_ADDR; + + /* + * Only enable the endpoint interrupt when we are actually + * waiting for data, hence we are dealing with level + * triggered interrupts ! + */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, AT91_UDP_INT_EP(ep_no)); + + DPRINTFN(15, "enable interrupts on endpoint %d\n", ep_no); + + /* put transfer on interrupt queue */ + usb2_transfer_enqueue(&xfer->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, + &at91dci_timeout, xfer->timeout); + } + } + return; +} + +static void +at91dci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + + DPRINTFN(9, "\n"); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_PRE_DATA) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + at91dci_device_done(xfer, std->err); + } + goto done; + } + /* setup buffer */ + std->ptr = sc->sc_hub_idata; + std->len = sizeof(sc->sc_hub_idata); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + +done: + return; +} + +static usb2_error_t +at91dci_standard_done_sub(struct usb2_xfer *xfer) +{ + struct at91dci_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +at91dci_standard_done(struct usb2_xfer *xfer) +{ + usb2_error_t err = 0; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = at91dci_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = at91dci_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = at91dci_standard_done_sub(xfer); + } +done: + at91dci_device_done(xfer, err); + return; +} + +/*------------------------------------------------------------------------* + * at91dci_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +at91dci_device_done(struct usb2_xfer *xfer, usb2_error_t error) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + uint8_t ep_no; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n", + xfer, xfer->pipe, error); + + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + ep_no = (xfer->endpoint & UE_ADDR); + + /* disable endpoint interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, AT91_UDP_INT_EP(ep_no)); + + DPRINTFN(15, "disable interrupts on endpoint %d\n", ep_no); + } + /* dequeue transfer and start next transfer */ + usb2_transfer_done(xfer, error); + return; +} + +static void +at91dci_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer, + struct usb2_pipe *pipe) +{ + struct at91dci_softc *sc; + uint32_t csr_val; + uint8_t csr_reg; + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + DPRINTFN(5, "pipe=%p\n", pipe); + + if (xfer) { + /* cancel any ongoing transfers */ + at91dci_device_done(xfer, USB_ERR_STALLED); + } + /* set FORCESTALL */ + sc = AT9100_DCI_BUS2SC(udev->bus); + csr_reg = (pipe->edesc->bEndpointAddress & UE_ADDR); + csr_reg = AT91_UDP_CSR(csr_reg); + csr_val = AT91_UDP_READ_4(sc, csr_reg); + AT91_CSR_ACK(csr_val, AT91_UDP_CSR_FORCESTALL); + AT91_UDP_WRITE_4(sc, csr_reg, csr_val); + return; +} + +static void +at91dci_clear_stall_sub(struct at91dci_softc *sc, uint8_t ep_no, + uint8_t ep_type, uint8_t ep_dir) +{ + const struct usb2_hw_ep_profile *pf; + uint32_t csr_val; + uint32_t temp; + uint8_t csr_reg; + uint8_t to; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* compute CSR register offset */ + csr_reg = AT91_UDP_CSR(ep_no); + + /* compute default CSR value */ + csr_val = 0; + AT91_CSR_ACK(csr_val, 0); + + /* disable endpoint */ + AT91_UDP_WRITE_4(sc, csr_reg, csr_val); + + /* get endpoint profile */ + at91dci_get_hw_ep_profile(NULL, &pf, ep_no); + + /* reset FIFO */ + AT91_UDP_WRITE_4(sc, AT91_UDP_RST, AT91_UDP_RST_EP(ep_no)); + AT91_UDP_WRITE_4(sc, AT91_UDP_RST, 0); + + /* + * NOTE: One would assume that a FIFO reset would release the + * FIFO banks aswell, but it doesn't! We have to do this + * manually! + */ + + /* release FIFO banks, if any */ + for (to = 0; to != 2; to++) { + + /* get csr value */ + csr_val = AT91_UDP_READ_4(sc, csr_reg); + + if (csr_val & (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1)) { + /* clear status bits */ + if (pf->support_multi_buffer) { + if (sc->sc_ep_flags[ep_no].fifo_bank) { + sc->sc_ep_flags[ep_no].fifo_bank = 0; + temp = AT91_UDP_CSR_RX_DATA_BK1; + } else { + sc->sc_ep_flags[ep_no].fifo_bank = 1; + temp = AT91_UDP_CSR_RX_DATA_BK0; + } + } else { + temp = (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1); + } + } else { + temp = 0; + } + + /* clear FORCESTALL */ + temp |= AT91_UDP_CSR_STALLSENT; + + AT91_CSR_ACK(csr_val, temp); + AT91_UDP_WRITE_4(sc, csr_reg, csr_val); + } + + /* compute default CSR value */ + csr_val = 0; + AT91_CSR_ACK(csr_val, 0); + + /* enable endpoint */ + csr_val &= ~AT91_UDP_CSR_ET_MASK; + csr_val |= AT91_UDP_CSR_EPEDS; + + if (ep_type == UE_CONTROL) { + csr_val |= AT91_UDP_CSR_ET_CTRL; + } else { + if (ep_type == UE_BULK) { + csr_val |= AT91_UDP_CSR_ET_BULK; + } else if (ep_type == UE_INTERRUPT) { + csr_val |= AT91_UDP_CSR_ET_INT; + } else { + csr_val |= AT91_UDP_CSR_ET_ISO; + } + if (ep_dir & UE_DIR_IN) { + csr_val |= AT91_UDP_CSR_ET_DIR_IN; + } + } + + /* enable endpoint */ + AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(ep_no), csr_val); + + return; +} + +static void +at91dci_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe) +{ + struct at91dci_softc *sc; + struct usb2_endpoint_descriptor *ed; + + DPRINTFN(5, "pipe=%p\n", pipe); + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + /* check mode */ + if (udev->flags.usb2_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = AT9100_DCI_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = pipe->edesc; + + /* reset endpoint */ + at91dci_clear_stall_sub(sc, + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); + return; +} + +usb2_error_t +at91dci_init(struct at91dci_softc *sc) +{ + uint32_t csr_val; + uint8_t n; + + DPRINTF("start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_1_1; + sc->sc_bus.methods = &at91dci_bus_methods; + + mtx_lock(&sc->sc_bus.mtx); + + /* turn on clocks */ + + if (sc->sc_clocks_on) { + (sc->sc_clocks_on) (sc->sc_clocks_arg); + } + /* wait a little for things to stabilise */ + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + + /* disable and clear all interrupts */ + + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, 0xFFFFFFFF); + AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, 0xFFFFFFFF); + + /* compute default CSR value */ + + csr_val = 0; + AT91_CSR_ACK(csr_val, 0); + + /* disable all endpoints */ + + for (n = 0; n != AT91_UDP_EP_MAX; n++) { + + /* disable endpoint */ + AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(n), csr_val); + } + + /* enable the control endpoint */ + + AT91_CSR_ACK(csr_val, AT91_UDP_CSR_ET_CTRL | + AT91_UDP_CSR_EPEDS); + + /* write to FIFO control register */ + + AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(0), csr_val); + + /* enable the interrupts we want */ + + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, AT91_UDP_INT_BUS); + + /* turn off clocks */ + + at91dci_clocks_off(sc); + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + + at91dci_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +at91dci_uninit(struct at91dci_softc *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + /* disable and clear all interrupts */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, 0xFFFFFFFF); + AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, 0xFFFFFFFF); + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + at91dci_pull_down(sc); + at91dci_clocks_off(sc); + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +void +at91dci_suspend(struct at91dci_softc *sc) +{ + return; +} + +void +at91dci_resume(struct at91dci_softc *sc) +{ + return; +} + +static void +at91dci_do_poll(struct usb2_bus *bus) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + at91dci_interrupt_poll(sc); + at91dci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/*------------------------------------------------------------------------* + * at91dci bulk support + *------------------------------------------------------------------------*/ +static void +at91dci_device_bulk_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_device_bulk_close(struct usb2_xfer *xfer) +{ + at91dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +at91dci_device_bulk_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_device_bulk_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + at91dci_setup_standard_chain(xfer); + at91dci_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods at91dci_device_bulk_methods = +{ + .open = at91dci_device_bulk_open, + .close = at91dci_device_bulk_close, + .enter = at91dci_device_bulk_enter, + .start = at91dci_device_bulk_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * at91dci control support + *------------------------------------------------------------------------*/ +static void +at91dci_device_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_device_ctrl_close(struct usb2_xfer *xfer) +{ + at91dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +at91dci_device_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_device_ctrl_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + at91dci_setup_standard_chain(xfer); + at91dci_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods at91dci_device_ctrl_methods = +{ + .open = at91dci_device_ctrl_open, + .close = at91dci_device_ctrl_close, + .enter = at91dci_device_ctrl_enter, + .start = at91dci_device_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * at91dci interrupt support + *------------------------------------------------------------------------*/ +static void +at91dci_device_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_device_intr_close(struct usb2_xfer *xfer) +{ + at91dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +at91dci_device_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_device_intr_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + at91dci_setup_standard_chain(xfer); + at91dci_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods at91dci_device_intr_methods = +{ + .open = at91dci_device_intr_open, + .close = at91dci_device_intr_close, + .enter = at91dci_device_intr_enter, + .start = at91dci_device_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * at91dci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +at91dci_device_isoc_fs_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_device_isoc_fs_close(struct usb2_xfer *xfer) +{ + at91dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +at91dci_device_isoc_fs_enter(struct usb2_xfer *xfer) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + uint32_t temp; + uint32_t nframes; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->pipe->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = AT91_UDP_READ_4(sc, AT91_UDP_FRM); + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (nframes - xfer->pipe->isoc_next) & AT91_UDP_FRM_MASK; + + if ((xfer->pipe->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->pipe->isoc_next = (nframes + 3) & AT91_UDP_FRM_MASK; + xfer->pipe->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->pipe->isoc_next - nframes) & AT91_UDP_FRM_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->pipe->isoc_next += xfer->nframes; + + /* setup TDs */ + at91dci_setup_standard_chain(xfer); + return; +} + +static void +at91dci_device_isoc_fs_start(struct usb2_xfer *xfer) +{ + /* start TD chain */ + at91dci_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods at91dci_device_isoc_fs_methods = +{ + .open = at91dci_device_isoc_fs_open, + .close = at91dci_device_isoc_fs_close, + .enter = at91dci_device_isoc_fs_enter, + .start = at91dci_device_isoc_fs_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * at91dci root control support + *------------------------------------------------------------------------* + * simulate a hardware HUB by handling + * all the necessary requests + *------------------------------------------------------------------------*/ + +static void +at91dci_root_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_root_ctrl_close(struct usb2_xfer *xfer) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + at91dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* + * USB descriptors for the virtual Root HUB: + */ + +static const struct usb2_device_descriptor at91dci_devd = { + .bLength = sizeof(struct usb2_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct usb2_device_qualifier at91dci_odevd = { + .bLength = sizeof(struct usb2_device_qualifier), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, +}; + +static const struct at91dci_config_desc at91dci_confd = { + .confd = { + .bLength = sizeof(struct usb2_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(at91dci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb2_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = UIPROTO_HSHUBSTT, + }, + + .endpd = { + .bLength = sizeof(struct usb2_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | AT9100_DCI_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb2_hub_descriptor_min at91dci_hubd = { + .bDescLength = sizeof(at91dci_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 16, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'A', 0, 'T', 0, 'M', 0, 'E', 0, 'L', 0 + +#define STRING_PRODUCT \ + 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, at91dci_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, at91dci_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, at91dci_product); + +static void +at91dci_root_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_root_ctrl_start(struct usb2_xfer *xfer) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command( + &sc->sc_config_td, NULL, &at91dci_root_ctrl_task, 0, 0); + + return; +} + +static void +at91dci_root_ctrl_task(struct at91dci_softc *sc, + struct at91dci_config_copy *cc, uint16_t refcount) +{ + at91dci_root_ctrl_poll(sc); + return; +} + +static void +at91dci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + uint16_t value; + uint16_t index; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_SETUP) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + at91dci_device_done(xfer, std->err); + } + goto done; + } + /* buffer reset */ + std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0); + std->len = 0; + + value = UGETW(std->req.wValue); + index = UGETW(std->req.wIndex); + + use_polling = mtx_owned(xfer->priv_mtx) ? 1 : 0; + + /* demultiplex the control request */ + + switch (std->req.bmRequestType) { + case UT_READ_DEVICE: + switch (std->req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (std->req.bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(std->req.wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(std->req.wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (std->req.bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (std->req.bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (std->req.bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (std->req.bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (std->req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + std->len = sizeof(at91dci_devd); + std->ptr = USB_ADD_BYTES(&at91dci_devd, 0); + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + std->len = sizeof(at91dci_confd); + std->ptr = USB_ADD_BYTES(&at91dci_confd, 0); + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + std->len = sizeof(at91dci_langtab); + std->ptr = USB_ADD_BYTES(&at91dci_langtab, 0); + goto tr_valid; + + case 1: /* Vendor */ + std->len = sizeof(at91dci_vendor); + std->ptr = USB_ADD_BYTES(&at91dci_vendor, 0); + goto tr_valid; + + case 2: /* Product */ + std->len = sizeof(at91dci_product); + std->ptr = USB_ADD_BYTES(&at91dci_product, 0); + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + std->len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + std->len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + std->len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + std->len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + at91dci_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + at91dci_pull_down(sc); + at91dci_clocks_off(sc); + break; + case UHF_C_PORT_CONNECTION: + sc->sc_flags.change_connect = 0; + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(9, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + at91dci_clocks_on(sc); + at91dci_pull_up(sc); + } else { + at91dci_pull_down(sc); + at91dci_clocks_off(sc); + } + + /* Select FULL-speed and Device Side Mode */ + + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + /* reset endpoint flags */ + bzero(sc->sc_ep_flags, sizeof(sc->sc_ep_flags)); + } + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + std->len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + std->ptr = USB_ADD_BYTES(&at91dci_hubd, 0); + std->len = sizeof(at91dci_hubd); + goto tr_valid; + +tr_stalled: + std->err = USB_ERR_STALLED; +tr_valid: +done: + return; +} + +static void +at91dci_root_ctrl_poll(struct at91dci_softc *sc) +{ + usb2_sw_transfer(&sc->sc_root_ctrl, + &at91dci_root_ctrl_done); + return; +} + +struct usb2_pipe_methods at91dci_root_ctrl_methods = +{ + .open = at91dci_root_ctrl_open, + .close = at91dci_root_ctrl_close, + .enter = at91dci_root_ctrl_enter, + .start = at91dci_root_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 0, +}; + +/*------------------------------------------------------------------------* + * at91dci root interrupt support + *------------------------------------------------------------------------*/ +static void +at91dci_root_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_root_intr_close(struct usb2_xfer *xfer) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + at91dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +at91dci_root_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_root_intr_start(struct usb2_xfer *xfer) +{ + struct at91dci_softc *sc = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +struct usb2_pipe_methods at91dci_root_intr_methods = +{ + .open = at91dci_root_intr_open, + .close = at91dci_root_intr_close, + .enter = at91dci_root_intr_enter, + .start = at91dci_root_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +static void +at91dci_xfer_setup(struct usb2_setup_params *parm) +{ + const struct usb2_hw_ep_profile *pf; + struct at91dci_softc *sc; + struct usb2_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + sc = AT9100_DCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x500; + + usb2_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if (parm->methods == &at91dci_device_ctrl_methods) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + + 1 /* SYNC 2 */ ; + + } else if (parm->methods == &at91dci_device_bulk_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &at91dci_device_intr_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &at91dci_device_isoc_fs_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else { + + ntd = 0; + } + + /* + * check if "usb2_transfer_setup_sub" set an error + */ + if (parm->err) { + return; + } + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + if (ntd) { + + ep_no = xfer->endpoint & UE_ADDR; + at91dci_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + } else { + ep_no = 0; + pf = NULL; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct at91dci_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->io_tag = sc->sc_io_tag; + td->io_hdl = sc->sc_io_hdl; + td->max_packet_size = xfer->max_packet_size; + td->status_reg = AT91_UDP_CSR(ep_no); + td->fifo_reg = AT91_UDP_FDR(ep_no); + if (pf->support_multi_buffer) { + td->support_multi_buffer = 1; + } + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; + return; +} + +static void +at91dci_xfer_unsetup(struct usb2_xfer *xfer) +{ + return; +} + +static void +at91dci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc, + struct usb2_pipe *pipe) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(udev->bus); + + DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + pipe, udev->address, + edesc->bEndpointAddress, udev->flags.usb2_mode, + sc->sc_rt_addr); + + if (udev->device_index == sc->sc_rt_addr) { + + if (udev->flags.usb2_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + switch (edesc->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &at91dci_root_ctrl_methods; + break; + case UE_DIR_IN | AT9100_DCI_INTR_ENDPT: + pipe->methods = &at91dci_root_intr_methods; + break; + default: + /* do nothing */ + break; + } + } else { + + if (udev->flags.usb2_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if (udev->speed != USB_SPEED_FULL) { + /* not supported */ + return; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + pipe->methods = &at91dci_device_ctrl_methods; + break; + case UE_INTERRUPT: + pipe->methods = &at91dci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + pipe->methods = &at91dci_device_isoc_fs_methods; + break; + case UE_BULK: + pipe->methods = &at91dci_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } + return; +} + +struct usb2_bus_methods at91dci_bus_methods = +{ + .pipe_init = &at91dci_pipe_init, + .xfer_setup = &at91dci_xfer_setup, + .xfer_unsetup = &at91dci_xfer_unsetup, + .do_poll = &at91dci_do_poll, + .get_hw_ep_profile = &at91dci_get_hw_ep_profile, + .set_stall = &at91dci_set_stall, + .clear_stall = &at91dci_clear_stall, + .vbus_interrupt = &at91dci_vbus_interrupt, + .rem_wakeup_set = &at91dci_rem_wakeup_set, +}; diff --git a/sys/dev/usb2/controller/at91dci.h b/sys/dev/usb2/controller/at91dci.h new file mode 100644 index 000000000000..bade80a38309 --- /dev/null +++ b/sys/dev/usb2/controller/at91dci.h @@ -0,0 +1,242 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2006 ATMEL + * Copyright (c) 2007 Hans Petter Selasky + * 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. + */ + +/* + * USB Device Port (UDP) register definition, based on + * "AT91RM9200.h" provided by ATMEL. + */ + +#ifndef _AT9100_DCI_H_ +#define _AT9100_DCI_H_ + +#define AT91_UDP_FRM 0x00 /* Frame number register */ +#define AT91_UDP_FRM_MASK (0x7FF << 0) /* Frame Number as Defined in + * the Packet Field Formats */ +#define AT91_UDP_FRM_ERR (0x1 << 16) /* Frame Error */ +#define AT91_UDP_FRM_OK (0x1 << 17) /* Frame OK */ + +#define AT91_UDP_GSTATE 0x04 /* Global state register */ +#define AT91_UDP_GSTATE_ADDR (0x1 << 0) /* Addressed state */ +#define AT91_UDP_GSTATE_CONFG (0x1 << 1) /* Configured */ +#define AT91_UDP_GSTATE_ESR (0x1 << 2) /* Enable Send Resume */ +#define AT91_UDP_GSTATE_RSM (0x1 << 3) /* A Resume Has Been Sent to + * the Host */ +#define AT91_UDP_GSTATE_RMW (0x1 << 4) /* Remote Wake Up Enable */ + +#define AT91_UDP_FADDR 0x08 /* Function Address Register */ +#define AT91_UDP_FADDR_MASK (0x7F << 0)/* Function Address Mask */ +#define AT91_UDP_FADDR_EN (0x1 << 8)/* Function Enable */ + +#define AT91_UDP_RES0 0x0C /* Reserved 0 */ + +#define AT91_UDP_IER 0x10 /* Interrupt Enable Register */ +#define AT91_UDP_IDR 0x14 /* Interrupt Disable Register */ +#define AT91_UDP_IMR 0x18 /* Interrupt Mask Register */ +#define AT91_UDP_ISR 0x1C /* Interrupt Status Register */ +#define AT91_UDP_ICR 0x20 /* Interrupt Clear Register */ +#define AT91_UDP_INT_EP(n) (0x1 <<(n))/* Endpoint "n" Interrupt */ +#define AT91_UDP_INT_RXSUSP (0x1 << 8)/* USB Suspend Interrupt */ +#define AT91_UDP_INT_RXRSM (0x1 << 9)/* USB Resume Interrupt */ +#define AT91_UDP_INT_EXTRSM (0x1 << 10)/* USB External Resume Interrupt */ +#define AT91_UDP_INT_SOFINT (0x1 << 11)/* USB Start Of frame Interrupt */ +#define AT91_UDP_INT_END_BR (0x1 << 12)/* USB End Of Bus Reset Interrupt */ +#define AT91_UDP_INT_WAKEUP (0x1 << 13)/* USB Resume Interrupt */ + +#define AT91_UDP_INT_BUS \ + (AT91_UDP_INT_RXSUSP|AT91_UDP_INT_RXRSM| \ + AT91_UDP_INT_END_BR) + +#define AT91_UDP_INT_EPS \ + (AT91_UDP_INT_EP(0)|AT91_UDP_INT_EP(1)| \ + AT91_UDP_INT_EP(2)|AT91_UDP_INT_EP(3)| \ + AT91_UDP_INT_EP(4)|AT91_UDP_INT_EP(5)) + +#define AT91_UDP_INT_DEFAULT \ + (AT91_UDP_INT_EPS|AT91_UDP_INT_BUS) + +#define AT91_UDP_RES1 0x24 /* Reserved 1 */ +#define AT91_UDP_RST 0x28 /* Reset Endpoint Register */ +#define AT91_UDP_RST_EP(n) (0x1 << (n))/* Reset Endpoint "n" */ + +#define AT91_UDP_RES2 0x2C /* Reserved 2 */ + +#define AT91_UDP_CSR(n) (0x30 + (4*(n)))/* Endpoint Control and Status + * Register */ +#define AT91_UDP_CSR_TXCOMP (0x1 << 0) /* Generates an IN packet with data + * previously written in the DPR */ +#define AT91_UDP_CSR_RX_DATA_BK0 (0x1 << 1) /* Receive Data Bank 0 */ +#define AT91_UDP_CSR_RXSETUP (0x1 << 2) /* Sends STALL to the Host + * (Control endpoints) */ +#define AT91_UDP_CSR_ISOERROR (0x1 << 3) /* Isochronous error + * (Isochronous endpoints) */ +#define AT91_UDP_CSR_STALLSENT (0x1 << 3) /* Stall sent (Control, bulk, + * interrupt endpoints) */ +#define AT91_UDP_CSR_TXPKTRDY (0x1 << 4) /* Transmit Packet Ready */ +#define AT91_UDP_CSR_FORCESTALL (0x1 << 5) /* Force Stall (used by + * Control, Bulk and + * Isochronous endpoints). */ +#define AT91_UDP_CSR_RX_DATA_BK1 (0x1 << 6) /* Receive Data Bank 1 (only + * used by endpoints with + * ping-pong attributes). */ +#define AT91_UDP_CSR_DIR (0x1 << 7) /* Transfer Direction */ +#define AT91_UDP_CSR_ET_MASK (0x7 << 8) /* Endpoint transfer type mask */ +#define AT91_UDP_CSR_ET_CTRL (0x0 << 8) /* Control IN+OUT */ +#define AT91_UDP_CSR_ET_ISO (0x1 << 8) /* Isochronous */ +#define AT91_UDP_CSR_ET_BULK (0x2 << 8) /* Bulk */ +#define AT91_UDP_CSR_ET_INT (0x3 << 8) /* Interrupt */ +#define AT91_UDP_CSR_ET_DIR_OUT (0x0 << 8) /* OUT tokens */ +#define AT91_UDP_CSR_ET_DIR_IN (0x4 << 8) /* IN tokens */ +#define AT91_UDP_CSR_DTGLE (0x1 << 11) /* Data Toggle */ +#define AT91_UDP_CSR_EPEDS (0x1 << 15) /* Endpoint Enable Disable */ +#define AT91_UDP_CSR_RXBYTECNT (0x7FF << 16) /* Number Of Bytes Available + * in the FIFO */ + +#define AT91_UDP_FDR(n) (0x50 + (4*(n)))/* Endpoint FIFO Data Register */ +#define AT91_UDP_RES3 0x70 /* Reserved 3 */ +#define AT91_UDP_TXVC 0x74 /* Transceiver Control Register */ +#define AT91_UDP_TXVC_DIS (0x1 << 8) + +#define AT91_UDP_EP_MAX 6 /* maximum number of endpoints + * supported */ + +#define AT91_UDP_READ_4(sc, reg) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define AT91_UDP_WRITE_4(sc, reg, data) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +struct at91dci_td; + +typedef uint8_t (at91dci_cmd_t)(struct at91dci_td *td); + +struct at91dci_td { + bus_space_tag_t io_tag; + bus_space_handle_t io_hdl; + struct at91dci_td *obj_next; + at91dci_cmd_t *func; + struct usb2_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint16_t max_packet_size; + uint8_t status_reg; + uint8_t fifo_reg; + uint8_t fifo_bank:1; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t support_multi_buffer:1; + uint8_t did_stall:1; +}; + +struct at91dci_std_temp { + at91dci_cmd_t *func; + struct usb2_page_cache *pc; + struct at91dci_td *td; + struct at91dci_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; +}; + +struct at91dci_config_desc { + struct usb2_config_descriptor confd; + struct usb2_interface_descriptor ifcd; + struct usb2_endpoint_descriptor endpd; +} __packed; + +union at91dci_hub_temp { + uWord wValue; + struct usb2_port_status ps; +}; + +struct at91dci_ep_flags { + uint8_t fifo_bank:1; /* hardware specific */ +}; + +struct at91dci_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t remote_wakeup:1; + uint8_t self_powered:1; + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; +}; + +struct at91dci_softc { + struct usb2_bus sc_bus; + union at91dci_hub_temp sc_hub_temp; + LIST_HEAD(, usb2_xfer) sc_interrupt_list_head; + struct usb2_sw_transfer sc_root_ctrl; + struct usb2_sw_transfer sc_root_intr; + struct usb2_config_td sc_config_td; + + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + void (*sc_clocks_on) (void *arg); + void (*sc_clocks_off) (void *arg); + void *sc_clocks_arg; + + void (*sc_pull_up) (void *arg); + void (*sc_pull_down) (void *arg); + void *sc_pull_arg; + + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root HUB config */ + + uint8_t sc_hub_idata[1]; + + struct at91dci_flags sc_flags; + struct at91dci_ep_flags sc_ep_flags[AT91_UDP_EP_MAX]; +}; + +/* prototypes */ + +usb2_error_t at91dci_init(struct at91dci_softc *sc); +void at91dci_uninit(struct at91dci_softc *sc); +void at91dci_suspend(struct at91dci_softc *sc); +void at91dci_resume(struct at91dci_softc *sc); +void at91dci_interrupt(struct at91dci_softc *sc); + +#endif /* _AT9100_DCI_H_ */ diff --git a/sys/dev/usb2/controller/at91dci_atmelarm.c b/sys/dev/usb2/controller/at91dci_atmelarm.c new file mode 100644 index 000000000000..0a6f96112b1a --- /dev/null +++ b/sys/dev/usb2/controller/at91dci_atmelarm.c @@ -0,0 +1,361 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2007-2008 Hans Petter Selasky. 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. + */ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define MEM_RID 0 + +/* Pin Definitions - do they belong here or somewhere else ? */ + +#define VBUS_MASK AT91C_PIO_PB24 +#define VBUS_BASE AT91RM92_PIOB_BASE + +#define PULLUP_MASK AT91C_PIO_PB22 +#define PULLUP_BASE AT91RM92_PIOB_BASE + +static device_probe_t at91_udp_probe; +static device_attach_t at91_udp_attach; +static device_detach_t at91_udp_detach; +static device_shutdown_t at91_udp_shutdown; + +struct at91_udp_softc { + struct at91dci_softc sc_dci; /* must be first */ + struct at91_pmc_clock *sc_iclk; + struct at91_pmc_clock *sc_fclk; + struct resource *sc_vbus_irq_res; + void *sc_vbus_intr_hdl; +}; + +static void +at91_vbus_interrupt(struct at91_udp_softc *sc) +{ + uint32_t temp; + uint8_t vbus_val; + + /* XXX temporary clear interrupts here */ + + temp = at91_pio_gpio_clear_interrupt(VBUS_BASE); + + /* just forward it */ + + vbus_val = at91_pio_gpio_get(VBUS_BASE, VBUS_MASK); + (sc->sc_dci.sc_bus.methods->vbus_interrupt) + (&sc->sc_dci.sc_bus, vbus_val); + return; +} + +static void +at91_udp_clocks_on(void *arg) +{ + struct at91_udp_softc *sc = arg; + + at91_pmc_clock_enable(sc->sc_iclk); + at91_pmc_clock_enable(sc->sc_fclk); + return; +} + +static void +at91_udp_clocks_off(void *arg) +{ + struct at91_udp_softc *sc = arg; + + at91_pmc_clock_disable(sc->sc_fclk); + at91_pmc_clock_disable(sc->sc_iclk); + return; +} + +static void +at91_udp_pull_up(void *arg) +{ + at91_pio_gpio_set(PULLUP_BASE, PULLUP_MASK); + return; +} + +static void +at91_udp_pull_down(void *arg) +{ + at91_pio_gpio_clear(PULLUP_BASE, PULLUP_MASK); + return; +} + +static int +at91_udp_probe(device_t dev) +{ + device_set_desc(dev, "AT91 integrated AT91_UDP controller"); + return (0); +} + +static int +at91_udp_attach(device_t dev) +{ + struct at91_udp_softc *sc = device_get_softc(dev); + int err; + int rid; + + if (sc == NULL) { + return (ENXIO); + } + /* setup AT9100 USB device controller interface softc */ + + sc->sc_dci.sc_clocks_on = &at91_udp_clocks_on; + sc->sc_dci.sc_clocks_off = &at91_udp_clocks_off; + sc->sc_dci.sc_clocks_arg = sc; + sc->sc_dci.sc_pull_up = &at91_udp_pull_up; + sc->sc_dci.sc_pull_down = &at91_udp_pull_down; + sc->sc_dci.sc_pull_arg = sc; + + /* get all DMA memory */ + + if (usb2_bus_mem_alloc_all(&sc->sc_dci.sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + /* + * configure VBUS input pin, enable deglitch and enable + * interrupt : + */ + at91_pio_use_gpio(VBUS_BASE, VBUS_MASK); + at91_pio_gpio_input(VBUS_BASE, VBUS_MASK); + at91_pio_gpio_set_deglitch(VBUS_BASE, VBUS_MASK, 1); + at91_pio_gpio_set_interrupt(VBUS_BASE, VBUS_MASK, 1); + + /* + * configure PULLUP output pin : + */ + at91_pio_use_gpio(PULLUP_BASE, PULLUP_MASK); + at91_pio_gpio_output(PULLUP_BASE, PULLUP_MASK, 0); + + at91_udp_pull_down(sc); + + /* wait 10ms for pulldown to stabilise */ + usb2_pause_mtx(NULL, 10); + + sc->sc_iclk = at91_pmc_clock_ref("udc_clk"); + sc->sc_fclk = at91_pmc_clock_ref("udpck"); + + rid = MEM_RID; + sc->sc_dci.sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + + if (!(sc->sc_dci.sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_dci.sc_io_tag = rman_get_bustag(sc->sc_dci.sc_io_res); + sc->sc_dci.sc_io_hdl = rman_get_bushandle(sc->sc_dci.sc_io_res); + sc->sc_dci.sc_io_size = rman_get_size(sc->sc_dci.sc_io_res); + + rid = 0; + sc->sc_dci.sc_irq_res = + bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (!(sc->sc_dci.sc_irq_res)) { + goto error; + } + rid = 1; + sc->sc_vbus_irq_res = + bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (!(sc->sc_vbus_irq_res)) { + goto error; + } + sc->sc_dci.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_dci.sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_dci.sc_bus.bdev, &sc->sc_dci.sc_bus); + + err = usb2_config_td_setup(&sc->sc_dci.sc_config_td, sc, + &sc->sc_dci.sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)at91dci_interrupt, sc, &sc->sc_dci.sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)at91dci_interrupt, sc, &sc->sc_dci.sc_intr_hdl); +#endif + if (err) { + sc->sc_dci.sc_intr_hdl = NULL; + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_vbus_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)at91_vbus_interrupt, sc, &sc->sc_vbus_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_vbus_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)at91_vbus_interrupt, sc, &sc->sc_vbus_intr_hdl); +#endif + if (err) { + sc->sc_vbus_intr_hdl = NULL; + goto error; + } + err = at91dci_init(&sc->sc_dci); + if (!err) { + err = device_probe_and_attach(sc->sc_dci.sc_bus.bdev); + } + if (err) { + goto error; + } else { + /* poll VBUS one time */ + at91_vbus_interrupt(sc); + } + return (0); + +error: + at91_udp_detach(dev); + return (ENXIO); +} + +static int +at91_udp_detach(device_t dev) +{ + struct at91_udp_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_dci.sc_bus.bdev) { + bdev = sc->sc_dci.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(dev); + + /* disable Transceiver */ + AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_TXVC, AT91_UDP_TXVC_DIS); + + /* disable and clear all interrupts */ + AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_IDR, 0xFFFFFFFF); + AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_ICR, 0xFFFFFFFF); + + /* disable VBUS interrupt */ + at91_pio_gpio_set_interrupt(VBUS_BASE, VBUS_MASK, 0); + + if (sc->sc_vbus_irq_res && sc->sc_vbus_intr_hdl) { + err = bus_teardown_intr(dev, sc->sc_vbus_irq_res, + sc->sc_vbus_intr_hdl); + sc->sc_vbus_intr_hdl = NULL; + } + if (sc->sc_vbus_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 1, + sc->sc_vbus_irq_res); + sc->sc_vbus_irq_res = NULL; + } + if (sc->sc_dci.sc_irq_res && sc->sc_dci.sc_intr_hdl) { + /* + * only call at91_udp_uninit() after at91_udp_init() + */ + at91dci_uninit(&sc->sc_dci); + + err = bus_teardown_intr(dev, sc->sc_dci.sc_irq_res, + sc->sc_dci.sc_intr_hdl); + sc->sc_dci.sc_intr_hdl = NULL; + } + if (sc->sc_dci.sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_dci.sc_irq_res); + sc->sc_dci.sc_irq_res = NULL; + } + if (sc->sc_dci.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, MEM_RID, + sc->sc_dci.sc_io_res); + sc->sc_dci.sc_io_res = NULL; + } + usb2_config_td_unsetup(&sc->sc_dci.sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_dci.sc_bus, NULL); + + /* disable clocks */ + at91_pmc_clock_disable(sc->sc_iclk); + at91_pmc_clock_disable(sc->sc_fclk); + at91_pmc_clock_deref(sc->sc_fclk); + at91_pmc_clock_deref(sc->sc_iclk); + + return (0); +} + +static int +at91_udp_shutdown(device_t dev) +{ + struct at91_udp_softc *sc = device_get_softc(dev); + int err; + + err = bus_generic_shutdown(dev); + if (err) + return (err); + + at91dci_uninit(&sc->sc_dci); + + return (0); +} + +static device_method_t at91_udp_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, at91_udp_probe), + DEVMETHOD(device_attach, at91_udp_attach), + DEVMETHOD(device_detach, at91_udp_detach), + DEVMETHOD(device_shutdown, at91_udp_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t at91_udp_driver = { + "at91_udp", + at91_udp_methods, + sizeof(struct at91_udp_softc), +}; + +static devclass_t at91_udp_devclass; + +DRIVER_MODULE(at91_udp, atmelarm, at91_udp_driver, at91_udp_devclass, 0, 0); diff --git a/sys/dev/usb2/controller/ehci2.c b/sys/dev/usb2/controller/ehci2.c new file mode 100644 index 000000000000..218a3620e511 --- /dev/null +++ b/sys/dev/usb2/controller/ehci2.c @@ -0,0 +1,3854 @@ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2004 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 2004 Lennart Augustsson. All rights reserved. + * Copyright (c) 2004 Charles M. Hannum. 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. + */ + +/* + * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. + * + * The EHCI 0.96 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r096.pdf + * The EHCI 1.0 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r10.pdf + * and the USB 2.0 spec at + * http://www.usb.org/developers/docs/usb_20.zip + * + */ + +/* + * TODO: + * 1) command failures are not recovered correctly + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#define USB_DEBUG_VAR ehcidebug +#define usb2_config_td_cc ehci_config_copy +#define usb2_config_td_softc ehci_softc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define EHCI_BUS2SC(bus) ((ehci_softc_t *)(((uint8_t *)(bus)) - \ + USB_P2U(&(((ehci_softc_t *)0)->sc_bus)))) + +#if USB_DEBUG +static int ehcidebug = 0; +static int ehcinohighspeed = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci"); +SYSCTL_INT(_hw_usb2_ehci, OID_AUTO, debug, CTLFLAG_RW, + &ehcidebug, 0, "Debug level"); +SYSCTL_INT(_hw_usb2_ehci, OID_AUTO, no_hs, CTLFLAG_RW, + &ehcinohighspeed, 0, "Disable High Speed USB"); + +static void ehci_dump_regs(ehci_softc_t *sc); +static void ehci_dump_sqh(ehci_qh_t *sqh); + +#endif + +#define EHCI_INTR_ENDPT 1 + +extern struct usb2_bus_methods ehci_bus_methods; +extern struct usb2_pipe_methods ehci_device_bulk_methods; +extern struct usb2_pipe_methods ehci_device_ctrl_methods; +extern struct usb2_pipe_methods ehci_device_intr_methods; +extern struct usb2_pipe_methods ehci_device_isoc_fs_methods; +extern struct usb2_pipe_methods ehci_device_isoc_hs_methods; +extern struct usb2_pipe_methods ehci_root_ctrl_methods; +extern struct usb2_pipe_methods ehci_root_intr_methods; + +static usb2_config_td_command_t ehci_root_ctrl_task; +static void ehci_do_poll(struct usb2_bus *bus); +static void ehci_root_ctrl_poll(struct ehci_softc *sc); +static void ehci_device_done(struct usb2_xfer *xfer, usb2_error_t error); +static uint8_t ehci_check_transfer(struct usb2_xfer *xfer); +static void ehci_timeout(void *arg); + +static usb2_sw_transfer_func_t ehci_root_intr_done; +static usb2_sw_transfer_func_t ehci_root_ctrl_done; + +struct ehci_std_temp { + struct usb2_page_cache *pc; + ehci_qtd_t *td; + ehci_qtd_t *td_next; + uint32_t average; + uint32_t qtd_status; + uint32_t len; + uint16_t max_frame_size; + uint8_t shortpkt; + uint8_t auto_data_toggle; + uint8_t setup_alt_next; + uint8_t short_frames_ok; +}; + +void +ehci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb) +{ + struct ehci_softc *sc = EHCI_BUS2SC(bus); + uint32_t i; + + cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg, + sizeof(uint32_t) * EHCI_FRAMELIST_COUNT, EHCI_FRAMELIST_ALIGN); + + cb(bus, &sc->sc_hw.async_start_pc, &sc->sc_hw.async_start_pg, + sizeof(ehci_qh_t), EHCI_QH_ALIGN); + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.intr_start_pc + i, + sc->sc_hw.intr_start_pg + i, + sizeof(ehci_qh_t), EHCI_QH_ALIGN); + } + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.isoc_hs_start_pc + i, + sc->sc_hw.isoc_hs_start_pg + i, + sizeof(ehci_itd_t), EHCI_ITD_ALIGN); + } + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.isoc_fs_start_pc + i, + sc->sc_hw.isoc_fs_start_pg + i, + sizeof(ehci_sitd_t), EHCI_SITD_ALIGN); + } + return; +} + +static usb2_error_t +ehci_hc_reset(ehci_softc_t *sc) +{ + uint32_t hcr; + uint32_t n; + + EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */ + + for (n = 0; n != 100; n++) { + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + hcr = EOREAD4(sc, EHCI_USBSTS); + if (hcr & EHCI_STS_HCH) { + hcr = 0; + break; + } + } + + /* + * Fall through and try reset anyway even though + * Table 2-9 in the EHCI spec says this will result + * in undefined behavior. + */ + + EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET); + for (n = 0; n != 100; n++) { + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + hcr = EOREAD4(sc, EHCI_USBCMD); + if (!(hcr & EHCI_CMD_HCRESET)) { + hcr = 0; + break; + } + } + + if (hcr) { + return (USB_ERR_IOERROR); + } + return (0); +} + +usb2_error_t +ehci_init(ehci_softc_t *sc) +{ + struct usb2_page_search buf_res; + uint32_t version; + uint32_t sparams; + uint32_t cparams; + uint32_t hcr; + uint16_t i; + uint16_t x; + uint16_t y; + uint16_t bit; + usb2_error_t err = 0; + + mtx_lock(&sc->sc_bus.mtx); + + DPRINTF("start\n"); + + usb2_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.mtx, + CALLOUT_RETURNUNLOCKED); + +#if USB_DEBUG + if (ehcidebug > 2) { + ehci_dump_regs(sc); + } +#endif + + sc->sc_offs = EREAD1(sc, EHCI_CAPLENGTH); + + version = EREAD2(sc, EHCI_HCIVERSION); + device_printf(sc->sc_bus.bdev, "EHCI version %x.%x\n", + version >> 8, version & 0xff); + + sparams = EREAD4(sc, EHCI_HCSPARAMS); + DPRINTF("sparams=0x%x\n", sparams); + + sc->sc_noport = EHCI_HCS_N_PORTS(sparams); + cparams = EREAD4(sc, EHCI_HCCPARAMS); + DPRINTF("cparams=0x%x\n", cparams); + + if (EHCI_HCC_64BIT(cparams)) { + DPRINTF("HCC uses 64-bit structures\n"); + + /* MUST clear segment register if 64 bit capable */ + EWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); + } + sc->sc_bus.usbrev = USB_REV_2_0; + + /* Reset the controller */ + DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev)); + + err = ehci_hc_reset(sc); + if (err) { + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + goto done; + } + /* + * use current frame-list-size selection 0: 1024*4 bytes 1: 512*4 + * bytes 2: 256*4 bytes 3: unknown + */ + if (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD)) == 3) { + device_printf(sc->sc_bus.bdev, "invalid frame-list-size\n"); + err = USB_ERR_IOERROR; + goto done; + } + /* set up the bus struct */ + sc->sc_bus.methods = &ehci_bus_methods; + + sc->sc_eintrs = EHCI_NORMAL_INTRS; + + for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + ehci_qh_t *qh; + + usb2_get_page(sc->sc_hw.intr_start_pc + i, 0, &buf_res); + + qh = buf_res.buffer; + + /* initialize page cache pointer */ + + qh->page_cache = sc->sc_hw.intr_start_pc + i; + + /* store a pointer to queue head */ + + sc->sc_intr_p_last[i] = qh; + + qh->qh_self = + htole32(buf_res.physaddr) | + htole32(EHCI_LINK_QH); + + qh->qh_endp = + htole32(EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH)); + qh->qh_endphub = + htole32(EHCI_QH_SET_MULT(1)); + qh->qh_curqtd = 0; + + qh->qh_qtd.qtd_next = + htole32(EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_altnext = + htole32(EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_status = + htole32(EHCI_QTD_HALTED); + } + + /* + * the QHs are arranged to give poll intervals that are + * powers of 2 times 1ms + */ + bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2; + while (bit) { + x = bit; + while (x & bit) { + ehci_qh_t *qh_x; + ehci_qh_t *qh_y; + + y = (x ^ bit) | (bit / 2); + + qh_x = sc->sc_intr_p_last[x]; + qh_y = sc->sc_intr_p_last[y]; + + /* + * the next QH has half the poll interval + */ + qh_x->qh_link = qh_y->qh_self; + + x++; + } + bit >>= 1; + } + + if (1) { + ehci_qh_t *qh; + + qh = sc->sc_intr_p_last[0]; + + /* the last (1ms) QH terminates */ + qh->qh_link = htole32(EHCI_LINK_TERMINATE); + } + for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + ehci_sitd_t *sitd; + ehci_itd_t *itd; + + usb2_get_page(sc->sc_hw.isoc_fs_start_pc + i, 0, &buf_res); + + sitd = buf_res.buffer; + + /* initialize page cache pointer */ + + sitd->page_cache = sc->sc_hw.isoc_fs_start_pc + i; + + /* store a pointer to the transfer descriptor */ + + sc->sc_isoc_fs_p_last[i] = sitd; + + /* initialize full speed isochronous */ + + sitd->sitd_self = + htole32(buf_res.physaddr) | + htole32(EHCI_LINK_SITD); + + sitd->sitd_back = + htole32(EHCI_LINK_TERMINATE); + + sitd->sitd_next = + sc->sc_intr_p_last[i | (EHCI_VIRTUAL_FRAMELIST_COUNT / 2)]->qh_self; + + + usb2_get_page(sc->sc_hw.isoc_hs_start_pc + i, 0, &buf_res); + + itd = buf_res.buffer; + + /* initialize page cache pointer */ + + itd->page_cache = sc->sc_hw.isoc_hs_start_pc + i; + + /* store a pointer to the transfer descriptor */ + + sc->sc_isoc_hs_p_last[i] = itd; + + /* initialize high speed isochronous */ + + itd->itd_self = + htole32(buf_res.physaddr) | + htole32(EHCI_LINK_ITD); + + itd->itd_next = + sitd->sitd_self; + } + + usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + + if (1) { + uint32_t *pframes; + + pframes = buf_res.buffer; + + /* + * execution order: + * pframes -> high speed isochronous -> + * full speed isochronous -> interrupt QH's + */ + for (i = 0; i < EHCI_FRAMELIST_COUNT; i++) { + pframes[i] = sc->sc_isoc_hs_p_last + [i & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1)]->itd_self; + } + } + /* setup sync list pointer */ + EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr); + + usb2_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res); + + if (1) { + + ehci_qh_t *qh; + + qh = buf_res.buffer; + + /* initialize page cache pointer */ + + qh->page_cache = &sc->sc_hw.async_start_pc; + + /* store a pointer to the queue head */ + + sc->sc_async_p_last = qh; + + /* init dummy QH that starts the async list */ + + qh->qh_self = + htole32(buf_res.physaddr) | + htole32(EHCI_LINK_QH); + + /* fill the QH */ + qh->qh_endp = + htole32(EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL); + qh->qh_endphub = htole32(EHCI_QH_SET_MULT(1)); + qh->qh_link = qh->qh_self; + qh->qh_curqtd = 0; + + /* fill the overlay qTD */ + qh->qh_qtd.qtd_next = htole32(EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_altnext = htole32(EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_status = htole32(EHCI_QTD_HALTED); + } + /* flush all cache into memory */ + + usb2_bus_mem_flush_all(&sc->sc_bus, &ehci_iterate_hw_softc); + +#if USB_DEBUG + if (ehcidebug) { + ehci_dump_sqh(sc->sc_async_p_last); + } +#endif + + /* setup async list pointer */ + EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH); + + + /* enable interrupts */ + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + /* turn on controller */ + EOWRITE4(sc, EHCI_USBCMD, + EHCI_CMD_ITC_1 | /* 1 microframes interrupt delay */ + (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) | + EHCI_CMD_ASE | + EHCI_CMD_PSE | + EHCI_CMD_RS); + + /* Take over port ownership */ + EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF); + + for (i = 0; i < 100; i++) { + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (!hcr) { + break; + } + } + if (hcr) { + device_printf(sc->sc_bus.bdev, "run timeout\n"); + err = USB_ERR_IOERROR; + goto done; + } +done: + mtx_unlock(&sc->sc_bus.mtx); + + if (!err) { + /* catch any lost interrupts */ + ehci_do_poll(&sc->sc_bus); + } + return (err); +} + +/* + * shut down the controller when the system is going down + */ +void +ehci_detach(struct ehci_softc *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + usb2_callout_stop(&sc->sc_tmo_pcd); + + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + if (ehci_hc_reset(sc)) { + DPRINTF("reset failed!\n"); + } + /* XXX let stray task complete */ + usb2_pause_mtx(&sc->sc_bus.mtx, 50); + + mtx_unlock(&sc->sc_bus.mtx); + + usb2_callout_drain(&sc->sc_tmo_pcd); + + return; +} + +void +ehci_suspend(struct ehci_softc *sc) +{ + uint32_t cmd; + uint32_t hcr; + uint8_t i; + + mtx_lock(&sc->sc_bus.mtx); + + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if (((cmd & EHCI_PS_PO) == 0) && + ((cmd & EHCI_PS_PE) == EHCI_PS_PE)) { + EOWRITE4(sc, EHCI_PORTSC(i), + cmd | EHCI_PS_SUSP); + } + } + + sc->sc_cmd = EOREAD4(sc, EHCI_USBCMD); + + cmd = sc->sc_cmd & ~(EHCI_CMD_ASE | EHCI_CMD_PSE); + EOWRITE4(sc, EHCI_USBCMD, cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & + (EHCI_STS_ASS | EHCI_STS_PSS); + + if (hcr == 0) { + break; + } + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + } + + if (hcr != 0) { + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + } + cmd &= ~EHCI_CMD_RS; + EOWRITE4(sc, EHCI_USBCMD, cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr == EHCI_STS_HCH) { + break; + } + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + } + + if (hcr != EHCI_STS_HCH) { + device_printf(sc->sc_bus.bdev, + "config timeout\n"); + } + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +void +ehci_resume(struct ehci_softc *sc) +{ + struct usb2_page_search buf_res; + uint32_t cmd; + uint32_t hcr; + uint8_t i; + + mtx_lock(&sc->sc_bus.mtx); + + /* restore things in case the bios doesn't */ + EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); + + usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr); + + usb2_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res); + EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH); + + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + hcr = 0; + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if (((cmd & EHCI_PS_PO) == 0) && + ((cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP)) { + EOWRITE4(sc, EHCI_PORTSC(i), + cmd | EHCI_PS_FPR); + hcr = 1; + } + } + + if (hcr) { + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_RESUME_WAIT); + + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if (((cmd & EHCI_PS_PO) == 0) && + ((cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP)) { + EOWRITE4(sc, EHCI_PORTSC(i), + cmd & ~EHCI_PS_FPR); + } + } + } + EOWRITE4(sc, EHCI_USBCMD, sc->sc_cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr != EHCI_STS_HCH) { + break; + } + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + } + if (hcr == EHCI_STS_HCH) { + device_printf(sc->sc_bus.bdev, "config timeout\n"); + } + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_RESUME_WAIT); + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + ehci_do_poll(&sc->sc_bus); + + return; +} + +void +ehci_shutdown(ehci_softc_t *sc) +{ + DPRINTF("stopping the HC\n"); + + mtx_lock(&sc->sc_bus.mtx); + + if (ehci_hc_reset(sc)) { + DPRINTF("reset failed!\n"); + } + mtx_unlock(&sc->sc_bus.mtx); +} + +#if USB_DEBUG +static void +ehci_dump_regs(ehci_softc_t *sc) +{ + uint32_t i; + + i = EOREAD4(sc, EHCI_USBCMD); + printf("cmd=0x%08x\n", i); + + if (i & EHCI_CMD_ITC_1) + printf(" EHCI_CMD_ITC_1\n"); + if (i & EHCI_CMD_ITC_2) + printf(" EHCI_CMD_ITC_2\n"); + if (i & EHCI_CMD_ITC_4) + printf(" EHCI_CMD_ITC_4\n"); + if (i & EHCI_CMD_ITC_8) + printf(" EHCI_CMD_ITC_8\n"); + if (i & EHCI_CMD_ITC_16) + printf(" EHCI_CMD_ITC_16\n"); + if (i & EHCI_CMD_ITC_32) + printf(" EHCI_CMD_ITC_32\n"); + if (i & EHCI_CMD_ITC_64) + printf(" EHCI_CMD_ITC_64\n"); + if (i & EHCI_CMD_ASPME) + printf(" EHCI_CMD_ASPME\n"); + if (i & EHCI_CMD_ASPMC) + printf(" EHCI_CMD_ASPMC\n"); + if (i & EHCI_CMD_LHCR) + printf(" EHCI_CMD_LHCR\n"); + if (i & EHCI_CMD_IAAD) + printf(" EHCI_CMD_IAAD\n"); + if (i & EHCI_CMD_ASE) + printf(" EHCI_CMD_ASE\n"); + if (i & EHCI_CMD_PSE) + printf(" EHCI_CMD_PSE\n"); + if (i & EHCI_CMD_FLS_M) + printf(" EHCI_CMD_FLS_M\n"); + if (i & EHCI_CMD_HCRESET) + printf(" EHCI_CMD_HCRESET\n"); + if (i & EHCI_CMD_RS) + printf(" EHCI_CMD_RS\n"); + + i = EOREAD4(sc, EHCI_USBSTS); + + printf("sts=0x%08x\n", i); + + if (i & EHCI_STS_ASS) + printf(" EHCI_STS_ASS\n"); + if (i & EHCI_STS_PSS) + printf(" EHCI_STS_PSS\n"); + if (i & EHCI_STS_REC) + printf(" EHCI_STS_REC\n"); + if (i & EHCI_STS_HCH) + printf(" EHCI_STS_HCH\n"); + if (i & EHCI_STS_IAA) + printf(" EHCI_STS_IAA\n"); + if (i & EHCI_STS_HSE) + printf(" EHCI_STS_HSE\n"); + if (i & EHCI_STS_FLR) + printf(" EHCI_STS_FLR\n"); + if (i & EHCI_STS_PCD) + printf(" EHCI_STS_PCD\n"); + if (i & EHCI_STS_ERRINT) + printf(" EHCI_STS_ERRINT\n"); + if (i & EHCI_STS_INT) + printf(" EHCI_STS_INT\n"); + + printf("ien=0x%08x\n", + EOREAD4(sc, EHCI_USBINTR)); + printf("frindex=0x%08x ctrdsegm=0x%08x periodic=0x%08x async=0x%08x\n", + EOREAD4(sc, EHCI_FRINDEX), + EOREAD4(sc, EHCI_CTRLDSSEGMENT), + EOREAD4(sc, EHCI_PERIODICLISTBASE), + EOREAD4(sc, EHCI_ASYNCLISTADDR)); + for (i = 1; i <= sc->sc_noport; i++) { + printf("port %d status=0x%08x\n", i, + EOREAD4(sc, EHCI_PORTSC(i))); + } + return; +} + +static void +ehci_dump_link(uint32_t link, int type) +{ + link = le32toh(link); + printf("0x%08x", link); + if (link & EHCI_LINK_TERMINATE) + printf(""); + else { + printf("<"); + if (type) { + switch (EHCI_LINK_TYPE(link)) { + case EHCI_LINK_ITD: + printf("ITD"); + break; + case EHCI_LINK_QH: + printf("QH"); + break; + case EHCI_LINK_SITD: + printf("SITD"); + break; + case EHCI_LINK_FSTN: + printf("FSTN"); + break; + } + } + printf(">"); + } + return; +} + +static void +ehci_dump_qtd(ehci_qtd_t *qtd) +{ + uint32_t s; + + printf(" next="); + ehci_dump_link(qtd->qtd_next, 0); + printf(" altnext="); + ehci_dump_link(qtd->qtd_altnext, 0); + printf("\n"); + s = le32toh(qtd->qtd_status); + printf(" status=0x%08x: toggle=%d bytes=0x%x ioc=%d c_page=0x%x\n", + s, EHCI_QTD_GET_TOGGLE(s), EHCI_QTD_GET_BYTES(s), + EHCI_QTD_GET_IOC(s), EHCI_QTD_GET_C_PAGE(s)); + printf(" cerr=%d pid=%d stat=%s%s%s%s%s%s%s%s\n", + EHCI_QTD_GET_CERR(s), EHCI_QTD_GET_PID(s), + (s & EHCI_QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE", + (s & EHCI_QTD_HALTED) ? "-HALTED" : "", + (s & EHCI_QTD_BUFERR) ? "-BUFERR" : "", + (s & EHCI_QTD_BABBLE) ? "-BABBLE" : "", + (s & EHCI_QTD_XACTERR) ? "-XACTERR" : "", + (s & EHCI_QTD_MISSEDMICRO) ? "-MISSED" : "", + (s & EHCI_QTD_SPLITXSTATE) ? "-SPLIT" : "", + (s & EHCI_QTD_PINGSTATE) ? "-PING" : ""); + + for (s = 0; s < 5; s++) { + printf(" buffer[%d]=0x%08x\n", s, + le32toh(qtd->qtd_buffer[s])); + } + for (s = 0; s < 5; s++) { + printf(" buffer_hi[%d]=0x%08x\n", s, + le32toh(qtd->qtd_buffer_hi[s])); + } + return; +} + +static uint8_t +ehci_dump_sqtd(ehci_qtd_t *sqtd) +{ + uint8_t temp; + + usb2_pc_cpu_invalidate(sqtd->page_cache); + printf("QTD(%p) at 0x%08x:\n", sqtd, le32toh(sqtd->qtd_self)); + ehci_dump_qtd(sqtd); + temp = (sqtd->qtd_next & htole32(EHCI_LINK_TERMINATE)) ? 1 : 0; + return (temp); +} + +static void +ehci_dump_sqtds(ehci_qtd_t *sqtd) +{ + uint16_t i; + uint8_t stop; + + stop = 0; + for (i = 0; sqtd && (i < 20) && !stop; sqtd = sqtd->obj_next, i++) { + stop = ehci_dump_sqtd(sqtd); + } + if (sqtd) { + printf("dump aborted, too many TDs\n"); + } + return; +} + +static void +ehci_dump_sqh(ehci_qh_t *qh) +{ + uint32_t endp, endphub; + + usb2_pc_cpu_invalidate(qh->page_cache); + printf("QH(%p) at 0x%08x:\n", qh, le32toh(qh->qh_self) & ~0x1F); + printf(" link="); + ehci_dump_link(qh->qh_link, 1); + printf("\n"); + endp = le32toh(qh->qh_endp); + printf(" endp=0x%08x\n", endp); + printf(" addr=0x%02x inact=%d endpt=%d eps=%d dtc=%d hrecl=%d\n", + EHCI_QH_GET_ADDR(endp), EHCI_QH_GET_INACT(endp), + EHCI_QH_GET_ENDPT(endp), EHCI_QH_GET_EPS(endp), + EHCI_QH_GET_DTC(endp), EHCI_QH_GET_HRECL(endp)); + printf(" mpl=0x%x ctl=%d nrl=%d\n", + EHCI_QH_GET_MPL(endp), EHCI_QH_GET_CTL(endp), + EHCI_QH_GET_NRL(endp)); + endphub = le32toh(qh->qh_endphub); + printf(" endphub=0x%08x\n", endphub); + printf(" smask=0x%02x cmask=0x%02x huba=0x%02x port=%d mult=%d\n", + EHCI_QH_GET_SMASK(endphub), EHCI_QH_GET_CMASK(endphub), + EHCI_QH_GET_HUBA(endphub), EHCI_QH_GET_PORT(endphub), + EHCI_QH_GET_MULT(endphub)); + printf(" curqtd="); + ehci_dump_link(qh->qh_curqtd, 0); + printf("\n"); + printf("Overlay qTD:\n"); + ehci_dump_qtd((void *)&qh->qh_qtd); + return; +} + +static void +ehci_dump_sitd(ehci_sitd_t *sitd) +{ + usb2_pc_cpu_invalidate(sitd->page_cache); + printf("SITD(%p) at 0x%08x\n", sitd, le32toh(sitd->sitd_self) & ~0x1F); + printf(" next=0x%08x\n", le32toh(sitd->sitd_next)); + printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n", + le32toh(sitd->sitd_portaddr), + (sitd->sitd_portaddr & htole32(EHCI_SITD_SET_DIR_IN)) + ? "in" : "out", + EHCI_SITD_GET_ADDR(le32toh(sitd->sitd_portaddr)), + EHCI_SITD_GET_ENDPT(le32toh(sitd->sitd_portaddr)), + EHCI_SITD_GET_PORT(le32toh(sitd->sitd_portaddr)), + EHCI_SITD_GET_HUBA(le32toh(sitd->sitd_portaddr))); + printf(" mask=0x%08x\n", le32toh(sitd->sitd_mask)); + printf(" status=0x%08x <%s> len=0x%x\n", le32toh(sitd->sitd_status), + (sitd->sitd_status & htole32(EHCI_SITD_ACTIVE)) ? "ACTIVE" : "", + EHCI_SITD_GET_LEN(le32toh(sitd->sitd_status))); + printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n", + le32toh(sitd->sitd_back), + le32toh(sitd->sitd_bp[0]), + le32toh(sitd->sitd_bp[1]), + le32toh(sitd->sitd_bp_hi[0]), + le32toh(sitd->sitd_bp_hi[1])); + return; +} + +static void +ehci_dump_itd(ehci_itd_t *itd) +{ + usb2_pc_cpu_invalidate(itd->page_cache); + printf("ITD(%p) at 0x%08x\n", itd, le32toh(itd->itd_self) & ~0x1F); + printf(" next=0x%08x\n", le32toh(itd->itd_next)); + printf(" status[0]=0x%08x; <%s>\n", le32toh(itd->itd_status[0]), + (itd->itd_status[0] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[1]=0x%08x; <%s>\n", le32toh(itd->itd_status[1]), + (itd->itd_status[1] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[2]=0x%08x; <%s>\n", le32toh(itd->itd_status[2]), + (itd->itd_status[2] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[3]=0x%08x; <%s>\n", le32toh(itd->itd_status[3]), + (itd->itd_status[3] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[4]=0x%08x; <%s>\n", le32toh(itd->itd_status[4]), + (itd->itd_status[4] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[5]=0x%08x; <%s>\n", le32toh(itd->itd_status[5]), + (itd->itd_status[5] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[6]=0x%08x; <%s>\n", le32toh(itd->itd_status[6]), + (itd->itd_status[6] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[7]=0x%08x; <%s>\n", le32toh(itd->itd_status[7]), + (itd->itd_status[7] & htole32(EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" bp[0]=0x%08x\n", le32toh(itd->itd_bp[0])); + printf(" addr=0x%02x; endpt=0x%01x\n", + EHCI_ITD_GET_ADDR(le32toh(itd->itd_bp[0])), + EHCI_ITD_GET_ENDPT(le32toh(itd->itd_bp[0]))); + printf(" bp[1]=0x%08x\n", le32toh(itd->itd_bp[1])); + printf(" dir=%s; mpl=0x%02x\n", + (le32toh(itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out", + EHCI_ITD_GET_MPL(le32toh(itd->itd_bp[1]))); + printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n", + le32toh(itd->itd_bp[2]), + le32toh(itd->itd_bp[3]), + le32toh(itd->itd_bp[4]), + le32toh(itd->itd_bp[5]), + le32toh(itd->itd_bp[6])); + printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n" + " 0x%08x,0x%08x,0x%08x\n", + le32toh(itd->itd_bp_hi[0]), + le32toh(itd->itd_bp_hi[1]), + le32toh(itd->itd_bp_hi[2]), + le32toh(itd->itd_bp_hi[3]), + le32toh(itd->itd_bp_hi[4]), + le32toh(itd->itd_bp_hi[5]), + le32toh(itd->itd_bp_hi[6])); + return; +} + +static void +ehci_dump_isoc(ehci_softc_t *sc) +{ + ehci_itd_t *itd; + ehci_sitd_t *sitd; + uint16_t max = 1000; + uint16_t pos; + + pos = (EOREAD4(sc, EHCI_FRINDEX) / 8) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + printf("%s: isochronous dump from frame 0x%03x:\n", + __FUNCTION__, pos); + + itd = sc->sc_isoc_hs_p_last[pos]; + sitd = sc->sc_isoc_fs_p_last[pos]; + + while (itd && max && max--) { + ehci_dump_itd(itd); + itd = itd->prev; + } + + while (sitd && max && max--) { + ehci_dump_sitd(sitd); + sitd = sitd->prev; + } + return; +} + +#endif + +static void +ehci_transfer_intr_enqueue(struct usb2_xfer *xfer) +{ + /* check for early completion */ + if (ehci_check_transfer(xfer)) { + return; + } + /* put transfer on interrupt queue */ + usb2_transfer_enqueue(&xfer->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, &ehci_timeout, xfer->timeout); + } + return; +} + +#define EHCI_APPEND_FS_TD(std,last) (last) = _ehci_append_fs_td(std,last) +static ehci_sitd_t * +_ehci_append_fs_td(ehci_sitd_t *std, ehci_sitd_t *last) +{ + DPRINTFN(11, "%p to %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->next = last->next; + std->sitd_next = last->sitd_next; + + std->prev = last; + + usb2_pc_cpu_flush(std->page_cache); + + /* + * the last->next->prev is never followed: std->next->prev = std; + */ + last->next = std; + last->sitd_next = std->sitd_self; + + usb2_pc_cpu_flush(last->page_cache); + + return (std); +} + +#define EHCI_APPEND_HS_TD(std,last) (last) = _ehci_append_hs_td(std,last) +static ehci_itd_t * +_ehci_append_hs_td(ehci_itd_t *std, ehci_itd_t *last) +{ + DPRINTFN(11, "%p to %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->next = last->next; + std->itd_next = last->itd_next; + + std->prev = last; + + usb2_pc_cpu_flush(std->page_cache); + + /* + * the last->next->prev is never followed: std->next->prev = std; + */ + last->next = std; + last->itd_next = std->itd_self; + + usb2_pc_cpu_flush(last->page_cache); + + return (std); +} + +#define EHCI_APPEND_QH(sqh,last) (last) = _ehci_append_qh(sqh,last) +static ehci_qh_t * +_ehci_append_qh(ehci_qh_t *sqh, ehci_qh_t *last) +{ + DPRINTFN(11, "%p to %p\n", sqh, last); + + /* (sc->sc_bus.mtx) must be locked */ + + sqh->next = last->next; + sqh->qh_link = last->qh_link; + + sqh->prev = last; + + usb2_pc_cpu_flush(sqh->page_cache); + + /* + * the last->next->prev is never followed: sqh->next->prev = sqh; + */ + + last->next = sqh; + last->qh_link = sqh->qh_self; + + usb2_pc_cpu_flush(last->page_cache); + +#if USB_DEBUG + if (ehcidebug > 5) { + printf("%s:\n", __FUNCTION__); + ehci_dump_sqh(sqh); + } +#endif + return (sqh); +} + +#define EHCI_REMOVE_FS_TD(std,last) (last) = _ehci_remove_fs_td(std,last) +static ehci_sitd_t * +_ehci_remove_fs_td(ehci_sitd_t *std, ehci_sitd_t *last) +{ + DPRINTFN(11, "%p from %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->prev->next = std->next; + std->prev->sitd_next = std->sitd_next; + + usb2_pc_cpu_flush(std->prev->page_cache); + + if (std->next) { + std->next->prev = std->prev; + usb2_pc_cpu_flush(std->next->page_cache); + } + return ((last == std) ? std->prev : last); +} + +#define EHCI_REMOVE_HS_TD(std,last) (last) = _ehci_remove_hs_td(std,last) +static ehci_itd_t * +_ehci_remove_hs_td(ehci_itd_t *std, ehci_itd_t *last) +{ + DPRINTFN(11, "%p from %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->prev->next = std->next; + std->prev->itd_next = std->itd_next; + + usb2_pc_cpu_flush(std->prev->page_cache); + + if (std->next) { + std->next->prev = std->prev; + usb2_pc_cpu_flush(std->next->page_cache); + } + return ((last == std) ? std->prev : last); +} + +#define EHCI_REMOVE_QH(sqh,last) (last) = _ehci_remove_qh(sqh,last) +static ehci_qh_t * +_ehci_remove_qh(ehci_qh_t *sqh, ehci_qh_t *last) +{ + DPRINTFN(11, "%p from %p\n", sqh, last); + + /* (sc->sc_bus.mtx) must be locked */ + + /* only remove if not removed from a queue */ + if (sqh->prev) { + + sqh->prev->next = sqh->next; + sqh->prev->qh_link = sqh->qh_link; + + usb2_pc_cpu_flush(sqh->prev->page_cache); + + if (sqh->next) { + sqh->next->prev = sqh->prev; + usb2_pc_cpu_flush(sqh->next->page_cache); + } + /* + * set the Terminate-bit in the e_next of the QH, in case + * the transferred packet was short so that the QH still + * points at the last used TD + */ + + sqh->qh_qtd.qtd_next = htole32(EHCI_LINK_TERMINATE); + + last = ((last == sqh) ? sqh->prev : last); + + sqh->prev = 0; + + usb2_pc_cpu_flush(sqh->page_cache); + } + return (last); +} + +static usb2_error_t +ehci_non_isoc_done_sub(struct usb2_xfer *xfer) +{ + ehci_qtd_t *td; + ehci_qtd_t *td_alt_next; + uint32_t status; + uint16_t len; + + td = xfer->td_transfer_cache; + td_alt_next = td->alt_next; + + while (1) { + + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->qtd_status); + + len = EHCI_QTD_GET_BYTES(status); + + /* + * Verify the status length and subtract + * the remainder from "frlengths[]": + */ + if (len > td->len) { + /* should not happen */ + DPRINTF("Invalid status length, " + "0x%04x/0x%04x bytes\n", len, td->len); + status |= EHCI_QTD_HALTED; + } else if (xfer->aframes != xfer->nframes) { + xfer->frlengths[xfer->aframes] -= len; + } + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + if (len == 0) { + /* + * Halt is ok if descriptor is last, + * and complete: + */ + status &= ~EHCI_QTD_HALTED; + } + td = NULL; + break; + } + /* Check for transfer error */ + if (status & EHCI_QTD_HALTED) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + td = td->alt_next; + } else { + /* the transfer is finished */ + td = NULL; + } + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* this USB frame is complete */ + break; + } + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + /* update data toggle */ + + xfer->pipe->toggle_next = + (status & EHCI_QTD_TOGGLE_MASK) ? 1 : 0; + +#if USB_DEBUG + if (status & EHCI_QTD_STATERRS) { + DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x" + "status=%s%s%s%s%s%s%s%s\n", + xfer->address, xfer->endpoint, xfer->aframes, + (status & EHCI_QTD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]", + (status & EHCI_QTD_HALTED) ? "[HALTED]" : "", + (status & EHCI_QTD_BUFERR) ? "[BUFERR]" : "", + (status & EHCI_QTD_BABBLE) ? "[BABBLE]" : "", + (status & EHCI_QTD_XACTERR) ? "[XACTERR]" : "", + (status & EHCI_QTD_MISSEDMICRO) ? "[MISSED]" : "", + (status & EHCI_QTD_SPLITXSTATE) ? "[SPLIT]" : "", + (status & EHCI_QTD_PINGSTATE) ? "[PING]" : ""); + } +#endif + + return ((status & EHCI_QTD_HALTED) ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +ehci_non_isoc_done(struct usb2_xfer *xfer) +{ + usb2_error_t err = 0; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + +#if USB_DEBUG + if (ehcidebug > 10) { + ehci_dump_sqtds(xfer->td_transfer_first); + } +#endif + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = ehci_non_isoc_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = ehci_non_isoc_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = ehci_non_isoc_done_sub(xfer); + } +done: + ehci_device_done(xfer, err); + return; +} + +/*------------------------------------------------------------------------* + * ehci_check_transfer + * + * Return values: + * 0: USB transfer is not finished + * Else: USB transfer is finished + *------------------------------------------------------------------------*/ +static uint8_t +ehci_check_transfer(struct usb2_xfer *xfer) +{ + struct usb2_pipe_methods *methods = xfer->pipe->methods; + + uint32_t status; + + DPRINTFN(13, "xfer=%p checking transfer\n", xfer); + + if (methods == &ehci_device_isoc_fs_methods) { + ehci_sitd_t *td; + + /* isochronous full speed transfer */ + + td = xfer->td_transfer_last; + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->sitd_status); + + /* also check if first is complete */ + + td = xfer->td_transfer_first; + usb2_pc_cpu_invalidate(td->page_cache); + status |= le32toh(td->sitd_status); + + if (!(status & EHCI_SITD_ACTIVE)) { + ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + goto transferred; + } + } else if (methods == &ehci_device_isoc_hs_methods) { + ehci_itd_t *td; + + /* isochronous high speed transfer */ + + td = xfer->td_transfer_last; + usb2_pc_cpu_invalidate(td->page_cache); + status = + td->itd_status[0] | td->itd_status[1] | + td->itd_status[2] | td->itd_status[3] | + td->itd_status[4] | td->itd_status[5] | + td->itd_status[6] | td->itd_status[7]; + + /* also check first transfer */ + td = xfer->td_transfer_first; + usb2_pc_cpu_invalidate(td->page_cache); + status |= + td->itd_status[0] | td->itd_status[1] | + td->itd_status[2] | td->itd_status[3] | + td->itd_status[4] | td->itd_status[5] | + td->itd_status[6] | td->itd_status[7]; + + /* if no transactions are active we continue */ + if (!(status & htole32(EHCI_ITD_ACTIVE))) { + ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + goto transferred; + } + } else { + ehci_qtd_t *td; + + /* non-isochronous transfer */ + + /* + * check whether there is an error somewhere in the middle, + * or whether there was a short packet (SPD and not ACTIVE) + */ + td = xfer->td_transfer_cache; + + while (1) { + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->qtd_status); + + /* + * if there is an active TD the transfer isn't done + */ + if (status & EHCI_QTD_ACTIVE) { + /* update cache */ + xfer->td_transfer_cache = td; + goto done; + } + /* + * last transfer descriptor makes the transfer done + */ + if (((void *)td) == xfer->td_transfer_last) { + break; + } + /* + * any kind of error makes the transfer done + */ + if (status & EHCI_QTD_HALTED) { + break; + } + /* + * if there is no alternate next transfer, a short + * packet also makes the transfer done + */ + if (EHCI_QTD_GET_BYTES(status)) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->alt_next; + continue; + } + } + /* transfer is done */ + break; + } + td = td->obj_next; + } + ehci_non_isoc_done(xfer); + goto transferred; + } + +done: + DPRINTFN(13, "xfer=%p is still active\n", xfer); + return (0); + +transferred: + return (1); +} + +static void +ehci_pcd_enable(ehci_softc_t *sc) +{ + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + sc->sc_eintrs |= EHCI_STS_PCD; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + /* acknowledge any PCD interrupt */ + EOWRITE4(sc, EHCI_USBSTS, EHCI_STS_PCD); + + usb2_sw_transfer(&sc->sc_root_intr, + &ehci_root_intr_done); + + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +static void +ehci_interrupt_poll(ehci_softc_t *sc) +{ + struct usb2_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + /* + * check if transfer is transferred + */ + if (ehci_check_transfer(xfer)) { + /* queue has been modified */ + goto repeat; + } + } + return; +} + +/*------------------------------------------------------------------------* + * ehci_interrupt - EHCI interrupt handler + * + * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, + * hence the interrupt handler will be setup before "sc->sc_bus.bdev" + * is present ! + *------------------------------------------------------------------------*/ +void +ehci_interrupt(ehci_softc_t *sc) +{ + uint32_t status; + + mtx_lock(&sc->sc_bus.mtx); + + DPRINTFN(16, "real interrupt\n"); + +#if USB_DEBUG + if (ehcidebug > 15) { + ehci_dump_regs(sc); + } +#endif + + status = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); + if (status == 0) { + /* the interrupt was not for us */ + goto done; + } + if (!(status & sc->sc_eintrs)) { + goto done; + } + EOWRITE4(sc, EHCI_USBSTS, status); /* acknowledge */ + + status &= sc->sc_eintrs; + + if (status & EHCI_STS_HSE) { + printf("%s: unrecoverable error, " + "controller halted\n", __FUNCTION__); +#if USB_DEBUG + ehci_dump_regs(sc); + ehci_dump_isoc(sc); +#endif + } + if (status & EHCI_STS_PCD) { + /* + * Disable PCD interrupt for now, because it will be + * on until the port has been reset. + */ + sc->sc_eintrs &= ~EHCI_STS_PCD; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + usb2_sw_transfer(&sc->sc_root_intr, + &ehci_root_intr_done); + + /* do not allow RHSC interrupts > 1 per second */ + usb2_callout_reset(&sc->sc_tmo_pcd, hz, + (void *)&ehci_pcd_enable, sc); + } + status &= ~(EHCI_STS_INT | EHCI_STS_ERRINT | EHCI_STS_PCD | EHCI_STS_IAA); + + if (status != 0) { + /* block unprocessed interrupts */ + sc->sc_eintrs &= ~status; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + printf("%s: blocking interrupts 0x%x\n", __FUNCTION__, status); + } + /* poll all the USB transfers */ + ehci_interrupt_poll(sc); + +done: + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/* + * called when a request does not complete + */ +static void +ehci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + ehci_softc_t *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + ehci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +ehci_do_poll(struct usb2_bus *bus) +{ + struct ehci_softc *sc = EHCI_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + ehci_interrupt_poll(sc); + ehci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +static void +ehci_setup_standard_chain_sub(struct ehci_std_temp *temp) +{ + struct usb2_page_search buf_res; + ehci_qtd_t *td; + ehci_qtd_t *td_next; + ehci_qtd_t *td_alt_next; + uint32_t qtd_altnext; + uint32_t buf_offset; + uint32_t average; + uint32_t len_old; + uint8_t shortpkt_old; + uint8_t precompute; + + qtd_altnext = htole32(EHCI_LINK_TERMINATE); + td_alt_next = NULL; + buf_offset = 0; + shortpkt_old = temp->shortpkt; + len_old = temp->len; + precompute = 1; + +restart: + + td = temp->td; + td_next = temp->td_next; + + while (1) { + + if (temp->len == 0) { + + if (temp->shortpkt) { + break; + } + /* send a Zero Length Packet, ZLP, last */ + + temp->shortpkt = 1; + average = 0; + + } else { + + average = temp->average; + + if (temp->len < average) { + if (temp->len % temp->max_frame_size) { + temp->shortpkt = 1; + } + average = temp->len; + } + } + + if (td_next == NULL) { + panic("%s: out of EHCI transfer descriptors!", __FUNCTION__); + } + /* get next TD */ + + td = td_next; + td_next = td->obj_next; + + /* check if we are pre-computing */ + + if (precompute) { + + /* update remaining length */ + + temp->len -= average; + + continue; + } + /* fill out current TD */ + + td->qtd_status = + temp->qtd_status | htole32(EHCI_QTD_SET_BYTES(average)); + + if (average == 0) { + + if (temp->auto_data_toggle == 0) { + + /* update data toggle, ZLP case */ + + temp->qtd_status ^= htole32(EHCI_QTD_TOGGLE_MASK); + } + td->len = 0; + + td->qtd_buffer[0] = 0; + td->qtd_buffer_hi[0] = 0; + + td->qtd_buffer[1] = 0; + td->qtd_buffer_hi[1] = 0; + + } else { + + uint8_t x; + + if (temp->auto_data_toggle == 0) { + + /* update data toggle */ + + if (((average + temp->max_frame_size - 1) / + temp->max_frame_size) & 1) { + temp->qtd_status ^= htole32(EHCI_QTD_TOGGLE_MASK); + } + } + td->len = average; + + /* update remaining length */ + + temp->len -= average; + + /* fill out buffer pointers */ + + usb2_get_page(temp->pc, buf_offset, &buf_res); + td->qtd_buffer[0] = htole32(buf_res.physaddr); + td->qtd_buffer_hi[0] = 0; + + x = 1; + + while (average > EHCI_PAGE_SIZE) { + average -= EHCI_PAGE_SIZE; + buf_offset += EHCI_PAGE_SIZE; + usb2_get_page(temp->pc, buf_offset, &buf_res); + td->qtd_buffer[x] = htole32(buf_res.physaddr & (~0xFFF)); + td->qtd_buffer_hi[x] = 0; + x++; + } + + /* + * NOTE: The "average" variable is never zero after + * exiting the loop above ! + * + * NOTE: We have to subtract one from the offset to + * ensure that we are computing the physical address + * of a valid page ! + */ + buf_offset += average; + usb2_get_page(temp->pc, buf_offset - 1, &buf_res); + td->qtd_buffer[x] = htole32(buf_res.physaddr & (~0xFFF)); + td->qtd_buffer_hi[x] = 0; + } + + if (td_next) { + /* link the current TD with the next one */ + td->qtd_next = td_next->qtd_self; + } + td->qtd_altnext = qtd_altnext; + td->alt_next = td_alt_next; + + usb2_pc_cpu_flush(td->page_cache); + } + + if (precompute) { + precompute = 0; + + /* setup alt next pointer, if any */ + if (temp->short_frames_ok) { + if (temp->setup_alt_next) { + td_alt_next = td_next; + qtd_altnext = td_next->qtd_self; + } + } else { + /* we use this field internally */ + td_alt_next = td_next; + } + + /* restore */ + temp->shortpkt = shortpkt_old; + temp->len = len_old; + goto restart; + } + temp->td = td; + temp->td_next = td_next; + + return; +} + +static void +ehci_setup_standard_chain(struct usb2_xfer *xfer, ehci_qh_t **qh_last) +{ + struct ehci_std_temp temp; + struct usb2_pipe_methods *methods; + ehci_qh_t *qh; + ehci_qtd_t *td; + uint32_t qh_endp; + uint32_t qh_endphub; + uint32_t x; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpoint), + xfer->sumlen, usb2_get_speed(xfer->udev)); + + temp.average = xfer->max_usb2_frame_size; + temp.max_frame_size = xfer->max_frame_size; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + temp.td = NULL; + temp.td_next = td; + temp.qtd_status = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.short_frames_ok = xfer->flags_int.short_frames_ok; + + if (xfer->flags_int.control_xfr) { + if (xfer->pipe->toggle_next) { + /* DATA1 is next */ + temp.qtd_status |= htole32(EHCI_QTD_SET_TOGGLE(1)); + } + temp.auto_data_toggle = 0; + } else { + temp.auto_data_toggle = 1; + } + + if (usb2_get_speed(xfer->udev) != USB_SPEED_HIGH) { + /* max 3 retries */ + temp.qtd_status |= htole32(EHCI_QTD_SET_CERR(3)); + } + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.qtd_status &= htole32(EHCI_QTD_SET_CERR(3)); + temp.qtd_status |= htole32 + (EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) | + EHCI_QTD_SET_TOGGLE(0)); + + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.shortpkt = temp.len ? 1 : 0; + + ehci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + temp.pc = xfer->frbuffers + x; + + x++; + + if (x == xfer->nframes) { + temp.setup_alt_next = 0; + } + /* keep previous data toggle and error count */ + + temp.qtd_status &= htole32(EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(1)); + + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.shortpkt = 0; + + } else { + + /* regular data transfer */ + + temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + /* set endpoint direction */ + + temp.qtd_status |= + (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) ? + htole32(EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) : + htole32(EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT)); + + ehci_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current endpoint + * direction. + */ + + temp.qtd_status &= htole32(EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(1)); + temp.qtd_status |= + (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) ? + htole32(EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) | + EHCI_QTD_SET_TOGGLE(1)) : + htole32(EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT) | + EHCI_QTD_SET_TOGGLE(1)); + + temp.len = 0; + temp.pc = NULL; + temp.shortpkt = 0; + + ehci_setup_standard_chain_sub(&temp); + } + td = temp.td; + + /* the last TD terminates the transfer: */ + td->qtd_next = htole32(EHCI_LINK_TERMINATE); + td->qtd_altnext = htole32(EHCI_LINK_TERMINATE); + td->qtd_status |= htole32(EHCI_QTD_IOC); + + usb2_pc_cpu_flush(td->page_cache); + + /* must have at least one frame! */ + + xfer->td_transfer_last = td; + +#if USB_DEBUG + if (ehcidebug > 8) { + DPRINTF("nexttog=%d; data before transfer:\n", + xfer->pipe->toggle_next); + ehci_dump_sqtds(xfer->td_transfer_first); + } +#endif + + methods = xfer->pipe->methods; + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + /* the "qh_link" field is filled when the QH is added */ + + qh_endp = + (EHCI_QH_SET_ADDR(xfer->address) | + EHCI_QH_SET_ENDPT(UE_GET_ADDR(xfer->endpoint)) | + EHCI_QH_SET_MPL(xfer->max_packet_size)); + + if (usb2_get_speed(xfer->udev) == USB_SPEED_HIGH) { + qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | + EHCI_QH_DTC | EHCI_QH_SET_NRL(8)); + } else { + + if (usb2_get_speed(xfer->udev) == USB_SPEED_FULL) { + qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_FULL) | + EHCI_QH_DTC); + } else { + qh_endp |= (EHCI_QH_SET_EPS(EHCI_QH_SPEED_LOW) | + EHCI_QH_DTC); + } + + if (methods == &ehci_device_ctrl_methods) { + qh_endp |= EHCI_QH_CTL; + } + if (methods != &ehci_device_intr_methods) { + /* Only try one time per microframe! */ + qh_endp |= EHCI_QH_SET_NRL(1); + } + } + + qh->qh_endp = htole32(qh_endp); + + qh_endphub = + (EHCI_QH_SET_MULT(xfer->max_packet_count & 3) | + EHCI_QH_SET_CMASK(xfer->usb2_cmask) | + EHCI_QH_SET_SMASK(xfer->usb2_smask) | + EHCI_QH_SET_HUBA(xfer->udev->hs_hub_addr) | + EHCI_QH_SET_PORT(xfer->udev->hs_port_no)); + + qh->qh_endphub = htole32(qh_endphub); + qh->qh_curqtd = htole32(0); + + /* fill the overlay qTD */ + qh->qh_qtd.qtd_status = htole32(0); + + if (temp.auto_data_toggle) { + + /* let the hardware compute the data toggle */ + + qh->qh_endp &= ~htole32(EHCI_QH_DTC); + + if (xfer->pipe->toggle_next) { + /* DATA1 is next */ + qh->qh_qtd.qtd_status |= htole32(EHCI_QTD_SET_TOGGLE(1)); + } + } + td = xfer->td_transfer_first; + + qh->qh_qtd.qtd_next = td->qtd_self; + qh->qh_qtd.qtd_altnext = htole32(EHCI_LINK_TERMINATE); + + usb2_pc_cpu_flush(qh->page_cache); + + EHCI_APPEND_QH(qh, *qh_last); + return; +} + +static void +ehci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct ehci_softc *sc = xfer->usb2_sc; + uint16_t i; + uint16_t m; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_PRE_DATA) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + ehci_device_done(xfer, std->err); + } + goto done; + } + /* setup buffer */ + std->ptr = sc->sc_hub_idata; + std->len = sizeof(sc->sc_hub_idata); + + /* clear any old interrupt data */ + bzero(sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); + + /* set bits */ + m = (sc->sc_noport + 1); + if (m > (8 * sizeof(sc->sc_hub_idata))) { + m = (8 * sizeof(sc->sc_hub_idata)); + } + for (i = 1; i < m; i++) { + /* pick out CHANGE bits from the status register */ + if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) { + sc->sc_hub_idata[i / 8] |= 1 << (i % 8); + DPRINTF("port %d changed\n", i); + } + } +done: + return; +} + +static void +ehci_isoc_fs_done(ehci_softc_t *sc, struct usb2_xfer *xfer) +{ + uint32_t nframes = xfer->nframes; + uint32_t status; + uint32_t *plen = xfer->frlengths; + uint16_t len = 0; + ehci_sitd_t *td = xfer->td_transfer_first; + ehci_sitd_t **pp_last = &sc->sc_isoc_fs_p_last[xfer->qh_pos]; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_fs_p_last[0]; + } +#if USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("isoc FS-TD\n"); + ehci_dump_sitd(td); + } +#endif + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->sitd_status); + + len = EHCI_SITD_GET_LEN(status); + + if (*plen >= len) { + len = *plen - len; + } else { + len = 0; + } + + *plen = len; + + /* remove FS-TD from schedule */ + EHCI_REMOVE_FS_TD(td, *pp_last); + + pp_last++; + plen++; + td = td->obj_next; + } + + xfer->aframes = xfer->nframes; + + return; +} + +static void +ehci_isoc_hs_done(ehci_softc_t *sc, struct usb2_xfer *xfer) +{ + uint32_t nframes = xfer->nframes; + uint32_t status; + uint32_t *plen = xfer->frlengths; + uint16_t len = 0; + uint8_t td_no = 0; + ehci_itd_t *td = xfer->td_transfer_first; + ehci_itd_t **pp_last = &sc->sc_isoc_hs_p_last[xfer->qh_pos]; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_hs_p_last[0]; + } +#if USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("isoc HS-TD\n"); + ehci_dump_itd(td); + } +#endif + + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->itd_status[td_no]); + + len = EHCI_ITD_GET_LEN(status); + + if (*plen >= len) { + /* + * The length is valid. NOTE: The complete + * length is written back into the status + * field, and not the remainder like with + * other transfer descriptor types. + */ + } else { + /* Invalid length - truncate */ + len = 0; + } + + *plen = len; + + plen++; + td_no++; + + if ((td_no == 8) || (nframes == 0)) { + /* remove HS-TD from schedule */ + EHCI_REMOVE_HS_TD(td, *pp_last); + pp_last++; + + td_no = 0; + td = td->obj_next; + } + } + xfer->aframes = xfer->nframes; + + return; +} + +/* NOTE: "done" can be run two times in a row, + * from close and from interrupt + */ +static void +ehci_device_done(struct usb2_xfer *xfer, usb2_error_t error) +{ + struct usb2_pipe_methods *methods = xfer->pipe->methods; + ehci_softc_t *sc = xfer->usb2_sc; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n", + xfer, xfer->pipe, error); + + if ((methods == &ehci_device_bulk_methods) || + (methods == &ehci_device_ctrl_methods)) { +#if USB_DEBUG + if (ehcidebug > 8) { + DPRINTF("nexttog=%d; data after transfer:\n", + xfer->pipe->toggle_next); + ehci_dump_sqtds(xfer->td_transfer_first); + } +#endif + + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_async_p_last); + } + if (methods == &ehci_device_intr_methods) { + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_intr_p_last[xfer->qh_pos]); + } + /* + * Only finish isochronous transfers once which will update + * "xfer->frlengths". + */ + if (xfer->td_transfer_first && + xfer->td_transfer_last) { + if (methods == &ehci_device_isoc_fs_methods) { + ehci_isoc_fs_done(sc, xfer); + } + if (methods == &ehci_device_isoc_hs_methods) { + ehci_isoc_hs_done(sc, xfer); + } + xfer->td_transfer_first = NULL; + xfer->td_transfer_last = NULL; + } + /* dequeue transfer and start next transfer */ + usb2_transfer_done(xfer, error); + return; +} + +/*------------------------------------------------------------------------* + * ehci bulk support + *------------------------------------------------------------------------*/ +static void +ehci_device_bulk_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_device_bulk_close(struct usb2_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ehci_device_bulk_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_device_bulk_start(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_async_p_last); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ehci_device_bulk_methods = +{ + .open = ehci_device_bulk_open, + .close = ehci_device_bulk_close, + .enter = ehci_device_bulk_enter, + .start = ehci_device_bulk_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ehci control support + *------------------------------------------------------------------------*/ +static void +ehci_device_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_device_ctrl_close(struct usb2_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ehci_device_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_device_ctrl_start(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_async_p_last); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ehci_device_ctrl_methods = +{ + .open = ehci_device_ctrl_open, + .close = ehci_device_ctrl_close, + .enter = ehci_device_ctrl_enter, + .start = ehci_device_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ehci interrupt support + *------------------------------------------------------------------------*/ +static void +ehci_device_intr_open(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + uint16_t best; + uint16_t bit; + uint16_t x; + uint8_t slot; + + /* Allocate a microframe slot first: */ + + slot = usb2_intr_schedule_adjust + (xfer->udev, xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX); + + if (usb2_get_speed(xfer->udev) == USB_SPEED_HIGH) { + xfer->usb2_uframe = slot; + xfer->usb2_smask = (1 << slot) & 0xFF; + xfer->usb2_cmask = 0; + } else { + xfer->usb2_uframe = slot; + xfer->usb2_smask = (1 << slot) & 0x3F; + xfer->usb2_cmask = (-(4 << slot)) & 0xFE; + } + + /* + * Find the best QH position corresponding to the given interval: + */ + + best = 0; + bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2; + while (bit) { + if (xfer->interval >= bit) { + x = bit; + best = bit; + while (x & bit) { + if (sc->sc_intr_stat[x] < + sc->sc_intr_stat[best]) { + best = x; + } + x++; + } + break; + } + bit >>= 1; + } + + sc->sc_intr_stat[best]++; + xfer->qh_pos = best; + + DPRINTFN(3, "best=%d interval=%d\n", + best, xfer->interval); + return; +} + +static void +ehci_device_intr_close(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + uint8_t slot; + + slot = usb2_intr_schedule_adjust + (xfer->udev, -(xfer->max_frame_size), xfer->usb2_uframe); + + sc->sc_intr_stat[xfer->qh_pos]--; + + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ehci_device_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_device_intr_start(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ehci_device_intr_methods = +{ + .open = ehci_device_intr_open, + .close = ehci_device_intr_close, + .enter = ehci_device_intr_enter, + .start = ehci_device_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ehci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +ehci_device_isoc_fs_open(struct usb2_xfer *xfer) +{ + ehci_sitd_t *td; + uint32_t sitd_portaddr; + uint8_t ds; + + sitd_portaddr = + EHCI_SITD_SET_ADDR(xfer->address) | + EHCI_SITD_SET_ENDPT(UE_GET_ADDR(xfer->endpoint)) | + EHCI_SITD_SET_HUBA(xfer->udev->hs_hub_addr) | + EHCI_SITD_SET_PORT(xfer->udev->hs_port_no); + + if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) { + sitd_portaddr |= EHCI_SITD_SET_DIR_IN; + } + sitd_portaddr = htole32(sitd_portaddr); + + /* initialize all TD's */ + + for (ds = 0; ds != 2; ds++) { + + for (td = xfer->td_start[ds]; td; td = td->obj_next) { + + td->sitd_portaddr = sitd_portaddr; + + /* + * TODO: make some kind of automatic + * SMASK/CMASK selection based on micro-frame + * usage + * + * micro-frame usage (8 microframes per 1ms) + */ + td->sitd_back = htole32(EHCI_LINK_TERMINATE); + + usb2_pc_cpu_flush(td->page_cache); + } + } + return; +} + +static void +ehci_device_isoc_fs_close(struct usb2_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ehci_device_isoc_fs_enter(struct usb2_xfer *xfer) +{ + struct usb2_page_search buf_res; + ehci_softc_t *sc = xfer->usb2_sc; + struct usb2_fs_isoc_schedule *fss_start; + struct usb2_fs_isoc_schedule *fss_end; + struct usb2_fs_isoc_schedule *fss; + ehci_sitd_t *td; + ehci_sitd_t *td_last = NULL; + ehci_sitd_t **pp_last; + uint32_t *plen; + uint32_t buf_offset; + uint32_t nframes; + uint32_t temp; + uint32_t sitd_mask; + uint16_t tlen; + uint8_t sa; + uint8_t sb; + uint8_t error; + +#if USB_DEBUG + uint8_t once = 1; + +#endif + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->pipe->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = EOREAD4(sc, EHCI_FRINDEX) / 8; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + buf_offset = (nframes - xfer->pipe->isoc_next) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + if ((xfer->pipe->is_synced == 0) || + (buf_offset < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->pipe->isoc_next = (nframes + 3) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + xfer->pipe->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = (xfer->pipe->isoc_next - nframes) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb2_fs_isoc_schedule_isoc_time_expand + (xfer->udev, &fss_start, &fss_end, nframes) + buf_offset + + xfer->nframes; + + /* get the real number of frames */ + + nframes = xfer->nframes; + + buf_offset = 0; + + plen = xfer->frlengths; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + xfer->td_transfer_first = td; + + pp_last = &sc->sc_isoc_fs_p_last[xfer->pipe->isoc_next]; + + /* store starting position */ + + xfer->qh_pos = xfer->pipe->isoc_next; + + fss = fss_start + (xfer->qh_pos % USB_ISOC_TIME_MAX); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_fs_p_last[0]; + } + if (fss >= fss_end) { + fss = fss_start; + } + /* reuse sitd_portaddr and sitd_back from last transfer */ + + if (*plen > xfer->max_frame_size) { +#if USB_DEBUG + if (once) { + once = 0; + printf("%s: frame length(%d) exceeds %d " + "bytes (frame truncated)\n", + __FUNCTION__, *plen, + xfer->max_frame_size); + } +#endif + *plen = xfer->max_frame_size; + } + /* + * We currently don't care if the ISOCHRONOUS schedule is + * full! + */ + error = usb2_fs_isoc_schedule_alloc(fss, &sa, *plen); + if (error) { + /* + * The FULL speed schedule is FULL! Set length + * to zero. + */ + *plen = 0; + } + if (*plen) { + /* + * only call "usb2_get_page()" when we have a + * non-zero length + */ + usb2_get_page(xfer->frbuffers, buf_offset, &buf_res); + td->sitd_bp[0] = htole32(buf_res.physaddr); + buf_offset += *plen; + /* + * NOTE: We need to subtract one from the offset so + * that we are on a valid page! + */ + usb2_get_page(xfer->frbuffers, buf_offset - 1, + &buf_res); + temp = buf_res.physaddr & ~0xFFF; + } else { + td->sitd_bp[0] = 0; + temp = 0; + } + + if (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) { + tlen = *plen; + if (tlen <= 188) { + temp |= 1; /* T-count = 1, TP = ALL */ + tlen = 1; + } else { + tlen += 187; + tlen /= 188; + temp |= tlen; /* T-count = [1..6] */ + temp |= 8; /* TP = Begin */ + } + + tlen += sa; + + if (tlen >= 8) { + sb = 0; + } else { + sb = (1 << tlen); + } + + sa = (1 << sa); + sa = (sb - sa) & 0x3F; + sb = 0; + } else { + sb = (-(4 << sa)) & 0xFE; + sa = (1 << sa) & 0x3F; + } + + sitd_mask = (EHCI_SITD_SET_SMASK(sa) | + EHCI_SITD_SET_CMASK(sb)); + + td->sitd_bp[1] = htole32(temp); + + td->sitd_mask = htole32(sitd_mask); + + if (nframes == 0) { + td->sitd_status = htole32 + (EHCI_SITD_IOC | + EHCI_SITD_ACTIVE | + EHCI_SITD_SET_LEN(*plen)); + } else { + td->sitd_status = htole32 + (EHCI_SITD_ACTIVE | + EHCI_SITD_SET_LEN(*plen)); + } + usb2_pc_cpu_flush(td->page_cache); + +#if USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("FS-TD %d\n", nframes); + ehci_dump_sitd(td); + } +#endif + /* insert TD into schedule */ + EHCI_APPEND_FS_TD(td, *pp_last); + pp_last++; + + plen++; + fss++; + td_last = td; + td = td->obj_next; + } + + xfer->td_transfer_last = td_last; + + /* update isoc_next */ + xfer->pipe->isoc_next = (pp_last - &sc->sc_isoc_fs_p_last[0]) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + return; +} + +static void +ehci_device_isoc_fs_start(struct usb2_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ehci_device_isoc_fs_methods = +{ + .open = ehci_device_isoc_fs_open, + .close = ehci_device_isoc_fs_close, + .enter = ehci_device_isoc_fs_enter, + .start = ehci_device_isoc_fs_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ehci high speed isochronous support + *------------------------------------------------------------------------*/ +static void +ehci_device_isoc_hs_open(struct usb2_xfer *xfer) +{ + ehci_itd_t *td; + uint32_t temp; + uint8_t ds; + + /* initialize all TD's */ + + for (ds = 0; ds != 2; ds++) { + + for (td = xfer->td_start[ds]; td; td = td->obj_next) { + + /* set TD inactive */ + td->itd_status[0] = 0; + td->itd_status[1] = 0; + td->itd_status[2] = 0; + td->itd_status[3] = 0; + td->itd_status[4] = 0; + td->itd_status[5] = 0; + td->itd_status[6] = 0; + td->itd_status[7] = 0; + + /* set endpoint and address */ + td->itd_bp[0] = htole32 + (EHCI_ITD_SET_ADDR(xfer->address) | + EHCI_ITD_SET_ENDPT(UE_GET_ADDR(xfer->endpoint))); + + temp = + EHCI_ITD_SET_MPL(xfer->max_packet_size & 0x7FF); + + /* set direction */ + if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) { + temp |= EHCI_ITD_SET_DIR_IN; + } + /* set maximum packet size */ + td->itd_bp[1] = htole32(temp); + + /* set transfer multiplier */ + td->itd_bp[2] = htole32(xfer->max_packet_count & 3); + + usb2_pc_cpu_flush(td->page_cache); + } + } + return; +} + +static void +ehci_device_isoc_hs_close(struct usb2_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ehci_device_isoc_hs_enter(struct usb2_xfer *xfer) +{ + struct usb2_page_search buf_res; + ehci_softc_t *sc = xfer->usb2_sc; + ehci_itd_t *td; + ehci_itd_t *td_last = NULL; + ehci_itd_t **pp_last; + bus_size_t page_addr; + uint32_t *plen; + uint32_t status; + uint32_t buf_offset; + uint32_t nframes; + uint32_t itd_offset[8 + 1]; + uint8_t x; + uint8_t td_no; + uint8_t page_no; + +#if USB_DEBUG + uint8_t once = 1; + +#endif + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->pipe->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = EOREAD4(sc, EHCI_FRINDEX) / 8; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + buf_offset = (nframes - xfer->pipe->isoc_next) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + if ((xfer->pipe->is_synced == 0) || + (buf_offset < ((xfer->nframes + 7) / 8))) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->pipe->isoc_next = (nframes + 3) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + xfer->pipe->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = (xfer->pipe->isoc_next - nframes) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb2_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + + ((xfer->nframes + 7) / 8); + + /* get the real number of frames */ + + nframes = xfer->nframes; + + buf_offset = 0; + td_no = 0; + + plen = xfer->frlengths; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + xfer->td_transfer_first = td; + + pp_last = &sc->sc_isoc_hs_p_last[xfer->pipe->isoc_next]; + + /* store starting position */ + + xfer->qh_pos = xfer->pipe->isoc_next; + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_hs_p_last[0]; + } + /* range check */ + if (*plen > xfer->max_frame_size) { +#if USB_DEBUG + if (once) { + once = 0; + printf("%s: frame length(%d) exceeds %d bytes " + "(frame truncated)\n", + __FUNCTION__, *plen, xfer->max_frame_size); + } +#endif + *plen = xfer->max_frame_size; + } + status = (EHCI_ITD_SET_LEN(*plen) | + EHCI_ITD_ACTIVE | + EHCI_ITD_SET_PG(0)); + td->itd_status[td_no] = htole32(status); + itd_offset[td_no] = buf_offset; + buf_offset += *plen; + plen++; + td_no++; + + if ((td_no == 8) || (nframes == 0)) { + + /* the rest of the transfers are not active, if any */ + for (x = td_no; x != 8; x++) { + td->itd_status[x] = 0; /* not active */ + } + + /* check if there is any data to be transferred */ + if (itd_offset[0] != buf_offset) { + page_no = 0; + itd_offset[td_no] = buf_offset; + + /* get first page offset */ + usb2_get_page(xfer->frbuffers, itd_offset[0], &buf_res); + /* get page address */ + page_addr = buf_res.physaddr & ~0xFFF; + /* update page address */ + td->itd_bp[0] &= htole32(0xFFF); + td->itd_bp[0] |= htole32(page_addr); + + for (x = 0; x != td_no; x++) { + /* set page number and page offset */ + status = (EHCI_ITD_SET_PG(page_no) | + (buf_res.physaddr & 0xFFF)); + td->itd_status[x] |= htole32(status); + + /* get next page offset */ + if (itd_offset[x + 1] == buf_offset) { + /* + * We subtract one so that + * we don't go off the last + * page! + */ + usb2_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); + } else { + usb2_get_page(xfer->frbuffers, itd_offset[x + 1], &buf_res); + } + + /* check if we need a new page */ + if ((buf_res.physaddr ^ page_addr) & ~0xFFF) { + /* new page needed */ + page_addr = buf_res.physaddr & ~0xFFF; + if (page_no == 6) { + panic("%s: too many pages\n", __FUNCTION__); + } + page_no++; + /* update page address */ + td->itd_bp[page_no] &= htole32(0xFFF); + td->itd_bp[page_no] |= htole32(page_addr); + } + } + } + /* set IOC bit if we are complete */ + if (nframes == 0) { + td->itd_status[7] |= htole32(EHCI_ITD_IOC); + } + usb2_pc_cpu_flush(td->page_cache); +#if USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("HS-TD %d\n", nframes); + ehci_dump_itd(td); + } +#endif + /* insert TD into schedule */ + EHCI_APPEND_HS_TD(td, *pp_last); + pp_last++; + + td_no = 0; + td_last = td; + td = td->obj_next; + } + } + + xfer->td_transfer_last = td_last; + + /* update isoc_next */ + xfer->pipe->isoc_next = (pp_last - &sc->sc_isoc_hs_p_last[0]) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + return; +} + +static void +ehci_device_isoc_hs_start(struct usb2_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ehci_device_isoc_hs_methods = +{ + .open = ehci_device_isoc_hs_open, + .close = ehci_device_isoc_hs_close, + .enter = ehci_device_isoc_hs_enter, + .start = ehci_device_isoc_hs_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ehci root control support + *------------------------------------------------------------------------* + * simulate a hardware hub by handling + * all the necessary requests + *------------------------------------------------------------------------*/ + +static void +ehci_root_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_root_ctrl_close(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* data structures and routines + * to emulate the root hub: + */ + +static const +struct usb2_device_descriptor ehci_devd = +{ + sizeof(struct usb2_device_descriptor), + UDESC_DEVICE, /* type */ + {0x00, 0x02}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_HSHUBSTT, /* protocol */ + 64, /* max packet */ + {0}, {0}, {0x00, 0x01}, /* device id */ + 1, 2, 0, /* string indicies */ + 1 /* # of configurations */ +}; + +static const +struct usb2_device_qualifier ehci_odevd = +{ + sizeof(struct usb2_device_qualifier), + UDESC_DEVICE_QUALIFIER, /* type */ + {0x00, 0x02}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 0, /* max packet */ + 0, /* # of configurations */ + 0 +}; + +static const struct ehci_config_desc ehci_confd = { + .confd = { + .bLength = sizeof(struct usb2_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(ehci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0 /* max power */ + }, + + .ifcd = { + .bLength = sizeof(struct usb2_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = UIPROTO_HSHUBSTT, + 0 + }, + + .endpd = { + .bLength = sizeof(struct usb2_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = UE_DIR_IN | EHCI_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ + .bInterval = 255, + }, +}; + +static const +struct usb2_hub_descriptor ehci_hubd = +{ + 0, /* dynamic length */ + UDESC_HUB, + 0, + {0, 0}, + 0, + 0, + {0}, +}; + +static void +ehci_disown(ehci_softc_t *sc, uint16_t index, uint8_t lowspeed) +{ + uint32_t port; + uint32_t v; + + DPRINTF("index=%d lowspeed=%d\n", index, lowspeed); + + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + EOWRITE4(sc, port, v | EHCI_PS_PO); +} + +static void +ehci_root_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_root_ctrl_start(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + + DPRINTF("\n"); + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &ehci_root_ctrl_task, 0, 0); + + return; +} + +static void +ehci_root_ctrl_task(struct ehci_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + ehci_root_ctrl_poll(sc); + return; +} + +static void +ehci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct ehci_softc *sc = xfer->usb2_sc; + char *ptr; + uint32_t port; + uint32_t v; + uint16_t i; + uint16_t value; + uint16_t index; + uint8_t l; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_SETUP) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + ehci_device_done(xfer, std->err); + } + goto done; + } + /* buffer reset */ + std->ptr = sc->sc_hub_desc.temp; + std->len = 0; + + value = UGETW(std->req.wValue); + index = UGETW(std->req.wIndex); + + use_polling = mtx_owned(xfer->priv_mtx) ? 1 : 0; + + DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " + "wValue=0x%04x wIndex=0x%04x\n", + std->req.bmRequestType, std->req.bRequest, + UGETW(std->req.wLength), value, index); + +#define C(x,y) ((x) | ((y) << 8)) + switch (C(std->req.bRequest, std->req.bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + std->len = 1; + sc->sc_hub_desc.temp[0] = sc->sc_conf; + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + switch (value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = sizeof(ehci_devd); + sc->sc_hub_desc.devd = ehci_devd; + break; + /* + * We can't really operate at another speed, + * but the specification says we need this + * descriptor: + */ + case UDESC_DEVICE_QUALIFIER: + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = sizeof(ehci_odevd); + sc->sc_hub_desc.odevd = ehci_odevd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = sizeof(ehci_confd); + std->ptr = USB_ADD_BYTES(&ehci_confd, 0); + break; + + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + ptr = "\001"; + break; + + case 1: /* Vendor */ + ptr = sc->sc_vendor; + break; + + case 2: /* Product */ + ptr = "EHCI root HUB"; + break; + + default: + ptr = ""; + break; + } + + std->len = usb2_make_str_desc + (sc->sc_hub_desc.temp, + sizeof(sc->sc_hub_desc.temp), + ptr); + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + std->len = 1; + sc->sc_hub_desc.temp[0] = 0; + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + std->len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + std->len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, 0); + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= USB_MAX_DEVICES) { + std->err = USB_ERR_IOERROR; + goto done; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if ((value != 0) && (value != 1)) { + std->err = USB_ERR_IOERROR; + goto done; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + std->err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n"); + + if ((index < 1) || + (index > sc->sc_noport)) { + std->err = USB_ERR_IOERROR; + goto done; + } + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + switch (value) { + case UHF_PORT_ENABLE: + EOWRITE4(sc, port, v & ~EHCI_PS_PE); + break; + case UHF_PORT_SUSPEND: + EOWRITE4(sc, port, v & ~EHCI_PS_SUSP); + break; + case UHF_PORT_POWER: + EOWRITE4(sc, port, v & ~EHCI_PS_PP); + break; + case UHF_PORT_TEST: + DPRINTFN(3, "clear port test " + "%d\n", index); + break; + case UHF_PORT_INDICATOR: + DPRINTFN(3, "clear port ind " + "%d\n", index); + EOWRITE4(sc, port, v & ~EHCI_PS_PIC); + break; + case UHF_C_PORT_CONNECTION: + EOWRITE4(sc, port, v | EHCI_PS_CSC); + break; + case UHF_C_PORT_ENABLE: + EOWRITE4(sc, port, v | EHCI_PS_PEC); + break; + case UHF_C_PORT_SUSPEND: + EOWRITE4(sc, port, v | EHCI_PS_SUSP); + break; + case UHF_C_PORT_OVER_CURRENT: + EOWRITE4(sc, port, v | EHCI_PS_OCC); + break; + case UHF_C_PORT_RESET: + sc->sc_isreset = 0; + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + v = EOREAD4(sc, EHCI_HCSPARAMS); + + sc->sc_hub_desc.hubd = ehci_hubd; + sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; + USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, + (EHCI_HCS_PPC(v) ? UHD_PWR_INDIVIDUAL : UHD_PWR_NO_SWITCH) | + (EHCI_HCS_P_INDICATOR(EREAD4(sc, EHCI_HCSPARAMS)) ? + UHD_PORT_IND : 0)); + sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200; /* XXX can't find out? */ + for (l = 0; l < sc->sc_noport; l++) { + /* XXX can't find out? */ + sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] &= ~(1 << (l % 8)); + } + sc->sc_hub_desc.hubd.bDescLength = + 8 + ((sc->sc_noport + 7) / 8); + std->len = sc->sc_hub_desc.hubd.bDescLength; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + std->len = 16; + bzero(sc->sc_hub_desc.temp, 16); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + DPRINTFN(9, "get port status i=%d\n", + index); + if ((index < 1) || + (index > sc->sc_noport)) { + std->err = USB_ERR_IOERROR; + goto done; + } + v = EOREAD4(sc, EHCI_PORTSC(index)); + DPRINTFN(9, "port status=0x%04x\n", v); + i = UPS_HIGH_SPEED; + if (v & EHCI_PS_CS) + i |= UPS_CURRENT_CONNECT_STATUS; + if (v & EHCI_PS_PE) + i |= UPS_PORT_ENABLED; + if (v & EHCI_PS_SUSP) + i |= UPS_SUSPEND; + if (v & EHCI_PS_OCA) + i |= UPS_OVERCURRENT_INDICATOR; + if (v & EHCI_PS_PR) + i |= UPS_RESET; + if (v & EHCI_PS_PP) + i |= UPS_PORT_POWER; + USETW(sc->sc_hub_desc.ps.wPortStatus, i); + i = 0; + if (v & EHCI_PS_CSC) + i |= UPS_C_CONNECT_STATUS; + if (v & EHCI_PS_PEC) + i |= UPS_C_PORT_ENABLED; + if (v & EHCI_PS_OCC) + i |= UPS_C_OVERCURRENT_INDICATOR; + if (sc->sc_isreset) + i |= UPS_C_PORT_RESET; + USETW(sc->sc_hub_desc.ps.wPortChange, i); + std->len = sizeof(sc->sc_hub_desc.ps); + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + std->err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if ((index < 1) || + (index > sc->sc_noport)) { + std->err = USB_ERR_IOERROR; + goto done; + } + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + switch (value) { + case UHF_PORT_ENABLE: + EOWRITE4(sc, port, v | EHCI_PS_PE); + break; + case UHF_PORT_SUSPEND: + EOWRITE4(sc, port, v | EHCI_PS_SUSP); + break; + case UHF_PORT_RESET: + DPRINTFN(6, "reset port %d\n", index); +#if USB_DEBUG + if (ehcinohighspeed) { + /* + * Connect USB device to companion + * controller. + */ + ehci_disown(sc, index, 1); + break; + } +#endif + if (EHCI_PS_IS_LOWSPEED(v)) { + /* Low speed device, give up ownership. */ + ehci_disown(sc, index, 1); + break; + } + /* Start reset sequence. */ + v &= ~(EHCI_PS_PE | EHCI_PS_PR); + EOWRITE4(sc, port, v | EHCI_PS_PR); + + if (use_polling) { + /* polling */ + DELAY(USB_PORT_ROOT_RESET_DELAY * 1000); + } else { + /* Wait for reset to complete. */ + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_PORT_ROOT_RESET_DELAY); + } + + /* Terminate reset sequence. */ + EOWRITE4(sc, port, v); + + if (use_polling) { + /* polling */ + DELAY(EHCI_PORT_RESET_COMPLETE * 1000); + } else { + /* Wait for HC to complete reset. */ + usb2_pause_mtx(&sc->sc_bus.mtx, + EHCI_PORT_RESET_COMPLETE); + } + + v = EOREAD4(sc, port); + DPRINTF("ehci after reset, status=0x%08x\n", v); + if (v & EHCI_PS_PR) { + device_printf(sc->sc_bus.bdev, + "port reset timeout\n"); + std->err = USB_ERR_TIMEOUT; + goto done; + } + if (!(v & EHCI_PS_PE)) { + /* + * Not a high speed device, give up + * ownership. + */ + ehci_disown(sc, index, 0); + break; + } + sc->sc_isreset = 1; + DPRINTF("ehci port %d reset, status = 0x%08x\n", + index, v); + break; + + case UHF_PORT_POWER: + DPRINTFN(3, "set port power %d\n", index); + EOWRITE4(sc, port, v | EHCI_PS_PP); + break; + + case UHF_PORT_TEST: + DPRINTFN(3, "set port test %d\n", index); + break; + + case UHF_PORT_INDICATOR: + DPRINTFN(3, "set port ind %d\n", index); + EOWRITE4(sc, port, v | EHCI_PS_PIC); + break; + + default: + std->err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): + case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): + case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): + case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } +done: + return; +} + +static void +ehci_root_ctrl_poll(struct ehci_softc *sc) +{ + usb2_sw_transfer(&sc->sc_root_ctrl, + &ehci_root_ctrl_done); + return; +} + +struct usb2_pipe_methods ehci_root_ctrl_methods = +{ + .open = ehci_root_ctrl_open, + .close = ehci_root_ctrl_close, + .enter = ehci_root_ctrl_enter, + .start = ehci_root_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 0, +}; + +/*------------------------------------------------------------------------* + * ehci root interrupt support + *------------------------------------------------------------------------*/ +static void +ehci_root_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_root_intr_close(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + ehci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ehci_root_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_root_intr_start(struct usb2_xfer *xfer) +{ + ehci_softc_t *sc = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +struct usb2_pipe_methods ehci_root_intr_methods = +{ + .open = ehci_root_intr_open, + .close = ehci_root_intr_close, + .enter = ehci_root_intr_enter, + .start = ehci_root_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +static void +ehci_xfer_setup(struct usb2_setup_params *parm) +{ + struct usb2_page_search page_info; + struct usb2_page_cache *pc; + ehci_softc_t *sc; + struct usb2_xfer *xfer; + void *last_obj; + uint32_t nqtd; + uint32_t nqh; + uint32_t nsitd; + uint32_t nitd; + uint32_t n; + + sc = EHCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + nqtd = 0; + nqh = 0; + nsitd = 0; + nitd = 0; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + /* + * compute maximum number of some structures + */ + if (parm->methods == &ehci_device_ctrl_methods) { + + /* + * The proof for the "nqtd" formula is illustrated like + * this: + * + * +------------------------------------+ + * | | + * | |remainder -> | + * | +-----+---+ | + * | | xxx | x | frm 0 | + * | +-----+---++ | + * | | xxx | xx | frm 1 | + * | +-----+----+ | + * | ... | + * +------------------------------------+ + * + * "xxx" means a completely full USB transfer descriptor + * + * "x" and "xx" means a short USB packet + * + * For the remainder of an USB transfer modulo + * "max_data_length" we need two USB transfer descriptors. + * One to transfer the remaining data and one to finalise + * with a zero length packet in case the "force_short_xfer" + * flag is set. We only need two USB transfer descriptors in + * the case where the transfer length of the first one is a + * factor of "max_frame_size". The rest of the needed USB + * transfer descriptors is given by the buffer size divided + * by the maximum data payload. + */ + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + 1 /* STATUS */ + + (xfer->max_data_length / xfer->max_usb2_frame_size)); + + } else if (parm->methods == &ehci_device_bulk_methods) { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_usb2_frame_size)); + + } else if (parm->methods == &ehci_device_intr_methods) { + + if (parm->speed == USB_SPEED_HIGH) { + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 3; + } else if (parm->speed == USB_SPEED_FULL) { + parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME; + parm->hc_max_packet_count = 1; + } else { + parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME / 8; + parm->hc_max_packet_count = 1; + } + + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_usb2_frame_size)); + + } else if (parm->methods == &ehci_device_isoc_fs_methods) { + + parm->hc_max_packet_size = 0x3FF; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x3FF; + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nsitd = xfer->nframes; + + } else if (parm->methods == &ehci_device_isoc_hs_methods) { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 3; + parm->hc_max_frame_size = 0xC00; + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nitd = (xfer->nframes + 7) / 8; + + } else { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x400; + + usb2_transfer_setup_sub(parm); + } + +alloc_dma_set: + + if (parm->err) { + return; + } + /* + * Allocate queue heads and transfer descriptors + */ + last_obj = NULL; + + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_itd_t), + EHCI_ITD_ALIGN, nitd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nitd; n++) { + ehci_itd_t *td; + + usb2_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->itd_self = htole32(page_info.physaddr | EHCI_LINK_ITD); + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb2_pc_cpu_flush(pc + n); + } + } + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_sitd_t), + EHCI_SITD_ALIGN, nsitd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nsitd; n++) { + ehci_sitd_t *td; + + usb2_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->sitd_self = htole32(page_info.physaddr | EHCI_LINK_SITD); + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb2_pc_cpu_flush(pc + n); + } + } + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_qtd_t), + EHCI_QTD_ALIGN, nqtd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqtd; n++) { + ehci_qtd_t *qtd; + + usb2_get_page(pc + n, 0, &page_info); + + qtd = page_info.buffer; + + /* init TD */ + qtd->qtd_self = htole32(page_info.physaddr); + qtd->obj_next = last_obj; + qtd->page_cache = pc + n; + + last_obj = qtd; + + usb2_pc_cpu_flush(pc + n); + } + } + xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; + + last_obj = NULL; + + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_qh_t), + EHCI_QH_ALIGN, nqh)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqh; n++) { + ehci_qh_t *qh; + + usb2_get_page(pc + n, 0, &page_info); + + qh = page_info.buffer; + + /* init QH */ + qh->qh_self = htole32(page_info.physaddr | EHCI_LINK_QH); + qh->obj_next = last_obj; + qh->page_cache = pc + n; + + last_obj = qh; + + usb2_pc_cpu_flush(pc + n); + } + } + xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; + + if (!xfer->flags_int.curr_dma_set) { + xfer->flags_int.curr_dma_set = 1; + goto alloc_dma_set; + } + return; +} + +static void +ehci_xfer_unsetup(struct usb2_xfer *xfer) +{ + return; +} + +static void +ehci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc, + struct usb2_pipe *pipe) +{ + ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); + + DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + pipe, udev->address, + edesc->bEndpointAddress, udev->flags.usb2_mode, + sc->sc_addr); + + if (udev->flags.usb2_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->device_index == sc->sc_addr) { + switch (edesc->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &ehci_root_ctrl_methods; + break; + case UE_DIR_IN | EHCI_INTR_ENDPT: + pipe->methods = &ehci_root_intr_methods; + break; + default: + /* do nothing */ + break; + } + } else { + if ((udev->speed != USB_SPEED_HIGH) && + ((udev->hs_hub_addr == 0) || + (udev->hs_port_no == 0) || + (udev->bus->devices[udev->hs_hub_addr] == NULL) || + (udev->bus->devices[udev->hs_hub_addr]->hub == NULL))) { + /* We need a transaction translator */ + goto done; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + pipe->methods = &ehci_device_ctrl_methods; + break; + case UE_INTERRUPT: + pipe->methods = &ehci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + if (udev->speed == USB_SPEED_HIGH) { + pipe->methods = &ehci_device_isoc_hs_methods; + } else if (udev->speed == USB_SPEED_FULL) { + pipe->methods = &ehci_device_isoc_fs_methods; + } + break; + case UE_BULK: + if (udev->speed != USB_SPEED_LOW) { + pipe->methods = &ehci_device_bulk_methods; + } + break; + default: + /* do nothing */ + break; + } + } +done: + return; +} + +static void +ehci_get_dma_delay(struct usb2_bus *bus, uint32_t *pus) +{ + /* + * Wait until the hardware has finished any possible use of + * the transfer descriptor(s) and QH + */ + *pus = (188); /* microseconds */ + return; +} + +struct usb2_bus_methods ehci_bus_methods = +{ + .pipe_init = ehci_pipe_init, + .xfer_setup = ehci_xfer_setup, + .xfer_unsetup = ehci_xfer_unsetup, + .do_poll = ehci_do_poll, + .get_dma_delay = ehci_get_dma_delay, +}; diff --git a/sys/dev/usb2/controller/ehci2.h b/sys/dev/usb2/controller/ehci2.h new file mode 100644 index 000000000000..f002d55119ae --- /dev/null +++ b/sys/dev/usb2/controller/ehci2.h @@ -0,0 +1,515 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#ifndef _EHCI_H_ +#define _EHCI_H_ + +/* PCI config registers */ +#define PCI_CBMEM 0x10 /* configuration base MEM */ +#define PCI_INTERFACE_EHCI 0x20 +#define PCI_USBREV 0x60 /* RO USB protocol revision */ +#define PCI_USB_REV_MASK 0xff +#define PCI_USB_REV_PRE_1_0 0x00 +#define PCI_USB_REV_1_0 0x10 +#define PCI_USB_REV_1_1 0x11 +#define PCI_USB_REV_2_0 0x20 +#define PCI_EHCI_FLADJ 0x61 /* RW Frame len adj, SOF=59488+6*fladj */ +#define PCI_EHCI_PORTWAKECAP 0x62 /* RW Port wake caps (opt) */ + +/* EHCI Extended Capabilities */ +#define EHCI_EC_LEGSUP 0x01 +#define EHCI_EECP_NEXT(x) (((x) >> 8) & 0xff) +#define EHCI_EECP_ID(x) ((x) & 0xff) + +/* Legacy support extended capability */ +#define EHCI_LEGSUP_BIOS_SEM 0x02 +#define EHCI_LEGSUP_OS_SEM 0x03 +#define EHCI_LEGSUP_USBLEGCTLSTS 0x04 + +/* EHCI capability registers */ +#define EHCI_CAPLENGTH 0x00 /* RO Capability register length field */ +/* reserved 0x01 */ +#define EHCI_HCIVERSION 0x02 /* RO Interface version number */ +#define EHCI_HCSPARAMS 0x04 /* RO Structural parameters */ +#define EHCI_HCS_DEBUGPORT(x) (((x) >> 20) & 0xf) +#define EHCI_HCS_P_INDICATOR(x) ((x) & 0x10000) +#define EHCI_HCS_N_CC(x) (((x) >> 12) & 0xf) /* # of companion ctlrs */ +#define EHCI_HCS_N_PCC(x) (((x) >> 8) & 0xf) /* # of ports per comp. */ +#define EHCI_HCS_PPC(x) ((x) & 0x10) /* port power control */ +#define EHCI_HCS_N_PORTS(x) ((x) & 0xf) /* # of ports */ +#define EHCI_HCCPARAMS 0x08 /* RO Capability parameters */ +#define EHCI_HCC_EECP(x) (((x) >> 8) & 0xff) /* extended ports caps */ +#define EHCI_HCC_IST(x) (((x) >> 4) & 0xf) /* isoc sched threshold */ +#define EHCI_HCC_ASPC(x) ((x) & 0x4) /* async sched park cap */ +#define EHCI_HCC_PFLF(x) ((x) & 0x2) /* prog frame list flag */ +#define EHCI_HCC_64BIT(x) ((x) & 0x1) /* 64 bit address cap */ +#define EHCI_HCSP_PORTROUTE 0x0c /* RO Companion port route description */ + +/* EHCI operational registers. Offset given by EHCI_CAPLENGTH register */ +#define EHCI_USBCMD 0x00 /* RO, RW, WO Command register */ +#define EHCI_CMD_ITC_M 0x00ff0000 /* RW interrupt threshold ctrl */ +#define EHCI_CMD_ITC_1 0x00010000 +#define EHCI_CMD_ITC_2 0x00020000 +#define EHCI_CMD_ITC_4 0x00040000 +#define EHCI_CMD_ITC_8 0x00080000 +#define EHCI_CMD_ITC_16 0x00100000 +#define EHCI_CMD_ITC_32 0x00200000 +#define EHCI_CMD_ITC_64 0x00400000 +#define EHCI_CMD_ASPME 0x00000800 /* RW/RO async park enable */ +#define EHCI_CMD_ASPMC 0x00000300 /* RW/RO async park count */ +#define EHCI_CMD_LHCR 0x00000080 /* RW light host ctrl reset */ +#define EHCI_CMD_IAAD 0x00000040 /* RW intr on async adv door + * bell */ +#define EHCI_CMD_ASE 0x00000020 /* RW async sched enable */ +#define EHCI_CMD_PSE 0x00000010 /* RW periodic sched enable */ +#define EHCI_CMD_FLS_M 0x0000000c /* RW/RO frame list size */ +#define EHCI_CMD_FLS(x) (((x) >> 2) & 3) /* RW/RO frame list size */ +#define EHCI_CMD_HCRESET 0x00000002 /* RW reset */ +#define EHCI_CMD_RS 0x00000001 /* RW run/stop */ +#define EHCI_USBSTS 0x04 /* RO, RW, RWC Status register */ +#define EHCI_STS_ASS 0x00008000 /* RO async sched status */ +#define EHCI_STS_PSS 0x00004000 /* RO periodic sched status */ +#define EHCI_STS_REC 0x00002000 /* RO reclamation */ +#define EHCI_STS_HCH 0x00001000 /* RO host controller halted */ +#define EHCI_STS_IAA 0x00000020 /* RWC interrupt on async adv */ +#define EHCI_STS_HSE 0x00000010 /* RWC host system error */ +#define EHCI_STS_FLR 0x00000008 /* RWC frame list rollover */ +#define EHCI_STS_PCD 0x00000004 /* RWC port change detect */ +#define EHCI_STS_ERRINT 0x00000002 /* RWC error interrupt */ +#define EHCI_STS_INT 0x00000001 /* RWC interrupt */ +#define EHCI_STS_INTRS(x) ((x) & 0x3f) + +/* + * NOTE: the doorbell interrupt is enabled, but the doorbell is never + * used! SiS chipsets require this. + */ +#define EHCI_NORMAL_INTRS (EHCI_STS_IAA | EHCI_STS_HSE | \ + EHCI_STS_PCD | EHCI_STS_ERRINT | EHCI_STS_INT) + +#define EHCI_USBINTR 0x08 /* RW Interrupt register */ +#define EHCI_INTR_IAAE 0x00000020 /* interrupt on async advance + * ena */ +#define EHCI_INTR_HSEE 0x00000010 /* host system error ena */ +#define EHCI_INTR_FLRE 0x00000008 /* frame list rollover ena */ +#define EHCI_INTR_PCIE 0x00000004 /* port change ena */ +#define EHCI_INTR_UEIE 0x00000002 /* USB error intr ena */ +#define EHCI_INTR_UIE 0x00000001 /* USB intr ena */ + +#define EHCI_FRINDEX 0x0c /* RW Frame Index register */ + +#define EHCI_CTRLDSSEGMENT 0x10 /* RW Control Data Structure Segment */ + +#define EHCI_PERIODICLISTBASE 0x14 /* RW Periodic List Base */ +#define EHCI_ASYNCLISTADDR 0x18 /* RW Async List Base */ + +#define EHCI_CONFIGFLAG 0x40 /* RW Configure Flag register */ +#define EHCI_CONF_CF 0x00000001 /* RW configure flag */ + +#define EHCI_PORTSC(n) (0x40+(4*(n))) /* RO, RW, RWC Port Status reg */ +#define EHCI_PS_WKOC_E 0x00400000 /* RW wake on over current ena */ +#define EHCI_PS_WKDSCNNT_E 0x00200000 /* RW wake on disconnect ena */ +#define EHCI_PS_WKCNNT_E 0x00100000 /* RW wake on connect ena */ +#define EHCI_PS_PTC 0x000f0000 /* RW port test control */ +#define EHCI_PS_PIC 0x0000c000 /* RW port indicator control */ +#define EHCI_PS_PO 0x00002000 /* RW port owner */ +#define EHCI_PS_PP 0x00001000 /* RW,RO port power */ +#define EHCI_PS_LS 0x00000c00 /* RO line status */ +#define EHCI_PS_IS_LOWSPEED(x) (((x) & EHCI_PS_LS) == 0x00000400) +#define EHCI_PS_PR 0x00000100 /* RW port reset */ +#define EHCI_PS_SUSP 0x00000080 /* RW suspend */ +#define EHCI_PS_FPR 0x00000040 /* RW force port resume */ +#define EHCI_PS_OCC 0x00000020 /* RWC over current change */ +#define EHCI_PS_OCA 0x00000010 /* RO over current active */ +#define EHCI_PS_PEC 0x00000008 /* RWC port enable change */ +#define EHCI_PS_PE 0x00000004 /* RW port enable */ +#define EHCI_PS_CSC 0x00000002 /* RWC connect status change */ +#define EHCI_PS_CS 0x00000001 /* RO connect status */ +#define EHCI_PS_CLEAR (EHCI_PS_OCC | EHCI_PS_PEC | EHCI_PS_CSC) + +#define EHCI_PORT_RESET_COMPLETE 2 /* ms */ + +/* + * Alignment NOTE: structures must be aligned so that the hardware can index + * without performing addition. + */ +#define EHCI_FRAMELIST_ALIGN 0x1000 /* bytes */ +#define EHCI_FRAMELIST_COUNT 1024 /* units */ +#define EHCI_VIRTUAL_FRAMELIST_COUNT 128 /* units */ + +#if ((8*EHCI_VIRTUAL_FRAMELIST_COUNT) < USB_MAX_HS_ISOC_FRAMES_PER_XFER) +#error "maximum number of high-speed isochronous frames is higher than supported!" +#endif + +#if (EHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER) +#error "maximum number of full-speed isochronous frames is higher than supported!" +#endif + +/* Link types */ +#define EHCI_LINK_TERMINATE 0x00000001 +#define EHCI_LINK_TYPE(x) ((x) & 0x00000006) +#define EHCI_LINK_ITD 0x0 +#define EHCI_LINK_QH 0x2 +#define EHCI_LINK_SITD 0x4 +#define EHCI_LINK_FSTN 0x6 +#define EHCI_LINK_ADDR(x) ((x) &~ 0x1f) + +/* Structures alignment (bytes) */ +#define EHCI_ITD_ALIGN 128 +#define EHCI_SITD_ALIGN 64 +#define EHCI_QTD_ALIGN 64 +#define EHCI_QH_ALIGN 128 +#define EHCI_FSTN_ALIGN 32 +/* Data buffers are divided into one or more pages */ +#define EHCI_PAGE_SIZE 0x1000 +#if ((USB_PAGE_SIZE < EHCI_PAGE_SIZE) || (EHCI_PAGE_SIZE == 0) || \ + (USB_PAGE_SIZE < EHCI_ITD_ALIGN) || (EHCI_ITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_SITD_ALIGN) || (EHCI_SITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_QTD_ALIGN) || (EHCI_QTD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_QH_ALIGN) || (EHCI_QH_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_FSTN_ALIGN) || (EHCI_FSTN_ALIGN == 0)) +#error "Invalid USB page size!" +#endif + + +/* + * Isochronous Transfer Descriptor. This descriptor is used for high speed + * transfers only. + */ +struct ehci_itd { + volatile uint32_t itd_next; + volatile uint32_t itd_status[8]; +#define EHCI_ITD_SET_LEN(x) ((x) << 16) +#define EHCI_ITD_GET_LEN(x) (((x) >> 16) & 0xFFF) +#define EHCI_ITD_IOC (1 << 15) +#define EHCI_ITD_SET_PG(x) ((x) << 12) +#define EHCI_ITD_GET_PG(x) (((x) >> 12) & 0x7) +#define EHCI_ITD_SET_OFFS(x) (x) +#define EHCI_ITD_GET_OFFS(x) (((x) >> 0) & 0xFFF) +#define EHCI_ITD_ACTIVE (1 << 31) +#define EHCI_ITD_DATABUFERR (1 << 30) +#define EHCI_ITD_BABBLE (1 << 29) +#define EHCI_ITD_XACTERR (1 << 28) + volatile uint32_t itd_bp[7]; + /* itd_bp[0] */ +#define EHCI_ITD_SET_ADDR(x) (x) +#define EHCI_ITD_GET_ADDR(x) (((x) >> 0) & 0x7F) +#define EHCI_ITD_SET_ENDPT(x) ((x) << 8) +#define EHCI_ITD_GET_ENDPT(x) (((x) >> 8) & 0xF) + /* itd_bp[1] */ +#define EHCI_ITD_SET_DIR_IN (1 << 11) +#define EHCI_ITD_SET_DIR_OUT (0 << 11) +#define EHCI_ITD_SET_MPL(x) (x) +#define EHCI_ITD_GET_MPL(x) (((x) >> 0) & 0x7FF) + volatile uint32_t itd_bp_hi[7]; +/* + * Extra information needed: + */ + uint32_t itd_self; + struct ehci_itd *next; + struct ehci_itd *prev; + struct ehci_itd *obj_next; + struct usb2_page_cache *page_cache; +} __aligned(EHCI_ITD_ALIGN); + +typedef struct ehci_itd ehci_itd_t; + +/* + * Split Transaction Isochronous Transfer Descriptor. This descriptor is used + * for full speed transfers only. + */ +struct ehci_sitd { + volatile uint32_t sitd_next; + volatile uint32_t sitd_portaddr; +#define EHCI_SITD_SET_DIR_OUT (0 << 31) +#define EHCI_SITD_SET_DIR_IN (1 << 31) +#define EHCI_SITD_SET_ADDR(x) (x) +#define EHCI_SITD_GET_ADDR(x) ((x) & 0x7F) +#define EHCI_SITD_SET_ENDPT(x) ((x) << 8) +#define EHCI_SITD_GET_ENDPT(x) (((x) >> 8) & 0xF) +#define EHCI_SITD_GET_DIR(x) ((x) >> 31) +#define EHCI_SITD_SET_PORT(x) ((x) << 24) +#define EHCI_SITD_GET_PORT(x) (((x) >> 24) & 0x7F) +#define EHCI_SITD_SET_HUBA(x) ((x) << 16) +#define EHCI_SITD_GET_HUBA(x) (((x) >> 16) & 0x7F) + volatile uint32_t sitd_mask; +#define EHCI_SITD_SET_SMASK(x) (x) +#define EHCI_SITD_SET_CMASK(x) ((x) << 8) + volatile uint32_t sitd_status; +#define EHCI_SITD_COMPLETE_SPLIT (1<<1) +#define EHCI_SITD_START_SPLIT (0<<1) +#define EHCI_SITD_MISSED_MICRO_FRAME (1<<2) +#define EHCI_SITD_XACTERR (1<<3) +#define EHCI_SITD_BABBLE (1<<4) +#define EHCI_SITD_DATABUFERR (1<<5) +#define EHCI_SITD_ERROR (1<<6) +#define EHCI_SITD_ACTIVE (1<<7) +#define EHCI_SITD_IOC (1<<31) +#define EHCI_SITD_SET_LEN(len) ((len)<<16) +#define EHCI_SITD_GET_LEN(x) (((x)>>16) & 0x3FF) + volatile uint32_t sitd_bp[2]; + volatile uint32_t sitd_back; + volatile uint32_t sitd_bp_hi[2]; +/* + * Extra information needed: + */ + uint32_t sitd_self; + struct ehci_sitd *next; + struct ehci_sitd *prev; + struct ehci_sitd *obj_next; + struct usb2_page_cache *page_cache; +} __aligned(EHCI_SITD_ALIGN); + +typedef struct ehci_sitd ehci_sitd_t; + +/* Queue Element Transfer Descriptor */ +struct ehci_qtd { + volatile uint32_t qtd_next; + volatile uint32_t qtd_altnext; + volatile uint32_t qtd_status; +#define EHCI_QTD_GET_STATUS(x) (((x) >> 0) & 0xff) +#define EHCI_QTD_SET_STATUS(x) ((x) << 0) +#define EHCI_QTD_ACTIVE 0x80 +#define EHCI_QTD_HALTED 0x40 +#define EHCI_QTD_BUFERR 0x20 +#define EHCI_QTD_BABBLE 0x10 +#define EHCI_QTD_XACTERR 0x08 +#define EHCI_QTD_MISSEDMICRO 0x04 +#define EHCI_QTD_SPLITXSTATE 0x02 +#define EHCI_QTD_PINGSTATE 0x01 +#define EHCI_QTD_STATERRS 0x74 +#define EHCI_QTD_GET_PID(x) (((x) >> 8) & 0x3) +#define EHCI_QTD_SET_PID(x) ((x) << 8) +#define EHCI_QTD_PID_OUT 0x0 +#define EHCI_QTD_PID_IN 0x1 +#define EHCI_QTD_PID_SETUP 0x2 +#define EHCI_QTD_GET_CERR(x) (((x) >> 10) & 0x3) +#define EHCI_QTD_SET_CERR(x) ((x) << 10) +#define EHCI_QTD_GET_C_PAGE(x) (((x) >> 12) & 0x7) +#define EHCI_QTD_SET_C_PAGE(x) ((x) << 12) +#define EHCI_QTD_GET_IOC(x) (((x) >> 15) & 0x1) +#define EHCI_QTD_IOC 0x00008000 +#define EHCI_QTD_GET_BYTES(x) (((x) >> 16) & 0x7fff) +#define EHCI_QTD_SET_BYTES(x) ((x) << 16) +#define EHCI_QTD_GET_TOGGLE(x) (((x) >> 31) & 0x1) +#define EHCI_QTD_SET_TOGGLE(x) ((x) << 31) +#define EHCI_QTD_TOGGLE_MASK 0x80000000 +#define EHCI_QTD_NBUFFERS 5 +#define EHCI_QTD_PAYLOAD_MAX ((EHCI_QTD_NBUFFERS-1)*EHCI_PAGE_SIZE) + volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS]; + volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS]; +/* + * Extra information needed: + */ + struct ehci_qtd *alt_next; + struct ehci_qtd *obj_next; + struct usb2_page_cache *page_cache; + uint32_t qtd_self; + uint16_t len; +} __aligned(EHCI_QTD_ALIGN); + +typedef struct ehci_qtd ehci_qtd_t; + +/* Queue Head Sub Structure */ +struct ehci_qh_sub { + volatile uint32_t qtd_next; + volatile uint32_t qtd_altnext; + volatile uint32_t qtd_status; + volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS]; + volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS]; +} __aligned(4); + +/* Queue Head */ +struct ehci_qh { + volatile uint32_t qh_link; + volatile uint32_t qh_endp; +#define EHCI_QH_GET_ADDR(x) (((x) >> 0) & 0x7f) /* endpoint addr */ +#define EHCI_QH_SET_ADDR(x) (x) +#define EHCI_QH_ADDRMASK 0x0000007f +#define EHCI_QH_GET_INACT(x) (((x) >> 7) & 0x01) /* inactivate on next */ +#define EHCI_QH_INACT 0x00000080 +#define EHCI_QH_GET_ENDPT(x) (((x) >> 8) & 0x0f) /* endpoint no */ +#define EHCI_QH_SET_ENDPT(x) ((x) << 8) +#define EHCI_QH_GET_EPS(x) (((x) >> 12) & 0x03) /* endpoint speed */ +#define EHCI_QH_SET_EPS(x) ((x) << 12) +#define EHCI_QH_SPEED_FULL 0x0 +#define EHCI_QH_SPEED_LOW 0x1 +#define EHCI_QH_SPEED_HIGH 0x2 +#define EHCI_QH_GET_DTC(x) (((x) >> 14) & 0x01) /* data toggle control */ +#define EHCI_QH_DTC 0x00004000 +#define EHCI_QH_GET_HRECL(x) (((x) >> 15) & 0x01) /* head of reclamation */ +#define EHCI_QH_HRECL 0x00008000 +#define EHCI_QH_GET_MPL(x) (((x) >> 16) & 0x7ff) /* max packet len */ +#define EHCI_QH_SET_MPL(x) ((x) << 16) +#define EHCI_QH_MPLMASK 0x07ff0000 +#define EHCI_QH_GET_CTL(x) (((x) >> 27) & 0x01) /* control endpoint */ +#define EHCI_QH_CTL 0x08000000 +#define EHCI_QH_GET_NRL(x) (((x) >> 28) & 0x0f) /* NAK reload */ +#define EHCI_QH_SET_NRL(x) ((x) << 28) + volatile uint32_t qh_endphub; +#define EHCI_QH_GET_SMASK(x) (((x) >> 0) & 0xff) /* intr sched mask */ +#define EHCI_QH_SET_SMASK(x) ((x) << 0) +#define EHCI_QH_GET_CMASK(x) (((x) >> 8) & 0xff) /* split completion mask */ +#define EHCI_QH_SET_CMASK(x) ((x) << 8) +#define EHCI_QH_GET_HUBA(x) (((x) >> 16) & 0x7f) /* hub address */ +#define EHCI_QH_SET_HUBA(x) ((x) << 16) +#define EHCI_QH_GET_PORT(x) (((x) >> 23) & 0x7f) /* hub port */ +#define EHCI_QH_SET_PORT(x) ((x) << 23) +#define EHCI_QH_GET_MULT(x) (((x) >> 30) & 0x03) /* pipe multiplier */ +#define EHCI_QH_SET_MULT(x) ((x) << 30) + volatile uint32_t qh_curqtd; + struct ehci_qh_sub qh_qtd; +/* + * Extra information needed: + */ + struct ehci_qh *next; + struct ehci_qh *prev; + struct ehci_qh *obj_next; + struct usb2_page_cache *page_cache; + uint32_t qh_self; +} __aligned(EHCI_QH_ALIGN); + +typedef struct ehci_qh ehci_qh_t; + +/* Periodic Frame Span Traversal Node */ +struct ehci_fstn { + volatile uint32_t fstn_link; + volatile uint32_t fstn_back; +} __aligned(EHCI_FSTN_ALIGN); + +typedef struct ehci_fstn ehci_fstn_t; + +struct ehci_hw_softc { + struct usb2_page_cache pframes_pc; + struct usb2_page_cache async_start_pc; + struct usb2_page_cache intr_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb2_page_cache isoc_hs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb2_page_cache isoc_fs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + + struct usb2_page pframes_pg; + struct usb2_page async_start_pg; + struct usb2_page intr_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb2_page isoc_hs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb2_page isoc_fs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; +}; + +struct ehci_config_desc { + struct usb2_config_descriptor confd; + struct usb2_interface_descriptor ifcd; + struct usb2_endpoint_descriptor endpd; +} __packed; + +union ehci_hub_desc { + struct usb2_status stat; + struct usb2_port_status ps; + struct usb2_device_descriptor devd; + struct usb2_device_qualifier odevd; + struct usb2_hub_descriptor hubd; + uint8_t temp[128]; +}; + +typedef struct ehci_softc { + struct ehci_hw_softc sc_hw; + struct usb2_bus sc_bus; /* base device */ + struct usb2_config_td sc_config_td; + struct usb2_callout sc_tmo_pcd; + union ehci_hub_desc sc_hub_desc; + struct usb2_sw_transfer sc_root_ctrl; + struct usb2_sw_transfer sc_root_intr; + + struct resource *sc_io_res; + struct resource *sc_irq_res; + struct ehci_qh *sc_async_p_last; + struct ehci_qh *sc_intr_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct ehci_sitd *sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct ehci_itd *sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + void *sc_intr_hdl; + device_t sc_dev; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_eintrs; + uint32_t sc_cmd; /* shadow of cmd register during + * suspend */ + + uint16_t sc_intr_stat[EHCI_VIRTUAL_FRAMELIST_COUNT]; + uint16_t sc_id_vendor; /* vendor ID for root hub */ + + uint8_t sc_offs; /* offset to operational registers */ + uint8_t sc_doorbell_disable; /* set on doorbell failure */ + uint8_t sc_noport; + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_isreset; + uint8_t sc_hub_idata[8]; + + char sc_vendor[16]; /* vendor string for root hub */ + +} ehci_softc_t; + +#define EREAD1(sc, a) bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#define EREAD2(sc, a) bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#define EREAD4(sc, a) bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#define EWRITE1(sc, a, x) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#define EWRITE2(sc, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#define EWRITE4(sc, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#define EOREAD1(sc, a) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#define EOREAD2(sc, a) \ + bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#define EOREAD4(sc, a) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#define EOWRITE1(sc, a, x) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) +#define EOWRITE2(sc, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) +#define EOWRITE4(sc, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) + +usb2_bus_mem_cb_t ehci_iterate_hw_softc; + +usb2_error_t ehci_init(ehci_softc_t *sc); +void ehci_detach(struct ehci_softc *sc); +void ehci_suspend(struct ehci_softc *sc); +void ehci_resume(struct ehci_softc *sc); +void ehci_shutdown(ehci_softc_t *sc); +void ehci_interrupt(ehci_softc_t *sc); + +#endif /* _EHCI_H_ */ diff --git a/sys/dev/usb2/controller/ehci2_pci.c b/sys/dev/usb2/controller/ehci2_pci.c new file mode 100644 index 000000000000..1fb7dc2cd749 --- /dev/null +++ b/sys/dev/usb2/controller/ehci2_pci.c @@ -0,0 +1,498 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. + * + * The EHCI 1.0 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r10.pdf + * and the USB 2.0 spec at + * http://www.usb.org/developers/docs/usb_20.zip + */ + +/* The low level controller code for EHCI has been split into + * PCI probes and EHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define PCI_EHCI_VENDORID_ACERLABS 0x10b9 +#define PCI_EHCI_VENDORID_AMD 0x1022 +#define PCI_EHCI_VENDORID_APPLE 0x106b +#define PCI_EHCI_VENDORID_ATI 0x1002 +#define PCI_EHCI_VENDORID_CMDTECH 0x1095 +#define PCI_EHCI_VENDORID_INTEL 0x8086 +#define PCI_EHCI_VENDORID_NEC 0x1033 +#define PCI_EHCI_VENDORID_OPTI 0x1045 +#define PCI_EHCI_VENDORID_PHILIPS 0x1131 +#define PCI_EHCI_VENDORID_SIS 0x1039 +#define PCI_EHCI_VENDORID_NVIDIA 0x12D2 +#define PCI_EHCI_VENDORID_NVIDIA2 0x10DE +#define PCI_EHCI_VENDORID_VIA 0x1106 + +#define PCI_EHCI_BASE_REG 0x10 + +static void ehci_pci_takecontroller(device_t self); + +static device_probe_t ehci_pci_probe; +static device_attach_t ehci_pci_attach; +static device_detach_t ehci_pci_detach; +static device_suspend_t ehci_pci_suspend; +static device_resume_t ehci_pci_resume; +static device_shutdown_t ehci_pci_shutdown; + +static int +ehci_pci_suspend(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_suspend(self); + if (err) + return (err); + ehci_suspend(sc); + return (0); +} + +static int +ehci_pci_resume(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + + ehci_pci_takecontroller(self); + ehci_resume(sc); + + bus_generic_resume(self); + + return (0); +} + +static int +ehci_pci_shutdown(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_shutdown(self); + if (err) + return (err); + ehci_shutdown(sc); + + return (0); +} + +static const char * +ehci_pci_match(device_t self) +{ + uint32_t device_id = pci_get_devid(self); + + switch (device_id) { + case 0x268c8086: + return ("Intel 63XXESB USB 2.0 controller"); + + case 0x523910b9: + return "ALi M5239 USB 2.0 controller"; + + case 0x10227463: + return "AMD 8111 USB 2.0 controller"; + + case 0x20951022: + return ("AMD CS5536 (Geode) USB 2.0 controller"); + + case 0x43451002: + return "ATI SB200 USB 2.0 controller"; + case 0x43731002: + return "ATI SB400 USB 2.0 controller"; + + case 0x25ad8086: + return "Intel 6300ESB USB 2.0 controller"; + case 0x24cd8086: + return "Intel 82801DB/L/M (ICH4) USB 2.0 controller"; + case 0x24dd8086: + return "Intel 82801EB/R (ICH5) USB 2.0 controller"; + case 0x265c8086: + return "Intel 82801FB (ICH6) USB 2.0 controller"; + case 0x27cc8086: + return "Intel 82801GB/R (ICH7) USB 2.0 controller"; + + case 0x28368086: + return "Intel 82801H (ICH8) USB 2.0 controller USB2-A"; + case 0x283a8086: + return "Intel 82801H (ICH8) USB 2.0 controller USB2-B"; + case 0x293a8086: + return "Intel 82801I (ICH9) USB 2.0 controller"; + case 0x293c8086: + return "Intel 82801I (ICH9) USB 2.0 controller"; + + case 0x00e01033: + return ("NEC uPD 720100 USB 2.0 controller"); + + case 0x006810de: + return "NVIDIA nForce2 USB 2.0 controller"; + case 0x008810de: + return "NVIDIA nForce2 Ultra 400 USB 2.0 controller"; + case 0x00d810de: + return "NVIDIA nForce3 USB 2.0 controller"; + case 0x00e810de: + return "NVIDIA nForce3 250 USB 2.0 controller"; + case 0x005b10de: + return "NVIDIA nForce4 USB 2.0 controller"; + + case 0x15621131: + return "Philips ISP156x USB 2.0 controller"; + + case 0x31041106: + return ("VIA VT6202 USB 2.0 controller"); + + default: + break; + } + + if ((pci_get_class(self) == PCIC_SERIALBUS) + && (pci_get_subclass(self) == PCIS_SERIALBUS_USB) + && (pci_get_progif(self) == PCI_INTERFACE_EHCI)) { + return ("EHCI (generic) USB 2.0 controller"); + } + return (NULL); /* dunno */ +} + +static int +ehci_pci_probe(device_t self) +{ + const char *desc = ehci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return (0); + } else { + return (ENXIO); + } +} + +static int +ehci_pci_attach(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + int err; + int rid; + + if (sc == NULL) { + device_printf(self, "Could not allocate sc\n"); + return (ENXIO); + } + /* get all DMA memory */ + + if (usb2_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { + return ENOMEM; + } + sc->sc_dev = self; + + pci_enable_busmaster(self); + + switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) { + case PCI_USB_REV_PRE_1_0: + case PCI_USB_REV_1_0: + case PCI_USB_REV_1_1: + /* + * NOTE: some EHCI USB controllers have the wrong USB + * revision number. It appears those controllers are + * fully compliant so we just ignore this value in + * some common cases. + */ + device_printf(self, "pre-2.0 USB revision (ignored)\n"); + /* fallthrough */ + case PCI_USB_REV_2_0: + sc->sc_bus.usbrev = USB_REV_2_0; + break; + default: + sc->sc_bus.usbrev = USB_REV_UNKNOWN; + break; + } + + rid = PCI_CBMEM; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map memory\n"); + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* + * ehci_pci_match will never return NULL if ehci_pci_probe + * succeeded + */ + device_set_desc(sc->sc_bus.bdev, ehci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_EHCI_VENDORID_ACERLABS: + sprintf(sc->sc_vendor, "AcerLabs"); + break; + case PCI_EHCI_VENDORID_AMD: + sprintf(sc->sc_vendor, "AMD"); + break; + case PCI_EHCI_VENDORID_APPLE: + sprintf(sc->sc_vendor, "Apple"); + break; + case PCI_EHCI_VENDORID_ATI: + sprintf(sc->sc_vendor, "ATI"); + break; + case PCI_EHCI_VENDORID_CMDTECH: + sprintf(sc->sc_vendor, "CMDTECH"); + break; + case PCI_EHCI_VENDORID_INTEL: + sprintf(sc->sc_vendor, "Intel"); + break; + case PCI_EHCI_VENDORID_NEC: + sprintf(sc->sc_vendor, "NEC"); + break; + case PCI_EHCI_VENDORID_OPTI: + sprintf(sc->sc_vendor, "OPTi"); + break; + case PCI_EHCI_VENDORID_PHILIPS: + sprintf(sc->sc_vendor, "Philips"); + break; + case PCI_EHCI_VENDORID_SIS: + sprintf(sc->sc_vendor, "SiS"); + break; + case PCI_EHCI_VENDORID_NVIDIA: + case PCI_EHCI_VENDORID_NVIDIA2: + sprintf(sc->sc_vendor, "nVidia"); + break; + case PCI_EHCI_VENDORID_VIA: + sprintf(sc->sc_vendor, "VIA"); + break; + default: + if (bootverbose) + device_printf(self, "(New EHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + + err = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_bus.mtx, + NULL, 0, 4); + if (err) { + device_printf(self, "could not setup config thread!\n"); + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)(void *)ehci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)(void *)ehci_interrupt, sc, &sc->sc_intr_hdl); +#endif + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + ehci_pci_takecontroller(self); + err = ehci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed err=%d\n", err); + goto error; + } + return (0); + +error: + ehci_pci_detach(self); + return (ENXIO); +} + +static int +ehci_pci_detach(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + device_t bdev; + + usb2_config_td_drain(&sc->sc_config_td); + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(self); + + pci_disable_busmaster(self); + + /* + * disable interrupts that might have been switched on in ehci_init + */ + if (sc->sc_io_res) { + EWRITE4(sc, EHCI_USBINTR, 0); + } + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call ehci_detach() after ehci_init() + */ + ehci_detach(sc); + + int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); + + return (0); +} + +static void +ehci_pci_takecontroller(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + uint32_t cparams; + uint32_t eec; + uint16_t to; + uint8_t eecp; + uint8_t bios_sem; + + cparams = EREAD4(sc, EHCI_HCCPARAMS); + + /* Synchronise with the BIOS if it owns the controller. */ + for (eecp = EHCI_HCC_EECP(cparams); eecp != 0; + eecp = EHCI_EECP_NEXT(eec)) { + eec = pci_read_config(self, eecp, 4); + if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) { + continue; + } + bios_sem = pci_read_config(self, eecp + + EHCI_LEGSUP_BIOS_SEM, 1); + if (bios_sem == 0) { + continue; + } + device_printf(sc->sc_bus.bdev, "waiting for BIOS " + "to give up control\n"); + pci_write_config(self, eecp + + EHCI_LEGSUP_OS_SEM, 1, 1); + to = 500; + while (1) { + bios_sem = pci_read_config(self, eecp + + EHCI_LEGSUP_BIOS_SEM, 1); + if (bios_sem == 0) + break; + + if (--to == 0) { + device_printf(sc->sc_bus.bdev, + "timed out waiting for BIOS\n"); + break; + } + usb2_pause_mtx(NULL, 10); /* wait 10ms */ + } + } + return; +} + +static driver_t ehci_driver = +{ + .name = "ehci", + .methods = (device_method_t[]){ + /* device interface */ + DEVMETHOD(device_probe, ehci_pci_probe), + DEVMETHOD(device_attach, ehci_pci_attach), + DEVMETHOD(device_detach, ehci_pci_detach), + DEVMETHOD(device_suspend, ehci_pci_suspend), + DEVMETHOD(device_resume, ehci_pci_resume), + DEVMETHOD(device_shutdown, ehci_pci_shutdown), + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} + }, + .size = sizeof(struct ehci_softc), +}; + +static devclass_t ehci_devclass; + +DRIVER_MODULE(ehci, pci, ehci_driver, ehci_devclass, 0, 0); +DRIVER_MODULE(ehci, cardbus, ehci_driver, ehci_devclass, 0, 0); +MODULE_DEPEND(ehci, usb2_controller, 1, 1, 1); +MODULE_DEPEND(ehci, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/musb2_otg.c b/sys/dev/usb2/controller/musb2_otg.c new file mode 100644 index 000000000000..b036d5c6123a --- /dev/null +++ b/sys/dev/usb2/controller/musb2_otg.c @@ -0,0 +1,2945 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * Thanks to Mentor Graphics for providing a reference driver for this + * USB chip at their homepage. + */ + +/* + * This file contains the driver for the Mentor Graphics Inventra USB + * 2.0 High Speed Dual-Role controller. + * + * NOTE: The current implementation only supports Device Side Mode! + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR musbotgdebug +#define usb2_config_td_cc musbotg_config_copy +#define usb2_config_td_softc musbotg_softc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MUSBOTG_INTR_ENDPT 1 + +#define MUSBOTG_BUS2SC(bus) \ + ((struct musbotg_softc *)(((uint8_t *)(bus)) - \ + USB_P2U(&(((struct musbotg_softc *)0)->sc_bus)))) + +#define MUSBOTG_PC2SC(pc) \ + MUSBOTG_BUS2SC((pc)->tag_parent->info->bus) + +#if USB_DEBUG +static int musbotgdebug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, musbotg, CTLFLAG_RW, 0, "USB musbotg"); +SYSCTL_INT(_hw_usb2_musbotg, OID_AUTO, debug, CTLFLAG_RW, + &musbotgdebug, 0, "Debug level"); +#endif + +/* prototypes */ + +struct usb2_bus_methods musbotg_bus_methods; +struct usb2_pipe_methods musbotg_device_bulk_methods; +struct usb2_pipe_methods musbotg_device_ctrl_methods; +struct usb2_pipe_methods musbotg_device_intr_methods; +struct usb2_pipe_methods musbotg_device_isoc_methods; +struct usb2_pipe_methods musbotg_root_ctrl_methods; +struct usb2_pipe_methods musbotg_root_intr_methods; + +static musbotg_cmd_t musbotg_setup_rx; +static musbotg_cmd_t musbotg_setup_data_rx; +static musbotg_cmd_t musbotg_setup_data_tx; +static musbotg_cmd_t musbotg_setup_status; +static musbotg_cmd_t musbotg_data_rx; +static musbotg_cmd_t musbotg_data_tx; +static void musbotg_device_done(struct usb2_xfer *xfer, usb2_error_t error); +static void musbotg_do_poll(struct usb2_bus *bus); +static void musbotg_root_ctrl_poll(struct musbotg_softc *sc); +static void musbotg_standard_done(struct usb2_xfer *xfer); +static void musbotg_interrupt_poll(struct musbotg_softc *sc); + +static usb2_sw_transfer_func_t musbotg_root_intr_done; +static usb2_sw_transfer_func_t musbotg_root_ctrl_done; +static usb2_config_td_command_t musbotg_root_ctrl_task; + +/* + * Here is a configuration that the chip supports. + */ +static const struct usb2_hw_ep_profile musbotg_ep_profile[1] = { + + [0] = { + .max_in_frame_size = 64,/* fixed */ + .max_out_frame_size = 64, /* fixed */ + .is_simplex = 1, + .support_control = 1, + } +}; + +static void +musbotg_get_hw_ep_profile(struct usb2_device *udev, + const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr) +{ + struct musbotg_softc *sc; + + sc = MUSBOTG_BUS2SC(udev->bus); + + if (ep_addr == 0) { + /* control endpoint */ + *ppf = musbotg_ep_profile; + } else if (ep_addr <= sc->sc_ep_max) { + /* other endpoints */ + *ppf = sc->sc_hw_ep_profile + ep_addr; + } else { + *ppf = NULL; + } + return; +} + +static void +musbotg_clocks_on(struct musbotg_softc *sc) +{ + if (sc->sc_flags.clocks_off && + sc->sc_flags.port_powered) { + + DPRINTFN(4, "\n"); + + if (sc->sc_clocks_on) { + (sc->sc_clocks_on) (sc->sc_clocks_arg); + } + sc->sc_flags.clocks_off = 0; + + /* XXX enable Transceiver */ + } + return; +} + +static void +musbotg_clocks_off(struct musbotg_softc *sc) +{ + if (!sc->sc_flags.clocks_off) { + + DPRINTFN(4, "\n"); + + /* XXX disable Transceiver */ + + if (sc->sc_clocks_off) { + (sc->sc_clocks_off) (sc->sc_clocks_arg); + } + sc->sc_flags.clocks_off = 1; + } + return; +} + +static void +musbotg_pull_common(struct musbotg_softc *sc, uint8_t on) +{ + uint8_t temp; + + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + if (on) + temp |= MUSB2_MASK_SOFTC; + else + temp &= ~MUSB2_MASK_SOFTC; + + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); + return; +} + +static void +musbotg_pull_up(struct musbotg_softc *sc) +{ + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + musbotg_pull_common(sc, 1); + } + return; +} + +static void +musbotg_pull_down(struct musbotg_softc *sc) +{ + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + musbotg_pull_common(sc, 0); + } + return; +} + +static void +musbotg_wakeup_peer(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + uint8_t temp; + uint8_t use_polling; + + if (!(sc->sc_flags.status_suspend)) { + return; + } + use_polling = mtx_owned(xfer->priv_mtx) ? 1 : 0; + + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + temp |= MUSB2_MASK_RESUME; + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); + + /* wait 8 milliseconds */ + if (use_polling) { + /* polling */ + DELAY(8000); + } else { + /* Wait for reset to complete. */ + usb2_pause_mtx(&sc->sc_bus.mtx, 8); + } + + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + temp &= ~MUSB2_MASK_RESUME; + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); + return; +} + +static void +musbotg_rem_wakeup_set(struct usb2_device *udev, uint8_t is_on) +{ + DPRINTFN(4, "is_on=%u\n", is_on); + return; +} + +static void +musbotg_set_address(struct musbotg_softc *sc, uint8_t addr) +{ + DPRINTFN(4, "addr=%d\n", addr); + addr &= 0x7F; + MUSB2_WRITE_1(sc, MUSB2_REG_FADDR, addr); + return; +} + +static uint8_t +musbotg_setup_rx(struct musbotg_td *td) +{ + struct musbotg_softc *sc; + struct usb2_device_request req; + uint16_t count; + uint8_t csr; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint 0 */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + /* + * NOTE: If DATAEND is set we should not call the + * callback, hence the status stage is not complete. + */ + if (csr & MUSB2_MASK_CSR0L_DATAEND) { + /* wait for interrupt */ + goto not_complete; + } + if (csr & MUSB2_MASK_CSR0L_SENTSTALL) { + /* clear SENTSTALL */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); + /* get latest status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + /* update EP0 state */ + sc->sc_ep0_busy = 0; + } + if (csr & MUSB2_MASK_CSR0L_SETUPEND) { + /* clear SETUPEND */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_SETUPEND_CLR); + /* get latest status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + /* update EP0 state */ + sc->sc_ep0_busy = 0; + } + if (sc->sc_ep0_busy) { + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(4, "stalling\n"); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_SENDSTALL); + td->did_stall = 1; + } + goto not_complete; + } + if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) { + goto not_complete; + } + /* get the packet byte count */ + count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + /* receive data */ + bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req)); + + /* copy data into real buffer */ + usb2_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* set pending command */ + sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR; + + /* we need set stall or dataend after this */ + sc->sc_ep0_busy = 1; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + } else { + sc->sc_dv_addr = 0xFF; + } + return (0); /* complete */ + +not_complete: + return (1); /* not complete */ +} + +/* Control endpoint only data handling functions (RX/TX/SYNC) */ + +static uint8_t +musbotg_setup_data_rx(struct musbotg_td *td) +{ + struct usb2_page_search buf_res; + struct musbotg_softc *sc; + uint16_t count; + uint8_t csr; + uint8_t got_short; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint 0 */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + /* check if a command is pending */ + if (sc->sc_ep0_cmd) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); + sc->sc_ep0_cmd = 0; + } + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + got_short = 0; + + if (csr & (MUSB2_MASK_CSR0L_SETUPEND | + MUSB2_MASK_CSR0L_SENTSTALL)) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(4, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) { + return (1); /* not complete */ + } + /* get the packet byte count */ + count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); + + /* verify the packet byte count */ + if (count != td->max_frame_size) { + if (count < td->max_frame_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + uint32_t temp; + + usb2_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* check for unaligned memory address */ + if (USB_P2U(buf_res.buffer) & 3) { + + temp = count & ~3; + + if (temp) { + /* receive data 4 bytes at a time */ + bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, + temp / 4); + } + temp = count & 3; + if (temp) { + /* receive data 1 byte at a time */ + bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), + (void *)(&sc->sc_bounce_buf[count / 4]), temp); + } + usb2_copy_in(td->pc, td->offset, + sc->sc_bounce_buf, count); + + /* update offset and remainder */ + td->offset += count; + td->remainder -= count; + break; + } + /* check if we can optimise */ + if (buf_res.length >= 4) { + + /* receive data 4 bytes at a time */ + bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), buf_res.buffer, + buf_res.length / 4); + + temp = buf_res.length & ~3; + + /* update counters */ + count -= temp; + td->offset += temp; + td->remainder -= temp; + continue; + } + /* receive data */ + bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR; + return (0); + } + /* else need to receive a zero length packet */ + } + /* write command - need more data */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_RXPKTRDY_CLR); + return (1); /* not complete */ +} + +static uint8_t +musbotg_setup_data_tx(struct musbotg_td *td) +{ + struct usb2_page_search buf_res; + struct musbotg_softc *sc; + uint16_t count; + uint8_t csr; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint 0 */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + /* check if a command is pending */ + if (sc->sc_ep0_cmd) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); + sc->sc_ep0_cmd = 0; + } + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + if (csr & (MUSB2_MASK_CSR0L_SETUPEND | + MUSB2_MASK_CSR0L_SENTSTALL)) { + /* + * The current transfer was aborted + * by the USB Host + */ + td->error = 1; + return (0); /* complete */ + } + if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) { + return (1); /* not complete */ + } + count = td->max_frame_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + while (count > 0) { + uint32_t temp; + + usb2_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* check for unaligned memory address */ + if (USB_P2U(buf_res.buffer) & 3) { + + usb2_copy_out(td->pc, td->offset, + sc->sc_bounce_buf, count); + + temp = count & ~3; + + if (temp) { + /* transmit data 4 bytes at a time */ + bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, + temp / 4); + } + temp = count & 3; + if (temp) { + /* receive data 1 byte at a time */ + bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), + ((void *)&sc->sc_bounce_buf[count / 4]), temp); + } + /* update offset and remainder */ + td->offset += count; + td->remainder -= count; + break; + } + /* check if we can optimise */ + if (buf_res.length >= 4) { + + /* transmit data 4 bytes at a time */ + bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), buf_res.buffer, + buf_res.length / 4); + + temp = buf_res.length & ~3; + + /* update counters */ + count -= temp; + td->offset += temp; + td->remainder -= temp; + continue; + } + /* transmit data */ + bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_TXPKTRDY; + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + /* write command */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_TXPKTRDY); + + return (1); /* not complete */ +} + +static uint8_t +musbotg_setup_status(struct musbotg_td *td) +{ + struct musbotg_softc *sc; + uint8_t csr; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint 0 */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + if (sc->sc_ep0_busy) { + sc->sc_ep0_busy = 0; + sc->sc_ep0_cmd |= MUSB2_MASK_CSR0L_DATAEND; + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); + sc->sc_ep0_cmd = 0; + } + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + if (csr & MUSB2_MASK_CSR0L_DATAEND) { + /* wait for interrupt */ + return (1); /* not complete */ + } + if (sc->sc_dv_addr != 0xFF) { + /* write function address */ + musbotg_set_address(sc, sc->sc_dv_addr); + } + return (0); /* complete */ +} + +static uint8_t +musbotg_data_rx(struct musbotg_td *td) +{ + struct usb2_page_search buf_res; + struct musbotg_softc *sc; + uint16_t count; + uint8_t csr; + uint8_t to; + uint8_t got_short; + + to = 8; /* don't loop forever! */ + got_short = 0; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->ep_no); + +repeat: + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + /* clear overrun */ + if (csr & MUSB2_MASK_CSRL_RXOVERRUN) { + /* make sure we don't clear "RXPKTRDY" */ + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXPKTRDY); + } + /* check status */ + if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) { + return (1); /* not complete */ + } + /* get the packet byte count */ + count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); + + DPRINTFN(4, "count=0x%04x\n", count); + + /* + * Check for short or invalid packet: + */ + if (count != td->max_frame_size) { + if (count < td->max_frame_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + uint32_t temp; + + usb2_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* check for unaligned memory address */ + if (USB_P2U(buf_res.buffer) & 3) { + + temp = count & ~3; + + if (temp) { + /* receive data 4 bytes at a time */ + bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), sc->sc_bounce_buf, + temp / 4); + } + temp = count & 3; + if (temp) { + /* receive data 1 byte at a time */ + bus_space_read_multi_1(sc->sc_io_tag, + sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->ep_no), + ((void *)&sc->sc_bounce_buf[count / 4]), temp); + } + usb2_copy_in(td->pc, td->offset, + sc->sc_bounce_buf, count); + + /* update offset and remainder */ + td->offset += count; + td->remainder -= count; + break; + } + /* check if we can optimise */ + if (buf_res.length >= 4) { + + /* receive data 4 bytes at a time */ + bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer, + buf_res.length / 4); + + temp = buf_res.length & ~3; + + /* update counters */ + count -= temp; + td->offset += temp; + td->remainder -= temp; + continue; + } + /* receive data */ + bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer, + buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* clear status bits */ + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +musbotg_data_tx(struct musbotg_td *td) +{ + struct usb2_page_search buf_res; + struct musbotg_softc *sc; + uint16_t count; + uint8_t csr; + uint8_t to; + + to = 8; /* don't loop forever! */ + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->ep_no); + +repeat: + + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + if (csr & (MUSB2_MASK_CSRL_TXINCOMP | + MUSB2_MASK_CSRL_TXUNDERRUN)) { + /* clear status bits */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); + } + if (csr & MUSB2_MASK_CSRL_TXPKTRDY) { + return (1); /* not complete */ + } + /* check for short packet */ + count = td->max_frame_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + while (count > 0) { + uint32_t temp; + + usb2_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* check for unaligned memory address */ + if (USB_P2U(buf_res.buffer) & 3) { + + usb2_copy_out(td->pc, td->offset, + sc->sc_bounce_buf, count); + + temp = count & ~3; + + if (temp) { + /* transmit data 4 bytes at a time */ + bus_space_write_multi_4(sc->sc_io_tag, + sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->ep_no), + sc->sc_bounce_buf, temp / 4); + } + temp = count & 3; + if (temp) { + /* receive data 1 byte at a time */ + bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), + ((void *)&sc->sc_bounce_buf[count / 4]), temp); + } + /* update offset and remainder */ + td->offset += count; + td->remainder -= count; + break; + } + /* check if we can optimise */ + if (buf_res.length >= 4) { + + /* transmit data 4 bytes at a time */ + bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer, + buf_res.length / 4); + + temp = buf_res.length & ~3; + + /* update counters */ + count -= temp; + td->offset += temp; + td->remainder -= temp; + continue; + } + /* transmit data */ + bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer, + buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* write command */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXPKTRDY); + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +musbotg_xfer_do_fifo(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc; + struct musbotg_td *td; + + DPRINTFN(8, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + sc = xfer->usb2_sc; + + /* compute all actual lengths */ + + musbotg_standard_done(xfer); + + return (0); /* complete */ +} + +static void +musbotg_interrupt_poll(struct musbotg_softc *sc) +{ + struct usb2_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!musbotg_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } + + return; +} + +static void +musbotg_vbus_interrupt(struct usb2_bus *bus, uint8_t is_on) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); + + DPRINTFN(4, "vbus = %u\n", is_on); + + mtx_lock(&sc->sc_bus.mtx); + if (is_on) { + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + + usb2_sw_transfer(&sc->sc_root_intr, + &musbotg_root_intr_done); + } + } else { + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + + usb2_sw_transfer(&sc->sc_root_intr, + &musbotg_root_intr_done); + } + } + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +void +musbotg_interrupt(struct musbotg_softc *sc) +{ + uint16_t rx_status; + uint16_t tx_status; + uint8_t usb_status; + uint8_t temp; + uint8_t to = 2; + + mtx_lock(&sc->sc_bus.mtx); + +repeat: + + /* read all interrupt registers */ + usb_status = MUSB2_READ_1(sc, MUSB2_REG_INTUSB); + + /* read all FIFO interrupts */ + rx_status = MUSB2_READ_2(sc, MUSB2_REG_INTRX); + tx_status = MUSB2_READ_2(sc, MUSB2_REG_INTTX); + + /* check for any bus state change interrupts */ + + if (usb_status & (MUSB2_MASK_IRESET | + MUSB2_MASK_IRESUME | MUSB2_MASK_ISUSP)) { + + DPRINTFN(4, "real bus interrupt 0x%08x\n", usb_status); + + if (usb_status & MUSB2_MASK_IRESET) { + + /* set correct state */ + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* determine line speed */ + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + if (temp & MUSB2_MASK_HSMODE) + sc->sc_flags.status_high_speed = 1; + else + sc->sc_flags.status_high_speed = 0; + + /* + * After reset all interrupts are on and we need to + * turn them off! + */ + temp = MUSB2_MASK_IRESET; + /* disable resume interrupt */ + temp &= ~MUSB2_MASK_IRESUME; + /* enable suspend interrupt */ + temp |= MUSB2_MASK_ISUSP; + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); + /* disable TX and RX interrupts */ + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); + } + /* + * If RXRSM and RXSUSP is set at the same time we interpret + * that like RESUME. Resume is set when there is at least 3 + * milliseconds of inactivity on the USB BUS. + */ + if (usb_status & MUSB2_MASK_IRESUME) { + if (sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + + temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE); + /* disable resume interrupt */ + temp &= ~MUSB2_MASK_IRESUME; + /* enable suspend interrupt */ + temp |= MUSB2_MASK_ISUSP; + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); + } + } else if (usb_status & MUSB2_MASK_ISUSP) { + if (!sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + + temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE); + /* disable suspend interrupt */ + temp &= ~MUSB2_MASK_ISUSP; + /* enable resume interrupt */ + temp |= MUSB2_MASK_IRESUME; + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); + } + } + /* complete root HUB interrupt endpoint */ + + usb2_sw_transfer(&sc->sc_root_intr, + &musbotg_root_intr_done); + } + /* check for any endpoint interrupts */ + + if (rx_status || tx_status) { + DPRINTFN(4, "real endpoint interrupt " + "rx=0x%04x, tx=0x%04x\n", rx_status, tx_status); + } + /* poll one time regardless of FIFO status */ + + musbotg_interrupt_poll(sc); + + if (--to) + goto repeat; + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +musbotg_setup_standard_chain_sub(struct musbotg_std_temp *temp) +{ + struct musbotg_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->error = 0; + td->did_stall = 0; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; + return; +} + +static void +musbotg_setup_standard_chain(struct usb2_xfer *xfer) +{ + struct musbotg_std_temp temp; + struct musbotg_softc *sc; + struct musbotg_td *td; + uint32_t x; + uint8_t ep_no; + + DPRINTFN(8, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpoint), + xfer->sumlen, usb2_get_speed(xfer->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.offset = 0; + + sc = xfer->usb2_sc; + ep_no = (xfer->endpoint & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &musbotg_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + + musbotg_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpoint & UE_DIR_IN) { + if (xfer->flags_int.control_xfr) + temp.func = &musbotg_setup_data_tx; + else + temp.func = &musbotg_data_tx; + } else { + if (xfer->flags_int.control_xfr) + temp.func = &musbotg_setup_data_rx; + else + temp.func = &musbotg_data_rx; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + temp.setup_alt_next = 0; + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + musbotg_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + temp.func = &musbotg_setup_status; + temp.len = 0; + temp.short_pkt = 0; + + musbotg_setup_standard_chain_sub(&temp); + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; + return; +} + +static void +musbotg_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + struct musbotg_softc *sc = xfer->usb2_sc; + + DPRINTFN(1, "xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + musbotg_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +musbotg_ep_int_set(struct usb2_xfer *xfer, uint8_t on) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + uint16_t temp; + uint8_t ep_no = xfer->endpoint & UE_ADDR; + + /* + * Only enable the endpoint interrupt when we are + * actually waiting for data, hence we are dealing + * with level triggered interrupts ! + */ + if (ep_no == 0) { + temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE); + if (on) + temp |= MUSB2_MASK_EPINT(0); + else + temp &= ~MUSB2_MASK_EPINT(0); + + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp); + } else { + if (USB_GET_DATA_ISREAD(xfer)) { + temp = MUSB2_READ_2(sc, MUSB2_REG_INTRXE); + if (on) + temp |= MUSB2_MASK_EPINT(ep_no); + else + temp &= ~MUSB2_MASK_EPINT(ep_no); + MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, temp); + + } else { + temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE); + if (on) + temp |= MUSB2_MASK_EPINT(ep_no); + else + temp &= ~MUSB2_MASK_EPINT(ep_no); + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp); + } + } + return; +} + +static void +musbotg_start_standard_chain(struct usb2_xfer *xfer) +{ + DPRINTFN(8, "\n"); + + /* poll one time */ + if (musbotg_xfer_do_fifo(xfer)) { + + musbotg_ep_int_set(xfer, 1); + + DPRINTFN(14, "enabled interrupts on endpoint\n"); + + /* put transfer on interrupt queue */ + usb2_transfer_enqueue(&xfer->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, + &musbotg_timeout, xfer->timeout); + } + } + return; +} + +static void +musbotg_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + + DPRINTFN(8, "\n"); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_PRE_DATA) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + musbotg_device_done(xfer, std->err); + } + goto done; + } + /* setup buffer */ + std->ptr = sc->sc_hub_idata; + std->len = sizeof(sc->sc_hub_idata); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + +done: + return; +} + +static usb2_error_t +musbotg_standard_done_sub(struct usb2_xfer *xfer) +{ + struct musbotg_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(8, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +musbotg_standard_done(struct usb2_xfer *xfer) +{ + usb2_error_t err = 0; + + DPRINTFN(12, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = musbotg_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = musbotg_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = musbotg_standard_done_sub(xfer); + } +done: + musbotg_device_done(xfer, err); + return; +} + +/*------------------------------------------------------------------------* + * musbotg_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +musbotg_device_done(struct usb2_xfer *xfer, usb2_error_t error) +{ + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n", + xfer, xfer->pipe, error); + + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + + musbotg_ep_int_set(xfer, 0); + + DPRINTFN(14, "disabled interrupts on endpoint\n"); + } + /* dequeue transfer and start next transfer */ + usb2_transfer_done(xfer, error); + return; +} + +static void +musbotg_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer, + struct usb2_pipe *pipe) +{ + struct musbotg_softc *sc; + uint8_t ep_no; + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + DPRINTFN(4, "pipe=%p\n", pipe); + + if (xfer) { + /* cancel any ongoing transfers */ + musbotg_device_done(xfer, USB_ERR_STALLED); + } + /* set FORCESTALL */ + sc = MUSBOTG_BUS2SC(udev->bus); + + ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR); + + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no); + + if (pipe->edesc->bEndpointAddress & UE_DIR_IN) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXSENDSTALL); + } else { + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXSENDSTALL); + } + return; +} + +static void +musbotg_clear_stall_sub(struct musbotg_softc *sc, uint16_t wMaxPacket, + uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) +{ + uint16_t mps; + uint16_t temp; + uint8_t csr; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no); + + /* compute max frame size */ + mps = wMaxPacket & 0x7FF; + switch ((wMaxPacket >> 11) & 3) { + case 1: + mps *= 2; + break; + case 2: + mps *= 3; + break; + default: + break; + } + + if (ep_dir == UE_DIR_IN) { + + temp = 0; + + /* Configure endpoint */ + switch (ep_type) { + case UE_INTERRUPT: + MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, + MUSB2_MASK_CSRH_TXMODE | temp); + break; + case UE_ISOCHRONOUS: + MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, + MUSB2_MASK_CSRH_TXMODE | + MUSB2_MASK_CSRH_TXISO | temp); + break; + case UE_BULK: + MUSB2_WRITE_1(sc, MUSB2_REG_TXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, + MUSB2_MASK_CSRH_TXMODE | temp); + break; + default: + break; + } + + /* Need to flush twice in case of double bufring */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXFFLUSH); + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXFFLUSH); + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + } + } + /* reset data toggle */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXDT_CLR); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + /* set double/single buffering */ + temp = MUSB2_READ_2(sc, MUSB2_REG_TXDBDIS); + if (mps <= (sc->sc_hw_ep_profile[ep_no]. + max_in_frame_size / 2)) { + /* double buffer */ + temp &= ~(1 << ep_no); + } else { + /* single buffer */ + temp |= (1 << ep_no); + } + MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, temp); + + /* clear sent stall */ + if (csr & MUSB2_MASK_CSRL_TXSENTSTALL) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + } + } else { + + temp = 0; + + /* Configure endpoint */ + switch (ep_type) { + case UE_INTERRUPT: + MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, + MUSB2_MASK_CSRH_RXNYET | temp); + break; + case UE_ISOCHRONOUS: + MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, + MUSB2_MASK_CSRH_RXNYET | + MUSB2_MASK_CSRH_RXISO | temp); + break; + case UE_BULK: + MUSB2_WRITE_1(sc, MUSB2_REG_RXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, temp); + break; + default: + break; + } + + /* Need to flush twice in case of double bufring */ + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + if (csr & MUSB2_MASK_CSRL_RXPKTRDY) { + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXFFLUSH); + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + if (csr & MUSB2_MASK_CSRL_RXPKTRDY) { + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXFFLUSH); + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + } + } + /* reset data toggle */ + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXDT_CLR); + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + + /* set double/single buffering */ + temp = MUSB2_READ_2(sc, MUSB2_REG_RXDBDIS); + if (mps <= (sc->sc_hw_ep_profile[ep_no]. + max_out_frame_size / 2)) { + /* double buffer */ + temp &= ~(1 << ep_no); + } else { + /* single buffer */ + temp |= (1 << ep_no); + } + MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, temp); + + /* clear sent stall */ + if (csr & MUSB2_MASK_CSRL_RXSENTSTALL) { + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); + } + } + return; +} + +static void +musbotg_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe) +{ + struct musbotg_softc *sc; + struct usb2_endpoint_descriptor *ed; + + DPRINTFN(4, "pipe=%p\n", pipe); + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + /* check mode */ + if (udev->flags.usb2_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = MUSBOTG_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = pipe->edesc; + + /* reset endpoint */ + musbotg_clear_stall_sub(sc, + UGETW(ed->wMaxPacketSize), + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); + return; +} + +usb2_error_t +musbotg_init(struct musbotg_softc *sc) +{ + struct usb2_hw_ep_profile *pf; + uint8_t nrx; + uint8_t ntx; + uint8_t temp; + uint8_t fsize; + uint8_t frx; + uint8_t ftx; + + DPRINTFN(1, "start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_2_0; + sc->sc_bus.methods = &musbotg_bus_methods; + + mtx_lock(&sc->sc_bus.mtx); + + /* turn on clocks */ + + if (sc->sc_clocks_on) { + (sc->sc_clocks_on) (sc->sc_clocks_arg); + } + /* wait a little for things to stabilise */ + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + + /* disable all interrupts */ + + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); + + /* disable pullup */ + + musbotg_pull_common(sc, 0); + + /* wait a little bit (10ms) */ + usb2_pause_mtx(&sc->sc_bus.mtx, 10); + + /* disable double packet buffering */ + MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, 0xFFFF); + MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, 0xFFFF); + + /* enable HighSpeed and ISO Update flags */ + + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, + MUSB2_MASK_HSENAB | MUSB2_MASK_ISOUPD); + + /* clear Session bit, if set */ + + temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); + temp &= ~MUSB2_MASK_SESS; + MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp); + + DPRINTF("DEVCTL=0x%02x\n", temp); + + /* disable testmode */ + + MUSB2_WRITE_1(sc, MUSB2_REG_TESTMODE, 0); + + /* set default value */ + + MUSB2_WRITE_1(sc, MUSB2_REG_MISC, 0); + + /* select endpoint index 0 */ + + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + /* read out number of endpoints */ + + nrx = + (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16); + + ntx = + (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16); + + /* these numbers exclude the control endpoint */ + + DPRINTFN(2, "RX/TX endpoints: %u/%u\n", nrx, ntx); + + sc->sc_ep_max = (nrx > ntx) ? nrx : ntx; + if (sc->sc_ep_max == 0) { + DPRINTFN(2, "ERROR: Looks like the clocks are off!\n"); + } + /* read out configuration data */ + + sc->sc_conf_data = MUSB2_READ_1(sc, MUSB2_REG_CONFDATA); + + DPRINTFN(2, "Config Data: 0x%02x\n", + sc->sc_conf_data); + + DPRINTFN(2, "HW version: 0x%04x\n", + MUSB2_READ_1(sc, MUSB2_REG_HWVERS)); + + /* initialise endpoint profiles */ + + for (temp = 1; temp <= sc->sc_ep_max; temp++) { + pf = sc->sc_hw_ep_profile + temp; + + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, temp); + + fsize = MUSB2_READ_1(sc, MUSB2_REG_FSIZE); + frx = (fsize & MUSB2_MASK_RX_FSIZE) / 16;; + ftx = (fsize & MUSB2_MASK_TX_FSIZE); + + DPRINTF("Endpoint %u FIFO size: IN=%u, OUT=%u\n", + temp, pf->max_in_frame_size, + pf->max_out_frame_size); + + if (frx && ftx && (temp <= nrx) && (temp <= ntx)) { + pf->max_in_frame_size = 1 << ftx; + pf->max_out_frame_size = 1 << frx; + pf->is_simplex = 0; /* duplex */ + pf->support_multi_buffer = 1; + pf->support_bulk = 1; + pf->support_interrupt = 1; + pf->support_isochronous = 1; + pf->support_in = 1; + pf->support_out = 1; + } else if (frx && (temp <= nrx)) { + pf->max_out_frame_size = 1 << frx; + pf->is_simplex = 1; /* simplex */ + pf->support_multi_buffer = 1; + pf->support_bulk = 1; + pf->support_interrupt = 1; + pf->support_isochronous = 1; + pf->support_out = 1; + } else if (ftx && (temp <= ntx)) { + pf->max_in_frame_size = 1 << ftx; + pf->is_simplex = 1; /* simplex */ + pf->support_multi_buffer = 1; + pf->support_bulk = 1; + pf->support_interrupt = 1; + pf->support_isochronous = 1; + pf->support_in = 1; + } + } + + /* turn on default interrupts */ + + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, + MUSB2_MASK_IRESET); + + musbotg_clocks_off(sc); + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + + musbotg_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +musbotg_uninit(struct musbotg_softc *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + /* disable all interrupts */ + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + musbotg_pull_down(sc); + musbotg_clocks_off(sc); + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +void +musbotg_suspend(struct musbotg_softc *sc) +{ + return; +} + +void +musbotg_resume(struct musbotg_softc *sc) +{ + return; +} + +static void +musbotg_do_poll(struct usb2_bus *bus) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + musbotg_interrupt_poll(sc); + musbotg_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/*------------------------------------------------------------------------* + * musbotg bulk support + *------------------------------------------------------------------------*/ +static void +musbotg_device_bulk_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_device_bulk_close(struct usb2_xfer *xfer) +{ + musbotg_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +musbotg_device_bulk_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_device_bulk_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + musbotg_setup_standard_chain(xfer); + musbotg_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods musbotg_device_bulk_methods = +{ + .open = musbotg_device_bulk_open, + .close = musbotg_device_bulk_close, + .enter = musbotg_device_bulk_enter, + .start = musbotg_device_bulk_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * musbotg control support + *------------------------------------------------------------------------*/ +static void +musbotg_device_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_device_ctrl_close(struct usb2_xfer *xfer) +{ + musbotg_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +musbotg_device_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_device_ctrl_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + musbotg_setup_standard_chain(xfer); + musbotg_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods musbotg_device_ctrl_methods = +{ + .open = musbotg_device_ctrl_open, + .close = musbotg_device_ctrl_close, + .enter = musbotg_device_ctrl_enter, + .start = musbotg_device_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * musbotg interrupt support + *------------------------------------------------------------------------*/ +static void +musbotg_device_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_device_intr_close(struct usb2_xfer *xfer) +{ + musbotg_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +musbotg_device_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_device_intr_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + musbotg_setup_standard_chain(xfer); + musbotg_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods musbotg_device_intr_methods = +{ + .open = musbotg_device_intr_open, + .close = musbotg_device_intr_close, + .enter = musbotg_device_intr_enter, + .start = musbotg_device_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * musbotg full speed isochronous support + *------------------------------------------------------------------------*/ +static void +musbotg_device_isoc_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_device_isoc_close(struct usb2_xfer *xfer) +{ + musbotg_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +musbotg_device_isoc_enter(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + uint32_t temp; + uint32_t nframes; + uint32_t fs_frames; + + DPRINTFN(5, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->pipe->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = MUSB2_READ_2(sc, MUSB2_REG_FRAME); + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (nframes - xfer->pipe->isoc_next) & MUSB2_MASK_FRAME; + + if (usb2_get_speed(xfer->udev) == USB_SPEED_HIGH) { + fs_frames = (xfer->nframes + 7) / 8; + } else { + fs_frames = xfer->nframes; + } + + if ((xfer->pipe->is_synced == 0) || + (temp < fs_frames)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->pipe->isoc_next = (nframes + 3) & MUSB2_MASK_FRAME; + xfer->pipe->is_synced = 1; + DPRINTFN(2, "start next=%d\n", xfer->pipe->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->pipe->isoc_next - nframes) & MUSB2_MASK_FRAME; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp + + fs_frames; + + /* compute frame number for next insertion */ + xfer->pipe->isoc_next += fs_frames; + + /* setup TDs */ + musbotg_setup_standard_chain(xfer); + return; +} + +static void +musbotg_device_isoc_start(struct usb2_xfer *xfer) +{ + /* start TD chain */ + musbotg_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods musbotg_device_isoc_methods = +{ + .open = musbotg_device_isoc_open, + .close = musbotg_device_isoc_close, + .enter = musbotg_device_isoc_enter, + .start = musbotg_device_isoc_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * musbotg root control support + *------------------------------------------------------------------------* + * simulate a hardware HUB by handling + * all the necessary requests + *------------------------------------------------------------------------*/ +static void +musbotg_root_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_root_ctrl_close(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + musbotg_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* + * USB descriptors for the virtual Root HUB: + */ + +static const struct usb2_device_descriptor musbotg_devd = { + .bLength = sizeof(struct usb2_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct usb2_device_qualifier musbotg_odevd = { + .bLength = sizeof(struct usb2_device_qualifier), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, +}; + +static const struct musbotg_config_desc musbotg_confd = { + .confd = { + .bLength = sizeof(struct usb2_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(musbotg_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb2_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = UIPROTO_HSHUBSTT, + }, + + .endpd = { + .bLength = sizeof(struct usb2_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | MUSBOTG_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb2_hub_descriptor_min musbotg_hubd = { + .bDescLength = sizeof(musbotg_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 16, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'M', 0, 'e', 0, 'n', 0, 't', 0, 'o', 0, 'r', 0, ' ', 0, \ + 'G', 0, 'r', 0, 'a', 0, 'p', 0, 'h', 0, 'i', 0, 'c', 0, 's', 0 + +#define STRING_PRODUCT \ + 'O', 0, 'T', 0, 'G', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, musbotg_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, musbotg_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, musbotg_product); + +static void +musbotg_root_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_root_ctrl_start(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command( + &sc->sc_config_td, NULL, &musbotg_root_ctrl_task, 0, 0); + + return; +} + +static void +musbotg_root_ctrl_task(struct musbotg_softc *sc, + struct musbotg_config_copy *cc, uint16_t refcount) +{ + musbotg_root_ctrl_poll(sc); + return; +} + +static void +musbotg_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + uint16_t value; + uint16_t index; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_SETUP) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + musbotg_device_done(xfer, std->err); + } + goto done; + } + /* buffer reset */ + std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0); + std->len = 0; + + value = UGETW(std->req.wValue); + index = UGETW(std->req.wIndex); + + use_polling = mtx_owned(xfer->priv_mtx) ? 1 : 0; + + /* demultiplex the control request */ + + switch (std->req.bmRequestType) { + case UT_READ_DEVICE: + switch (std->req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (std->req.bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(std->req.wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(std->req.wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (std->req.bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (std->req.bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (std->req.bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (std->req.bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (std->req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + std->len = sizeof(musbotg_devd); + std->ptr = USB_ADD_BYTES(&musbotg_devd, 0); + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + std->len = sizeof(musbotg_confd); + std->ptr = USB_ADD_BYTES(&musbotg_confd, 0); + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + std->len = sizeof(musbotg_langtab); + std->ptr = USB_ADD_BYTES(&musbotg_langtab, 0); + goto tr_valid; + + case 1: /* Vendor */ + std->len = sizeof(musbotg_vendor); + std->ptr = USB_ADD_BYTES(&musbotg_vendor, 0); + goto tr_valid; + + case 2: /* Product */ + std->len = sizeof(musbotg_product); + std->ptr = USB_ADD_BYTES(&musbotg_product, 0); + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + std->len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + std->len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + std->len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + std->len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(8, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + musbotg_wakeup_peer(xfer); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + musbotg_pull_down(sc); + musbotg_clocks_off(sc); + break; + case UHF_C_PORT_CONNECTION: + sc->sc_flags.change_connect = 0; + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(8, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(8, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + musbotg_clocks_on(sc); + musbotg_pull_up(sc); + } else { + musbotg_pull_down(sc); + musbotg_clocks_off(sc); + } + + /* Select Device Side Mode */ + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.status_high_speed) { + value |= UPS_HIGH_SPEED; + } + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + /* reset EP0 state */ + sc->sc_ep0_busy = 0; + sc->sc_ep0_cmd = 0; + } + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + std->len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + std->ptr = USB_ADD_BYTES(&musbotg_hubd, 0); + std->len = sizeof(musbotg_hubd); + goto tr_valid; + +tr_stalled: + std->err = USB_ERR_STALLED; +tr_valid: +done: + return; +} + +static void +musbotg_root_ctrl_poll(struct musbotg_softc *sc) +{ + usb2_sw_transfer(&sc->sc_root_ctrl, + &musbotg_root_ctrl_done); + return; +} + +struct usb2_pipe_methods musbotg_root_ctrl_methods = +{ + .open = musbotg_root_ctrl_open, + .close = musbotg_root_ctrl_close, + .enter = musbotg_root_ctrl_enter, + .start = musbotg_root_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 0, +}; + +/*------------------------------------------------------------------------* + * musbotg root interrupt support + *------------------------------------------------------------------------*/ +static void +musbotg_root_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_root_intr_close(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + musbotg_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +musbotg_root_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_root_intr_start(struct usb2_xfer *xfer) +{ + struct musbotg_softc *sc = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +struct usb2_pipe_methods musbotg_root_intr_methods = +{ + .open = musbotg_root_intr_open, + .close = musbotg_root_intr_close, + .enter = musbotg_root_intr_enter, + .start = musbotg_root_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +static void +musbotg_xfer_setup(struct usb2_setup_params *parm) +{ + const struct usb2_hw_ep_profile *pf; + struct musbotg_softc *sc; + struct usb2_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + sc = MUSBOTG_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x400; + parm->hc_max_frame_size = 0x400; + + if ((parm->methods == &musbotg_device_isoc_methods) || + (parm->methods == &musbotg_device_intr_methods)) + parm->hc_max_packet_count = 3; + else + parm->hc_max_packet_count = 1; + + usb2_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if (parm->methods == &musbotg_device_ctrl_methods) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; + + } else if (parm->methods == &musbotg_device_bulk_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &musbotg_device_intr_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &musbotg_device_isoc_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else { + + ntd = 0; + } + + /* + * check if "usb2_transfer_setup_sub" set an error + */ + if (parm->err) { + return; + } + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + if (ntd) { + + ep_no = xfer->endpoint & UE_ADDR; + musbotg_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + } else { + ep_no = 0; + pf = NULL; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct musbotg_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->max_frame_size = xfer->max_frame_size; + td->ep_no = ep_no; + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; + return; +} + +static void +musbotg_xfer_unsetup(struct usb2_xfer *xfer) +{ + return; +} + +static void +musbotg_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc, + struct usb2_pipe *pipe) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); + + DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + pipe, udev->address, + edesc->bEndpointAddress, udev->flags.usb2_mode, + sc->sc_rt_addr); + + if (udev->device_index == sc->sc_rt_addr) { + + if (udev->flags.usb2_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + switch (edesc->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &musbotg_root_ctrl_methods; + break; + case UE_DIR_IN | MUSBOTG_INTR_ENDPT: + pipe->methods = &musbotg_root_intr_methods; + break; + default: + /* do nothing */ + break; + } + } else { + + if (udev->flags.usb2_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if ((udev->speed != USB_SPEED_FULL) && + (udev->speed != USB_SPEED_HIGH)) { + /* not supported */ + return; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + pipe->methods = &musbotg_device_ctrl_methods; + break; + case UE_INTERRUPT: + pipe->methods = &musbotg_device_intr_methods; + break; + case UE_ISOCHRONOUS: + pipe->methods = &musbotg_device_isoc_methods; + break; + case UE_BULK: + pipe->methods = &musbotg_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } + return; +} + +struct usb2_bus_methods musbotg_bus_methods = +{ + .pipe_init = &musbotg_pipe_init, + .xfer_setup = &musbotg_xfer_setup, + .xfer_unsetup = &musbotg_xfer_unsetup, + .do_poll = &musbotg_do_poll, + .get_hw_ep_profile = &musbotg_get_hw_ep_profile, + .set_stall = &musbotg_set_stall, + .clear_stall = &musbotg_clear_stall, + .vbus_interrupt = &musbotg_vbus_interrupt, + .rem_wakeup_set = &musbotg_rem_wakeup_set, +}; diff --git a/sys/dev/usb2/controller/musb2_otg.h b/sys/dev/usb2/controller/musb2_otg.h new file mode 100644 index 000000000000..68193e2ea085 --- /dev/null +++ b/sys/dev/usb2/controller/musb2_otg.h @@ -0,0 +1,403 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * This header file defines the registers of the Mentor Graphics + * USB OnTheGo Inventra chip. + */ + +#ifndef _MUSB2_OTG_H_ +#define _MUSB2_OTG_H_ + +/* Common registers */ + +#define MUSB2_REG_FADDR 0x0000 /* function address register */ +#define MUSB2_MASK_FADDR 0x7F + +#define MUSB2_REG_POWER 0x0001 /* power register */ +#define MUSB2_MASK_SUSPM_ENA 0x01 +#define MUSB2_MASK_SUSPMODE 0x02 +#define MUSB2_MASK_RESUME 0x04 +#define MUSB2_MASK_RESET 0x08 +#define MUSB2_MASK_HSMODE 0x10 +#define MUSB2_MASK_HSENAB 0x20 +#define MUSB2_MASK_SOFTC 0x40 +#define MUSB2_MASK_ISOUPD 0x80 + +/* Endpoint interrupt handling */ + +#define MUSB2_REG_INTTX 0x0002 /* transmit interrupt register */ +#define MUSB2_REG_INTRX 0x0004 /* receive interrupt register */ +#define MUSB2_REG_INTTXE 0x0006 /* transmit interrupt enable register */ +#define MUSB2_REG_INTRXE 0x0008 /* receive interrupt enable register */ +#define MUSB2_MASK_EPINT(epn) (1 << (epn)) /* epn = [0..15] */ + +/* Common interrupt handling */ + +#define MUSB2_REG_INTUSB 0x000A /* USB interrupt register */ +#define MUSB2_MASK_ISUSP 0x01 +#define MUSB2_MASK_IRESUME 0x02 +#define MUSB2_MASK_IRESET 0x04 +#define MUSB2_MASK_IBABBLE 0x04 +#define MUSB2_MASK_ISOF 0x08 +#define MUSB2_MASK_ICONN 0x10 +#define MUSB2_MASK_IDISC 0x20 +#define MUSB2_MASK_ISESSRQ 0x40 +#define MUSB2_MASK_IVBUSERR 0x80 + +#define MUSB2_REG_INTUSBE 0x000B /* USB interrupt enable register */ +#define MUSB2_REG_FRAME 0x000C /* USB frame register */ +#define MUSB2_MASK_FRAME 0x3FF /* 0..1023 */ + +#define MUSB2_REG_EPINDEX 0x000E /* endpoint index register */ +#define MUSB2_MASK_EPINDEX 0x0F + +#define MUSB2_REG_TESTMODE 0x000F /* test mode register */ +#define MUSB2_MASK_TSE0_NAK 0x01 +#define MUSB2_MASK_TJ 0x02 +#define MUSB2_MASK_TK 0x04 +#define MUSB2_MASK_TPACKET 0x08 +#define MUSB2_MASK_TFORCE_HS 0x10 +#define MUSB2_MASK_TFORCE_LS 0x20 +#define MUSB2_MASK_TFIFO_ACC 0x40 +#define MUSB2_MASK_TFORCE_HC 0x80 + +#define MUSB2_REG_INDEXED_CSR 0x0010 /* EP control status register offset */ + +#define MUSB2_REG_TXMAXP (0x0000 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_REG_RXMAXP (0x0004 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_PKTSIZE 0x03FF /* in bytes, should be even */ +#define MUSB2_MASK_PKTMULT 0xFC00 /* HS packet multiplier: 0..2 */ + +#define MUSB2_REG_TXCSRL (0x0002 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_CSRL_TXPKTRDY 0x01 +#define MUSB2_MASK_CSRL_TXFIFONEMPTY 0x02 +#define MUSB2_MASK_CSRL_TXUNDERRUN 0x04 /* Device Mode */ +#define MUSB2_MASK_CSRL_TXERROR 0x04 /* Host Mode */ +#define MUSB2_MASK_CSRL_TXFFLUSH 0x08 +#define MUSB2_MASK_CSRL_TXSENDSTALL 0x10/* Device Mode */ +#define MUSB2_MASK_CSRL_TXSETUPPKT 0x10 /* Host Mode */ +#define MUSB2_MASK_CSRL_TXSENTSTALL 0x20/* Device Mode */ +#define MUSB2_MASK_CSRL_TXSTALLED 0x20 /* Host Mode */ +#define MUSB2_MASK_CSRL_TXDT_CLR 0x40 +#define MUSB2_MASK_CSRL_TXINCOMP 0x80 + +/* Device Side Mode */ +#define MUSB2_MASK_CSR0L_RXPKTRDY 0x01 +#define MUSB2_MASK_CSR0L_TXPKTRDY 0x02 +#define MUSB2_MASK_CSR0L_SENTSTALL 0x04 +#define MUSB2_MASK_CSR0L_DATAEND 0x08 +#define MUSB2_MASK_CSR0L_SETUPEND 0x10 +#define MUSB2_MASK_CSR0L_SENDSTALL 0x20 +#define MUSB2_MASK_CSR0L_RXPKTRDY_CLR 0x40 +#define MUSB2_MASK_CSR0L_SETUPEND_CLR 0x80 + +/* Host Side Mode */ +#define MUSB2_MASK_CSR0L_RXSTALL 0x04 +#define MUSB2_MASK_CSR0L_SETUPPKT 0x08 +#define MUSB2_MASK_CSR0L_ERROR 0x10 +#define MUSB2_MASK_CSR0L_REQPKT 0x20 +#define MUSB2_MASK_CSR0L_STATUSPKT 0x40 +#define MUSB2_MASK_CSR0L_NAKTIMO 0x80 + +#define MUSB2_REG_TXCSRH (0x0003 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_CSRH_TXDT_VAL 0x01 /* Host Mode */ +#define MUSB2_MASK_CSRH_TXDT_WR 0x02 /* Host Mode */ +#define MUSB2_MASK_CSRH_TXDMAREQMODE 0x04 +#define MUSB2_MASK_CSRH_TXDT_SWITCH 0x08 +#define MUSB2_MASK_CSRH_TXDMAREQENA 0x10 +#define MUSB2_MASK_CSRH_RXMODE 0x00 +#define MUSB2_MASK_CSRH_TXMODE 0x20 +#define MUSB2_MASK_CSRH_TXISO 0x40 /* Device Mode */ +#define MUSB2_MASK_CSRH_TXAUTOSET 0x80 + +#define MUSB2_MASK_CSR0H_FFLUSH 0x01 /* Device Side flush FIFO */ +#define MUSB2_MASK_CSR0H_DT 0x02 /* Host Side data toggle */ +#define MUSB2_MASK_CSR0H_DT_SET 0x04 /* Host Side */ +#define MUSB2_MASK_CSR0H_PING_DIS 0x08 /* Host Side */ + +#define MUSB2_REG_RXCSRL (0x0006 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_CSRL_RXPKTRDY 0x01 +#define MUSB2_MASK_CSRL_RXFIFOFULL 0x02 +#define MUSB2_MASK_CSRL_RXOVERRUN 0x04 +#define MUSB2_MASK_CSRL_RXDATAERR 0x08 +#define MUSB2_MASK_CSRL_RXFFLUSH 0x10 +#define MUSB2_MASK_CSRL_RXSENDSTALL 0x20/* Device Mode */ +#define MUSB2_MASK_CSRL_RXREQPKT 0x20 /* Host Mode */ +#define MUSB2_MASK_CSRL_RXSENTSTALL 0x40/* Device Mode */ +#define MUSB2_MASK_CSRL_RXSTALL 0x40 /* Host Mode */ +#define MUSB2_MASK_CSRL_RXDT_CLR 0x80 + +#define MUSB2_REG_RXCSRH (0x0007 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_CSRH_RXINCOMP 0x01 +#define MUSB2_MASK_CSRH_RXDT_VAL 0x02 /* Host Mode */ +#define MUSB2_MASK_CSRH_RXDT_SET 0x04 /* Host Mode */ +#define MUSB2_MASK_CSRH_RXDMAREQMODE 0x08 +#define MUSB2_MASK_CSRH_RXNYET 0x10 +#define MUSB2_MASK_CSRH_RXDMAREQENA 0x20 +#define MUSB2_MASK_CSRH_RXISO 0x40 /* Device Mode */ +#define MUSB2_MASK_CSRH_RXAUTOREQ 0x40 /* Host Mode */ +#define MUSB2_MASK_CSRH_RXAUTOCLEAR 0x80 + +#define MUSB2_REG_RXCOUNT (0x0008 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_RXCOUNT 0xFFFF + +#define MUSB2_REG_TXTI (0x000A + MUSB2_REG_INDEXED_CSR) +#define MUSB2_REG_RXTI (0x000C + MUSB2_REG_INDEXED_CSR) + +/* Host Mode */ +#define MUSB2_MASK_TI_SPEED 0xC0 +#define MUSB2_MASK_TI_SPEED_LO 0xC0 +#define MUSB2_MASK_TI_SPEED_FS 0x80 +#define MUSB2_MASK_TI_SPEED_HS 0x40 +#define MUSB2_MASK_TI_PROTO_CTRL 0x00 +#define MUSB2_MASK_TI_PROTO_ISOC 0x10 +#define MUSB2_MASK_TI_PROTO_BULK 0x20 +#define MUSB2_MASK_TI_PROTO_INTR 0x30 +#define MUSB2_MASK_TI_EP_NUM 0x0F + +#define MUSB2_REG_TXNAKLIMIT (0x000B /* EPN=0 */ + MUSB2_REG_INDEXED_CSR) +#define MUSB2_REG_RXNAKLIMIT (0x000D /* EPN=0 */ + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_NAKLIMIT 0xFF + +#define MUSB2_REG_FSIZE (0x000F + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_RX_FSIZE 0xF0 /* 3..13, 2**n bytes */ +#define MUSB2_MASK_TX_FSIZE 0x0F /* 3..13, 2**n bytes */ + +#define MUSB2_REG_EPFIFO(n) (0x0020 + (4*(n))) + +#define MUSB2_REG_CONFDATA 0x000F /* EPN=0 */ +#define MUSB2_MASK_CD_UTMI_DW 0x01 +#define MUSB2_MASK_CD_SOFTCONE 0x02 +#define MUSB2_MASK_CD_DYNFIFOSZ 0x04 +#define MUSB2_MASK_CD_HBTXE 0x08 +#define MUSB2_MASK_CD_HBRXE 0x10 +#define MUSB2_MASK_CD_BIGEND 0x20 +#define MUSB2_MASK_CD_MPTXE 0x40 +#define MUSB2_MASK_CD_MPRXE 0x80 + +/* Various registers */ + +#define MUSB2_REG_DEVCTL 0x0060 +#define MUSB2_MASK_SESS 0x01 +#define MUSB2_MASK_HOSTREQ 0x02 +#define MUSB2_MASK_HOSTMD 0x04 +#define MUSB2_MASK_VBUS0 0x08 +#define MUSB2_MASK_VBUS1 0x10 +#define MUSB2_MASK_LSDEV 0x20 +#define MUSB2_MASK_FSDEV 0x40 +#define MUSB2_MASK_BDEV 0x80 + +#define MUSB2_REG_MISC 0x0061 +#define MUSB2_MASK_RXEDMA 0x01 +#define MUSB2_MASK_TXEDMA 0x02 + +#define MUSB2_REG_TXFIFOSZ 0x0062 +#define MUSB2_REG_RXFIFOSZ 0x0063 +#define MUSB2_MASK_FIFODB 0x10 /* set if double buffering, r/w */ +#define MUSB2_MASK_FIFOSZ 0x0F +#define MUSB2_VAL_FIFOSZ_8 0 +#define MUSB2_VAL_FIFOSZ_16 1 +#define MUSB2_VAL_FIFOSZ_32 2 +#define MUSB2_VAL_FIFOSZ_64 3 +#define MUSB2_VAL_FIFOSZ_128 4 +#define MUSB2_VAL_FIFOSZ_256 5 +#define MUSB2_VAL_FIFOSZ_512 6 +#define MUSB2_VAL_FIFOSZ_1024 7 +#define MUSB2_VAL_FIFOSZ_2048 8 +#define MUSB2_VAL_FIFOSZ_4096 9 + +#define MUSB2_REG_TXFIFOADD 0x0064 +#define MUSB2_REG_RXFIFOADD 0x0066 +#define MUSB2_MASK_FIFOADD 0xFFF /* unit is 8-bytes */ + +#define MUSB2_REG_VSTATUS 0x0068 +#define MUSB2_REG_VCONTROL 0x0068 +#define MUSB2_REG_HWVERS 0x006C +#define MUSB2_REG_ULPI_BASE 0x0070 + +#define MUSB2_REG_EPINFO 0x0078 +#define MUSB2_MASK_NRXEP 0xF0 +#define MUSB2_MASK_NTXEP 0x0F + +#define MUSB2_REG_RAMINFO 0x0079 +#define MUSB2_REG_LINKINFO 0x007A + +#define MUSB2_REG_VPLEN 0x007B +#define MUSB2_MASK_VPLEN 0xFF + +#define MUSB2_REG_HS_EOF1 0x007C +#define MUSB2_REG_FS_EOF1 0x007D +#define MUSB2_REG_LS_EOF1 0x007E +#define MUSB2_REG_SOFT_RST 0x007F +#define MUSB2_MASK_SRST 0x01 +#define MUSB2_MASK_SRSTX 0x02 + +#define MUSB2_REG_RQPKTCOUNT(n) (0x0300 + (4*(n)) +#define MUSB2_REG_RXDBDIS 0x0340 +#define MUSB2_REG_TXDBDIS 0x0342 +#define MUSB2_MASK_DB(n) (1 << (n)) /* disable double buffer, n = [0..15] */ + +#define MUSB2_REG_CHIRPTO 0x0344 +#define MUSB2_REG_HSRESUM 0x0346 + +/* Host Mode only registers */ + +#define MUSB2_REG_TXFADDR(n) (0x0080 + (8*(n))) +#define MUSB2_REG_TXHADDR(n) (0x0082 + (8*(n))) +#define MUSB2_REG_TXHUBPORT(n) (0x0083 + (8*(n))) +#define MUSB2_REG_RXFADDR(n) (0x0084 + (8*(n))) +#define MUSB2_REG_RXHADDR(n) (0x0086 + (8*(n))) +#define MUSB2_REG_RXHPORT(n) (0x0087 + (8*(n))) + +#define MUSB2_EP_MAX 16 /* maximum number of endpoints */ + +#define MUSB2_READ_2(sc, reg) \ + bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define MUSB2_WRITE_2(sc, reg, data) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +#define MUSB2_READ_1(sc, reg) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define MUSB2_WRITE_1(sc, reg, data) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +struct musbotg_td; +struct musbotg_softc; + +typedef uint8_t (musbotg_cmd_t)(struct musbotg_td *td); + +struct musbotg_dma { + struct musbotg_softc *sc; + uint32_t dma_chan; + uint8_t busy:1; + uint8_t complete:1; + uint8_t error:1; +}; + +struct musbotg_td { + struct musbotg_td *obj_next; + musbotg_cmd_t *func; + struct usb2_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint16_t max_frame_size; /* packet_size * mult */ + uint8_t ep_no; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t support_multi_buffer:1; + uint8_t did_stall:1; + uint8_t dma_enabled:1; +}; + +struct musbotg_std_temp { + musbotg_cmd_t *func; + struct usb2_page_cache *pc; + struct musbotg_td *td; + struct musbotg_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; +}; + +struct musbotg_config_desc { + struct usb2_config_descriptor confd; + struct usb2_interface_descriptor ifcd; + struct usb2_endpoint_descriptor endpd; +} __packed; + +union musbotg_hub_temp { + uWord wValue; + struct usb2_port_status ps; +}; + +struct musbotg_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t status_high_speed:1; /* set if High Speed is selected */ + uint8_t remote_wakeup:1; + uint8_t self_powered:1; + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; +}; + +struct musbotg_softc { + struct usb2_bus sc_bus; + union musbotg_hub_temp sc_hub_temp; + struct usb2_sw_transfer sc_root_ctrl; + struct usb2_sw_transfer sc_root_intr; + struct usb2_config_td sc_config_td; + struct usb2_hw_ep_profile sc_hw_ep_profile[16]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + void (*sc_clocks_on) (void *arg); + void (*sc_clocks_off) (void *arg); + void *sc_clocks_arg; + + uint32_t sc_bounce_buf[(1024 * 3) / 4]; /* bounce buffer */ + + uint8_t sc_ep_max; /* maximum number of RX and TX + * endpoints supported */ + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root HUB config */ + uint8_t sc_ep0_busy; /* set if ep0 is busy */ + uint8_t sc_ep0_cmd; /* pending commands */ + uint8_t sc_conf_data; /* copy of hardware register */ + + uint8_t sc_hub_idata[1]; + + struct musbotg_flags sc_flags; +}; + +/* prototypes */ + +usb2_error_t musbotg_init(struct musbotg_softc *sc); +void musbotg_uninit(struct musbotg_softc *sc); +void musbotg_suspend(struct musbotg_softc *sc); +void musbotg_resume(struct musbotg_softc *sc); +void musbotg_interrupt(struct musbotg_softc *sc); + +#endif /* _MUSB2_OTG_H_ */ diff --git a/sys/dev/usb2/controller/musb2_otg_atmelarm.c b/sys/dev/usb2/controller/musb2_otg_atmelarm.c new file mode 100644 index 000000000000..58ee13496448 --- /dev/null +++ b/sys/dev/usb2/controller/musb2_otg_atmelarm.c @@ -0,0 +1,256 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static device_probe_t musbotg_probe; +static device_attach_t musbotg_attach; +static device_detach_t musbotg_detach; +static device_shutdown_t musbotg_shutdown; + +struct musbotg_super_softc { + struct musbotg_softc sc_otg; /* must be first */ +}; + +static void +musbotg_vbus_interrupt(struct musbotg_super_softc *sc) +{ + uint8_t vbus_val = 1; /* fake VBUS on - TODO */ + + /* just forward it */ + + (sc->sc_otg.sc_bus.methods->vbus_interrupt) + (&sc->sc_otg.sc_bus, vbus_val); + return; +} + +static void +musbotg_clocks_on(void *arg) +{ +#if 0 + struct musbotg_super_softc *sc = arg; + +#endif + + return; +} + +static void +musbotg_clocks_off(void *arg) +{ +#if 0 + struct musbotg_super_softc *sc = arg; + +#endif + + return; +} + +static int +musbotg_probe(device_t dev) +{ + device_set_desc(dev, "MUSB OTG integrated USB controller"); + return (0); +} + +static int +musbotg_attach(device_t dev) +{ + struct musbotg_super_softc *sc = device_get_softc(dev); + int err; + int rid; + + if (sc == NULL) { + return (ENXIO); + } + /* setup MUSB OTG USB controller interface softc */ + + sc->sc_otg.sc_clocks_on = &musbotg_clocks_on; + sc->sc_otg.sc_clocks_off = &musbotg_clocks_off; + sc->sc_otg.sc_clocks_arg = sc; + + /* get all DMA memory */ + + if (usb2_bus_mem_alloc_all(&sc->sc_otg.sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_otg.sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + + if (!(sc->sc_otg.sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_size = rman_get_size(sc->sc_otg.sc_io_res); + + rid = 0; + sc->sc_otg.sc_irq_res = + bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (!(sc->sc_otg.sc_irq_res)) { + goto error; + } + sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_otg.sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus); + + err = usb2_config_td_setup(&sc->sc_otg.sc_config_td, sc, + &sc->sc_otg.sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)musbotg_interrupt, sc, &sc->sc_otg.sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)musbotg_interrupt, sc, &sc->sc_otg.sc_intr_hdl); +#endif + if (err) { + sc->sc_otg.sc_intr_hdl = NULL; + goto error; + } + err = musbotg_init(&sc->sc_otg); + if (!err) { + err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev); + } + if (err) { + goto error; + } else { + /* poll VBUS one time */ + musbotg_vbus_interrupt(sc); + } + return (0); + +error: + musbotg_detach(dev); + return (ENXIO); +} + +static int +musbotg_detach(device_t dev) +{ + struct musbotg_super_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_otg.sc_bus.bdev) { + bdev = sc->sc_otg.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(dev); + + if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) { + /* + * only call musbotg_uninit() after musbotg_init() + */ + musbotg_uninit(&sc->sc_otg); + + err = bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, + sc->sc_otg.sc_intr_hdl); + sc->sc_otg.sc_intr_hdl = NULL; + } + /* free IRQ channel, if any */ + if (sc->sc_otg.sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_otg.sc_irq_res); + sc->sc_otg.sc_irq_res = NULL; + } + /* free memory resource, if any */ + if (sc->sc_otg.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, 0, + sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_res = NULL; + } + usb2_config_td_unsetup(&sc->sc_otg.sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL); + + return (0); +} + +static int +musbotg_shutdown(device_t dev) +{ + struct musbotg_super_softc *sc = device_get_softc(dev); + int err; + + err = bus_generic_shutdown(dev); + if (err) + return (err); + + musbotg_uninit(&sc->sc_otg); + + return (0); +} + +static device_method_t musbotg_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, musbotg_probe), + DEVMETHOD(device_attach, musbotg_attach), + DEVMETHOD(device_detach, musbotg_detach), + DEVMETHOD(device_shutdown, musbotg_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t musbotg_driver = { + "musbotg", + musbotg_methods, + sizeof(struct musbotg_super_softc), +}; + +static devclass_t musbotg_devclass; + +DRIVER_MODULE(musbotg, atmelarm, musbotg_driver, musbotg_devclass, 0, 0); +MODULE_DEPEND(musbotg, usb2_controller, 1, 1, 1); +MODULE_DEPEND(musbotg, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/ohci2.c b/sys/dev/usb2/controller/ohci2.c new file mode 100644 index 000000000000..54133ba828ed --- /dev/null +++ b/sys/dev/usb2/controller/ohci2.c @@ -0,0 +1,2802 @@ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * USB Open Host Controller driver. + * + * OHCI spec: http://www.compaq.com/productinfo/development/openhci.html + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR ohcidebug +#define usb2_config_td_cc ohci_config_copy +#define usb2_config_td_softc ohci_softc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define OHCI_BUS2SC(bus) ((ohci_softc_t *)(((uint8_t *)(bus)) - \ + USB_P2U(&(((ohci_softc_t *)0)->sc_bus)))) + +#if USB_DEBUG +static int ohcidebug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci"); +SYSCTL_INT(_hw_usb2_ohci, OID_AUTO, debug, CTLFLAG_RW, + &ohcidebug, 0, "ohci debug level"); +static void ohci_dumpregs(ohci_softc_t *); +static void ohci_dump_tds(ohci_td_t *); +static uint8_t ohci_dump_td(ohci_td_t *); +static void ohci_dump_ed(ohci_ed_t *); +static uint8_t ohci_dump_itd(ohci_itd_t *); +static void ohci_dump_itds(ohci_itd_t *); + +#endif + +#define OBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \ + BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) +#define OWRITE1(sc, r, x) \ + do { OBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) +#define OWRITE2(sc, r, x) \ + do { OBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) +#define OWRITE4(sc, r, x) \ + do { OBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) +#define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) +#define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) +#define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) + +#define OHCI_INTR_ENDPT 1 + +extern struct usb2_bus_methods ohci_bus_methods; +extern struct usb2_pipe_methods ohci_device_bulk_methods; +extern struct usb2_pipe_methods ohci_device_ctrl_methods; +extern struct usb2_pipe_methods ohci_device_intr_methods; +extern struct usb2_pipe_methods ohci_device_isoc_methods; +extern struct usb2_pipe_methods ohci_root_ctrl_methods; +extern struct usb2_pipe_methods ohci_root_intr_methods; + +static usb2_config_td_command_t ohci_root_ctrl_task; +static void ohci_root_ctrl_poll(struct ohci_softc *sc); +static void ohci_do_poll(struct usb2_bus *bus); +static void ohci_device_done(struct usb2_xfer *xfer, usb2_error_t error); + +static usb2_sw_transfer_func_t ohci_root_intr_done; +static usb2_sw_transfer_func_t ohci_root_ctrl_done; +static void ohci_timeout(void *arg); +static uint8_t ohci_check_transfer(struct usb2_xfer *xfer); + +struct ohci_std_temp { + struct usb2_page_cache *pc; + ohci_td_t *td; + ohci_td_t *td_next; + uint32_t average; + uint32_t td_flags; + uint32_t len; + uint16_t max_frame_size; + uint8_t shortpkt; + uint8_t setup_alt_next; + uint8_t short_frames_ok; +}; + +static struct ohci_hcca * +ohci_get_hcca(ohci_softc_t *sc) +{ + usb2_pc_cpu_invalidate(&sc->sc_hw.hcca_pc); + return (sc->sc_hcca_p); +} + +void +ohci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb) +{ + struct ohci_softc *sc = OHCI_BUS2SC(bus); + uint32_t i; + + cb(bus, &sc->sc_hw.hcca_pc, &sc->sc_hw.hcca_pg, + sizeof(ohci_hcca_t), OHCI_HCCA_ALIGN); + + cb(bus, &sc->sc_hw.ctrl_start_pc, &sc->sc_hw.ctrl_start_pg, + sizeof(ohci_ed_t), OHCI_ED_ALIGN); + + cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg, + sizeof(ohci_ed_t), OHCI_ED_ALIGN); + + cb(bus, &sc->sc_hw.isoc_start_pc, &sc->sc_hw.isoc_start_pg, + sizeof(ohci_ed_t), OHCI_ED_ALIGN); + + for (i = 0; i != OHCI_NO_EDS; i++) { + cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i, + sizeof(ohci_ed_t), OHCI_ED_ALIGN); + } + return; +} + +static usb2_error_t +ohci_controller_init(ohci_softc_t *sc) +{ + struct usb2_page_search buf_res; + uint32_t i; + uint32_t ctl; + uint32_t ival; + uint32_t hcr; + uint32_t fm; + uint32_t per; + uint32_t desca; + + /* Determine in what context we are running. */ + ctl = OREAD4(sc, OHCI_CONTROL); + if (ctl & OHCI_IR) { + /* SMM active, request change */ + DPRINTF("SMM active, request owner change\n"); + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_OCR); + for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) { + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + ctl = OREAD4(sc, OHCI_CONTROL); + } + if (ctl & OHCI_IR) { + device_printf(sc->sc_bus.bdev, + "SMM does not respond, resetting\n"); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + goto reset; + } + } else { + DPRINTF("cold started\n"); +reset: + /* controller was cold started */ + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_BUS_RESET_DELAY); + } + + /* + * This reset should not be necessary according to the OHCI spec, but + * without it some controllers do not start. + */ + DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev)); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_BUS_RESET_DELAY); + + /* we now own the host controller and the bus has been reset */ + ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL)); + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */ + /* nominal time for a reset is 10 us */ + for (i = 0; i < 10; i++) { + DELAY(10); + hcr = OREAD4(sc, OHCI_COMMAND_STATUS) & OHCI_HCR; + if (!hcr) { + break; + } + } + if (hcr) { + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + return (USB_ERR_IOERROR); + } +#if USB_DEBUG + if (ohcidebug > 15) { + ohci_dumpregs(sc); + } +#endif + + /* The controller is now in SUSPEND state, we have 2ms to finish. */ + + /* set up HC registers */ + usb2_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res); + OWRITE4(sc, OHCI_HCCA, buf_res.physaddr); + + usb2_get_page(&sc->sc_hw.ctrl_start_pc, 0, &buf_res); + OWRITE4(sc, OHCI_CONTROL_HEAD_ED, buf_res.physaddr); + + usb2_get_page(&sc->sc_hw.bulk_start_pc, 0, &buf_res); + OWRITE4(sc, OHCI_BULK_HEAD_ED, buf_res.physaddr); + + /* disable all interrupts and then switch on all desired interrupts */ + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_eintrs | OHCI_MIE); + /* switch on desired functional features */ + ctl = OREAD4(sc, OHCI_CONTROL); + ctl &= ~(OHCI_CBSR_MASK | OHCI_LES | OHCI_HCFS_MASK | OHCI_IR); + ctl |= OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE | + OHCI_RATIO_1_4 | OHCI_HCFS_OPERATIONAL; + /* And finally start it! */ + OWRITE4(sc, OHCI_CONTROL, ctl); + + /* + * The controller is now OPERATIONAL. Set a some final + * registers that should be set earlier, but that the + * controller ignores when in the SUSPEND state. + */ + fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT; + fm |= OHCI_FSMPS(ival) | ival; + OWRITE4(sc, OHCI_FM_INTERVAL, fm); + per = OHCI_PERIODIC(ival); /* 90% periodic */ + OWRITE4(sc, OHCI_PERIODIC_START, per); + + /* Fiddle the No OverCurrent Protection bit to avoid chip bug. */ + desca = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); + OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca | OHCI_NOCP); + OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC); /* Enable port power */ + usb2_pause_mtx(&sc->sc_bus.mtx, + OHCI_ENABLE_POWER_DELAY); + OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca); + + /* + * The AMD756 requires a delay before re-reading the register, + * otherwise it will occasionally report 0 ports. + */ + sc->sc_noport = 0; + for (i = 0; (i < 10) && (sc->sc_noport == 0); i++) { + usb2_pause_mtx(&sc->sc_bus.mtx, + OHCI_READ_DESC_DELAY); + sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A)); + } + +#if USB_DEBUG + if (ohcidebug > 5) { + ohci_dumpregs(sc); + } +#endif + return (USB_ERR_NORMAL_COMPLETION); +} + +static struct ohci_ed * +ohci_init_ed(struct usb2_page_cache *pc) +{ + struct usb2_page_search buf_res; + struct ohci_ed *ed; + + usb2_get_page(pc, 0, &buf_res); + + ed = buf_res.buffer; + + ed->ed_self = htole32(buf_res.physaddr); + ed->ed_flags = htole32(OHCI_ED_SKIP); + ed->page_cache = pc; + + return (ed); +} + +usb2_error_t +ohci_init(ohci_softc_t *sc) +{ + struct usb2_page_search buf_res; + uint16_t i; + uint16_t bit; + uint16_t x; + uint16_t y; + + mtx_lock(&sc->sc_bus.mtx); + + DPRINTF("start\n"); + + sc->sc_eintrs = OHCI_NORMAL_INTRS; + + /* + * Setup all ED's + */ + + sc->sc_ctrl_p_last = + ohci_init_ed(&sc->sc_hw.ctrl_start_pc); + + sc->sc_bulk_p_last = + ohci_init_ed(&sc->sc_hw.bulk_start_pc); + + sc->sc_isoc_p_last = + ohci_init_ed(&sc->sc_hw.isoc_start_pc); + + for (i = 0; i != OHCI_NO_EDS; i++) { + sc->sc_intr_p_last[i] = + ohci_init_ed(sc->sc_hw.intr_start_pc + i); + } + + /* + * the QHs are arranged to give poll intervals that are + * powers of 2 times 1ms + */ + bit = OHCI_NO_EDS / 2; + while (bit) { + x = bit; + while (x & bit) { + ohci_ed_t *ed_x; + ohci_ed_t *ed_y; + + y = (x ^ bit) | (bit / 2); + + /* + * the next QH has half the poll interval + */ + ed_x = sc->sc_intr_p_last[x]; + ed_y = sc->sc_intr_p_last[y]; + + ed_x->next = NULL; + ed_x->ed_next = ed_y->ed_self; + + x++; + } + bit >>= 1; + } + + if (1) { + + ohci_ed_t *ed_int; + ohci_ed_t *ed_isc; + + ed_int = sc->sc_intr_p_last[0]; + ed_isc = sc->sc_isoc_p_last; + + /* the last (1ms) QH */ + ed_int->next = ed_isc; + ed_int->ed_next = ed_isc->ed_self; + } + usb2_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res); + + sc->sc_hcca_p = buf_res.buffer; + + /* + * Fill HCCA interrupt table. The bit reversal is to get + * the tree set up properly to spread the interrupts. + */ + for (i = 0; i != OHCI_NO_INTRS; i++) { + sc->sc_hcca_p->hcca_interrupt_table[i] = + sc->sc_intr_p_last[i | (OHCI_NO_EDS / 2)]->ed_self; + } + /* flush all cache into memory */ + + usb2_bus_mem_flush_all(&sc->sc_bus, &ohci_iterate_hw_softc); + + /* set up the bus struct */ + sc->sc_bus.methods = &ohci_bus_methods; + + usb2_callout_init_mtx(&sc->sc_tmo_rhsc, &sc->sc_bus.mtx, + CALLOUT_RETURNUNLOCKED); + +#if USB_DEBUG + if (ohcidebug > 15) { + for (i = 0; i != OHCI_NO_EDS; i++) { + printf("ed#%d ", i); + ohci_dump_ed(sc->sc_intr_p_last[i]); + } + printf("iso "); + ohci_dump_ed(sc->sc_isoc_p_last); + } +#endif + + sc->sc_bus.usbrev = USB_REV_1_0; + + if (ohci_controller_init(sc)) { + mtx_unlock(&sc->sc_bus.mtx); + return (USB_ERR_INVAL); + } else { + mtx_unlock(&sc->sc_bus.mtx); + /* catch any lost interrupts */ + ohci_do_poll(&sc->sc_bus); + return (USB_ERR_NORMAL_COMPLETION); + } +} + +/* + * shut down the controller when the system is going down + */ +void +ohci_detach(struct ohci_softc *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + usb2_callout_stop(&sc->sc_tmo_rhsc); + + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + + /* XXX let stray task complete */ + usb2_pause_mtx(&sc->sc_bus.mtx, 50); + + mtx_unlock(&sc->sc_bus.mtx); + + usb2_callout_drain(&sc->sc_tmo_rhsc); + + return; +} + +/* NOTE: suspend/resume is called from + * interrupt context and cannot sleep! + */ +void +ohci_suspend(ohci_softc_t *sc) +{ + uint32_t ctl; + + mtx_lock(&sc->sc_bus.mtx); + +#if USB_DEBUG + DPRINTF("\n"); + if (ohcidebug > 2) { + ohci_dumpregs(sc); + } +#endif + + ctl = OREAD4(sc, OHCI_CONTROL) & ~OHCI_HCFS_MASK; + if (sc->sc_control == 0) { + /* + * Preserve register values, in case that APM BIOS + * does not recover them. + */ + sc->sc_control = ctl; + sc->sc_intre = OREAD4(sc, OHCI_INTERRUPT_ENABLE); + } + ctl |= OHCI_HCFS_SUSPEND; + OWRITE4(sc, OHCI_CONTROL, ctl); + + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_RESUME_WAIT); + + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +void +ohci_resume(ohci_softc_t *sc) +{ + uint32_t ctl; + + mtx_lock(&sc->sc_bus.mtx); + +#if USB_DEBUG + DPRINTF("\n"); + if (ohcidebug > 2) { + ohci_dumpregs(sc); + } +#endif + /* some broken BIOSes never initialize the Controller chip */ + ohci_controller_init(sc); + + if (sc->sc_intre) { + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, + sc->sc_intre & (OHCI_ALL_INTRS | OHCI_MIE)); + } + if (sc->sc_control) + ctl = sc->sc_control; + else + ctl = OREAD4(sc, OHCI_CONTROL); + ctl |= OHCI_HCFS_RESUME; + OWRITE4(sc, OHCI_CONTROL, ctl); + usb2_pause_mtx(&sc->sc_bus.mtx, USB_RESUME_DELAY); + ctl = (ctl & ~OHCI_HCFS_MASK) | OHCI_HCFS_OPERATIONAL; + OWRITE4(sc, OHCI_CONTROL, ctl); + usb2_pause_mtx(&sc->sc_bus.mtx, USB_RESUME_RECOVERY); + sc->sc_control = sc->sc_intre = 0; + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + ohci_do_poll(&sc->sc_bus); + + return; +} + +#if USB_DEBUG +static void +ohci_dumpregs(ohci_softc_t *sc) +{ + struct ohci_hcca *hcca; + + DPRINTF("ohci_dumpregs: rev=0x%08x control=0x%08x command=0x%08x\n", + OREAD4(sc, OHCI_REVISION), + OREAD4(sc, OHCI_CONTROL), + OREAD4(sc, OHCI_COMMAND_STATUS)); + DPRINTF(" intrstat=0x%08x intre=0x%08x intrd=0x%08x\n", + OREAD4(sc, OHCI_INTERRUPT_STATUS), + OREAD4(sc, OHCI_INTERRUPT_ENABLE), + OREAD4(sc, OHCI_INTERRUPT_DISABLE)); + DPRINTF(" hcca=0x%08x percur=0x%08x ctrlhd=0x%08x\n", + OREAD4(sc, OHCI_HCCA), + OREAD4(sc, OHCI_PERIOD_CURRENT_ED), + OREAD4(sc, OHCI_CONTROL_HEAD_ED)); + DPRINTF(" ctrlcur=0x%08x bulkhd=0x%08x bulkcur=0x%08x\n", + OREAD4(sc, OHCI_CONTROL_CURRENT_ED), + OREAD4(sc, OHCI_BULK_HEAD_ED), + OREAD4(sc, OHCI_BULK_CURRENT_ED)); + DPRINTF(" done=0x%08x fmival=0x%08x fmrem=0x%08x\n", + OREAD4(sc, OHCI_DONE_HEAD), + OREAD4(sc, OHCI_FM_INTERVAL), + OREAD4(sc, OHCI_FM_REMAINING)); + DPRINTF(" fmnum=0x%08x perst=0x%08x lsthrs=0x%08x\n", + OREAD4(sc, OHCI_FM_NUMBER), + OREAD4(sc, OHCI_PERIODIC_START), + OREAD4(sc, OHCI_LS_THRESHOLD)); + DPRINTF(" desca=0x%08x descb=0x%08x stat=0x%08x\n", + OREAD4(sc, OHCI_RH_DESCRIPTOR_A), + OREAD4(sc, OHCI_RH_DESCRIPTOR_B), + OREAD4(sc, OHCI_RH_STATUS)); + DPRINTF(" port1=0x%08x port2=0x%08x\n", + OREAD4(sc, OHCI_RH_PORT_STATUS(1)), + OREAD4(sc, OHCI_RH_PORT_STATUS(2))); + + hcca = ohci_get_hcca(sc); + + DPRINTF(" HCCA: frame_number=0x%04x done_head=0x%08x\n", + le32toh(hcca->hcca_frame_number), + le32toh(hcca->hcca_done_head)); + return; +} +static void +ohci_dump_tds(ohci_td_t *std) +{ + for (; std; std = std->obj_next) { + if (ohci_dump_td(std)) { + break; + } + } + return; +} + +static uint8_t +ohci_dump_td(ohci_td_t *std) +{ + uint32_t td_flags; + uint8_t temp; + + usb2_pc_cpu_invalidate(std->page_cache); + + td_flags = le32toh(std->td_flags); + temp = (std->td_next == 0); + + printf("TD(%p) at 0x%08x: %s%s%s%s%s delay=%d ec=%d " + "cc=%d\ncbp=0x%08x next=0x%08x be=0x%08x\n", + std, le32toh(std->td_self), + (td_flags & OHCI_TD_R) ? "-R" : "", + (td_flags & OHCI_TD_OUT) ? "-OUT" : "", + (td_flags & OHCI_TD_IN) ? "-IN" : "", + ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_1) ? "-TOG1" : "", + ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_0) ? "-TOG0" : "", + OHCI_TD_GET_DI(td_flags), + OHCI_TD_GET_EC(td_flags), + OHCI_TD_GET_CC(td_flags), + le32toh(std->td_cbp), + le32toh(std->td_next), + le32toh(std->td_be)); + + return (temp); +} + +static uint8_t +ohci_dump_itd(ohci_itd_t *sitd) +{ + uint32_t itd_flags; + uint16_t i; + uint8_t temp; + + usb2_pc_cpu_invalidate(sitd->page_cache); + + itd_flags = le32toh(sitd->itd_flags); + temp = (sitd->itd_next == 0); + + printf("ITD(%p) at 0x%08x: sf=%d di=%d fc=%d cc=%d\n" + "bp0=0x%08x next=0x%08x be=0x%08x\n", + sitd, le32toh(sitd->itd_self), + OHCI_ITD_GET_SF(itd_flags), + OHCI_ITD_GET_DI(itd_flags), + OHCI_ITD_GET_FC(itd_flags), + OHCI_ITD_GET_CC(itd_flags), + le32toh(sitd->itd_bp0), + le32toh(sitd->itd_next), + le32toh(sitd->itd_be)); + for (i = 0; i < OHCI_ITD_NOFFSET; i++) { + printf("offs[%d]=0x%04x ", i, + (uint32_t)le16toh(sitd->itd_offset[i])); + } + printf("\n"); + + return (temp); +} + +static void +ohci_dump_itds(ohci_itd_t *sitd) +{ + for (; sitd; sitd = sitd->obj_next) { + if (ohci_dump_itd(sitd)) { + break; + } + } + return; +} + +static void +ohci_dump_ed(ohci_ed_t *sed) +{ + uint32_t ed_flags; + uint32_t ed_headp; + + usb2_pc_cpu_invalidate(sed->page_cache); + + ed_flags = le32toh(sed->ed_flags); + ed_headp = le32toh(sed->ed_headp); + + printf("ED(%p) at 0x%08x: addr=%d endpt=%d maxp=%d flags=%s%s%s%s%s\n" + "tailp=0x%08x headflags=%s%s headp=0x%08x nexted=0x%08x\n", + sed, le32toh(sed->ed_self), + OHCI_ED_GET_FA(ed_flags), + OHCI_ED_GET_EN(ed_flags), + OHCI_ED_GET_MAXP(ed_flags), + (ed_flags & OHCI_ED_DIR_OUT) ? "-OUT" : "", + (ed_flags & OHCI_ED_DIR_IN) ? "-IN" : "", + (ed_flags & OHCI_ED_SPEED) ? "-LOWSPEED" : "", + (ed_flags & OHCI_ED_SKIP) ? "-SKIP" : "", + (ed_flags & OHCI_ED_FORMAT_ISO) ? "-ISO" : "", + le32toh(sed->ed_tailp), + (ed_headp & OHCI_HALTED) ? "-HALTED" : "", + (ed_headp & OHCI_TOGGLECARRY) ? "-CARRY" : "", + le32toh(sed->ed_headp), + le32toh(sed->ed_next)); + return; +} + +#endif + +static void +ohci_transfer_intr_enqueue(struct usb2_xfer *xfer) +{ + /* check for early completion */ + if (ohci_check_transfer(xfer)) { + return; + } + /* put transfer on interrupt queue */ + usb2_transfer_enqueue(&xfer->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, &ohci_timeout, xfer->timeout); + } + return; +} + +#define OHCI_APPEND_QH(sed,td_self,last) (last) = _ohci_append_qh(sed,td_self,last) +static ohci_ed_t * +_ohci_append_qh(ohci_ed_t *sed, uint32_t td_self, ohci_ed_t *last) +{ + DPRINTFN(11, "%p to %p\n", sed, last); + + /* (sc->sc_bus.mtx) must be locked */ + + sed->next = last->next; + sed->ed_next = last->ed_next; + sed->ed_tailp = 0; + sed->ed_headp = td_self; + + sed->prev = last; + + usb2_pc_cpu_flush(sed->page_cache); + + /* + * the last->next->prev is never followed: sed->next->prev = sed; + */ + + last->next = sed; + last->ed_next = sed->ed_self; + + usb2_pc_cpu_flush(last->page_cache); + + return (sed); +} + +#define OHCI_REMOVE_QH(sed,last) (last) = _ohci_remove_qh(sed,last) +static ohci_ed_t * +_ohci_remove_qh(ohci_ed_t *sed, ohci_ed_t *last) +{ + DPRINTFN(11, "%p from %p\n", sed, last); + + /* (sc->sc_bus.mtx) must be locked */ + + /* only remove if not removed from a queue */ + if (sed->prev) { + + sed->prev->next = sed->next; + sed->prev->ed_next = sed->ed_next; + + usb2_pc_cpu_flush(sed->prev->page_cache); + + if (sed->next) { + sed->next->prev = sed->prev; + usb2_pc_cpu_flush(sed->next->page_cache); + } + /* + * terminate transfer in case the transferred packet was + * short so that the ED still points at the last used TD + */ + sed->ed_flags |= htole32(OHCI_ED_SKIP); + sed->ed_headp = sed->ed_tailp; + + last = ((last == sed) ? sed->prev : last); + + sed->prev = 0; + + usb2_pc_cpu_flush(sed->page_cache); + } + return (last); +} + +static void +ohci_isoc_done(struct usb2_xfer *xfer) +{ + uint8_t nframes; + uint32_t *plen = xfer->frlengths; + volatile uint16_t *olen; + uint16_t len = 0; + ohci_itd_t *td = xfer->td_transfer_first; + + while (1) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } +#if USB_DEBUG + if (ohcidebug > 5) { + DPRINTF("isoc TD\n"); + ohci_dump_itd(td); + } +#endif + usb2_pc_cpu_invalidate(td->page_cache); + + nframes = td->frames; + olen = &td->itd_offset[0]; + + if (nframes > 8) { + nframes = 8; + } + while (nframes--) { + len = le16toh(*olen); + + if ((len >> 12) == OHCI_CC_NOT_ACCESSED) { + len = 0; + } else { + len &= ((1 << 12) - 1); + } + + if (len > *plen) { + len = 0;/* invalid length */ + } + *plen = len; + plen++; + olen++; + } + + if (((void *)td) == xfer->td_transfer_last) { + break; + } + td = td->obj_next; + } + + xfer->aframes = xfer->nframes; + ohci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + return; +} + +#if USB_DEBUG +static const char *const + ohci_cc_strs[] = +{ + "NO_ERROR", + "CRC", + "BIT_STUFFING", + "DATA_TOGGLE_MISMATCH", + + "STALL", + "DEVICE_NOT_RESPONDING", + "PID_CHECK_FAILURE", + "UNEXPECTED_PID", + + "DATA_OVERRUN", + "DATA_UNDERRUN", + "BUFFER_OVERRUN", + "BUFFER_UNDERRUN", + + "reserved", + "reserved", + "NOT_ACCESSED", + "NOT_ACCESSED" +}; + +#endif + +static usb2_error_t +ohci_non_isoc_done_sub(struct usb2_xfer *xfer) +{ + ohci_td_t *td; + ohci_td_t *td_alt_next; + uint32_t temp; + uint32_t phy_start; + uint32_t phy_end; + uint32_t td_flags; + uint16_t cc; + + td = xfer->td_transfer_cache; + td_alt_next = td->alt_next; + td_flags = 0; + + while (1) { + + usb2_pc_cpu_invalidate(td->page_cache); + phy_start = le32toh(td->td_cbp); + td_flags = le32toh(td->td_flags); + cc = OHCI_TD_GET_CC(td_flags); + + if (phy_start) { + /* + * short transfer - compute the number of remaining + * bytes in the hardware buffer: + */ + phy_end = le32toh(td->td_be); + temp = (OHCI_PAGE(phy_start ^ phy_end) ? + (OHCI_PAGE_SIZE + 1) : 0x0001); + temp += OHCI_PAGE_OFFSET(phy_end); + temp -= OHCI_PAGE_OFFSET(phy_start); + + if (temp > td->len) { + /* guard against corruption */ + cc = OHCI_CC_STALL; + } else if (xfer->aframes != xfer->nframes) { + /* + * subtract remaining length from + * "frlengths[]" + */ + xfer->frlengths[xfer->aframes] -= temp; + } + } + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + td = NULL; + break; + } + /* Check transfer status */ + if (cc) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check for short transfer */ + if (phy_start) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + td = td->alt_next; + } else { + /* the transfer is finished */ + td = NULL; + } + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* this USB frame is complete */ + break; + } + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + DPRINTFN(16, "error cc=%d (%s)\n", + cc, ohci_cc_strs[cc]); + + return ((cc == 0) ? USB_ERR_NORMAL_COMPLETION : + (cc == OHCI_CC_STALL) ? USB_ERR_STALLED : USB_ERR_IOERROR); +} + +static void +ohci_non_isoc_done(struct usb2_xfer *xfer) +{ + usb2_error_t err = 0; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + +#if USB_DEBUG + if (ohcidebug > 10) { + ohci_dump_tds(xfer->td_transfer_first); + } +#endif + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = ohci_non_isoc_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = ohci_non_isoc_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = ohci_non_isoc_done_sub(xfer); + } +done: + ohci_device_done(xfer, err); + return; +} + +/*------------------------------------------------------------------------* + * ohci_check_transfer_sub + *------------------------------------------------------------------------*/ +static void +ohci_check_transfer_sub(struct usb2_xfer *xfer) +{ + ohci_td_t *td; + ohci_ed_t *ed; + uint32_t phy_start; + uint32_t td_flags; + uint32_t td_next; + uint16_t cc; + + td = xfer->td_transfer_cache; + + while (1) { + + usb2_pc_cpu_invalidate(td->page_cache); + phy_start = le32toh(td->td_cbp); + td_flags = le32toh(td->td_flags); + td_next = le32toh(td->td_next); + + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check transfer status */ + cc = OHCI_TD_GET_CC(td_flags); + if (cc) { + /* the transfer is finished */ + td = NULL; + break; + } + /* + * Check if we reached the last packet + * or if there is a short packet: + */ + + if (((td_next & (~0xF)) == OHCI_TD_NEXT_END) || phy_start) { + /* follow alt next */ + td = td->alt_next; + break; + } + td = td->obj_next; + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + if (td) { + + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + ed->ed_headp = td->td_self; + usb2_pc_cpu_flush(ed->page_cache); + + DPRINTFN(13, "xfer=%p following alt next\n", xfer); + } + return; +} + +/*------------------------------------------------------------------------* + * ohci_check_transfer + * + * Return values: + * 0: USB transfer is not finished + * Else: USB transfer is finished + *------------------------------------------------------------------------*/ +static uint8_t +ohci_check_transfer(struct usb2_xfer *xfer) +{ + ohci_ed_t *ed; + uint32_t ed_flags; + uint32_t ed_headp; + uint32_t ed_tailp; + + DPRINTFN(13, "xfer=%p checking transfer\n", xfer); + + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + usb2_pc_cpu_invalidate(ed->page_cache); + ed_flags = le32toh(ed->ed_flags); + ed_headp = le32toh(ed->ed_headp); + ed_tailp = le32toh(ed->ed_tailp); + + if ((ed_flags & OHCI_ED_SKIP) || + (ed_headp & OHCI_HALTED) || + (((ed_headp ^ ed_tailp) & (~0xF)) == 0)) { + if (xfer->pipe->methods == &ohci_device_isoc_methods) { + /* isochronous transfer */ + ohci_isoc_done(xfer); + } else { + if (xfer->flags_int.short_frames_ok) { + ohci_check_transfer_sub(xfer); + if (xfer->td_transfer_cache) { + /* not finished yet */ + return (0); + } + } + /* store data-toggle */ + if (ed_headp & OHCI_TOGGLECARRY) { + xfer->pipe->toggle_next = 1; + } else { + xfer->pipe->toggle_next = 0; + } + + /* non-isochronous transfer */ + ohci_non_isoc_done(xfer); + } + return (1); + } + DPRINTFN(13, "xfer=%p is still active\n", xfer); + return (0); +} + +static void +ohci_rhsc_enable(ohci_softc_t *sc) +{ + DPRINTFN(5, "\n"); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + sc->sc_eintrs |= OHCI_RHSC; + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC); + + /* acknowledge any RHSC interrupt */ + OWRITE4(sc, OHCI_INTERRUPT_STATUS, OHCI_RHSC); + + usb2_sw_transfer(&sc->sc_root_intr, + &ohci_root_intr_done); + + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +static void +ohci_interrupt_poll(ohci_softc_t *sc) +{ + struct usb2_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + /* + * check if transfer is transferred + */ + if (ohci_check_transfer(xfer)) { + /* queue has been modified */ + goto repeat; + } + } + return; +} + +/*------------------------------------------------------------------------* + * ohci_interrupt - OHCI interrupt handler + * + * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, + * hence the interrupt handler will be setup before "sc->sc_bus.bdev" + * is present ! + *------------------------------------------------------------------------*/ +void +ohci_interrupt(ohci_softc_t *sc) +{ + struct ohci_hcca *hcca; + uint32_t status; + uint32_t done; + + mtx_lock(&sc->sc_bus.mtx); + + hcca = ohci_get_hcca(sc); + + DPRINTFN(16, "real interrupt\n"); + +#if USB_DEBUG + if (ohcidebug > 15) { + ohci_dumpregs(sc); + } +#endif + + done = le32toh(hcca->hcca_done_head); + + /* + * The LSb of done is used to inform the HC Driver that an interrupt + * condition exists for both the Done list and for another event + * recorded in HcInterruptStatus. On an interrupt from the HC, the + * HC Driver checks the HccaDoneHead Value. If this value is 0, then + * the interrupt was caused by other than the HccaDoneHead update + * and the HcInterruptStatus register needs to be accessed to + * determine that exact interrupt cause. If HccaDoneHead is nonzero, + * then a Done list update interrupt is indicated and if the LSb of + * done is nonzero, then an additional interrupt event is indicated + * and HcInterruptStatus should be checked to determine its cause. + */ + if (done != 0) { + status = 0; + + if (done & ~OHCI_DONE_INTRS) { + status |= OHCI_WDH; + } + if (done & OHCI_DONE_INTRS) { + status |= OREAD4(sc, OHCI_INTERRUPT_STATUS); + } + hcca->hcca_done_head = 0; + + usb2_pc_cpu_flush(&sc->sc_hw.hcca_pc); + } else { + status = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH; + } + + status &= ~OHCI_MIE; + if (status == 0) { + /* + * nothing to be done (PCI shared + * interrupt) + */ + goto done; + } + OWRITE4(sc, OHCI_INTERRUPT_STATUS, status); /* Acknowledge */ + + status &= sc->sc_eintrs; + if (status == 0) { + goto done; + } + if (status & (OHCI_SO | OHCI_RD | OHCI_UE | OHCI_RHSC)) { +#if 0 + if (status & OHCI_SO) { + /* XXX do what */ + } +#endif + if (status & OHCI_RD) { + printf("%s: resume detect\n", __FUNCTION__); + /* XXX process resume detect */ + } + if (status & OHCI_UE) { + printf("%s: unrecoverable error, " + "controller halted\n", __FUNCTION__); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + /* XXX what else */ + } + if (status & OHCI_RHSC) { + /* + * Disable RHSC interrupt for now, because it will be + * on until the port has been reset. + */ + sc->sc_eintrs &= ~OHCI_RHSC; + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC); + + usb2_sw_transfer(&sc->sc_root_intr, + &ohci_root_intr_done); + + /* do not allow RHSC interrupts > 1 per second */ + usb2_callout_reset(&sc->sc_tmo_rhsc, hz, + (void *)&ohci_rhsc_enable, sc); + } + } + status &= ~(OHCI_RHSC | OHCI_WDH | OHCI_SO); + if (status != 0) { + /* Block unprocessed interrupts. XXX */ + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, status); + sc->sc_eintrs &= ~status; + printf("%s: blocking intrs 0x%x\n", + __FUNCTION__, status); + } + /* poll all the USB transfers */ + ohci_interrupt_poll(sc); + +done: + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/* + * called when a request does not complete + */ +static void +ohci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + ohci_softc_t *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + ohci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +ohci_do_poll(struct usb2_bus *bus) +{ + struct ohci_softc *sc = OHCI_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + ohci_interrupt_poll(sc); + ohci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +static void +ohci_setup_standard_chain_sub(struct ohci_std_temp *temp) +{ + struct usb2_page_search buf_res; + ohci_td_t *td; + ohci_td_t *td_next; + ohci_td_t *td_alt_next; + uint32_t buf_offset; + uint32_t average; + uint32_t len_old; + uint8_t shortpkt_old; + uint8_t precompute; + + td_alt_next = NULL; + buf_offset = 0; + shortpkt_old = temp->shortpkt; + len_old = temp->len; + precompute = 1; + + /* software is used to detect short incoming transfers */ + + if ((temp->td_flags & htole32(OHCI_TD_DP_MASK)) == htole32(OHCI_TD_IN)) { + temp->td_flags |= htole32(OHCI_TD_R); + } else { + temp->td_flags &= ~htole32(OHCI_TD_R); + } + +restart: + + td = temp->td; + td_next = temp->td_next; + + while (1) { + + if (temp->len == 0) { + + if (temp->shortpkt) { + break; + } + /* send a Zero Length Packet, ZLP, last */ + + temp->shortpkt = 1; + average = 0; + + } else { + + average = temp->average; + + if (temp->len < average) { + if (temp->len % temp->max_frame_size) { + temp->shortpkt = 1; + } + average = temp->len; + } + } + + if (td_next == NULL) { + panic("%s: out of OHCI transfer descriptors!", __FUNCTION__); + } + /* get next TD */ + + td = td_next; + td_next = td->obj_next; + + /* check if we are pre-computing */ + + if (precompute) { + + /* update remaining length */ + + temp->len -= average; + + continue; + } + /* fill out current TD */ + td->td_flags = temp->td_flags; + + /* the next TD uses TOGGLE_CARRY */ + temp->td_flags &= ~htole32(OHCI_TD_TOGGLE_MASK); + + if (average == 0) { + + td->td_cbp = 0; + td->td_be = ~0; + td->len = 0; + + } else { + + usb2_get_page(temp->pc, buf_offset, &buf_res); + td->td_cbp = htole32(buf_res.physaddr); + buf_offset += (average - 1); + + usb2_get_page(temp->pc, buf_offset, &buf_res); + td->td_be = htole32(buf_res.physaddr); + buf_offset++; + + td->len = average; + + /* update remaining length */ + + temp->len -= average; + } + + if ((td_next == td_alt_next) && temp->setup_alt_next) { + /* we need to receive these frames one by one ! */ + td->td_flags &= htole32(~OHCI_TD_INTR_MASK); + td->td_flags |= htole32(OHCI_TD_SET_DI(1)); + td->td_next = htole32(OHCI_TD_NEXT_END); + } else { + if (td_next) { + /* link the current TD with the next one */ + td->td_next = td_next->td_self; + } + } + + td->alt_next = td_alt_next; + + usb2_pc_cpu_flush(td->page_cache); + } + + if (precompute) { + precompute = 0; + + /* setup alt next pointer, if any */ + if (temp->short_frames_ok) { + if (temp->setup_alt_next) { + td_alt_next = td_next; + } + } else { + /* we use this field internally */ + td_alt_next = td_next; + } + + /* restore */ + temp->shortpkt = shortpkt_old; + temp->len = len_old; + goto restart; + } + temp->td = td; + temp->td_next = td_next; + + return; +} + +static void +ohci_setup_standard_chain(struct usb2_xfer *xfer, ohci_ed_t **ed_last) +{ + struct ohci_std_temp temp; + struct usb2_pipe_methods *methods; + ohci_ed_t *ed; + ohci_td_t *td; + uint32_t ed_flags; + uint32_t x; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpoint), + xfer->sumlen, usb2_get_speed(xfer->udev)); + + temp.average = xfer->max_usb2_frame_size; + temp.max_frame_size = xfer->max_frame_size; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + temp.td = NULL; + temp.td_next = td; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.short_frames_ok = xfer->flags_int.short_frames_ok; + + methods = xfer->pipe->methods; + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC | + OHCI_TD_TOGGLE_0 | OHCI_TD_NOINTR); + + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.shortpkt = temp.len ? 1 : 0; + + ohci_setup_standard_chain_sub(&temp); + + /* + * XXX assume that the setup message is + * contained within one USB packet: + */ + xfer->pipe->toggle_next = 1; + } + x = 1; + } else { + x = 0; + } + temp.td_flags = htole32(OHCI_TD_NOCC | OHCI_TD_NOINTR); + + /* set data toggle */ + + if (xfer->pipe->toggle_next) { + temp.td_flags |= htole32(OHCI_TD_TOGGLE_1); + } else { + temp.td_flags |= htole32(OHCI_TD_TOGGLE_0); + } + + /* set endpoint direction */ + + if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) { + temp.td_flags |= htole32(OHCI_TD_IN); + } else { + temp.td_flags |= htole32(OHCI_TD_OUT); + } + + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + temp.pc = xfer->frbuffers + x; + + x++; + + if (x == xfer->nframes) { + temp.setup_alt_next = 0; + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.shortpkt = 0; + + } else { + + /* regular data transfer */ + + temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + ohci_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current endpoint + * direction. + */ + + /* set endpoint direction and data toggle */ + + if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) { + temp.td_flags = htole32(OHCI_TD_OUT | + OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); + } else { + temp.td_flags = htole32(OHCI_TD_IN | + OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); + } + + temp.len = 0; + temp.pc = NULL; + temp.shortpkt = 0; + + ohci_setup_standard_chain_sub(&temp); + } + td = temp.td; + + td->td_next = htole32(OHCI_TD_NEXT_END); + td->td_flags &= ~htole32(OHCI_TD_INTR_MASK); + td->td_flags |= htole32(OHCI_TD_SET_DI(1)); + + usb2_pc_cpu_flush(td->page_cache); + + /* must have at least one frame! */ + + xfer->td_transfer_last = td; + +#if USB_DEBUG + if (ohcidebug > 8) { + DPRINTF("nexttog=%d; data before transfer:\n", + xfer->pipe->toggle_next); + ohci_dump_tds(xfer->td_transfer_first); + } +#endif + + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + ed_flags = (OHCI_ED_SET_FA(xfer->address) | + OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpoint)) | + OHCI_ED_SET_MAXP(xfer->max_frame_size)); + + ed_flags |= (OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD); + + if (xfer->udev->speed == USB_SPEED_LOW) { + ed_flags |= OHCI_ED_SPEED; + } + ed->ed_flags = htole32(ed_flags); + + usb2_pc_cpu_flush(ed->page_cache); + + td = xfer->td_transfer_first; + + OHCI_APPEND_QH(ed, td->td_self, *ed_last); + + if (methods == &ohci_device_bulk_methods) { + ohci_softc_t *sc = xfer->usb2_sc; + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); + } + if (methods == &ohci_device_ctrl_methods) { + ohci_softc_t *sc = xfer->usb2_sc; + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); + } + return; +} + +static void +ohci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + ohci_softc_t *sc = xfer->usb2_sc; + uint32_t hstatus; + uint16_t i; + uint16_t m; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_PRE_DATA) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + ohci_device_done(xfer, std->err); + } + goto done; + } + /* setup buffer */ + std->ptr = sc->sc_hub_idata; + std->len = sizeof(sc->sc_hub_idata); + + /* clear any old interrupt data */ + bzero(sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); + + hstatus = OREAD4(sc, OHCI_RH_STATUS); + DPRINTF("sc=%p xfer=%p hstatus=0x%08x\n", + sc, xfer, hstatus); + + /* set bits */ + m = (sc->sc_noport + 1); + if (m > (8 * sizeof(sc->sc_hub_idata))) { + m = (8 * sizeof(sc->sc_hub_idata)); + } + for (i = 1; i < m; i++) { + /* pick out CHANGE bits from the status register */ + if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) { + sc->sc_hub_idata[i / 8] |= 1 << (i % 8); + DPRINTF("port %d changed\n", i); + } + } +done: + return; +} + +/* NOTE: "done" can be run two times in a row, + * from close and from interrupt + */ +static void +ohci_device_done(struct usb2_xfer *xfer, usb2_error_t error) +{ + struct usb2_pipe_methods *methods = xfer->pipe->methods; + ohci_softc_t *sc = xfer->usb2_sc; + ohci_ed_t *ed; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + + DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n", + xfer, xfer->pipe, error); + + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + if (ed) { + usb2_pc_cpu_invalidate(ed->page_cache); + } + if (methods == &ohci_device_bulk_methods) { + OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last); + } + if (methods == &ohci_device_ctrl_methods) { + OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last); + } + if (methods == &ohci_device_intr_methods) { + OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); + } + if (methods == &ohci_device_isoc_methods) { + OHCI_REMOVE_QH(ed, sc->sc_isoc_p_last); + } + xfer->td_transfer_first = NULL; + xfer->td_transfer_last = NULL; + + /* dequeue transfer and start next transfer */ + usb2_transfer_done(xfer, error); + return; +} + +/*------------------------------------------------------------------------* + * ohci bulk support + *------------------------------------------------------------------------*/ +static void +ohci_device_bulk_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_device_bulk_close(struct usb2_xfer *xfer) +{ + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ohci_device_bulk_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_device_bulk_start(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + /* setup TD's and QH */ + ohci_setup_standard_chain(xfer, &sc->sc_bulk_p_last); + + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ohci_device_bulk_methods = +{ + .open = ohci_device_bulk_open, + .close = ohci_device_bulk_close, + .enter = ohci_device_bulk_enter, + .start = ohci_device_bulk_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ohci control support + *------------------------------------------------------------------------*/ +static void +ohci_device_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_device_ctrl_close(struct usb2_xfer *xfer) +{ + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ohci_device_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_device_ctrl_start(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + /* setup TD's and QH */ + ohci_setup_standard_chain(xfer, &sc->sc_ctrl_p_last); + + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ohci_device_ctrl_methods = +{ + .open = ohci_device_ctrl_open, + .close = ohci_device_ctrl_close, + .enter = ohci_device_ctrl_enter, + .start = ohci_device_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ohci interrupt support + *------------------------------------------------------------------------*/ +static void +ohci_device_intr_open(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + uint16_t best; + uint16_t bit; + uint16_t x; + + best = 0; + bit = OHCI_NO_EDS / 2; + while (bit) { + if (xfer->interval >= bit) { + x = bit; + best = bit; + while (x & bit) { + if (sc->sc_intr_stat[x] < + sc->sc_intr_stat[best]) { + best = x; + } + x++; + } + break; + } + bit >>= 1; + } + + sc->sc_intr_stat[best]++; + xfer->qh_pos = best; + + DPRINTFN(3, "best=%d interval=%d\n", + best, xfer->interval); + return; +} + +static void +ohci_device_intr_close(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + sc->sc_intr_stat[xfer->qh_pos]--; + + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ohci_device_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_device_intr_start(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + /* setup TD's and QH */ + ohci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]); + + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ohci_device_intr_methods = +{ + .open = ohci_device_intr_open, + .close = ohci_device_intr_close, + .enter = ohci_device_intr_enter, + .start = ohci_device_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ohci isochronous support + *------------------------------------------------------------------------*/ +static void +ohci_device_isoc_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_device_isoc_close(struct usb2_xfer *xfer) +{ + /**/ + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ohci_device_isoc_enter(struct usb2_xfer *xfer) +{ + struct usb2_page_search buf_res; + ohci_softc_t *sc = xfer->usb2_sc; + struct ohci_hcca *hcca; + uint32_t buf_offset; + uint32_t nframes; + uint32_t ed_flags; + uint32_t *plen; + uint16_t itd_offset[OHCI_ITD_NOFFSET]; + uint16_t length; + uint8_t ncur; + ohci_itd_t *td; + ohci_itd_t *td_last = NULL; + ohci_ed_t *ed; + + hcca = ohci_get_hcca(sc); + + nframes = le32toh(hcca->hcca_frame_number); + + DPRINTFN(6, "xfer=%p isoc_next=%u nframes=%u hcca_fn=%u\n", + xfer, xfer->pipe->isoc_next, xfer->nframes, nframes); + + if ((xfer->pipe->is_synced == 0) || + (((nframes - xfer->pipe->isoc_next) & 0xFFFF) < xfer->nframes) || + (((xfer->pipe->isoc_next - nframes) & 0xFFFF) >= 128)) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->pipe->isoc_next = (nframes + 3) & 0xFFFF; + xfer->pipe->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = ((xfer->pipe->isoc_next - nframes) & 0xFFFF); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + (usb2_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + + xfer->nframes); + + /* get the real number of frames */ + + nframes = xfer->nframes; + + buf_offset = 0; + + plen = xfer->frlengths; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + + xfer->td_transfer_first = td; + + ncur = 0; + length = 0; + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + itd_offset[ncur] = length; + buf_offset += *plen; + length += *plen; + plen++; + ncur++; + + if ( /* check if the ITD is full */ + (ncur == OHCI_ITD_NOFFSET) || + /* check if we have put more than 4K into the ITD */ + (length & 0xF000) || + /* check if it is the last frame */ + (nframes == 0)) { + + /* fill current ITD */ + td->itd_flags = htole32( + OHCI_ITD_NOCC | + OHCI_ITD_SET_SF(xfer->pipe->isoc_next) | + OHCI_ITD_NOINTR | + OHCI_ITD_SET_FC(ncur)); + + td->frames = ncur; + xfer->pipe->isoc_next += ncur; + + if (length == 0) { + /* all zero */ + td->itd_bp0 = 0; + td->itd_be = ~0; + + while (ncur--) { + td->itd_offset[ncur] = + htole16(OHCI_ITD_MK_OFFS(0)); + } + } else { + usb2_get_page(xfer->frbuffers, buf_offset - length, &buf_res); + length = OHCI_PAGE_MASK(buf_res.physaddr); + buf_res.physaddr = + OHCI_PAGE(buf_res.physaddr); + td->itd_bp0 = htole32(buf_res.physaddr); + usb2_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); + td->itd_be = htole32(buf_res.physaddr); + + while (ncur--) { + itd_offset[ncur] += length; + itd_offset[ncur] = + OHCI_ITD_MK_OFFS(itd_offset[ncur]); + td->itd_offset[ncur] = + htole16(itd_offset[ncur]); + } + } + ncur = 0; + length = 0; + td_last = td; + td = td->obj_next; + + if (td) { + /* link the last TD with the next one */ + td_last->itd_next = td->itd_self; + } + usb2_pc_cpu_flush(td_last->page_cache); + } + } + + /* update the last TD */ + td_last->itd_flags &= ~htole32(OHCI_ITD_NOINTR); + td_last->itd_flags |= htole32(OHCI_ITD_SET_DI(0)); + td_last->itd_next = 0; + + usb2_pc_cpu_flush(td_last->page_cache); + + xfer->td_transfer_last = td_last; + +#if USB_DEBUG + if (ohcidebug > 8) { + DPRINTF("data before transfer:\n"); + ohci_dump_itds(xfer->td_transfer_first); + } +#endif + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + if (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) + ed_flags = (OHCI_ED_DIR_IN | OHCI_ED_FORMAT_ISO); + else + ed_flags = (OHCI_ED_DIR_OUT | OHCI_ED_FORMAT_ISO); + + ed_flags |= (OHCI_ED_SET_FA(xfer->address) | + OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpoint)) | + OHCI_ED_SET_MAXP(xfer->max_frame_size)); + + if (xfer->udev->speed == USB_SPEED_LOW) { + ed_flags |= OHCI_ED_SPEED; + } + ed->ed_flags = htole32(ed_flags); + + usb2_pc_cpu_flush(ed->page_cache); + + td = xfer->td_transfer_first; + + OHCI_APPEND_QH(ed, td->itd_self, sc->sc_isoc_p_last); + return; +} + +static void +ohci_device_isoc_start(struct usb2_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods ohci_device_isoc_methods = +{ + .open = ohci_device_isoc_open, + .close = ohci_device_isoc_close, + .enter = ohci_device_isoc_enter, + .start = ohci_device_isoc_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * ohci root control support + *------------------------------------------------------------------------* + * simulate a hardware hub by handling + * all the necessary requests + *------------------------------------------------------------------------*/ + +static void +ohci_root_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_root_ctrl_close(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* data structures and routines + * to emulate the root hub: + */ +static const +struct usb2_device_descriptor ohci_devd = +{ + sizeof(struct usb2_device_descriptor), + UDESC_DEVICE, /* type */ + {0x00, 0x01}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 64, /* max packet */ + {0}, {0}, {0x00, 0x01}, /* device id */ + 1, 2, 0, /* string indicies */ + 1 /* # of configurations */ +}; + +static const +struct ohci_config_desc ohci_confd = +{ + .confd = { + .bLength = sizeof(struct usb2_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(ohci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, /* max power */ + }, + + .ifcd = { + .bLength = sizeof(struct usb2_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = UIPROTO_FSHUB, + }, + + .endpd = { + .bLength = sizeof(struct usb2_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = UE_DIR_IN | OHCI_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 32,/* max packet (255 ports) */ + .bInterval = 255, + }, +}; + +static const +struct usb2_hub_descriptor ohci_hubd = +{ + 0, /* dynamic length */ + UDESC_HUB, + 0, + {0, 0}, + 0, + 0, + {0}, +}; + +static void +ohci_root_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_root_ctrl_start(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &ohci_root_ctrl_task, 0, 0); + + return; +} + +static void +ohci_root_ctrl_task(struct ohci_softc *sc, + struct ohci_config_copy *cc, uint16_t refcount) +{ + ohci_root_ctrl_poll(sc); + return; +} + +static void +ohci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + ohci_softc_t *sc = xfer->usb2_sc; + char *ptr; + uint32_t port; + uint32_t v; + uint16_t value; + uint16_t index; + uint8_t l; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_SETUP) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + ohci_device_done(xfer, std->err); + } + goto done; + } + /* buffer reset */ + std->ptr = sc->sc_hub_desc.temp; + std->len = 0; + + value = UGETW(std->req.wValue); + index = UGETW(std->req.wIndex); + + use_polling = mtx_owned(xfer->priv_mtx) ? 1 : 0; + + DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " + "wValue=0x%04x wIndex=0x%04x\n", + std->req.bmRequestType, std->req.bRequest, + UGETW(std->req.wLength), value, index); + +#define C(x,y) ((x) | ((y) << 8)) + switch (C(std->req.bRequest, std->req.bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + std->len = 1; + sc->sc_hub_desc.temp[0] = sc->sc_conf; + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + switch (value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = sizeof(ohci_devd); + sc->sc_hub_desc.devd = ohci_devd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = sizeof(ohci_confd); + std->ptr = USB_ADD_BYTES(&ohci_confd, 0); + break; + + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + ptr = "\001"; + break; + + case 1: /* Vendor */ + ptr = sc->sc_vendor; + break; + + case 2: /* Product */ + ptr = "OHCI root HUB"; + break; + + default: + ptr = ""; + break; + } + + std->len = usb2_make_str_desc + (sc->sc_hub_desc.temp, + sizeof(sc->sc_hub_desc.temp), + ptr); + break; + + default: + std->err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + std->len = 1; + sc->sc_hub_desc.temp[0] = 0; + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + std->len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + std->len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, 0); + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= USB_MAX_DEVICES) { + std->err = USB_ERR_IOERROR; + goto done; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if ((value != 0) && (value != 1)) { + std->err = USB_ERR_IOERROR; + goto done; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + std->err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE " + "port=%d feature=%d\n", + index, value); + if ((index < 1) || + (index > sc->sc_noport)) { + std->err = USB_ERR_IOERROR; + goto done; + } + port = OHCI_RH_PORT_STATUS(index); + switch (value) { + case UHF_PORT_ENABLE: + OWRITE4(sc, port, UPS_CURRENT_CONNECT_STATUS); + break; + case UHF_PORT_SUSPEND: + OWRITE4(sc, port, UPS_OVERCURRENT_INDICATOR); + break; + case UHF_PORT_POWER: + /* Yes, writing to the LOW_SPEED bit clears power. */ + OWRITE4(sc, port, UPS_LOW_SPEED); + break; + case UHF_C_PORT_CONNECTION: + OWRITE4(sc, port, UPS_C_CONNECT_STATUS << 16); + break; + case UHF_C_PORT_ENABLE: + OWRITE4(sc, port, UPS_C_PORT_ENABLED << 16); + break; + case UHF_C_PORT_SUSPEND: + OWRITE4(sc, port, UPS_C_SUSPEND << 16); + break; + case UHF_C_PORT_OVER_CURRENT: + OWRITE4(sc, port, UPS_C_OVERCURRENT_INDICATOR << 16); + break; + case UHF_C_PORT_RESET: + OWRITE4(sc, port, UPS_C_PORT_RESET << 16); + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + switch (value) { + case UHF_C_PORT_CONNECTION: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_SUSPEND: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* enable RHSC interrupt if condition is cleared. */ + if ((OREAD4(sc, port) >> 16) == 0) { + ohci_rhsc_enable(sc); + mtx_lock(&sc->sc_bus.mtx); + } + break; + default: + break; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + v = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); + + sc->sc_hub_desc.hubd = ohci_hubd; + sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; + USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, + (v & OHCI_NPS ? UHD_PWR_NO_SWITCH : + v & OHCI_PSM ? UHD_PWR_GANGED : UHD_PWR_INDIVIDUAL) + /* XXX overcurrent */ + ); + sc->sc_hub_desc.hubd.bPwrOn2PwrGood = OHCI_GET_POTPGT(v); + v = OREAD4(sc, OHCI_RH_DESCRIPTOR_B); + + for (l = 0; l < sc->sc_noport; l++) { + if (v & 1) { + sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] |= (1 << (l % 8)); + } + v >>= 1; + } + sc->sc_hub_desc.hubd.bDescLength = + 8 + ((sc->sc_noport + 7) / 8); + std->len = sc->sc_hub_desc.hubd.bDescLength; + break; + + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + std->len = 16; + bzero(sc->sc_hub_desc.temp, 16); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + DPRINTFN(9, "get port status i=%d\n", + index); + if ((index < 1) || + (index > sc->sc_noport)) { + std->err = USB_ERR_IOERROR; + goto done; + } + v = OREAD4(sc, OHCI_RH_PORT_STATUS(index)); + DPRINTFN(9, "port status=0x%04x\n", v); + USETW(sc->sc_hub_desc.ps.wPortStatus, v); + USETW(sc->sc_hub_desc.ps.wPortChange, v >> 16); + std->len = sizeof(sc->sc_hub_desc.ps); + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + std->err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if ((index < 1) || + (index > sc->sc_noport)) { + std->err = USB_ERR_IOERROR; + goto done; + } + port = OHCI_RH_PORT_STATUS(index); + switch (value) { + case UHF_PORT_ENABLE: + OWRITE4(sc, port, UPS_PORT_ENABLED); + break; + case UHF_PORT_SUSPEND: + OWRITE4(sc, port, UPS_SUSPEND); + break; + case UHF_PORT_RESET: + DPRINTFN(6, "reset port %d\n", index); + OWRITE4(sc, port, UPS_RESET); + for (v = 0;; v++) { + if (v < 12) { + if (use_polling) { + /* polling */ + DELAY(USB_PORT_ROOT_RESET_DELAY * 1000); + } else { + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_PORT_ROOT_RESET_DELAY); + } + + if ((OREAD4(sc, port) & UPS_RESET) == 0) { + break; + } + } else { + std->err = USB_ERR_TIMEOUT; + goto done; + } + } + DPRINTFN(9, "ohci port %d reset, status = 0x%04x\n", + index, OREAD4(sc, port)); + break; + case UHF_PORT_POWER: + DPRINTFN(3, "set port power %d\n", index); + OWRITE4(sc, port, UPS_PORT_POWER); + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } +done: + return; +} + +static void +ohci_root_ctrl_poll(struct ohci_softc *sc) +{ + usb2_sw_transfer(&sc->sc_root_ctrl, + &ohci_root_ctrl_done); + return; +} + +struct usb2_pipe_methods ohci_root_ctrl_methods = +{ + .open = ohci_root_ctrl_open, + .close = ohci_root_ctrl_close, + .enter = ohci_root_ctrl_enter, + .start = ohci_root_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 0, +}; + +/*------------------------------------------------------------------------* + * ohci root interrupt support + *------------------------------------------------------------------------*/ +static void +ohci_root_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_root_intr_close(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + ohci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +ohci_root_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_root_intr_start(struct usb2_xfer *xfer) +{ + ohci_softc_t *sc = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +struct usb2_pipe_methods ohci_root_intr_methods = +{ + .open = ohci_root_intr_open, + .close = ohci_root_intr_close, + .enter = ohci_root_intr_enter, + .start = ohci_root_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +static void +ohci_xfer_setup(struct usb2_setup_params *parm) +{ + struct usb2_page_search page_info; + struct usb2_page_cache *pc; + ohci_softc_t *sc; + struct usb2_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t nitd; + uint32_t nqh; + uint32_t n; + + sc = OHCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = OHCI_PAGE_SIZE; + + /* + * calculate ntd and nqh + */ + if (parm->methods == &ohci_device_ctrl_methods) { + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nitd = 0; + ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + + (xfer->max_data_length / xfer->max_usb2_frame_size)); + nqh = 1; + + } else if (parm->methods == &ohci_device_bulk_methods) { + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nitd = 0; + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_usb2_frame_size)); + nqh = 1; + + } else if (parm->methods == &ohci_device_intr_methods) { + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nitd = 0; + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_usb2_frame_size)); + nqh = 1; + + } else if (parm->methods == &ohci_device_isoc_methods) { + xfer->flags_int.bdma_enable = 1; + + usb2_transfer_setup_sub(parm); + + nitd = ((xfer->max_data_length / OHCI_PAGE_SIZE) + + ((xfer->nframes + OHCI_ITD_NOFFSET - 1) / OHCI_ITD_NOFFSET) + + 1 /* EXTRA */ ); + ntd = 0; + nqh = 1; + + } else { + + usb2_transfer_setup_sub(parm); + + nitd = 0; + ntd = 0; + nqh = 0; + } + +alloc_dma_set: + + if (parm->err) { + return; + } + last_obj = NULL; + + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(ohci_td_t), + OHCI_TD_ALIGN, ntd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != ntd; n++) { + ohci_td_t *td; + + usb2_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->td_self = htole32(page_info.physaddr); + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb2_pc_cpu_flush(pc + n); + } + } + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(ohci_itd_t), + OHCI_ITD_ALIGN, nitd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nitd; n++) { + ohci_itd_t *itd; + + usb2_get_page(pc + n, 0, &page_info); + + itd = page_info.buffer; + + /* init TD */ + itd->itd_self = htole32(page_info.physaddr); + itd->obj_next = last_obj; + itd->page_cache = pc + n; + + last_obj = itd; + + usb2_pc_cpu_flush(pc + n); + } + } + xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; + + last_obj = NULL; + + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(ohci_ed_t), + OHCI_ED_ALIGN, nqh)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqh; n++) { + ohci_ed_t *ed; + + usb2_get_page(pc + n, 0, &page_info); + + ed = page_info.buffer; + + /* init QH */ + ed->ed_self = htole32(page_info.physaddr); + ed->obj_next = last_obj; + ed->page_cache = pc + n; + + last_obj = ed; + + usb2_pc_cpu_flush(pc + n); + } + } + xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; + + if (!xfer->flags_int.curr_dma_set) { + xfer->flags_int.curr_dma_set = 1; + goto alloc_dma_set; + } + return; +} + +static void +ohci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc, + struct usb2_pipe *pipe) +{ + ohci_softc_t *sc = OHCI_BUS2SC(udev->bus); + + DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + pipe, udev->address, + edesc->bEndpointAddress, udev->flags.usb2_mode, + sc->sc_addr); + + if (udev->flags.usb2_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->device_index == sc->sc_addr) { + switch (edesc->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &ohci_root_ctrl_methods; + break; + case UE_DIR_IN | OHCI_INTR_ENDPT: + pipe->methods = &ohci_root_intr_methods; + break; + default: + /* do nothing */ + break; + } + } else { + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + pipe->methods = &ohci_device_ctrl_methods; + break; + case UE_INTERRUPT: + pipe->methods = &ohci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + if (udev->speed == USB_SPEED_FULL) { + pipe->methods = &ohci_device_isoc_methods; + } + break; + case UE_BULK: + if (udev->speed != USB_SPEED_LOW) { + pipe->methods = &ohci_device_bulk_methods; + } + break; + default: + /* do nothing */ + break; + } + } + return; +} + +static void +ohci_xfer_unsetup(struct usb2_xfer *xfer) +{ + return; +} + +static void +ohci_get_dma_delay(struct usb2_bus *bus, uint32_t *pus) +{ + /* + * Wait until hardware has finished any possible use of the + * transfer descriptor(s) and QH + */ + *pus = (1125); /* microseconds */ + return; +} + +struct usb2_bus_methods ohci_bus_methods = +{ + .pipe_init = ohci_pipe_init, + .xfer_setup = ohci_xfer_setup, + .xfer_unsetup = ohci_xfer_unsetup, + .do_poll = ohci_do_poll, + .get_dma_delay = ohci_get_dma_delay, +}; diff --git a/sys/dev/usb2/controller/ohci2.h b/sys/dev/usb2/controller/ohci2.h new file mode 100644 index 000000000000..fefde6cf54ca --- /dev/null +++ b/sys/dev/usb2/controller/ohci2.h @@ -0,0 +1,364 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#ifndef _OHCI_H_ +#define _OHCI_H_ + +/* PCI config registers */ +#define PCI_CBMEM 0x10 /* configuration base memory */ +#define PCI_INTERFACE_OHCI 0x10 + +/* OHCI registers */ +#define OHCI_REVISION 0x00 /* OHCI revision */ +#define OHCI_REV_LO(rev) ((rev) & 0xf) +#define OHCI_REV_HI(rev) (((rev)>>4) & 0xf) +#define OHCI_REV_LEGACY(rev) ((rev) & 0x100) +#define OHCI_CONTROL 0x04 +#define OHCI_CBSR_MASK 0x00000003 /* Control/Bulk Service Ratio */ +#define OHCI_RATIO_1_1 0x00000000 +#define OHCI_RATIO_1_2 0x00000001 +#define OHCI_RATIO_1_3 0x00000002 +#define OHCI_RATIO_1_4 0x00000003 +#define OHCI_PLE 0x00000004 /* Periodic List Enable */ +#define OHCI_IE 0x00000008 /* Isochronous Enable */ +#define OHCI_CLE 0x00000010 /* Control List Enable */ +#define OHCI_BLE 0x00000020 /* Bulk List Enable */ +#define OHCI_HCFS_MASK 0x000000c0 /* HostControllerFunctionalStat + * e */ +#define OHCI_HCFS_RESET 0x00000000 +#define OHCI_HCFS_RESUME 0x00000040 +#define OHCI_HCFS_OPERATIONAL 0x00000080 +#define OHCI_HCFS_SUSPEND 0x000000c0 +#define OHCI_IR 0x00000100 /* Interrupt Routing */ +#define OHCI_RWC 0x00000200 /* Remote Wakeup Connected */ +#define OHCI_RWE 0x00000400 /* Remote Wakeup Enabled */ +#define OHCI_COMMAND_STATUS 0x08 +#define OHCI_HCR 0x00000001 /* Host Controller Reset */ +#define OHCI_CLF 0x00000002 /* Control List Filled */ +#define OHCI_BLF 0x00000004 /* Bulk List Filled */ +#define OHCI_OCR 0x00000008 /* Ownership Change Request */ +#define OHCI_SOC_MASK 0x00030000 /* Scheduling Overrun Count */ +#define OHCI_INTERRUPT_STATUS 0x0c +#define OHCI_SO 0x00000001 /* Scheduling Overrun */ +#define OHCI_WDH 0x00000002 /* Writeback Done Head */ +#define OHCI_SF 0x00000004 /* Start of Frame */ +#define OHCI_RD 0x00000008 /* Resume Detected */ +#define OHCI_UE 0x00000010 /* Unrecoverable Error */ +#define OHCI_FNO 0x00000020 /* Frame Number Overflow */ +#define OHCI_RHSC 0x00000040 /* Root Hub Status Change */ +#define OHCI_OC 0x40000000 /* Ownership Change */ +#define OHCI_MIE 0x80000000 /* Master Interrupt Enable */ +#define OHCI_INTERRUPT_ENABLE 0x10 +#define OHCI_INTERRUPT_DISABLE 0x14 +#define OHCI_HCCA 0x18 +#define OHCI_PERIOD_CURRENT_ED 0x1c +#define OHCI_CONTROL_HEAD_ED 0x20 +#define OHCI_CONTROL_CURRENT_ED 0x24 +#define OHCI_BULK_HEAD_ED 0x28 +#define OHCI_BULK_CURRENT_ED 0x2c +#define OHCI_DONE_HEAD 0x30 +#define OHCI_FM_INTERVAL 0x34 +#define OHCI_GET_IVAL(s) ((s) & 0x3fff) +#define OHCI_GET_FSMPS(s) (((s) >> 16) & 0x7fff) +#define OHCI_FIT 0x80000000 +#define OHCI_FM_REMAINING 0x38 +#define OHCI_FM_NUMBER 0x3c +#define OHCI_PERIODIC_START 0x40 +#define OHCI_LS_THRESHOLD 0x44 +#define OHCI_RH_DESCRIPTOR_A 0x48 +#define OHCI_GET_NDP(s) ((s) & 0xff) +#define OHCI_PSM 0x0100 /* Power Switching Mode */ +#define OHCI_NPS 0x0200 /* No Power Switching */ +#define OHCI_DT 0x0400 /* Device Type */ +#define OHCI_OCPM 0x0800 /* Overcurrent Protection Mode */ +#define OHCI_NOCP 0x1000 /* No Overcurrent Protection */ +#define OHCI_GET_POTPGT(s) ((s) >> 24) +#define OHCI_RH_DESCRIPTOR_B 0x4c +#define OHCI_RH_STATUS 0x50 +#define OHCI_LPS 0x00000001 /* Local Power Status */ +#define OHCI_OCI 0x00000002 /* OverCurrent Indicator */ +#define OHCI_DRWE 0x00008000 /* Device Remote Wakeup Enable */ +#define OHCI_LPSC 0x00010000 /* Local Power Status Change */ +#define OHCI_CCIC 0x00020000 /* OverCurrent Indicator + * Change */ +#define OHCI_CRWE 0x80000000 /* Clear Remote Wakeup Enable */ +#define OHCI_RH_PORT_STATUS(n) (0x50 + ((n)*4)) /* 1 based indexing */ + +#define OHCI_LES (OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE) +#define OHCI_ALL_INTRS (OHCI_SO | OHCI_WDH | OHCI_SF | \ + OHCI_RD | OHCI_UE | OHCI_FNO | \ + OHCI_RHSC | OHCI_OC) +#define OHCI_NORMAL_INTRS (OHCI_WDH | OHCI_RD | OHCI_UE | OHCI_RHSC) + +#define OHCI_FSMPS(i) (((i-210)*6/7) << 16) +#define OHCI_PERIODIC(i) ((i)*9/10) + +#define OHCI_NO_INTRS 32 +#define OHCI_HCCA_SIZE 256 + +/* Structures alignment (bytes) */ +#define OHCI_HCCA_ALIGN 256 +#define OHCI_ED_ALIGN 16 +#define OHCI_TD_ALIGN 16 +#define OHCI_ITD_ALIGN 32 + +#define OHCI_PAGE_SIZE 0x1000 +#define OHCI_PAGE(x) ((x) &~ 0xfff) +#define OHCI_PAGE_OFFSET(x) ((x) & 0xfff) +#define OHCI_PAGE_MASK(x) ((x) & 0xfff) + +#if ((USB_PAGE_SIZE < OHCI_ED_ALIGN) || (OHCI_ED_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_TD_ALIGN) || (OHCI_TD_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_ITD_ALIGN) || (OHCI_ITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_PAGE_SIZE) || (OHCI_PAGE_SIZE == 0)) +#error "Invalid USB page size!" +#endif + +#define OHCI_VIRTUAL_FRAMELIST_COUNT 128/* dummy */ + +#if (OHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER) +#error "maximum number of full-speed isochronous frames is higher than supported!" +#endif + +struct ohci_hcca { + volatile uint32_t hcca_interrupt_table[OHCI_NO_INTRS]; + volatile uint32_t hcca_frame_number; + volatile uint32_t hcca_done_head; +#define OHCI_DONE_INTRS 1 +} __aligned(OHCI_HCCA_ALIGN); + +typedef struct ohci_hcca ohci_hcca_t; + +struct ohci_ed { + volatile uint32_t ed_flags; +#define OHCI_ED_GET_FA(s) ((s) & 0x7f) +#define OHCI_ED_ADDRMASK 0x0000007f +#define OHCI_ED_SET_FA(s) (s) +#define OHCI_ED_GET_EN(s) (((s) >> 7) & 0xf) +#define OHCI_ED_SET_EN(s) ((s) << 7) +#define OHCI_ED_DIR_MASK 0x00001800 +#define OHCI_ED_DIR_TD 0x00000000 +#define OHCI_ED_DIR_OUT 0x00000800 +#define OHCI_ED_DIR_IN 0x00001000 +#define OHCI_ED_SPEED 0x00002000 +#define OHCI_ED_SKIP 0x00004000 +#define OHCI_ED_FORMAT_GEN 0x00000000 +#define OHCI_ED_FORMAT_ISO 0x00008000 +#define OHCI_ED_GET_MAXP(s) (((s) >> 16) & 0x07ff) +#define OHCI_ED_SET_MAXP(s) ((s) << 16) +#define OHCI_ED_MAXPMASK (0x7ff << 16) + volatile uint32_t ed_tailp; + volatile uint32_t ed_headp; +#define OHCI_HALTED 0x00000001 +#define OHCI_TOGGLECARRY 0x00000002 +#define OHCI_HEADMASK 0xfffffffc + volatile uint32_t ed_next; +/* + * Extra information needed: + */ + struct ohci_ed *next; + struct ohci_ed *prev; + struct ohci_ed *obj_next; + struct usb2_page_cache *page_cache; + uint32_t ed_self; +} __aligned(OHCI_ED_ALIGN); + +typedef struct ohci_ed ohci_ed_t; + +struct ohci_td { + volatile uint32_t td_flags; +#define OHCI_TD_R 0x00040000 /* Buffer Rounding */ +#define OHCI_TD_DP_MASK 0x00180000 /* Direction / PID */ +#define OHCI_TD_SETUP 0x00000000 +#define OHCI_TD_OUT 0x00080000 +#define OHCI_TD_IN 0x00100000 +#define OHCI_TD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ +#define OHCI_TD_SET_DI(x) ((x) << 21) +#define OHCI_TD_NOINTR 0x00e00000 +#define OHCI_TD_INTR_MASK 0x00e00000 +#define OHCI_TD_TOGGLE_CARRY 0x00000000 +#define OHCI_TD_TOGGLE_0 0x02000000 +#define OHCI_TD_TOGGLE_1 0x03000000 +#define OHCI_TD_TOGGLE_MASK 0x03000000 +#define OHCI_TD_GET_EC(x) (((x) >> 26) & 3) /* Error Count */ +#define OHCI_TD_GET_CC(x) ((x) >> 28) /* Condition Code */ +#define OHCI_TD_SET_CC(x) ((x) << 28) +#define OHCI_TD_NOCC 0xf0000000 + volatile uint32_t td_cbp; /* Current Buffer Pointer */ + volatile uint32_t td_next; /* Next TD */ +#define OHCI_TD_NEXT_END 0 + volatile uint32_t td_be; /* Buffer End */ +/* + * Extra information needed: + */ + struct ohci_td *obj_next; + struct ohci_td *alt_next; + struct usb2_page_cache *page_cache; + uint32_t td_self; + uint16_t len; +} __aligned(OHCI_TD_ALIGN); + +typedef struct ohci_td ohci_td_t; + +struct ohci_itd { + volatile uint32_t itd_flags; +#define OHCI_ITD_GET_SF(x) ((x) & 0x0000ffff) +#define OHCI_ITD_SET_SF(x) ((x) & 0xffff) +#define OHCI_ITD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ +#define OHCI_ITD_SET_DI(x) ((x) << 21) +#define OHCI_ITD_NOINTR 0x00e00000 +#define OHCI_ITD_GET_FC(x) ((((x) >> 24) & 7)+1) /* Frame Count */ +#define OHCI_ITD_SET_FC(x) (((x)-1) << 24) +#define OHCI_ITD_GET_CC(x) ((x) >> 28) /* Condition Code */ +#define OHCI_ITD_NOCC 0xf0000000 +#define OHCI_ITD_NOFFSET 8 + volatile uint32_t itd_bp0; /* Buffer Page 0 */ + volatile uint32_t itd_next; /* Next ITD */ + volatile uint32_t itd_be; /* Buffer End */ + volatile uint16_t itd_offset[OHCI_ITD_NOFFSET]; /* Buffer offsets and + * Status */ +#define OHCI_ITD_PAGE_SELECT 0x00001000 +#define OHCI_ITD_MK_OFFS(len) (0xe000 | ((len) & 0x1fff)) +#define OHCI_ITD_PSW_LENGTH(x) ((x) & 0xfff) /* Transfer length */ +#define OHCI_ITD_PSW_GET_CC(x) ((x) >> 12) /* Condition Code */ +/* + * Extra information needed: + */ + struct ohci_itd *obj_next; + struct usb2_page_cache *page_cache; + uint32_t itd_self; + uint8_t frames; +} __aligned(OHCI_ITD_ALIGN); + +typedef struct ohci_itd ohci_itd_t; + +#define OHCI_CC_NO_ERROR 0 +#define OHCI_CC_CRC 1 +#define OHCI_CC_BIT_STUFFING 2 +#define OHCI_CC_DATA_TOGGLE_MISMATCH 3 +#define OHCI_CC_STALL 4 +#define OHCI_CC_DEVICE_NOT_RESPONDING 5 +#define OHCI_CC_PID_CHECK_FAILURE 6 +#define OHCI_CC_UNEXPECTED_PID 7 +#define OHCI_CC_DATA_OVERRUN 8 +#define OHCI_CC_DATA_UNDERRUN 9 +#define OHCI_CC_BUFFER_OVERRUN 12 +#define OHCI_CC_BUFFER_UNDERRUN 13 +#define OHCI_CC_NOT_ACCESSED 15 + +/* Some delay needed when changing certain registers. */ +#define OHCI_ENABLE_POWER_DELAY 5 +#define OHCI_READ_DESC_DELAY 5 + +#define OHCI_NO_EDS (2*OHCI_NO_INTRS) + +struct ohci_hw_softc { + struct usb2_page_cache hcca_pc; + struct usb2_page_cache ctrl_start_pc; + struct usb2_page_cache bulk_start_pc; + struct usb2_page_cache isoc_start_pc; + struct usb2_page_cache intr_start_pc[OHCI_NO_EDS]; + + struct usb2_page hcca_pg; + struct usb2_page ctrl_start_pg; + struct usb2_page bulk_start_pg; + struct usb2_page isoc_start_pg; + struct usb2_page intr_start_pg[OHCI_NO_EDS]; +}; + +struct ohci_config_desc { + struct usb2_config_descriptor confd; + struct usb2_interface_descriptor ifcd; + struct usb2_endpoint_descriptor endpd; +} __packed; + +union ohci_hub_desc { + struct usb2_status stat; + struct usb2_port_status ps; + struct usb2_device_descriptor devd; + struct usb2_hub_descriptor hubd; + uint8_t temp[128]; +}; + +typedef struct ohci_softc { + struct ohci_hw_softc sc_hw; + struct usb2_bus sc_bus; /* base device */ + struct usb2_config_td sc_config_td; + struct usb2_callout sc_tmo_rhsc; + union ohci_hub_desc sc_hub_desc; + struct usb2_sw_transfer sc_root_ctrl; + struct usb2_sw_transfer sc_root_intr; + + struct resource *sc_io_res; + struct resource *sc_irq_res; + struct ohci_hcca *sc_hcca_p; + struct ohci_ed *sc_ctrl_p_last; + struct ohci_ed *sc_bulk_p_last; + struct ohci_ed *sc_isoc_p_last; + struct ohci_ed *sc_intr_p_last[OHCI_NO_EDS]; + void *sc_intr_hdl; + device_t sc_dev; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_eintrs; /* enabled interrupts */ + uint32_t sc_control; /* Preserved during suspend/standby */ + uint32_t sc_intre; + + uint16_t sc_intr_stat[OHCI_NO_EDS]; + uint16_t sc_id_vendor; + + uint8_t sc_noport; + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_hub_idata[32]; + + char sc_vendor[16]; + +} ohci_softc_t; + +usb2_bus_mem_cb_t ohci_iterate_hw_softc; + +usb2_error_t ohci_init(ohci_softc_t *sc); +void ohci_detach(struct ohci_softc *sc); +void ohci_suspend(ohci_softc_t *sc); +void ohci_resume(ohci_softc_t *sc); +void ohci_interrupt(ohci_softc_t *sc); + +#endif /* _OHCI_H_ */ diff --git a/sys/dev/usb2/controller/ohci2_atmelarm.c b/sys/dev/usb2/controller/ohci2_atmelarm.c new file mode 100644 index 000000000000..83325bef7b15 --- /dev/null +++ b/sys/dev/usb2/controller/ohci2_atmelarm.c @@ -0,0 +1,232 @@ +/*- + * Copyright (c) 2006 M. Warner Losh. 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#define MEM_RID 0 + +static device_probe_t ohci_atmelarm_probe; +static device_attach_t ohci_atmelarm_attach; +static device_detach_t ohci_atmelarm_detach; + +struct at91_ohci_softc { + struct ohci_softc sc_ohci; /* must be first */ + struct at91_pmc_clock *iclk; + struct at91_pmc_clock *fclk; +}; + +static int +ohci_atmelarm_probe(device_t dev) +{ + device_set_desc(dev, "AT91 integrated OHCI controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +ohci_atmelarm_attach(device_t dev) +{ + struct at91_ohci_softc *sc = device_get_softc(dev); + int err; + int rid; + + if (sc == NULL) { + return (ENXIO); + } + /* get all DMA memory */ + + if (usb2_bus_mem_alloc_all(&sc->sc_ohci.sc_bus, + USB_GET_DMA_TAG(dev), &ohci_iterate_hw_softc)) { + return ENOMEM; + } + sc->iclk = at91_pmc_clock_ref("ohci_clk"); + sc->fclk = at91_pmc_clock_ref("uhpck"); + + sc->sc_ohci.sc_dev = dev; + + rid = MEM_RID; + sc->sc_ohci.sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &rid, RF_ACTIVE); + + if (!(sc->sc_ohci.sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_ohci.sc_io_tag = rman_get_bustag(sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_hdl = rman_get_bushandle(sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_size = rman_get_size(sc->sc_ohci.sc_io_res); + + rid = 0; + sc->sc_ohci.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (!(sc->sc_ohci.sc_irq_res)) { + goto error; + } + sc->sc_ohci.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_ohci.sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_ohci.sc_bus.bdev, &sc->sc_ohci.sc_bus); + + strlcpy(sc->sc_ohci.sc_vendor, "Atmel", sizeof(sc->sc_ohci.sc_vendor)); + + err = usb2_config_td_setup(&sc->sc_ohci.sc_config_td, sc, + &sc->sc_ohci.sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_ohci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)ohci_interrupt, sc, &sc->sc_ohci.sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_ohci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)ohci_interrupt, sc, &sc->sc_ohci.sc_intr_hdl); +#endif + if (err) { + sc->sc_ohci.sc_intr_hdl = NULL; + goto error; + } + /* + * turn on the clocks from the AT91's point of view. Keep the unit in reset. + */ + at91_pmc_clock_enable(sc->iclk); + at91_pmc_clock_enable(sc->fclk); + bus_space_write_4(sc->sc_ohci.sc_io_tag, sc->sc_ohci.sc_io_hdl, + OHCI_CONTROL, 0); + + err = ohci_init(&sc->sc_ohci); + if (!err) { + err = device_probe_and_attach(sc->sc_ohci.sc_bus.bdev); + } + if (err) { + goto error; + } + return (0); + +error: + ohci_atmelarm_detach(dev); + return (ENXIO); +} + +static int +ohci_atmelarm_detach(device_t dev) +{ + struct at91_ohci_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_ohci.sc_bus.bdev) { + bdev = sc->sc_ohci.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(dev); + + /* + * Put the controller into reset, then disable clocks and do + * the MI tear down. We have to disable the clocks/hardware + * after we do the rest of the teardown. We also disable the + * clocks in the opposite order we acquire them, but that + * doesn't seem to be absolutely necessary. We free up the + * clocks after we disable them, so the system could, in + * theory, reuse them. + */ + bus_space_write_4(sc->sc_ohci.sc_io_tag, sc->sc_ohci.sc_io_hdl, + OHCI_CONTROL, 0); + + at91_pmc_clock_disable(sc->fclk); + at91_pmc_clock_disable(sc->iclk); + at91_pmc_clock_deref(sc->fclk); + at91_pmc_clock_deref(sc->iclk); + + if (sc->sc_ohci.sc_irq_res && sc->sc_ohci.sc_intr_hdl) { + /* + * only call ohci_detach() after ohci_init() + */ + ohci_detach(&sc->sc_ohci); + + err = bus_teardown_intr(dev, sc->sc_ohci.sc_irq_res, sc->sc_ohci.sc_intr_hdl); + sc->sc_ohci.sc_intr_hdl = NULL; + } + if (sc->sc_ohci.sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_ohci.sc_irq_res); + sc->sc_ohci.sc_irq_res = NULL; + } + if (sc->sc_ohci.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, MEM_RID, + sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_res = NULL; + } + usb2_config_td_unsetup(&sc->sc_ohci.sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_ohci.sc_bus, &ohci_iterate_hw_softc); + + return (0); +} + +static device_method_t ohci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ohci_atmelarm_probe), + DEVMETHOD(device_attach, ohci_atmelarm_attach), + DEVMETHOD(device_detach, ohci_atmelarm_detach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t ohci_driver = { + "ohci", + ohci_methods, + sizeof(struct at91_ohci_softc), +}; + +static devclass_t ohci_devclass; + +DRIVER_MODULE(ohci, atmelarm, ohci_driver, ohci_devclass, 0, 0); +MODULE_DEPEND(ohci, usb2_controller, 1, 1, 1); +MODULE_DEPEND(ohci, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/ohci2_pci.c b/sys/dev/usb2/controller/ohci2_pci.c new file mode 100644 index 000000000000..60ae0b6f4f59 --- /dev/null +++ b/sys/dev/usb2/controller/ohci2_pci.c @@ -0,0 +1,392 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * USB Open Host Controller driver. + * + * OHCI spec: http://www.intel.com/design/usb/ohci11d.pdf + */ + +/* The low level controller code for OHCI has been split into + * PCI probes and OHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define PCI_OHCI_VENDORID_ACERLABS 0x10b9 +#define PCI_OHCI_VENDORID_AMD 0x1022 +#define PCI_OHCI_VENDORID_APPLE 0x106b +#define PCI_OHCI_VENDORID_ATI 0x1002 +#define PCI_OHCI_VENDORID_CMDTECH 0x1095 +#define PCI_OHCI_VENDORID_NEC 0x1033 +#define PCI_OHCI_VENDORID_NVIDIA 0x12D2 +#define PCI_OHCI_VENDORID_NVIDIA2 0x10DE +#define PCI_OHCI_VENDORID_OPTI 0x1045 +#define PCI_OHCI_VENDORID_SIS 0x1039 +#define PCI_OHCI_VENDORID_SUN 0x108e + +#define PCI_OHCI_BASE_REG 0x10 + +static device_probe_t ohci_pci_probe; +static device_attach_t ohci_pci_attach; +static device_detach_t ohci_pci_detach; +static device_suspend_t ohci_pci_suspend; +static device_resume_t ohci_pci_resume; + +static int +ohci_pci_suspend(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_suspend(self); + if (err) { + return (err); + } + ohci_suspend(sc); + return (0); +} + +static int +ohci_pci_resume(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + uint32_t reg, int_line; + + if (pci_get_powerstate(self) != PCI_POWERSTATE_D0) { + device_printf(self, "chip is in D%d mode " + "-- setting to D0\n", pci_get_powerstate(self)); + reg = pci_read_config(self, PCI_CBMEM, 4); + int_line = pci_read_config(self, PCIR_INTLINE, 4); + pci_set_powerstate(self, PCI_POWERSTATE_D0); + pci_write_config(self, PCI_CBMEM, reg, 4); + pci_write_config(self, PCIR_INTLINE, int_line, 4); + } + ohci_resume(sc); + + bus_generic_resume(self); + return (0); +} + +static const char * +ohci_pci_match(device_t self) +{ + uint32_t device_id = pci_get_devid(self); + + switch (device_id) { + case 0x523710b9: + return ("AcerLabs M5237 (Aladdin-V) USB controller"); + + case 0x740c1022: + return ("AMD-756 USB Controller"); + + case 0x74141022: + return ("AMD-766 USB Controller"); + + case 0x43741002: + return "ATI SB400 USB Controller"; + case 0x43751002: + return "ATI SB400 USB Controller"; + + case 0x06701095: + return ("CMD Tech 670 (USB0670) USB controller"); + + case 0x06731095: + return ("CMD Tech 673 (USB0673) USB controller"); + + case 0xc8611045: + return ("OPTi 82C861 (FireLink) USB controller"); + + case 0x00351033: + return ("NEC uPD 9210 USB controller"); + + case 0x00d710de: + return ("nVidia nForce3 USB Controller"); + + case 0x70011039: + return ("SiS 5571 USB controller"); + + case 0x1103108e: + return "Sun PCIO-2 USB controller"; + + case 0x0019106b: + return ("Apple KeyLargo USB controller"); + + default: + break; + } + if ((pci_get_class(self) == PCIC_SERIALBUS) && + (pci_get_subclass(self) == PCIS_SERIALBUS_USB) && + (pci_get_progif(self) == PCI_INTERFACE_OHCI)) { + return ("OHCI (generic) USB controller"); + } + return (NULL); +} + +static int +ohci_pci_probe(device_t self) +{ + const char *desc = ohci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return (0); + } else { + return (ENXIO); + } +} + +static int +ohci_pci_attach(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + int rid; + int err; + + if (sc == NULL) { + device_printf(self, "Could not allocate sc\n"); + return (ENXIO); + } + /* get all DMA memory */ + + if (usb2_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), + &ohci_iterate_hw_softc)) { + return ENOMEM; + } + sc->sc_dev = self; + + pci_enable_busmaster(self); + + rid = PCI_CBMEM; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map memory\n"); + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* + * ohci_pci_match will never return NULL if ohci_pci_probe + * succeeded + */ + device_set_desc(sc->sc_bus.bdev, ohci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_OHCI_VENDORID_ACERLABS: + sprintf(sc->sc_vendor, "AcerLabs"); + break; + case PCI_OHCI_VENDORID_AMD: + sprintf(sc->sc_vendor, "AMD"); + break; + case PCI_OHCI_VENDORID_APPLE: + sprintf(sc->sc_vendor, "Apple"); + break; + case PCI_OHCI_VENDORID_ATI: + sprintf(sc->sc_vendor, "ATI"); + break; + case PCI_OHCI_VENDORID_CMDTECH: + sprintf(sc->sc_vendor, "CMDTECH"); + break; + case PCI_OHCI_VENDORID_NEC: + sprintf(sc->sc_vendor, "NEC"); + break; + case PCI_OHCI_VENDORID_NVIDIA: + case PCI_OHCI_VENDORID_NVIDIA2: + sprintf(sc->sc_vendor, "nVidia"); + break; + case PCI_OHCI_VENDORID_OPTI: + sprintf(sc->sc_vendor, "OPTi"); + break; + case PCI_OHCI_VENDORID_SIS: + sprintf(sc->sc_vendor, "SiS"); + break; + case PCI_OHCI_VENDORID_SUN: + sprintf(sc->sc_vendor, "SUN"); + break; + default: + if (bootverbose) { + device_printf(self, "(New OHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + } + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + + err = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_bus.mtx, + NULL, 0, 4); + if (err) { + device_printf(self, "could not setup config thread!\n"); + goto error; + } + /* sc->sc_bus.usbrev; set by ohci_init() */ + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)(void *)ohci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)(void *)ohci_interrupt, sc, &sc->sc_intr_hdl); +#endif + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + err = ohci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed\n"); + goto error; + } + return (0); + +error: + ohci_pci_detach(self); + return (ENXIO); +} + +static int +ohci_pci_detach(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + device_t bdev; + + usb2_config_td_drain(&sc->sc_config_td); + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(self); + + pci_disable_busmaster(self); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call ohci_detach() after ohci_init() + */ + ohci_detach(sc); + + int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) { + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + } + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_bus, &ohci_iterate_hw_softc); + + return (0); +} + +static driver_t ohci_driver = +{ + .name = "ohci", + .methods = (device_method_t[]){ + /* device interface */ + DEVMETHOD(device_probe, ohci_pci_probe), + DEVMETHOD(device_attach, ohci_pci_attach), + DEVMETHOD(device_detach, ohci_pci_detach), + DEVMETHOD(device_suspend, ohci_pci_suspend), + DEVMETHOD(device_resume, ohci_pci_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} + }, + .size = sizeof(struct ohci_softc), +}; + +static devclass_t ohci_devclass; + +DRIVER_MODULE(ohci, pci, ohci_driver, ohci_devclass, 0, 0); +DRIVER_MODULE(ohci, cardbus, ohci_driver, ohci_devclass, 0, 0); +MODULE_DEPEND(ohci, usb2_controller, 1, 1, 1); +MODULE_DEPEND(ohci, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/uhci2.c b/sys/dev/usb2/controller/uhci2.c new file mode 100644 index 000000000000..9a938d9cbdb1 --- /dev/null +++ b/sys/dev/usb2/controller/uhci2.c @@ -0,0 +1,3256 @@ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * USB Universal Host Controller driver. + * Handles e.g. PIIX3 and PIIX4. + * + * UHCI spec: http://developer.intel.com/design/USB/UHCI11D.htm + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + * PIIXn spec: ftp://download.intel.com/design/intarch/datashts/29055002.pdf + * ftp://download.intel.com/design/intarch/datashts/29056201.pdf + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR uhcidebug +#define usb2_config_td_cc uhci_config_copy +#define usb2_config_td_softc uhci_softc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define alt_next next +#define UHCI_BUS2SC(bus) ((uhci_softc_t *)(((uint8_t *)(bus)) - \ + USB_P2U(&(((uhci_softc_t *)0)->sc_bus)))) + +#if USB_DEBUG +static int uhcidebug = 0; +static int uhcinoloop = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uhci, CTLFLAG_RW, 0, "USB uhci"); +SYSCTL_INT(_hw_usb2_uhci, OID_AUTO, debug, CTLFLAG_RW, + &uhcidebug, 0, "uhci debug level"); +SYSCTL_INT(_hw_usb2_uhci, OID_AUTO, loop, CTLFLAG_RW, + &uhcinoloop, 0, "uhci noloop"); +static void uhci_dumpregs(uhci_softc_t *sc); +static void uhci_dump_tds(uhci_td_t *td); + +#endif + +#define UBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \ + BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) +#define UWRITE1(sc, r, x) \ + do { UBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UWRITE2(sc, r, x) \ + do { UBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UWRITE4(sc, r, x) \ + do { UBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UREAD1(sc, r) (UBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) +#define UREAD2(sc, r) (UBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) +#define UREAD4(sc, r) (UBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) + +#define UHCICMD(sc, cmd) UWRITE2(sc, UHCI_CMD, cmd) +#define UHCISTS(sc) UREAD2(sc, UHCI_STS) + +#define UHCI_RESET_TIMEOUT 100 /* ms, reset timeout */ + +#define UHCI_INTR_ENDPT 1 + +struct uhci_mem_layout { + + struct usb2_page_search buf_res; + struct usb2_page_search fix_res; + + struct usb2_page_cache *buf_pc; + struct usb2_page_cache *fix_pc; + + uint32_t buf_offset; + + uint16_t max_frame_size; +}; + +struct uhci_std_temp { + + struct uhci_mem_layout ml; + uhci_td_t *td; + uhci_td_t *td_next; + uint32_t average; + uint32_t td_status; + uint32_t td_token; + uint32_t len; + uint16_t max_frame_size; + uint8_t shortpkt; + uint8_t setup_alt_next; + uint8_t short_frames_ok; +}; + +extern struct usb2_bus_methods uhci_bus_methods; +extern struct usb2_pipe_methods uhci_device_bulk_methods; +extern struct usb2_pipe_methods uhci_device_ctrl_methods; +extern struct usb2_pipe_methods uhci_device_intr_methods; +extern struct usb2_pipe_methods uhci_device_isoc_methods; +extern struct usb2_pipe_methods uhci_root_ctrl_methods; +extern struct usb2_pipe_methods uhci_root_intr_methods; + +static usb2_config_td_command_t uhci_root_ctrl_task; +static void uhci_root_ctrl_poll(struct uhci_softc *sc); +static void uhci_do_poll(struct usb2_bus *bus); +static void uhci_device_done(struct usb2_xfer *xfer, usb2_error_t error); +static void uhci_transfer_intr_enqueue(struct usb2_xfer *xfer); +static void uhci_root_intr_check(void *arg); +static void uhci_timeout(void *arg); +static uint8_t uhci_check_transfer(struct usb2_xfer *xfer); + +void +uhci_iterate_hw_softc(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *cb) +{ + struct uhci_softc *sc = UHCI_BUS2SC(bus); + uint32_t i; + + cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg, + sizeof(uint32_t) * UHCI_FRAMELIST_COUNT, UHCI_FRAMELIST_ALIGN); + + cb(bus, &sc->sc_hw.ls_ctl_start_pc, &sc->sc_hw.ls_ctl_start_pg, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.fs_ctl_start_pc, &sc->sc_hw.fs_ctl_start_pg, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.last_qh_pc, &sc->sc_hw.last_qh_pg, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.last_td_pc, &sc->sc_hw.last_td_pg, + sizeof(uhci_td_t), UHCI_TD_ALIGN); + + for (i = 0; i != UHCI_VFRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.isoc_start_pc + i, + sc->sc_hw.isoc_start_pg + i, + sizeof(uhci_td_t), UHCI_TD_ALIGN); + } + + for (i = 0; i != UHCI_IFRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.intr_start_pc + i, + sc->sc_hw.intr_start_pg + i, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + } + return; +} + +static void +uhci_mem_layout_init(struct uhci_mem_layout *ml, struct usb2_xfer *xfer) +{ + ml->buf_pc = xfer->frbuffers + 0; + ml->fix_pc = xfer->buf_fixup; + + ml->buf_offset = 0; + + ml->max_frame_size = xfer->max_frame_size; + + return; +} + +static void +uhci_mem_layout_fixup(struct uhci_mem_layout *ml, struct uhci_td *td) +{ + usb2_get_page(ml->buf_pc, ml->buf_offset, &ml->buf_res); + + if (ml->buf_res.length < td->len) { + + /* need to do a fixup */ + + usb2_get_page(ml->fix_pc, 0, &ml->fix_res); + + td->td_buffer = htole32(ml->fix_res.physaddr); + + /* + * The UHCI driver cannot handle + * page crossings, so a fixup is + * needed: + * + * +----+----+ - - - + * | YYY|Y | + * +----+----+ - - - + * \ \ + * \ \ + * +----+ + * |YYYY| (fixup) + * +----+ + */ + + if ((td->td_token & htole32(UHCI_TD_PID)) == + htole32(UHCI_TD_PID_IN)) { + td->fix_pc = ml->fix_pc; + usb2_pc_cpu_invalidate(ml->fix_pc); + + } else { + td->fix_pc = NULL; + + /* copy data to fixup location */ + + usb2_copy_out(ml->buf_pc, ml->buf_offset, + ml->fix_res.buffer, td->len); + + usb2_pc_cpu_flush(ml->fix_pc); + } + + /* prepare next fixup */ + + ml->fix_pc++; + + } else { + + td->td_buffer = htole32(ml->buf_res.physaddr); + td->fix_pc = NULL; + } + + /* prepare next data location */ + + ml->buf_offset += td->len; + + return; +} + +void +uhci_reset(uhci_softc_t *sc) +{ + struct usb2_page_search buf_res; + uint16_t n; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + DPRINTF("resetting the HC\n"); + + /* disable interrupts */ + + UWRITE2(sc, UHCI_INTR, 0); + + /* global reset */ + + UHCICMD(sc, UHCI_CMD_GRESET); + + /* wait */ + + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_BUS_RESET_DELAY); + + /* terminate all transfers */ + + UHCICMD(sc, UHCI_CMD_HCRESET); + + /* the reset bit goes low when the controller is done */ + + n = UHCI_RESET_TIMEOUT; + while (n--) { + /* wait one millisecond */ + + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + + if (!(UREAD2(sc, UHCI_CMD) & UHCI_CMD_HCRESET)) { + goto done_1; + } + } + + device_printf(sc->sc_bus.bdev, + "controller did not reset\n"); + +done_1: + + n = 10; + while (n--) { + /* wait one millisecond */ + + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + + /* check if HC is stopped */ + if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) { + goto done_2; + } + } + + device_printf(sc->sc_bus.bdev, + "controller did not stop\n"); + +done_2: + + /* reload the configuration */ + usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + UWRITE4(sc, UHCI_FLBASEADDR, buf_res.physaddr); + UWRITE2(sc, UHCI_FRNUM, sc->sc_saved_frnum); + UWRITE1(sc, UHCI_SOF, sc->sc_saved_sof); + return; +} + +static void +uhci_start(uhci_softc_t *sc) +{ + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + DPRINTFN(2, "enabling\n"); + + /* enable interrupts */ + + UWRITE2(sc, UHCI_INTR, + (UHCI_INTR_TOCRCIE | + UHCI_INTR_RIE | + UHCI_INTR_IOCE | + UHCI_INTR_SPIE)); + + /* + * assume 64 byte packets at frame end and start HC controller + */ + + UHCICMD(sc, (UHCI_CMD_MAXP | UHCI_CMD_RS)); + + uint8_t n = 10; + + while (n--) { + /* wait one millisecond */ + + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + + /* check that controller has started */ + + if (!(UREAD2(sc, UHCI_STS) & UHCI_STS_HCH)) { + goto done; + } + } + + device_printf(sc->sc_bus.bdev, + "cannot start HC controller\n"); + +done: + return; +} + +static struct uhci_qh * +uhci_init_qh(struct usb2_page_cache *pc) +{ + struct usb2_page_search buf_res; + struct uhci_qh *qh; + + usb2_get_page(pc, 0, &buf_res); + + qh = buf_res.buffer; + + qh->qh_self = + htole32(buf_res.physaddr) | + htole32(UHCI_PTR_QH); + + qh->page_cache = pc; + + return (qh); +} + +static struct uhci_td * +uhci_init_td(struct usb2_page_cache *pc) +{ + struct usb2_page_search buf_res; + struct uhci_td *td; + + usb2_get_page(pc, 0, &buf_res); + + td = buf_res.buffer; + + td->td_self = + htole32(buf_res.physaddr) | + htole32(UHCI_PTR_TD); + + td->page_cache = pc; + + return (td); +} + +usb2_error_t +uhci_init(uhci_softc_t *sc) +{ + uint16_t bit; + uint16_t x; + uint16_t y; + + mtx_lock(&sc->sc_bus.mtx); + + DPRINTF("start\n"); + +#if USB_DEBUG + if (uhcidebug > 2) { + uhci_dumpregs(sc); + } +#endif + + sc->sc_saved_sof = 0x40; /* default value */ + sc->sc_saved_frnum = 0; /* default frame number */ + + /* + * Setup QH's + */ + sc->sc_ls_ctl_p_last = + uhci_init_qh(&sc->sc_hw.ls_ctl_start_pc); + + sc->sc_fs_ctl_p_last = + uhci_init_qh(&sc->sc_hw.fs_ctl_start_pc); + + sc->sc_bulk_p_last = + uhci_init_qh(&sc->sc_hw.bulk_start_pc); +#if 0 + sc->sc_reclaim_qh_p = + sc->sc_fs_ctl_p_last; +#else + /* setup reclaim looping point */ + sc->sc_reclaim_qh_p = + sc->sc_bulk_p_last; +#endif + + sc->sc_last_qh_p = + uhci_init_qh(&sc->sc_hw.last_qh_pc); + + sc->sc_last_td_p = + uhci_init_td(&sc->sc_hw.last_td_pc); + + for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) { + sc->sc_isoc_p_last[x] = + uhci_init_td(sc->sc_hw.isoc_start_pc + x); + } + + for (x = 0; x != UHCI_IFRAMELIST_COUNT; x++) { + sc->sc_intr_p_last[x] = + uhci_init_qh(sc->sc_hw.intr_start_pc + x); + } + + /* + * the QHs are arranged to give poll intervals that are + * powers of 2 times 1ms + */ + bit = UHCI_IFRAMELIST_COUNT / 2; + while (bit) { + x = bit; + while (x & bit) { + uhci_qh_t *qh_x; + uhci_qh_t *qh_y; + + y = (x ^ bit) | (bit / 2); + + /* + * the next QH has half the poll interval + */ + qh_x = sc->sc_intr_p_last[x]; + qh_y = sc->sc_intr_p_last[y]; + + qh_x->h_next = NULL; + qh_x->qh_h_next = qh_y->qh_self; + qh_x->e_next = NULL; + qh_x->qh_e_next = htole32(UHCI_PTR_T); + x++; + } + bit >>= 1; + } + + if (1) { + uhci_qh_t *qh_ls; + uhci_qh_t *qh_intr; + + qh_ls = sc->sc_ls_ctl_p_last; + qh_intr = sc->sc_intr_p_last[0]; + + /* start QH for interrupt traffic */ + qh_intr->h_next = qh_ls; + qh_intr->qh_h_next = qh_ls->qh_self; + qh_intr->e_next = 0; + qh_intr->qh_e_next = htole32(UHCI_PTR_T); + } + for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) { + + uhci_td_t *td_x; + uhci_qh_t *qh_intr; + + td_x = sc->sc_isoc_p_last[x]; + qh_intr = sc->sc_intr_p_last[x | (UHCI_IFRAMELIST_COUNT / 2)]; + + /* start TD for isochronous traffic */ + td_x->next = NULL; + td_x->td_next = qh_intr->qh_self; + td_x->td_status = htole32(UHCI_TD_IOS); + td_x->td_token = htole32(0); + td_x->td_buffer = htole32(0); + } + + if (1) { + uhci_qh_t *qh_ls; + uhci_qh_t *qh_fs; + + qh_ls = sc->sc_ls_ctl_p_last; + qh_fs = sc->sc_fs_ctl_p_last; + + /* start QH where low speed control traffic will be queued */ + qh_ls->h_next = qh_fs; + qh_ls->qh_h_next = qh_fs->qh_self; + qh_ls->e_next = 0; + qh_ls->qh_e_next = htole32(UHCI_PTR_T); + } + if (1) { + uhci_qh_t *qh_ctl; + uhci_qh_t *qh_blk; + uhci_qh_t *qh_lst; + uhci_td_t *td_lst; + + qh_ctl = sc->sc_fs_ctl_p_last; + qh_blk = sc->sc_bulk_p_last; + + /* start QH where full speed control traffic will be queued */ + qh_ctl->h_next = qh_blk; + qh_ctl->qh_h_next = qh_blk->qh_self; + qh_ctl->e_next = 0; + qh_ctl->qh_e_next = htole32(UHCI_PTR_T); + + qh_lst = sc->sc_last_qh_p; + + /* start QH where bulk traffic will be queued */ + qh_blk->h_next = qh_lst; + qh_blk->qh_h_next = qh_lst->qh_self; + qh_blk->e_next = 0; + qh_blk->qh_e_next = htole32(UHCI_PTR_T); + + td_lst = sc->sc_last_td_p; + + /* end QH which is used for looping the QHs */ + qh_lst->h_next = 0; + qh_lst->qh_h_next = htole32(UHCI_PTR_T); /* end of QH chain */ + qh_lst->e_next = td_lst; + qh_lst->qh_e_next = td_lst->td_self; + + /* + * end TD which hangs from the last QH, to avoid a bug in the PIIX + * that makes it run berserk otherwise + */ + td_lst->next = 0; + td_lst->td_next = htole32(UHCI_PTR_T); + td_lst->td_status = htole32(0); /* inactive */ + td_lst->td_token = htole32(0); + td_lst->td_buffer = htole32(0); + } + if (1) { + struct usb2_page_search buf_res; + uint32_t *pframes; + + usb2_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + + pframes = buf_res.buffer; + + + /* + * Setup UHCI framelist + * + * Execution order: + * + * pframes -> full speed isochronous -> interrupt QH's -> low + * speed control -> full speed control -> bulk transfers + * + */ + + for (x = 0; x != UHCI_FRAMELIST_COUNT; x++) { + pframes[x] = + sc->sc_isoc_p_last[x % UHCI_VFRAMELIST_COUNT]->td_self; + } + } + /* flush all cache into memory */ + + usb2_bus_mem_flush_all(&sc->sc_bus, &uhci_iterate_hw_softc); + + /* set up the bus struct */ + sc->sc_bus.methods = &uhci_bus_methods; + + /* reset the controller */ + uhci_reset(sc); + + /* start the controller */ + uhci_start(sc); + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch lost interrupts */ + uhci_do_poll(&sc->sc_bus); + + return (0); +} + +/* NOTE: suspend/resume is called from + * interrupt context and cannot sleep! + */ + +void +uhci_suspend(uhci_softc_t *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + +#if USB_DEBUG + if (uhcidebug > 2) { + uhci_dumpregs(sc); + } +#endif + /* save some state if BIOS doesn't */ + + sc->sc_saved_frnum = UREAD2(sc, UHCI_FRNUM); + sc->sc_saved_sof = UREAD1(sc, UHCI_SOF); + + /* stop the controller */ + + uhci_reset(sc); + + /* enter global suspend */ + + UHCICMD(sc, UHCI_CMD_EGSM); + + usb2_pause_mtx(&sc->sc_bus.mtx, USB_RESUME_WAIT); + + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +void +uhci_resume(uhci_softc_t *sc) +{ + mtx_lock(&sc->sc_bus.mtx); + + /* reset the controller */ + + uhci_reset(sc); + + /* force global resume */ + + UHCICMD(sc, UHCI_CMD_FGR); + + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_RESUME_DELAY); + + /* and start traffic again */ + + uhci_start(sc); + +#if USB_DEBUG + if (uhcidebug > 2) { + uhci_dumpregs(sc); + } +#endif + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch lost interrupts */ + uhci_do_poll(&sc->sc_bus); + + return; +} + +#if USB_DEBUG +static void +uhci_dumpregs(uhci_softc_t *sc) +{ + DPRINTFN(0, "%s regs: cmd=%04x, sts=%04x, intr=%04x, frnum=%04x, " + "flbase=%08x, sof=%04x, portsc1=%04x, portsc2=%04x\n", + device_get_nameunit(sc->sc_bus.bdev), + UREAD2(sc, UHCI_CMD), + UREAD2(sc, UHCI_STS), + UREAD2(sc, UHCI_INTR), + UREAD2(sc, UHCI_FRNUM), + UREAD4(sc, UHCI_FLBASEADDR), + UREAD1(sc, UHCI_SOF), + UREAD2(sc, UHCI_PORTSC1), + UREAD2(sc, UHCI_PORTSC2)); + return; +} + +static uint8_t +uhci_dump_td(uhci_td_t *p) +{ + uint32_t td_next; + uint32_t td_status; + uint32_t td_token; + uint8_t temp; + + usb2_pc_cpu_invalidate(p->page_cache); + + td_next = le32toh(p->td_next); + td_status = le32toh(p->td_status); + td_token = le32toh(p->td_token); + + /* + * Check whether the link pointer in this TD marks the link pointer + * as end of queue: + */ + temp = ((td_next & UHCI_PTR_T) || (td_next == 0)); + + printf("TD(%p) at 0x%08x = link=0x%08x status=0x%08x " + "token=0x%08x buffer=0x%08x\n", + p, + le32toh(p->td_self), + td_next, + td_status, + td_token, + le32toh(p->td_buffer)); + + printf("TD(%p) td_next=%s%s%s td_status=%s%s%s%s%s%s%s%s%s%s%s, errcnt=%d, actlen=%d pid=%02x," + "addr=%d,endpt=%d,D=%d,maxlen=%d\n", + p, + (td_next & 1) ? "-T" : "", + (td_next & 2) ? "-Q" : "", + (td_next & 4) ? "-VF" : "", + (td_status & UHCI_TD_BITSTUFF) ? "-BITSTUFF" : "", + (td_status & UHCI_TD_CRCTO) ? "-CRCTO" : "", + (td_status & UHCI_TD_NAK) ? "-NAK" : "", + (td_status & UHCI_TD_BABBLE) ? "-BABBLE" : "", + (td_status & UHCI_TD_DBUFFER) ? "-DBUFFER" : "", + (td_status & UHCI_TD_STALLED) ? "-STALLED" : "", + (td_status & UHCI_TD_ACTIVE) ? "-ACTIVE" : "", + (td_status & UHCI_TD_IOC) ? "-IOC" : "", + (td_status & UHCI_TD_IOS) ? "-IOS" : "", + (td_status & UHCI_TD_LS) ? "-LS" : "", + (td_status & UHCI_TD_SPD) ? "-SPD" : "", + UHCI_TD_GET_ERRCNT(td_status), + UHCI_TD_GET_ACTLEN(td_status), + UHCI_TD_GET_PID(td_token), + UHCI_TD_GET_DEVADDR(td_token), + UHCI_TD_GET_ENDPT(td_token), + UHCI_TD_GET_DT(td_token), + UHCI_TD_GET_MAXLEN(td_token)); + + return (temp); +} + +static uint8_t +uhci_dump_qh(uhci_qh_t *sqh) +{ + uint8_t temp; + uint32_t qh_h_next; + uint32_t qh_e_next; + + usb2_pc_cpu_invalidate(sqh->page_cache); + + qh_h_next = le32toh(sqh->qh_h_next); + qh_e_next = le32toh(sqh->qh_e_next); + + DPRINTFN(0, "QH(%p) at 0x%08x: h_next=0x%08x e_next=0x%08x\n", sqh, + le32toh(sqh->qh_self), qh_h_next, qh_e_next); + + temp = ((((sqh->h_next != NULL) && !(qh_h_next & UHCI_PTR_T)) ? 1 : 0) | + (((sqh->e_next != NULL) && !(qh_e_next & UHCI_PTR_T)) ? 2 : 0)); + + return (temp); +} + +static void +uhci_dump_all(uhci_softc_t *sc) +{ + uhci_dumpregs(sc); + uhci_dump_qh(sc->sc_ls_ctl_p_last); + uhci_dump_qh(sc->sc_fs_ctl_p_last); + uhci_dump_qh(sc->sc_bulk_p_last); + uhci_dump_qh(sc->sc_last_qh_p); + return; +} + +static void +uhci_dump_qhs(uhci_qh_t *sqh) +{ + uint8_t temp; + + temp = uhci_dump_qh(sqh); + + /* + * uhci_dump_qhs displays all the QHs and TDs from the given QH + * onwards Traverses sideways first, then down. + * + * QH1 QH2 No QH TD2.1 TD2.2 TD1.1 etc. + * + * TD2.x being the TDs queued at QH2 and QH1 being referenced from QH1. + */ + + if (temp & 1) + uhci_dump_qhs(sqh->h_next); + else + DPRINTF("No QH\n"); + + if (temp & 2) + uhci_dump_tds(sqh->e_next); + else + DPRINTF("No TD\n"); + + return; +} + +static void +uhci_dump_tds(uhci_td_t *td) +{ + for (; + td != NULL; + td = td->obj_next) { + if (uhci_dump_td(td)) { + break; + } + } + return; +} + +#endif + +/* + * Let the last QH loop back to the full speed control transfer QH. + * This is what intel calls "bandwidth reclamation" and improves + * USB performance a lot for some devices. + * If we are already looping, just count it. + */ +static void +uhci_add_loop(uhci_softc_t *sc) +{ + struct uhci_qh *qh_lst; + struct uhci_qh *qh_rec; + +#if USB_DEBUG + if (uhcinoloop) { + return; + } +#endif + if (++(sc->sc_loops) == 1) { + DPRINTFN(6, "add\n"); + + qh_lst = sc->sc_last_qh_p; + qh_rec = sc->sc_reclaim_qh_p; + + /* NOTE: we don't loop back the soft pointer */ + + qh_lst->qh_h_next = qh_rec->qh_self; + usb2_pc_cpu_flush(qh_lst->page_cache); + } + return; +} + +static void +uhci_rem_loop(uhci_softc_t *sc) +{ + struct uhci_qh *qh_lst; + +#if USB_DEBUG + if (uhcinoloop) { + return; + } +#endif + if (--(sc->sc_loops) == 0) { + DPRINTFN(6, "remove\n"); + + qh_lst = sc->sc_last_qh_p; + qh_lst->qh_h_next = htole32(UHCI_PTR_T); + usb2_pc_cpu_flush(qh_lst->page_cache); + } + return; +} + +static void +uhci_transfer_intr_enqueue(struct usb2_xfer *xfer) +{ + /* check for early completion */ + if (uhci_check_transfer(xfer)) { + return; + } + /* put transfer on interrupt queue */ + usb2_transfer_enqueue(&xfer->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, &uhci_timeout, xfer->timeout); + } + return; +} + +#define UHCI_APPEND_TD(std,last) (last) = _uhci_append_td(std,last) +static uhci_td_t * +_uhci_append_td(uhci_td_t *std, uhci_td_t *last) +{ + DPRINTFN(11, "%p to %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->next = last->next; + std->td_next = last->td_next; + + std->prev = last; + + usb2_pc_cpu_flush(std->page_cache); + + /* + * the last->next->prev is never followed: std->next->prev = std; + */ + last->next = std; + last->td_next = std->td_self; + + usb2_pc_cpu_flush(last->page_cache); + + return (std); +} + +#define UHCI_APPEND_QH(sqh,td,last) (last) = _uhci_append_qh(sqh,td,last) +static uhci_qh_t * +_uhci_append_qh(uhci_qh_t *sqh, uhci_td_t *td, uhci_qh_t *last) +{ + DPRINTFN(11, "%p to %p\n", sqh, last); + + /* (sc->sc_bus.mtx) must be locked */ + + sqh->e_next = td; + sqh->qh_e_next = td->td_self; + + sqh->h_next = last->h_next; + sqh->qh_h_next = last->qh_h_next; + + sqh->h_prev = last; + + usb2_pc_cpu_flush(sqh->page_cache); + + /* + * The "last->h_next->h_prev" is never followed: + * + * "sqh->h_next->h_prev" = sqh; + */ + + last->h_next = sqh; + last->qh_h_next = sqh->qh_self; + + usb2_pc_cpu_flush(last->page_cache); + + return (sqh); +} + +/**/ + +#define UHCI_REMOVE_TD(std,last) (last) = _uhci_remove_td(std,last) +static uhci_td_t * +_uhci_remove_td(uhci_td_t *std, uhci_td_t *last) +{ + DPRINTFN(11, "%p from %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->prev->next = std->next; + std->prev->td_next = std->td_next; + + usb2_pc_cpu_flush(std->prev->page_cache); + + if (std->next) { + std->next->prev = std->prev; + usb2_pc_cpu_flush(std->next->page_cache); + } + return ((last == std) ? std->prev : last); +} + +#define UHCI_REMOVE_QH(sqh,last) (last) = _uhci_remove_qh(sqh,last) +static uhci_qh_t * +_uhci_remove_qh(uhci_qh_t *sqh, uhci_qh_t *last) +{ + DPRINTFN(11, "%p from %p\n", sqh, last); + + /* (sc->sc_bus.mtx) must be locked */ + + /* only remove if not removed from a queue */ + if (sqh->h_prev) { + + sqh->h_prev->h_next = sqh->h_next; + sqh->h_prev->qh_h_next = sqh->qh_h_next; + + usb2_pc_cpu_flush(sqh->h_prev->page_cache); + + if (sqh->h_next) { + sqh->h_next->h_prev = sqh->h_prev; + usb2_pc_cpu_flush(sqh->h_next->page_cache); + } + /* + * set the Terminate-bit in the e_next of the QH, in case + * the transferred packet was short so that the QH still + * points at the last used TD + */ + sqh->qh_e_next = htole32(UHCI_PTR_T); + + last = ((last == sqh) ? sqh->h_prev : last); + + sqh->h_prev = 0; + + usb2_pc_cpu_flush(sqh->page_cache); + } + return (last); +} + +static void +uhci_isoc_done(uhci_softc_t *sc, struct usb2_xfer *xfer) +{ + struct usb2_page_search res; + uint32_t nframes = xfer->nframes; + uint32_t status; + uint32_t offset = 0; + uint32_t *plen = xfer->frlengths; + uint16_t len = 0; + uhci_td_t *td = xfer->td_transfer_first; + uhci_td_t **pp_last = &sc->sc_isoc_p_last[xfer->qh_pos]; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + + /* sync any DMA memory before doing fixups */ + + usb2_bdma_post_sync(xfer); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_p_last[0]; + } +#if USB_DEBUG + if (uhcidebug > 5) { + DPRINTF("isoc TD\n"); + uhci_dump_td(td); + } +#endif + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->td_status); + + len = UHCI_TD_GET_ACTLEN(status); + + if (len > *plen) { + len = *plen; + } + if (td->fix_pc) { + + usb2_get_page(td->fix_pc, 0, &res); + + /* copy data from fixup location to real location */ + + usb2_pc_cpu_invalidate(td->fix_pc); + + usb2_copy_in(xfer->frbuffers, offset, + res.buffer, len); + } + offset += *plen; + + *plen = len; + + /* remove TD from schedule */ + UHCI_REMOVE_TD(td, *pp_last); + + pp_last++; + plen++; + td = td->obj_next; + } + + xfer->aframes = xfer->nframes; + + return; +} + +static usb2_error_t +uhci_non_isoc_done_sub(struct usb2_xfer *xfer) +{ + struct usb2_page_search res; + uhci_td_t *td; + uhci_td_t *td_alt_next; + uint32_t status; + uint32_t token; + uint16_t len; + + td = xfer->td_transfer_cache; + td_alt_next = td->alt_next; + + if (xfer->aframes != xfer->nframes) { + xfer->frlengths[xfer->aframes] = 0; + } + while (1) { + + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->td_status); + token = le32toh(td->td_token); + + /* + * Verify the status and add + * up the actual length: + */ + + len = UHCI_TD_GET_ACTLEN(status); + if (len > td->len) { + /* should not happen */ + DPRINTF("Invalid status length, " + "0x%04x/0x%04x bytes\n", len, td->len); + status |= UHCI_TD_STALLED; + + } else if ((xfer->aframes != xfer->nframes) && (len > 0)) { + + if (td->fix_pc) { + + usb2_get_page(td->fix_pc, 0, &res); + + /* + * copy data from fixup location to real + * location + */ + + usb2_pc_cpu_invalidate(td->fix_pc); + + usb2_copy_in(xfer->frbuffers + xfer->aframes, + xfer->frlengths[xfer->aframes], res.buffer, len); + } + /* update actual length */ + + xfer->frlengths[xfer->aframes] += len; + } + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + td = NULL; + break; + } + if (status & UHCI_TD_STALLED) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check for short transfer */ + if (len != td->len) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + td = td->alt_next; + } else { + /* the transfer is finished */ + td = NULL; + } + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* this USB frame is complete */ + break; + } + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + /* update data toggle */ + + xfer->pipe->toggle_next = (token & UHCI_TD_SET_DT(1)) ? 0 : 1; + +#if USB_DEBUG + if (status & UHCI_TD_ERROR) { + DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x " + "status=%s%s%s%s%s%s%s%s%s%s%s\n", + xfer->address, xfer->endpoint, xfer->aframes, + (status & UHCI_TD_BITSTUFF) ? "[BITSTUFF]" : "", + (status & UHCI_TD_CRCTO) ? "[CRCTO]" : "", + (status & UHCI_TD_NAK) ? "[NAK]" : "", + (status & UHCI_TD_BABBLE) ? "[BABBLE]" : "", + (status & UHCI_TD_DBUFFER) ? "[DBUFFER]" : "", + (status & UHCI_TD_STALLED) ? "[STALLED]" : "", + (status & UHCI_TD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]", + (status & UHCI_TD_IOC) ? "[IOC]" : "", + (status & UHCI_TD_IOS) ? "[IOS]" : "", + (status & UHCI_TD_LS) ? "[LS]" : "", + (status & UHCI_TD_SPD) ? "[SPD]" : ""); + } +#endif + return (status & UHCI_TD_STALLED) ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION; +} + +static void +uhci_non_isoc_done(struct usb2_xfer *xfer) +{ + usb2_error_t err = 0; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + +#if USB_DEBUG + if (uhcidebug > 10) { + uhci_dump_tds(xfer->td_transfer_first); + } +#endif + + /* sync any DMA memory before doing fixups */ + + usb2_bdma_post_sync(xfer); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + err = uhci_non_isoc_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = uhci_non_isoc_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = uhci_non_isoc_done_sub(xfer); + } +done: + uhci_device_done(xfer, err); + return; +} + +/*------------------------------------------------------------------------* + * uhci_check_transfer_sub + * + * The main purpose of this function is to update the data-toggle + * in case it is wrong. + *------------------------------------------------------------------------*/ +static void +uhci_check_transfer_sub(struct usb2_xfer *xfer) +{ + uhci_qh_t *qh; + uhci_td_t *td; + uhci_td_t *td_alt_next; + + uint32_t td_token; + uint32_t td_self; + + td = xfer->td_transfer_cache; + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + td_token = td->obj_next->td_token; + td = td->alt_next; + xfer->td_transfer_cache = td; + td_self = td->td_self; + td_alt_next = td->alt_next; + + if ((td->td_token ^ td_token) & htole32(UHCI_TD_SET_DT(1))) { + + /* + * The data toggle is wrong and + * we need to switch it ! + */ + + while (1) { + + td->td_token ^= htole32(UHCI_TD_SET_DT(1)); + usb2_pc_cpu_flush(td->page_cache); + + if (td == xfer->td_transfer_last) { + /* last transfer */ + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* next frame */ + break; + } + } + } + /* update the QH */ + qh->qh_e_next = td_self; + usb2_pc_cpu_flush(qh->page_cache); + + DPRINTFN(13, "xfer=%p following alt next\n", xfer); + return; +} + +/*------------------------------------------------------------------------* + * uhci_check_transfer + * + * Return values: + * 0: USB transfer is not finished + * Else: USB transfer is finished + *------------------------------------------------------------------------*/ +static uint8_t +uhci_check_transfer(struct usb2_xfer *xfer) +{ + uint32_t status; + uint32_t token; + uhci_td_t *td; + + DPRINTFN(16, "xfer=%p checking transfer\n", xfer); + + if (xfer->pipe->methods == &uhci_device_isoc_methods) { + /* isochronous transfer */ + + td = xfer->td_transfer_last; + + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->td_status); + + /* check also if the first is complete */ + + td = xfer->td_transfer_first; + + usb2_pc_cpu_invalidate(td->page_cache); + status |= le32toh(td->td_status); + + if (!(status & UHCI_TD_ACTIVE)) { + uhci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + goto transferred; + } + } else { + /* non-isochronous transfer */ + + /* + * check whether there is an error somewhere + * in the middle, or whether there was a short + * packet (SPD and not ACTIVE) + */ + td = xfer->td_transfer_cache; + + while (1) { + usb2_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->td_status); + token = le32toh(td->td_token); + + /* + * if there is an active TD the transfer isn't done + */ + if (status & UHCI_TD_ACTIVE) { + /* update cache */ + xfer->td_transfer_cache = td; + goto done; + } + /* + * last transfer descriptor makes the transfer done + */ + if (((void *)td) == xfer->td_transfer_last) { + break; + } + /* + * any kind of error makes the transfer done + */ + if (status & UHCI_TD_STALLED) { + break; + } + /* + * check if we reached the last packet + * or if there is a short packet: + */ + if ((td->td_next == htole32(UHCI_PTR_T)) || + (UHCI_TD_GET_ACTLEN(status) < td->len)) { + + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + /* update cache */ + xfer->td_transfer_cache = td; + uhci_check_transfer_sub(xfer); + goto done; + } + } + /* transfer is done */ + break; + } + td = td->obj_next; + } + uhci_non_isoc_done(xfer); + goto transferred; + } + +done: + DPRINTFN(13, "xfer=%p is still active\n", xfer); + return (0); + +transferred: + return (1); +} + +static void +uhci_interrupt_poll(uhci_softc_t *sc) +{ + struct usb2_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + /* + * check if transfer is transferred + */ + if (uhci_check_transfer(xfer)) { + /* queue has been modified */ + goto repeat; + } + } + return; +} + +/*------------------------------------------------------------------------* + * uhci_interrupt - UHCI interrupt handler + * + * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, + * hence the interrupt handler will be setup before "sc->sc_bus.bdev" + * is present ! + *------------------------------------------------------------------------*/ +void +uhci_interrupt(uhci_softc_t *sc) +{ + uint32_t status; + + mtx_lock(&sc->sc_bus.mtx); + + DPRINTFN(16, "real interrupt\n"); + +#if USB_DEBUG + if (uhcidebug > 15) { + uhci_dumpregs(sc); + } +#endif + status = UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS; + if (status == 0) { + /* the interrupt was not for us */ + goto done; + } + if (status & (UHCI_STS_RD | UHCI_STS_HSE | + UHCI_STS_HCPE | UHCI_STS_HCH)) { + + if (status & UHCI_STS_RD) { +#if USB_DEBUG + printf("%s: resume detect\n", + __FUNCTION__); +#endif + } + if (status & UHCI_STS_HSE) { + printf("%s: host system error\n", + __FUNCTION__); + } + if (status & UHCI_STS_HCPE) { + printf("%s: host controller process error\n", + __FUNCTION__); + } + if (status & UHCI_STS_HCH) { + /* no acknowledge needed */ + printf("%s: host controller halted\n", + __FUNCTION__); +#if USB_DEBUG + uhci_dump_all(sc); +#endif + } + } + /* get acknowledge bits */ + status &= (UHCI_STS_USBINT | + UHCI_STS_USBEI | + UHCI_STS_RD | + UHCI_STS_HSE | + UHCI_STS_HCPE); + + if (status == 0) { + /* nothing to acknowledge */ + goto done; + } + /* acknowledge interrupts */ + UWRITE2(sc, UHCI_STS, status); + + /* poll all the USB transfers */ + uhci_interrupt_poll(sc); + +done: + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/* + * called when a request does not complete + */ +static void +uhci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + uhci_softc_t *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + uhci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +uhci_do_poll(struct usb2_bus *bus) +{ + struct uhci_softc *sc = UHCI_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + uhci_interrupt_poll(sc); + uhci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +static void +uhci_setup_standard_chain_sub(struct uhci_std_temp *temp) +{ + uhci_td_t *td; + uhci_td_t *td_next; + uhci_td_t *td_alt_next; + uint32_t average; + uint32_t len_old; + uint8_t shortpkt_old; + uint8_t precompute; + + td_alt_next = NULL; + shortpkt_old = temp->shortpkt; + len_old = temp->len; + precompute = 1; + + /* software is used to detect short incoming transfers */ + + if ((temp->td_token & htole32(UHCI_TD_PID)) == htole32(UHCI_TD_PID_IN)) { + temp->td_status |= htole32(UHCI_TD_SPD); + } else { + temp->td_status &= ~htole32(UHCI_TD_SPD); + } + + temp->ml.buf_offset = 0; + +restart: + + temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0)); + temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->average)); + + td = temp->td; + td_next = temp->td_next; + + while (1) { + + if (temp->len == 0) { + + if (temp->shortpkt) { + break; + } + /* send a Zero Length Packet, ZLP, last */ + + temp->shortpkt = 1; + temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(0)); + average = 0; + + } else { + + average = temp->average; + + if (temp->len < average) { + temp->shortpkt = 1; + temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0)); + temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->len)); + average = temp->len; + } + } + + if (td_next == NULL) { + panic("%s: out of UHCI transfer descriptors!", __FUNCTION__); + } + /* get next TD */ + + td = td_next; + td_next = td->obj_next; + + /* check if we are pre-computing */ + + if (precompute) { + + /* update remaining length */ + + temp->len -= average; + + continue; + } + /* fill out current TD */ + + td->td_status = temp->td_status; + td->td_token = temp->td_token; + + /* update data toggle */ + + temp->td_token ^= htole32(UHCI_TD_SET_DT(1)); + + if (average == 0) { + + td->len = 0; + td->td_buffer = 0; + td->fix_pc = NULL; + + } else { + + /* update remaining length */ + + temp->len -= average; + + td->len = average; + + /* fill out buffer pointer and do fixup, if any */ + + uhci_mem_layout_fixup(&temp->ml, td); + } + + td->alt_next = td_alt_next; + + if ((td_next == td_alt_next) && temp->setup_alt_next) { + /* we need to receive these frames one by one ! */ + td->td_status |= htole32(UHCI_TD_IOC); + td->td_next = htole32(UHCI_PTR_T); + } else { + if (td_next) { + /* link the current TD with the next one */ + td->td_next = td_next->td_self; + } + } + + usb2_pc_cpu_flush(td->page_cache); + } + + if (precompute) { + precompute = 0; + + /* setup alt next pointer, if any */ + if (temp->short_frames_ok) { + if (temp->setup_alt_next) { + td_alt_next = td_next; + } + } else { + /* we use this field internally */ + td_alt_next = td_next; + } + + /* restore */ + temp->shortpkt = shortpkt_old; + temp->len = len_old; + goto restart; + } + temp->td = td; + temp->td_next = td_next; + + return; +} + +static uhci_td_t * +uhci_setup_standard_chain(struct usb2_xfer *xfer) +{ + struct uhci_std_temp temp; + uhci_td_t *td; + uint32_t x; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpoint), + xfer->sumlen, usb2_get_speed(xfer->udev)); + + temp.average = xfer->max_frame_size; + temp.max_frame_size = xfer->max_frame_size; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + temp.td = NULL; + temp.td_next = td; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.short_frames_ok = xfer->flags_int.short_frames_ok; + + uhci_mem_layout_init(&temp.ml, xfer); + + temp.td_status = + htole32(UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) | + UHCI_TD_ACTIVE)); + + if (xfer->udev->speed == USB_SPEED_LOW) { + temp.td_status |= htole32(UHCI_TD_LS); + } + temp.td_token = + htole32(UHCI_TD_SET_ENDPT(xfer->endpoint) | + UHCI_TD_SET_DEVADDR(xfer->address)); + + if (xfer->pipe->toggle_next) { + /* DATA1 is next */ + temp.td_token |= htole32(UHCI_TD_SET_DT(1)); + } + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | + UHCI_TD_SET_ENDPT(0xF)); + temp.td_token |= htole32(UHCI_TD_PID_SETUP | + UHCI_TD_SET_DT(0)); + + temp.len = xfer->frlengths[0]; + temp.ml.buf_pc = xfer->frbuffers + 0; + temp.shortpkt = temp.len ? 1 : 0; + + uhci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + temp.ml.buf_pc = xfer->frbuffers + x; + + x++; + + if (x == xfer->nframes) { + temp.setup_alt_next = 0; + } + /* + * Keep previous data toggle, + * device address and endpoint number: + */ + + temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | + UHCI_TD_SET_ENDPT(0xF) | + UHCI_TD_SET_DT(1)); + + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.shortpkt = 0; + + } else { + + /* regular data transfer */ + + temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + /* set endpoint direction */ + + temp.td_token |= + (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) ? + htole32(UHCI_TD_PID_IN) : + htole32(UHCI_TD_PID_OUT); + + uhci_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * send a DATA1 message and reverse the current endpoint + * direction + */ + + temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | + UHCI_TD_SET_ENDPT(0xF) | + UHCI_TD_SET_DT(1)); + temp.td_token |= + (UE_GET_DIR(xfer->endpoint) == UE_DIR_OUT) ? + htole32(UHCI_TD_PID_IN | UHCI_TD_SET_DT(1)) : + htole32(UHCI_TD_PID_OUT | UHCI_TD_SET_DT(1)); + + temp.len = 0; + temp.ml.buf_pc = NULL; + temp.shortpkt = 0; + + uhci_setup_standard_chain_sub(&temp); + } + td = temp.td; + + td->td_next = htole32(UHCI_PTR_T); + + /* set interrupt bit */ + + td->td_status |= htole32(UHCI_TD_IOC); + + usb2_pc_cpu_flush(td->page_cache); + + /* must have at least one frame! */ + + xfer->td_transfer_last = td; + +#if USB_DEBUG + if (uhcidebug > 8) { + DPRINTF("nexttog=%d; data before transfer:\n", + xfer->pipe->toggle_next); + uhci_dump_tds(xfer->td_transfer_first); + } +#endif + return (xfer->td_transfer_first); +} + +/* NOTE: "done" can be run two times in a row, + * from close and from interrupt + */ + +static void +uhci_device_done(struct usb2_xfer *xfer, usb2_error_t error) +{ + struct usb2_pipe_methods *methods = xfer->pipe->methods; + uhci_softc_t *sc = xfer->usb2_sc; + uhci_qh_t *qh; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n", + xfer, xfer->pipe, error); + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + if (qh) { + usb2_pc_cpu_invalidate(qh->page_cache); + + qh->e_next = 0; + qh->qh_e_next = htole32(UHCI_PTR_T); + + usb2_pc_cpu_flush(qh->page_cache); + } + if (xfer->flags_int.bandwidth_reclaimed) { + xfer->flags_int.bandwidth_reclaimed = 0; + uhci_rem_loop(sc); + } + if (methods == &uhci_device_bulk_methods) { + UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last); + } + if (methods == &uhci_device_ctrl_methods) { + if (xfer->udev->speed == USB_SPEED_LOW) { + UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last); + } else { + UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last); + } + } + if (methods == &uhci_device_intr_methods) { + UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); + } + /* + * Only finish isochronous transfers once + * which will update "xfer->frlengths". + */ + if (xfer->td_transfer_first && + xfer->td_transfer_last) { + if (methods == &uhci_device_isoc_methods) { + uhci_isoc_done(sc, xfer); + } + xfer->td_transfer_first = NULL; + xfer->td_transfer_last = NULL; + } + /* dequeue transfer and start next transfer */ + usb2_transfer_done(xfer, error); + return; +} + +/*------------------------------------------------------------------------* + * uhci bulk support + *------------------------------------------------------------------------*/ +static void +uhci_device_bulk_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_device_bulk_close(struct usb2_xfer *xfer) +{ + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uhci_device_bulk_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_device_bulk_start(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + uhci_td_t *td; + uhci_qh_t *qh; + + /* setup TD's */ + td = uhci_setup_standard_chain(xfer); + + /* setup QH */ + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + UHCI_APPEND_QH(qh, td, sc->sc_bulk_p_last); + uhci_add_loop(sc); + xfer->flags_int.bandwidth_reclaimed = 1; + + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods uhci_device_bulk_methods = +{ + .open = uhci_device_bulk_open, + .close = uhci_device_bulk_close, + .enter = uhci_device_bulk_enter, + .start = uhci_device_bulk_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * uhci control support + *------------------------------------------------------------------------*/ +static void +uhci_device_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_device_ctrl_close(struct usb2_xfer *xfer) +{ + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uhci_device_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_device_ctrl_start(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + uhci_qh_t *qh; + uhci_td_t *td; + + /* setup TD's */ + td = uhci_setup_standard_chain(xfer); + + /* setup QH */ + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + /* + * NOTE: some devices choke on bandwidth- reclamation for control + * transfers + */ + if (xfer->udev->speed == USB_SPEED_LOW) { + UHCI_APPEND_QH(qh, td, sc->sc_ls_ctl_p_last); + } else { + UHCI_APPEND_QH(qh, td, sc->sc_fs_ctl_p_last); + } + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods uhci_device_ctrl_methods = +{ + .open = uhci_device_ctrl_open, + .close = uhci_device_ctrl_close, + .enter = uhci_device_ctrl_enter, + .start = uhci_device_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * uhci interrupt support + *------------------------------------------------------------------------*/ +static void +uhci_device_intr_open(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + uint16_t best; + uint16_t bit; + uint16_t x; + + best = 0; + bit = UHCI_IFRAMELIST_COUNT / 2; + while (bit) { + if (xfer->interval >= bit) { + x = bit; + best = bit; + while (x & bit) { + if (sc->sc_intr_stat[x] < + sc->sc_intr_stat[best]) { + best = x; + } + x++; + } + break; + } + bit >>= 1; + } + + sc->sc_intr_stat[best]++; + xfer->qh_pos = best; + + DPRINTFN(3, "best=%d interval=%d\n", + best, xfer->interval); + return; +} + +static void +uhci_device_intr_close(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + + sc->sc_intr_stat[xfer->qh_pos]--; + + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uhci_device_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_device_intr_start(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + uhci_qh_t *qh; + uhci_td_t *td; + + /* setup TD's */ + td = uhci_setup_standard_chain(xfer); + + /* setup QH */ + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + /* enter QHs into the controller data structures */ + UHCI_APPEND_QH(qh, td, sc->sc_intr_p_last[xfer->qh_pos]); + + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods uhci_device_intr_methods = +{ + .open = uhci_device_intr_open, + .close = uhci_device_intr_close, + .enter = uhci_device_intr_enter, + .start = uhci_device_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * uhci isochronous support + *------------------------------------------------------------------------*/ +static void +uhci_device_isoc_open(struct usb2_xfer *xfer) +{ + uhci_td_t *td; + uint32_t td_token; + uint8_t ds; + + td_token = + (UE_GET_DIR(xfer->endpoint) == UE_DIR_IN) ? + UHCI_TD_IN(0, xfer->endpoint, xfer->address, 0) : + UHCI_TD_OUT(0, xfer->endpoint, xfer->address, 0); + + td_token = htole32(td_token); + + /* initialize all TD's */ + + for (ds = 0; ds != 2; ds++) { + + for (td = xfer->td_start[ds]; td; td = td->obj_next) { + + /* mark TD as inactive */ + td->td_status = htole32(UHCI_TD_IOS); + td->td_token = td_token; + + usb2_pc_cpu_flush(td->page_cache); + } + } + return; +} + +static void +uhci_device_isoc_close(struct usb2_xfer *xfer) +{ + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uhci_device_isoc_enter(struct usb2_xfer *xfer) +{ + struct uhci_mem_layout ml; + uhci_softc_t *sc = xfer->usb2_sc; + uint32_t nframes; + uint32_t temp; + uint32_t *plen; + +#if USB_DEBUG + uint8_t once = 1; + +#endif + uhci_td_t *td; + uhci_td_t *td_last = NULL; + uhci_td_t **pp_last; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->pipe->isoc_next, xfer->nframes); + + nframes = UREAD2(sc, UHCI_FRNUM); + + temp = (nframes - xfer->pipe->isoc_next) & + (UHCI_VFRAMELIST_COUNT - 1); + + if ((xfer->pipe->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->pipe->isoc_next = (nframes + 3) & (UHCI_VFRAMELIST_COUNT - 1); + xfer->pipe->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->pipe->isoc_next - nframes) & + (UHCI_VFRAMELIST_COUNT - 1); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* get the real number of frames */ + + nframes = xfer->nframes; + + uhci_mem_layout_init(&ml, xfer); + + plen = xfer->frlengths; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + xfer->td_transfer_first = td; + + pp_last = &sc->sc_isoc_p_last[xfer->pipe->isoc_next]; + + /* store starting position */ + + xfer->qh_pos = xfer->pipe->isoc_next; + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_p_last[0]; + } + if (*plen > xfer->max_frame_size) { +#if USB_DEBUG + if (once) { + once = 0; + printf("%s: frame length(%d) exceeds %d " + "bytes (frame truncated)\n", + __FUNCTION__, *plen, + xfer->max_frame_size); + } +#endif + *plen = xfer->max_frame_size; + } + /* reuse td_token from last transfer */ + + td->td_token &= htole32(~UHCI_TD_MAXLEN_MASK); + td->td_token |= htole32(UHCI_TD_SET_MAXLEN(*plen)); + + td->len = *plen; + + if (td->len == 0) { + /* + * Do not call "uhci_mem_layout_fixup()" when the + * length is zero! + */ + td->td_buffer = 0; + td->fix_pc = NULL; + + } else { + + /* fill out buffer pointer and do fixup, if any */ + + uhci_mem_layout_fixup(&ml, td); + + } + + /* update status */ + if (nframes == 0) { + td->td_status = htole32 + (UHCI_TD_ZERO_ACTLEN + (UHCI_TD_SET_ERRCNT(0) | + UHCI_TD_ACTIVE | + UHCI_TD_IOS | + UHCI_TD_IOC)); + } else { + td->td_status = htole32 + (UHCI_TD_ZERO_ACTLEN + (UHCI_TD_SET_ERRCNT(0) | + UHCI_TD_ACTIVE | + UHCI_TD_IOS)); + } + + usb2_pc_cpu_flush(td->page_cache); + +#if USB_DEBUG + if (uhcidebug > 5) { + DPRINTF("TD %d\n", nframes); + uhci_dump_td(td); + } +#endif + /* insert TD into schedule */ + UHCI_APPEND_TD(td, *pp_last); + pp_last++; + + plen++; + td_last = td; + td = td->obj_next; + } + + xfer->td_transfer_last = td_last; + + /* update isoc_next */ + xfer->pipe->isoc_next = (pp_last - &sc->sc_isoc_p_last[0]) & + (UHCI_VFRAMELIST_COUNT - 1); + + return; +} + +static void +uhci_device_isoc_start(struct usb2_xfer *xfer) +{ + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); + return; +} + +struct usb2_pipe_methods uhci_device_isoc_methods = +{ + .open = uhci_device_isoc_open, + .close = uhci_device_isoc_close, + .enter = uhci_device_isoc_enter, + .start = uhci_device_isoc_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * uhci root control support + *------------------------------------------------------------------------* + * simulate a hardware hub by handling + * all the necessary requests + *------------------------------------------------------------------------*/ + +static void +uhci_root_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_root_ctrl_close(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* data structures and routines + * to emulate the root hub: + */ + +static const +struct usb2_device_descriptor uhci_devd = +{ + sizeof(struct usb2_device_descriptor), + UDESC_DEVICE, /* type */ + {0x00, 0x01}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 64, /* max packet */ + {0}, {0}, {0x00, 0x01}, /* device id */ + 1, 2, 0, /* string indicies */ + 1 /* # of configurations */ +}; + +static const struct uhci_config_desc uhci_confd = { + .confd = { + .bLength = sizeof(struct usb2_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(uhci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0 /* max power */ + }, + + .ifcd = { + .bLength = sizeof(struct usb2_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = UIPROTO_FSHUB, + }, + + .endpd = { + .bLength = sizeof(struct usb2_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = UE_DIR_IN | UHCI_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ + .bInterval = 255, + }, +}; + +static const +struct usb2_hub_descriptor_min uhci_hubd_piix = +{ + sizeof(uhci_hubd_piix), + UDESC_HUB, + 2, + {UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0}, + 50, /* power on to power good */ + 0, + {0x00}, /* both ports are removable */ +}; + +/* + * The USB hub protocol requires that SET_FEATURE(PORT_RESET) also + * enables the port, and also states that SET_FEATURE(PORT_ENABLE) + * should not be used by the USB subsystem. As we cannot issue a + * SET_FEATURE(PORT_ENABLE) externally, we must ensure that the port + * will be enabled as part of the reset. + * + * On the VT83C572, the port cannot be successfully enabled until the + * outstanding "port enable change" and "connection status change" + * events have been reset. + */ +static usb2_error_t +uhci_portreset(uhci_softc_t *sc, uint16_t index, uint8_t use_polling) +{ + uint16_t port; + uint16_t x; + uint8_t lim; + + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else + return (USB_ERR_IOERROR); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PR); + + if (use_polling) { + /* polling */ + DELAY(USB_PORT_ROOT_RESET_DELAY * 1000); + } else { + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_PORT_ROOT_RESET_DELAY); + } + + DPRINTFN(4, "uhci port %d reset, status0 = 0x%04x\n", + index, UREAD2(sc, port)); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); + + if (use_polling) { + /* polling */ + DELAY(1000); + } else { + usb2_pause_mtx(&sc->sc_bus.mtx, 1); + } + + DPRINTFN(4, "uhci port %d reset, status1 = 0x%04x\n", + index, UREAD2(sc, port)); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PE); + + for (lim = 0; lim < 12; lim++) { + + if (use_polling) { + /* polling */ + DELAY(USB_PORT_RESET_DELAY * 1000); + } else { + usb2_pause_mtx(&sc->sc_bus.mtx, + USB_PORT_RESET_DELAY); + } + + x = UREAD2(sc, port); + + DPRINTFN(4, "uhci port %d iteration %u, status = 0x%04x\n", + index, lim, x); + + if (!(x & UHCI_PORTSC_CCS)) { + /* + * No device is connected (or was disconnected + * during reset). Consider the port reset. + * The delay must be long enough to ensure on + * the initial iteration that the device + * connection will have been registered. 50ms + * appears to be sufficient, but 20ms is not. + */ + DPRINTFN(4, "uhci port %d loop %u, device detached\n", + index, lim); + goto done; + } + if (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)) { + /* + * Port enabled changed and/or connection + * status changed were set. Reset either or + * both raised flags (by writing a 1 to that + * bit), and wait again for state to settle. + */ + UWRITE2(sc, port, URWMASK(x) | + (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC))); + continue; + } + if (x & UHCI_PORTSC_PE) { + /* port is enabled */ + goto done; + } + UWRITE2(sc, port, URWMASK(x) | UHCI_PORTSC_PE); + } + + DPRINTFN(2, "uhci port %d reset timed out\n", index); + return (USB_ERR_TIMEOUT); + +done: + DPRINTFN(4, "uhci port %d reset, status2 = 0x%04x\n", + index, UREAD2(sc, port)); + + sc->sc_isreset = 1; + return (USB_ERR_NORMAL_COMPLETION); +} + +static void +uhci_root_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_root_ctrl_start(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + + DPRINTF("\n"); + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &uhci_root_ctrl_task, 0, 0); + + return; +} + +static void +uhci_root_ctrl_task(struct uhci_softc *sc, + struct uhci_config_copy *cc, uint16_t refcount) +{ + uhci_root_ctrl_poll(sc); + return; +} + +static void +uhci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + uhci_softc_t *sc = xfer->usb2_sc; + char *ptr; + uint16_t x; + uint16_t port; + uint16_t value; + uint16_t index; + uint16_t status; + uint16_t change; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_SETUP) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + uhci_device_done(xfer, std->err); + } + goto done; + } + /* buffer reset */ + std->ptr = sc->sc_hub_desc.temp; + std->len = 0; + + value = UGETW(std->req.wValue); + index = UGETW(std->req.wIndex); + + use_polling = mtx_owned(xfer->priv_mtx) ? 1 : 0; + + DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " + "wValue=0x%04x wIndex=0x%04x\n", + std->req.bmRequestType, std->req.bRequest, + UGETW(std->req.wLength), value, index); + +#define C(x,y) ((x) | ((y) << 8)) + switch (C(std->req.bRequest, std->req.bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + std->len = 1; + sc->sc_hub_desc.temp[0] = sc->sc_conf; + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + switch (value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = sizeof(uhci_devd); + sc->sc_hub_desc.devd = uhci_devd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = sizeof(uhci_confd); + std->ptr = USB_ADD_BYTES(&uhci_confd, 0); + break; + + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + ptr = "\001"; + break; + + case 1: /* Vendor */ + ptr = sc->sc_vendor; + break; + + case 2: /* Product */ + ptr = "UHCI root HUB"; + break; + + default: + ptr = ""; + break; + } + + std->len = usb2_make_str_desc + (sc->sc_hub_desc.temp, + sizeof(sc->sc_hub_desc.temp), + ptr); + break; + + default: + std->err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + std->len = 1; + sc->sc_hub_desc.temp[0] = 0; + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + std->len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + std->len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, 0); + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= USB_MAX_DEVICES) { + std->err = USB_ERR_IOERROR; + goto done; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if ((value != 0) && (value != 1)) { + std->err = USB_ERR_IOERROR; + goto done; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + std->err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(4, "UR_CLEAR_PORT_FEATURE " + "port=%d feature=%d\n", + index, value); + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + std->err = USB_ERR_IOERROR; + goto done; + } + switch (value) { + case UHF_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PE); + break; + case UHF_PORT_SUSPEND: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_SUSP); + break; + case UHF_PORT_RESET: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); + break; + case UHF_C_PORT_CONNECTION: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_CSC); + break; + case UHF_C_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_POEDC); + break; + case UHF_C_PORT_OVER_CURRENT: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_OCIC); + break; + case UHF_C_PORT_RESET: + sc->sc_isreset = 0; + std->err = USB_ERR_NORMAL_COMPLETION; + goto done; + case UHF_PORT_CONNECTION: + case UHF_PORT_OVER_CURRENT: + case UHF_PORT_POWER: + case UHF_PORT_LOW_SPEED: + case UHF_C_PORT_SUSPEND: + default: + std->err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_BUS_STATE, UT_READ_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = 1; + sc->sc_hub_desc.temp[0] = + ((UREAD2(sc, port) & UHCI_PORTSC_LS) >> + UHCI_PORTSC_LS_SHIFT); + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + std->err = USB_ERR_IOERROR; + goto done; + } + std->len = sizeof(uhci_hubd_piix); + std->ptr = USB_ADD_BYTES(&uhci_hubd_piix, 0); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + std->len = 16; + bzero(sc->sc_hub_desc.temp, 16); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + std->err = USB_ERR_IOERROR; + goto done; + } + x = UREAD2(sc, port); + status = change = 0; + if (x & UHCI_PORTSC_CCS) + status |= UPS_CURRENT_CONNECT_STATUS; + if (x & UHCI_PORTSC_CSC) + change |= UPS_C_CONNECT_STATUS; + if (x & UHCI_PORTSC_PE) + status |= UPS_PORT_ENABLED; + if (x & UHCI_PORTSC_POEDC) + change |= UPS_C_PORT_ENABLED; + if (x & UHCI_PORTSC_OCI) + status |= UPS_OVERCURRENT_INDICATOR; + if (x & UHCI_PORTSC_OCIC) + change |= UPS_C_OVERCURRENT_INDICATOR; + if (x & UHCI_PORTSC_SUSP) + status |= UPS_SUSPEND; + if (x & UHCI_PORTSC_LSDA) + status |= UPS_LOW_SPEED; + status |= UPS_PORT_POWER; + if (sc->sc_isreset) + change |= UPS_C_PORT_RESET; + USETW(sc->sc_hub_desc.ps.wPortStatus, status); + USETW(sc->sc_hub_desc.ps.wPortChange, change); + std->len = sizeof(sc->sc_hub_desc.ps); + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + std->err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + std->err = USB_ERR_IOERROR; + goto done; + } + switch (value) { + case UHF_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PE); + break; + case UHF_PORT_SUSPEND: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_SUSP); + break; + case UHF_PORT_RESET: + std->err = uhci_portreset(sc, index, use_polling); + goto done; + case UHF_PORT_POWER: + /* pretend we turned on power */ + std->err = USB_ERR_NORMAL_COMPLETION; + goto done; + case UHF_C_PORT_CONNECTION: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_PORT_CONNECTION: + case UHF_PORT_OVER_CURRENT: + case UHF_PORT_LOW_SPEED: + case UHF_C_PORT_SUSPEND: + case UHF_C_PORT_RESET: + default: + std->err = USB_ERR_IOERROR; + goto done; + } + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } +done: + return; +} + +static void +uhci_root_ctrl_poll(struct uhci_softc *sc) +{ + usb2_sw_transfer(&sc->sc_root_ctrl, + &uhci_root_ctrl_done); + return; +} + +struct usb2_pipe_methods uhci_root_ctrl_methods = +{ + .open = uhci_root_ctrl_open, + .close = uhci_root_ctrl_close, + .enter = uhci_root_ctrl_enter, + .start = uhci_root_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 0, +}; + +/*------------------------------------------------------------------------* + * uhci root interrupt support + *------------------------------------------------------------------------*/ +static void +uhci_root_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_root_intr_close(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + uhci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uhci_root_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_root_intr_start(struct usb2_xfer *xfer) +{ + uhci_softc_t *sc = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + + usb2_transfer_timeout_ms(xfer, + &uhci_root_intr_check, xfer->interval); + return; +} + +static void +uhci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + uhci_softc_t *sc = xfer->usb2_sc; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_PRE_DATA) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer is transferred */ + uhci_device_done(xfer, std->err); + } + goto done; + } + /* setup buffer */ + std->ptr = sc->sc_hub_idata; + std->len = sizeof(sc->sc_hub_idata); +done: + return; +} + +/* + * this routine is executed periodically and simulates interrupts + * from the root controller interrupt pipe for port status change + */ +static void +uhci_root_intr_check(void *arg) +{ + struct usb2_xfer *xfer = arg; + uhci_softc_t *sc = xfer->usb2_sc; + + DPRINTFN(21, "\n"); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + sc->sc_hub_idata[0] = 0; + + if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC | UHCI_PORTSC_OCIC)) { + sc->sc_hub_idata[0] |= 1 << 1; + } + if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC | UHCI_PORTSC_OCIC)) { + sc->sc_hub_idata[0] |= 1 << 2; + } + if ((sc->sc_hub_idata[0] == 0) || !(UREAD2(sc, UHCI_CMD) & UHCI_CMD_RS)) { + /* + * no change or controller not running, try again in a while + */ + uhci_root_intr_start(xfer); + } else { + usb2_sw_transfer(&sc->sc_root_intr, + &uhci_root_intr_done); + } + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +struct usb2_pipe_methods uhci_root_intr_methods = +{ + .open = uhci_root_intr_open, + .close = uhci_root_intr_close, + .enter = uhci_root_intr_enter, + .start = uhci_root_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +static void +uhci_xfer_setup(struct usb2_setup_params *parm) +{ + struct usb2_page_search page_info; + struct usb2_page_cache *pc; + uhci_softc_t *sc; + struct usb2_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t nqh; + uint32_t nfixup; + uint32_t n; + uint16_t align; + + sc = UHCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x500; + + /* + * compute ntd and nqh + */ + if (parm->methods == &uhci_device_ctrl_methods) { + xfer->flags_int.bdma_enable = 1; + xfer->flags_int.bdma_no_post_sync = 1; + + usb2_transfer_setup_sub(parm); + + /* see EHCI HC driver for proof of "ntd" formula */ + + nqh = 1; + ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + + (xfer->max_data_length / xfer->max_frame_size)); + + } else if (parm->methods == &uhci_device_bulk_methods) { + xfer->flags_int.bdma_enable = 1; + xfer->flags_int.bdma_no_post_sync = 1; + + usb2_transfer_setup_sub(parm); + + nqh = 1; + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_frame_size)); + + } else if (parm->methods == &uhci_device_intr_methods) { + xfer->flags_int.bdma_enable = 1; + xfer->flags_int.bdma_no_post_sync = 1; + + usb2_transfer_setup_sub(parm); + + nqh = 1; + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_frame_size)); + + } else if (parm->methods == &uhci_device_isoc_methods) { + xfer->flags_int.bdma_enable = 1; + xfer->flags_int.bdma_no_post_sync = 1; + + usb2_transfer_setup_sub(parm); + + nqh = 0; + ntd = xfer->nframes; + + } else { + + usb2_transfer_setup_sub(parm); + + nqh = 0; + ntd = 0; + } + + if (parm->err) { + return; + } + /* + * NOTE: the UHCI controller requires that + * every packet must be contiguous on + * the same USB memory page ! + */ + nfixup = (parm->bufsize / USB_PAGE_SIZE) + 1; + + /* + * Compute a suitable power of two alignment + * for our "max_frame_size" fixup buffer(s): + */ + align = xfer->max_frame_size; + n = 0; + while (align) { + align >>= 1; + n++; + } + + /* check for power of two */ + if (!(xfer->max_frame_size & + (xfer->max_frame_size - 1))) { + n--; + } + /* + * We don't allow alignments of + * less than 8 bytes: + * + * NOTE: Allocating using an aligment + * of 1 byte has special meaning! + */ + if (n < 3) { + n = 3; + } + align = (1 << n); + + if (usb2_transfer_setup_sub_malloc( + parm, &pc, xfer->max_frame_size, + align, nfixup)) { + parm->err = USB_ERR_NOMEM; + return; + } + xfer->buf_fixup = pc; + +alloc_dma_set: + + if (parm->err) { + return; + } + last_obj = NULL; + + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(uhci_td_t), + UHCI_TD_ALIGN, ntd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != ntd; n++) { + uhci_td_t *td; + + usb2_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + if ((parm->methods == &uhci_device_bulk_methods) || + (parm->methods == &uhci_device_ctrl_methods) || + (parm->methods == &uhci_device_intr_methods)) { + /* set depth first bit */ + td->td_self = htole32(page_info.physaddr | + UHCI_PTR_TD | UHCI_PTR_VF); + } else { + td->td_self = htole32(page_info.physaddr | + UHCI_PTR_TD); + } + + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb2_pc_cpu_flush(pc + n); + } + } + xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; + + last_obj = NULL; + + if (usb2_transfer_setup_sub_malloc( + parm, &pc, sizeof(uhci_qh_t), + UHCI_QH_ALIGN, nqh)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqh; n++) { + uhci_qh_t *qh; + + usb2_get_page(pc + n, 0, &page_info); + + qh = page_info.buffer; + + /* init QH */ + qh->qh_self = htole32(page_info.physaddr | UHCI_PTR_QH); + qh->obj_next = last_obj; + qh->page_cache = pc + n; + + last_obj = qh; + + usb2_pc_cpu_flush(pc + n); + } + } + xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; + + if (!xfer->flags_int.curr_dma_set) { + xfer->flags_int.curr_dma_set = 1; + goto alloc_dma_set; + } + return; +} + +static void +uhci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc, + struct usb2_pipe *pipe) +{ + uhci_softc_t *sc = UHCI_BUS2SC(udev->bus); + + DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + pipe, udev->address, + edesc->bEndpointAddress, udev->flags.usb2_mode, + sc->sc_addr); + + if (udev->flags.usb2_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->device_index == sc->sc_addr) { + switch (edesc->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &uhci_root_ctrl_methods; + break; + case UE_DIR_IN | UHCI_INTR_ENDPT: + pipe->methods = &uhci_root_intr_methods; + break; + default: + /* do nothing */ + break; + } + } else { + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + pipe->methods = &uhci_device_ctrl_methods; + break; + case UE_INTERRUPT: + pipe->methods = &uhci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + if (udev->speed == USB_SPEED_FULL) { + pipe->methods = &uhci_device_isoc_methods; + } + break; + case UE_BULK: + if (udev->speed != USB_SPEED_LOW) { + pipe->methods = &uhci_device_bulk_methods; + } + break; + default: + /* do nothing */ + break; + } + } + return; +} + +static void +uhci_xfer_unsetup(struct usb2_xfer *xfer) +{ + return; +} + +static void +uhci_get_dma_delay(struct usb2_bus *bus, uint32_t *pus) +{ + /* + * Wait until hardware has finished any possible use of the + * transfer descriptor(s) and QH + */ + *pus = (1125); /* microseconds */ + return; +} + +struct usb2_bus_methods uhci_bus_methods = +{ + .pipe_init = uhci_pipe_init, + .xfer_setup = uhci_xfer_setup, + .xfer_unsetup = uhci_xfer_unsetup, + .do_poll = uhci_do_poll, + .get_dma_delay = uhci_get_dma_delay, +}; diff --git a/sys/dev/usb2/controller/uhci2.h b/sys/dev/usb2/controller/uhci2.h new file mode 100644 index 000000000000..9be89ed349be --- /dev/null +++ b/sys/dev/usb2/controller/uhci2.h @@ -0,0 +1,318 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#ifndef _UHCI_H_ +#define _UHCI_H_ + +/* PCI config registers */ +#define PCI_USBREV 0x60 /* USB protocol revision */ +#define PCI_USB_REV_MASK 0xff +#define PCI_USB_REV_PRE_1_0 0x00 +#define PCI_USB_REV_1_0 0x10 +#define PCI_USB_REV_1_1 0x11 +#define PCI_LEGSUP 0xc0 /* Legacy Support register */ +#define PCI_LEGSUP_USBPIRQDEN 0x2000 /* USB PIRQ D Enable */ +#define PCI_CBIO 0x20 /* configuration base IO */ +#define PCI_INTERFACE_UHCI 0x00 + +/* UHCI registers */ +#define UHCI_CMD 0x00 +#define UHCI_CMD_RS 0x0001 +#define UHCI_CMD_HCRESET 0x0002 +#define UHCI_CMD_GRESET 0x0004 +#define UHCI_CMD_EGSM 0x0008 +#define UHCI_CMD_FGR 0x0010 +#define UHCI_CMD_SWDBG 0x0020 +#define UHCI_CMD_CF 0x0040 +#define UHCI_CMD_MAXP 0x0080 +#define UHCI_STS 0x02 +#define UHCI_STS_USBINT 0x0001 +#define UHCI_STS_USBEI 0x0002 +#define UHCI_STS_RD 0x0004 +#define UHCI_STS_HSE 0x0008 +#define UHCI_STS_HCPE 0x0010 +#define UHCI_STS_HCH 0x0020 +#define UHCI_STS_ALLINTRS 0x003f +#define UHCI_INTR 0x04 +#define UHCI_INTR_TOCRCIE 0x0001 +#define UHCI_INTR_RIE 0x0002 +#define UHCI_INTR_IOCE 0x0004 +#define UHCI_INTR_SPIE 0x0008 +#define UHCI_FRNUM 0x06 +#define UHCI_FRNUM_MASK 0x03ff +#define UHCI_FLBASEADDR 0x08 +#define UHCI_SOF 0x0c +#define UHCI_SOF_MASK 0x7f +#define UHCI_PORTSC1 0x010 +#define UHCI_PORTSC2 0x012 +#define UHCI_PORTSC_CCS 0x0001 +#define UHCI_PORTSC_CSC 0x0002 +#define UHCI_PORTSC_PE 0x0004 +#define UHCI_PORTSC_POEDC 0x0008 +#define UHCI_PORTSC_LS 0x0030 +#define UHCI_PORTSC_LS_SHIFT 4 +#define UHCI_PORTSC_RD 0x0040 +#define UHCI_PORTSC_LSDA 0x0100 +#define UHCI_PORTSC_PR 0x0200 +#define UHCI_PORTSC_OCI 0x0400 +#define UHCI_PORTSC_OCIC 0x0800 +#define UHCI_PORTSC_SUSP 0x1000 + +#define URWMASK(x) ((x) & (UHCI_PORTSC_SUSP | \ + UHCI_PORTSC_PR | UHCI_PORTSC_RD | \ + UHCI_PORTSC_PE)) + +#define UHCI_FRAMELIST_COUNT 1024 /* units */ +#define UHCI_FRAMELIST_ALIGN 4096 /* bytes */ + +/* Structures alignment (bytes) */ +#define UHCI_TD_ALIGN 16 +#define UHCI_QH_ALIGN 16 + +#if ((USB_PAGE_SIZE < UHCI_TD_ALIGN) || (UHCI_TD_ALIGN == 0) || \ + (USB_PAGE_SIZE < UHCI_QH_ALIGN) || (UHCI_QH_ALIGN == 0)) +#error "Invalid USB page size!" +#endif + +typedef uint32_t uhci_physaddr_t; + +#define UHCI_PTR_T 0x00000001 +#define UHCI_PTR_TD 0x00000000 +#define UHCI_PTR_QH 0x00000002 +#define UHCI_PTR_VF 0x00000004 + +#define UHCI_QH_REMOVE_DELAY 5 /* us - QH remove delay */ + +/* + * The Queue Heads (QH) and Transfer Descriptors (TD) are accessed by + * both the CPU and the USB-controller which run concurrently. Great + * care must be taken. When the data-structures are linked into the + * USB controller's frame list, the USB-controller "owns" the + * td_status and qh_elink fields, which will not be written by the + * CPU. + * + */ + +struct uhci_td { +/* + * Data used by the UHCI controller. + * volatile is used in order to mantain struct members ordering. + */ + volatile uint32_t td_next; + volatile uint32_t td_status; +#define UHCI_TD_GET_ACTLEN(s) (((s) + 1) & 0x3ff) +#define UHCI_TD_ZERO_ACTLEN(t) ((t) | 0x3ff) +#define UHCI_TD_BITSTUFF 0x00020000 +#define UHCI_TD_CRCTO 0x00040000 +#define UHCI_TD_NAK 0x00080000 +#define UHCI_TD_BABBLE 0x00100000 +#define UHCI_TD_DBUFFER 0x00200000 +#define UHCI_TD_STALLED 0x00400000 +#define UHCI_TD_ACTIVE 0x00800000 +#define UHCI_TD_IOC 0x01000000 +#define UHCI_TD_IOS 0x02000000 +#define UHCI_TD_LS 0x04000000 +#define UHCI_TD_GET_ERRCNT(s) (((s) >> 27) & 3) +#define UHCI_TD_SET_ERRCNT(n) ((n) << 27) +#define UHCI_TD_SPD 0x20000000 + volatile uint32_t td_token; +#define UHCI_TD_PID 0x000000ff +#define UHCI_TD_PID_IN 0x00000069 +#define UHCI_TD_PID_OUT 0x000000e1 +#define UHCI_TD_PID_SETUP 0x0000002d +#define UHCI_TD_GET_PID(s) ((s) & 0xff) +#define UHCI_TD_SET_DEVADDR(a) ((a) << 8) +#define UHCI_TD_GET_DEVADDR(s) (((s) >> 8) & 0x7f) +#define UHCI_TD_SET_ENDPT(e) (((e) & 0xf) << 15) +#define UHCI_TD_GET_ENDPT(s) (((s) >> 15) & 0xf) +#define UHCI_TD_SET_DT(t) ((t) << 19) +#define UHCI_TD_GET_DT(s) (((s) >> 19) & 1) +#define UHCI_TD_SET_MAXLEN(l) (((l)-1) << 21) +#define UHCI_TD_GET_MAXLEN(s) ((((s) >> 21) + 1) & 0x7ff) +#define UHCI_TD_MAXLEN_MASK 0xffe00000 + volatile uint32_t td_buffer; +/* + * Extra information needed: + */ + struct uhci_td *next; + struct uhci_td *prev; + struct uhci_td *obj_next; + struct usb2_page_cache *page_cache; + struct usb2_page_cache *fix_pc; + uint32_t td_self; + uint16_t len; +} __aligned(UHCI_TD_ALIGN); + +typedef struct uhci_td uhci_td_t; + +#define UHCI_TD_ERROR (UHCI_TD_BITSTUFF | UHCI_TD_CRCTO | \ + UHCI_TD_BABBLE | UHCI_TD_DBUFFER | UHCI_TD_STALLED) + +#define UHCI_TD_SETUP(len, endp, dev) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | \ + UHCI_TD_SET_DEVADDR(dev) | \ + UHCI_TD_PID_SETUP) + +#define UHCI_TD_OUT(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | \ + UHCI_TD_SET_DEVADDR(dev) | \ + UHCI_TD_PID_OUT | UHCI_TD_SET_DT(dt)) + +#define UHCI_TD_IN(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | \ + UHCI_TD_SET_DEVADDR(dev) | \ + UHCI_TD_PID_IN | UHCI_TD_SET_DT(dt)) + +struct uhci_qh { +/* + * Data used by the UHCI controller. + */ + volatile uint32_t qh_h_next; + volatile uint32_t qh_e_next; +/* + * Extra information needed: + */ + struct uhci_qh *h_next; + struct uhci_qh *h_prev; + struct uhci_qh *obj_next; + struct uhci_td *e_next; + struct usb2_page_cache *page_cache; + uint32_t qh_self; + uint16_t intr_pos; +} __aligned(UHCI_QH_ALIGN); + +typedef struct uhci_qh uhci_qh_t; + +/* Maximum number of isochronous TD's and QH's interrupt */ +#define UHCI_VFRAMELIST_COUNT 128 +#define UHCI_IFRAMELIST_COUNT (2 * UHCI_VFRAMELIST_COUNT) + +#if (((UHCI_VFRAMELIST_COUNT & (UHCI_VFRAMELIST_COUNT-1)) != 0) || \ + (UHCI_VFRAMELIST_COUNT > UHCI_FRAMELIST_COUNT)) +#error "UHCI_VFRAMELIST_COUNT is not power of two" +#error "or UHCI_VFRAMELIST_COUNT > UHCI_FRAMELIST_COUNT" +#endif + +#if (UHCI_VFRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER) +#error "maximum number of full-speed isochronous frames is higher than supported!" +#endif + +struct uhci_config_desc { + struct usb2_config_descriptor confd; + struct usb2_interface_descriptor ifcd; + struct usb2_endpoint_descriptor endpd; +} __packed; + +union uhci_hub_desc { + struct usb2_status stat; + struct usb2_port_status ps; + struct usb2_device_descriptor devd; + uint8_t temp[128]; +}; + +struct uhci_hw_softc { + struct usb2_page_cache pframes_pc; + struct usb2_page_cache isoc_start_pc[UHCI_VFRAMELIST_COUNT]; + struct usb2_page_cache intr_start_pc[UHCI_IFRAMELIST_COUNT]; + struct usb2_page_cache ls_ctl_start_pc; + struct usb2_page_cache fs_ctl_start_pc; + struct usb2_page_cache bulk_start_pc; + struct usb2_page_cache last_qh_pc; + struct usb2_page_cache last_td_pc; + + struct usb2_page pframes_pg; + struct usb2_page isoc_start_pg[UHCI_VFRAMELIST_COUNT]; + struct usb2_page intr_start_pg[UHCI_IFRAMELIST_COUNT]; + struct usb2_page ls_ctl_start_pg; + struct usb2_page fs_ctl_start_pg; + struct usb2_page bulk_start_pg; + struct usb2_page last_qh_pg; + struct usb2_page last_td_pg; +}; + +typedef struct uhci_softc { + struct uhci_hw_softc sc_hw; + struct usb2_bus sc_bus; /* base device */ + struct usb2_config_td sc_config_td; + union uhci_hub_desc sc_hub_desc; + struct usb2_sw_transfer sc_root_ctrl; + struct usb2_sw_transfer sc_root_intr; + + struct uhci_td *sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]; /* pointer to last TD + * for isochronous */ + struct uhci_qh *sc_intr_p_last[UHCI_IFRAMELIST_COUNT]; /* pointer to last QH + * for interrupt */ + struct uhci_qh *sc_ls_ctl_p_last; /* pointer to last QH for low + * speed control */ + struct uhci_qh *sc_fs_ctl_p_last; /* pointer to last QH for full + * speed control */ + struct uhci_qh *sc_bulk_p_last; /* pointer to last QH for bulk */ + struct uhci_qh *sc_reclaim_qh_p; + struct uhci_qh *sc_last_qh_p; + struct uhci_td *sc_last_td_p; + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + device_t sc_dev; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_loops; /* number of QHs that wants looping */ + + uint16_t sc_intr_stat[UHCI_IFRAMELIST_COUNT]; + uint16_t sc_saved_frnum; + + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_isreset; + uint8_t sc_saved_sof; + uint8_t sc_hub_idata[1]; + + char sc_vendor[16]; /* vendor string for root hub */ +} uhci_softc_t; + +usb2_bus_mem_cb_t uhci_iterate_hw_softc; + +usb2_error_t uhci_init(uhci_softc_t *sc); +void uhci_suspend(uhci_softc_t *sc); +void uhci_resume(uhci_softc_t *sc); +void uhci_reset(uhci_softc_t *sc); +void uhci_interrupt(uhci_softc_t *sc); + +#endif /* _UHCI_H_ */ diff --git a/sys/dev/usb2/controller/uhci2_pci.c b/sys/dev/usb2/controller/uhci2_pci.c new file mode 100644 index 000000000000..947f9afbc8ee --- /dev/null +++ b/sys/dev/usb2/controller/uhci2_pci.c @@ -0,0 +1,453 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* Universal Host Controller Interface + * + * UHCI spec: http://www.intel.com/ + */ + +/* The low level controller code for UHCI has been split into + * PCI probes and UHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define PCI_UHCI_VENDORID_INTEL 0x8086 +#define PCI_UHCI_VENDORID_VIA 0x1106 + +/* PIIX4E has no separate stepping */ + +#define PCI_UHCI_BASE_REG 0x20 + +static device_probe_t uhci_pci_probe; +static device_attach_t uhci_pci_attach; +static device_detach_t uhci_pci_detach; +static device_suspend_t uhci_pci_suspend; +static device_resume_t uhci_pci_resume; + +static int +uhci_pci_suspend(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_suspend(self); + if (err) { + return (err); + } + uhci_suspend(sc); + return (0); +} + +static int +uhci_pci_resume(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + + pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); + + uhci_resume(sc); + + bus_generic_resume(self); + return (0); +} + +static const char * +uhci_pci_match(device_t self) +{ + uint32_t device_id = pci_get_devid(self); + + switch (device_id) { + case 0x26888086: + return ("Intel 631XESB/632XESB/3100 USB controller USB-1"); + + case 0x26898086: + return ("Intel 631XESB/632XESB/3100 USB controller USB-2"); + + case 0x268a8086: + return ("Intel 631XESB/632XESB/3100 USB controller USB-3"); + + case 0x268b8086: + return ("Intel 631XESB/632XESB/3100 USB controller USB-4"); + + case 0x70208086: + return ("Intel 82371SB (PIIX3) USB controller"); + + case 0x71128086: + return ("Intel 82371AB/EB (PIIX4) USB controller"); + + case 0x24128086: + return ("Intel 82801AA (ICH) USB controller"); + + case 0x24228086: + return ("Intel 82801AB (ICH0) USB controller"); + + case 0x24428086: + return ("Intel 82801BA/BAM (ICH2) USB controller USB-A"); + + case 0x24448086: + return ("Intel 82801BA/BAM (ICH2) USB controller USB-B"); + + case 0x24828086: + return ("Intel 82801CA/CAM (ICH3) USB controller USB-A"); + + case 0x24848086: + return ("Intel 82801CA/CAM (ICH3) USB controller USB-B"); + + case 0x24878086: + return ("Intel 82801CA/CAM (ICH3) USB controller USB-C"); + + case 0x24c28086: + return ("Intel 82801DB (ICH4) USB controller USB-A"); + + case 0x24c48086: + return ("Intel 82801DB (ICH4) USB controller USB-B"); + + case 0x24c78086: + return ("Intel 82801DB (ICH4) USB controller USB-C"); + + case 0x24d28086: + return ("Intel 82801EB (ICH5) USB controller USB-A"); + + case 0x24d48086: + return ("Intel 82801EB (ICH5) USB controller USB-B"); + + case 0x24d78086: + return ("Intel 82801EB (ICH5) USB controller USB-C"); + + case 0x24de8086: + return ("Intel 82801EB (ICH5) USB controller USB-D"); + + case 0x26588086: + return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-A"); + + case 0x26598086: + return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-B"); + + case 0x265a8086: + return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-C"); + + case 0x265b8086: + return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-D"); + + case 0x28308086: + return ("Intel 82801H (ICH8) USB controller USB-A"); + case 0x28318086: + return ("Intel 82801H (ICH8) USB controller USB-B"); + case 0x28328086: + return ("Intel 82801H (ICH8) USB controller USB-C"); + case 0x28348086: + return ("Intel 82801H (ICH8) USB controller USB-D"); + case 0x28358086: + return ("Intel 82801H (ICH8) USB controller USB-E"); + case 0x29348086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29358086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29368086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29378086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29388086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29398086: + return ("Intel 82801I (ICH9) USB controller"); + + case 0x719a8086: + return ("Intel 82443MX USB controller"); + + case 0x76028086: + return ("Intel 82372FB/82468GX USB controller"); + + case 0x30381106: + return ("VIA 83C572 USB controller"); + + default: + break; + } + + if ((pci_get_class(self) == PCIC_SERIALBUS) && + (pci_get_subclass(self) == PCIS_SERIALBUS_USB) && + (pci_get_progif(self) == PCI_INTERFACE_UHCI)) { + return ("UHCI (generic) USB controller"); + } + return (NULL); +} + +static int +uhci_pci_probe(device_t self) +{ + const char *desc = uhci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return (0); + } else { + return (ENXIO); + } +} + +static int +uhci_pci_attach(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + int rid; + int err; + + if (sc == NULL) { + device_printf(self, "Could not allocate sc\n"); + return (ENXIO); + } + /* get all DMA memory */ + + if (usb2_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), + &uhci_iterate_hw_softc)) { + return ENOMEM; + } + sc->sc_dev = self; + + pci_enable_busmaster(self); + + rid = PCI_UHCI_BASE_REG; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid, + RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map ports\n"); + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + /* disable interrupts */ + bus_space_write_2(sc->sc_io_tag, sc->sc_io_hdl, UHCI_INTR, 0); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* + * uhci_pci_match must never return NULL if uhci_pci_probe + * succeeded + */ + device_set_desc(sc->sc_bus.bdev, uhci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_UHCI_VENDORID_INTEL: + sprintf(sc->sc_vendor, "Intel"); + break; + case PCI_UHCI_VENDORID_VIA: + sprintf(sc->sc_vendor, "VIA"); + break; + default: + if (bootverbose) { + device_printf(self, "(New UHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + } + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + + switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) { + case PCI_USB_REV_PRE_1_0: + sc->sc_bus.usbrev = USB_REV_PRE_1_0; + break; + case PCI_USB_REV_1_0: + sc->sc_bus.usbrev = USB_REV_1_0; + break; + default: + sc->sc_bus.usbrev = USB_REV_UNKNOWN; + break; + } + + err = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_bus.mtx, + NULL, 0, 4); + if (err) { + device_printf(self, "could not setup config thread!\n"); + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)(void *)uhci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)(void *)uhci_interrupt, sc, &sc->sc_intr_hdl); +#endif + + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + /* + * Set the PIRQD enable bit and switch off all the others. We don't + * want legacy support to interfere with us XXX Does this also mean + * that the BIOS won't touch the keyboard anymore if it is connected + * to the ports of the root hub? + */ +#if USB_DEBUG + if (pci_read_config(self, PCI_LEGSUP, 2) != PCI_LEGSUP_USBPIRQDEN) { + device_printf(self, "LegSup = 0x%04x\n", + pci_read_config(self, PCI_LEGSUP, 2)); + } +#endif + pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); + + err = uhci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed\n"); + goto error; + } + return (0); + +error: + uhci_pci_detach(self); + return (ENXIO); +} + +int +uhci_pci_detach(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + device_t bdev; + + usb2_config_td_drain(&sc->sc_config_td); + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(self); + + /* + * disable interrupts that might have been switched on in + * uhci_init. + */ + if (sc->sc_io_res) { + mtx_lock(&sc->sc_bus.mtx); + + /* stop the controller */ + uhci_reset(sc); + + mtx_unlock(&sc->sc_bus.mtx); + } + pci_disable_busmaster(self); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) { + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + } + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_IOPORT, PCI_UHCI_BASE_REG, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_bus, &uhci_iterate_hw_softc); + + return (0); +} + +static driver_t uhci_driver = +{ + .name = "uhci", + .methods = (device_method_t[]){ + /* device interface */ + DEVMETHOD(device_probe, uhci_pci_probe), + DEVMETHOD(device_attach, uhci_pci_attach), + DEVMETHOD(device_detach, uhci_pci_detach), + + DEVMETHOD(device_suspend, uhci_pci_suspend), + DEVMETHOD(device_resume, uhci_pci_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + {0, 0} + }, + .size = sizeof(struct uhci_softc), +}; + +static devclass_t uhci_devclass; + +DRIVER_MODULE(uhci, pci, uhci_driver, uhci_devclass, 0, 0); +DRIVER_MODULE(uhci, cardbus, uhci_driver, uhci_devclass, 0, 0); +MODULE_DEPEND(uhci, usb2_controller, 1, 1, 1); +MODULE_DEPEND(uhci, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/controller/usb2_bus.h b/sys/dev/usb2/controller/usb2_bus.h new file mode 100644 index 000000000000..cfe98f7c64fb --- /dev/null +++ b/sys/dev/usb2/controller/usb2_bus.h @@ -0,0 +1,88 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_BUS_H_ +#define _USB2_BUS_H_ + +/* + * The following structure defines the USB explore message sent to the + * USB explore process. + */ + +struct usb2_bus_msg { + struct usb2_proc_msg hdr; + struct usb2_bus *bus; +}; + +/* + * The following structure defines the USB statistics structure. + */ +struct usb2_bus_stat { + uint32_t uds_requests[4]; +}; + +/* + * The following structure defines an USB BUS. There is one USB BUS + * for every Host or Device controller. + */ +struct usb2_bus { + struct usb2_bus_stat stats_err; + struct usb2_bus_stat stats_ok; + struct usb2_process explore_proc; + struct usb2_bus_msg explore_msg[2]; + struct usb2_bus_msg detach_msg[2]; + struct mtx mtx; /* This mutex protects the USB + * hardware */ + struct usb2_perm perm; + struct usb2_xfer_queue intr_q; + + device_t bdev; /* filled by HC driver */ + + struct usb2_dma_parent_tag dma_parent_tag[1]; + struct usb2_dma_tag dma_tags[USB_BUS_DMA_TAG_MAX]; + + struct usb2_bus_methods *methods; /* filled by HC driver */ + struct usb2_device *devices[USB_MAX_DEVICES]; + + uint32_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; + uint32_t transfer_count[4]; + uint16_t isoc_time_last; /* in milliseconds */ + + uint8_t alloc_failed; /* Set if memory allocation failed. */ + uint8_t driver_added_refcount; /* Current driver generation count */ + uint8_t usbrev; /* USB revision. See "USB_REV_XXX". */ + + uint8_t devices_max; /* maximum number of USB devices */ + uint8_t do_probe; /* set if USB BUS should be re-probed */ + + union { + struct usb2_hw_ep_scratch hw_ep_scratch[1]; + struct usb2_temp_setup temp_setup[1]; + uint8_t data[128]; + } scratch[1]; +}; + +#endif /* _USB2_BUS_H_ */ diff --git a/sys/dev/usb2/controller/usb2_controller.c b/sys/dev/usb2/controller/usb2_controller.c new file mode 100644 index 000000000000..fdfa3a657e41 --- /dev/null +++ b/sys/dev/usb2/controller/usb2_controller.c @@ -0,0 +1,477 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_ctrl_debug + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* function prototypes */ + +static device_probe_t usb2_probe; +static device_attach_t usb2_attach; +static device_detach_t usb2_detach; + +static void usb2_attach_sub(device_t dev, struct usb2_bus *bus); +static void usb2_post_init(void *arg); +static void usb2_bus_mem_flush_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc, struct usb2_page *pg, uint32_t size, uint32_t align); +static void usb2_bus_mem_alloc_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc, struct usb2_page *pg, uint32_t size, uint32_t align); +static void usb2_bus_mem_free_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc, struct usb2_page *pg, uint32_t size, uint32_t align); + +/* static variables */ + +#if USB_DEBUG +static int usb2_ctrl_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ctrl, CTLFLAG_RW, 0, "USB controller"); +SYSCTL_INT(_hw_usb2_ctrl, OID_AUTO, debug, CTLFLAG_RW, &usb2_ctrl_debug, 0, + "Debug level"); +#endif + +static uint8_t usb2_post_init_called = 0; + +static devclass_t usb2_devclass; + +static device_method_t usb2_methods[] = { + DEVMETHOD(device_probe, usb2_probe), + DEVMETHOD(device_attach, usb2_attach), + DEVMETHOD(device_detach, usb2_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + {0, 0} +}; + +static driver_t usb2_driver = { + .name = "usbus", + .methods = usb2_methods, + .size = 0, +}; + +DRIVER_MODULE(usbus, ohci, usb2_driver, usb2_devclass, 0, 0); +DRIVER_MODULE(usbus, uhci, usb2_driver, usb2_devclass, 0, 0); +DRIVER_MODULE(usbus, ehci, usb2_driver, usb2_devclass, 0, 0); +DRIVER_MODULE(usbus, at91_udp, usb2_driver, usb2_devclass, 0, 0); +DRIVER_MODULE(usbus, uss820, usb2_driver, usb2_devclass, 0, 0); + +MODULE_DEPEND(usb2_controller, usb2_core, 1, 1, 1); +MODULE_VERSION(usb2_controller, 1); + +/*------------------------------------------------------------------------* + * usb2_probe + * + * This function is called from "{ehci,ohci,uhci}_pci_attach()". + *------------------------------------------------------------------------*/ +static int +usb2_probe(device_t dev) +{ + DPRINTF("\n"); + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_attach + *------------------------------------------------------------------------*/ +static int +usb2_attach(device_t dev) +{ + struct usb2_bus *bus = device_get_ivars(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + DPRINTFN(0, "USB device has no ivars\n"); + return (ENXIO); + } + if (usb2_post_init_called) { + mtx_lock(&Giant); + usb2_attach_sub(dev, bus); + mtx_unlock(&Giant); + usb2_needs_explore(bus, 1); + } + return (0); /* return success */ +} + +/*------------------------------------------------------------------------* + * usb2_detach + *------------------------------------------------------------------------*/ +static int +usb2_detach(device_t dev) +{ + struct usb2_bus *bus = device_get_softc(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + /* was never setup properly */ + return (0); + } + /* Let the USB explore process detach all devices. */ + + mtx_lock(&bus->mtx); + if (usb2_proc_msignal(&bus->explore_proc, + &bus->detach_msg[0], &bus->detach_msg[1])) { + /* ignore */ + } + /* Wait for detach to complete */ + + usb2_proc_mwait(&bus->explore_proc, + &bus->detach_msg[0], &bus->detach_msg[1]); + + mtx_unlock(&bus->mtx); + + /* Get rid of USB explore process */ + + usb2_proc_unsetup(&bus->explore_proc); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_bus_explore + * + * This function is used to explore the device tree from the root. + *------------------------------------------------------------------------*/ +static void +usb2_bus_explore(struct usb2_proc_msg *pm) +{ + struct usb2_bus *bus; + struct usb2_device *udev; + + bus = ((struct usb2_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + + if (udev && udev->hub) { + + if (bus->do_probe) { + bus->do_probe = 0; + bus->driver_added_refcount++; + } + if (bus->driver_added_refcount == 0) { + /* avoid zero, hence that is memory default */ + bus->driver_added_refcount = 1; + } + mtx_unlock(&bus->mtx); + + mtx_lock(&Giant); + + /* + * Explore the Root USB HUB. This call can sleep, + * exiting Giant, which is actually Giant. + */ + (udev->hub->explore) (udev); + + mtx_unlock(&Giant); + + mtx_lock(&bus->mtx); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_bus_detach + * + * This function is used to detach the device tree from the root. + *------------------------------------------------------------------------*/ +static void +usb2_bus_detach(struct usb2_proc_msg *pm) +{ + struct usb2_bus *bus; + struct usb2_device *udev; + device_t dev; + + bus = ((struct usb2_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + dev = bus->bdev; + /* clear the softc */ + device_set_softc(dev, NULL); + mtx_unlock(&bus->mtx); + + mtx_lock(&Giant); + + /* detach children first */ + bus_generic_detach(dev); + + /* + * Free USB Root device, but not any sub-devices, hence they + * are freed by the caller of this function: + */ + usb2_detach_device(udev, USB_IFACE_INDEX_ANY, 0); + usb2_free_device(udev); + + mtx_unlock(&Giant); + mtx_lock(&bus->mtx); + /* clear bdev variable last */ + bus->bdev = NULL; + return; +} + +/*------------------------------------------------------------------------* + * usb2_attach_sub + * + * This function is the real USB bus attach code. It is factored out, + * hence it can be called at two different places in time. During + * bootup this function is called from "usb2_post_init". During + * hot-plug it is called directly from the "usb2_attach()" method. + *------------------------------------------------------------------------*/ +static void +usb2_attach_sub(device_t dev, struct usb2_bus *bus) +{ + struct usb2_device *child; + usb2_error_t err; + uint8_t speed; + + DPRINTF("\n"); + + mtx_assert(&Giant, MA_OWNED); + + switch (bus->usbrev) { + case USB_REV_1_0: + speed = USB_SPEED_FULL; + device_printf(bus->bdev, "12Mbps Full Speed USB v1.0\n"); + break; + + case USB_REV_1_1: + speed = USB_SPEED_FULL; + device_printf(bus->bdev, "12Mbps Full Speed USB v1.1\n"); + break; + + case USB_REV_2_0: + speed = USB_SPEED_HIGH; + device_printf(bus->bdev, "480Mbps High Speed USB v2.0\n"); + break; + + case USB_REV_2_5: + speed = USB_SPEED_VARIABLE; + device_printf(bus->bdev, "480Mbps Wireless USB v2.5\n"); + break; + + default: + device_printf(bus->bdev, "Unsupported USB revision!\n"); + return; + } + + /* Allocate the Root USB device */ + + child = usb2_alloc_device(bus->bdev, bus, NULL, 0, 0, 1, + speed, USB_MODE_HOST); + if (child) { + err = usb2_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + if (!err) { + if (!bus->devices[USB_ROOT_HUB_ADDR]->hub) { + err = USB_ERR_NO_ROOT_HUB; + } + } + } else { + err = USB_ERR_NOMEM; + } + + if (err) { + device_printf(bus->bdev, "Root HUB problem, error=%s\n", + usb2_errstr(err)); + } + /* Initialise USB process messages */ + bus->explore_msg[0].hdr.pm_callback = &usb2_bus_explore; + bus->explore_msg[0].bus = bus; + bus->explore_msg[1].hdr.pm_callback = &usb2_bus_explore; + bus->explore_msg[1].bus = bus; + + bus->detach_msg[0].hdr.pm_callback = &usb2_bus_detach; + bus->detach_msg[0].bus = bus; + bus->detach_msg[1].hdr.pm_callback = &usb2_bus_detach; + bus->detach_msg[1].bus = bus; + + /* Create a new USB process */ + if (usb2_proc_setup(&bus->explore_proc, + &bus->mtx, USB_PRI_MED)) { + printf("WARNING: Creation of USB explore process failed.\n"); + } + /* set softc - we are ready */ + device_set_softc(dev, bus); + return; +} + +/*------------------------------------------------------------------------* + * usb2_post_init + * + * This function is called to attach all USB busses that were found + * during bootup. + *------------------------------------------------------------------------*/ +static void +usb2_post_init(void *arg) +{ + struct usb2_bus *bus; + devclass_t dc; + device_t dev; + int max; + int n; + + mtx_lock(&Giant); + + usb2_devclass_ptr = devclass_find("usbus"); + + dc = usb2_devclass_ptr; + if (dc) { + max = devclass_get_maxunit(dc) + 1; + for (n = 0; n != max; n++) { + dev = devclass_get_device(dc, n); + if (dev && device_is_attached(dev)) { + bus = device_get_ivars(dev); + if (bus) { + mtx_lock(&Giant); + usb2_attach_sub(dev, bus); + mtx_unlock(&Giant); + } + } + } + } else { + DPRINTFN(0, "no devclass\n"); + } + usb2_post_init_called = 1; + + /* explore all USB busses in parallell */ + + usb2_needs_explore_all(); + + mtx_unlock(&Giant); + + return; +} + +SYSINIT(usb2_post_init, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb2_post_init, NULL); +SYSUNINIT(usb2_bus_unload, SI_SUB_KLD, SI_ORDER_ANY, usb2_bus_unload, NULL); + +/*------------------------------------------------------------------------* + * usb2_bus_mem_flush_all_cb + *------------------------------------------------------------------------*/ +static void +usb2_bus_mem_flush_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc, + struct usb2_page *pg, uint32_t size, uint32_t align) +{ + usb2_pc_cpu_flush(pc); + return; +} + +/*------------------------------------------------------------------------* + * usb2_bus_mem_flush_all - factored out code + *------------------------------------------------------------------------*/ +void +usb2_bus_mem_flush_all(struct usb2_bus *bus, usb2_bus_mem_cb_t *cb) +{ + if (cb) { + cb(bus, &usb2_bus_mem_flush_all_cb); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_bus_mem_alloc_all_cb + *------------------------------------------------------------------------*/ +static void +usb2_bus_mem_alloc_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc, + struct usb2_page *pg, uint32_t size, uint32_t align) +{ + /* need to initialize the page cache */ + pc->tag_parent = bus->dma_parent_tag; + + if (usb2_pc_alloc_mem(pc, pg, size, align)) { + bus->alloc_failed = 1; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_bus_mem_alloc_all - factored out code + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_bus_mem_alloc_all(struct usb2_bus *bus, bus_dma_tag_t dmat, + usb2_bus_mem_cb_t *cb) +{ + bus->alloc_failed = 0; + + bus->devices_max = USB_MAX_DEVICES; + + mtx_init(&bus->mtx, "USB lock", + NULL, MTX_DEF | MTX_RECURSE); + + TAILQ_INIT(&bus->intr_q.head); + + usb2_dma_tag_setup(bus->dma_parent_tag, bus->dma_tags, + dmat, &bus->mtx, NULL, NULL, 32, USB_BUS_DMA_TAG_MAX); + + if (cb) { + cb(bus, &usb2_bus_mem_alloc_all_cb); + } + if (bus->alloc_failed) { + usb2_bus_mem_free_all(bus, cb); + } + return (bus->alloc_failed); +} + +/*------------------------------------------------------------------------* + * usb2_bus_mem_free_all_cb + *------------------------------------------------------------------------*/ +static void +usb2_bus_mem_free_all_cb(struct usb2_bus *bus, struct usb2_page_cache *pc, + struct usb2_page *pg, uint32_t size, uint32_t align) +{ + usb2_pc_free_mem(pc); + return; +} + +/*------------------------------------------------------------------------* + * usb2_bus_mem_free_all - factored out code + *------------------------------------------------------------------------*/ +void +usb2_bus_mem_free_all(struct usb2_bus *bus, usb2_bus_mem_cb_t *cb) +{ + if (cb) { + cb(bus, &usb2_bus_mem_free_all_cb); + } + usb2_dma_tag_unsetup(bus->dma_parent_tag); + + mtx_destroy(&bus->mtx); + + return; +} diff --git a/sys/dev/usb2/controller/usb2_controller.h b/sys/dev/usb2/controller/usb2_controller.h new file mode 100644 index 000000000000..496e8c253b4a --- /dev/null +++ b/sys/dev/usb2/controller/usb2_controller.h @@ -0,0 +1,172 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_CONTROLLER_H_ +#define _USB2_CONTROLLER_H_ + +/* defines */ + +#define USB_BUS_DMA_TAG_MAX 8 + +/* structure prototypes */ + +struct usb2_bus; +struct usb2_page; +struct usb2_pipe; +struct usb2_page_cache; +struct usb2_setup_params; +struct usb2_hw_ep_profile; +struct usb2_fs_isoc_schedule; +struct usb2_config_descriptor; +struct usb2_endpoint_descriptor; + +/* typedefs */ + +typedef void (usb2_bus_mem_sub_cb_t)(struct usb2_bus *bus, struct usb2_page_cache *pc, struct usb2_page *pg, uint32_t size, uint32_t align); +typedef void (usb2_bus_mem_cb_t)(struct usb2_bus *bus, usb2_bus_mem_sub_cb_t *scb); + +/* + * The following structure is used to define all the USB BUS + * callbacks. + */ +struct usb2_bus_methods { + + /* USB Device and Host mode - Mandatory */ + + void (*pipe_init) (struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc, struct usb2_pipe *pipe); + void (*do_poll) (struct usb2_bus *); + void (*xfer_setup) (struct usb2_setup_params *parm); + void (*xfer_unsetup) (struct usb2_xfer *xfer); + void (*get_dma_delay) (struct usb2_bus *, uint32_t *pdelay); + + /* USB Device mode only - Mandatory */ + + void (*get_hw_ep_profile) (struct usb2_device *udev, const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr); + void (*set_stall) (struct usb2_device *udev, struct usb2_xfer *xfer, struct usb2_pipe *pipe); + void (*clear_stall) (struct usb2_device *udev, struct usb2_pipe *pipe); + void (*rem_wakeup_set) (struct usb2_device *udev, uint8_t is_on); + + /* USB Device mode only - Optional */ + + void (*vbus_interrupt) (struct usb2_bus *, uint8_t is_on); +}; + +/* + * The following structure is used to define all the USB pipe + * callbacks. + */ +struct usb2_pipe_methods { + + /* Mandatory USB Device and Host mode callbacks: */ + + void (*open) (struct usb2_xfer *xfer); + void (*close) (struct usb2_xfer *xfer); + + void (*enter) (struct usb2_xfer *xfer); + void (*start) (struct usb2_xfer *xfer); + + /* Optional */ + + uint8_t (*isdone) (struct usb2_xfer *xfer); + void *info; + + /* Flags */ + + uint8_t enter_is_cancelable:1; + uint8_t start_is_cancelable:1; +}; + +/* + * The following structure keeps information about what a hardware USB + * endpoint supports. + */ +struct usb2_hw_ep_profile { + uint16_t max_in_frame_size; /* IN-token direction */ + uint16_t max_out_frame_size; /* OUT-token direction */ + uint8_t is_simplex:1; + uint8_t support_multi_buffer:1; + uint8_t support_bulk:1; + uint8_t support_control:1; + uint8_t support_interrupt:1; + uint8_t support_isochronous:1; + uint8_t support_in:1; /* IN-token is supported */ + uint8_t support_out:1; /* OUT-token is supported */ +}; + +/* + * The following structure is used when trying to allocate hardware + * endpoints for an USB configuration in USB device side mode. + */ +struct usb2_hw_ep_scratch_sub { + const struct usb2_hw_ep_profile *pf; + uint16_t max_frame_size; + uint8_t hw_endpoint_out; + uint8_t hw_endpoint_in; + uint8_t needs_ep_type; + uint8_t needs_in:1; + uint8_t needs_out:1; +}; + +/* + * The following structure is used when trying to allocate hardware + * endpoints for an USB configuration in USB device side mode. + */ +struct usb2_hw_ep_scratch { + struct usb2_hw_ep_scratch_sub ep[USB_EP_MAX]; + struct usb2_hw_ep_scratch_sub *ep_max; + struct usb2_config_descriptor *cd; + struct usb2_device *udev; + struct usb2_bus_methods *methods; + uint8_t bmOutAlloc[(USB_EP_MAX + 15) / 16]; + uint8_t bmInAlloc[(USB_EP_MAX + 15) / 16]; +}; + +/* + * The following structure is used when generating USB descriptors + * from USB templates. + */ +struct usb2_temp_setup { + void *buf; + uint32_t size; + uint8_t usb2_speed; + uint8_t self_powered; + uint8_t bNumEndpoints; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bConfigurationValue; + usb2_error_t err; +}; + +/* prototypes */ + +void usb2_bus_mem_flush_all(struct usb2_bus *bus, usb2_bus_mem_cb_t *cb); +uint8_t usb2_bus_mem_alloc_all(struct usb2_bus *bus, bus_dma_tag_t dmat, usb2_bus_mem_cb_t *cb); +void usb2_bus_mem_free_all(struct usb2_bus *bus, usb2_bus_mem_cb_t *cb); +uint16_t usb2_isoc_time_expand(struct usb2_bus *bus, uint16_t isoc_time_curr); +uint16_t usb2_fs_isoc_schedule_isoc_time_expand(struct usb2_device *udev, struct usb2_fs_isoc_schedule **pp_start, struct usb2_fs_isoc_schedule **pp_end, uint16_t isoc_time); +uint8_t usb2_fs_isoc_schedule_alloc(struct usb2_fs_isoc_schedule *fss, uint8_t *pstart, uint16_t len); + +#endif /* _USB2_CONTROLLER_H_ */ diff --git a/sys/dev/usb2/controller/usb2_pci.h b/sys/dev/usb2/controller/usb2_pci.h new file mode 100644 index 000000000000..9297c298f0ba --- /dev/null +++ b/sys/dev/usb2/controller/usb2_pci.h @@ -0,0 +1,39 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_PCI_H_ +#define _USB2_PCI_H_ + +/* + * We don't want the following files included everywhere, that's why + * they are in a separate file. + */ +#include +#include + +#include + +#endif /* _USB2_PCI_H_ */ diff --git a/sys/dev/usb2/controller/uss820dci.c b/sys/dev/usb2/controller/uss820dci.c new file mode 100644 index 000000000000..cc9f2196d5c8 --- /dev/null +++ b/sys/dev/usb2/controller/uss820dci.c @@ -0,0 +1,2572 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky + * 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. + */ + +/* + * This file contains the driver for the USS820 series USB Device + * Controller + * + * NOTE: The datasheet does not document everything! + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR uss820dcidebug +#define usb2_config_td_cc uss820dci_config_copy +#define usb2_config_td_softc uss820dci_softc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define USS820_DCI_BUS2SC(bus) \ + ((struct uss820dci_softc *)(((uint8_t *)(bus)) - \ + USB_P2U(&(((struct uss820dci_softc *)0)->sc_bus)))) + +#define USS820_DCI_PC2SC(pc) \ + USS820_DCI_BUS2SC((pc)->tag_parent->info->bus) + +#if USB_DEBUG +static int uss820dcidebug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uss820dci, CTLFLAG_RW, 0, "USB uss820dci"); +SYSCTL_INT(_hw_usb2_uss820dci, OID_AUTO, debug, CTLFLAG_RW, + &uss820dcidebug, 0, "uss820dci debug level"); +#endif + +#define USS820_DCI_INTR_ENDPT 1 + +/* prototypes */ + +struct usb2_bus_methods uss820dci_bus_methods; +struct usb2_pipe_methods uss820dci_device_bulk_methods; +struct usb2_pipe_methods uss820dci_device_ctrl_methods; +struct usb2_pipe_methods uss820dci_device_intr_methods; +struct usb2_pipe_methods uss820dci_device_isoc_fs_methods; +struct usb2_pipe_methods uss820dci_root_ctrl_methods; +struct usb2_pipe_methods uss820dci_root_intr_methods; + +static uss820dci_cmd_t uss820dci_setup_rx; +static uss820dci_cmd_t uss820dci_data_rx; +static uss820dci_cmd_t uss820dci_data_tx; +static uss820dci_cmd_t uss820dci_data_tx_sync; +static void uss820dci_device_done(struct usb2_xfer *xfer, usb2_error_t error); +static void uss820dci_do_poll(struct usb2_bus *bus); +static void uss820dci_root_ctrl_poll(struct uss820dci_softc *sc); +static void uss820dci_standard_done(struct usb2_xfer *xfer); +static void uss820dci_intr_set(struct usb2_xfer *xfer, uint8_t set); +static void uss820dci_update_shared_1(struct uss820dci_softc *sc, uint8_t reg, uint8_t keep_mask, uint8_t set_mask); + +static usb2_sw_transfer_func_t uss820dci_root_intr_done; +static usb2_sw_transfer_func_t uss820dci_root_ctrl_done; +static usb2_config_td_command_t uss820dci_root_ctrl_task; + +/* + * Here is a list of what the USS820D chip can support. The main + * limitation is that the sum of the buffer sizes must be less than + * 1120 bytes. + */ +static const struct usb2_hw_ep_profile + uss820dci_ep_profile[] = { + + [0] = { + .max_in_frame_size = 32, + .max_out_frame_size = 32, + .is_simplex = 0, + .support_control = 1, + }, + [1] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 0, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_in = 1, + .support_out = 1, + }, + [2] = { + .max_in_frame_size = 8, + .max_out_frame_size = 8, + .is_simplex = 0, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_in = 1, + .support_out = 1, + }, + [3] = { + .max_in_frame_size = 256, + .max_out_frame_size = 256, + .is_simplex = 0, + .support_multi_buffer = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, +}; + +static void +uss820dci_update_shared_1(struct uss820dci_softc *sc, uint8_t reg, + uint8_t keep_mask, uint8_t set_mask) +{ + uint8_t temp; + + USS820_WRITE_1(sc, USS820_PEND, 1); + temp = USS820_READ_1(sc, reg); + temp &= (keep_mask); + temp |= (set_mask); + USS820_WRITE_1(sc, reg, temp); + USS820_WRITE_1(sc, USS820_PEND, 0); + return; +} + +static void +uss820dci_get_hw_ep_profile(struct usb2_device *udev, + const struct usb2_hw_ep_profile **ppf, uint8_t ep_addr) +{ + if (ep_addr == 0) { + *ppf = uss820dci_ep_profile + 0; + } else if (ep_addr < 5) { + *ppf = uss820dci_ep_profile + 1; + } else if (ep_addr < 7) { + *ppf = uss820dci_ep_profile + 2; + } else if (ep_addr == 7) { + *ppf = uss820dci_ep_profile + 3; + } else { + *ppf = NULL; + } + return; +} + +static void +uss820dci_pull_up(struct uss820dci_softc *sc) +{ + uint8_t temp; + + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + + DPRINTF("\n"); + + temp = USS820_READ_1(sc, USS820_MCSR); + temp |= USS820_MCSR_DPEN; + USS820_WRITE_1(sc, USS820_MCSR, temp); + } + return; +} + +static void +uss820dci_pull_down(struct uss820dci_softc *sc) +{ + uint8_t temp; + + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + + DPRINTF("\n"); + + temp = USS820_READ_1(sc, USS820_MCSR); + temp &= ~USS820_MCSR_DPEN; + USS820_WRITE_1(sc, USS820_MCSR, temp); + } + return; +} + +static void +uss820dci_wakeup_peer(struct uss820dci_softc *sc) +{ + if (!(sc->sc_flags.status_suspend)) { + return; + } + DPRINTFN(0, "not supported\n"); + + return; +} + +static void +uss820dci_rem_wakeup_set(struct usb2_device *udev, uint8_t is_on) +{ + struct uss820dci_softc *sc; + uint8_t temp; + + DPRINTFN(5, "is_on=%u\n", is_on); + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + sc = USS820_DCI_BUS2SC(udev->bus); + + temp = USS820_READ_1(sc, USS820_SCR); + + if (is_on) { + temp |= USS820_SCR_RWUPE; + } else { + temp &= ~USS820_SCR_RWUPE; + } + + USS820_WRITE_1(sc, USS820_SCR, temp); + + return; +} + +static void +uss820dci_set_address(struct uss820dci_softc *sc, uint8_t addr) +{ + DPRINTFN(5, "addr=%d\n", addr); + + USS820_WRITE_1(sc, USS820_FADDR, addr); + + return; +} + +static uint8_t +uss820dci_setup_rx(struct uss820dci_td *td) +{ + struct uss820dci_softc *sc; + struct usb2_device_request req; + uint16_t count; + uint8_t rx_stat; + uint8_t temp; + + /* select the correct endpoint */ + bus_space_write_1(td->io_tag, td->io_hdl, + td->ep_reg, td->ep_index); + + /* read out FIFO status */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_stat_reg); + + /* get pointer to softc */ + sc = USS820_DCI_PC2SC(td->pc); + + DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder); + + if (!(rx_stat & USS820_RXSTAT_RXSETUP)) { + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(5, "stalling\n"); + + /* set stall */ + + uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, + (USS820_EPCON_TXSTL | USS820_EPCON_RXSTL)); + + td->did_stall = 1; + } + goto not_complete; + } + /* clear stall and all I/O */ + uss820dci_update_shared_1(sc, USS820_EPCON, + 0xFF ^ (USS820_EPCON_TXSTL | + USS820_EPCON_RXSTL | + USS820_EPCON_RXIE | + USS820_EPCON_TXOE), 0); + + /* clear end overwrite flag */ + uss820dci_update_shared_1(sc, USS820_RXSTAT, + 0xFF ^ USS820_RXSTAT_EDOVW, 0); + + /* get the packet byte count */ + count = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_count_low_reg); + count |= (bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_count_high_reg) << 8); + count &= 0x3FF; + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + /* receive data */ + bus_space_read_multi_1(td->io_tag, td->io_hdl, + td->rx_fifo_reg, (void *)&req, sizeof(req)); + + /* read out FIFO status */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_stat_reg); + + if (rx_stat & (USS820_RXSTAT_EDOVW | + USS820_RXSTAT_STOVW)) { + DPRINTF("new SETUP packet received\n"); + return (1); /* not complete */ + } + /* clear receive setup bit */ + uss820dci_update_shared_1(sc, USS820_RXSTAT, + 0xFF ^ (USS820_RXSTAT_RXSETUP | + USS820_RXSTAT_EDOVW | + USS820_RXSTAT_STOVW), 0); + + /* set RXFFRC bit */ + temp = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_cntl_reg); + temp |= USS820_RXCON_RXFFRC; + bus_space_write_1(td->io_tag, td->io_hdl, + td->rx_cntl_reg, temp); + + /* copy data into real buffer */ + usb2_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + } else { + sc->sc_dv_addr = 0xFF; + } + return (0); /* complete */ + +not_complete: + /* clear end overwrite flag, if any */ + if (rx_stat & USS820_RXSTAT_RXSETUP) { + uss820dci_update_shared_1(sc, USS820_RXSTAT, + 0xFF ^ (USS820_RXSTAT_EDOVW | + USS820_RXSTAT_STOVW | + USS820_RXSTAT_RXSETUP), 0); + } + return (1); /* not complete */ + +} + +static uint8_t +uss820dci_data_rx(struct uss820dci_td *td) +{ + struct usb2_page_search buf_res; + uint16_t count; + uint8_t rx_flag; + uint8_t rx_stat; + uint8_t rx_cntl; + uint8_t to; + uint8_t got_short; + + to = 2; /* don't loop forever! */ + got_short = 0; + + /* select the correct endpoint */ + bus_space_write_1(td->io_tag, td->io_hdl, td->ep_reg, td->ep_index); + + /* check if any of the FIFO banks have data */ +repeat: + /* read out FIFO flag */ + rx_flag = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_flag_reg); + /* read out FIFO status */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_stat_reg); + + DPRINTFN(5, "rx_stat=0x%02x rx_flag=0x%02x rem=%u\n", + rx_stat, rx_flag, td->remainder); + + if (rx_stat & (USS820_RXSTAT_RXSETUP | + USS820_RXSTAT_RXSOVW | + USS820_RXSTAT_EDOVW)) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + /* check for errors */ + if (rx_flag & (USS820_RXFLG_RXOVF | + USS820_RXFLG_RXURF)) { + DPRINTFN(5, "overflow or underflow\n"); + /* should not happen */ + td->error = 1; + return (0); /* complete */ + } + /* check status */ + if (!(rx_flag & (USS820_RXFLG_RXFIF0 | + USS820_RXFLG_RXFIF1))) { + + /* read out EPCON register */ + /* enable RX input */ + if (!td->did_stall) { + uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc), + USS820_EPCON, 0xFF, USS820_EPCON_RXIE); + td->did_stall = 1; + } + return (1); /* not complete */ + } + /* get the packet byte count */ + count = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_count_low_reg); + + count |= (bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_count_high_reg) << 8); + count &= 0x3FF; + + DPRINTFN(5, "count=0x%04x\n", count); + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + usb2_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* receive data */ + bus_space_read_multi_1(td->io_tag, td->io_hdl, + td->rx_fifo_reg, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* set RXFFRC bit */ + rx_cntl = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_cntl_reg); + rx_cntl |= USS820_RXCON_RXFFRC; + bus_space_write_1(td->io_tag, td->io_hdl, + td->rx_cntl_reg, rx_cntl); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +uss820dci_data_tx(struct uss820dci_td *td) +{ + struct usb2_page_search buf_res; + uint16_t count; + uint16_t count_copy; + uint8_t rx_stat; + uint8_t tx_flag; + uint8_t to; + + /* select the correct endpoint */ + bus_space_write_1(td->io_tag, td->io_hdl, + td->ep_reg, td->ep_index); + + to = 2; /* don't loop forever! */ + +repeat: + /* read out TX FIFO flags */ + tx_flag = bus_space_read_1(td->io_tag, td->io_hdl, + td->tx_flag_reg); + + /* read out RX FIFO status last */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_stat_reg); + + DPRINTFN(5, "rx_stat=0x%02x tx_flag=0x%02x rem=%u\n", + rx_stat, tx_flag, td->remainder); + + if (rx_stat & (USS820_RXSTAT_RXSETUP | + USS820_RXSTAT_RXSOVW | + USS820_RXSTAT_EDOVW)) { + /* + * The current transfer was aborted + * by the USB Host + */ + td->error = 1; + return (0); /* complete */ + } + if (tx_flag & (USS820_TXFLG_TXOVF | + USS820_TXFLG_TXURF)) { + td->error = 1; + return (0); /* complete */ + } + if (tx_flag & USS820_TXFLG_TXFIF0) { + if (tx_flag & USS820_TXFLG_TXFIF1) { + return (1); /* not complete */ + } + } + if ((!td->support_multi_buffer) && + (tx_flag & (USS820_TXFLG_TXFIF0 | + USS820_TXFLG_TXFIF1))) { + return (1); /* not complete */ + } + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + count_copy = count; + while (count > 0) { + + usb2_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* transmit data */ + bus_space_write_multi_1(td->io_tag, td->io_hdl, + td->tx_fifo_reg, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* post-write high packet byte count first */ + bus_space_write_1(td->io_tag, td->io_hdl, + td->tx_count_high_reg, count_copy >> 8); + + /* post-write low packet byte count last */ + bus_space_write_1(td->io_tag, td->io_hdl, + td->tx_count_low_reg, count_copy); + + /* + * Enable TX output, which must happen after that we have written + * data into the FIFO. This is undocumented. + */ + if (!td->did_stall) { + uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc), + USS820_EPCON, 0xFF, USS820_EPCON_TXOE); + td->did_stall = 1; + } + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +uss820dci_data_tx_sync(struct uss820dci_td *td) +{ + struct uss820dci_softc *sc; + uint8_t rx_stat; + uint8_t tx_flag; + + /* select the correct endpoint */ + bus_space_write_1(td->io_tag, td->io_hdl, + td->ep_reg, td->ep_index); + + /* read out TX FIFO flag */ + tx_flag = bus_space_read_1(td->io_tag, td->io_hdl, + td->tx_flag_reg); + + /* read out RX FIFO status last */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + td->rx_stat_reg); + + DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder); + + if (rx_stat & (USS820_RXSTAT_RXSETUP | + USS820_RXSTAT_RXSOVW | + USS820_RXSTAT_EDOVW)) { + DPRINTFN(5, "faking complete\n"); + /* Race condition */ + return (0); /* complete */ + } + DPRINTFN(5, "tx_flag=0x%02x rem=%u\n", + tx_flag, td->remainder); + + if (tx_flag & (USS820_TXFLG_TXOVF | + USS820_TXFLG_TXURF)) { + td->error = 1; + return (0); /* complete */ + } + if (tx_flag & (USS820_TXFLG_TXFIF0 | + USS820_TXFLG_TXFIF1)) { + return (1); /* not complete */ + } + sc = USS820_DCI_PC2SC(td->pc); + if (sc->sc_dv_addr != 0xFF) { + /* write function address */ + uss820dci_set_address(sc, sc->sc_dv_addr); + } + return (0); /* complete */ +} + +static uint8_t +uss820dci_xfer_do_fifo(struct usb2_xfer *xfer) +{ + struct uss820dci_td *td; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor. + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + /* compute all actual lengths */ + + uss820dci_standard_done(xfer); + + return (0); /* complete */ +} + +static void +uss820dci_interrupt_poll(struct uss820dci_softc *sc) +{ + struct usb2_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!uss820dci_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } + return; +} + +static void +uss820dci_wait_suspend(struct uss820dci_softc *sc, uint8_t on) +{ + uint8_t scr; + uint8_t scratch; + + scr = USS820_READ_1(sc, USS820_SCR); + scratch = USS820_READ_1(sc, USS820_SCRATCH); + + if (on) { + scr |= USS820_SCR_IE_SUSP; + scratch &= ~USS820_SCRATCH_IE_RESUME; + } else { + scr &= ~USS820_SCR_IE_SUSP; + scratch |= USS820_SCRATCH_IE_RESUME; + } + + USS820_WRITE_1(sc, USS820_SCR, scr); + USS820_WRITE_1(sc, USS820_SCRATCH, scratch); + return; +} + +void +uss820dci_interrupt(struct uss820dci_softc *sc) +{ + uint8_t ssr; + uint8_t event; + + mtx_lock(&sc->sc_bus.mtx); + + ssr = USS820_READ_1(sc, USS820_SSR); + + ssr &= (USS820_SSR_SUSPEND | + USS820_SSR_RESUME | + USS820_SSR_RESET); + + /* acknowledge all interrupts */ + + uss820dci_update_shared_1(sc, USS820_SSR, 0, 0); + + /* check for any bus state change interrupts */ + + if (ssr) { + + event = 0; + + if (ssr & USS820_SSR_RESET) { + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* disable resume interrupt */ + uss820dci_wait_suspend(sc, 1); + + event = 1; + } + /* + * If "RESUME" and "SUSPEND" is set at the same time + * we interpret that like "RESUME". Resume is set when + * there is at least 3 milliseconds of inactivity on + * the USB BUS. + */ + if (ssr & USS820_SSR_RESUME) { + if (sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + /* disable resume interrupt */ + uss820dci_wait_suspend(sc, 1); + event = 1; + } + } else if (ssr & USS820_SSR_SUSPEND) { + if (!sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + /* enable resume interrupt */ + uss820dci_wait_suspend(sc, 0); + event = 1; + } + } + if (event) { + + DPRINTF("real bus interrupt 0x%02x\n", ssr); + + /* complete root HUB interrupt endpoint */ + + usb2_sw_transfer(&sc->sc_root_intr, + &uss820dci_root_intr_done); + } + } + /* acknowledge all SBI interrupts */ + uss820dci_update_shared_1(sc, USS820_SBI, 0, 0); + + /* acknowledge all SBI1 interrupts */ + uss820dci_update_shared_1(sc, USS820_SBI1, 0, 0); + + /* poll all active transfers */ + uss820dci_interrupt_poll(sc); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +uss820dci_setup_standard_chain_sub(struct uss820_std_temp *temp) +{ + struct uss820dci_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->error = 0; + td->did_stall = 0; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; + return; +} + +static void +uss820dci_setup_standard_chain(struct usb2_xfer *xfer) +{ + struct uss820_std_temp temp; + struct uss820dci_softc *sc; + struct uss820dci_td *td; + uint32_t x; + uint8_t ep_no; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpoint), + xfer->sumlen, usb2_get_speed(xfer->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.offset = 0; + + sc = xfer->usb2_sc; + ep_no = (xfer->endpoint & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &uss820dci_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + + uss820dci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpoint & UE_DIR_IN) { + temp.func = &uss820dci_data_tx; + } else { + temp.func = &uss820dci_data_rx; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + temp.setup_alt_next = 0; + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + uss820dci_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + uint8_t need_sync; + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + if (xfer->endpoint & UE_DIR_IN) { + temp.func = &uss820dci_data_rx; + need_sync = 0; + } else { + temp.func = &uss820dci_data_tx; + need_sync = 1; + } + temp.len = 0; + temp.short_pkt = 0; + + uss820dci_setup_standard_chain_sub(&temp); + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &uss820dci_data_tx_sync; + temp.len = 0; + temp.short_pkt = 0; + + uss820dci_setup_standard_chain_sub(&temp); + } + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; + + return; +} + +static void +uss820dci_timeout(void *arg) +{ + struct usb2_xfer *xfer = arg; + struct uss820dci_softc *sc = xfer->usb2_sc; + + DPRINTF("xfer=%p\n", xfer); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + /* transfer is transferred */ + uss820dci_device_done(xfer, USB_ERR_TIMEOUT); + + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +static void +uss820dci_intr_set(struct usb2_xfer *xfer, uint8_t set) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + uint8_t ep_no = (xfer->endpoint & UE_ADDR); + uint8_t ep_reg; + uint8_t temp; + + DPRINTFN(15, "endpoint 0x%02x\n", xfer->endpoint); + + if (ep_no > 3) { + ep_reg = USS820_SBIE1; + } else { + ep_reg = USS820_SBIE; + } + + ep_no &= 3; + ep_no = 1 << (2 * ep_no); + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + ep_no <<= 1; /* RX interrupt only */ + } else { + ep_no |= (ep_no << 1); /* RX and TX interrupt */ + } + } else { + if (!(xfer->endpoint & UE_DIR_IN)) { + ep_no <<= 1; + } + } + temp = USS820_READ_1(sc, ep_reg); + if (set) { + temp |= ep_no; + } else { + temp &= ~ep_no; + } + USS820_WRITE_1(sc, ep_reg, temp); + return; +} + +static void +uss820dci_start_standard_chain(struct usb2_xfer *xfer) +{ + DPRINTFN(9, "\n"); + + /* poll one time */ + if (uss820dci_xfer_do_fifo(xfer)) { + + /* + * Only enable the endpoint interrupt when we are + * actually waiting for data, hence we are dealing + * with level triggered interrupts ! + */ + uss820dci_intr_set(xfer, 1); + + /* put transfer on interrupt queue */ + usb2_transfer_enqueue(&xfer->udev->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usb2_transfer_timeout_ms(xfer, + &uss820dci_timeout, xfer->timeout); + } + } + return; +} + +static void +uss820dci_root_intr_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + + DPRINTFN(9, "\n"); + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_PRE_DATA) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + uss820dci_device_done(xfer, std->err); + } + goto done; + } + /* setup buffer */ + std->ptr = sc->sc_hub_idata; + std->len = sizeof(sc->sc_hub_idata); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + +done: + return; +} + +static usb2_error_t +uss820dci_standard_done_sub(struct usb2_xfer *xfer) +{ + struct uss820dci_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +uss820dci_standard_done(struct usb2_xfer *xfer) +{ + usb2_error_t err = 0; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->pipe); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = uss820dci_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = uss820dci_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = uss820dci_standard_done_sub(xfer); + } +done: + uss820dci_device_done(xfer, err); + return; +} + +/*------------------------------------------------------------------------* + * uss820dci_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +uss820dci_device_done(struct usb2_xfer *xfer, usb2_error_t error) +{ + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + DPRINTFN(2, "xfer=%p, pipe=%p, error=%d\n", + xfer, xfer->pipe, error); + + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + uss820dci_intr_set(xfer, 0); + } + /* dequeue transfer and start next transfer */ + usb2_transfer_done(xfer, error); + return; +} + +static void +uss820dci_set_stall(struct usb2_device *udev, struct usb2_xfer *xfer, + struct usb2_pipe *pipe) +{ + struct uss820dci_softc *sc; + uint8_t ep_no; + uint8_t ep_type; + uint8_t ep_dir; + uint8_t temp; + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + DPRINTFN(5, "pipe=%p\n", pipe); + + if (xfer) { + /* cancel any ongoing transfers */ + uss820dci_device_done(xfer, USB_ERR_STALLED); + } + /* set FORCESTALL */ + sc = USS820_DCI_BUS2SC(udev->bus); + ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR); + ep_dir = (pipe->edesc->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)); + ep_type = (pipe->edesc->bmAttributes & UE_XFERTYPE); + + if (ep_type == UE_CONTROL) { + /* should not happen */ + return; + } + USS820_WRITE_1(sc, USS820_EPINDEX, ep_no); + + if (ep_dir == UE_DIR_IN) { + temp = USS820_EPCON_TXSTL; + } else { + temp = USS820_EPCON_RXSTL; + } + uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, temp); + return; +} + +static void +uss820dci_clear_stall_sub(struct uss820dci_softc *sc, + uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) +{ + uint8_t temp; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* select endpoint index */ + USS820_WRITE_1(sc, USS820_EPINDEX, ep_no); + + /* clear stall and disable I/O transfers */ + if (ep_dir == UE_DIR_IN) { + temp = 0xFF ^ (USS820_EPCON_TXOE | + USS820_EPCON_TXSTL); + } else { + temp = 0xFF ^ (USS820_EPCON_RXIE | + USS820_EPCON_RXSTL); + } + uss820dci_update_shared_1(sc, USS820_EPCON, temp, 0); + + if (ep_dir == UE_DIR_IN) { + /* reset data toggle */ + USS820_WRITE_1(sc, USS820_TXSTAT, + USS820_TXSTAT_TXSOVW); + + /* reset FIFO */ + temp = USS820_READ_1(sc, USS820_TXCON); + temp |= USS820_TXCON_TXCLR; + USS820_WRITE_1(sc, USS820_TXCON, temp); + temp &= ~USS820_TXCON_TXCLR; + USS820_WRITE_1(sc, USS820_TXCON, temp); + } else { + + /* reset data toggle */ + uss820dci_update_shared_1(sc, USS820_RXSTAT, + 0, USS820_RXSTAT_RXSOVW); + + /* reset FIFO */ + temp = USS820_READ_1(sc, USS820_RXCON); + temp |= USS820_RXCON_RXCLR; + temp &= ~USS820_RXCON_RXFFRC; + USS820_WRITE_1(sc, USS820_RXCON, temp); + temp &= ~USS820_RXCON_RXCLR; + USS820_WRITE_1(sc, USS820_RXCON, temp); + } + return; +} + +static void +uss820dci_clear_stall(struct usb2_device *udev, struct usb2_pipe *pipe) +{ + struct uss820dci_softc *sc; + struct usb2_endpoint_descriptor *ed; + + mtx_assert(&udev->bus->mtx, MA_OWNED); + + DPRINTFN(5, "pipe=%p\n", pipe); + + /* check mode */ + if (udev->flags.usb2_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = USS820_DCI_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = pipe->edesc; + + /* reset endpoint */ + uss820dci_clear_stall_sub(sc, + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); + + return; +} + +usb2_error_t +uss820dci_init(struct uss820dci_softc *sc) +{ + const struct usb2_hw_ep_profile *pf; + uint8_t n; + uint8_t temp; + + DPRINTF("start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_1_1; + sc->sc_bus.methods = &uss820dci_bus_methods; + + mtx_lock(&sc->sc_bus.mtx); + + /* we always have VBUS */ + sc->sc_flags.status_vbus = 1; + + /* reset the chip */ + USS820_WRITE_1(sc, USS820_SCR, USS820_SCR_SRESET); + DELAY(100); + USS820_WRITE_1(sc, USS820_SCR, 0); + + /* wait for reset to complete */ + for (n = 0;; n++) { + + temp = USS820_READ_1(sc, USS820_MCSR); + + if (temp & USS820_MCSR_INIT) { + break; + } + if (n == 100) { + mtx_unlock(&sc->sc_bus.mtx); + return (USB_ERR_INVAL); + } + /* wait a little for things to stabilise */ + DELAY(100); + } + + /* do a pulldown */ + uss820dci_pull_down(sc); + + /* wait 10ms for pulldown to stabilise */ + usb2_pause_mtx(&sc->sc_bus.mtx, 10); + + /* check hardware revision */ + temp = USS820_READ_1(sc, USS820_REV); + + if (temp < 0x13) { + mtx_unlock(&sc->sc_bus.mtx); + return (USB_ERR_INVAL); + } + /* enable interrupts */ + USS820_WRITE_1(sc, USS820_SCR, + USS820_SCR_T_IRQ | + USS820_SCR_IE_RESET | + USS820_SCR_IE_SUSP | + USS820_SCR_IRQPOL); + + /* enable interrupts */ + USS820_WRITE_1(sc, USS820_SCRATCH, + USS820_SCRATCH_IE_RESUME); + + /* enable features */ + USS820_WRITE_1(sc, USS820_MCSR, + USS820_MCSR_BDFEAT | + USS820_MCSR_FEAT); + + sc->sc_flags.mcsr_feat = 1; + + /* disable interrupts */ + USS820_WRITE_1(sc, USS820_SBIE, 0); + + /* disable interrupts */ + USS820_WRITE_1(sc, USS820_SBIE1, 0); + + /* disable all endpoints */ + for (n = 0; n != USS820_EP_MAX; n++) { + + /* select endpoint */ + USS820_WRITE_1(sc, USS820_EPINDEX, n); + + /* disable endpoint */ + uss820dci_update_shared_1(sc, USS820_EPCON, 0, 0); + } + + /* + * Initialise default values for some registers that cannot be + * changed during operation! + */ + for (n = 0; n != USS820_EP_MAX; n++) { + + uss820dci_get_hw_ep_profile(NULL, &pf, n); + + /* the maximum frame sizes should be the same */ + if (pf->max_in_frame_size != pf->max_out_frame_size) { + DPRINTF("Max frame size mismatch %u != %u\n", + pf->max_in_frame_size, pf->max_out_frame_size); + } + if (pf->support_isochronous) { + if (pf->max_in_frame_size <= 64) { + temp = (USS820_TXCON_FFSZ_16_64 | + USS820_TXCON_TXISO | + USS820_TXCON_ATM); + } else if (pf->max_in_frame_size <= 256) { + temp = (USS820_TXCON_FFSZ_64_256 | + USS820_TXCON_TXISO | + USS820_TXCON_ATM); + } else if (pf->max_in_frame_size <= 512) { + temp = (USS820_TXCON_FFSZ_8_512 | + USS820_TXCON_TXISO | + USS820_TXCON_ATM); + } else { /* 1024 bytes */ + temp = (USS820_TXCON_FFSZ_32_1024 | + USS820_TXCON_TXISO | + USS820_TXCON_ATM); + } + } else { + if ((pf->max_in_frame_size <= 8) && + (sc->sc_flags.mcsr_feat)) { + temp = (USS820_TXCON_FFSZ_8_512 | + USS820_TXCON_ATM); + } else if (pf->max_in_frame_size <= 16) { + temp = (USS820_TXCON_FFSZ_16_64 | + USS820_TXCON_ATM); + } else if ((pf->max_in_frame_size <= 32) && + (sc->sc_flags.mcsr_feat)) { + temp = (USS820_TXCON_FFSZ_32_1024 | + USS820_TXCON_ATM); + } else { /* 64 bytes */ + temp = (USS820_TXCON_FFSZ_64_256 | + USS820_TXCON_ATM); + } + } + + /* need to configure the chip early */ + + USS820_WRITE_1(sc, USS820_EPINDEX, n); + USS820_WRITE_1(sc, USS820_TXCON, temp); + USS820_WRITE_1(sc, USS820_RXCON, temp); + + if (pf->support_control) { + temp = USS820_EPCON_CTLEP | + USS820_EPCON_RXSPM | + USS820_EPCON_RXIE | + USS820_EPCON_RXEPEN | + USS820_EPCON_TXOE | + USS820_EPCON_TXEPEN; + } else { + temp = USS820_EPCON_RXEPEN | USS820_EPCON_TXEPEN; + } + + uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, temp); + } + + mtx_unlock(&sc->sc_bus.mtx); + + /* catch any lost interrupts */ + + uss820dci_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +uss820dci_uninit(struct uss820dci_softc *sc) +{ + uint8_t temp; + + mtx_lock(&sc->sc_bus.mtx); + + /* disable all interrupts */ + temp = USS820_READ_1(sc, USS820_SCR); + temp &= ~USS820_SCR_T_IRQ; + USS820_WRITE_1(sc, USS820_SCR, temp); + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + uss820dci_pull_down(sc); + mtx_unlock(&sc->sc_bus.mtx); + + return; +} + +void +uss820dci_suspend(struct uss820dci_softc *sc) +{ + return; +} + +void +uss820dci_resume(struct uss820dci_softc *sc) +{ + return; +} + +static void +uss820dci_do_poll(struct usb2_bus *bus) +{ + struct uss820dci_softc *sc = USS820_DCI_BUS2SC(bus); + + mtx_lock(&sc->sc_bus.mtx); + uss820dci_interrupt_poll(sc); + uss820dci_root_ctrl_poll(sc); + mtx_unlock(&sc->sc_bus.mtx); + return; +} + +/*------------------------------------------------------------------------* + * at91dci bulk support + *------------------------------------------------------------------------*/ +static void +uss820dci_device_bulk_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_bulk_close(struct usb2_xfer *xfer) +{ + uss820dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uss820dci_device_bulk_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_bulk_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + uss820dci_setup_standard_chain(xfer); + uss820dci_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods uss820dci_device_bulk_methods = +{ + .open = uss820dci_device_bulk_open, + .close = uss820dci_device_bulk_close, + .enter = uss820dci_device_bulk_enter, + .start = uss820dci_device_bulk_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * at91dci control support + *------------------------------------------------------------------------*/ +static void +uss820dci_device_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_ctrl_close(struct usb2_xfer *xfer) +{ + uss820dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uss820dci_device_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_ctrl_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + uss820dci_setup_standard_chain(xfer); + uss820dci_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods uss820dci_device_ctrl_methods = +{ + .open = uss820dci_device_ctrl_open, + .close = uss820dci_device_ctrl_close, + .enter = uss820dci_device_ctrl_enter, + .start = uss820dci_device_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * at91dci interrupt support + *------------------------------------------------------------------------*/ +static void +uss820dci_device_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_intr_close(struct usb2_xfer *xfer) +{ + uss820dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uss820dci_device_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_intr_start(struct usb2_xfer *xfer) +{ + /* setup TDs */ + uss820dci_setup_standard_chain(xfer); + uss820dci_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods uss820dci_device_intr_methods = +{ + .open = uss820dci_device_intr_open, + .close = uss820dci_device_intr_close, + .enter = uss820dci_device_intr_enter, + .start = uss820dci_device_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * at91dci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +uss820dci_device_isoc_fs_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_isoc_fs_close(struct usb2_xfer *xfer) +{ + uss820dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uss820dci_device_isoc_fs_enter(struct usb2_xfer *xfer) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + uint32_t temp; + uint32_t nframes; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->pipe->isoc_next, xfer->nframes); + + /* get the current frame index - we don't need the high bits */ + + nframes = USS820_READ_1(sc, USS820_SOFL); + + /* + * check if the frame index is within the window where the + * frames will be inserted + */ + temp = (nframes - xfer->pipe->isoc_next) & USS820_SOFL_MASK; + + if ((xfer->pipe->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->pipe->isoc_next = (nframes + 3) & USS820_SOFL_MASK; + xfer->pipe->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->pipe->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->pipe->isoc_next - nframes) & USS820_SOFL_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb2_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->pipe->isoc_next += xfer->nframes; + + /* setup TDs */ + uss820dci_setup_standard_chain(xfer); + return; +} + +static void +uss820dci_device_isoc_fs_start(struct usb2_xfer *xfer) +{ + /* start TD chain */ + uss820dci_start_standard_chain(xfer); + return; +} + +struct usb2_pipe_methods uss820dci_device_isoc_fs_methods = +{ + .open = uss820dci_device_isoc_fs_open, + .close = uss820dci_device_isoc_fs_close, + .enter = uss820dci_device_isoc_fs_enter, + .start = uss820dci_device_isoc_fs_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +/*------------------------------------------------------------------------* + * at91dci root control support + *------------------------------------------------------------------------* + * simulate a hardware HUB by handling + * all the necessary requests + *------------------------------------------------------------------------*/ + +static void +uss820dci_root_ctrl_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_root_ctrl_close(struct usb2_xfer *xfer) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + + if (sc->sc_root_ctrl.xfer == xfer) { + sc->sc_root_ctrl.xfer = NULL; + } + uss820dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +/* + * USB descriptors for the virtual Root HUB: + */ + +static const struct usb2_device_descriptor uss820dci_devd = { + .bLength = sizeof(struct usb2_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct usb2_device_qualifier uss820dci_odevd = { + .bLength = sizeof(struct usb2_device_qualifier), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, +}; + +static const struct uss820dci_config_desc uss820dci_confd = { + .confd = { + .bLength = sizeof(struct usb2_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(uss820dci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb2_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = UIPROTO_HSHUBSTT, + }, + + .endpd = { + .bLength = sizeof(struct usb2_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | USS820_DCI_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb2_hub_descriptor_min uss820dci_hubd = { + .bDescLength = sizeof(uss820dci_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 16, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'A', 0, 'G', 0, 'E', 0, 'R', 0, 'E', 0 + +#define STRING_PRODUCT \ + 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, uss820dci_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, uss820dci_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, uss820dci_product); + +static void +uss820dci_root_ctrl_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_root_ctrl_start(struct usb2_xfer *xfer) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + + sc->sc_root_ctrl.xfer = xfer; + + usb2_config_td_queue_command( + &sc->sc_config_td, NULL, &uss820dci_root_ctrl_task, 0, 0); + + return; +} + +static void +uss820dci_root_ctrl_task(struct uss820dci_softc *sc, + struct uss820dci_config_copy *cc, uint16_t refcount) +{ + uss820dci_root_ctrl_poll(sc); + return; +} + +static void +uss820dci_root_ctrl_done(struct usb2_xfer *xfer, + struct usb2_sw_transfer *std) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + uint16_t value; + uint16_t index; + uint8_t use_polling; + + mtx_assert(&sc->sc_bus.mtx, MA_OWNED); + + if (std->state != USB_SW_TR_SETUP) { + if (std->state == USB_SW_TR_PRE_CALLBACK) { + /* transfer transferred */ + uss820dci_device_done(xfer, std->err); + } + goto done; + } + /* buffer reset */ + std->ptr = USB_ADD_BYTES(&sc->sc_hub_temp, 0); + std->len = 0; + + value = UGETW(std->req.wValue); + index = UGETW(std->req.wIndex); + + use_polling = mtx_owned(xfer->priv_mtx) ? 1 : 0; + + /* demultiplex the control request */ + + switch (std->req.bmRequestType) { + case UT_READ_DEVICE: + switch (std->req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (std->req.bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(std->req.wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(std->req.wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (std->req.bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (std->req.bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (std->req.bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (std->req.bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (std->req.bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (std->req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + std->len = sizeof(uss820dci_devd); + std->ptr = USB_ADD_BYTES(&uss820dci_devd, 0); + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + std->len = sizeof(uss820dci_confd); + std->ptr = USB_ADD_BYTES(&uss820dci_confd, 0); + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + std->len = sizeof(uss820dci_langtab); + std->ptr = USB_ADD_BYTES(&uss820dci_langtab, 0); + goto tr_valid; + + case 1: /* Vendor */ + std->len = sizeof(uss820dci_vendor); + std->ptr = USB_ADD_BYTES(&uss820dci_vendor, 0); + goto tr_valid; + + case 2: /* Product */ + std->len = sizeof(uss820dci_product); + std->ptr = USB_ADD_BYTES(&uss820dci_product, 0); + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + std->len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + std->len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + std->len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + std->len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + uss820dci_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + uss820dci_pull_down(sc); + break; + case UHF_C_PORT_CONNECTION: + sc->sc_flags.change_connect = 0; + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + std->err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(9, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + uss820dci_pull_up(sc); + } else { + uss820dci_pull_down(sc); + } + + /* Select FULL-speed and Device Side Mode */ + + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + std->len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + std->ptr = USB_ADD_BYTES(&uss820dci_hubd, 0); + std->len = sizeof(uss820dci_hubd); + goto tr_valid; + +tr_stalled: + std->err = USB_ERR_STALLED; +tr_valid: +done: + return; +} + +static void +uss820dci_root_ctrl_poll(struct uss820dci_softc *sc) +{ + usb2_sw_transfer(&sc->sc_root_ctrl, + &uss820dci_root_ctrl_done); + return; +} + +struct usb2_pipe_methods uss820dci_root_ctrl_methods = +{ + .open = uss820dci_root_ctrl_open, + .close = uss820dci_root_ctrl_close, + .enter = uss820dci_root_ctrl_enter, + .start = uss820dci_root_ctrl_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 0, +}; + +/*------------------------------------------------------------------------* + * at91dci root interrupt support + *------------------------------------------------------------------------*/ +static void +uss820dci_root_intr_open(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_root_intr_close(struct usb2_xfer *xfer) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + + if (sc->sc_root_intr.xfer == xfer) { + sc->sc_root_intr.xfer = NULL; + } + uss820dci_device_done(xfer, USB_ERR_CANCELLED); + return; +} + +static void +uss820dci_root_intr_enter(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_root_intr_start(struct usb2_xfer *xfer) +{ + struct uss820dci_softc *sc = xfer->usb2_sc; + + sc->sc_root_intr.xfer = xfer; + return; +} + +struct usb2_pipe_methods uss820dci_root_intr_methods = +{ + .open = uss820dci_root_intr_open, + .close = uss820dci_root_intr_close, + .enter = uss820dci_root_intr_enter, + .start = uss820dci_root_intr_start, + .enter_is_cancelable = 1, + .start_is_cancelable = 1, +}; + +static void +uss820dci_xfer_setup(struct usb2_setup_params *parm) +{ + const struct usb2_hw_ep_profile *pf; + struct uss820dci_softc *sc; + struct usb2_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + sc = USS820_DCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * setup xfer + */ + xfer->usb2_sc = sc; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x500; + + usb2_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if (parm->methods == &uss820dci_device_ctrl_methods) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; + + } else if (parm->methods == &uss820dci_device_bulk_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &uss820dci_device_intr_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &uss820dci_device_isoc_fs_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else { + + ntd = 0; + } + + /* + * check if "usb2_transfer_setup_sub" set an error + */ + if (parm->err) { + return; + } + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + if (ntd) { + + ep_no = xfer->endpoint & UE_ADDR; + uss820dci_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + } else { + ep_no = 0; + pf = NULL; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct uss820dci_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->io_tag = sc->sc_io_tag; + td->io_hdl = sc->sc_io_hdl; + td->max_packet_size = xfer->max_packet_size; + td->rx_stat_reg = USS820_GET_REG(sc, USS820_RXSTAT); + td->tx_stat_reg = USS820_GET_REG(sc, USS820_TXSTAT); + td->rx_flag_reg = USS820_GET_REG(sc, USS820_RXFLG); + td->tx_flag_reg = USS820_GET_REG(sc, USS820_TXFLG); + td->rx_fifo_reg = USS820_GET_REG(sc, USS820_RXDAT); + td->tx_fifo_reg = USS820_GET_REG(sc, USS820_TXDAT); + td->rx_count_low_reg = USS820_GET_REG(sc, USS820_RXCNTL); + td->rx_count_high_reg = USS820_GET_REG(sc, USS820_RXCNTH); + td->tx_count_low_reg = USS820_GET_REG(sc, USS820_TXCNTL); + td->tx_count_high_reg = USS820_GET_REG(sc, USS820_TXCNTH); + td->rx_cntl_reg = USS820_GET_REG(sc, USS820_RXCON); + td->tx_cntl_reg = USS820_GET_REG(sc, USS820_TXCON); + td->pend_reg = USS820_GET_REG(sc, USS820_PEND); + td->ep_reg = USS820_GET_REG(sc, USS820_EPINDEX); + td->ep_index = ep_no; + if (pf->support_multi_buffer && + (parm->methods != &uss820dci_device_ctrl_methods)) { + td->support_multi_buffer = 1; + } + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; + return; +} + +static void +uss820dci_xfer_unsetup(struct usb2_xfer *xfer) +{ + return; +} + +static void +uss820dci_pipe_init(struct usb2_device *udev, struct usb2_endpoint_descriptor *edesc, + struct usb2_pipe *pipe) +{ + struct uss820dci_softc *sc = USS820_DCI_BUS2SC(udev->bus); + + DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + pipe, udev->address, + edesc->bEndpointAddress, udev->flags.usb2_mode, + sc->sc_rt_addr); + + if (udev->device_index == sc->sc_rt_addr) { + + if (udev->flags.usb2_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + switch (edesc->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &uss820dci_root_ctrl_methods; + break; + case UE_DIR_IN | USS820_DCI_INTR_ENDPT: + pipe->methods = &uss820dci_root_intr_methods; + break; + default: + /* do nothing */ + break; + } + } else { + + if (udev->flags.usb2_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if (udev->speed != USB_SPEED_FULL) { + /* not supported */ + return; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + pipe->methods = &uss820dci_device_ctrl_methods; + break; + case UE_INTERRUPT: + pipe->methods = &uss820dci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + pipe->methods = &uss820dci_device_isoc_fs_methods; + break; + case UE_BULK: + pipe->methods = &uss820dci_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } + return; +} + +struct usb2_bus_methods uss820dci_bus_methods = +{ + .pipe_init = &uss820dci_pipe_init, + .xfer_setup = &uss820dci_xfer_setup, + .xfer_unsetup = &uss820dci_xfer_unsetup, + .do_poll = &uss820dci_do_poll, + .get_hw_ep_profile = &uss820dci_get_hw_ep_profile, + .set_stall = &uss820dci_set_stall, + .clear_stall = &uss820dci_clear_stall, + .rem_wakeup_set = &uss820dci_rem_wakeup_set, +}; diff --git a/sys/dev/usb2/controller/uss820dci.h b/sys/dev/usb2/controller/uss820dci.h new file mode 100644 index 000000000000..aa8b535a9fd5 --- /dev/null +++ b/sys/dev/usb2/controller/uss820dci.h @@ -0,0 +1,375 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Hans Petter Selasky + * 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. + */ + +#ifndef _USS820_DCI_H_ +#define _USS820_DCI_H_ + +#define USS820_EP_MAX 8 /* maximum number of endpoints */ + +#define USS820_TXDAT 0x00 /* Transmit FIFO data */ + +#define USS820_TXCNTL 0x01 /* Transmit FIFO byte count low */ +#define USS820_TXCNTL_MASK 0xFF + +#define USS820_TXCNTH 0x02 /* Transmit FIFO byte count high */ +#define USS820_TXCNTH_MASK 0x03 +#define USS820_TXCNTH_UNUSED 0xFC + +#define USS820_TXCON 0x03 /* USB transmit FIFO control */ +#define USS820_TXCON_REVRP 0x01 +#define USS820_TXCON_ADVRM 0x02 +#define USS820_TXCON_ATM 0x04 /* Automatic Transmit Management */ +#define USS820_TXCON_TXISO 0x08 /* Transmit Isochronous Data */ +#define USS820_TXCON_UNUSED 0x10 +#define USS820_TXCON_FFSZ_16_64 0x00 +#define USS820_TXCON_FFSZ_64_256 0x20 +#define USS820_TXCON_FFSZ_8_512 0x40 +#define USS820_TXCON_FFSZ_32_1024 0x60 +#define USS820_TXCON_FFSZ_MASK 0x60 +#define USS820_TXCON_TXCLR 0x80 /* Transmit FIFO clear */ + +#define USS820_TXFLG 0x04 /* Transmit FIFO flag (Read Only) */ +#define USS820_TXFLG_TXOVF 0x01 /* TX overrun */ +#define USS820_TXFLG_TXURF 0x02 /* TX underrun */ +#define USS820_TXFLG_TXFULL 0x04 /* TX full */ +#define USS820_TXFLG_TXEMP 0x08 /* TX empty */ +#define USS820_TXFLG_UNUSED 0x30 +#define USS820_TXFLG_TXFIF0 0x40 +#define USS820_TXFLG_TXFIF1 0x80 + +#define USS820_RXDAT 0x05 /* Receive FIFO data */ + +#define USS820_RXCNTL 0x06 /* Receive FIFO byte count low */ +#define USS820_RXCNTL_MASK 0xFF + +#define USS820_RXCNTH 0x07 /* Receive FIFO byte count high */ +#define USS820_RXCNTH_MASK 0x03 +#define USS820_RXCNTH_UNUSED 0xFC + +#define USS820_RXCON 0x08 /* Receive FIFO control */ +#define USS820_RXCON_REVWP 0x01 +#define USS820_RXCON_ADVWM 0x02 +#define USS820_RXCON_ARM 0x04 /* Auto Receive Management */ +#define USS820_RXCON_RXISO 0x08 /* Receive Isochronous Data */ +#define USS820_RXCON_RXFFRC 0x10 /* FIFO Read Complete */ +#define USS820_RXCON_FFSZ_16_64 0x00 +#define USS820_RXCON_FFSZ_64_256 0x20 +#define USS820_RXCON_FFSZ_8_512 0x40 +#define USS820_RXCON_FFSZ_32_1024 0x60 +#define USS820_RXCON_RXCLR 0x80 /* Receive FIFO clear */ + +#define USS820_RXFLG 0x09 /* Receive FIFO flag (Read Only) */ +#define USS820_RXFLG_RXOVF 0x01 /* RX overflow */ +#define USS820_RXFLG_RXURF 0x02 /* RX underflow */ +#define USS820_RXFLG_RXFULL 0x04 /* RX full */ +#define USS820_RXFLG_RXEMP 0x08 /* RX empty */ +#define USS820_RXFLG_RXFLUSH 0x10 /* RX flush */ +#define USS820_RXFLG_UNUSED 0x20 +#define USS820_RXFLG_RXFIF0 0x40 +#define USS820_RXFLG_RXFIF1 0x80 + +#define USS820_EPINDEX 0x0a /* Endpoint index selection */ +#define USS820_EPINDEX_MASK 0x07 +#define USS820_EPINDEX_UNUSED 0xF8 + +#define USS820_EPCON 0x0b /* Endpoint control */ +#define USS820_EPCON_TXEPEN 0x01 /* Transmit Endpoint Enable */ +#define USS820_EPCON_TXOE 0x02 /* Transmit Output Enable */ +#define USS820_EPCON_RXEPEN 0x04 /* Receive Endpoint Enable */ +#define USS820_EPCON_RXIE 0x08 /* Receive Input Enable */ +#define USS820_EPCON_RXSPM 0x10 /* Receive Single-Packet Mode */ +#define USS820_EPCON_CTLEP 0x20 /* Control Endpoint */ +#define USS820_EPCON_TXSTL 0x40 /* Stall Transmit Endpoint */ +#define USS820_EPCON_RXSTL 0x80 /* Stall Receive Endpoint */ + +#define USS820_TXSTAT 0x0c /* Transmit status */ +#define USS820_TXSTAT_TXACK 0x01 /* Transmit Acknowledge */ +#define USS820_TXSTAT_TXERR 0x02 /* Transmit Error */ +#define USS820_TXSTAT_TXVOID 0x04 /* Transmit Void */ +#define USS820_TXSTAT_TXSOVW 0x08 /* Transmit Data Sequence Overwrite + * Bit */ +#define USS820_TXSTAT_TXFLUSH 0x10 /* Transmit FIFO Packet Flushed */ +#define USS820_TXSTAT_TXNAKE 0x20 /* Transmit NAK Mode Enable */ +#define USS820_TXSTAT_TXDSAM 0x40 /* Transmit Data-Set-Available Mode */ +#define USS820_TXSTAT_TXSEQ 0x80 /* Transmitter Current Sequence Bit */ + +#define USS820_RXSTAT 0x0d /* Receive status */ +#define USS820_RXSTAT_RXACK 0x01 /* Receive Acknowledge */ +#define USS820_RXSTAT_RXERR 0x02 /* Receive Error */ +#define USS820_RXSTAT_RXVOID 0x04 /* Receive Void */ +#define USS820_RXSTAT_RXSOVW 0x08 /* Receive Data Sequence Overwrite Bit */ +#define USS820_RXSTAT_EDOVW 0x10 /* End Overwrite Flag */ +#define USS820_RXSTAT_STOVW 0x20 /* Start Overwrite Flag */ +#define USS820_RXSTAT_RXSETUP 0x40 /* Received SETUP token */ +#define USS820_RXSTAT_RXSEQ 0x80 /* Receiver Endpoint Sequence Bit */ + +#define USS820_SOFL 0x0e /* Start Of Frame counter low */ +#define USS820_SOFL_MASK 0xFF + +#define USS820_SOFH 0x0f /* Start Of Frame counter high */ +#define USS820_SOFH_MASK 0x07 +#define USS820_SOFH_SOFDIS 0x08 /* SOF Pin Output Disable */ +#define USS820_SOFH_FTLOCK 0x10 /* Frame Timer Lock */ +#define USS820_SOFH_SOFIE 0x20 /* SOF Interrupt Enable */ +#define USS820_SOFH_ASOF 0x40 /* Any Start of Frame */ +#define USS820_SOFH_SOFACK 0x80 /* SOF Token Received Without Error */ + +#define USS820_FADDR 0x10 /* Function Address */ +#define USS820_FADDR_MASK 0x7F +#define USS820_FADDR_UNUSED 0x80 + +#define USS820_SCR 0x11 /* System Control */ +#define USS820_SCR_UNUSED 0x01 +#define USS820_SCR_T_IRQ 0x02 /* Global Interrupt Enable */ +#define USS820_SCR_IRQLVL 0x04 /* Interrupt Mode */ +#define USS820_SCR_SRESET 0x08 /* Software reset */ +#define USS820_SCR_IE_RESET 0x10 /* Enable Reset Interrupt */ +#define USS820_SCR_IE_SUSP 0x20 /* Enable Suspend Interrupt */ +#define USS820_SCR_RWUPE 0x40 /* Enable Remote Wake-Up Feature */ +#define USS820_SCR_IRQPOL 0x80 /* IRQ polarity */ + +#define USS820_SSR 0x12 /* System Status */ +#define USS820_SSR_RESET 0x01 /* Reset Condition Detected on USB + * cable */ +#define USS820_SSR_SUSPEND 0x02 /* Suspend Detected */ +#define USS820_SSR_RESUME 0x04 /* Resume Detected */ +#define USS820_SSR_SUSPDIS 0x08 /* Suspend Disable */ +#define USS820_SSR_SUSPPO 0x10 /* Suspend Power Off */ +#define USS820_SSR_UNUSED 0xE0 + +#define USS820_UNK0 0x13 /* Unknown */ +#define USS820_UNK0_UNUSED 0xFF + +#define USS820_SBI 0x14 /* Serial bus interrupt low */ +#define USS820_SBI_FTXD0 0x01 /* Function Transmit Done, EP 0 */ +#define USS820_SBI_FRXD0 0x02 /* Function Receive Done, EP 0 */ +#define USS820_SBI_FTXD1 0x04 +#define USS820_SBI_FRXD1 0x08 +#define USS820_SBI_FTXD2 0x10 +#define USS820_SBI_FRXD2 0x20 +#define USS820_SBI_FTXD3 0x40 +#define USS820_SBI_FRXD3 0x80 + +#define USS820_SBI1 0x15 /* Serial bus interrupt high */ +#define USS820_SBI1_FTXD4 0x01 +#define USS820_SBI1_FRXD4 0x02 +#define USS820_SBI1_FTXD5 0x04 +#define USS820_SBI1_FRXD5 0x08 +#define USS820_SBI1_FTXD6 0x10 +#define USS820_SBI1_FRXD6 0x20 +#define USS820_SBI1_FTXD7 0x40 +#define USS820_SBI1_FRXD7 0x80 + +#define USS820_SBIE 0x16 /* Serial bus interrupt enable low */ +#define USS820_SBIE_FTXIE0 0x01 +#define USS820_SBIE_FRXIE0 0x02 +#define USS820_SBIE_FTXIE1 0x04 +#define USS820_SBIE_FRXIE1 0x08 +#define USS820_SBIE_FTXIE2 0x10 +#define USS820_SBIE_FRXIE2 0x20 +#define USS820_SBIE_FTXIE3 0x40 +#define USS820_SBIE_FRXIE3 0x80 + +#define USS820_SBIE1 0x17 /* Serial bus interrupt enable high */ +#define USS820_SBIE1_FTXIE4 0x01 +#define USS820_SBIE1_FRXIE4 0x02 +#define USS820_SBIE1_FTXIE5 0x04 +#define USS820_SBIE1_FRXIE5 0x08 +#define USS820_SBIE1_FTXIE6 0x10 +#define USS820_SBIE1_FRXIE6 0x20 +#define USS820_SBIE1_FTXIE7 0x40 +#define USS820_SBIE1_FRXIE7 0x80 + +#define USS820_REV 0x18 /* Hardware revision */ +#define USS820_REV_MIN 0x0F +#define USS820_REV_MAJ 0xF0 + +#define USS820_LOCK 0x19 /* Suspend power-off locking */ +#define USS820_LOCK_UNLOCKED 0x01 +#define USS820_LOCK_UNUSED 0xFE + +#define USS820_PEND 0x1a /* Pend hardware status update */ +#define USS820_PEND_PEND 0x01 +#define USS820_PEND_UNUSED 0xFE + +#define USS820_SCRATCH 0x1b /* Scratch firmware information */ +#define USS820_SCRATCH_MASK 0x7F +#define USS820_SCRATCH_IE_RESUME 0x80 /* Enable Resume Interrupt */ + +#define USS820_MCSR 0x1c /* Miscellaneous control and status */ +#define USS820_MCSR_DPEN 0x01 /* DPLS Pull-Up Enable */ +#define USS820_MCSR_SUSPLOE 0x02 /* Suspend Lock Out Enable */ +#define USS820_MCSR_BDFEAT 0x04 /* Board Feature Enable */ +#define USS820_MCSR_FEAT 0x08 /* Feature Enable */ +#define USS820_MCSR_PKGID 0x10 /* Package Identification */ +#define USS820_MCSR_SUSPS 0x20 /* Suspend Status */ +#define USS820_MCSR_INIT 0x40 /* Device Initialized */ +#define USS820_MCSR_RWUPR 0x80 /* Remote Wakeup-Up Remember */ + +#define USS820_DSAV 0x1d /* Data set available low (Read Only) */ +#define USS820_DSAV_TXAV0 0x01 +#define USS820_DSAV_RXAV0 0x02 +#define USS820_DSAV_TXAV1 0x04 +#define USS820_DSAV_RXAV1 0x08 +#define USS820_DSAV_TXAV2 0x10 +#define USS820_DSAV_RXAV2 0x20 +#define USS820_DSAV_TXAV3 0x40 +#define USS820_DSAV_RXAV3 0x80 + +#define USS820_DSAV1 0x1e /* Data set available high */ +#define USS820_DSAV1_TXAV4 0x01 +#define USS820_DSAV1_RXAV4 0x02 +#define USS820_DSAV1_TXAV5 0x04 +#define USS820_DSAV1_RXAV5 0x08 +#define USS820_DSAV1_TXAV6 0x10 +#define USS820_DSAV1_RXAV6 0x20 +#define USS820_DSAV1_TXAV7 0x40 +#define USS820_DSAV1_RXAV7 0x80 + +#define USS820_UNK1 0x1f /* Unknown */ +#define USS820_UNK1_UNKNOWN 0xFF + +#define USS820_GET_REG(sc,reg) \ + ((reg) << (sc)->sc_reg_shift) + +#define USS820_READ_1(sc, reg) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, \ + USS820_GET_REG(sc,reg)) + +#define USS820_WRITE_1(sc, reg, data) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, \ + USS820_GET_REG(sc,reg), data) + +struct uss820dci_td; + +typedef uint8_t (uss820dci_cmd_t)(struct uss820dci_td *td); + +struct uss820dci_td { + bus_space_tag_t io_tag; + bus_space_handle_t io_hdl; + struct uss820dci_td *obj_next; + uss820dci_cmd_t *func; + struct usb2_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint16_t max_packet_size; + uint8_t rx_stat_reg; + uint8_t tx_stat_reg; + uint8_t rx_flag_reg; + uint8_t tx_flag_reg; + uint8_t rx_fifo_reg; + uint8_t tx_fifo_reg; + uint8_t rx_count_low_reg; + uint8_t rx_count_high_reg; + uint8_t tx_count_low_reg; + uint8_t tx_count_high_reg; + uint8_t rx_cntl_reg; + uint8_t tx_cntl_reg; + uint8_t ep_reg; + uint8_t pend_reg; + uint8_t ep_index; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t support_multi_buffer:1; + uint8_t did_stall:1; +}; + +struct uss820_std_temp { + uss820dci_cmd_t *func; + struct usb2_page_cache *pc; + struct uss820dci_td *td; + struct uss820dci_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; +}; + +struct uss820dci_config_desc { + struct usb2_config_descriptor confd; + struct usb2_interface_descriptor ifcd; + struct usb2_endpoint_descriptor endpd; +} __packed; + +union uss820_hub_temp { + uWord wValue; + struct usb2_port_status ps; +}; + +struct uss820_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; + uint8_t mcsr_feat:1; +}; + +struct uss820dci_softc { + struct usb2_bus sc_bus; + union uss820_hub_temp sc_hub_temp; + LIST_HEAD(, usb2_xfer) sc_interrupt_list_head; + struct usb2_sw_transfer sc_root_ctrl; + struct usb2_sw_transfer sc_root_intr; + struct usb2_config_td sc_config_td; + + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root HUB config */ + uint8_t sc_reg_shift; + + uint8_t sc_hub_idata[1]; + + struct uss820_flags sc_flags; +}; + +/* prototypes */ + +usb2_error_t uss820dci_init(struct uss820dci_softc *sc); +void uss820dci_uninit(struct uss820dci_softc *sc); +void uss820dci_suspend(struct uss820dci_softc *sc); +void uss820dci_resume(struct uss820dci_softc *sc); +void uss820dci_interrupt(struct uss820dci_softc *sc); + +#endif /* _USS820_DCI_H_ */ diff --git a/sys/dev/usb2/controller/uss820dci_atmelarm.c b/sys/dev/usb2/controller/uss820dci_atmelarm.c new file mode 100644 index 000000000000..be71fb310abb --- /dev/null +++ b/sys/dev/usb2/controller/uss820dci_atmelarm.c @@ -0,0 +1,247 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2008 Hans Petter Selasky + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static device_probe_t uss820_atmelarm_probe; +static device_attach_t uss820_atmelarm_attach; +static device_detach_t uss820_atmelarm_detach; +static device_suspend_t uss820_atmelarm_suspend; +static device_resume_t uss820_atmelarm_resume; +static device_shutdown_t uss820_atmelarm_shutdown; + +static device_method_t uss820dci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uss820_atmelarm_probe), + DEVMETHOD(device_attach, uss820_atmelarm_attach), + DEVMETHOD(device_detach, uss820_atmelarm_detach), + DEVMETHOD(device_suspend, uss820_atmelarm_suspend), + DEVMETHOD(device_resume, uss820_atmelarm_resume), + DEVMETHOD(device_shutdown, uss820_atmelarm_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t uss820dci_driver = { + .name = "uss820", + .methods = uss820dci_methods, + .size = sizeof(struct uss820dci_softc), +}; + +static devclass_t uss820dci_devclass; + +DRIVER_MODULE(uss820, atmelarm, uss820dci_driver, uss820dci_devclass, 0, 0); +MODULE_DEPEND(uss820, usb2_controller, 1, 1, 1); +MODULE_DEPEND(uss820, usb2_core, 1, 1, 1); + +static const char *const uss820_desc = "USS820 USB Device Controller"; + +static int +uss820_atmelarm_suspend(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + + err = bus_generic_suspend(dev); + if (err == 0) { + uss820dci_suspend(sc); + } + return (err); +} + +static int +uss820_atmelarm_resume(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + + uss820dci_resume(sc); + + err = bus_generic_resume(dev); + + return (err); +} + +static int +uss820_atmelarm_shutdown(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + + err = bus_generic_shutdown(dev); + if (err) + return (err); + + uss820dci_uninit(sc); + + return (0); +} + +static int +uss820_atmelarm_probe(device_t dev) +{ + device_set_desc(dev, uss820_desc); + return (0); /* success */ +} + +static int +uss820_atmelarm_attach(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + int rid; + + if (sc == NULL) { + return (ENXIO); + } + /* get all DMA memory */ + + if (usb2_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); + + if (!sc->sc_io_res) { + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + /* multiply all addresses by 4 */ + sc->sc_reg_shift = 2; + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + goto error; + } + sc->sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + err = usb2_config_td_setup(&sc->sc_config_td, sc, + &sc->sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)uss820dci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)uss820dci_interrupt, sc, &sc->sc_intr_hdl); +#endif + if (err) { + sc->sc_intr_hdl = NULL; + goto error; + } + err = uss820dci_init(sc); + if (err) { + device_printf(dev, "Init failed\n"); + goto error; + } + err = device_probe_and_attach(sc->sc_bus.bdev); + if (err) { + device_printf(dev, "USB probe and attach failed\n"); + goto error; + } + return (0); + +error: + uss820_atmelarm_detach(dev); + return (ENXIO); +} + +static int +uss820_atmelarm_detach(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(dev); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call at91_udp_uninit() after at91_udp_init() + */ + uss820dci_uninit(sc); + + err = bus_teardown_intr(dev, sc->sc_irq_res, + sc->sc_intr_hdl); + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_bus, NULL); + + return (0); +} diff --git a/sys/dev/usb2/controller/uss820dci_pccard.c b/sys/dev/usb2/controller/uss820dci_pccard.c new file mode 100644 index 000000000000..f57935d7bc79 --- /dev/null +++ b/sys/dev/usb2/controller/uss820dci_pccard.c @@ -0,0 +1,266 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2008 Hans Petter Selasky + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +static device_probe_t uss820_pccard_probe; +static device_attach_t uss820_pccard_attach; +static device_detach_t uss820_pccard_detach; +static device_suspend_t uss820_pccard_suspend; +static device_resume_t uss820_pccard_resume; +static device_shutdown_t uss820_pccard_shutdown; + +static uint8_t uss820_pccard_lookup(device_t dev); + +static device_method_t uss820dci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uss820_pccard_probe), + DEVMETHOD(device_attach, uss820_pccard_attach), + DEVMETHOD(device_detach, uss820_pccard_detach), + DEVMETHOD(device_suspend, uss820_pccard_suspend), + DEVMETHOD(device_resume, uss820_pccard_resume), + DEVMETHOD(device_shutdown, uss820_pccard_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t uss820dci_driver = { + .name = "uss820", + .methods = uss820dci_methods, + .size = sizeof(struct uss820dci_softc), +}; + +static devclass_t uss820dci_devclass; + +DRIVER_MODULE(uss820, pccard, uss820dci_driver, uss820dci_devclass, 0, 0); +MODULE_DEPEND(uss820, usb2_core, 1, 1, 1); + +static const char *const uss820_desc = "USS820 USB Device Controller"; + +static int +uss820_pccard_suspend(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + + err = bus_generic_suspend(dev); + if (err == 0) { + uss820dci_suspend(sc); + } + return (err); +} + +static int +uss820_pccard_resume(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + + uss820dci_resume(sc); + + err = bus_generic_resume(dev); + + return (err); +} + +static int +uss820_pccard_shutdown(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + + err = bus_generic_shutdown(dev); + if (err) + return (err); + + uss820dci_uninit(sc); + + return (0); +} + +static uint8_t +uss820_pccard_lookup(device_t dev) +{ + uint32_t prod; + uint32_t vend; + + pccard_get_vendor(dev, &vend); + pccard_get_product(dev, &prod); + + /* ID's will be added later */ + return (0); +} + +static int +uss820_pccard_probe(device_t dev) +{ + if (uss820_pccard_lookup(dev)) { + device_set_desc(dev, uss820_desc); + return (0); + } + return (ENXIO); +} +static int +uss820_pccard_attach(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + int rid; + + if (sc == NULL) { + return (ENXIO); + } + /* get all DMA memory */ + + if (usb2_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); + + if (!sc->sc_io_res) { + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + /* multiply all addresses by 4 */ + sc->sc_reg_shift = 2; + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + goto error; + } + sc->sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + err = usb2_config_td_setup(&sc->sc_config_td, sc, + &sc->sc_bus.mtx, NULL, 0, 4); + if (err) { + device_printf(dev, "could not setup config thread!\n"); + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)uss820dci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (void *)uss820dci_interrupt, sc, &sc->sc_intr_hdl); +#endif + if (err) { + sc->sc_intr_hdl = NULL; + goto error; + } + err = uss820dci_init(sc); + if (err) { + device_printf(dev, "Init failed\n"); + goto error; + } + err = device_probe_and_attach(sc->sc_bus.bdev); + if (err) { + device_printf(dev, "USB probe and attach failed\n"); + goto error; + } + return (0); + +error: + uss820_pccard_detach(dev); + return (ENXIO); +} + +static int +uss820_pccard_detach(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_all_children(dev); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call at91_udp_uninit() after at91_udp_init() + */ + uss820dci_uninit(sc); + + err = bus_teardown_intr(dev, sc->sc_irq_res, + sc->sc_intr_hdl); + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_bus_mem_free_all(&sc->sc_bus, NULL); + + return (0); +} diff --git a/sys/dev/usb2/core/README.TXT b/sys/dev/usb2/core/README.TXT new file mode 100644 index 000000000000..736723049424 --- /dev/null +++ b/sys/dev/usb2/core/README.TXT @@ -0,0 +1,411 @@ + +$FreeBSD$ + +DESCRIPTION OF THE NEW USB API + +The new USB 2.0 API consists of 5 functions. All transfer types are +managed using these functions. There is no longer need for separate +functions to setup INTERRUPT- and ISOCHRONOUS- transfers. + ++--------------------------------------------------------------+ +| | +| "usb2_transfer_setup" - This function will allocate all | +| necessary DMA memory and might | +| sleep! | +| | +| "usb2_transfer_unsetup" - This function will stop the USB | +| transfer, if it is currently | +| active, release all DMA | +| memory and might sleep! | +| | +| "usb2_transfer_start" - This function will start an USB | +| transfer, if not already started.| +| This function is always | +| non-blocking. ** | +| | +| "usb2_transfer_stop" - This function will stop an USB | +| transfer, if not already stopped.| +| The callback function will be | +| called before this function | +| returns. This function is always | +| non-blocking. ** | +| | +| "usb2_transfer_drain" - This function will stop an USB | +| transfer, if not already stopped | +| and wait for any additional | +| DMA load operations to complete. | +| Buffers that are loaded into DMA | +| using "usb2_set_frame_data" can | +| safely be freed after that | +| this function has returned. This | +| function can block the caller. | +| | +| ** These functions must be called with the private driver's | +| lock locked. | +| | +| NOTE: These USB API functions are NULL safe, with regard | +| to the USB transfer structure pointer. | ++--------------------------------------------------------------+ + +Reference: /sys/dev/usb2/core/usb2_transfer.c + +/* + * A simple USB callback state-machine: + * + * +->-----------------------+ + * | | + * +-<-+-------[tr_setup]--------+-<-+-<-[start/restart] + * | | + * | | + * | | + * +------>-[tr_transferred]---------+ + * | | + * +--------->-[tr_error]------------+ + */ + +void +usb2_default_callback(struct usb2_xfer *xfer) +{ + /* + * NOTE: it is not allowed to return + * before "USB_CHECK_STATUS()", + * even if the system is tearing down! + */ + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + /* + * Setup xfer->frlengths[], xfer->nframes + * and write data to xfer->frbuffers[], if any + */ + + /**/ + usb2_start_hardware(xfer); + return; + + case USB_ST_TRANSFERRED: + /* + * Read data from xfer->frbuffers[], if any. + * "xfer->frlengths[]" should now have been + * updated to the actual length. + */ + return; + + default: /* Error */ + /* print error message and clear stall for example */ + return; + } +} + +=== Notes for USB control transfers === + +An USB control transfer has three parts. First the SETUP packet, then +DATA packet(s) and then a STATUS packet. The SETUP packet is always +pointed to by "xfer->frbuffers[0]" and the length is stored in +"xfer->frlengths[0]" also if there should not be sent any SETUP +packet! If an USB control transfer has no DATA stage, then +"xfer->nframes" should be set to 1. Else the default value is +"xfer->nframes" equal to 2. + +Example1: SETUP + STATUS + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + usb2_start_hardware(xfer); + +Example2: SETUP + DATA + STATUS + xfer->nframes = 2; + xfer->frlenghts[0] = 8; + xfer->frlenghts[1] = 1; + usb2_start_hardware(xfer); + +Example3: SETUP + DATA + STATUS - split +1st callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + usb2_start_hardware(xfer); + +2nd callback: + /* IMPORTANT: frbuffer[0] must still point at the setup packet! */ + xfer->nframes = 2; + xfer->frlenghts[0] = 0; + xfer->frlenghts[1] = 1; + usb2_start_hardware(xfer); + +Example4: SETUP + STATUS - split +1st callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 8; + xfer->flags.manual_status = 1; + usb2_start_hardware(xfer); + +2nd callback: + xfer->nframes = 1; + xfer->frlenghts[0] = 0; + xfer->flags.manual_status = 0; + usb2_start_hardware(xfer); + + +=== General USB transfer notes === + + 1) Something that one should be aware of is that all USB callbacks support +recursation. That means one can start/stop whatever transfer from the callback +of another transfer one desires. Also the transfer that is currently called +back. Recursion is handled like this that when the callback that wants to +recurse returns it is called one more time. + + 2) After that the "usb2_start_hardware()" function has been called in +the callback one can always depend on that "tr_error" or "tr_transferred" +will get jumped afterwards. Always! + + 3) Sleeping functions can only be called from the attach routine of the +driver. Else one should not use sleeping functions unless one has to. It is +very difficult with sleep, because one has to think that the device might have +detached when the thread returns from sleep. + + 4) Polling. + + use_polling + This flag can be used with any callback and will cause the + "usb2_transfer_start()" function to wait using "DELAY()", + without exiting any mutexes, until the transfer is finished or + has timed out. This flag can be changed during operation. + + NOTE: If polling is used the "timeout" field should be non-zero! + NOTE: USB_ERR_CANCELLED is returned in case of timeout + instead of USB_ERR_TIMEOUT! + + + +USB device driver examples: + +/sys/dev/usb2/ethernet/if_axe.c +/sys/dev/usb2/ethernet/if_aue.c + +QUICK REFERENCE +=============== + + +/*------------------------------------------------------------------------* + * usb2_error_t + * usb2_transfer_setup(udev, ifaces, pxfer, setup_start, + * n_setup, priv_sc, priv_mtx) + *------------------------------------------------------------------------*/ + +- "udev" is a pointer to "struct usb2_device". + +- "ifaces" array of interface index numbers to use. See "if_index". + +- "pxfer" is a pointer to an array of USB transfer pointers that are + initialized to NULL, and then pointed to allocated USB transfers. + +- "setup_start" is a pointer to an array of USB config structures. + +- "n_setup" is a number telling the USB system how many USB transfers + should be setup. + +- "priv_sc" is the private softc pointer, which will be used to + initialize "xfer->priv_sc". + +- "priv_mtx" is the private mutex protecting the transfer structure and + the softc. This pointer is used to initialize "xfer->priv_mtx". + +/*------------------------------------------------------------------------* + * void + * usb2_transfer_unsetup(pxfer, n_setup) + *------------------------------------------------------------------------*/ + +- "pxfer" is a pointer to an array of USB transfer pointers, that may + be NULL, that should be freed by the USB system. + +- "n_setup" is a number telling the USB system how many USB transfers + should be unsetup + +NOTE: This function can sleep, waiting for active mutexes to become unlocked! +NOTE: It is not allowed to call "usb2_transfer_unsetup" from the callback + of a USB transfer. + +/*------------------------------------------------------------------------* + * void + * usb2_transfer_start(xfer) + *------------------------------------------------------------------------*/ + +- "xfer" is pointer to a USB transfer that should be started + +NOTE: this function must be called with "priv_mtx" locked + +/*------------------------------------------------------------------------* + * void + * usb2_transfer_stop(xfer) + *------------------------------------------------------------------------*/ + +- "xfer" is a pointer to a USB transfer that should be stopped + +NOTE: this function must be called with "priv_mtx" locked + +NOTE: if the transfer was in progress, the callback will called with + "xfer->error=USB_ERR_CANCELLED", before this function returns + +/*------------------------------------------------------------------------* + * struct usb2_config { + * type, endpoint, direction, interval, timeout, frames, index + * flags, bufsize, callback + * }; + *------------------------------------------------------------------------*/ + +- The "type" field selects the USB pipe type. Valid values are: + UE_INTERRUPT, UE_CONTROL, UE_BULK, UE_ISOCHRONOUS. The special + value UE_BULK_INTR will select BULK and INTERRUPT pipes. + This field is mandatory. + +- The "endpoint" field selects the USB endpoint number. A value of + 0xFF, "-1" or "UE_ADDR_ANY" will select the first matching endpoint. + This field is mandatory. + +- The "direction" field selects the USB endpoint direction. A value of + "UE_DIR_ANY" will select the first matching endpoint. Else valid + values are: "UE_DIR_IN" and "UE_DIR_OUT". "UE_DIR_IN" and + "UE_DIR_OUT" can be binary ORed by "UE_DIR_SID" which means that the + direction will be swapped in case of USB_MODE_DEVICE. Note that + "UE_DIR_IN" refers to the data transfer direction of the "IN" tokens + and "UE_DIR_OUT" refers to the data transfer direction of the "OUT" + tokens. This field is mandatory. + +- The "interval" field selects the interrupt interval. The value of this + field is given in milliseconds and is independent of device speed. Depending + on the endpoint type, this field has different meaning: + + UE_INTERRUPT) + "0" use the default interrupt interval based on endpoint descriptor. + "Else" use the given value for polling rate. + + UE_ISOCHRONOUS) + "0" use default. + "Else" the value is ignored. + + UE_BULK) + UE_CONTROL) + "0" no transfer pre-delay. + "Else" a delay as given by this field in milliseconds is + inserted before the hardware is started when + "usb2_start_hardware()" is called. + NOTE: The transfer timeout, if any, is started after that + the pre-delay has elapsed! + +- The "timeout" field, if non-zero, will set the transfer timeout in + milliseconds. If the "timeout" field is zero and the transfer type + is ISOCHRONOUS a timeout of 250ms will be used. + +- The "frames" field sets the maximum number of frames. If zero is + specified it will yield the following results: + + UE_BULK) + UE_INTERRUPT) + xfer->nframes = 1; + + UE_CONTROL) + xfer->nframes = 2; + + UE_ISOCHRONOUS) + Not allowed. Will cause an error. + +- The "ep_index" field allows you to give a number, in case more + endpoints match the description, that selects which matching + "ep_index" should be used. + +- The "if_index" field allows you to select which of the interface + numbers in the "ifaces" array parameter passed to "usb2_transfer_setup" + that should be used when setting up the given USB transfer. + +- The "flags" field has type "struct usb2_xfer_flags" and allows one + to set initial flags an USB transfer. Valid flags are: + + force_short_xfer + This flag forces the last transmitted USB packet to be short. + A short packet has a length of less than "xfer->max_packet_size", + which derives from "wMaxPacketSize". This flag can be changed + during operation. + + short_xfer_ok + This flag allows the received transfer length, "xfer->actlen" + to be less than "xfer->sumlen" upon completion of a transfer. + This flag can be changed during operation. + + pipe_bof + This flag causes a failing USB transfer to remain first + in the PIPE queue except in the case of "xfer->error" equal + to "USB_ERR_CANCELLED". No other USB transfers in the affected + PIPE queue will be started until either: + + 1) The failing USB transfer is stopped using "usb2_transfer_stop()". + 2) The failing USB transfer performs a successful transfer. + + The purpose of this flag is to avoid races when multiple + transfers are queued for execution on an USB endpoint, and the + first executing transfer fails leading to the need for + clearing of stall for example. In this case this flag is used + to prevent the following USB transfers from being executed at + the same time the clear-stall command is executed on the USB + control endpoint. This flag can be changed during operation. + + "BOF" is short for "Block On Failure" + + NOTE: This flag should be set on all BULK and INTERRUPT + USB transfers which use an endpoint that can be shared + between userland and kernel. + + proxy_buffer + Setting this flag will cause that the total buffer size will + be rounded up to the nearest atomic hardware transfer + size. The maximum data length of any USB transfer is always + stored in the "xfer->max_data_length". For control transfers + the USB kernel will allocate additional space for the 8-bytes + of SETUP header. These 8-bytes are not counted by the + "xfer->max_data_length" variable. This flag can not be changed + during operation. + + ext_buffer + Setting this flag will cause that no data buffer will be + allocated. Instead the USB client must supply a data buffer. + This flag can not be changed during operation. + + manual_status + Setting this flag prevents an USB STATUS stage to be appended + to the end of the USB control transfer. If no control data is + transferred this flag must be cleared. Else an error will be + returned to the USB callback. This flag is mostly useful for + the USB device side. This flag can be changed during + operation. + + no_pipe_ok + Setting this flag causes the USB_ERR_NO_PIPE error to be + ignored. This flag can not be changed during operation. + + stall_pipe + Setting this flag will cause STALL pids to be sent to the + endpoint belonging to this transfer before the transfer is + started. The transfer is started at the moment the host issues + a clear-stall command on the STALL'ed endpoint. This flag can + be changed during operation. This flag does only have effect + in USB device side mode except for control endpoints. This + flag is cleared when the stall command has been executed. This + flag can only be changed outside the callback function by + using the functions "usb2_transfer_set_stall()" and + "usb2_transfer_clear_stall()" ! + +- The "bufsize" field sets the total buffer size in bytes. If + this field is zero, "wMaxPacketSize" will be used, multiplied by the + "frames" field if the transfer type is ISOCHRONOUS. This is useful for + setting up interrupt pipes. This field is mandatory. + + NOTE: For control transfers "bufsize" includes + the length of the request structure. + +- The "callback" pointer sets the USB callback. This field is mandatory. + +MUTEX NOTE: +=========== + +When you create a mutex using "mtx_init()", don't forget to call +"mtx_destroy()" at detach, else you can get "freed memory accessed" +panics. + +--HPS diff --git a/sys/dev/usb2/core/usb2_busdma.c b/sys/dev/usb2/core/usb2_busdma.c new file mode 100644 index 000000000000..1b8c4a1fc3ee --- /dev/null +++ b/sys/dev/usb2/core/usb2_busdma.c @@ -0,0 +1,1401 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static void usb2_dma_tag_create(struct usb2_dma_tag *udt, uint32_t size, uint32_t align); +static void usb2_dma_tag_destroy(struct usb2_dma_tag *udt); + +#ifdef __FreeBSD__ +static void usb2_dma_lock_cb(void *arg, bus_dma_lock_op_t op); +static int32_t usb2_m_copy_in_cb(void *arg, void *src, uint32_t count); +static void usb2_pc_alloc_mem_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error); +static void usb2_pc_load_mem_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error); +static void usb2_pc_common_mem_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error, uint8_t isload); + +#endif + +#ifdef __NetBSD__ +static int32_t usb2_m_copy_in_cb(void *arg, caddr_t src, uint32_t count); +static void usb2_pc_common_mem_cb(struct usb2_page_cache *pc, bus_dma_segment_t *segs, int nseg, int error, uint8_t isload); + +#endif + +/*------------------------------------------------------------------------* + * usb2_get_page - lookup DMA-able memory for the given offset + * + * NOTE: Only call this function when the "page_cache" structure has + * been properly initialized ! + *------------------------------------------------------------------------*/ +void +usb2_get_page(struct usb2_page_cache *pc, uint32_t offset, + struct usb2_page_search *res) +{ + struct usb2_page *page; + + if (pc->page_start) { + + /* Case 1 - something has been loaded into DMA */ + + if (pc->buffer) { + + /* Case 1a - Kernel Virtual Address */ + + res->buffer = USB_ADD_BYTES(pc->buffer, offset); + } + offset += pc->page_offset_buf; + + /* compute destination page */ + + page = pc->page_start; + + if (pc->ismultiseg) { + + page += (offset / USB_PAGE_SIZE); + + offset %= USB_PAGE_SIZE; + + res->length = USB_PAGE_SIZE - offset; + res->physaddr = page->physaddr + offset; + } else { + res->length = 0 - 1; + res->physaddr = page->physaddr + offset; + } + if (!pc->buffer) { + + /* Case 1b - Non Kernel Virtual Address */ + + res->buffer = USB_ADD_BYTES(page->buffer, offset); + } + } else { + + /* Case 2 - Plain PIO */ + + res->buffer = USB_ADD_BYTES(pc->buffer, offset); + res->length = 0 - 1; + res->physaddr = 0; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_copy_in - copy directly to DMA-able memory + *------------------------------------------------------------------------*/ +void +usb2_copy_in(struct usb2_page_cache *cache, uint32_t offset, + const void *ptr, uint32_t len) +{ + struct usb2_page_search buf_res; + + while (len != 0) { + + usb2_get_page(cache, offset, &buf_res); + + if (buf_res.length > len) { + buf_res.length = len; + } + bcopy(ptr, buf_res.buffer, buf_res.length); + + offset += buf_res.length; + len -= buf_res.length; + ptr = USB_ADD_BYTES(ptr, buf_res.length); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_copy_in_user - copy directly to DMA-able memory from userland + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb2_copy_in_user(struct usb2_page_cache *cache, uint32_t offset, + const void *ptr, uint32_t len) +{ + struct usb2_page_search buf_res; + int error; + + while (len != 0) { + + usb2_get_page(cache, offset, &buf_res); + + if (buf_res.length > len) { + buf_res.length = len; + } + error = copyin(ptr, buf_res.buffer, buf_res.length); + if (error) + return (error); + + offset += buf_res.length; + len -= buf_res.length; + ptr = USB_ADD_BYTES(ptr, buf_res.length); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_m_copy_in - copy a mbuf chain directly into DMA-able memory + *------------------------------------------------------------------------*/ +struct usb2_m_copy_in_arg { + struct usb2_page_cache *cache; + uint32_t dst_offset; +}; + +static int32_t +#ifdef __FreeBSD__ +usb2_m_copy_in_cb(void *arg, void *src, uint32_t count) +#else +usb2_m_copy_in_cb(void *arg, caddr_t src, uint32_t count) +#endif +{ + register struct usb2_m_copy_in_arg *ua = arg; + + usb2_copy_in(ua->cache, ua->dst_offset, src, count); + ua->dst_offset += count; + return (0); +} + +void +usb2_m_copy_in(struct usb2_page_cache *cache, uint32_t dst_offset, + struct mbuf *m, uint32_t src_offset, uint32_t src_len) +{ + struct usb2_m_copy_in_arg arg = {cache, dst_offset}; + register int error; + + error = m_apply(m, src_offset, src_len, &usb2_m_copy_in_cb, &arg); + return; +} + +/*------------------------------------------------------------------------* + * usb2_uiomove - factored out code + *------------------------------------------------------------------------*/ +int +usb2_uiomove(struct usb2_page_cache *pc, struct uio *uio, + uint32_t pc_offset, uint32_t len) +{ + struct usb2_page_search res; + int error = 0; + + while (len != 0) { + + usb2_get_page(pc, pc_offset, &res); + + if (res.length > len) { + res.length = len; + } + /* + * "uiomove()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things + */ + error = uiomove(res.buffer, res.length, uio); + + if (error) { + break; + } + pc_offset += res.length; + len -= res.length; + } + return (error); +} + +/*------------------------------------------------------------------------* + * usb2_copy_out - copy directly from DMA-able memory + *------------------------------------------------------------------------*/ +void +usb2_copy_out(struct usb2_page_cache *cache, uint32_t offset, + void *ptr, uint32_t len) +{ + struct usb2_page_search res; + + while (len != 0) { + + usb2_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + bcopy(res.buffer, ptr, res.length); + + offset += res.length; + len -= res.length; + ptr = USB_ADD_BYTES(ptr, res.length); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_copy_out_user - copy directly from DMA-able memory to userland + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb2_copy_out_user(struct usb2_page_cache *cache, uint32_t offset, + void *ptr, uint32_t len) +{ + struct usb2_page_search res; + int error; + + while (len != 0) { + + usb2_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + error = copyout(res.buffer, ptr, res.length); + if (error) + return (error); + + offset += res.length; + len -= res.length; + ptr = USB_ADD_BYTES(ptr, res.length); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_bzero - zero DMA-able memory + *------------------------------------------------------------------------*/ +void +usb2_bzero(struct usb2_page_cache *cache, uint32_t offset, uint32_t len) +{ + struct usb2_page_search res; + + while (len != 0) { + + usb2_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + bzero(res.buffer, res.length); + + offset += res.length; + len -= res.length; + } + return; +} + + +#ifdef __FreeBSD__ + +/*------------------------------------------------------------------------* + * usb2_dma_lock_cb - dummy callback + *------------------------------------------------------------------------*/ +static void +usb2_dma_lock_cb(void *arg, bus_dma_lock_op_t op) +{ + /* we use "mtx_owned()" instead of this function */ + return; +} + +/*------------------------------------------------------------------------* + * usb2_dma_tag_create - allocate a DMA tag + * + * NOTE: If the "align" parameter has a value of 1 the DMA-tag will + * allow multi-segment mappings. Else all mappings are single-segment. + *------------------------------------------------------------------------*/ +static void +usb2_dma_tag_create(struct usb2_dma_tag *udt, + uint32_t size, uint32_t align) +{ + bus_dma_tag_t tag; + + if (bus_dma_tag_create + ( /* parent */ udt->tag_parent->tag, + /* alignment */ align, + /* boundary */ USB_PAGE_SIZE, + /* lowaddr */ (2ULL << (udt->tag_parent->dma_bits - 1)) - 1, + /* highaddr */ BUS_SPACE_MAXADDR, + /* filter */ NULL, + /* filterarg */ NULL, + /* maxsize */ size, + /* nsegments */ (align == 1) ? + (2 + (size / USB_PAGE_SIZE)) : 1, + /* maxsegsz */ (align == 1) ? + USB_PAGE_SIZE : size, + /* flags */ 0, + /* lockfn */ &usb2_dma_lock_cb, + /* lockarg */ NULL, + &tag)) { + tag = NULL; + } + udt->tag = tag; + return; +} + +/*------------------------------------------------------------------------* + * usb2_dma_tag_free - free a DMA tag + *------------------------------------------------------------------------*/ +static void +usb2_dma_tag_destroy(struct usb2_dma_tag *udt) +{ + bus_dma_tag_destroy(udt->tag); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_alloc_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb2_pc_alloc_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error) +{ + usb2_pc_common_mem_cb(arg, segs, nseg, error, 0); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_load_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb2_pc_load_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error) +{ + usb2_pc_common_mem_cb(arg, segs, nseg, error, 1); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_common_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb2_pc_common_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error, uint8_t isload) +{ + struct usb2_dma_parent_tag *uptag; + struct usb2_page_cache *pc; + struct usb2_page *pg; + uint32_t rem; + uint8_t owned; + + pc = arg; + uptag = pc->tag_parent; + + /* + * XXX There is sometimes recursive locking here. + * XXX We should try to find a better solution. + * XXX Until further the "owned" variable does + * XXX the trick. + */ + + if (error) { + goto done; + } + pg = pc->page_start; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + rem = segs->ds_addr & (USB_PAGE_SIZE - 1); + pc->page_offset_buf = rem; + pc->page_offset_end += rem; + nseg--; + + while (nseg > 0) { + nseg--; + segs++; + pg++; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + } + +done: + owned = mtx_owned(uptag->mtx); + if (!owned) + mtx_lock(uptag->mtx); + + uptag->dma_error = (error ? 1 : 0); + if (isload) { + (uptag->func) (uptag); + } else { + usb2_cv_broadcast(uptag->cv); + } + if (!owned) + mtx_unlock(uptag->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_alloc_mem - allocate DMA'able memory + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_alloc_mem(struct usb2_page_cache *pc, struct usb2_page *pg, + uint32_t size, uint32_t align) +{ + struct usb2_dma_parent_tag *uptag; + struct usb2_dma_tag *utag; + bus_dmamap_t map; + void *ptr; + int err; + + uptag = pc->tag_parent; + + if (align != 1) { + /* + * The alignment must be greater or equal to the + * "size" else the object can be split between two + * memory pages and we get a problem! + */ + while (align < size) { + align *= 2; + if (align == 0) { + goto error; + } + } +#if 1 + /* + * XXX BUS-DMA workaround - FIXME later: + * + * We assume that that the aligment at this point of + * the code is greater than or equal to the size and + * less than two times the size, so that if we double + * the size, the size will be greater than the + * alignment. + * + * The bus-dma system has a check for "alignment" + * being less than "size". If that check fails we end + * up using contigmalloc which is page based even for + * small allocations. Try to avoid that to save + * memory, hence we sometimes to a large number of + * small allocations! + */ + if (size <= (USB_PAGE_SIZE / 2)) { + size *= 2; + } +#endif + } + /* get the correct DMA tag */ + utag = usb2_dma_tag_find(uptag, size, align); + if (utag == NULL) { + goto error; + } + /* allocate memory */ + if (bus_dmamem_alloc( + utag->tag, &ptr, (BUS_DMA_WAITOK | BUS_DMA_COHERENT), &map)) { + goto error; + } + /* setup page cache */ + pc->buffer = ptr; + pc->page_start = pg; + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->map = map; + pc->tag = utag->tag; + pc->ismultiseg = (align == 1); + + mtx_lock(uptag->mtx); + + /* load memory into DMA */ + err = bus_dmamap_load( + utag->tag, map, ptr, size, &usb2_pc_alloc_mem_cb, + pc, (BUS_DMA_WAITOK | BUS_DMA_COHERENT)); + + if (err == EINPROGRESS) { + usb2_cv_wait(uptag->cv, uptag->mtx); + err = 0; + } + mtx_unlock(uptag->mtx); + + if (err || uptag->dma_error) { + bus_dmamem_free(utag->tag, ptr, map); + goto error; + } + bzero(ptr, size); + + usb2_pc_cpu_flush(pc); + + return (0); + +error: + /* reset most of the page cache */ + pc->buffer = NULL; + pc->page_start = NULL; + pc->page_offset_buf = 0; + pc->page_offset_end = 0; + pc->map = NULL; + pc->tag = NULL; + return (1); +} + +/*------------------------------------------------------------------------* + * usb2_pc_free_mem - free DMA memory + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_pc_free_mem(struct usb2_page_cache *pc) +{ + if (pc && pc->buffer) { + + bus_dmamap_unload(pc->tag, pc->map); + + bus_dmamem_free(pc->tag, pc->buffer, pc->map); + + pc->buffer = NULL; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_load_mem - load virtual memory into DMA + * + * Return values: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_load_mem(struct usb2_page_cache *pc, uint32_t size, uint8_t sync) +{ + /* setup page cache */ + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->ismultiseg = 1; + + mtx_assert(pc->tag_parent->mtx, MA_OWNED); + + if (size > 0) { + if (sync) { + struct usb2_dma_parent_tag *uptag; + int err; + + uptag = pc->tag_parent; + + /* + * Try to load memory into DMA. + */ + err = bus_dmamap_load( + pc->tag, pc->map, pc->buffer, size, + &usb2_pc_alloc_mem_cb, pc, BUS_DMA_WAITOK); + if (err == EINPROGRESS) { + usb2_cv_wait(uptag->cv, uptag->mtx); + err = 0; + } + if (err || uptag->dma_error) { + return (1); + } + } else { + + /* + * Try to load memory into DMA. The callback + * will be called in all cases: + */ + if (bus_dmamap_load( + pc->tag, pc->map, pc->buffer, size, + &usb2_pc_load_mem_cb, pc, BUS_DMA_WAITOK)) { + } + } + } else { + if (!sync) { + /* + * Call callback so that refcount is decremented + * properly: + */ + pc->tag_parent->dma_error = 0; + (pc->tag_parent->func) (pc->tag_parent); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_pc_cpu_invalidate - invalidate CPU cache + *------------------------------------------------------------------------*/ +void +usb2_pc_cpu_invalidate(struct usb2_page_cache *pc) +{ + bus_dmamap_sync(pc->tag, pc->map, + BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_cpu_flush - flush CPU cache + *------------------------------------------------------------------------*/ +void +usb2_pc_cpu_flush(struct usb2_page_cache *pc) +{ + bus_dmamap_sync(pc->tag, pc->map, + BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_dmamap_create - create a DMA map + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_dmamap_create(struct usb2_page_cache *pc, uint32_t size) +{ + struct usb2_xfer_root *info; + struct usb2_dma_tag *utag; + + /* get info */ + info = pc->tag_parent->info; + + /* sanity check */ + if (info == NULL) { + goto error; + } + utag = usb2_dma_tag_find(pc->tag_parent, size, 1); + if (utag == NULL) { + goto error; + } + /* create DMA map */ + if (bus_dmamap_create(utag->tag, 0, &pc->map)) { + goto error; + } + pc->tag = utag->tag; + return 0; /* success */ + +error: + pc->map = NULL; + pc->tag = NULL; + return 1; /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_pc_dmamap_destroy + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_pc_dmamap_destroy(struct usb2_page_cache *pc) +{ + if (pc && pc->tag) { + bus_dmamap_destroy(pc->tag, pc->map); + pc->tag = NULL; + pc->map = NULL; + } + return; +} + +#endif + +#ifdef __NetBSD__ + +/*------------------------------------------------------------------------* + * usb2_dma_tag_create - allocate a DMA tag + * + * NOTE: If the "align" parameter has a value of 1 the DMA-tag will + * allow multi-segment mappings. Else all mappings are single-segment. + *------------------------------------------------------------------------*/ +static void +usb2_dma_tag_create(struct usb2_dma_tag *udt, + uint32_t size, uint32_t align) +{ + uint32_t nseg; + + if (align == 1) { + nseg = (2 + (size / USB_PAGE_SIZE)); + } else { + nseg = 1; + } + + udt->p_seg = malloc(nseg * sizeof(*(udt->p_seg)), + M_USB, M_WAITOK | M_ZERO); + + if (udt->p_seg == NULL) { + return; + } + udt->tag = udt->tag_parent->tag; + udt->n_seg = nseg; + return; +} + +/*------------------------------------------------------------------------* + * usb2_dma_tag_free - free a DMA tag + *------------------------------------------------------------------------*/ +static void +usb2_dma_tag_destroy(struct usb2_dma_tag *udt) +{ + free(udt->p_seg, M_USB); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_common_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb2_pc_common_mem_cb(struct usb2_page_cache *pc, bus_dma_segment_t *segs, + int nseg, int error, uint8_t isload, uint8_t dolock) +{ + struct usb2_dma_parent_tag *uptag; + struct usb2_page *pg; + uint32_t rem; + uint8_t ext_seg; /* extend last segment */ + + uptag = pc->tag_parent; + + if (error) { + goto done; + } + pg = pc->page_start; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + rem = segs->ds_addr & (USB_PAGE_SIZE - 1); + pc->page_offset_buf = rem; + pc->page_offset_end += rem; + if (nseg < ((pc->page_offset_end + + (USB_PAGE_SIZE - 1)) / USB_PAGE_SIZE)) { + ext_seg = 1; + } else { + ext_seg = 0; + } + nseg--; + + while (nseg > 0) { + nseg--; + segs++; + pg++; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + } + + /* + * XXX The segments we get from BUS-DMA are not aligned, + * XXX so we need to extend the last segment if we are + * XXX unaligned and cross the segment boundary! + */ + if (ext_seg && pc->ismultiseg) { + (pg + 1)->physaddr = pg->physaddr + USB_PAGE_SIZE; + } +done: + if (dolock) + mtx_lock(uptag->mtx); + + uptag->dma_error = (error ? 1 : 0); + if (isload) { + (uptag->func) (uptag); + } + if (dolock) + mtx_unlock(uptag->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_alloc_mem - allocate DMA'able memory + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_alloc_mem(struct usb2_page_cache *pc, struct usb2_page *pg, + uint32_t size, uint32_t align) +{ + struct usb2_dma_parent_tag *uptag; + struct usb2_dma_tag *utag; + caddr_t ptr = NULL; + bus_dmamap_t map; + int seg_count; + + uptag = pc->tag_parent; + + if (align != 1) { + /* + * The alignment must be greater or equal to the + * "size" else the object can be split between two + * memory pages and we get a problem! + */ + while (align < size) { + align *= 2; + if (align == 0) { + goto done_5; + } + } + } + /* get the correct DMA tag */ + utag = usb2_dma_tag_find(pc->tag_parent, size, align); + if (utag == NULL) { + goto done_5; + } + if (bus_dmamem_alloc(utag->tag, size, align, 0, utag->p_seg, + utag->n_seg, &seg_count, BUS_DMA_WAITOK)) { + goto done_4; + } + if (bus_dmamem_map(utag->tag, utag->p_seg, seg_count, size, + &ptr, BUS_DMA_WAITOK | BUS_DMA_COHERENT)) { + goto done_3; + } + if (bus_dmamap_create(utag->tag, size, utag->n_seg, (align == 1) ? + USB_PAGE_SIZE : size, 0, BUS_DMA_WAITOK, &map)) { + goto done_2; + } + if (bus_dmamap_load(utag->tag, map, ptr, size, NULL, + BUS_DMA_WAITOK)) { + goto done_1; + } + pc->p_seg = malloc(seg_count * sizeof(*(pc->p_seg)), + M_USB, M_WAITOK | M_ZERO); + if (pc->p_seg == NULL) { + goto done_0; + } + /* store number if actual segments used */ + pc->n_seg = seg_count; + + /* make a copy of the segments */ + bcopy(utag->p_seg, pc->p_seg, + seg_count * sizeof(*(pc->p_seg))); + + /* setup page cache */ + pc->buffer = ptr; + pc->page_start = pg; + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->map = map; + pc->tag = utag->tag; + pc->ismultiseg = (align == 1); + + usb2_pc_common_mem_cb(pc, utag->p_seg, seg_count, 0, 0, 1); + + bzero(ptr, size); + + usb2_pc_cpu_flush(pc); + + return (0); + +done_0: + bus_dmamap_unload(utag->tag, map); +done_1: + bus_dmamap_destroy(utag->tag, map); +done_2: + bus_dmamem_unmap(utag->tag, ptr, size); +done_3: + bus_dmamem_free(utag->tag, utag->p_seg, seg_count); +done_4: + /* utag is destroyed later */ +done_5: + /* reset most of the page cache */ + pc->buffer = NULL; + pc->page_start = NULL; + pc->page_offset_buf = 0; + pc->page_offset_end = 0; + pc->map = NULL; + pc->tag = NULL; + pc->n_seg = 0; + pc->p_seg = NULL; + return (1); +} + +/*------------------------------------------------------------------------* + * usb2_pc_free_mem - free DMA memory + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_pc_free_mem(struct usb2_page_cache *pc) +{ + if (pc && pc->buffer) { + bus_dmamap_unload(pc->tag, pc->map); + bus_dmamap_destroy(pc->tag, pc->map); + bus_dmamem_unmap(pc->tag, pc->buffer, + pc->page_offset_end - pc->page_offset_buf); + bus_dmamem_free(pc->tag, pc->p_seg, pc->n_seg); + free(pc->p_seg, M_USB); + pc->buffer = NULL; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_load_mem - load virtual memory into DMA + * + * Return values: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_load_mem(struct usb2_page_cache *pc, uint32_t size, uint8_t sync) +{ + int error; + + /* setup page cache */ + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->ismultiseg = 1; + + if (size > 0) { + + /* try to load memory into DMA using using no wait option */ + if (bus_dmamap_load(pc->tag, pc->map, pc->buffer, + size, NULL, BUS_DMA_NOWAIT)) { + error = ENOMEM; + } else { + error = 0; + } + + usb2_pc_common_mem_cb(pc, pc->map->dm_segs, + pc->map->dm_nsegs, error, !sync); + + if (error) { + return (1); + } + } else { + if (!sync) { + /* + * Call callback so that refcount is decremented + * properly: + */ + pc->tag_parent->dma_error = 0; + (pc->tag_parent->func) (pc->tag_parent); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_pc_cpu_invalidate - invalidate CPU cache + *------------------------------------------------------------------------*/ +void +usb2_pc_cpu_invalidate(struct usb2_page_cache *pc) +{ + uint32_t len; + + len = pc->page_offset_end - pc->page_offset_buf; + + bus_dmamap_sync(pc->tag, pc->map, 0, len, + BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_cpu_flush - flush CPU cache + *------------------------------------------------------------------------*/ +void +usb2_pc_cpu_flush(struct usb2_page_cache *pc) +{ + uint32_t len; + + len = pc->page_offset_end - pc->page_offset_buf; + + bus_dmamap_sync(pc->tag, pc->map, 0, len, + BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pc_dmamap_create - create a DMA map + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_pc_dmamap_create(struct usb2_page_cache *pc, uint32_t size) +{ + struct usb2_xfer_root *info; + struct usb2_dma_tag *utag; + + /* get info */ + info = pc->tag_parent->info; + + /* sanity check */ + if (info == NULL) { + goto error; + } + utag = usb2_dma_tag_find(pc->tag_parent, size, 1); + if (utag == NULL) { + goto error; + } + if (bus_dmamap_create(utag->tag, size, utag->n_seg, + USB_PAGE_SIZE, 0, BUS_DMA_WAITOK, &pc->map)) { + goto error; + } + pc->tag = utag->tag; + pc->p_seg = utag->p_seg; + pc->n_seg = utag->n_seg; + return 0; /* success */ + +error: + pc->map = NULL; + pc->tag = NULL; + pc->p_seg = NULL; + pc->n_seg = 0; + return 1; /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_pc_dmamap_destroy + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_pc_dmamap_destroy(struct usb2_page_cache *pc) +{ + if (pc && pc->tag) { + bus_dmamap_destroy(pc->tag, pc->map); + pc->tag = NULL; + pc->map = NULL; + } + return; +} + +#endif + +/*------------------------------------------------------------------------* + * usb2_dma_tag_find - factored out code + *------------------------------------------------------------------------*/ +struct usb2_dma_tag * +usb2_dma_tag_find(struct usb2_dma_parent_tag *udpt, + uint32_t size, uint32_t align) +{ + struct usb2_dma_tag *udt; + uint8_t nudt; + + USB_ASSERT(align > 0, ("Invalid parameter align = 0!\n")); + USB_ASSERT(size > 0, ("Invalid parameter size = 0!\n")); + + udt = udpt->utag_first; + nudt = udpt->utag_max; + + while (nudt--) { + + if (udt->align == 0) { + usb2_dma_tag_create(udt, size, align); + if (udt->tag == NULL) { + return (NULL); + } + udt->align = align; + udt->size = size; + return (udt); + } + if ((udt->align == align) && (udt->size == size)) { + return (udt); + } + udt++; + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_dma_tag_setup - initialise USB DMA tags + *------------------------------------------------------------------------*/ +void +usb2_dma_tag_setup(struct usb2_dma_parent_tag *udpt, + struct usb2_dma_tag *udt, bus_dma_tag_t dmat, + struct mtx *mtx, usb2_dma_callback_t *func, + struct usb2_xfer_root *info, uint8_t ndmabits, + uint8_t nudt) +{ + bzero(udpt, sizeof(*udpt)); + + /* sanity checking */ + if ((nudt == 0) || + (ndmabits == 0) || + (mtx == NULL)) { + /* something is corrupt */ + return; + } +#ifdef __FreeBSD__ + /* initialise condition variable */ + usb2_cv_init(udpt->cv, "USB DMA CV"); +#endif + + /* store some information */ + udpt->mtx = mtx; + udpt->info = info; + udpt->func = func; + udpt->tag = dmat; + udpt->utag_first = udt; + udpt->utag_max = nudt; + udpt->dma_bits = ndmabits; + + while (nudt--) { + bzero(udt, sizeof(*udt)); + udt->tag_parent = udpt; + udt++; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_bus_tag_unsetup - factored out code + *------------------------------------------------------------------------*/ +void +usb2_dma_tag_unsetup(struct usb2_dma_parent_tag *udpt) +{ + struct usb2_dma_tag *udt; + uint8_t nudt; + + udt = udpt->utag_first; + nudt = udpt->utag_max; + + while (nudt--) { + + if (udt->align) { + /* destroy the USB DMA tag */ + usb2_dma_tag_destroy(udt); + udt->align = 0; + } + udt++; + } + + if (udpt->utag_max) { +#ifdef __FreeBSD__ + /* destroy the condition variable */ + usb2_cv_destroy(udpt->cv); +#endif + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_bdma_work_loop + * + * This function handles loading of virtual buffers into DMA and is + * only called when "dma_refcount" is zero. + *------------------------------------------------------------------------*/ +void +usb2_bdma_work_loop(struct usb2_xfer_queue *pq) +{ + struct usb2_xfer_root *info; + struct usb2_xfer *xfer; + uint32_t nframes; + + xfer = pq->curr; + info = xfer->usb2_root; + + mtx_assert(info->priv_mtx, MA_OWNED); + + if (xfer->error) { + /* some error happened */ + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, 0); + mtx_unlock(xfer->usb2_mtx); + return; + } + if (!xfer->flags_int.bdma_setup) { + struct usb2_page *pg; + uint32_t frlength_0; + uint8_t isread; + + xfer->flags_int.bdma_setup = 1; + + /* reset BUS-DMA load state */ + + info->dma_error = 0; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + frlength_0 = xfer->sumlen; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + frlength_0 = xfer->frlengths[0]; + } + + /* + * Set DMA direction first. This is needed to + * select the correct cache invalidate and cache + * flush operations. + */ + isread = USB_GET_DATA_ISREAD(xfer); + pg = xfer->dma_page_ptr; + + if (xfer->flags_int.control_xfr && + xfer->flags_int.control_hdr) { + /* special case */ + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + /* The device controller writes to memory */ + xfer->frbuffers[0].isread = 1; + } else { + /* The host controller reads from memory */ + xfer->frbuffers[0].isread = 0; + } + } else { + /* default case */ + xfer->frbuffers[0].isread = isread; + } + + /* + * Setup the "page_start" pointer which points to an array of + * USB pages where information about the physical address of a + * page will be stored. Also initialise the "isread" field of + * the USB page caches. + */ + xfer->frbuffers[0].page_start = pg; + + info->dma_nframes = nframes; + info->dma_currframe = 0; + info->dma_frlength_0 = frlength_0; + + pg += (frlength_0 / USB_PAGE_SIZE); + pg += 2; + + while (--nframes > 0) { + xfer->frbuffers[nframes].isread = isread; + xfer->frbuffers[nframes].page_start = pg; + + pg += (xfer->frlengths[nframes] / USB_PAGE_SIZE); + pg += 2; + } + + } + if (info->dma_error) { + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, USB_ERR_DMA_LOAD_FAILED); + mtx_unlock(xfer->usb2_mtx); + return; + } + if (info->dma_currframe != info->dma_nframes) { + + if (info->dma_currframe == 0) { + /* special case */ + usb2_pc_load_mem(xfer->frbuffers, + info->dma_frlength_0, 0); + } else { + /* default case */ + nframes = info->dma_currframe; + usb2_pc_load_mem(xfer->frbuffers + nframes, + xfer->frlengths[nframes], 0); + } + + /* advance frame index */ + info->dma_currframe++; + + return; + } + /* go ahead */ + usb2_bdma_pre_sync(xfer); + + /* start loading next USB transfer, if any */ + usb2_command_wrapper(pq, NULL); + + /* finally start the hardware */ + usb2_pipe_enter(xfer); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_bdma_done_event + * + * This function is called when the BUS-DMA has loaded virtual memory + * into DMA, if any. + *------------------------------------------------------------------------*/ +void +usb2_bdma_done_event(struct usb2_dma_parent_tag *udpt) +{ + struct usb2_xfer_root *info; + + info = udpt->info; + + mtx_assert(info->priv_mtx, MA_OWNED); + + /* copy error */ + info->dma_error = udpt->dma_error; + + /* enter workloop again */ + usb2_command_wrapper(&info->dma_q, + info->dma_q.curr); + return; +} + +/*------------------------------------------------------------------------* + * usb2_bdma_pre_sync + * + * This function handles DMA synchronisation that must be done before + * an USB transfer is started. + *------------------------------------------------------------------------*/ +void +usb2_bdma_pre_sync(struct usb2_xfer *xfer) +{ + struct usb2_page_cache *pc; + uint32_t nframes; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + } + + pc = xfer->frbuffers; + + while (nframes--) { + + if (pc->page_offset_buf != pc->page_offset_end) { + if (pc->isread) { + usb2_pc_cpu_invalidate(pc); + } else { + usb2_pc_cpu_flush(pc); + } + } + pc++; + } + + return; +} + +/*------------------------------------------------------------------------* + * usb2_bdma_post_sync + * + * This function handles DMA synchronisation that must be done after + * an USB transfer is complete. + *------------------------------------------------------------------------*/ +void +usb2_bdma_post_sync(struct usb2_xfer *xfer) +{ + struct usb2_page_cache *pc; + uint32_t nframes; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + } + + pc = xfer->frbuffers; + + while (nframes--) { + + if (pc->page_offset_buf != pc->page_offset_end) { + if (pc->isread) { + usb2_pc_cpu_invalidate(pc); + } + } + pc++; + } + return; +} diff --git a/sys/dev/usb2/core/usb2_busdma.h b/sys/dev/usb2/core/usb2_busdma.h new file mode 100644 index 000000000000..0b035995e624 --- /dev/null +++ b/sys/dev/usb2/core/usb2_busdma.h @@ -0,0 +1,169 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_BUSDMA_H_ +#define _USB2_BUSDMA_H_ + +#include +#include + +#include + +/* defines */ + +#define USB_PAGE_SIZE PAGE_SIZE /* use system PAGE_SIZE */ + +#ifdef __FreeBSD__ +#if (__FreeBSD_version >= 700020) +#define USB_GET_DMA_TAG(dev) bus_get_dma_tag(dev) +#else +#define USB_GET_DMA_TAG(dev) NULL /* XXX */ +#endif +#endif + +/* structure prototypes */ + +struct usb2_xfer_root; +struct usb2_dma_parent_tag; + +/* + * The following typedef defines the USB DMA load done callback. + */ + +typedef void (usb2_dma_callback_t)(struct usb2_dma_parent_tag *udpt); + +/* + * The following structure defines physical and non kernel virtual + * address of a memory page having size USB_PAGE_SIZE. + */ +struct usb2_page { + bus_size_t physaddr; + void *buffer; /* non Kernel Virtual Address */ +}; + +/* + * The following structure is used when needing the kernel virtual + * pointer and the physical address belonging to an offset in an USB + * page cache. + */ +struct usb2_page_search { + void *buffer; + bus_size_t physaddr; + uint32_t length; +}; + +/* + * The following structure is used to keep information about a DMA + * memory allocation. + */ +struct usb2_page_cache { + +#ifdef __FreeBSD__ + bus_dma_tag_t tag; + bus_dmamap_t map; +#endif +#ifdef __NetBSD__ + bus_dma_tag_t tag; + bus_dmamap_t map; + bus_dma_segment_t *p_seg; +#endif + struct usb2_page *page_start; + struct usb2_dma_parent_tag *tag_parent; /* always set */ + void *buffer; /* virtual buffer pointer */ +#ifdef __NetBSD__ + int n_seg; +#endif + uint32_t page_offset_buf; + uint32_t page_offset_end; + uint8_t isread:1; /* set if we are currently reading + * from the memory. Else write. */ + uint8_t ismultiseg:1; /* set if we can have multiple + * segments */ +}; + +/* + * The following structure describes the parent USB DMA tag. + */ +struct usb2_dma_parent_tag { +#ifdef __FreeBSD__ + struct cv cv[1]; /* internal condition variable */ +#endif + + bus_dma_tag_t tag; /* always set */ + + struct mtx *mtx; /* private mutex, always set */ + struct usb2_xfer_root *info; /* used by the callback function */ + usb2_dma_callback_t *func; /* load complete callback function */ + struct usb2_dma_tag *utag_first;/* pointer to first USB DMA tag */ + + uint8_t dma_error; /* set if DMA load operation failed */ + uint8_t dma_bits; /* number of DMA address lines */ + uint8_t utag_max; /* number of USB DMA tags */ +}; + +/* + * The following structure describes an USB DMA tag. + */ +struct usb2_dma_tag { +#ifdef __NetBSD__ + bus_dma_segment_t *p_seg; +#endif + struct usb2_dma_parent_tag *tag_parent; + bus_dma_tag_t tag; + + uint32_t align; + uint32_t size; +#ifdef __NetBSD__ + uint32_t n_seg; +#endif +}; + +/* function prototypes */ + +int usb2_uiomove(struct usb2_page_cache *pc, struct uio *uio, uint32_t pc_offset, uint32_t len); +struct usb2_dma_tag *usb2_dma_tag_find(struct usb2_dma_parent_tag *udpt, uint32_t size, uint32_t align); +uint8_t usb2_pc_alloc_mem(struct usb2_page_cache *pc, struct usb2_page *pg, uint32_t size, uint32_t align); +uint8_t usb2_pc_dmamap_create(struct usb2_page_cache *pc, uint32_t size); +uint8_t usb2_pc_load_mem(struct usb2_page_cache *pc, uint32_t size, uint8_t sync); +void usb2_bdma_done_event(struct usb2_dma_parent_tag *udpt); +void usb2_bdma_post_sync(struct usb2_xfer *xfer); +void usb2_bdma_pre_sync(struct usb2_xfer *xfer); +void usb2_bdma_work_loop(struct usb2_xfer_queue *pq); +void usb2_bzero(struct usb2_page_cache *cache, uint32_t offset, uint32_t len); +void usb2_copy_in(struct usb2_page_cache *cache, uint32_t offset, const void *ptr, uint32_t len); +int usb2_copy_in_user(struct usb2_page_cache *cache, uint32_t offset, const void *ptr, uint32_t len); +void usb2_copy_out(struct usb2_page_cache *cache, uint32_t offset, void *ptr, uint32_t len); +int usb2_copy_out_user(struct usb2_page_cache *cache, uint32_t offset, void *ptr, uint32_t len); +void usb2_dma_tag_setup(struct usb2_dma_parent_tag *udpt, struct usb2_dma_tag *udt, bus_dma_tag_t dmat, struct mtx *mtx, usb2_dma_callback_t *func, struct usb2_xfer_root *info, uint8_t ndmabits, uint8_t nudt); +void usb2_dma_tag_unsetup(struct usb2_dma_parent_tag *udpt); +void usb2_get_page(struct usb2_page_cache *pc, uint32_t offset, struct usb2_page_search *res); +void usb2_m_copy_in(struct usb2_page_cache *cache, uint32_t dst_offset, struct mbuf *m, uint32_t src_offset, uint32_t src_len); +void usb2_pc_cpu_flush(struct usb2_page_cache *pc); +void usb2_pc_cpu_invalidate(struct usb2_page_cache *pc); +void usb2_pc_dmamap_destroy(struct usb2_page_cache *pc); +void usb2_pc_free_mem(struct usb2_page_cache *pc); + +#endif /* _USB2_BUSDMA_H_ */ diff --git a/sys/dev/usb2/core/usb2_compat_linux.c b/sys/dev/usb2/core/usb2_compat_linux.c new file mode 100644 index 000000000000..7b0212d11891 --- /dev/null +++ b/sys/dev/usb2/core/usb2_compat_linux.c @@ -0,0 +1,1659 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Luigi Rizzo - Universita` di Pisa. All rights reserved. + * Copyright (c) 2007 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct usb_linux_softc { + LIST_ENTRY(usb_linux_softc) sc_attached_list; + + device_t sc_fbsd_dev; + struct usb2_device *sc_fbsd_udev; + struct usb_interface *sc_ui; + struct usb_driver *sc_udrv; +}; + +/* prototypes */ +static device_probe_t usb_linux_probe; +static device_attach_t usb_linux_attach; +static device_detach_t usb_linux_detach; +static device_suspend_t usb_linux_suspend; +static device_resume_t usb_linux_resume; +static device_shutdown_t usb_linux_shutdown; + +static usb2_callback_t usb_linux_isoc_callback; +static usb2_callback_t usb_linux_non_isoc_callback; + +static usb_complete_t usb_linux_wait_complete; + +static uint16_t usb_max_isoc_frames(struct usb_device *dev); +static int usb_start_wait_urb(struct urb *urb, uint32_t timeout, uint16_t *p_actlen); +static const struct usb_device_id *usb_linux_lookup_id(const struct usb_device_id *id, struct usb2_attach_arg *uaa); +static struct usb_driver *usb_linux_get_usb_driver(struct usb_linux_softc *sc); +static struct usb_device *usb_linux_create_usb_device(struct usb2_device *udev, device_t dev); +static void usb_linux_cleanup_interface(struct usb_device *dev, struct usb_interface *iface); +static void usb_linux_complete(struct usb2_xfer *xfer); +static int usb_unlink_urb_sub(struct urb *urb, uint8_t drain); + +/*------------------------------------------------------------------------* + * FreeBSD USB interface + *------------------------------------------------------------------------*/ + +static LIST_HEAD(, usb_linux_softc) usb_linux_attached_list; +static LIST_HEAD(, usb_driver) usb_linux_driver_list; + +static device_method_t usb_linux_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, usb_linux_probe), + DEVMETHOD(device_attach, usb_linux_attach), + DEVMETHOD(device_detach, usb_linux_detach), + DEVMETHOD(device_suspend, usb_linux_suspend), + DEVMETHOD(device_resume, usb_linux_resume), + DEVMETHOD(device_shutdown, usb_linux_shutdown), + + {0, 0} +}; + +static driver_t usb_linux_driver = { + .name = "usb_linux", + .methods = usb_linux_methods, + .size = sizeof(struct usb_linux_softc), +}; + +static devclass_t usb_linux_devclass; + +DRIVER_MODULE(usb_linux, ushub, usb_linux_driver, usb_linux_devclass, NULL, 0); + +/*------------------------------------------------------------------------* + * usb_linux_lookup_id + * + * This functions takes an array of "struct usb_device_id" and tries + * to match the entries with the information in "struct usb2_attach_arg". + * If it finds a match the matching entry will be returned. + * Else "NULL" will be returned. + *------------------------------------------------------------------------*/ +static const struct usb_device_id * +usb_linux_lookup_id(const struct usb_device_id *id, struct usb2_attach_arg *uaa) +{ + if (id == NULL) { + goto done; + } + /* + * Keep on matching array entries until we find one with + * "match_flags" equal to zero, which indicates the end of the + * array: + */ + for (; id->match_flags; id++) { + + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->idVendor != uaa->info.idVendor)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + (id->idProduct != uaa->info.idProduct)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) && + (id->bcdDevice_lo > uaa->info.bcdDevice)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) && + (id->bcdDevice_hi < uaa->info.bcdDevice)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && + (id->bDeviceClass != uaa->info.bDeviceClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) && + (id->bDeviceSubClass != uaa->info.bDeviceSubClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && + (id->bDeviceProtocol != uaa->info.bDeviceProtocol)) { + continue; + } + if ((uaa->info.bDeviceClass == 0xFF) && + !(id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->match_flags & (USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS | + USB_DEVICE_ID_MATCH_INT_PROTOCOL))) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) && + (id->bInterfaceClass != uaa->info.bInterfaceClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) && + (id->bInterfaceSubClass != uaa->info.bInterfaceSubClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) && + (id->bInterfaceProtocol != uaa->info.bInterfaceProtocol)) { + continue; + } + /* we found a match! */ + return (id); + } + +done: + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_linux_probe + * + * This function is the FreeBSD probe callback. It is called from the + * FreeBSD USB stack through the "device_probe_and_attach()" function. + *------------------------------------------------------------------------*/ +static int +usb_linux_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb_driver *udrv; + int err = ENXIO; + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + mtx_lock(&Giant); + LIST_FOREACH(udrv, &usb_linux_driver_list, linux_driver_list) { + if (usb_linux_lookup_id(udrv->id_table, uaa)) { + err = 0; + break; + } + } + mtx_unlock(&Giant); + + return (err); +} + +/*------------------------------------------------------------------------* + * usb_linux_get_usb_driver + * + * This function returns the pointer to the "struct usb_driver" where + * the Linux USB device driver "struct usb_device_id" match was found. + * We apply a lock before reading out the pointer to avoid races. + *------------------------------------------------------------------------*/ +static struct usb_driver * +usb_linux_get_usb_driver(struct usb_linux_softc *sc) +{ + struct usb_driver *udrv; + + mtx_lock(&Giant); + udrv = sc->sc_udrv; + mtx_unlock(&Giant); + return (udrv); +} + +/*------------------------------------------------------------------------* + * usb_linux_attach + * + * This function is the FreeBSD attach callback. It is called from the + * FreeBSD USB stack through the "device_probe_and_attach()" function. + * This function is called when "usb_linux_probe()" returns zero. + *------------------------------------------------------------------------*/ +static int +usb_linux_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv; + struct usb_device *p_dev; + const struct usb_device_id *id = NULL; + + if (sc == NULL) { + return (ENOMEM); + } + mtx_lock(&Giant); + LIST_FOREACH(udrv, &usb_linux_driver_list, linux_driver_list) { + id = usb_linux_lookup_id(udrv->id_table, uaa); + if (id) + break; + } + mtx_unlock(&Giant); + + if (id == NULL) { + return (ENXIO); + } + /* + * Save some memory and only create the Linux compat structure when + * needed: + */ + p_dev = uaa->device->linux_dev; + if (p_dev == NULL) { + p_dev = usb_linux_create_usb_device(uaa->device, dev); + if (p_dev == NULL) { + return (ENOMEM); + } + uaa->device->linux_dev = p_dev; + } + device_set_usb2_desc(dev); + + sc->sc_fbsd_udev = uaa->device; + sc->sc_fbsd_dev = dev; + sc->sc_udrv = udrv; + sc->sc_ui = usb_ifnum_to_if(p_dev, uaa->info.bIfaceNum); + if (sc->sc_ui == NULL) { + return (EINVAL); + } + if (udrv->probe) { + if ((udrv->probe) (sc->sc_ui, id)) { + return (ENXIO); + } + } + mtx_lock(&Giant); + LIST_INSERT_HEAD(&usb_linux_attached_list, sc, sc_attached_list); + mtx_unlock(&Giant); + + /* success */ + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_detach + * + * This function is the FreeBSD detach callback. It is called from the + * FreeBSD USB stack through the "device_detach()" function. + *------------------------------------------------------------------------*/ +static int +usb_linux_detach(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = NULL; + + mtx_lock(&Giant); + if (sc->sc_attached_list.le_prev) { + LIST_REMOVE(sc, sc_attached_list); + sc->sc_attached_list.le_prev = NULL; + udrv = sc->sc_udrv; + sc->sc_udrv = NULL; + } + mtx_unlock(&Giant); + + if (udrv && udrv->disconnect) { + (udrv->disconnect) (sc->sc_ui); + } + /* + * Make sure that we free all FreeBSD USB transfers belonging to + * this Linux "usb_interface", hence they will most likely not be + * needed any more. + */ + usb_linux_cleanup_interface(sc->sc_fbsd_udev->linux_dev, sc->sc_ui); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_suspend + * + * This function is the FreeBSD suspend callback. Usually it does nothing. + *------------------------------------------------------------------------*/ +static int +usb_linux_suspend(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = usb_linux_get_usb_driver(sc); + int err; + + if (udrv && udrv->suspend) { + err = (udrv->suspend) (sc->sc_ui, 0); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_resume + * + * This function is the FreeBSD resume callback. Usually it does nothing. + *------------------------------------------------------------------------*/ +static int +usb_linux_resume(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = usb_linux_get_usb_driver(sc); + int err; + + if (udrv && udrv->resume) { + err = (udrv->resume) (sc->sc_ui); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_shutdown + * + * This function is the FreeBSD shutdown callback. Usually it does nothing. + *------------------------------------------------------------------------*/ +static int +usb_linux_shutdown(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = usb_linux_get_usb_driver(sc); + + if (udrv && udrv->shutdown) { + (udrv->shutdown) (sc->sc_ui); + } + return (0); +} + +/*------------------------------------------------------------------------* + * Linux emulation layer + *------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------* + * usb_max_isoc_frames + * + * The following function returns the maximum number of isochronous + * frames that we support per URB. It is not part of the Linux USB API. + *------------------------------------------------------------------------*/ +static uint16_t +usb_max_isoc_frames(struct usb_device *dev) +{ + return ((usb2_get_speed(dev->bsd_udev) == USB_SPEED_HIGH) ? + USB_MAX_HIGH_SPEED_ISOC_FRAMES : USB_MAX_FULL_SPEED_ISOC_FRAMES); +} + +/*------------------------------------------------------------------------* + * usb_submit_urb + * + * This function is used to queue an URB after that it has been + * initialized. If it returns non-zero, it means that the URB was not + * queued. + *------------------------------------------------------------------------*/ +int +usb_submit_urb(struct urb *urb, uint16_t mem_flags) +{ + struct usb_host_endpoint *uhe; + + if (urb == NULL) { + return (-EINVAL); + } + mtx_assert(&Giant, MA_OWNED); + + if (urb->pipe == NULL) { + return (-EINVAL); + } + uhe = urb->pipe; + + /* + * Check that we have got a FreeBSD USB transfer that will dequeue + * the URB structure and do the real transfer. If there are no USB + * transfers, then we return an error. + */ + if (uhe->bsd_xfer[0] || + uhe->bsd_xfer[1]) { + /* we are ready! */ + + TAILQ_INSERT_HEAD(&uhe->bsd_urb_list, urb, bsd_urb_list); + + urb->status = -EINPROGRESS; + + usb2_transfer_start(uhe->bsd_xfer[0]); + usb2_transfer_start(uhe->bsd_xfer[1]); + } else { + /* no pipes have been setup yet! */ + urb->status = -EINVAL; + return (-EINVAL); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_unlink_urb + * + * This function is used to stop an URB after that it is been + * submitted, but before the "complete" callback has been called. On + *------------------------------------------------------------------------*/ +int +usb_unlink_urb(struct urb *urb) +{ + return (usb_unlink_urb_sub(urb, 0)); +} + +static void +usb_unlink_bsd(struct usb2_xfer *xfer, + struct urb *urb, uint8_t drain) +{ + if (xfer && + usb2_transfer_pending(xfer) && + (xfer->priv_fifo == (void *)urb)) { + if (drain) { + mtx_unlock(&Giant); + usb2_transfer_drain(xfer); + mtx_lock(&Giant); + } else { + usb2_transfer_stop(xfer); + } + usb2_transfer_start(xfer); + } + return; +} + +static int +usb_unlink_urb_sub(struct urb *urb, uint8_t drain) +{ + struct usb_host_endpoint *uhe; + uint16_t x; + + if (urb == NULL) { + return (-EINVAL); + } + mtx_assert(&Giant, MA_OWNED); + + if (urb->pipe == NULL) { + return (-EINVAL); + } + uhe = urb->pipe; + + if (urb->bsd_urb_list.tqe_prev) { + + /* not started yet, just remove it from the queue */ + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + urb->status = -ECONNRESET; + urb->actual_length = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + urb->iso_frame_desc[x].actual_length = 0; + } + + if (urb->complete) { + (urb->complete) (urb); + } + } else { + + /* + * If the URB is not on the URB list, then check if one of + * the FreeBSD USB transfer are processing the current URB. + * If so, re-start that transfer, which will lead to the + * termination of that URB: + */ + usb_unlink_bsd(uhe->bsd_xfer[0], urb, drain); + usb_unlink_bsd(uhe->bsd_xfer[1], urb, drain); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_clear_halt + * + * This function must always be used to clear the stall. Stall is when + * an USB endpoint returns a stall message to the USB host controller. + * Until the stall is cleared, no data can be transferred. + *------------------------------------------------------------------------*/ +int +usb_clear_halt(struct usb_device *dev, struct usb_host_endpoint *uhe) +{ + struct usb2_config cfg[1]; + struct usb2_pipe *pipe; + uint8_t type; + uint8_t addr; + + if (uhe == NULL) + return (-EINVAL); + + type = uhe->desc.bmAttributes & UE_XFERTYPE; + addr = uhe->desc.bEndpointAddress; + + bzero(cfg, sizeof(cfg)); + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + + pipe = usb2_get_pipe(dev->bsd_udev, uhe->bsd_iface_index, cfg); + if (pipe == NULL) + return (-EINVAL); + + usb2_clear_data_toggle(dev->bsd_udev, pipe); + + return (usb_control_msg(dev, &dev->ep0, + UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT, + UF_ENDPOINT_HALT, addr, NULL, 0, 1000)); +} + +/*------------------------------------------------------------------------* + * usb_start_wait_urb + * + * This is an internal function that is used to perform synchronous + * Linux USB transfers. + *------------------------------------------------------------------------*/ +static int +usb_start_wait_urb(struct urb *urb, uint32_t timeout, uint16_t *p_actlen) +{ + int err; + + /* you must have a timeout! */ + if (timeout == 0) { + timeout = 1; + } + urb->complete = &usb_linux_wait_complete; + urb->timeout = timeout; + urb->transfer_flags |= URB_WAIT_WAKEUP; + urb->transfer_flags &= ~URB_IS_SLEEPING; + + err = usb_submit_urb(urb, 0); + if (err) + goto done; + + /* + * the URB might have completed before we get here, so check that by + * using some flags! + */ + while (urb->transfer_flags & URB_WAIT_WAKEUP) { + urb->transfer_flags |= URB_IS_SLEEPING; + usb2_cv_wait(&urb->cv_wait, &Giant); + urb->transfer_flags &= ~URB_IS_SLEEPING; + } + + err = urb->status; + +done: + if (err) { + *p_actlen = 0; + } else { + *p_actlen = urb->actual_length; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_control_msg + * + * The following function performs a control transfer sequence one any + * control, bulk or interrupt endpoint, specified by "uhe". A control + * transfer means that you transfer an 8-byte header first followed by + * a data-phase as indicated by the 8-byte header. The "timeout" is + * given in milliseconds. + * + * Return values: + * 0: Success + * < 0: Failure + * > 0: Acutal length + *------------------------------------------------------------------------*/ +int +usb_control_msg(struct usb_device *dev, struct usb_host_endpoint *uhe, + uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, void *data, + uint16_t size, uint32_t timeout) +{ + struct usb2_device_request req; + struct urb *urb; + int err; + uint16_t actlen; + uint8_t type; + uint8_t addr; + + req.bmRequestType = requesttype; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, size); + + if (uhe == NULL) { + return (-EINVAL); + } + type = (uhe->desc.bmAttributes & UE_XFERTYPE); + addr = (uhe->desc.bEndpointAddress & UE_ADDR); + + if (type != UE_CONTROL) { + return (-EINVAL); + } + if (addr == 0) { + /* + * The FreeBSD USB stack supports standard control + * transfers on control endpoint zero: + */ + err = usb2_do_request_flags(dev->bsd_udev, + &Giant, &req, data, USB_SHORT_XFER_OK, + &actlen, timeout); + if (err) { + err = -EPIPE; + } else { + err = actlen; + } + return (err); + } + if (dev->bsd_udev->flags.usb2_mode != USB_MODE_HOST) { + /* not supported */ + return (-EINVAL); + } + err = usb_setup_endpoint(dev, uhe, 1 /* dummy */ ); + + /* + * NOTE: we need to allocate real memory here so that we don't + * transfer data to/from the stack! + * + * 0xFFFF is a FreeBSD specific magic value. + */ + urb = usb_alloc_urb(0xFFFF, size); + if (urb == NULL) + return (-ENOMEM); + + urb->dev = dev; + urb->pipe = uhe; + + bcopy(&req, urb->setup_packet, sizeof(req)); + + if (size && (!(req.bmRequestType & UT_READ))) { + /* move the data to a real buffer */ + bcopy(data, USB_ADD_BYTES(urb->setup_packet, + sizeof(req)), size); + } + err = usb_start_wait_urb(urb, timeout, &actlen); + + if (req.bmRequestType & UT_READ) { + if (actlen) { + bcopy(USB_ADD_BYTES(urb->setup_packet, + sizeof(req)), data, actlen); + } + } + usb_free_urb(urb); + + if (err == 0) { + err = actlen; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_set_interface + * + * The following function will select which alternate setting of an + * USB interface you plan to use. By default alternate setting with + * index zero is selected. Note that "iface_no" is not the interface + * index, but rather the value of "bInterfaceNumber". + *------------------------------------------------------------------------*/ +int +usb_set_interface(struct usb_device *dev, uint8_t iface_no, uint8_t alt_index) +{ + struct usb_interface *p_ui = usb_ifnum_to_if(dev, iface_no); + int err; + + if (p_ui == NULL) + return (-EINVAL); + if (alt_index >= p_ui->num_altsetting) + return (-EINVAL); + usb_linux_cleanup_interface(dev, p_ui); + err = -usb2_set_alt_interface_index(dev->bsd_udev, + p_ui->bsd_iface_index, alt_index); + if (err == 0) { + p_ui->cur_altsetting = p_ui->altsetting + alt_index; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_setup_endpoint + * + * The following function is an extension to the Linux USB API that + * allows you to set a maximum buffer size for a given USB endpoint. + * The maximum buffer size is per URB. If you don't call this function + * to set a maximum buffer size, the endpoint will not be functional. + * Note that for isochronous endpoints the maximum buffer size must be + * a non-zero dummy, hence this function will base the maximum buffer + * size on "wMaxPacketSize". + *------------------------------------------------------------------------*/ +int +usb_setup_endpoint(struct usb_device *dev, + struct usb_host_endpoint *uhe, uint32_t bufsize) +{ + struct usb2_config cfg[2]; + uint8_t type = uhe->desc.bmAttributes & UE_XFERTYPE; + uint8_t addr = uhe->desc.bEndpointAddress; + + if (uhe->fbsd_buf_size == bufsize) { + /* optimize */ + return (0); + } + usb2_transfer_unsetup(uhe->bsd_xfer, 2); + + uhe->fbsd_buf_size = bufsize; + + if (bufsize == 0) { + return (0); + } + bzero(cfg, sizeof(cfg)); + + if (type == UE_ISOCHRONOUS) { + + /* + * Isochronous transfers are special in that they don't fit + * into the BULK/INTR/CONTROL transfer model. + */ + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + cfg[0].mh.callback = &usb_linux_isoc_callback; + cfg[0].mh.bufsize = 0; /* use wMaxPacketSize */ + cfg[0].mh.frames = usb_max_isoc_frames(dev); + cfg[0].mh.flags.proxy_buffer = 1; +#if 0 + /* + * The Linux USB API allows non back-to-back + * isochronous frames which we do not support. If the + * isochronous frames are not back-to-back we need to + * do a copy, and then we need a buffer for + * that. Enable this at your own risk. + */ + cfg[0].mh.flags.ext_buffer = 1; +#endif + cfg[0].mh.flags.short_xfer_ok = 1; + + bcopy(cfg, cfg + 1, sizeof(*cfg)); + + /* Allocate and setup two generic FreeBSD USB transfers */ + + if (usb2_transfer_setup(dev->bsd_udev, &uhe->bsd_iface_index, + uhe->bsd_xfer, cfg, 2, uhe, &Giant)) { + return (-EINVAL); + } + } else { + if (bufsize > (1 << 22)) { + /* limit buffer size */ + bufsize = (1 << 22); + } + /* Allocate and setup one generic FreeBSD USB transfer */ + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + cfg[0].mh.callback = &usb_linux_non_isoc_callback; + cfg[0].mh.bufsize = bufsize; + cfg[0].mh.flags.ext_buffer = 1; /* enable zero-copy */ + cfg[0].mh.flags.proxy_buffer = 1; + cfg[0].mh.flags.short_xfer_ok = 1; + + if (usb2_transfer_setup(dev->bsd_udev, &uhe->bsd_iface_index, + uhe->bsd_xfer, cfg, 1, uhe, &Giant)) { + return (-EINVAL); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_create_usb_device + * + * The following function is used to build up a per USB device + * structure tree, that mimics the Linux one. The root structure + * is returned by this function. + *------------------------------------------------------------------------*/ +static struct usb_device * +usb_linux_create_usb_device(struct usb2_device *udev, device_t dev) +{ + struct usb2_config_descriptor *cd = usb2_get_config_descriptor(udev); + struct usb2_descriptor *desc; + struct usb2_interface_descriptor *id; + struct usb2_endpoint_descriptor *ed; + struct usb_device *p_ud = NULL; + struct usb_interface *p_ui = NULL; + struct usb_host_interface *p_uhi = NULL; + struct usb_host_endpoint *p_uhe = NULL; + uint32_t size; + uint16_t niface_total; + uint16_t nedesc; + uint16_t iface_no_curr; + uint16_t iface_index; + uint8_t pass; + uint8_t iface_no; + + /* + * We do two passes. One pass for computing necessary memory size + * and one pass to initialize all the allocated memory structures. + */ + for (pass = 0; pass < 2; pass++) { + + iface_no_curr = 0 - 1; + niface_total = 0; + iface_index = 0; + nedesc = 0; + desc = NULL; + + /* + * Iterate over all the USB descriptors. Use the USB config + * descriptor pointer provided by the FreeBSD USB stack. + */ + while ((desc = usb2_desc_foreach(cd, desc))) { + + /* + * Build up a tree according to the descriptors we + * find: + */ + switch (desc->bDescriptorType) { + case UDESC_DEVICE: + break; + + case UDESC_ENDPOINT: + ed = (void *)desc; + if ((ed->bLength < sizeof(*ed)) || + (iface_index == 0)) + break; + if (p_uhe) { + bcopy(ed, &p_uhe->desc, sizeof(p_uhe->desc)); + p_uhe->bsd_iface_index = iface_index - 1; + p_uhe++; + } + if (p_uhi) { + (p_uhi - 1)->desc.bNumEndpoints++; + } + nedesc++; + break; + + case UDESC_INTERFACE: + id = (void *)desc; + if (id->bLength < sizeof(*id)) + break; + if (p_uhi) { + bcopy(id, &p_uhi->desc, sizeof(p_uhi->desc)); + p_uhi->desc.bNumEndpoints = 0; + p_uhi->endpoint = p_uhe; + p_uhi->string = ""; + p_uhi->bsd_iface_index = iface_index; + p_uhi++; + } + iface_no = id->bInterfaceNumber; + niface_total++; + if (iface_no_curr != iface_no) { + if (p_ui) { + p_ui->altsetting = p_uhi - 1; + p_ui->cur_altsetting = p_uhi - 1; + p_ui->num_altsetting = 1; + p_ui->bsd_iface_index = iface_index; + p_ui->linux_udev = p_ud; + p_ui++; + } + iface_no_curr = iface_no; + iface_index++; + } else { + if (p_ui) { + (p_ui - 1)->num_altsetting++; + } + } + break; + + default: + break; + } + } + + if (pass == 0) { + + size = ((sizeof(*p_ud) * 1) + + (sizeof(*p_uhe) * nedesc) + + (sizeof(*p_ui) * iface_index) + + (sizeof(*p_uhi) * niface_total)); + + p_ud = malloc(size, M_USBDEV, M_WAITOK | M_ZERO); + if (p_ud == NULL) { + goto done; + } + p_uhe = (void *)(p_ud + 1); + p_ui = (void *)(p_uhe + nedesc); + p_uhi = (void *)(p_ui + iface_index); + + p_ud->product = ""; + p_ud->manufacturer = ""; + p_ud->serial = ""; + p_ud->speed = usb2_get_speed(udev); + p_ud->bsd_udev = udev; + p_ud->bsd_iface_start = p_ui; + p_ud->bsd_iface_end = p_ui + iface_index; + p_ud->bsd_endpoint_start = p_uhe; + p_ud->bsd_endpoint_end = p_uhe + nedesc; + p_ud->devnum = device_get_unit(dev); + bcopy(&udev->ddesc, &p_ud->descriptor, + sizeof(p_ud->descriptor)); + bcopy(udev->default_pipe.edesc, &p_ud->ep0.desc, + sizeof(p_ud->ep0.desc)); + } + } +done: + return (p_ud); +} + +/*------------------------------------------------------------------------* + * usb_alloc_urb + * + * This function should always be used when you allocate an URB for + * use with the USB Linux stack. In case of an isochronous transfer + * you must specifiy the maximum number of "iso_packets" which you + * plan to transfer per URB. This function is always blocking, and + * "mem_flags" are not regarded like on Linux. + *------------------------------------------------------------------------*/ +struct urb * +usb_alloc_urb(uint16_t iso_packets, uint16_t mem_flags) +{ + struct urb *urb; + uint32_t size; + + if (iso_packets == 0xFFFF) { + /* + * FreeBSD specific magic value to ask for control transfer + * memory allocation: + */ + size = sizeof(*urb) + sizeof(struct usb2_device_request) + mem_flags; + } else { + size = sizeof(*urb) + (iso_packets * sizeof(urb->iso_frame_desc[0])); + } + + urb = malloc(size, M_USBDEV, M_WAITOK | M_ZERO); + if (urb) { + + usb2_cv_init(&urb->cv_wait, "URBWAIT"); + if (iso_packets == 0xFFFF) { + urb->setup_packet = (void *)(urb + 1); + urb->transfer_buffer = (void *)(urb->setup_packet + + sizeof(struct usb2_device_request)); + } else { + urb->number_of_packets = iso_packets; + } + } + return (urb); +} + +/*------------------------------------------------------------------------* + * usb_find_host_endpoint + * + * The following function will return the Linux USB host endpoint + * structure that matches the given endpoint type and endpoint + * value. If no match is found, NULL is returned. This function is not + * part of the Linux USB API and is only used internally. + *------------------------------------------------------------------------*/ +struct usb_host_endpoint * +usb_find_host_endpoint(struct usb_device *dev, uint8_t type, uint8_t ep) +{ + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + struct usb_host_interface *uhi; + struct usb_interface *ui; + uint8_t ea; + uint8_t at; + uint8_t mask; + + if (dev == NULL) { + return (NULL); + } + if (type == UE_CONTROL) { + mask = UE_ADDR; + } else { + mask = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR); + } + + ep &= mask; + + /* + * Iterate over all the interfaces searching the selected alternate + * setting only, and all belonging endpoints. + */ + for (ui = dev->bsd_iface_start; + ui != dev->bsd_iface_end; + ui++) { + uhi = ui->cur_altsetting; + if (uhi) { + uhe_end = uhi->endpoint + uhi->desc.bNumEndpoints; + for (uhe = uhi->endpoint; + uhe != uhe_end; + uhe++) { + ea = uhe->desc.bEndpointAddress; + at = uhe->desc.bmAttributes; + + if (((ea & mask) == ep) && + ((at & UE_XFERTYPE) == type)) { + return (uhe); + } + } + } + } + + if ((type == UE_CONTROL) && ((ep & UE_ADDR) == 0)) { + return (&dev->ep0); + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_altnum_to_altsetting + * + * The following function returns a pointer to an alternate setting by + * index given a "usb_interface" pointer. If the alternate setting by + * index does not exist, NULL is returned. And alternate setting is a + * variant of an interface, but usually with slightly different + * characteristics. + *------------------------------------------------------------------------*/ +struct usb_host_interface * +usb_altnum_to_altsetting(const struct usb_interface *intf, uint8_t alt_index) +{ + if (alt_index >= intf->num_altsetting) { + return (NULL); + } + return (intf->altsetting + alt_index); +} + +/*------------------------------------------------------------------------* + * usb_ifnum_to_if + * + * The following function searches up an USB interface by + * "bInterfaceNumber". If no match is found, NULL is returned. + *------------------------------------------------------------------------*/ +struct usb_interface * +usb_ifnum_to_if(struct usb_device *dev, uint8_t iface_no) +{ + struct usb_interface *p_ui; + + for (p_ui = dev->bsd_iface_start; + p_ui != dev->bsd_iface_end; + p_ui++) { + if ((p_ui->num_altsetting > 0) && + (p_ui->altsetting->desc.bInterfaceNumber == iface_no)) { + return (p_ui); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_buffer_alloc + *------------------------------------------------------------------------*/ +void * +usb_buffer_alloc(struct usb_device *dev, uint32_t size, uint16_t mem_flags, uint8_t *dma_addr) +{ + return (malloc(size, M_USBDEV, M_WAITOK | M_ZERO)); +} + +/*------------------------------------------------------------------------* + * usb_get_intfdata + *------------------------------------------------------------------------*/ +void * +usb_get_intfdata(struct usb_interface *intf) +{ + return (intf->bsd_priv_sc); +} + +/*------------------------------------------------------------------------* + * usb_linux_register + * + * The following function is used by the "USB_DRIVER_EXPORT()" macro, + * and is used to register a Linux USB driver, so that its + * "usb_device_id" structures gets searched a probe time. This + * function is not part of the Linux USB API, and is for internal use + * only. + *------------------------------------------------------------------------*/ +void +usb_linux_register(void *arg) +{ + struct usb_driver *drv = arg; + + mtx_lock(&Giant); + LIST_INSERT_HEAD(&usb_linux_driver_list, drv, linux_driver_list); + mtx_unlock(&Giant); + + usb2_needs_explore_all(); + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_deregister + * + * The following function is used by the "USB_DRIVER_EXPORT()" macro, + * and is used to deregister a Linux USB driver. This function will + * ensure that all driver instances belonging to the Linux USB device + * driver in question, gets detached before the driver is + * unloaded. This function is not part of the Linux USB API, and is + * for internal use only. + *------------------------------------------------------------------------*/ +void +usb_linux_deregister(void *arg) +{ + struct usb_driver *drv = arg; + struct usb_linux_softc *sc; + +repeat: + mtx_lock(&Giant); + LIST_FOREACH(sc, &usb_linux_attached_list, sc_attached_list) { + if (sc->sc_udrv == drv) { + mtx_unlock(&Giant); + device_detach(sc->sc_fbsd_dev); + goto repeat; + } + } + LIST_REMOVE(drv, linux_driver_list); + mtx_unlock(&Giant); + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_free_device + * + * The following function is only used by the FreeBSD USB stack, to + * cleanup and free memory after that a Linux USB device was attached. + *------------------------------------------------------------------------*/ +void +usb_linux_free_device(struct usb_device *dev) +{ + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + int err; + + uhe = dev->bsd_endpoint_start; + uhe_end = dev->bsd_endpoint_end; + while (uhe != uhe_end) { + err = usb_setup_endpoint(dev, uhe, 0); + uhe++; + } + err = usb_setup_endpoint(dev, &dev->ep0, 0); + free(dev, M_USBDEV); + return; +} + +/*------------------------------------------------------------------------* + * usb_buffer_free + *------------------------------------------------------------------------*/ +void +usb_buffer_free(struct usb_device *dev, uint32_t size, + void *addr, uint8_t dma_addr) +{ + free(addr, M_USBDEV); + return; +} + +/*------------------------------------------------------------------------* + * usb_free_urb + *------------------------------------------------------------------------*/ +void +usb_free_urb(struct urb *urb) +{ + if (urb == NULL) { + return; + } + /* make sure that the current URB is not active */ + usb_kill_urb(urb); + + /* destroy condition variable */ + usb2_cv_destroy(&urb->cv_wait); + + /* just free it */ + free(urb, M_USBDEV); + return; +} + +/*------------------------------------------------------------------------* + * usb_init_urb + * + * The following function can be used to initialize a custom URB. It + * is not recommended to use this function. Use "usb_alloc_urb()" + * instead. + *------------------------------------------------------------------------*/ +void +usb_init_urb(struct urb *urb) +{ + if (urb == NULL) { + return; + } + bzero(urb, sizeof(*urb)); + return; +} + +/*------------------------------------------------------------------------* + * usb_kill_urb + *------------------------------------------------------------------------*/ +void +usb_kill_urb(struct urb *urb) +{ + if (usb_unlink_urb_sub(urb, 1)) { + /* ignore */ + } + return; +} + +/*------------------------------------------------------------------------* + * usb_set_intfdata + * + * The following function sets the per Linux USB interface private + * data pointer. It is used by most Linux USB device drivers. + *------------------------------------------------------------------------*/ +void +usb_set_intfdata(struct usb_interface *intf, void *data) +{ + intf->bsd_priv_sc = data; + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_cleanup_interface + * + * The following function will release all FreeBSD USB transfers + * associated with a Linux USB interface. It is for internal use only. + *------------------------------------------------------------------------*/ +static void +usb_linux_cleanup_interface(struct usb_device *dev, struct usb_interface *iface) +{ + struct usb_host_interface *uhi; + struct usb_host_interface *uhi_end; + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + int err; + + uhi = iface->altsetting; + uhi_end = iface->altsetting + iface->num_altsetting; + while (uhi != uhi_end) { + uhe = uhi->endpoint; + uhe_end = uhi->endpoint + uhi->desc.bNumEndpoints; + while (uhe != uhe_end) { + err = usb_setup_endpoint(dev, uhe, 0); + uhe++; + } + uhi++; + } + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_wait_complete + * + * The following function is used by "usb_start_wait_urb()" to wake it + * up, when an USB transfer has finished. + *------------------------------------------------------------------------*/ +static void +usb_linux_wait_complete(struct urb *urb) +{ + if (urb->transfer_flags & URB_IS_SLEEPING) { + usb2_cv_signal(&urb->cv_wait); + } + urb->transfer_flags &= ~URB_WAIT_WAKEUP; + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_complete + *------------------------------------------------------------------------*/ +static void +usb_linux_complete(struct usb2_xfer *xfer) +{ + struct urb *urb; + + urb = xfer->priv_fifo; + xfer->priv_fifo = NULL; + if (urb->complete) { + (urb->complete) (urb); + } + return; +} + +/*------------------------------------------------------------------------* + * usb_linux_isoc_callback + * + * The following is the FreeBSD isochronous USB callback. Isochronous + * frames are USB packets transferred 1000 or 8000 times per second, + * depending on whether a full- or high- speed USB transfer is + * used. + *------------------------------------------------------------------------*/ +static void +usb_linux_isoc_callback(struct usb2_xfer *xfer) +{ + uint32_t max_frame = xfer->max_frame_size; + uint32_t offset; + uint16_t x; + struct urb *urb = xfer->priv_fifo; + struct usb_host_endpoint *uhe = xfer->priv_sc; + struct usb_iso_packet_descriptor *uipd; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (urb->bsd_isread) { + + /* copy in data with regard to the URB */ + + offset = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + uipd->actual_length = xfer->frlengths[x]; + uipd->status = 0; + if (!xfer->flags.ext_buffer) { + usb2_copy_out(xfer->frbuffers, offset, + USB_ADD_BYTES(urb->transfer_buffer, + uipd->offset), uipd->actual_length); + } + offset += max_frame; + } + } else { + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + uipd->actual_length = xfer->frlengths[x]; + uipd->status = 0; + } + } + + urb->actual_length = xfer->actlen; + + /* check for short transfer */ + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + urb->status = -EPIPE; /* XXX should be + * EREMOTEIO */ + } else { + urb->status = 0; + } + } else { + /* success */ + urb->status = 0; + } + + /* call callback */ + usb_linux_complete(xfer); + + case USB_ST_SETUP: +tr_setup: + + if (xfer->priv_fifo == NULL) { + + /* get next transfer */ + urb = TAILQ_FIRST(&uhe->bsd_urb_list); + if (urb == NULL) { + /* nothing to do */ + return; + } + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + + x = xfer->max_frame_count; + if (urb->number_of_packets > x) { + /* XXX simply truncate the transfer */ + urb->number_of_packets = x; + } + } else { + DPRINTF("Already got a transfer\n"); + + /* already got a transfer (should not happen) */ + urb = xfer->priv_fifo; + } + + urb->bsd_isread = (uhe->desc.bEndpointAddress & UE_DIR_IN) ? 1 : 0; + + if (!(urb->bsd_isread)) { + + /* copy out data with regard to the URB */ + + offset = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + xfer->frlengths[x] = uipd->length; + if (!xfer->flags.ext_buffer) { + usb2_copy_in(xfer->frbuffers, offset, + USB_ADD_BYTES(urb->transfer_buffer, + uipd->offset), uipd->length); + } + offset += uipd->length; + } + } else { + + /* + * compute the transfer length into the "offset" + * variable + */ + + offset = urb->number_of_packets * max_frame; + + /* setup "frlengths" array */ + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + xfer->frlengths[x] = max_frame; + } + } + + if (xfer->flags.ext_buffer) { + /* set virtual address to load */ + usb2_set_frame_data(xfer, + urb->transfer_buffer, 0); + } + xfer->priv_fifo = urb; + xfer->flags.force_short_xfer = 0; + xfer->timeout = urb->timeout; + xfer->nframes = urb->number_of_packets; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + urb->status = -ECONNRESET; + } else { + urb->status = -EPIPE; /* stalled */ + } + + /* Set zero for "actual_length" */ + urb->actual_length = 0; + + /* Set zero for "actual_length" */ + for (x = 0; x < urb->number_of_packets; x++) { + urb->iso_frame_desc[x].actual_length = 0; + } + + /* call callback */ + usb_linux_complete(xfer); + + if (xfer->error == USB_ERR_CANCELLED) { + /* we need to return in this case */ + return; + } + goto tr_setup; + + } +} + +/*------------------------------------------------------------------------* + * usb_linux_non_isoc_callback + * + * The following is the FreeBSD BULK/INTERRUPT and CONTROL USB + * callback. It dequeues Linux USB stack compatible URB's, transforms + * the URB fields into a FreeBSD USB transfer, and defragments the USB + * transfer as required. When the transfer is complete the "complete" + * callback is called. + *------------------------------------------------------------------------*/ +static void +usb_linux_non_isoc_callback(struct usb2_xfer *xfer) +{ + enum { + REQ_SIZE = sizeof(struct usb2_device_request) + }; + struct urb *urb = xfer->priv_fifo; + struct usb_host_endpoint *uhe = xfer->priv_sc; + uint8_t *ptr; + uint32_t max_bulk = xfer->max_data_length; + uint8_t data_frame = xfer->flags_int.control_xfr ? 1 : 0; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->flags_int.control_xfr) { + + /* don't transfer the setup packet again: */ + + xfer->frlengths[0] = 0; + } + if (urb->bsd_isread && (!xfer->flags.ext_buffer)) { + /* copy in data with regard to the URB */ + usb2_copy_out(xfer->frbuffers + data_frame, 0, + urb->bsd_data_ptr, xfer->frlengths[data_frame]); + } + urb->bsd_length_rem -= xfer->frlengths[data_frame]; + urb->bsd_data_ptr += xfer->frlengths[data_frame]; + urb->actual_length += xfer->frlengths[data_frame]; + + /* check for short transfer */ + if (xfer->actlen < xfer->sumlen) { + urb->bsd_length_rem = 0; + + /* short transfer */ + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + urb->status = -EPIPE; + } else { + urb->status = 0; + } + } else { + /* check remainder */ + if (urb->bsd_length_rem > 0) { + goto setup_bulk; + } + /* success */ + urb->status = 0; + } + + /* call callback */ + usb_linux_complete(xfer); + + case USB_ST_SETUP: +tr_setup: + /* get next transfer */ + urb = TAILQ_FIRST(&uhe->bsd_urb_list); + if (urb == NULL) { + /* nothing to do */ + return; + } + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + + xfer->priv_fifo = urb; + xfer->flags.force_short_xfer = 0; + xfer->timeout = urb->timeout; + + if (xfer->flags_int.control_xfr) { + + /* + * USB control transfers need special handling. + * First copy in the header, then copy in data! + */ + if (!xfer->flags.ext_buffer) { + usb2_copy_in(xfer->frbuffers, 0, + urb->setup_packet, REQ_SIZE); + } else { + /* set virtual address to load */ + usb2_set_frame_data(xfer, + urb->setup_packet, 0); + } + + xfer->frlengths[0] = REQ_SIZE; + + ptr = urb->setup_packet; + + /* setup data transfer direction and length */ + urb->bsd_isread = (ptr[0] & UT_READ) ? 1 : 0; + urb->bsd_length_rem = ptr[6] | (ptr[7] << 8); + + } else { + + /* setup data transfer direction */ + + urb->bsd_length_rem = urb->transfer_buffer_length; + urb->bsd_isread = (uhe->desc.bEndpointAddress & + UE_DIR_IN) ? 1 : 0; + } + + urb->bsd_data_ptr = urb->transfer_buffer; + urb->actual_length = 0; + +setup_bulk: + if (max_bulk > urb->bsd_length_rem) { + max_bulk = urb->bsd_length_rem; + } + /* check if we need to force a short transfer */ + + if ((max_bulk == urb->bsd_length_rem) && + (urb->transfer_flags & URB_ZERO_PACKET) && + (!xfer->flags_int.control_xfr)) { + xfer->flags.force_short_xfer = 1; + } + /* check if we need to copy in data */ + + if (xfer->flags.ext_buffer) { + /* set virtual address to load */ + usb2_set_frame_data(xfer, urb->bsd_data_ptr, + data_frame); + } else if (!urb->bsd_isread) { + /* copy out data with regard to the URB */ + usb2_copy_in(xfer->frbuffers + data_frame, 0, + urb->bsd_data_ptr, max_bulk); + } + xfer->frlengths[data_frame] = max_bulk; + if (xfer->flags_int.control_xfr) { + if (max_bulk > 0) { + xfer->nframes = 2; + } else { + xfer->nframes = 1; + } + } else { + xfer->nframes = 1; + } + usb2_start_hardware(xfer); + return; + + default: + if (xfer->error == USB_ERR_CANCELLED) { + urb->status = -ECONNRESET; + } else { + urb->status = -EPIPE; + } + + /* Set zero for "actual_length" */ + urb->actual_length = 0; + + /* call callback */ + usb_linux_complete(xfer); + + if (xfer->error == USB_ERR_CANCELLED) { + /* we need to return in this case */ + return; + } + goto tr_setup; + } +} diff --git a/sys/dev/usb2/core/usb2_compat_linux.h b/sys/dev/usb2/core/usb2_compat_linux.h new file mode 100644 index 000000000000..b8ddaf4fe93f --- /dev/null +++ b/sys/dev/usb2/core/usb2_compat_linux.h @@ -0,0 +1,465 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Luigi Rizzo - Universita` di Pisa. All rights reserved. + * Copyright (c) 2007 Hans Petter Selasky. 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. + */ + +#ifndef _USB_COMPAT_LINUX_H +#define _USB_COMPAT_LINUX_H + +struct usb_device; +struct usb_interface; +struct usb_driver; +struct urb; + +typedef void *pm_message_t; +typedef void (usb_complete_t)(struct urb *); + +#define USB_MAX_FULL_SPEED_ISOC_FRAMES (60 * 1) +#define USB_MAX_HIGH_SPEED_ISOC_FRAMES (60 * 8) + +/* + * Linux compatible USB device drivers put their device information + * into the "usb_device_id" structure using the "USB_DEVICE()" macro. + * The "MODULE_DEVICE_TABLE()" macro can be used to export this + * information to userland. + */ +struct usb_device_id { + /* which fields to match against */ + uint16_t match_flags; +#define USB_DEVICE_ID_MATCH_VENDOR 0x0001 +#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002 +#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004 +#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008 +#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010 +#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020 +#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040 +#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080 +#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100 +#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200 + + /* Used for product specific matches; the BCD range is inclusive */ + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice_lo; + uint16_t bcdDevice_hi; + + /* Used for device class matches */ + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + + /* Used for interface class matches */ + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + /* Hook for driver specific information */ + unsigned long driver_info; +}; + +#define USB_DEVICE_ID_MATCH_DEVICE \ + (USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT) + +#define USB_DEVICE(vend,prod) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend), \ + .idProduct = (prod) + +/* The "usb_driver" structure holds the Linux USB device driver + * callbacks, and a pointer to device ID's which this entry should + * match against. Usually this entry is exposed to the USB emulation + * layer using the "USB_DRIVER_EXPORT()" macro, which is defined + * below. + */ +struct usb_driver { + const char *name; + + int (*probe) (struct usb_interface *intf, + const struct usb_device_id *id); + + void (*disconnect) (struct usb_interface *intf); + + int (*ioctl) (struct usb_interface *intf, unsigned int code, + void *buf); + + int (*suspend) (struct usb_interface *intf, pm_message_t message); + int (*resume) (struct usb_interface *intf); + + const struct usb_device_id *id_table; + + void (*shutdown) (struct usb_interface *intf); + + LIST_ENTRY(usb_driver) linux_driver_list; +}; + +#define USB_DRIVER_EXPORT(id,p_usb_drv) \ + SYSINIT(id,SI_SUB_KLD,SI_ORDER_FIRST,usb_linux_register,p_usb_drv); \ + SYSUNINIT(id,SI_SUB_KLD,SI_ORDER_ANY,usb_linux_deregister,p_usb_drv) + +/* + * The following structure is the same as "usb_device_descriptor_t" + * except that 16-bit values are "uint16_t" and not an array of "uint8_t". + * It is used by Linux USB device drivers. + */ +struct usb_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} __packed; + +/* + * The following structure is the same as + * "usb_interface_descriptor_t". It is used by + * Linux USB device drivers. + */ +struct usb_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; +} __packed; + +/* + * The following structure is the same as "usb_endpoint_descriptor_t" + * except that 16-bit values are "uint16_t" and not an array of "uint8_t". + * It is used by Linux USB device drivers. + */ +struct usb_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + + /* extension for audio endpoints only: */ + uint8_t bRefresh; + uint8_t bSynchAddress; +} __packed; + +#define USB_DT_ENDPOINT_SIZE 7 +#define USB_DT_ENDPOINT_AUDIO_SIZE 9 + +/* + * Endpoints + */ +#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ +#define USB_ENDPOINT_DIR_MASK 0x80 + +#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 +#define USB_ENDPOINT_MAX_ADJUSTABLE 0x80 + +/* CONTROL REQUEST SUPPORT */ + +/* + * Definition of direction mask for + * "bEndpointAddress" and "bmRequestType": + */ +#define USB_DIR_MASK 0x80 +#define USB_DIR_OUT 0x00 /* write to USB device */ +#define USB_DIR_IN 0x80 /* read from USB device */ + +/* + * Definition of type mask for + * "bmRequestType": + */ +#define USB_TYPE_MASK (0x03 << 5) +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +/* + * Definition of receiver mask for + * "bmRequestType": + */ +#define USB_RECIP_MASK 0x1f +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +/* + * Definition of standard request values for + * "bRequest": + */ +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +#define USB_REQ_SET_ENCRYPTION 0x0D /* Wireless USB */ +#define USB_REQ_GET_ENCRYPTION 0x0E +#define USB_REQ_SET_HANDSHAKE 0x0F +#define USB_REQ_GET_HANDSHAKE 0x10 +#define USB_REQ_SET_CONNECTION 0x11 +#define USB_REQ_SET_SECURITY_DATA 0x12 +#define USB_REQ_GET_SECURITY_DATA 0x13 +#define USB_REQ_SET_WUSB_DATA 0x14 +#define USB_REQ_LOOPBACK_DATA_WRITE 0x15 +#define USB_REQ_LOOPBACK_DATA_READ 0x16 +#define USB_REQ_SET_INTERFACE_DS 0x17 + +/* + * USB feature flags are written using USB_REQ_{CLEAR,SET}_FEATURE, and + * are read as a bit array returned by USB_REQ_GET_STATUS. (So there + * are at most sixteen features of each type.) + */ +#define USB_DEVICE_SELF_POWERED 0 /* (read only) */ +#define USB_DEVICE_REMOTE_WAKEUP 1 /* dev may initiate wakeup */ +#define USB_DEVICE_TEST_MODE 2 /* (wired high speed only) */ +#define USB_DEVICE_BATTERY 2 /* (wireless) */ +#define USB_DEVICE_B_HNP_ENABLE 3 /* (otg) dev may initiate HNP */ +#define USB_DEVICE_WUSB_DEVICE 3 /* (wireless) */ +#define USB_DEVICE_A_HNP_SUPPORT 4 /* (otg) RH port supports HNP */ +#define USB_DEVICE_A_ALT_HNP_SUPPORT 5 /* (otg) other RH port does */ +#define USB_DEVICE_DEBUG_MODE 6 /* (special devices only) */ + +#define USB_ENDPOINT_HALT 0 /* IN/OUT will STALL */ + +#define PIPE_ISOCHRONOUS 0x01 /* UE_ISOCHRONOUS */ +#define PIPE_INTERRUPT 0x03 /* UE_INTERRUPT */ +#define PIPE_CONTROL 0x00 /* UE_CONTROL */ +#define PIPE_BULK 0x02 /* UE_BULK */ + +/* Whenever Linux references an USB endpoint: + * a) to initialize "urb->pipe" + * b) second argument passed to "usb_control_msg()" + * + * Then it uses one of the following macros. The "endpoint" argument + * is the physical endpoint value masked by 0xF. The "dev" argument + * is a pointer to "struct usb_device". + */ +#define usb_sndctrlpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_CONTROL, (endpoint) | USB_DIR_OUT) + +#define usb_rcvctrlpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_CONTROL, (endpoint) | USB_DIR_IN) + +#define usb_sndisocpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_ISOCHRONOUS, (endpoint) | USB_DIR_OUT) + +#define usb_rcvisocpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_ISOCHRONOUS, (endpoint) | USB_DIR_IN) + +#define usb_sndbulkpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_BULK, (endpoint) | USB_DIR_OUT) + +#define usb_rcvbulkpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_BULK, (endpoint) | USB_DIR_IN) + +#define usb_sndintpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_INTERRUPT, (endpoint) | USB_DIR_OUT) + +#define usb_rcvintpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_INTERRUPT, (endpoint) | USB_DIR_IN) + +/* The following four structures makes up a tree, where we have the + * leaf structure, "usb_host_endpoint", first, and the root structure, + * "usb_device", last. The four structures below mirror the structure + * of the USB descriptors belonging to an USB configuration. Please + * refer to the USB specification for a definition of "endpoints" and + * "interfaces". + */ +struct usb_host_endpoint { + struct usb_endpoint_descriptor desc; + + TAILQ_HEAD(, urb) bsd_urb_list; + + struct usb2_xfer *bsd_xfer[2]; + + uint8_t *extra; /* Extra descriptors */ + + uint32_t fbsd_buf_size; + + uint16_t extralen; + + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +struct usb_host_interface { + struct usb_interface_descriptor desc; + + /* the following array has size "desc.bNumEndpoint" */ + struct usb_host_endpoint *endpoint; + + const char *string; /* iInterface string, if present */ + uint8_t *extra; /* Extra descriptors */ + + uint16_t extralen; + + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +struct usb_interface { + /* array of alternate settings for this interface */ + struct usb_host_interface *altsetting; + struct usb_host_interface *cur_altsetting; + struct usb_device *linux_udev; + void *bsd_priv_sc; /* device specific information */ + + uint8_t num_altsetting; /* number of alternate settings */ + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +struct usb_device { + struct usb_device_descriptor descriptor; + struct usb_host_endpoint ep0; + + struct usb2_device *bsd_udev; + struct usb_interface *bsd_iface_start; + struct usb_interface *bsd_iface_end; + struct usb_host_endpoint *bsd_endpoint_start; + struct usb_host_endpoint *bsd_endpoint_end; + + /* static strings from the device */ + const char *product; /* iProduct string, if present */ + const char *manufacturer; /* iManufacturer string, if present */ + const char *serial; /* iSerialNumber string, if present */ + + uint16_t devnum; + + uint8_t speed; /* USB_SPEED_XXX */ +} __aligned(USB_HOST_ALIGN); + +/* + * The following structure is used to extend "struct urb" when we are + * dealing with an isochronous endpoint. It contains information about + * the data offset and data length of an isochronous packet. + * The "actual_length" field is updated before the "complete" + * callback in the "urb" structure is called. + */ +struct usb_iso_packet_descriptor { + uint32_t offset; /* depreciated buffer offset (the + * packets are usually back to back) */ + uint16_t length; /* expected length */ + uint16_t actual_length; + uint16_t status; +}; + +/* + * The following structure holds various information about an USB + * transfer. This structure is used for all kinds of USB transfers. + * + * URB is short for USB Request Block. + */ +struct urb { + TAILQ_ENTRY(urb) bsd_urb_list; + struct cv cv_wait; + + struct usb_device *dev; /* (in) pointer to associated device */ + struct usb_host_endpoint *pipe; /* (in) pipe pointer */ + uint8_t *setup_packet; /* (in) setup packet (control only) */ + uint8_t *bsd_data_ptr; + void *transfer_buffer; /* (in) associated data buffer */ + void *context; /* (in) context for completion */ + usb_complete_t *complete; /* (in) completion routine */ + + uint32_t transfer_buffer_length;/* (in) data buffer length */ + uint32_t actual_length; /* (return) actual transfer length */ + uint32_t bsd_length_rem; + uint32_t timeout; /* FreeBSD specific */ + + uint16_t transfer_flags; /* (in) */ +#define URB_SHORT_NOT_OK 0x0001 /* report short transfers like errors */ +#define URB_ISO_ASAP 0x0002 /* ignore "start_frame" field */ +#define URB_ZERO_PACKET 0x0004 /* the USB transfer ends with a short + * packet */ +#define URB_NO_TRANSFER_DMA_MAP 0x0008 /* "transfer_dma" is valid on submit */ +#define URB_WAIT_WAKEUP 0x0010 /* custom flags */ +#define URB_IS_SLEEPING 0x0020 /* custom flags */ + + uint16_t start_frame; /* (modify) start frame (ISO) */ + uint16_t number_of_packets; /* (in) number of ISO packets */ + uint16_t interval; /* (modify) transfer interval + * (INT/ISO) */ + uint16_t error_count; /* (return) number of ISO errors */ + int16_t status; /* (return) status */ + + uint8_t setup_dma; /* (in) not used on FreeBSD */ + uint8_t transfer_dma; /* (in) not used on FreeBSD */ + uint8_t bsd_isread; + + struct usb_iso_packet_descriptor iso_frame_desc[]; /* (in) ISO ONLY */ +}; + +/* various prototypes */ + +int usb_submit_urb(struct urb *urb, uint16_t mem_flags); +int usb_unlink_urb(struct urb *urb); +int usb_clear_halt(struct usb_device *dev, struct usb_host_endpoint *uhe); +int usb_control_msg(struct usb_device *dev, struct usb_host_endpoint *pipe, uint8_t request, uint8_t requesttype, uint16_t value, uint16_t index, void *data, uint16_t size, uint32_t timeout); +int usb_set_interface(struct usb_device *dev, uint8_t ifnum, uint8_t alternate); +int usb_setup_endpoint(struct usb_device *dev, struct usb_host_endpoint *uhe, uint32_t bufsize); + +struct usb_host_endpoint *usb_find_host_endpoint(struct usb_device *dev, uint8_t type, uint8_t ep); +struct urb *usb_alloc_urb(uint16_t iso_packets, uint16_t mem_flags); +struct usb_host_interface *usb_altnum_to_altsetting(const struct usb_interface *intf, uint8_t alt_index); +struct usb_interface *usb_ifnum_to_if(struct usb_device *dev, uint8_t iface_no); + +void *usb_buffer_alloc(struct usb_device *dev, uint32_t size, uint16_t mem_flags, uint8_t *dma_addr); +void *usb_get_intfdata(struct usb_interface *intf); + +void usb_buffer_free(struct usb_device *dev, uint32_t size, void *addr, uint8_t dma_addr); +void usb_free_urb(struct urb *urb); +void usb_init_urb(struct urb *urb); +void usb_kill_urb(struct urb *urb); +void usb_set_intfdata(struct usb_interface *intf, void *data); +void usb_linux_register(void *arg); +void usb_linux_deregister(void *arg); + +#define interface_to_usbdev(intf) (intf)->linux_udev +#define interface_to_bsddev(intf) (intf)->linux_udev->bsd_udev + +#endif /* _USB_COMPAT_LINUX_H */ diff --git a/sys/dev/usb2/core/usb2_config_td.c b/sys/dev/usb2/core/usb2_config_td.c new file mode 100644 index 000000000000..ac652fd256db --- /dev/null +++ b/sys/dev/usb2/core/usb2_config_td.c @@ -0,0 +1,320 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include + +static void usb2_config_td_sync_cb(struct usb2_config_td_softc *sc, struct usb2_config_td_cc *cc, uint16_t ref); + +static void +usb2_config_td_dispatch(struct usb2_proc_msg *pm) +{ + struct usb2_config_td_item *pi = (void *)pm; + struct usb2_config_td *ctd = pi->p_ctd; + + DPRINTF("\n"); + + (pi->command_func) (ctd->p_softc, (void *)(pi + 1), pi->command_ref); + + if (TAILQ_NEXT(pm, pm_qentry) == NULL) { + /* last command */ + if (ctd->p_end_of_commands) { + (ctd->p_end_of_commands) (ctd->p_softc); + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_config_td_setup + * + * NOTE: the structure pointed to by "ctd" must be zeroed before calling + * this function! + * + * Return values: + * 0: success + * Else: failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_config_td_setup(struct usb2_config_td *ctd, void *priv_sc, + struct mtx *priv_mtx, + usb2_config_td_end_of_commands_t *p_func_eoc, + uint16_t item_size, uint16_t item_count) +{ + struct usb2_config_td_item *pi; + uint16_t n; + + DPRINTF(" size=%u, count=%u \n", item_size, item_count); + + if (item_count >= 256) { + DPRINTFN(0, "too many items!\n"); + return (1); + } + ctd->p_softc = priv_sc; + ctd->p_end_of_commands = p_func_eoc; + ctd->msg_count = (2 * item_count); + ctd->msg_size = + (sizeof(struct usb2_config_td_item) + item_size); + ctd->p_msgs = + malloc(ctd->msg_size * ctd->msg_count, M_USBDEV, M_WAITOK | M_ZERO); + if (ctd->p_msgs == NULL) { + return (1); + } + if (usb2_proc_setup(&ctd->usb2_proc, priv_mtx, USB_PRI_MED)) { + free(ctd->p_msgs, M_USBDEV); + ctd->p_msgs = NULL; + return (1); + } + /* initialise messages */ + pi = USB_ADD_BYTES(ctd->p_msgs, 0); + for (n = 0; n != ctd->msg_count; n++) { + pi->hdr.pm_callback = &usb2_config_td_dispatch; + pi->p_ctd = ctd; + pi = USB_ADD_BYTES(pi, ctd->msg_size); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_config_td_drain + * + * This function will tear down an USB config thread, waiting for the + * currently executing command to return. + * + * NOTE: If the structure pointed to by "ctd" is all zero, + * this function does nothing. + *------------------------------------------------------------------------*/ +void +usb2_config_td_drain(struct usb2_config_td *ctd) +{ + DPRINTF("\n"); + if (ctd->p_msgs) { + usb2_proc_drain(&ctd->usb2_proc); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_config_td_unsetup + * + * NOTE: If the structure pointed to by "ctd" is all zero, + * this function does nothing. + *------------------------------------------------------------------------*/ +void +usb2_config_td_unsetup(struct usb2_config_td *ctd) +{ + DPRINTF("\n"); + + usb2_config_td_drain(ctd); + + if (ctd->p_msgs) { + usb2_proc_unsetup(&ctd->usb2_proc); + free(ctd->p_msgs, M_USBDEV); + ctd->p_msgs = NULL; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_config_td_queue_command + * + * This function will enter a command into the config thread queue for + * execution. The "command_sync" field was previously used to indicate + * the queue count which is now fixed at two elements. If the + * "command_sync" field is equal to "USB2_CONFIG_TD_SYNC" the command + * will be executed synchronously from the config thread. The + * "command_ref" argument is the reference count for the current + * command which is passed on to the "command_post_func" + * function. This parameter can be used to make a command + * unique. "command_pre_func" is called from this function when we + * have the final queue element. "command_post_func" is called from + * the USB config thread when the command reaches the beginning of the + * USB config thread queue. This function must be called locked. + *------------------------------------------------------------------------*/ +void +usb2_config_td_queue_command(struct usb2_config_td *ctd, + usb2_config_td_command_t *command_pre_func, + usb2_config_td_command_t *command_post_func, + uint16_t command_sync, + uint16_t command_ref) +{ + struct usb2_config_td_item *pi; + struct usb2_config_td_item *pi_0; + struct usb2_config_td_item *pi_1; + uint16_t n; + + if (usb2_config_td_is_gone(ctd)) { + DPRINTF("gone\n"); + /* nothing more to do */ + return; + } + DPRINTF("\n"); + + pi = USB_ADD_BYTES(ctd->p_msgs, 0); + for (n = 0;; n += 2) { + if (n == ctd->msg_count) { + /* should not happen */ + panic("%s:%d: out of memory!\n", + __FUNCTION__, __LINE__); + return; + } + if (pi->command_func == NULL) { + /* reserve our entry */ + pi->command_func = command_post_func; + pi->command_ref = command_ref; + pi_0 = pi; + pi = USB_ADD_BYTES(pi, ctd->msg_size); + pi->command_func = command_post_func; + pi->command_ref = command_ref; + pi_1 = pi; + break; + } + if ((pi->command_func == command_post_func) && + (pi->command_ref == command_ref)) { + /* found an entry */ + pi_0 = pi; + pi = USB_ADD_BYTES(pi, ctd->msg_size); + pi_1 = pi; + break; + } + pi = USB_ADD_BYTES(pi, (2 * ctd->msg_size)); + } + + /* + * We have two message structures. One of them will get + * queued: + */ + pi = usb2_proc_msignal(&ctd->usb2_proc, pi_0, pi_1); + + /* + * The job of the post-command function is to finish the command in + * a separate context to allow calls to sleeping functions + * basically. Queue the post command before calling the pre command. + * That way commands queued by the pre command will be queued after + * the current command. + */ + + /* + * The job of the pre-command function is to copy the needed + * configuration to the provided structure and to execute other + * commands that must happen immediately + */ + if (command_pre_func) { + (command_pre_func) (ctd->p_softc, (void *)(pi + 1), command_ref); + } + if (command_sync == USB2_CONFIG_TD_SYNC) { + usb2_proc_mwait(&ctd->usb2_proc, pi_0, pi_1); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_config_td_is_gone + * + * Return values: + * 0: config thread is running + * Else: config thread is gone + *------------------------------------------------------------------------*/ +uint8_t +usb2_config_td_is_gone(struct usb2_config_td *ctd) +{ + return (usb2_proc_is_gone(&ctd->usb2_proc)); +} + +/*------------------------------------------------------------------------* + * usb2_config_td_sleep + * + * NOTE: this function can only be called from the config thread + * + * Return values: + * 0: normal delay + * Else: config thread is gone + *------------------------------------------------------------------------*/ +uint8_t +usb2_config_td_sleep(struct usb2_config_td *ctd, uint32_t timeout) +{ + uint8_t is_gone = usb2_config_td_is_gone(ctd); + + if (is_gone) { + goto done; + } + if (timeout == 0) { + /* + * Zero means no timeout, so avoid that by setting + * timeout to one: + */ + timeout = 1; + } + mtx_unlock(ctd->usb2_proc.up_mtx); + + if (pause("USBWAIT", timeout)) { + /* ignore */ + } + mtx_lock(ctd->usb2_proc.up_mtx); + + is_gone = usb2_config_td_is_gone(ctd); +done: + return (is_gone); +} + +/*------------------------------------------------------------------------* + * usb2_config_td_sync + * + * This function will wait until all commands have been executed on + * the config thread. This function must be called locked and can + * sleep. + * + * Return values: + * 0: success + * Else: config thread is gone + *------------------------------------------------------------------------*/ +uint8_t +usb2_config_td_sync(struct usb2_config_td *ctd) +{ + if (usb2_config_td_is_gone(ctd)) { + return (1); + } + usb2_config_td_queue_command(ctd, NULL, + &usb2_config_td_sync_cb, USB2_CONFIG_TD_SYNC, 0); + + if (usb2_config_td_is_gone(ctd)) { + return (1); + } + return (0); +} + +static void +usb2_config_td_sync_cb(struct usb2_config_td_softc *sc, + struct usb2_config_td_cc *cc, uint16_t ref) +{ + return; +} diff --git a/sys/dev/usb2/core/usb2_config_td.h b/sys/dev/usb2/core/usb2_config_td.h new file mode 100644 index 000000000000..1f7d378b0bf0 --- /dev/null +++ b/sys/dev/usb2/core/usb2_config_td.h @@ -0,0 +1,71 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_CONFIG_TD_H_ +#define _USB2_CONFIG_TD_H_ + +struct usb2_config_td_softc; +struct usb2_config_td_cc; + +#define USB2_CONFIG_TD_SYNC 0xFFFF /* magic value */ + +typedef void (usb2_config_td_command_t)(struct usb2_config_td_softc *sc, struct usb2_config_td_cc *cc, uint16_t reference); +typedef void (usb2_config_td_end_of_commands_t)(struct usb2_config_td_softc *sc); + +/* + * The following structure defines a command that should be executed + * using the USB config thread system. + */ +struct usb2_config_td_item { + struct usb2_proc_msg hdr; + struct usb2_config_td *p_ctd; + usb2_config_td_command_t *command_func; + uint16_t command_ref; +} __aligned(USB_HOST_ALIGN); + +/* + * The following structure defines an USB config thread. + */ +struct usb2_config_td { + struct usb2_process usb2_proc; + struct usb2_config_td_softc *p_softc; + usb2_config_td_end_of_commands_t *p_end_of_commands; + void *p_msgs; + uint16_t msg_size; + uint16_t msg_count; +}; + +/* prototypes */ + +uint8_t usb2_config_td_setup(struct usb2_config_td *ctd, void *priv_sc, struct mtx *priv_mtx, usb2_config_td_end_of_commands_t *p_func_eoc, uint16_t item_size, uint16_t item_count); +void usb2_config_td_drain(struct usb2_config_td *ctd); +void usb2_config_td_unsetup(struct usb2_config_td *ctd); +void usb2_config_td_queue_command(struct usb2_config_td *ctd, usb2_config_td_command_t *pre_func, usb2_config_td_command_t *post_func, uint16_t command_sync, uint16_t command_ref); +uint8_t usb2_config_td_is_gone(struct usb2_config_td *ctd); +uint8_t usb2_config_td_sleep(struct usb2_config_td *ctd, uint32_t timeout); +uint8_t usb2_config_td_sync(struct usb2_config_td *ctd); + +#endif /* _USB2_CONFIG_TD_H_ */ diff --git a/sys/dev/usb2/core/usb2_core.c b/sys/dev/usb2/core/usb2_core.c new file mode 100644 index 000000000000..b96f53c71225 --- /dev/null +++ b/sys/dev/usb2/core/usb2_core.c @@ -0,0 +1,40 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * USB specifications and other documentation can be found at + * http://www.usb.org/developers/docs/ and + * http://www.usb.org/developers/devclass_docs/ + */ + +#include +#include + +MALLOC_DEFINE(M_USB, "USB", "USB"); +MALLOC_DEFINE(M_USBDEV, "USBdev", "USB device"); +MALLOC_DEFINE(M_USBHC, "USBHC", "USB host controller"); + +MODULE_VERSION(usb2_core, 1); diff --git a/sys/dev/usb2/core/usb2_core.h b/sys/dev/usb2/core/usb2_core.h new file mode 100644 index 000000000000..c0cec7a1725e --- /dev/null +++ b/sys/dev/usb2/core/usb2_core.h @@ -0,0 +1,448 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * Including this file is mandatory for all USB related c-files in the + * kernel. + */ + +#ifndef _USB2_CORE_H_ +#define _USB2_CORE_H_ + +/* Default USB configuration */ + +#ifndef USB_NO_POLL +#define USB_NO_POLL 0 +#endif + +#ifndef USB_USE_CONDVAR +#define USB_USE_CONDVAR 0 +#endif + +#ifndef USB_TD_GET_PROC +#define USB_TD_GET_PROC(td) (td)->td_proc +#endif + +#ifndef USB_PROC_GET_GID +#define USB_PROC_GET_GID(td) (td)->p_pgid +#endif + +#ifndef USB_VNOPS_FO_CLOSE +#define USB_VNOPS_FO_CLOSE(fp, td, perr) do { \ + (td)->td_fpop = (fp); \ + *(perr) = vnops.fo_close(fp, td); \ + (td)->td_fpop = NULL; \ +} while (0) +#endif + +#ifndef USB_VNOPS_FO_STAT +#define USB_VNOPS_FO_STAT(fp, sb, cred, td) \ + vnops.fo_stat(fp, sb, cred, td) +#endif + +#ifndef USB_VNOPS_FO_TRUNCATE +#define USB_VNOPS_FO_TRUNCATE(fp, length, cred, td) \ + vnops.fo_truncate(fp, length, cred, td) +#endif + +/* Include files */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "usb2_if.h" +#include "opt_usb.h" +#include "opt_bus.h" + +#define USB_STACK_VERSION 2000 /* 2.0 */ + +#define USB_HOST_ALIGN 8 /* bytes, must be power of two */ + +#define USB_ROOT_HUB_ADDR 1 /* value */ + +#define USB_ISOC_TIME_MAX 128 /* ms */ +#define USB_FS_ISOC_UFRAME_MAX 4 /* exclusive unit */ + +#if (USB_FS_ISOC_UFRAME_MAX > 6) +#error "USB_FS_ISOC_UFRAME_MAX cannot be set higher than 6" +#endif + +#define USB_MAX_FS_ISOC_FRAMES_PER_XFER (120) /* units */ +#define USB_MAX_HS_ISOC_FRAMES_PER_XFER (8*120) /* units */ + +#define USB_MAX_IPACKET 8 /* maximum size of the initial USB + * data packet */ +#ifndef USB_VERBOSE +#define USB_VERBOSE 1 +#endif + +#define USB_HUB_MAX_DEPTH 5 + +/* USB transfer states */ + +#define USB_ST_SETUP 0 +#define USB_ST_TRANSFERRED 1 +#define USB_ST_ERROR 2 + +/* + * The following macro will return the current state of an USB + * transfer like defined by the "USB_ST_XXX" enums. + */ +#define USB_GET_STATE(xfer) ((xfer)->usb2_state) + +/* + * The following macro will tell if an USB transfer is currently + * receiving or transferring data. + */ +#define USB_GET_DATA_ISREAD(xfer) (((xfer)->flags_int.usb2_mode == \ + USB_MODE_DEVICE) ? ((xfer->endpoint & UE_DIR_IN) ? 0 : 1) : \ + ((xfer->endpoint & UE_DIR_IN) ? 1 : 0)) + +/* + * The following macros are used used to convert milliseconds into + * HZ. We use 1024 instead of 1000 milliseconds per second to save a + * full division. + */ +#define USB_MS_HZ 1024 + +#define USB_MS_TO_TICKS(ms) \ + (((uint32_t)((((uint32_t)(ms)) * ((uint32_t)(hz))) + USB_MS_HZ - 1)) / USB_MS_HZ) + +/* macros */ + +#define usb2_callout_init_mtx(c,m,f) callout_init_mtx(&(c)->co,m,f) +#define usb2_callout_reset(c,t,f,d) callout_reset(&(c)->co,t,f,d) +#define usb2_callout_stop(c) callout_stop(&(c)->co) +#define usb2_callout_drain(c) callout_drain(&(c)->co) +#define usb2_callout_pending(c) callout_pending(&(c)->co) + +/* structure prototypes */ + +struct file; +struct usb2_bus; +struct usb2_device; +struct usb2_page; +struct usb2_page_cache; +struct usb2_xfer; +struct usb2_xfer_root; + +/* typedefs */ + +typedef uint8_t usb2_error_t; + +typedef void (usb2_callback_t)(struct usb2_xfer *); + +/* structures */ + +/* + * This structure contains permissions. + */ + +struct usb2_perm { + uint32_t uid; + uint32_t gid; + uint16_t mode; +}; + +/* + * Common queue structure for USB transfers. + */ +struct usb2_xfer_queue { + TAILQ_HEAD(, usb2_xfer) head; + struct usb2_xfer *curr; /* current USB transfer processed */ + void (*command) (struct usb2_xfer_queue *pq); + uint8_t recurse_1:1; + uint8_t recurse_2:1; +}; + +/* + * The following is a wrapper for the callout structure to ease + * porting the code to other platforms. + */ +struct usb2_callout { + struct callout co; +}; + +/* + * The following structure defines a set of USB transfer flags. + */ +struct usb2_xfer_flags { + uint8_t force_short_xfer:1; /* force a short transmit transfer + * last */ + uint8_t short_xfer_ok:1; /* allow short receive transfers */ + uint8_t short_frames_ok:1; /* allow short frames */ + uint8_t pipe_bof:1; /* block pipe on failure */ + uint8_t proxy_buffer:1; /* makes buffer size a factor of + * "max_frame_size" */ + uint8_t ext_buffer:1; /* uses external DMA buffer */ + uint8_t manual_status:1; /* non automatic status stage on + * control transfers */ + uint8_t no_pipe_ok:1; /* set if "USB_ERR_NO_PIPE" error can + * be ignored */ + uint8_t stall_pipe:1; /* set if the endpoint belonging to + * this USB transfer should be stalled + * before starting this transfer! */ +}; + +/* + * The following structure defines a set of internal USB transfer + * flags. + */ +struct usb2_xfer_flags_int { + uint16_t control_rem; /* remainder in bytes */ + + uint8_t open:1; /* set if USB pipe has been opened */ + uint8_t transferring:1; /* set if an USB transfer is in + * progress */ + uint8_t did_dma_delay:1; /* set if we waited for HW DMA */ + uint8_t did_close:1; /* set if we closed the USB transfer */ + uint8_t draining:1; /* set if we are draining an USB + * transfer */ + uint8_t started:1; /* keeps track of started or stopped */ + uint8_t bandwidth_reclaimed:1; + uint8_t control_xfr:1; /* set if control transfer */ + uint8_t control_hdr:1; /* set if control header should be + * sent */ + uint8_t control_act:1; /* set if control transfer is active */ + + uint8_t short_frames_ok:1; /* filtered version */ + uint8_t short_xfer_ok:1; /* filtered version */ + uint8_t bdma_enable:1; /* filtered version (only set if + * hardware supports DMA) */ + uint8_t bdma_no_post_sync:1; /* set if the USB callback wrapper + * should not do the BUS-DMA post sync + * operation */ + uint8_t bdma_setup:1; /* set if BUS-DMA has been setup */ + uint8_t isochronous_xfr:1; /* set if isochronous transfer */ + uint8_t usb2_mode:1; /* shadow copy of "udev->usb2_mode" */ + uint8_t curr_dma_set:1; /* used by USB HC/DC driver */ + uint8_t can_cancel_immed:1; /* set if USB transfer can be + * cancelled immediately */ +}; + +/* + * The following structure defines the symmetric part of an USB config + * structure. + */ +struct usb2_config_sub { + usb2_callback_t *callback; /* USB transfer callback */ + uint32_t bufsize; /* total pipe buffer size in bytes */ + uint32_t frames; /* maximum number of USB frames */ + uint16_t interval; /* interval in milliseconds */ +#define USB_DEFAULT_INTERVAL 0 + uint16_t timeout; /* transfer timeout in milliseconds */ + struct usb2_xfer_flags flags; /* transfer flags */ +}; + +/* + * The following structure define an USB configuration, that basically + * is used when setting up an USB transfer. + */ +struct usb2_config { + struct usb2_config_sub mh; /* parameters for USB_MODE_HOST */ + struct usb2_config_sub md; /* parameters for USB_MODE_DEVICE */ + uint8_t type; /* pipe type */ + uint8_t endpoint; /* pipe number */ + uint8_t direction; /* pipe direction */ + uint8_t ep_index; /* pipe index match to use */ + uint8_t if_index; /* "ifaces" index to use */ +}; + +/* + * The following structure defines an USB transfer. + */ +struct usb2_xfer { + struct usb2_callout timeout_handle; + TAILQ_ENTRY(usb2_xfer) wait_entry; /* used at various places */ + + struct usb2_page_cache *buf_fixup; /* fixup buffer(s) */ + struct usb2_xfer_queue *wait_queue; /* pointer to queue that we + * are waiting on */ + struct usb2_page *dma_page_ptr; + struct usb2_pipe *pipe; /* our USB pipe */ + struct usb2_device *udev; + struct mtx *priv_mtx; /* cannot be changed during operation */ + struct mtx *usb2_mtx; /* used by HC driver */ + struct usb2_xfer_root *usb2_root; /* used by HC driver */ + void *usb2_sc; /* used by HC driver */ + void *qh_start[2]; /* used by HC driver */ + void *td_start[2]; /* used by HC driver */ + void *td_transfer_first; /* used by HC driver */ + void *td_transfer_last; /* used by HC driver */ + void *td_transfer_cache; /* used by HC driver */ + void *priv_sc; /* device driver data pointer 1 */ + void *priv_fifo; /* device driver data pointer 2 */ + void *local_buffer; + uint32_t *frlengths; + struct usb2_page_cache *frbuffers; + usb2_callback_t *callback; + + uint32_t max_usb2_frame_size; + uint32_t max_data_length; + uint32_t sumlen; /* sum of all lengths in bytes */ + uint32_t actlen; /* actual length in bytes */ + uint32_t timeout; /* milliseconds */ +#define USB_NO_TIMEOUT 0 +#define USB_DEFAULT_TIMEOUT 5000 /* 5000 ms = 5 seconds */ + + uint32_t max_frame_count; /* initial value of "nframes" after + * setup */ + uint32_t nframes; /* number of USB frames to transfer */ + uint32_t aframes; /* actual number of USB frames + * transferred */ + + uint16_t max_packet_size; + uint16_t max_frame_size; + uint16_t qh_pos; + uint16_t isoc_time_complete; /* in ms */ + uint16_t interval; /* milliseconds */ + + uint8_t address; /* physical USB address */ + uint8_t endpoint; /* physical USB endpoint */ + uint8_t max_packet_count; + uint8_t usb2_smask; + uint8_t usb2_cmask; + uint8_t usb2_uframe; + uint8_t usb2_state; + + usb2_error_t error; + + struct usb2_xfer_flags flags; + struct usb2_xfer_flags_int flags_int; +}; + +/* + * The following structure keeps information that is used to match + * against an array of "usb2_device_id" elements. + */ +struct usb2_lookup_info { + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t bIfaceIndex; + uint8_t bIfaceNum; + uint8_t bConfigIndex; + uint8_t bConfigNum; +}; + +/* Structure used by probe and attach */ + +struct usb2_attach_arg { + struct usb2_lookup_info info; + device_t temp_dev; /* for internal use */ + const void *driver_info; /* for internal use */ + struct usb2_device *device; /* current device */ + struct usb2_interface *iface; /* current interface */ + uint8_t usb2_mode; /* see USB_MODE_XXX */ + uint8_t port; + uint8_t use_generic; /* hint for generic drivers */ +}; + +/* Structure used when referring an USB device */ + +struct usb2_location { + struct usb2_bus *bus; + struct usb2_device *udev; + struct usb2_interface *iface; + struct usb2_fifo *rxfifo; + struct usb2_fifo *txfifo; + uint32_t devloc; /* original devloc */ + uint16_t bus_index; + uint8_t dev_index; + uint8_t iface_index; + uint8_t ep_index; + uint8_t is_read; + uint8_t is_write; + uint8_t is_uref; +}; + +/* external variables */ + +MALLOC_DECLARE(M_USB); +MALLOC_DECLARE(M_USBDEV); +MALLOC_DECLARE(M_USBHC); + +extern struct mtx usb2_ref_lock; + +/* typedefs */ + +typedef struct malloc_type *usb2_malloc_type; + +/* prototypes */ + +const char *usb2_errstr(usb2_error_t error); +struct usb2_config_descriptor *usb2_get_config_descriptor(struct usb2_device *udev); +struct usb2_device_descriptor *usb2_get_device_descriptor(struct usb2_device *udev); +struct usb2_interface *usb2_get_iface(struct usb2_device *udev, uint8_t iface_index); +struct usb2_interface_descriptor *usb2_get_interface_descriptor(struct usb2_interface *iface); +uint8_t usb2_clear_stall_callback(struct usb2_xfer *xfer1, struct usb2_xfer *xfer2); +uint8_t usb2_get_interface_altindex(struct usb2_interface *iface); +usb2_error_t usb2_set_alt_interface_index(struct usb2_device *udev, uint8_t iface_index, uint8_t alt_index); +uint8_t usb2_get_speed(struct usb2_device *udev); +usb2_error_t usb2_transfer_setup(struct usb2_device *udev, const uint8_t *ifaces, struct usb2_xfer **pxfer, const struct usb2_config *setup_start, uint16_t n_setup, void *priv_sc, struct mtx *priv_mtx); +void usb2_set_frame_data(struct usb2_xfer *xfer, void *ptr, uint32_t frindex); +void usb2_set_frame_offset(struct usb2_xfer *xfer, uint32_t offset, uint32_t frindex); +void usb2_start_hardware(struct usb2_xfer *xfer); +void usb2_transfer_clear_stall(struct usb2_xfer *xfer); +void usb2_transfer_drain(struct usb2_xfer *xfer); +void usb2_transfer_set_stall(struct usb2_xfer *xfer); +void usb2_transfer_start(struct usb2_xfer *xfer); +void usb2_transfer_stop(struct usb2_xfer *xfer); +void usb2_transfer_unsetup(struct usb2_xfer **pxfer, uint16_t n_setup); +usb2_error_t usb2_ref_device(struct file *fp, struct usb2_location *ploc, uint32_t devloc); +void usb2_unref_device(struct usb2_location *ploc); +void usb2_set_parent_iface(struct usb2_device *udev, uint8_t iface_index, uint8_t parent_index); +void usb2_set_iface_perm(struct usb2_device *udev, uint8_t iface_index, uint32_t uid, uint32_t gid, uint16_t mode); +uint8_t usb2_get_bus_index(struct usb2_device *udev); +uint8_t usb2_get_device_index(struct usb2_device *udev); + +#endif /* _USB2_CORE_H_ */ diff --git a/sys/dev/usb2/core/usb2_debug.c b/sys/dev/usb2/core/usb2_debug.c new file mode 100644 index 000000000000..7969662387c7 --- /dev/null +++ b/sys/dev/usb2/core/usb2_debug.c @@ -0,0 +1,153 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +#include +#include +#include +#include + +/* + * Define this unconditionally in case a kernel module is loaded that + * has been compiled with debugging options. + */ +int usb2_debug = 0; + +SYSCTL_NODE(_hw, OID_AUTO, usb2, CTLFLAG_RW, 0, "USB debugging"); +SYSCTL_INT(_hw_usb2, OID_AUTO, debug, CTLFLAG_RW, + &usb2_debug, 0, "Debug level"); + +/*------------------------------------------------------------------------* + * usb2_dump_iface + * + * This function dumps information about an USB interface. + *------------------------------------------------------------------------*/ +void +usb2_dump_iface(struct usb2_interface *iface) +{ + printf("usb2_dump_iface: iface=%p\n", iface); + if (iface == NULL) { + return; + } + printf(" iface=%p idesc=%p altindex=%d\n", + iface, iface->idesc, iface->alt_index); + return; +} + +/*------------------------------------------------------------------------* + * usb2_dump_device + * + * This function dumps information about an USB device. + *------------------------------------------------------------------------*/ +void +usb2_dump_device(struct usb2_device *udev) +{ + printf("usb2_dump_device: dev=%p\n", udev); + if (udev == NULL) { + return; + } + printf(" bus=%p \n" + " address=%d config=%d depth=%d speed=%d self_powered=%d\n" + " power=%d langid=%d\n", + udev->bus, + udev->address, udev->curr_config_no, udev->depth, udev->speed, + udev->flags.self_powered, udev->power, udev->langid); + return; +} + +/*------------------------------------------------------------------------* + * usb2_dump_queue + * + * This function dumps the USB transfer that are queued up on an USB pipe. + *------------------------------------------------------------------------*/ +void +usb2_dump_queue(struct usb2_pipe *pipe) +{ + struct usb2_xfer *xfer; + + printf("usb2_dump_queue: pipe=%p xfer: ", pipe); + TAILQ_FOREACH(xfer, &pipe->pipe_q.head, wait_entry) { + printf(" %p", xfer); + } + printf("\n"); + return; +} + +/*------------------------------------------------------------------------* + * usb2_dump_pipe + * + * This function dumps information about an USB pipe. + *------------------------------------------------------------------------*/ +void +usb2_dump_pipe(struct usb2_pipe *pipe) +{ + if (pipe) { + printf("usb2_dump_pipe: pipe=%p", pipe); + + printf(" edesc=%p isoc_next=%d toggle_next=%d", + pipe->edesc, pipe->isoc_next, pipe->toggle_next); + + if (pipe->edesc) { + printf(" bEndpointAddress=0x%02x", + pipe->edesc->bEndpointAddress); + } + printf("\n"); + usb2_dump_queue(pipe); + } else { + printf("usb2_dump_pipe: pipe=NULL\n"); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_dump_xfer + * + * This function dumps information about an USB transfer. + *------------------------------------------------------------------------*/ +void +usb2_dump_xfer(struct usb2_xfer *xfer) +{ + printf("usb2_dump_xfer: xfer=%p\n", xfer); + if (xfer == NULL) { + return; + } + if (xfer->pipe == NULL) { + printf("xfer %p: pipe=NULL\n", + xfer); + return; + } + printf("xfer %p: udev=%p vid=0x%04x pid=0x%04x addr=%d " + "pipe=%p ep=0x%02x attr=0x%02x\n", + xfer, xfer->udev, + UGETW(xfer->udev->ddesc.idVendor), + UGETW(xfer->udev->ddesc.idProduct), + xfer->udev->address, xfer->pipe, + xfer->pipe->edesc->bEndpointAddress, + xfer->pipe->edesc->bmAttributes); + return; +} diff --git a/sys/dev/usb2/core/usb2_debug.h b/sys/dev/usb2/core/usb2_debug.h new file mode 100644 index 000000000000..92dcbd5b9c2c --- /dev/null +++ b/sys/dev/usb2/core/usb2_debug.h @@ -0,0 +1,70 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* This file contains various factored out debug macros. */ + +#ifndef _USB2_DEBUG_H_ +#define _USB2_DEBUG_H_ + +/* Declare parent SYSCTL USB node. */ +SYSCTL_DECL(_hw_usb2); + +/* Declare global USB debug variable. */ +extern int usb2_debug; + +/* Force debugging until further */ +#ifndef USB_DEBUG +#define USB_DEBUG 1 +#endif + +/* Check if USB debugging is enabled. */ +#ifdef USB_DEBUG_VAR +#if (USB_DEBUG != 0) +#define DPRINTFN(n,fmt,...) do { \ + if ((USB_DEBUG_VAR) >= (n)) { \ + printf("%s:%u: " fmt, \ + __FUNCTION__, __LINE__,## __VA_ARGS__); \ + } \ +} while (0) +#define DPRINTF(...) DPRINTFN(1, __VA_ARGS__) +#else +#define DPRINTF(...) do { } while (0) +#define DPRINTFN(...) do { } while (0) +#endif +#endif + +struct usb2_interface; +struct usb2_device; +struct usb2_pipe; +struct usb2_xfer; + +void usb2_dump_iface(struct usb2_interface *iface); +void usb2_dump_device(struct usb2_device *udev); +void usb2_dump_queue(struct usb2_pipe *pipe); +void usb2_dump_pipe(struct usb2_pipe *pipe); +void usb2_dump_xfer(struct usb2_xfer *xfer); + +#endif /* _USB2_DEBUG_H_ */ diff --git a/sys/dev/usb2/core/usb2_dev.c b/sys/dev/usb2/core/usb2_dev.c new file mode 100644 index 000000000000..1afe83e1f9ee --- /dev/null +++ b/sys/dev/usb2/core/usb2_dev.c @@ -0,0 +1,2786 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2006-2008 Hans Petter Selasky. 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. + * + * + * usb2_dev.c - An abstraction layer for creating devices under /dev/... + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_fifo_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#if USB_DEBUG +static int usb2_fifo_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, dev, CTLFLAG_RW, 0, "USB device"); +SYSCTL_INT(_hw_usb2_dev, OID_AUTO, debug, CTLFLAG_RW, + &usb2_fifo_debug, 0, "Debug Level"); +#endif + +#if ((__FreeBSD_version >= 700001) || (__FreeBSD_version == 0) || \ + ((__FreeBSD_version >= 600034) && (__FreeBSD_version < 700000))) +#define USB_UCRED struct ucred *ucred, +#else +#define USB_UCRED +#endif + +/* prototypes */ + +static uint32_t usb2_path_convert_one(const char **pp); +static uint32_t usb2_path_convert(const char *path); +static int usb2_check_access(int fflags, struct usb2_perm *puser); +static int usb2_fifo_open(struct usb2_fifo *f, struct file *fp, struct thread *td, int fflags); +static void usb2_fifo_close(struct usb2_fifo *f, struct thread *td, int fflags); +static void usb2_dev_init(void *arg); +static void usb2_dev_init_post(void *arg); +static void usb2_dev_uninit(void *arg); +static int usb2_fifo_uiomove(struct usb2_fifo *f, void *cp, int n, struct uio *uio); +static void usb2_fifo_check_methods(struct usb2_fifo_methods *pm); +static void usb2_clone(void *arg, USB_UCRED char *name, int namelen, struct cdev **dev); +static struct usb2_fifo *usb2_fifo_alloc(void); +static struct usb2_pipe *usb2_dev_get_pipe(struct usb2_device *udev, uint8_t iface_index, uint8_t ep_index, uint8_t dir); + +static d_fdopen_t usb2_fdopen; +static d_close_t usb2_close; +static d_ioctl_t usb2_ioctl; + +static fo_rdwr_t usb2_read_f; +static fo_rdwr_t usb2_write_f; + +#if __FreeBSD_version > 800009 +static fo_truncate_t usb2_truncate_f; + +#endif +static fo_ioctl_t usb2_ioctl_f; +static fo_poll_t usb2_poll_f; +static fo_kqfilter_t usb2_kqfilter_f; +static fo_stat_t usb2_stat_f; +static fo_close_t usb2_close_f; + +static usb2_fifo_open_t usb2_fifo_dummy_open; +static usb2_fifo_close_t usb2_fifo_dummy_close; +static usb2_fifo_ioctl_t usb2_fifo_dummy_ioctl; +static usb2_fifo_cmd_t usb2_fifo_dummy_cmd; + +static struct usb2_perm usb2_perm = { + .uid = UID_ROOT, + .gid = GID_OPERATOR, + .mode = 0660, +}; + +static struct cdevsw usb2_devsw = { + .d_version = D_VERSION, + .d_fdopen = usb2_fdopen, + .d_close = usb2_close, + .d_ioctl = usb2_ioctl, + .d_name = "usb", + .d_flags = D_TRACKCLOSE, +}; + +static struct fileops usb2_ops_f = { + .fo_read = usb2_read_f, + .fo_write = usb2_write_f, +#if __FreeBSD_version > 800009 + .fo_truncate = usb2_truncate_f, +#endif + .fo_ioctl = usb2_ioctl_f, + .fo_poll = usb2_poll_f, + .fo_kqfilter = usb2_kqfilter_f, + .fo_stat = usb2_stat_f, + .fo_close = usb2_close_f, + .fo_flags = DFLAG_PASSABLE | DFLAG_SEEKABLE +}; + +static const dev_clone_fn usb2_clone_ptr = &usb2_clone; +static struct cdev *usb2_dev; +static uint32_t usb2_last_devloc = 0 - 1; +static eventhandler_tag usb2_clone_tag; +static void *usb2_old_f_data; +static struct fileops *usb2_old_f_ops; +static TAILQ_HEAD(, usb2_symlink) usb2_sym_head; +static struct sx usb2_sym_lock; + +struct mtx usb2_ref_lock; + +static uint32_t +usb2_path_convert_one(const char **pp) +{ + const char *ptr; + uint32_t temp = 0; + + ptr = *pp; + + while ((*ptr >= '0') && (*ptr <= '9')) { + temp *= 10; + temp += (*ptr - '0'); + if (temp >= 1000000) { + /* catch overflow early */ + return (0 - 1); + } + ptr++; + } + + if (*ptr == '.') { + /* skip dot */ + ptr++; + } + *pp = ptr; + + return (temp); +} + +/*------------------------------------------------------------------------* + * usb2_path_convert + * + * Path format: "/dev/usb..." + * + * Returns: Path converted into numerical format. + *------------------------------------------------------------------------*/ +static uint32_t +usb2_path_convert(const char *path) +{ + uint32_t temp; + uint32_t devloc; + + devloc = 0; + + temp = usb2_path_convert_one(&path); + + if (temp >= USB_BUS_MAX) { + return (0 - 1); + } + devloc += temp; + + temp = usb2_path_convert_one(&path); + + if (temp >= USB_DEV_MAX) { + return (0 - 1); + } + devloc += (temp * USB_BUS_MAX); + + temp = usb2_path_convert_one(&path); + + if (temp >= USB_IFACE_MAX) { + return (0 - 1); + } + devloc += (temp * USB_DEV_MAX * USB_BUS_MAX); + + temp = usb2_path_convert_one(&path); + + if (temp >= ((USB_FIFO_MAX / 2) + (USB_EP_MAX / 2))) { + return (0 - 1); + } + devloc += (temp * USB_IFACE_MAX * USB_DEV_MAX * USB_BUS_MAX); + + return (devloc); +} + +/*------------------------------------------------------------------------* + * usb2_set_iface_perm + * + * This function will set the interface permissions. + *------------------------------------------------------------------------*/ +void +usb2_set_iface_perm(struct usb2_device *udev, uint8_t iface_index, + uint32_t uid, uint32_t gid, uint16_t mode) +{ + struct usb2_interface *iface; + + iface = usb2_get_iface(udev, iface_index); + if (iface && iface->idesc) { + mtx_lock(&usb2_ref_lock); + iface->perm.uid = uid; + iface->perm.gid = gid; + iface->perm.mode = mode; + mtx_unlock(&usb2_ref_lock); + + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_perm + * + * This function will set the permissions at the given level. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +static int +usb2_set_perm(struct usb2_dev_perm *psrc, uint8_t level) +{ + struct usb2_location loc; + struct usb2_perm *pdst; + uint32_t devloc; + int error; + + /* check if the current thread can change USB permissions. */ + error = priv_check(curthread, PRIV_ROOT); + if (error) { + return (error); + } + /* range check device location */ + if ((psrc->bus_index >= USB_BUS_MAX) || + (psrc->dev_index >= USB_DEV_MAX) || + (psrc->iface_index >= USB_IFACE_MAX)) { + return (EINVAL); + } + if (level == 1) + devloc = USB_BUS_MAX; /* use root-HUB to access bus */ + else + devloc = 0; + switch (level) { + case 3: + devloc += psrc->iface_index * + USB_DEV_MAX * USB_BUS_MAX; + /* FALLTHROUGH */ + case 2: + devloc += psrc->dev_index * + USB_BUS_MAX; + /* FALLTHROUGH */ + case 1: + devloc += psrc->bus_index; + break; + default: + break; + } + + if ((level > 0) && (level < 4)) { + error = usb2_ref_device(NULL, &loc, devloc); + if (error) { + return (error); + } + } + switch (level) { + case 3: + if (loc.iface == NULL) { + usb2_unref_device(&loc); + return (EINVAL); + } + pdst = &loc.iface->perm; + break; + case 2: + pdst = &loc.udev->perm; + break; + case 1: + pdst = &loc.bus->perm; + break; + default: + pdst = &usb2_perm; + break; + } + + /* all permissions are protected by "usb2_ref_lock" */ + mtx_lock(&usb2_ref_lock); + pdst->uid = psrc->user_id; + pdst->gid = psrc->group_id; + pdst->mode = psrc->mode; + mtx_unlock(&usb2_ref_lock); + + if ((level > 0) && (level < 4)) { + usb2_unref_device(&loc); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_get_perm + * + * This function will get the permissions at the given level. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +static int +usb2_get_perm(struct usb2_dev_perm *pdst, uint8_t level) +{ + struct usb2_location loc; + struct usb2_perm *psrc; + uint32_t devloc; + int error; + + if ((pdst->bus_index >= USB_BUS_MAX) || + (pdst->dev_index >= USB_DEV_MAX) || + (pdst->iface_index >= USB_IFACE_MAX)) { + return (EINVAL); + } + if (level == 1) + devloc = USB_BUS_MAX; /* use root-HUB to access bus */ + else + devloc = 0; + switch (level) { + case 3: + devloc += pdst->iface_index * + USB_DEV_MAX * USB_BUS_MAX; + /* FALLTHROUGH */ + case 2: + devloc += pdst->dev_index * + USB_BUS_MAX; + /* FALLTHROUGH */ + case 1: + devloc += pdst->bus_index; + break; + default: + break; + } + + if ((level > 0) && (level < 4)) { + error = usb2_ref_device(NULL, &loc, devloc); + if (error) { + return (error); + } + } + switch (level) { + case 3: + if (loc.iface == NULL) { + usb2_unref_device(&loc); + return (EINVAL); + } + psrc = &loc.iface->perm; + break; + case 2: + psrc = &loc.udev->perm; + break; + case 1: + psrc = &loc.bus->perm; + break; + default: + psrc = &usb2_perm; + break; + } + + /* all permissions are protected by "usb2_ref_lock" */ + mtx_lock(&usb2_ref_lock); + if (psrc->mode != 0) { + pdst->user_id = psrc->uid; + pdst->group_id = psrc->gid; + pdst->mode = psrc->mode; + } else { + /* access entry at this level and location is not active */ + pdst->user_id = 0; + pdst->group_id = 0; + pdst->mode = 0; + } + mtx_unlock(&usb2_ref_lock); + + if ((level > 0) && (level < 4)) { + usb2_unref_device(&loc); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_check_access + * + * This function will verify the given access information. + * + * Return values: + * 0: Access granted. + * Else: No access granted. + *------------------------------------------------------------------------*/ +static int +usb2_check_access(int fflags, struct usb2_perm *puser) +{ + mode_t accmode; + + if ((fflags & (FWRITE | FREAD)) && (puser->mode != 0)) { + /* continue */ + } else { + return (EPERM); /* no access */ + } + + accmode = 0; + if (fflags & FWRITE) + accmode |= VWRITE; + if (fflags & FREAD) + accmode |= VREAD; + + return (vaccess(VCHR, puser->mode, puser->uid, + puser->gid, accmode, curthread->td_ucred, NULL)); +} + +/*------------------------------------------------------------------------* + * usb2_ref_device + * + * This function is used to atomically refer an USB device by its + * device location. If this function returns success the USB device + * will not dissappear until the USB device is unreferenced. + * + * Return values: + * 0: Success, refcount incremented on the given USB device. + * Else: Failure. + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_ref_device(struct file *fp, struct usb2_location *ploc, uint32_t devloc) +{ + struct usb2_fifo **ppf; + struct usb2_fifo *f; + int fflags; + uint8_t need_uref; + + if (fp) { + /* check if we need uref hint */ + need_uref = devloc ? 0 : 1; + /* get devloc - already verified */ + devloc = USB_P2U(fp->f_data); + /* get file flags */ + fflags = fp->f_flag; + /* only ref FIFO */ + ploc->is_uref = 0; + /* devloc should be valid */ + } else { + /* we need uref */ + need_uref = 1; + /* only ref device */ + fflags = 0; + /* search for FIFO */ + ploc->is_uref = 1; + /* check "devloc" */ + if (devloc >= (USB_BUS_MAX * USB_DEV_MAX * + USB_IFACE_MAX * ((USB_EP_MAX / 2) + (USB_FIFO_MAX / 2)))) { + return (USB_ERR_INVAL); + } + } + + /* store device location */ + ploc->devloc = devloc; + ploc->bus_index = devloc % USB_BUS_MAX; + ploc->dev_index = (devloc / USB_BUS_MAX) % USB_DEV_MAX; + ploc->iface_index = (devloc / (USB_BUS_MAX * + USB_DEV_MAX)) % USB_IFACE_MAX; + ploc->ep_index = (devloc / (USB_BUS_MAX * USB_DEV_MAX * + USB_IFACE_MAX)); + + mtx_lock(&usb2_ref_lock); + ploc->bus = devclass_get_softc(usb2_devclass_ptr, ploc->bus_index); + if (ploc->bus == NULL) { + DPRINTFN(2, "no bus at %u\n", ploc->bus_index); + goto error; + } + if (ploc->dev_index >= ploc->bus->devices_max) { + DPRINTFN(2, "invalid dev index, %u\n", ploc->dev_index); + goto error; + } + ploc->udev = ploc->bus->devices[ploc->dev_index]; + if (ploc->udev == NULL) { + DPRINTFN(2, "no device at %u\n", ploc->dev_index); + goto error; + } + if (ploc->udev->refcount == USB_DEV_REF_MAX) { + DPRINTFN(2, "no dev ref\n"); + goto error; + } + ploc->iface = usb2_get_iface(ploc->udev, ploc->iface_index); + if (ploc->ep_index != 0) { + /* non control endpoint - we need an interface */ + if (ploc->iface == NULL) { + DPRINTFN(2, "no iface\n"); + goto error; + } + if (ploc->iface->idesc == NULL) { + DPRINTFN(2, "no idesc\n"); + goto error; + } + } + /* check if we are doing an open */ + if (fp == NULL) { + /* set defaults */ + ploc->txfifo = NULL; + ploc->rxfifo = NULL; + ploc->is_write = 0; + ploc->is_read = 0; + } else { + /* check for write */ + if (fflags & FWRITE) { + ppf = ploc->udev->fifo; + f = ppf[ploc->ep_index + USB_FIFO_TX]; + ploc->txfifo = f; + ploc->is_write = 1; /* ref */ + if ((f == NULL) || + (f->refcount == USB_FIFO_REF_MAX) || + (f->curr_file != fp)) { + goto error; + } + } else { + ploc->txfifo = NULL; + ploc->is_write = 0; /* no ref */ + } + + /* check for read */ + if (fflags & FREAD) { + ppf = ploc->udev->fifo; + f = ppf[ploc->ep_index + USB_FIFO_RX]; + ploc->rxfifo = f; + ploc->is_read = 1; /* ref */ + if ((f == NULL) || + (f->refcount == USB_FIFO_REF_MAX) || + (f->curr_file != fp)) { + goto error; + } + } else { + ploc->rxfifo = NULL; + ploc->is_read = 0; /* no ref */ + } + } + + /* when everything is OK we increment the refcounts */ + if (ploc->is_write) { + DPRINTFN(2, "ref write\n"); + ploc->txfifo->refcount++; + if (ploc->txfifo->flag_no_uref == 0) { + /* we need extra locking */ + ploc->is_uref = 1; + } + } + if (ploc->is_read) { + DPRINTFN(2, "ref read\n"); + ploc->rxfifo->refcount++; + if (ploc->rxfifo->flag_no_uref == 0) { + /* we need extra locking */ + ploc->is_uref = 1; + } + } + if (ploc->is_uref) { + if (need_uref) { + DPRINTFN(2, "ref udev - needed\n"); + ploc->udev->refcount++; + } else { + DPRINTFN(2, "ref udev - not needed\n"); + ploc->is_uref = 0; + } + } + mtx_unlock(&usb2_ref_lock); + + if (ploc->is_uref) { + /* + * We are about to alter the bus-state. Apply the + * required locks. + */ + sx_xlock(ploc->udev->default_sx + 1); + mtx_lock(&Giant); /* XXX */ + } + return (0); + +error: + mtx_unlock(&usb2_ref_lock); + DPRINTFN(2, "fail\n"); + return (USB_ERR_INVAL); +} + +/*------------------------------------------------------------------------* + * usb2_unref_device + * + * This function will release the reference count by one unit for the + * given USB device. + *------------------------------------------------------------------------*/ +void +usb2_unref_device(struct usb2_location *ploc) +{ + if (ploc->is_uref) { + mtx_unlock(&Giant); /* XXX */ + sx_unlock(ploc->udev->default_sx + 1); + } + mtx_lock(&usb2_ref_lock); + if (ploc->is_read) { + if (--(ploc->rxfifo->refcount) == 0) { + usb2_cv_signal(&ploc->rxfifo->cv_drain); + } + } + if (ploc->is_write) { + if (--(ploc->txfifo->refcount) == 0) { + usb2_cv_signal(&ploc->txfifo->cv_drain); + } + } + if (ploc->is_uref) { + if (--(ploc->udev->refcount) == 0) { + usb2_cv_signal(ploc->udev->default_cv + 1); + } + } + mtx_unlock(&usb2_ref_lock); + return; +} + +static struct usb2_fifo * +usb2_fifo_alloc(void) +{ + struct usb2_fifo *f; + + f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO); + if (f) { + usb2_cv_init(&f->cv_io, "FIFO-IO"); + usb2_cv_init(&f->cv_drain, "FIFO-DRAIN"); + f->refcount = 1; + } + return (f); +} + +/*------------------------------------------------------------------------* + * usb2_fifo_create + *------------------------------------------------------------------------*/ +static int +usb2_fifo_create(struct usb2_location *ploc, uint32_t *pdevloc, int fflags) +{ + struct usb2_device *udev = ploc->udev; + struct usb2_fifo *f; + struct usb2_pipe *pipe; + uint8_t iface_index = ploc->iface_index; + uint8_t dev_ep_index = ploc->ep_index; + uint8_t n; + uint8_t is_tx; + uint8_t is_rx; + uint8_t no_null; + uint8_t is_busy; + + is_tx = (fflags & FWRITE) ? 1 : 0; + is_rx = (fflags & FREAD) ? 1 : 0; + no_null = 1; + is_busy = 0; + + /* search for a free FIFO slot */ + + for (n = 0;; n += 2) { + + if (n == USB_FIFO_MAX) { + if (no_null) { + no_null = 0; + n = 0; + } else { + /* end of FIFOs reached */ + return (ENOMEM); + } + } + /* Check for TX FIFO */ + if (is_tx) { + f = udev->fifo[n + USB_FIFO_TX]; + if (f != NULL) { + if (f->dev_ep_index != dev_ep_index) { + /* wrong endpoint index */ + continue; + } + if ((dev_ep_index != 0) && + (f->iface_index != iface_index)) { + /* wrong interface index */ + continue; + } + if (f->curr_file != NULL) { + /* FIFO is opened */ + is_busy = 1; + continue; + } + } else if (no_null) { + continue; + } + } + /* Check for RX FIFO */ + if (is_rx) { + f = udev->fifo[n + USB_FIFO_RX]; + if (f != NULL) { + if (f->dev_ep_index != dev_ep_index) { + /* wrong endpoint index */ + continue; + } + if ((dev_ep_index != 0) && + (f->iface_index != iface_index)) { + /* wrong interface index */ + continue; + } + if (f->curr_file != NULL) { + /* FIFO is opened */ + is_busy = 1; + continue; + } + } else if (no_null) { + continue; + } + } + break; + } + + if (no_null == 0) { + if (dev_ep_index >= (USB_EP_MAX / 2)) { + /* we don't create any endpoints in this range */ + return (is_busy ? EBUSY : EINVAL); + } + } + /* Check TX FIFO */ + if (is_tx && + (udev->fifo[n + USB_FIFO_TX] == NULL)) { + pipe = usb2_dev_get_pipe(udev, + iface_index, dev_ep_index, USB_FIFO_TX); + if (pipe == NULL) { + return (EINVAL); + } + f = usb2_fifo_alloc(); + if (f == NULL) { + return (ENOMEM); + } + /* update some fields */ + f->fifo_index = n + USB_FIFO_TX; + f->dev_ep_index = dev_ep_index; + f->priv_mtx = udev->default_mtx; + f->priv_sc0 = pipe; + f->methods = &usb2_ugen_methods; + f->iface_index = iface_index; + f->udev = udev; + if (dev_ep_index != 0) { + f->flag_no_uref = 1; + } + mtx_lock(&usb2_ref_lock); + udev->fifo[n + USB_FIFO_TX] = f; + mtx_unlock(&usb2_ref_lock); + } + /* Check RX FIFO */ + if (is_rx && + (udev->fifo[n + USB_FIFO_RX] == NULL)) { + + pipe = usb2_dev_get_pipe(udev, + iface_index, dev_ep_index, USB_FIFO_RX); + if (pipe == NULL) { + return (EINVAL); + } + f = usb2_fifo_alloc(); + if (f == NULL) { + return (ENOMEM); + } + /* update some fields */ + f->fifo_index = n + USB_FIFO_RX; + f->dev_ep_index = dev_ep_index; + f->priv_mtx = udev->default_mtx; + f->priv_sc0 = pipe; + f->methods = &usb2_ugen_methods; + f->iface_index = iface_index; + f->udev = udev; + if (dev_ep_index != 0) { + f->flag_no_uref = 1; + } + mtx_lock(&usb2_ref_lock); + udev->fifo[n + USB_FIFO_RX] = f; + mtx_unlock(&usb2_ref_lock); + } + if (is_tx) { + ploc->txfifo = udev->fifo[n + USB_FIFO_TX]; + } + if (is_rx) { + ploc->rxfifo = udev->fifo[n + USB_FIFO_RX]; + } + /* replace endpoint index by FIFO index */ + + (*pdevloc) %= (USB_BUS_MAX * USB_DEV_MAX * USB_IFACE_MAX); + (*pdevloc) += (USB_BUS_MAX * USB_DEV_MAX * USB_IFACE_MAX) * n; + + /* complete */ + + return (0); +} + +void +usb2_fifo_free(struct usb2_fifo *f) +{ + uint8_t n; + + if (f == NULL) { + /* be NULL safe */ + return; + } + /* destroy symlink devices, if any */ + for (n = 0; n != 2; n++) { + if (f->symlink[n]) { + usb2_free_symlink(f->symlink[n]); + f->symlink[n] = NULL; + } + } + mtx_lock(&usb2_ref_lock); + + /* delink ourselves to stop calls from userland */ + if ((f->fifo_index < USB_FIFO_MAX) && + (f->udev != NULL) && + (f->udev->fifo[f->fifo_index] == f)) { + f->udev->fifo[f->fifo_index] = NULL; + } else { + DPRINTFN(0, "USB FIFO %p has not been linked!\n", f); + } + + /* decrease refcount */ + f->refcount--; + /* prevent any write flush */ + f->flag_iserror = 1; + /* need to wait until all callers have exited */ + while (f->refcount != 0) { + mtx_unlock(&usb2_ref_lock); /* avoid LOR */ + mtx_lock(f->priv_mtx); + /* get I/O thread out of any sleep state */ + if (f->flag_sleeping) { + f->flag_sleeping = 0; + usb2_cv_broadcast(&f->cv_io); + } + mtx_unlock(f->priv_mtx); + mtx_lock(&usb2_ref_lock); + + /* wait for sync */ + usb2_cv_wait(&f->cv_drain, &usb2_ref_lock); + } + mtx_unlock(&usb2_ref_lock); + + /* take care of closing the device here, if any */ + usb2_fifo_close(f, curthread, 0); + + usb2_cv_destroy(&f->cv_io); + usb2_cv_destroy(&f->cv_drain); + + free(f, M_USBDEV); + return; +} + +static struct usb2_pipe * +usb2_dev_get_pipe(struct usb2_device *udev, + uint8_t iface_index, uint8_t ep_index, uint8_t dir) +{ + struct usb2_pipe *pipe; + uint8_t ep_dir; + + if (ep_index == 0) { + pipe = &udev->default_pipe; + } else { + if (dir == USB_FIFO_RX) { + if (udev->flags.usb2_mode == USB_MODE_HOST) { + ep_dir = UE_DIR_IN; + } else { + ep_dir = UE_DIR_OUT; + } + } else { + if (udev->flags.usb2_mode == USB_MODE_HOST) { + ep_dir = UE_DIR_OUT; + } else { + ep_dir = UE_DIR_IN; + } + } + pipe = usb2_get_pipe_by_addr(udev, ep_index | ep_dir); + } + + if (pipe == NULL) { + /* if the pipe does not exist then return */ + return (NULL); + } + if (pipe->edesc == NULL) { + /* invalid pipe */ + return (NULL); + } + if (ep_index != 0) { + if (pipe->iface_index != iface_index) { + /* + * Permissions violation - trying to access a + * pipe that does not belong to the interface. + */ + return (NULL); + } + } + return (pipe); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_fifo_open + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb2_fifo_open(struct usb2_fifo *f, struct file *fp, struct thread *td, + int fflags) +{ + int err; + + if (f == NULL) { + /* no FIFO there */ + DPRINTFN(2, "no FIFO\n"); + return (ENXIO); + } + /* remove FWRITE and FREAD flags */ + fflags &= ~(FWRITE | FREAD); + + /* set correct file flags */ + if ((f->fifo_index & 1) == USB_FIFO_TX) { + fflags |= FWRITE; + } else { + fflags |= FREAD; + } + + /* check if we are already opened */ + /* we don't need any locks when checking this variable */ + if (f->curr_file) { + err = EBUSY; + goto done; + } + /* call open method */ + err = (f->methods->f_open) (f, fflags, td); + if (err) { + goto done; + } + mtx_lock(f->priv_mtx); + + /* reset sleep flag */ + f->flag_sleeping = 0; + + /* reset error flag */ + f->flag_iserror = 0; + + /* reset complete flag */ + f->flag_iscomplete = 0; + + /* reset select flag */ + f->flag_isselect = 0; + + /* reset flushing flag */ + f->flag_flushing = 0; + + /* reset ASYNC proc flag */ + f->async_p = NULL; + + /* set which file we belong to */ + mtx_lock(&usb2_ref_lock); + f->curr_file = fp; + mtx_unlock(&usb2_ref_lock); + + /* reset queue */ + usb2_fifo_reset(f); + + mtx_unlock(f->priv_mtx); +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_fifo_reset + *------------------------------------------------------------------------*/ +void +usb2_fifo_reset(struct usb2_fifo *f) +{ + struct usb2_mbuf *m; + + if (f == NULL) { + return; + } + while (1) { + USB_IF_DEQUEUE(&f->used_q, m); + if (m) { + USB_IF_ENQUEUE(&f->free_q, m); + } else { + break; + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_close + *------------------------------------------------------------------------*/ +static void +usb2_fifo_close(struct usb2_fifo *f, struct thread *td, int fflags) +{ + int err; + + /* check if we are not opened */ + if (!f->curr_file) { + /* nothing to do - already closed */ + return; + } + mtx_lock(f->priv_mtx); + + /* clear current file flag */ + f->curr_file = NULL; + + /* check if we are selected */ + if (f->flag_isselect) { + selwakeup(&f->selinfo); + f->flag_isselect = 0; + } + /* check if a thread wants SIGIO */ + if (f->async_p != NULL) { + PROC_LOCK(f->async_p); + psignal(f->async_p, SIGIO); + PROC_UNLOCK(f->async_p); + f->async_p = NULL; + } + /* remove FWRITE and FREAD flags */ + fflags &= ~(FWRITE | FREAD); + + /* flush written data, if any */ + if ((f->fifo_index & 1) == USB_FIFO_TX) { + + if (!f->flag_iserror) { + + /* set flushing flag */ + f->flag_flushing = 1; + + /* start write transfer, if not already started */ + (f->methods->f_start_write) (f); + + /* check if flushed already */ + while (f->flag_flushing && + (!f->flag_iserror)) { + /* wait until all data has been written */ + f->flag_sleeping = 1; + err = usb2_cv_wait_sig(&f->cv_io, f->priv_mtx); + if (err) { + DPRINTF("signal received\n"); + break; + } + } + } + fflags |= FWRITE; + + /* stop write transfer, if not already stopped */ + (f->methods->f_stop_write) (f); + } else { + fflags |= FREAD; + + /* stop write transfer, if not already stopped */ + (f->methods->f_stop_read) (f); + } + + /* check if we are sleeping */ + if (f->flag_sleeping) { + DPRINTFN(2, "Sleeping at close!\n"); + } + mtx_unlock(f->priv_mtx); + + /* call close method */ + (f->methods->f_close) (f, fflags, td); + + DPRINTF("closed\n"); + return; +} + +/*------------------------------------------------------------------------* + * usb2_check_thread_perm + * + * Returns: + * 0: Has permission. + * Else: No permission. + *------------------------------------------------------------------------*/ +int +usb2_check_thread_perm(struct usb2_device *udev, struct thread *td, + int fflags, uint8_t iface_index, uint8_t ep_index) +{ + struct usb2_interface *iface; + int err; + + iface = usb2_get_iface(udev, iface_index); + if (iface == NULL) { + return (EINVAL); + } + if (iface->idesc == NULL) { + return (EINVAL); + } + /* scan down the permissions tree */ + if ((ep_index != 0) && iface && + (usb2_check_access(fflags, &iface->perm) == 0)) { + /* we got access through the interface */ + err = 0; + } else if (udev && + (usb2_check_access(fflags, &udev->perm) == 0)) { + /* we got access through the device */ + err = 0; + } else if (udev->bus && + (usb2_check_access(fflags, &udev->bus->perm) == 0)) { + /* we got access through the USB bus */ + err = 0; + } else if (usb2_check_access(fflags, &usb2_perm) == 0) { + /* we got general access */ + err = 0; + } else { + /* no access */ + err = EPERM; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_fdopen - cdev callback + *------------------------------------------------------------------------*/ +static int +usb2_fdopen(struct cdev *dev, int xxx_oflags, struct thread *td, + struct file *fp) +{ + struct usb2_location loc; + uint32_t devloc; + int err; + int fflags; + + DPRINTFN(2, "oflags=0x%08x\n", xxx_oflags); + + devloc = usb2_last_devloc; + usb2_last_devloc = (0 - 1); /* reset "usb2_last_devloc" */ + + if (fp == NULL) { + DPRINTFN(2, "fp == NULL\n"); + return (ENXIO); + } + if (usb2_old_f_data != fp->f_data) { + if (usb2_old_f_data != NULL) { + DPRINTFN(0, "File data mismatch!\n"); + return (ENXIO); + } + usb2_old_f_data = fp->f_data; + } + if (usb2_old_f_ops != fp->f_ops) { + if (usb2_old_f_ops != NULL) { + DPRINTFN(0, "File ops mismatch!\n"); + return (ENXIO); + } + usb2_old_f_ops = fp->f_ops; + } + fflags = fp->f_flag; + DPRINTFN(2, "fflags=0x%08x\n", fflags); + + if (!(fflags & (FREAD | FWRITE))) { + /* should not happen */ + return (EPERM); + } + if (devloc == (uint32_t)(0 - 2)) { + /* tried to open "/dev/usb" */ + return (0); + } else if (devloc == (uint32_t)(0 - 1)) { + /* tried to open "/dev/usb " */ + DPRINTFN(2, "no devloc\n"); + return (ENXIO); + } + err = usb2_ref_device(NULL, &loc, devloc); + if (err) { + DPRINTFN(2, "cannot ref device\n"); + return (ENXIO); + } + err = usb2_check_thread_perm(loc.udev, td, fflags, + loc.iface_index, loc.ep_index); + + /* check for error */ + if (err) { + usb2_unref_device(&loc); + return (err); + } + /* create FIFOs, if any */ + err = usb2_fifo_create(&loc, &devloc, fflags); + /* check for error */ + if (err) { + usb2_unref_device(&loc); + return (err); + } + if (fflags & FREAD) { + err = usb2_fifo_open(loc.rxfifo, fp, td, fflags); + if (err) { + DPRINTFN(2, "read open failed\n"); + usb2_unref_device(&loc); + return (err); + } + } + if (fflags & FWRITE) { + err = usb2_fifo_open(loc.txfifo, fp, td, fflags); + if (err) { + DPRINTFN(2, "write open failed\n"); + if (fflags & FREAD) { + usb2_fifo_close(loc.rxfifo, td, + fflags); + } + usb2_unref_device(&loc); + return (err); + } + } + /* + * Take over the file so that we get all the callbacks + * directly and don't have to create another device: + */ + finit(fp, fp->f_flag, DTYPE_VNODE, + ((uint8_t *)0) + devloc, &usb2_ops_f); + + usb2_unref_device(&loc); + + DPRINTFN(2, "error=%d\n", err); + + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_close - cdev callback + *------------------------------------------------------------------------*/ +static int +usb2_close(struct cdev *dev, int flag, int mode, struct thread *p) +{ + DPRINTF("\n"); + return (0); /* nothing to do */ +} + +/*------------------------------------------------------------------------* + * usb2_close - cdev callback + *------------------------------------------------------------------------*/ +static int +usb2_ioctl(struct cdev *dev, u_long cmd, caddr_t data, + int fflag, struct thread *td) +{ + union { + struct usb2_read_dir *urd; + struct usb2_dev_perm *udp; + void *data; + } u; + int err; + + u.data = data; + + switch (cmd) { + case USB_READ_DIR: + err = usb2_read_symlink(u.urd->urd_data, + u.urd->urd_startentry, u.urd->urd_maxlen); + break; + case USB_SET_IFACE_PERM: + err = usb2_set_perm(u.udp, 3); + break; + case USB_SET_DEVICE_PERM: + err = usb2_set_perm(u.udp, 2); + break; + case USB_SET_BUS_PERM: + err = usb2_set_perm(u.udp, 1); + break; + case USB_SET_ROOT_PERM: + err = usb2_set_perm(u.udp, 0); + break; + case USB_GET_IFACE_PERM: + err = usb2_get_perm(u.udp, 3); + break; + case USB_GET_DEVICE_PERM: + err = usb2_get_perm(u.udp, 2); + break; + case USB_GET_BUS_PERM: + err = usb2_get_perm(u.udp, 1); + break; + case USB_GET_ROOT_PERM: + err = usb2_get_perm(u.udp, 0); + break; + case USB_DEV_QUIRK_GET: + case USB_QUIRK_NAME_GET: + case USB_DEV_QUIRK_ADD: + case USB_DEV_QUIRK_REMOVE: + err = usb2_quirk_ioctl_p(cmd, data, fflag, td); + break; + default: + err = ENOTTY; + break; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_clone - cdev callback + * + * This function is the kernel clone callback for "/dev/usbX.Y". + * + * NOTE: This function assumes that the clone and device open + * operation is atomic. + *------------------------------------------------------------------------*/ +static void +usb2_clone(void *arg, USB_UCRED char *name, int namelen, struct cdev **dev) +{ + enum { + USB_DNAME_LEN = sizeof(USB_DEVICE_NAME) - 1, + USB_GNAME_LEN = sizeof(USB_GENERIC_NAME) - 1, + }; + + if (*dev) { + /* someone else has created a device */ + return; + } + /* reset device location */ + usb2_last_devloc = (uint32_t)(0 - 1); + + /* + * Check if we are matching "usb", "ugen" or an internal + * symbolic link: + */ + if ((namelen >= USB_DNAME_LEN) && + (bcmp(name, USB_DEVICE_NAME, USB_DNAME_LEN) == 0)) { + if (namelen == USB_DNAME_LEN) { + /* USB management device location */ + usb2_last_devloc = (uint32_t)(0 - 2); + } else { + /* USB endpoint */ + usb2_last_devloc = + usb2_path_convert(name + USB_DNAME_LEN); + } + } else if ((namelen >= USB_GNAME_LEN) && + (bcmp(name, USB_GENERIC_NAME, USB_GNAME_LEN) == 0)) { + if (namelen == USB_GNAME_LEN) { + /* USB management device location */ + usb2_last_devloc = (uint32_t)(0 - 2); + } else { + /* USB endpoint */ + usb2_last_devloc = + usb2_path_convert(name + USB_GNAME_LEN); + } + } + if (usb2_last_devloc == (uint32_t)(0 - 1)) { + /* Search for symbolic link */ + usb2_last_devloc = + usb2_lookup_symlink(name, namelen); + } + if (usb2_last_devloc == (uint32_t)(0 - 1)) { + /* invalid location */ + return; + } + dev_ref(usb2_dev); + *dev = usb2_dev; + return; +} + +static void +usb2_dev_init(void *arg) +{ + mtx_init(&usb2_ref_lock, "USB ref mutex", NULL, MTX_DEF); + sx_init(&usb2_sym_lock, "USB sym mutex"); + TAILQ_INIT(&usb2_sym_head); + + /* check the UGEN methods */ + usb2_fifo_check_methods(&usb2_ugen_methods); + return; +} + +SYSINIT(usb2_dev_init, SI_SUB_KLD, SI_ORDER_FIRST, usb2_dev_init, NULL); + +static void +usb2_dev_init_post(void *arg) +{ + /* + * Create a dummy device so that we are visible. This device + * should never be opened. Therefore a space character is + * appended after the USB device name. + * + * NOTE: The permissions of this device is 0777, because we + * check the permissions again in the open routine against the + * real USB permissions which are not 0777. Else USB access + * will be limited to one user and one group. + */ + usb2_dev = make_dev(&usb2_devsw, 0, UID_ROOT, GID_OPERATOR, + 0777, USB_DEVICE_NAME " "); + if (usb2_dev == NULL) { + DPRINTFN(0, "Could not create usb bus device!\n"); + } + usb2_clone_tag = EVENTHANDLER_REGISTER(dev_clone, usb2_clone_ptr, NULL, 1000); + if (usb2_clone_tag == NULL) { + DPRINTFN(0, "Registering clone handler failed!\n"); + } + return; +} + +SYSINIT(usb2_dev_init_post, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, usb2_dev_init_post, NULL); + +static void +usb2_dev_uninit(void *arg) +{ + if (usb2_clone_tag) { + EVENTHANDLER_DEREGISTER(dev_clone, usb2_clone_tag); + usb2_clone_tag = NULL; + } + if (usb2_dev) { + destroy_dev(usb2_dev); + usb2_dev = NULL; + } + mtx_destroy(&usb2_ref_lock); + sx_destroy(&usb2_sym_lock); + return; +} + +SYSUNINIT(usb2_dev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb2_dev_uninit, NULL); + +static int +usb2_close_f(struct file *fp, struct thread *td) +{ + struct usb2_location loc; + int fflags; + int err; + + fflags = fp->f_flag; + + DPRINTFN(2, "fflags=%u\n", fflags); + + err = usb2_ref_device(fp, &loc, 0);; + + /* restore some file variables */ + fp->f_ops = usb2_old_f_ops; + fp->f_data = usb2_old_f_data; + + /* check for error */ + if (err) { + DPRINTFN(2, "could not ref\n"); + goto done; + } + if (fflags & FREAD) { + usb2_fifo_close(loc.rxfifo, td, fflags); + } + if (fflags & FWRITE) { + usb2_fifo_close(loc.txfifo, td, fflags); + } + usb2_unref_device(&loc); + +done: + /* call old close method */ + USB_VNOPS_FO_CLOSE(fp, td, &err); + + return (err); +} + +static int +usb2_ioctl_f_sub(struct usb2_fifo *f, u_long cmd, void *addr, + struct thread *td) +{ + int error = 0; + + switch (cmd) { + case FIODTYPE: + *(int *)addr = 0; /* character device */ + break; + + case FIONBIO: + /* handled by upper FS layer */ + break; + + case FIOASYNC: + if (*(int *)addr) { + if (f->async_p != NULL) { + error = EBUSY; + break; + } + f->async_p = USB_TD_GET_PROC(td); + } else { + f->async_p = NULL; + } + break; + + /* XXX this is not the most general solution */ + case TIOCSPGRP: + if (f->async_p == NULL) { + error = EINVAL; + break; + } + if (*(int *)addr != USB_PROC_GET_GID(f->async_p)) { + error = EPERM; + break; + } + break; + default: + return (ENOTTY); + } + return (error); +} + +static int +usb2_ioctl_f(struct file *fp, u_long cmd, void *addr, + struct ucred *cred, struct thread *td) +{ + struct usb2_location loc; + int fflags; + int err_rx; + int err_tx; + int err; + uint8_t is_common = 0; + + err = usb2_ref_device(fp, &loc, 0);; + if (err) { + return (ENXIO); + } + fflags = fp->f_flag; + + DPRINTFN(2, "fflags=%u, cmd=0x%lx\n", fflags, cmd); + + if (fflags & FREAD) { + if (fflags & FWRITE) { + /* + * Make sure that the IOCTL is not + * duplicated: + */ + is_common = 1; + } + err_rx = usb2_ioctl_f_sub(loc.rxfifo, cmd, addr, td); + if (err_rx == ENOTTY) { + err_rx = (loc.rxfifo->methods->f_ioctl) ( + loc.rxfifo, cmd, addr, + is_common ? fflags : (fflags & ~FWRITE), td); + } + } else { + err_rx = 0; + } + if (fflags & FWRITE) { + err_tx = usb2_ioctl_f_sub(loc.txfifo, cmd, addr, td); + if (err_tx == ENOTTY) { + if (is_common) + err_tx = 0; /* already handled this IOCTL */ + else + err_tx = (loc.txfifo->methods->f_ioctl) ( + loc.txfifo, cmd, addr, fflags & ~FREAD, td); + } + } else { + err_tx = 0; + } + + if (err_rx) { + err = err_rx; + } else if (err_tx) { + err = err_tx; + } else { + err = 0; /* no error */ + } + usb2_unref_device(&loc); + return (err); +} + +/* ARGSUSED */ +static int +usb2_kqfilter_f(struct file *fp, struct knote *kn) +{ + return (ENXIO); +} + +/* ARGSUSED */ +static int +usb2_poll_f(struct file *fp, int events, + struct ucred *cred, struct thread *td) +{ + struct usb2_location loc; + struct usb2_fifo *f; + struct usb2_mbuf *m; + int fflags; + int revents; + uint8_t usbfs_active = 0; + + revents = usb2_ref_device(fp, &loc, 1 /* no uref */ );; + if (revents) { + return (POLLHUP); + } + fflags = fp->f_flag; + + /* figure out if the USB File System is active */ + + if (fflags & FWRITE) { + f = loc.txfifo; + if (f->fs_ep_max != 0) { + usbfs_active = 1; + } + } + if (fflags & FREAD) { + f = loc.rxfifo; + if (f->fs_ep_max != 0) { + usbfs_active = 1; + } + } + /* Figure out who needs service */ + + if ((events & (POLLOUT | POLLWRNORM)) && + (fflags & FWRITE)) { + + f = loc.txfifo; + + mtx_lock(f->priv_mtx); + + if (!usbfs_active) { + if (f->flag_iserror) { + /* we got an error */ + m = (void *)1; + } else { + if (f->queue_data == NULL) { + /* + * start write transfer, if not + * already started + */ + (f->methods->f_start_write) (f); + } + /* check if any packets are available */ + USB_IF_POLL(&f->free_q, m); + } + } else { + if (f->flag_iscomplete) { + m = (void *)1; + } else { + m = NULL; + } + } + + if (m) { + revents |= events & (POLLOUT | POLLWRNORM); + } else { + f->flag_isselect = 1; + selrecord(td, &f->selinfo); + } + + mtx_unlock(f->priv_mtx); + } + if ((events & (POLLIN | POLLRDNORM)) && + (fflags & FREAD)) { + + f = loc.rxfifo; + + mtx_lock(f->priv_mtx); + + if (!usbfs_active) { + if (f->flag_iserror) { + /* we have and error */ + m = (void *)1; + } else { + if (f->queue_data == NULL) { + /* + * start read transfer, if not + * already started + */ + (f->methods->f_start_read) (f); + } + /* check if any packets are available */ + USB_IF_POLL(&f->used_q, m); + } + } else { + if (f->flag_iscomplete) { + m = (void *)1; + } else { + m = NULL; + } + } + + if (m) { + revents |= events & (POLLIN | POLLRDNORM); + } else { + f->flag_isselect = 1; + selrecord(td, &f->selinfo); + + /* start reading data */ + (f->methods->f_start_read) (f); + } + + mtx_unlock(f->priv_mtx); + } + usb2_unref_device(&loc); + return (revents); +} + +/* ARGSUSED */ +static int +usb2_read_f(struct file *fp, struct uio *uio, struct ucred *cred, + int flags, struct thread *td) +{ + struct usb2_location loc; + struct usb2_fifo *f; + struct usb2_mbuf *m; + int fflags; + int resid; + int io_len; + int err; + uint8_t tr_data = 0; + + DPRINTFN(2, "\n"); + + fflags = fp->f_flag & (O_NONBLOCK | O_DIRECT | FREAD | FWRITE); + if (fflags & O_DIRECT) + fflags |= IO_DIRECT; + + err = usb2_ref_device(fp, &loc, 1 /* no uref */ ); + if (err) { + return (ENXIO); + } + f = loc.rxfifo; + if (f == NULL) { + /* should not happen */ + return (EPERM); + } + resid = uio->uio_resid; + + if ((flags & FOF_OFFSET) == 0) + uio->uio_offset = fp->f_offset; + + mtx_lock(f->priv_mtx); + + if (f->flag_iserror) { + err = EIO; + goto done; + } + while (uio->uio_resid > 0) { + + if (f->fs_ep_max == 0) { + USB_IF_DEQUEUE(&f->used_q, m); + } else { + /* + * The queue is used for events that should be + * retrieved using the "USB_FS_COMPLETE" + * ioctl. + */ + m = NULL; + } + + if (m == NULL) { + + /* start read transfer, if not already started */ + + (f->methods->f_start_read) (f); + + if (fflags & O_NONBLOCK) { + if (tr_data) { + /* return length before error */ + break; + } + err = EWOULDBLOCK; + break; + } + DPRINTF("sleeping\n"); + + err = usb2_fifo_wait(f); + if (err) { + break; + } + continue; + } else { + tr_data = 1; + } + + io_len = MIN(m->cur_data_len, uio->uio_resid); + + DPRINTFN(2, "transfer %d bytes from %p\n", + io_len, m->cur_data_ptr); + + err = usb2_fifo_uiomove(f, + m->cur_data_ptr, io_len, uio); + + m->cur_data_len -= io_len; + m->cur_data_ptr += io_len; + + if (m->cur_data_len == 0) { + + uint8_t last_packet; + + last_packet = m->last_packet; + + USB_IF_ENQUEUE(&f->free_q, m); + + if (last_packet) { + /* keep framing */ + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + + if (err) { + break; + } + } +done: + mtx_unlock(f->priv_mtx); + + usb2_unref_device(&loc); + + if ((flags & FOF_OFFSET) == 0) + fp->f_offset = uio->uio_offset; + fp->f_nextoff = uio->uio_offset; + return (err); +} + +static int +usb2_stat_f(struct file *fp, struct stat *sb, struct ucred *cred, struct thread *td) +{ + return (USB_VNOPS_FO_STAT(fp, sb, cred, td)); +} + +#if __FreeBSD_version > 800009 +static int +usb2_truncate_f(struct file *fp, off_t length, struct ucred *cred, struct thread *td) +{ + return (USB_VNOPS_FO_TRUNCATE(fp, length, cred, td)); +} + +#endif + +/* ARGSUSED */ +static int +usb2_write_f(struct file *fp, struct uio *uio, struct ucred *cred, + int flags, struct thread *td) +{ + struct usb2_location loc; + struct usb2_fifo *f; + struct usb2_mbuf *m; + int fflags; + int resid; + int io_len; + int err; + uint8_t tr_data = 0; + + DPRINTFN(2, "\n"); + + fflags = fp->f_flag & (O_NONBLOCK | O_DIRECT | + FREAD | FWRITE | O_FSYNC); + if (fflags & O_DIRECT) + fflags |= IO_DIRECT; + + err = usb2_ref_device(fp, &loc, 1 /* no uref */ ); + if (err) { + return (ENXIO); + } + f = loc.txfifo; + if (f == NULL) { + /* should not happen */ + usb2_unref_device(&loc); + return (EPERM); + } + resid = uio->uio_resid; + + if ((flags & FOF_OFFSET) == 0) + uio->uio_offset = fp->f_offset; + + mtx_lock(f->priv_mtx); + + if (f->flag_iserror) { + err = EIO; + goto done; + } + if ((f->queue_data == NULL) && (f->fs_ep_max == 0)) { + /* start write transfer, if not already started */ + (f->methods->f_start_write) (f); + } + /* we allow writing zero length data */ + do { + if (f->fs_ep_max == 0) { + USB_IF_DEQUEUE(&f->free_q, m); + } else { + /* + * The queue is used for events that should be + * retrieved using the "USB_FS_COMPLETE" + * ioctl. + */ + m = NULL; + } + + if (m == NULL) { + + if (fflags & O_NONBLOCK) { + if (tr_data) { + /* return length before error */ + break; + } + err = EWOULDBLOCK; + break; + } + DPRINTF("sleeping\n"); + + err = usb2_fifo_wait(f); + if (err) { + break; + } + continue; + } else { + tr_data = 1; + } + + USB_MBUF_RESET(m); + + io_len = MIN(m->cur_data_len, uio->uio_resid); + + m->cur_data_len = io_len; + + DPRINTFN(2, "transfer %d bytes to %p\n", + io_len, m->cur_data_ptr); + + err = usb2_fifo_uiomove(f, + m->cur_data_ptr, io_len, uio); + + if (err) { + USB_IF_ENQUEUE(&f->free_q, m); + break; + } else { + USB_IF_ENQUEUE(&f->used_q, m); + (f->methods->f_start_write) (f); + } + } while (uio->uio_resid > 0); +done: + mtx_unlock(f->priv_mtx); + + usb2_unref_device(&loc); + + if ((flags & FOF_OFFSET) == 0) + fp->f_offset = uio->uio_offset; + fp->f_nextoff = uio->uio_offset; + + return (err); +} + +static int +usb2_fifo_uiomove(struct usb2_fifo *f, void *cp, + int n, struct uio *uio) +{ + int error; + + mtx_unlock(f->priv_mtx); + + /* + * "uiomove()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things: + */ + error = uiomove(cp, n, uio); + + mtx_lock(f->priv_mtx); + + return (error); +} + +int +usb2_fifo_wait(struct usb2_fifo *f) +{ + int err; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->flag_iserror) { + /* we are gone */ + return (EIO); + } + f->flag_sleeping = 1; + + err = usb2_cv_wait_sig(&f->cv_io, f->priv_mtx); + + if (f->flag_iserror) { + /* we are gone */ + err = EIO; + } + return (err); +} + +void +usb2_fifo_signal(struct usb2_fifo *f) +{ + if (f->flag_sleeping) { + f->flag_sleeping = 0; + usb2_cv_broadcast(&f->cv_io); + } + return; +} + +void +usb2_fifo_wakeup(struct usb2_fifo *f) +{ + usb2_fifo_signal(f); + + if (f->flag_isselect) { + selwakeup(&f->selinfo); + f->flag_isselect = 0; + } + if (f->async_p != NULL) { + PROC_LOCK(f->async_p); + psignal(f->async_p, SIGIO); + PROC_UNLOCK(f->async_p); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_opened + * + * Returns: + * 0: FIFO not opened. + * Else: FIFO is opened. + *------------------------------------------------------------------------*/ +uint8_t +usb2_fifo_opened(struct usb2_fifo *f) +{ + uint8_t temp; + uint8_t do_unlock; + + if (f == NULL) { + return (0); /* be NULL safe */ + } + if (mtx_owned(f->priv_mtx)) { + do_unlock = 0; + } else { + do_unlock = 1; + mtx_lock(f->priv_mtx); + } + temp = f->curr_file ? 1 : 0; + if (do_unlock) { + mtx_unlock(f->priv_mtx); + } + return (temp); +} + + +static int +usb2_fifo_dummy_open(struct usb2_fifo *fifo, + int fflags, struct thread *td) +{ + return (0); +} + +static void +usb2_fifo_dummy_close(struct usb2_fifo *fifo, + int fflags, struct thread *td) +{ + return; +} + +static int +usb2_fifo_dummy_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr, + int fflags, struct thread *td) +{ + return (ENOTTY); +} + +static void +usb2_fifo_dummy_cmd(struct usb2_fifo *fifo) +{ + fifo->flag_flushing = 0; /* not flushing */ + return; +} + +static void +usb2_fifo_check_methods(struct usb2_fifo_methods *pm) +{ + /* check that all callback functions are OK */ + + if (pm->f_open == NULL) + pm->f_open = &usb2_fifo_dummy_open; + + if (pm->f_close == NULL) + pm->f_close = &usb2_fifo_dummy_close; + + if (pm->f_ioctl == NULL) + pm->f_ioctl = &usb2_fifo_dummy_ioctl; + + if (pm->f_start_read == NULL) + pm->f_start_read = &usb2_fifo_dummy_cmd; + + if (pm->f_stop_read == NULL) + pm->f_stop_read = &usb2_fifo_dummy_cmd; + + if (pm->f_start_write == NULL) + pm->f_start_write = &usb2_fifo_dummy_cmd; + + if (pm->f_stop_write == NULL) + pm->f_stop_write = &usb2_fifo_dummy_cmd; + + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_attach + * + * The following function will create a duplex FIFO. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +int +usb2_fifo_attach(struct usb2_device *udev, void *priv_sc, + struct mtx *priv_mtx, struct usb2_fifo_methods *pm, + struct usb2_fifo_sc *f_sc, uint16_t unit, uint16_t subunit, + uint8_t iface_index) +{ + struct usb2_fifo *f_tx; + struct usb2_fifo *f_rx; + char buf[32]; + char src[32]; + uint8_t n; + + f_sc->fp[USB_FIFO_TX] = NULL; + f_sc->fp[USB_FIFO_RX] = NULL; + + if (pm == NULL) + return (EINVAL); + + /* check the methods */ + usb2_fifo_check_methods(pm); + + if (priv_mtx == NULL) + priv_mtx = &Giant; + + /* search for a free FIFO slot */ + for (n = 0;; n += 2) { + + if (n == USB_FIFO_MAX) { + /* end of FIFOs reached */ + return (ENOMEM); + } + /* Check for TX FIFO */ + if (udev->fifo[n + USB_FIFO_TX] != NULL) { + continue; + } + /* Check for RX FIFO */ + if (udev->fifo[n + USB_FIFO_RX] != NULL) { + continue; + } + break; + } + + f_tx = usb2_fifo_alloc(); + f_rx = usb2_fifo_alloc(); + + if ((f_tx == NULL) || (f_rx == NULL)) { + usb2_fifo_free(f_tx); + usb2_fifo_free(f_rx); + return (ENOMEM); + } + /* initialise FIFO structures */ + + f_tx->fifo_index = n + USB_FIFO_TX; + f_tx->dev_ep_index = (n / 2) + (USB_EP_MAX / 2); + f_tx->priv_mtx = priv_mtx; + f_tx->priv_sc0 = priv_sc; + f_tx->methods = pm; + f_tx->iface_index = iface_index; + f_tx->udev = udev; + f_tx->flag_no_uref = 1; + + f_rx->fifo_index = n + USB_FIFO_RX; + f_rx->dev_ep_index = (n / 2) + (USB_EP_MAX / 2); + f_rx->priv_mtx = priv_mtx; + f_rx->priv_sc0 = priv_sc; + f_rx->methods = pm; + f_rx->iface_index = iface_index; + f_rx->udev = udev; + f_rx->flag_no_uref = 1; + + f_sc->fp[USB_FIFO_TX] = f_tx; + f_sc->fp[USB_FIFO_RX] = f_rx; + + mtx_lock(&usb2_ref_lock); + udev->fifo[f_tx->fifo_index] = f_tx; + udev->fifo[f_rx->fifo_index] = f_rx; + mtx_unlock(&usb2_ref_lock); + + if (snprintf(src, sizeof(src), + USB_DEVICE_NAME "%u.%u.%u.%u", + device_get_unit(udev->bus->bdev), + udev->device_index, + iface_index, + f_tx->dev_ep_index)) { + /* ignore */ + } + for (n = 0; n != 4; n++) { + + if (pm->basename[n] == NULL) { + continue; + } + if (subunit == 0xFFFF) { + if (snprintf(buf, sizeof(buf), + "%s%u%s", pm->basename[n], + unit, pm->postfix[n] ? + pm->postfix[n] : "")) { + /* ignore */ + } + } else { + if (snprintf(buf, sizeof(buf), + "%s%u.%u%s", pm->basename[n], + unit, subunit, pm->postfix[n] ? + pm->postfix[n] : "")) { + /* ignore */ + } + } + + /* + * Distribute the symbolic links into two FIFO structures: + */ + if (n & 1) { + f_rx->symlink[n / 2] = + usb2_alloc_symlink(src, "%s", buf); + } else { + f_tx->symlink[n / 2] = + usb2_alloc_symlink(src, "%s", buf); + } + printf("Symlink: %s -> %s\n", buf, src); + } + + DPRINTFN(2, "attached %p/%p\n", f_tx, f_rx); + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_fifo_alloc_buffer + * + * Return values: + * 0: Success + * Else failure + *------------------------------------------------------------------------*/ +int +usb2_fifo_alloc_buffer(struct usb2_fifo *f, uint32_t bufsize, + uint16_t nbuf) +{ + usb2_fifo_free_buffer(f); + + /* allocate an endpoint */ + f->free_q.ifq_maxlen = nbuf; + f->used_q.ifq_maxlen = nbuf; + + f->queue_data = usb2_alloc_mbufs( + M_USBDEV, &f->free_q, bufsize, nbuf); + + if ((f->queue_data == NULL) && bufsize && nbuf) { + return (ENOMEM); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_fifo_free_buffer + * + * This function will free the buffers associated with a FIFO. This + * function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usb2_fifo_free_buffer(struct usb2_fifo *f) +{ + if (f->queue_data) { + /* free old buffer */ + free(f->queue_data, M_USBDEV); + f->queue_data = NULL; + } + /* reset queues */ + + bzero(&f->free_q, sizeof(f->free_q)); + bzero(&f->used_q, sizeof(f->used_q)); + return; +} + +void +usb2_fifo_detach(struct usb2_fifo_sc *f_sc) +{ + if (f_sc == NULL) { + return; + } + usb2_fifo_free(f_sc->fp[USB_FIFO_TX]); + usb2_fifo_free(f_sc->fp[USB_FIFO_RX]); + + f_sc->fp[USB_FIFO_TX] = NULL; + f_sc->fp[USB_FIFO_RX] = NULL; + + DPRINTFN(2, "detached %p\n", f_sc); + + return; +} + +uint32_t +usb2_fifo_put_bytes_max(struct usb2_fifo *f) +{ + struct usb2_mbuf *m; + uint32_t len; + + USB_IF_POLL(&f->free_q, m); + + if (m) { + len = m->max_data_len; + } else { + len = 0; + } + return (len); +} + +/*------------------------------------------------------------------------* + * usb2_fifo_put_data + * + * what: + * 0 - normal operation + * 1 - set last packet flag to enforce framing + *------------------------------------------------------------------------*/ +void +usb2_fifo_put_data(struct usb2_fifo *f, struct usb2_page_cache *pc, + uint32_t offset, uint32_t len, uint8_t what) +{ + struct usb2_mbuf *m; + uint32_t io_len; + + while (len || (what == 1)) { + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + USB_MBUF_RESET(m); + + io_len = MIN(len, m->cur_data_len); + + usb2_copy_out(pc, offset, m->cur_data_ptr, io_len); + + m->cur_data_len = io_len; + offset += io_len; + len -= io_len; + + if ((len == 0) && (what == 1)) { + m->last_packet = 1; + } + USB_IF_ENQUEUE(&f->used_q, m); + + usb2_fifo_wakeup(f); + + if ((len == 0) || (what == 1)) { + break; + } + } else { + break; + } + } + return; +} + +void +usb2_fifo_put_data_linear(struct usb2_fifo *f, void *ptr, + uint32_t len, uint8_t what) +{ + struct usb2_mbuf *m; + uint32_t io_len; + + while (len || (what == 1)) { + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + USB_MBUF_RESET(m); + + io_len = MIN(len, m->cur_data_len); + + bcopy(ptr, m->cur_data_ptr, io_len); + + m->cur_data_len = io_len; + ptr = USB_ADD_BYTES(ptr, io_len); + len -= io_len; + + if ((len == 0) && (what == 1)) { + m->last_packet = 1; + } + USB_IF_ENQUEUE(&f->used_q, m); + + usb2_fifo_wakeup(f); + + if ((len == 0) || (what == 1)) { + break; + } + } else { + break; + } + } + return; +} + +uint8_t +usb2_fifo_put_data_buffer(struct usb2_fifo *f, void *ptr, uint32_t len) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + m->cur_data_len = len; + m->cur_data_ptr = ptr; + USB_IF_ENQUEUE(&f->used_q, m); + usb2_fifo_wakeup(f); + return (1); + } + return (0); +} + +void +usb2_fifo_put_data_error(struct usb2_fifo *f) +{ + f->flag_iserror = 1; + usb2_fifo_wakeup(f); + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_get_data + * + * what: + * 0 - normal operation + * 1 - only get one "usb2_mbuf" + * + * returns: + * 0 - no more data + * 1 - data in buffer + *------------------------------------------------------------------------*/ +uint8_t +usb2_fifo_get_data(struct usb2_fifo *f, struct usb2_page_cache *pc, + uint32_t offset, uint32_t len, uint32_t *actlen, + uint8_t what) +{ + struct usb2_mbuf *m; + uint32_t io_len; + uint8_t tr_data = 0; + + actlen[0] = 0; + + while (1) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + + tr_data = 1; + + io_len = MIN(len, m->cur_data_len); + + usb2_copy_in(pc, offset, m->cur_data_ptr, io_len); + + len -= io_len; + offset += io_len; + actlen[0] += io_len; + m->cur_data_ptr += io_len; + m->cur_data_len -= io_len; + + if ((m->cur_data_len == 0) || (what == 1)) { + USB_IF_ENQUEUE(&f->free_q, m); + + usb2_fifo_wakeup(f); + + if (what == 1) { + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + } else { + + if (tr_data) { + /* wait for data to be written out */ + break; + } + if (f->flag_flushing) { + f->flag_flushing = 0; + usb2_fifo_wakeup(f); + } + break; + } + if (len == 0) { + break; + } + } + return (tr_data); +} + +uint8_t +usb2_fifo_get_data_linear(struct usb2_fifo *f, void *ptr, + uint32_t len, uint32_t *actlen, uint8_t what) +{ + struct usb2_mbuf *m; + uint32_t io_len; + uint8_t tr_data = 0; + + actlen[0] = 0; + + while (1) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + + tr_data = 1; + + io_len = MIN(len, m->cur_data_len); + + bcopy(m->cur_data_ptr, ptr, io_len); + + len -= io_len; + ptr = USB_ADD_BYTES(ptr, io_len); + actlen[0] += io_len; + m->cur_data_ptr += io_len; + m->cur_data_len -= io_len; + + if ((m->cur_data_len == 0) || (what == 1)) { + USB_IF_ENQUEUE(&f->free_q, m); + + usb2_fifo_wakeup(f); + + if (what == 1) { + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + } else { + + if (tr_data) { + /* wait for data to be written out */ + break; + } + if (f->flag_flushing) { + f->flag_flushing = 0; + usb2_fifo_wakeup(f); + } + break; + } + if (len == 0) { + break; + } + } + return (tr_data); +} + +uint8_t +usb2_fifo_get_data_buffer(struct usb2_fifo *f, void **pptr, uint32_t *plen) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + *plen = m->cur_data_len; + *pptr = m->cur_data_ptr; + + USB_IF_PREPEND(&f->used_q, m); + return (1); + } + return (0); +} + +void +usb2_fifo_get_data_next(struct usb2_fifo *f) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + USB_IF_ENQUEUE(&f->free_q, m); + usb2_fifo_wakeup(f); + } + return; +} + +void +usb2_fifo_get_data_error(struct usb2_fifo *f) +{ + f->flag_iserror = 1; + usb2_fifo_wakeup(f); + return; +} + +/*------------------------------------------------------------------------* + * usb2_alloc_symlink + * + * Return values: + * NULL: Failure + * Else: Pointer to symlink entry + *------------------------------------------------------------------------*/ +struct usb2_symlink * +usb2_alloc_symlink(const char *target, const char *fmt,...) +{ + struct usb2_symlink *ps; + va_list ap; + + ps = malloc(sizeof(*ps), M_USBDEV, M_WAITOK); + if (ps == NULL) { + return (ps); + } + strlcpy(ps->dst_path, target, sizeof(ps->dst_path)); + ps->dst_len = strlen(ps->dst_path); + + va_start(ap, fmt); + vsnrprintf(ps->src_path, + sizeof(ps->src_path), 32, fmt, ap); + va_end(ap); + ps->src_len = strlen(ps->src_path); + + sx_xlock(&usb2_sym_lock); + TAILQ_INSERT_TAIL(&usb2_sym_head, ps, sym_entry); + sx_unlock(&usb2_sym_lock); + return (ps); +} + +/*------------------------------------------------------------------------* + * usb2_free_symlink + *------------------------------------------------------------------------*/ +void +usb2_free_symlink(struct usb2_symlink *ps) +{ + if (ps == NULL) { + return; + } + sx_xlock(&usb2_sym_lock); + TAILQ_REMOVE(&usb2_sym_head, ps, sym_entry); + sx_unlock(&usb2_sym_lock); + + free(ps, M_USBDEV); + return; +} + +/*------------------------------------------------------------------------* + * usb2_lookup_symlink + * + * Return value: + * Numerical device location + *------------------------------------------------------------------------*/ +uint32_t +usb2_lookup_symlink(const char *src_ptr, uint8_t src_len) +{ + enum { + USB_DNAME_LEN = sizeof(USB_DEVICE_NAME) - 1, + }; + struct usb2_symlink *ps; + uint32_t temp; + + sx_xlock(&usb2_sym_lock); + + TAILQ_FOREACH(ps, &usb2_sym_head, sym_entry) { + + if (src_len != ps->src_len) + continue; + + if (memcmp(ps->src_path, src_ptr, src_len)) + continue; + + if (USB_DNAME_LEN > ps->dst_len) + continue; + + if (memcmp(ps->dst_path, USB_DEVICE_NAME, USB_DNAME_LEN)) + continue; + + temp = usb2_path_convert(ps->dst_path + USB_DNAME_LEN); + sx_unlock(&usb2_sym_lock); + + return (temp); + } + sx_unlock(&usb2_sym_lock); + return (0 - 1); +} + +/*------------------------------------------------------------------------* + * usb2_read_symlink + * + * Return value: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb2_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len) +{ + struct usb2_symlink *ps; + uint32_t temp; + uint32_t delta = 0; + uint8_t len; + int error = 0; + + sx_xlock(&usb2_sym_lock); + + TAILQ_FOREACH(ps, &usb2_sym_head, sym_entry) { + + /* + * Compute total length of source and destination symlink + * strings pluss one length byte and two NUL bytes: + */ + temp = ps->src_len + ps->dst_len + 3; + + if (temp > 255) { + /* + * Skip entry because this length cannot fit + * into one byte: + */ + continue; + } + if (startentry != 0) { + /* decrement read offset */ + startentry--; + continue; + } + if (temp > user_len) { + /* out of buffer space */ + break; + } + len = temp; + + /* copy out total length */ + + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + /* copy out source string */ + + error = copyout(ps->src_path, + USB_ADD_BYTES(user_ptr, delta), ps->src_len); + if (error) { + break; + } + len = 0; + delta += ps->src_len; + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + /* copy out destination string */ + + error = copyout(ps->dst_path, + USB_ADD_BYTES(user_ptr, delta), ps->dst_len); + if (error) { + break; + } + len = 0; + delta += ps->dst_len; + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + user_len -= temp; + } + + /* a zero length entry indicates the end */ + + if ((user_len != 0) && (error == 0)) { + + len = 0; + + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + } + sx_unlock(&usb2_sym_lock); + return (error); +} diff --git a/sys/dev/usb2/core/usb2_dev.h b/sys/dev/usb2/core/usb2_dev.h new file mode 100644 index 000000000000..859dd4ebb728 --- /dev/null +++ b/sys/dev/usb2/core/usb2_dev.h @@ -0,0 +1,149 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_DEV_H_ +#define _USB2_DEV_H_ + +#include +#include +#include +#include +#include +#include +#include + +#define USB_FIFO_TX 0 +#define USB_FIFO_RX 1 + +struct usb2_fifo; + +typedef int (usb2_fifo_open_t)(struct usb2_fifo *fifo, int fflags, struct thread *td); +typedef void (usb2_fifo_close_t)(struct usb2_fifo *fifo, int fflags, struct thread *td); +typedef int (usb2_fifo_ioctl_t)(struct usb2_fifo *fifo, u_long cmd, void *addr, int fflags, struct thread *td); +typedef void (usb2_fifo_cmd_t)(struct usb2_fifo *fifo); + +struct usb2_symlink { + TAILQ_ENTRY(usb2_symlink) sym_entry; + char src_path[32]; /* Source path - including terminating + * zero */ + char dst_path[32]; /* Destination path - including + * terminating zero */ + uint8_t src_len; /* String length */ + uint8_t dst_len; /* String length */ +}; + +/* + * Locking note for the following functions. All the + * "usb2_fifo_cmd_t" functions are called locked. The others are + * called unlocked. + */ +struct usb2_fifo_methods { + usb2_fifo_open_t *f_open; + usb2_fifo_close_t *f_close; + usb2_fifo_ioctl_t *f_ioctl; + usb2_fifo_cmd_t *f_start_read; + usb2_fifo_cmd_t *f_stop_read; + usb2_fifo_cmd_t *f_start_write; + usb2_fifo_cmd_t *f_stop_write; + const char *basename[4]; + const char *postfix[4]; +}; + +/* + * Most of the fields in the "usb2_fifo" structure are used by the + * generic USB access layer. + */ +struct usb2_fifo { + struct usb2_ifqueue free_q; + struct usb2_ifqueue used_q; + struct selinfo selinfo; + struct cv cv_io; + struct cv cv_drain; + struct usb2_fifo_methods *methods; + struct usb2_symlink *symlink[2];/* our symlinks */ + struct proc *async_p; /* process that wants SIGIO */ + struct usb2_fs_endpoint *fs_ep_ptr; + struct usb2_device *udev; + struct usb2_xfer *xfer[2]; + struct usb2_xfer **fs_xfer; + struct mtx *priv_mtx; /* client data */ + struct file *curr_file; /* set if FIFO is opened by a FILE */ + void *priv_sc0; /* client data */ + void *priv_sc1; /* client data */ + void *queue_data; + uint32_t timeout; /* timeout in milliseconds */ + uint32_t bufsize; /* BULK and INTERRUPT buffer size */ + uint16_t nframes; /* for isochronous mode */ + uint16_t dev_ep_index; /* our device endpoint index */ + uint8_t flag_no_uref; /* set if FIFO is not control endpoint */ + uint8_t flag_sleeping; /* set if FIFO is sleeping */ + uint8_t flag_iscomplete; /* set if a USB transfer is complete */ + uint8_t flag_iserror; /* set if FIFO error happened */ + uint8_t flag_isselect; /* set if FIFO is selected */ + uint8_t flag_flushing; /* set if FIFO is flushing data */ + uint8_t flag_short; /* set if short_ok or force_short + * transfer flags should be set */ + uint8_t flag_stall; /* set if clear stall should be run */ + uint8_t iface_index; /* set to the interface we belong to */ + uint8_t fifo_index; /* set to the FIFO index in "struct + * usb2_device" */ + uint8_t fs_ep_max; + uint8_t fifo_zlp; /* zero length packet count */ + uint8_t refcount; +#define USB_FIFO_REF_MAX 0xFF +}; + +struct usb2_fifo_sc { + struct usb2_fifo *fp[2]; +}; + +int usb2_fifo_wait(struct usb2_fifo *fifo); +void usb2_fifo_signal(struct usb2_fifo *fifo); +int usb2_fifo_alloc_buffer(struct usb2_fifo *f, uint32_t bufsize, uint16_t nbuf); +void usb2_fifo_free_buffer(struct usb2_fifo *f); +int usb2_fifo_attach(struct usb2_device *udev, void *priv_sc, struct mtx *priv_mtx, struct usb2_fifo_methods *pm, struct usb2_fifo_sc *f_sc, uint16_t unit, uint16_t subunit, uint8_t iface_index); +void usb2_fifo_detach(struct usb2_fifo_sc *f_sc); +uint32_t usb2_fifo_put_bytes_max(struct usb2_fifo *fifo); +void usb2_fifo_put_data(struct usb2_fifo *fifo, struct usb2_page_cache *pc, uint32_t offset, uint32_t len, uint8_t what); +void usb2_fifo_put_data_linear(struct usb2_fifo *fifo, void *ptr, uint32_t len, uint8_t what); +uint8_t usb2_fifo_put_data_buffer(struct usb2_fifo *f, void *ptr, uint32_t len); +void usb2_fifo_put_data_error(struct usb2_fifo *fifo); +uint8_t usb2_fifo_get_data(struct usb2_fifo *fifo, struct usb2_page_cache *pc, uint32_t offset, uint32_t len, uint32_t *actlen, uint8_t what); +uint8_t usb2_fifo_get_data_linear(struct usb2_fifo *fifo, void *ptr, uint32_t len, uint32_t *actlen, uint8_t what); +uint8_t usb2_fifo_get_data_buffer(struct usb2_fifo *f, void **pptr, uint32_t *plen); +void usb2_fifo_get_data_next(struct usb2_fifo *f); +void usb2_fifo_get_data_error(struct usb2_fifo *fifo); +uint8_t usb2_fifo_opened(struct usb2_fifo *fifo); +void usb2_fifo_free(struct usb2_fifo *f); +void usb2_fifo_reset(struct usb2_fifo *f); +int usb2_check_thread_perm(struct usb2_device *udev, struct thread *td, int fflags, uint8_t iface_index, uint8_t ep_index); +void usb2_fifo_wakeup(struct usb2_fifo *f); +struct usb2_symlink *usb2_alloc_symlink(const char *target, const char *fmt,...); +void usb2_free_symlink(struct usb2_symlink *ps); +uint32_t usb2_lookup_symlink(const char *src_ptr, uint8_t src_len); +int usb2_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len); + +#endif /* _USB2_DEV_H_ */ diff --git a/sys/dev/usb2/core/usb2_device.c b/sys/dev/usb2/core/usb2_device.c new file mode 100644 index 000000000000..934886525f55 --- /dev/null +++ b/sys/dev/usb2/core/usb2_device.c @@ -0,0 +1,2110 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +/* function prototypes */ + +static void usb2_fill_pipe_data(struct usb2_device *udev, uint8_t iface_index, struct usb2_endpoint_descriptor *edesc, struct usb2_pipe *pipe); +static void usb2_free_pipe_data(struct usb2_device *udev, uint8_t iface_index, uint8_t iface_mask); +static void usb2_free_iface_data(struct usb2_device *udev); +static void usb2_detach_device_sub(struct usb2_device *udev, device_t *ppdev, uint8_t free_subdev); +static uint8_t usb2_probe_and_attach_sub(struct usb2_device *udev, struct usb2_attach_arg *uaa); +static void usb2_init_attach_arg(struct usb2_device *udev, struct usb2_attach_arg *uaa); +static void usb2_suspend_resume_sub(struct usb2_device *udev, device_t dev, uint8_t do_suspend); +static void usb2_clear_stall_proc(struct usb2_proc_msg *_pm); +static void usb2_check_strings(struct usb2_device *udev); +static usb2_error_t usb2_fill_iface_data(struct usb2_device *udev, uint8_t iface_index, uint8_t alt_index); +static void usb2_notify_addq(const char *type, struct usb2_device *udev); +static void usb2_fifo_free_wrap(struct usb2_device *udev, uint8_t iface_index, uint8_t free_all); + +/* static structures */ + +static const uint8_t usb2_hub_speed_combs[USB_SPEED_MAX][USB_SPEED_MAX] = { + /* HUB *//* subdevice */ + [USB_SPEED_HIGH][USB_SPEED_HIGH] = 1, + [USB_SPEED_HIGH][USB_SPEED_FULL] = 1, + [USB_SPEED_HIGH][USB_SPEED_LOW] = 1, + [USB_SPEED_FULL][USB_SPEED_FULL] = 1, + [USB_SPEED_FULL][USB_SPEED_LOW] = 1, + [USB_SPEED_LOW][USB_SPEED_LOW] = 1, +}; + +/* This variable is global to allow easy access to it: */ + +int usb2_template = 0; + +SYSCTL_INT(_hw_usb2, OID_AUTO, template, CTLFLAG_RW, + &usb2_template, 0, "Selected USB device side template"); + + +/*------------------------------------------------------------------------* + * usb2_get_pipe_by_addr + * + * This function searches for an USB pipe by endpoint address and + * direction. + * + * Returns: + * NULL: Failure + * Else: Success + *------------------------------------------------------------------------*/ +struct usb2_pipe * +usb2_get_pipe_by_addr(struct usb2_device *udev, uint8_t ea_val) +{ + struct usb2_pipe *pipe = udev->pipes; + struct usb2_pipe *pipe_end = udev->pipes + USB_EP_MAX; + enum { + EA_MASK = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR), + }; + + /* + * According to the USB specification not all bits are used + * for the endpoint address. Keep defined bits only: + */ + ea_val &= EA_MASK; + + /* + * Iterate accross all the USB pipes searching for a match + * based on the endpoint address: + */ + for (; pipe != pipe_end; pipe++) { + + if (pipe->edesc == NULL) { + continue; + } + /* do the mask and check the value */ + if ((pipe->edesc->bEndpointAddress & EA_MASK) == ea_val) { + goto found; + } + } + + /* + * The default pipe is always present and is checked separately: + */ + if ((udev->default_pipe.edesc) && + ((udev->default_pipe.edesc->bEndpointAddress & EA_MASK) == ea_val)) { + pipe = &udev->default_pipe; + goto found; + } + return (NULL); + +found: + return (pipe); +} + +/*------------------------------------------------------------------------* + * usb2_get_pipe + * + * This function searches for an USB pipe based on the information + * given by the passed "struct usb2_config" pointer. + * + * Return values: + * NULL: No match. + * Else: Pointer to "struct usb2_pipe". + *------------------------------------------------------------------------*/ +struct usb2_pipe * +usb2_get_pipe(struct usb2_device *udev, uint8_t iface_index, + const struct usb2_config *setup) +{ + struct usb2_pipe *pipe = udev->pipes; + struct usb2_pipe *pipe_end = udev->pipes + USB_EP_MAX; + uint8_t index = setup->ep_index; + uint8_t ea_mask; + uint8_t ea_val; + uint8_t type_mask; + uint8_t type_val; + + DPRINTFN(10, "udev=%p iface_index=%d address=0x%x " + "type=0x%x dir=0x%x index=%d\n", + udev, iface_index, setup->endpoint, + setup->type, setup->direction, setup->ep_index); + + /* setup expected endpoint direction mask and value */ + + if (setup->direction == UE_DIR_ANY) { + /* match any endpoint direction */ + ea_mask = 0; + ea_val = 0; + } else { + /* match the given endpoint direction */ + ea_mask = (UE_DIR_IN | UE_DIR_OUT); + ea_val = (setup->direction & (UE_DIR_IN | UE_DIR_OUT)); + } + + /* setup expected endpoint address */ + + if (setup->endpoint == UE_ADDR_ANY) { + /* match any endpoint address */ + } else { + /* match the given endpoint address */ + ea_mask |= UE_ADDR; + ea_val |= (setup->endpoint & UE_ADDR); + } + + /* setup expected endpoint type */ + + if (setup->type == UE_BULK_INTR) { + /* this will match BULK and INTERRUPT endpoints */ + type_mask = 2; + type_val = 2; + } else if (setup->type == UE_TYPE_ANY) { + /* match any endpoint type */ + type_mask = 0; + type_val = 0; + } else { + /* match the given endpoint type */ + type_mask = UE_XFERTYPE; + type_val = (setup->type & UE_XFERTYPE); + } + + /* + * Iterate accross all the USB pipes searching for a match + * based on the endpoint address. Note that we are searching + * the pipes from the beginning of the "udev->pipes" array. + */ + for (; pipe != pipe_end; pipe++) { + + if ((pipe->edesc == NULL) || + (pipe->iface_index != iface_index)) { + continue; + } + /* do the masks and check the values */ + + if (((pipe->edesc->bEndpointAddress & ea_mask) == ea_val) && + ((pipe->edesc->bmAttributes & type_mask) == type_val)) { + if (!index--) { + goto found; + } + } + } + + /* + * Match against default pipe last, so that "any pipe", "any + * address" and "any direction" returns the first pipe of the + * interface. "iface_index" and "direction" is ignored: + */ + if ((udev->default_pipe.edesc) && + ((udev->default_pipe.edesc->bEndpointAddress & ea_mask) == ea_val) && + ((udev->default_pipe.edesc->bmAttributes & type_mask) == type_val) && + (!index)) { + pipe = &udev->default_pipe; + goto found; + } + return (NULL); + +found: + return (pipe); +} + +/*------------------------------------------------------------------------* + * usb2_interface_count + * + * This function stores the number of USB interfaces excluding + * alternate settings, which the USB config descriptor reports into + * the unsigned 8-bit integer pointed to by "count". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_interface_count(struct usb2_device *udev, uint8_t *count) +{ + if (udev->cdesc == NULL) { + *count = 0; + return (USB_ERR_NOT_CONFIGURED); + } + *count = udev->cdesc->bNumInterface; + return (USB_ERR_NORMAL_COMPLETION); +} + + +/*------------------------------------------------------------------------* + * usb2_fill_pipe_data + * + * This function will initialise the USB pipe structure pointed to by + * the "pipe" argument. + *------------------------------------------------------------------------*/ +static void +usb2_fill_pipe_data(struct usb2_device *udev, uint8_t iface_index, + struct usb2_endpoint_descriptor *edesc, struct usb2_pipe *pipe) +{ + bzero(pipe, sizeof(*pipe)); + + (udev->bus->methods->pipe_init) (udev, edesc, pipe); + + if (pipe->methods == NULL) { + /* the pipe is invalid: just return */ + return; + } + /* initialise USB pipe structure */ + pipe->edesc = edesc; + pipe->iface_index = iface_index; + TAILQ_INIT(&pipe->pipe_q.head); + pipe->pipe_q.command = &usb2_pipe_start; + + /* clear stall, if any */ + if (udev->bus->methods->clear_stall) { + mtx_lock(&udev->bus->mtx); + (udev->bus->methods->clear_stall) (udev, pipe); + mtx_unlock(&udev->bus->mtx); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_free_pipe_data + * + * This function will free USB pipe data for the given interface + * index. Hence we do not have any dynamic allocations we simply clear + * "pipe->edesc" to indicate that the USB pipe structure can be + * reused. The pipes belonging to the given interface should not be in + * use when this function is called and no check is performed to + * prevent this. + *------------------------------------------------------------------------*/ +static void +usb2_free_pipe_data(struct usb2_device *udev, + uint8_t iface_index, uint8_t iface_mask) +{ + struct usb2_pipe *pipe = udev->pipes; + struct usb2_pipe *pipe_end = udev->pipes + USB_EP_MAX; + + while (pipe != pipe_end) { + if ((pipe->iface_index & iface_mask) == iface_index) { + /* free pipe */ + pipe->edesc = NULL; + } + pipe++; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_fill_iface_data + * + * This function will fill in interface data and allocate USB pipes + * for all the endpoints that belong to the given interface. This + * function is typically called when setting the configuration or when + * setting an alternate interface. + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_fill_iface_data(struct usb2_device *udev, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_pipe *pipe; + struct usb2_pipe *pipe_end; + struct usb2_interface_descriptor *id; + struct usb2_endpoint_descriptor *ed = NULL; + struct usb2_descriptor *desc; + uint8_t nendpt; + + if (iface == NULL) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "iface_index=%d alt_index=%d\n", + iface_index, alt_index); + + sx_assert(udev->default_sx + 1, SA_LOCKED); + + pipe = udev->pipes; + pipe_end = udev->pipes + USB_EP_MAX; + + /* + * Check if any USB pipes on the given USB interface are in + * use: + */ + while (pipe != pipe_end) { + if ((pipe->edesc != NULL) && + (pipe->iface_index == iface_index) && + (pipe->refcount != 0)) { + return (USB_ERR_IN_USE); + } + pipe++; + } + + pipe = &udev->pipes[0]; + + id = usb2_find_idesc(udev->cdesc, iface_index, alt_index); + if (id == NULL) { + return (USB_ERR_INVAL); + } + /* + * Free old pipes after we know that an interface descriptor exists, + * if any. + */ + usb2_free_pipe_data(udev, iface_index, 0 - 1); + + /* Setup USB interface structure */ + iface->idesc = id; + iface->alt_index = alt_index; + iface->parent_iface_index = USB_IFACE_INDEX_ANY; + + nendpt = id->bNumEndpoints; + DPRINTFN(5, "found idesc nendpt=%d\n", nendpt); + + desc = (void *)id; + + while (nendpt--) { + DPRINTFN(11, "endpt=%d\n", nendpt); + + while ((desc = usb2_desc_foreach(udev->cdesc, desc))) { + if ((desc->bDescriptorType == UDESC_ENDPOINT) && + (desc->bLength >= sizeof(*ed))) { + goto found; + } + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + } + goto error; + +found: + ed = (void *)desc; + + /* find a free pipe */ + while (pipe != pipe_end) { + if (pipe->edesc == NULL) { + /* pipe is free */ + usb2_fill_pipe_data(udev, iface_index, ed, pipe); + break; + } + pipe++; + } + } + return (USB_ERR_NORMAL_COMPLETION); + +error: + /* passed end, or bad desc */ + DPRINTFN(0, "%s: bad descriptor(s), addr=%d!\n", + __FUNCTION__, udev->address); + + /* free old pipes if any */ + usb2_free_pipe_data(udev, iface_index, 0 - 1); + return (USB_ERR_INVAL); +} + +/*------------------------------------------------------------------------* + * usb2_free_iface_data + * + * This function will free all USB interfaces and USB pipes belonging + * to an USB device. + *------------------------------------------------------------------------*/ +static void +usb2_free_iface_data(struct usb2_device *udev) +{ + struct usb2_interface *iface = udev->ifaces; + struct usb2_interface *iface_end = udev->ifaces + USB_IFACE_MAX; + + /* mtx_assert() */ + + /* free Linux compat device, if any */ + if (udev->linux_dev) { + usb_linux_free_device(udev->linux_dev); + udev->linux_dev = NULL; + } + /* free all pipes, if any */ + usb2_free_pipe_data(udev, 0, 0); + + /* free all interfaces, if any */ + while (iface != iface_end) { + iface->idesc = NULL; + iface->alt_index = 0; + iface->parent_iface_index = USB_IFACE_INDEX_ANY; + iface->perm.mode = 0; /* disable permissions */ + iface++; + } + + /* free "cdesc" after "ifaces", if any */ + if (udev->cdesc) { + free(udev->cdesc, M_USB); + udev->cdesc = NULL; + } + /* set unconfigured state */ + udev->curr_config_no = USB_UNCONFIG_NO; + udev->curr_config_index = USB_UNCONFIG_INDEX; + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_config_index + * + * This function selects configuration by index, independent of the + * actual configuration number. This function should not be used by + * USB drivers. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_set_config_index(struct usb2_device *udev, uint8_t index) +{ + struct usb2_status ds; + struct usb2_hub_descriptor hd; + struct usb2_config_descriptor *cdp; + uint16_t power; + uint16_t max_power; + uint8_t nifc; + uint8_t selfpowered; + uint8_t do_unlock; + usb2_error_t err; + + DPRINTFN(6, "udev=%p index=%d\n", udev, index); + + /* automatic locking */ + if (sx_xlocked(udev->default_sx + 1)) { + do_unlock = 0; + } else { + do_unlock = 1; + sx_xlock(udev->default_sx + 1); + } + + /* detach all interface drivers */ + usb2_detach_device(udev, USB_IFACE_INDEX_ANY, 1); + + /* free all FIFOs except control endpoint FIFOs */ + usb2_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, 0); + + /* free all configuration data structures */ + usb2_free_iface_data(udev); + + if (index == USB_UNCONFIG_INDEX) { + /* + * Leave unallocated when unconfiguring the + * device. "usb2_free_iface_data()" will also reset + * the current config number and index. + */ + err = usb2_req_set_config(udev, &Giant, USB_UNCONFIG_NO); + goto done; + } + /* get the full config descriptor */ + err = usb2_req_get_config_desc_full(udev, + &Giant, &cdp, M_USB, index); + if (err) { + goto done; + } + /* set the new config descriptor */ + + udev->cdesc = cdp; + + if (cdp->bNumInterface > USB_IFACE_MAX) { + DPRINTFN(0, "too many interfaces: %d\n", cdp->bNumInterface); + cdp->bNumInterface = USB_IFACE_MAX; + } + /* Figure out if the device is self or bus powered. */ + selfpowered = 0; + if ((!udev->flags.uq_bus_powered) && + (cdp->bmAttributes & UC_SELF_POWERED) && + (udev->flags.usb2_mode == USB_MODE_HOST)) { + /* May be self powered. */ + if (cdp->bmAttributes & UC_BUS_POWERED) { + /* Must ask device. */ + if (udev->flags.uq_power_claim) { + /* + * HUB claims to be self powered, but isn't. + * It seems that the power status can be + * determined by the HUB characteristics. + */ + err = usb2_req_get_hub_descriptor + (udev, &Giant, &hd, 1); + if (err) { + DPRINTFN(0, "could not read " + "HUB descriptor: %s\n", + usb2_errstr(err)); + + } else if (UGETW(hd.wHubCharacteristics) & + UHD_PWR_INDIVIDUAL) { + selfpowered = 1; + } + DPRINTF("characteristics=0x%04x\n", + UGETW(hd.wHubCharacteristics)); + } else { + err = usb2_req_get_device_status + (udev, &Giant, &ds); + if (err) { + DPRINTFN(0, "could not read " + "device status: %s\n", + usb2_errstr(err)); + } else if (UGETW(ds.wStatus) & UDS_SELF_POWERED) { + selfpowered = 1; + } + DPRINTF("status=0x%04x \n", + UGETW(ds.wStatus)); + } + } else + selfpowered = 1; + } + DPRINTF("udev=%p cdesc=%p (addr %d) cno=%d attr=0x%02x, " + "selfpowered=%d, power=%d\n", + udev, cdp, + cdp->bConfigurationValue, udev->address, cdp->bmAttributes, + selfpowered, cdp->bMaxPower * 2); + + /* Check if we have enough power. */ + power = cdp->bMaxPower * 2; + + if (udev->parent_hub) { + max_power = udev->parent_hub->hub->portpower; + } else { + max_power = USB_MAX_POWER; + } + + if (power > max_power) { + DPRINTFN(0, "power exceeded %d > %d\n", power, max_power); + err = USB_ERR_NO_POWER; + goto done; + } + /* Only update "self_powered" in USB Host Mode */ + if (udev->flags.usb2_mode == USB_MODE_HOST) { + udev->flags.self_powered = selfpowered; + } + udev->power = power; + udev->curr_config_no = cdp->bConfigurationValue; + udev->curr_config_index = index; + + /* Set the actual configuration value. */ + err = usb2_req_set_config(udev, &Giant, cdp->bConfigurationValue); + if (err) { + goto done; + } + /* Allocate and fill interface data. */ + nifc = cdp->bNumInterface; + while (nifc--) { + err = usb2_fill_iface_data(udev, nifc, 0); + if (err) { + goto done; + } + } + +done: + DPRINTF("error=%s\n", usb2_errstr(err)); + if (err) { + usb2_free_iface_data(udev); + } + if (do_unlock) { + sx_unlock(udev->default_sx + 1); + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_set_alt_interface_index + * + * This function will select an alternate interface index for the + * given interface index. The interface should not be in use when this + * function is called. That means there should be no open USB + * transfers. Else an error is returned. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_set_alt_interface_index(struct usb2_device *udev, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + usb2_error_t err; + uint8_t do_unlock; + + /* automatic locking */ + if (sx_xlocked(udev->default_sx + 1)) { + do_unlock = 0; + } else { + do_unlock = 1; + sx_xlock(udev->default_sx + 1); + } + if (iface == NULL) { + err = USB_ERR_INVAL; + goto done; + } + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + usb2_detach_device(udev, iface_index, 1); + } + /* free all FIFOs for this interface */ + usb2_fifo_free_wrap(udev, iface_index, 0); + + err = usb2_fill_iface_data(udev, iface_index, alt_index); + if (err) { + goto done; + } + err = usb2_req_set_alt_interface_no + (udev, &Giant, iface_index, + iface->idesc->bAlternateSetting); + +done: + if (do_unlock) { + sx_unlock(udev->default_sx + 1); + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_set_endpoint_stall + * + * This function is used to make a BULK or INTERRUPT endpoint + * send STALL tokens. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_set_endpoint_stall(struct usb2_device *udev, struct usb2_pipe *pipe, + uint8_t do_stall) +{ + struct usb2_xfer *xfer; + uint8_t et; + uint8_t was_stalled; + + if (pipe == NULL) { + /* nothing to do */ + DPRINTF("Cannot find endpoint\n"); + /* + * Pretend that the clear or set stall request is + * successful else some USB host stacks can do + * strange things, especially when a control endpoint + * stalls. + */ + return (0); + } + et = (pipe->edesc->bmAttributes & UE_XFERTYPE); + + if ((et != UE_BULK) && + (et != UE_INTERRUPT)) { + /* + * Should not stall control + * nor isochronous endpoints. + */ + DPRINTF("Invalid endpoint\n"); + return (0); + } + mtx_lock(&udev->bus->mtx); + + /* store current stall state */ + was_stalled = pipe->is_stalled; + + /* check for no change */ + if (was_stalled && do_stall) { + /* if the pipe is already stalled do nothing */ + mtx_unlock(&udev->bus->mtx); + DPRINTF("No change\n"); + return (0); + } + /* set stalled state */ + pipe->is_stalled = 1; + + if (do_stall || (!was_stalled)) { + if (!was_stalled) { + /* lookup the current USB transfer, if any */ + xfer = pipe->pipe_q.curr; + } else { + xfer = NULL; + } + + /* + * If "xfer" is non-NULL the "set_stall" method will + * complete the USB transfer like in case of a timeout + * setting the error code "USB_ERR_STALLED". + */ + (udev->bus->methods->set_stall) (udev, xfer, pipe); + } + if (!do_stall) { + pipe->toggle_next = 0; /* reset data toggle */ + pipe->is_stalled = 0; /* clear stalled state */ + + (udev->bus->methods->clear_stall) (udev, pipe); + + /* start up the current or next transfer, if any */ + usb2_command_wrapper(&pipe->pipe_q, pipe->pipe_q.curr); + } + mtx_unlock(&udev->bus->mtx); + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_reset_iface_endpoints - used in USB device side mode + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_reset_iface_endpoints(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_pipe *pipe; + struct usb2_pipe *pipe_end; + usb2_error_t err; + + pipe = udev->pipes; + pipe_end = udev->pipes + USB_EP_MAX; + + for (; pipe != pipe_end; pipe++) { + + if ((pipe->edesc == NULL) || + (pipe->iface_index != iface_index)) { + continue; + } + /* simulate a clear stall from the peer */ + err = usb2_set_endpoint_stall(udev, pipe, 0); + if (err) { + /* just ignore */ + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_detach_device_sub + * + * This function will try to detach an USB device. If it fails a panic + * will result. + *------------------------------------------------------------------------*/ +static void +usb2_detach_device_sub(struct usb2_device *udev, device_t *ppdev, + uint8_t free_subdev) +{ + device_t dev; + int err; + + if (!free_subdev) { + + *ppdev = NULL; + + } else if (*ppdev) { + + /* + * NOTE: It is important to clear "*ppdev" before deleting + * the child due to some device methods being called late + * during the delete process ! + */ + dev = *ppdev; + *ppdev = NULL; + + device_printf(dev, "at %s, port %d, addr %d " + "(disconnected)\n", + device_get_nameunit(udev->parent_dev), + udev->port_no, udev->address); + + if (device_is_attached(dev)) { + if (udev->flags.suspended) { + err = DEVICE_RESUME(dev); + if (err) { + device_printf(dev, "Resume failed!\n"); + } + } + if (device_detach(dev)) { + goto error; + } + } + if (device_delete_child(udev->parent_dev, dev)) { + goto error; + } + } + return; + +error: + /* Detach is not allowed to fail in the USB world */ + panic("An USB driver would not detach!\n"); + return; +} + +/*------------------------------------------------------------------------* + * usb2_detach_device + * + * The following function will detach the matching interfaces. + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_detach_device(struct usb2_device *udev, uint8_t iface_index, + uint8_t free_subdev) +{ + struct usb2_interface *iface; + uint8_t i; + uint8_t do_unlock; + + if (udev == NULL) { + /* nothing to do */ + return; + } + DPRINTFN(4, "udev=%p\n", udev); + + /* automatic locking */ + if (sx_xlocked(udev->default_sx + 1)) { + do_unlock = 0; + } else { + do_unlock = 1; + sx_xlock(udev->default_sx + 1); + } + + /* + * First detach the child to give the child's detach routine a + * chance to detach the sub-devices in the correct order. + * Then delete the child using "device_delete_child()" which + * will detach all sub-devices from the bottom and upwards! + */ + if (iface_index != USB_IFACE_INDEX_ANY) { + i = iface_index; + iface_index = i + 1; + } else { + i = 0; + iface_index = USB_IFACE_MAX; + } + + /* do the detach */ + + for (; i != iface_index; i++) { + + iface = usb2_get_iface(udev, i); + if (iface == NULL) { + /* looks like the end of the USB interfaces */ + break; + } + usb2_detach_device_sub(udev, &iface->subdev, free_subdev); + } + + if (do_unlock) { + sx_unlock(udev->default_sx + 1); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_probe_and_attach_sub + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb2_probe_and_attach_sub(struct usb2_device *udev, + struct usb2_attach_arg *uaa) +{ + struct usb2_interface *iface; + device_t dev; + int err; + + iface = uaa->iface; + if (iface->parent_iface_index != USB_IFACE_INDEX_ANY) { + /* leave interface alone */ + return (0); + } + dev = iface->subdev; + if (dev) { + + /* clean up after module unload */ + + if (device_is_attached(dev)) { + /* already a device there */ + return (0); + } + /* clear "iface->subdev" as early as possible */ + + iface->subdev = NULL; + + if (device_delete_child(udev->parent_dev, dev)) { + + /* + * Panic here, else one can get a double call + * to device_detach(). USB devices should + * never fail on detach! + */ + panic("device_delete_child() failed!\n"); + } + } + if (uaa->temp_dev == NULL) { + + /* create a new child */ + uaa->temp_dev = device_add_child(udev->parent_dev, NULL, -1); + if (uaa->temp_dev == NULL) { + device_printf(udev->parent_dev, + "Device creation failed!\n"); + return (1); /* failure */ + } + device_set_ivars(uaa->temp_dev, uaa); + device_quiet(uaa->temp_dev); + } + /* + * Set "subdev" before probe and attach so that "devd" gets + * the information it needs. + */ + iface->subdev = uaa->temp_dev; + + if (device_probe_and_attach(iface->subdev) == 0) { + /* + * The USB attach arguments are only available during probe + * and attach ! + */ + uaa->temp_dev = NULL; + device_set_ivars(iface->subdev, NULL); + + if (udev->flags.suspended) { + err = DEVICE_SUSPEND(iface->subdev); + device_printf(iface->subdev, "Suspend failed\n"); + } + return (0); /* success */ + } else { + /* No USB driver found */ + iface->subdev = NULL; + } + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_set_parent_iface + * + * Using this function will lock the alternate interface setting on an + * interface. It is typically used for multi interface drivers. In USB + * device side mode it is assumed that the alternate interfaces all + * have the same endpoint descriptors. The default parent index value + * is "USB_IFACE_INDEX_ANY". Then the alternate setting value is not + * locked. + *------------------------------------------------------------------------*/ +void +usb2_set_parent_iface(struct usb2_device *udev, uint8_t iface_index, + uint8_t parent_index) +{ + struct usb2_interface *iface; + + iface = usb2_get_iface(udev, iface_index); + if (iface) { + iface->parent_iface_index = parent_index; + } + return; +} + +static void +usb2_init_attach_arg(struct usb2_device *udev, + struct usb2_attach_arg *uaa) +{ + bzero(uaa, sizeof(*uaa)); + + uaa->device = udev; + uaa->usb2_mode = udev->flags.usb2_mode; + uaa->port = udev->port_no; + + uaa->info.idVendor = UGETW(udev->ddesc.idVendor); + uaa->info.idProduct = UGETW(udev->ddesc.idProduct); + uaa->info.bcdDevice = UGETW(udev->ddesc.bcdDevice); + uaa->info.bDeviceClass = udev->ddesc.bDeviceClass; + uaa->info.bDeviceSubClass = udev->ddesc.bDeviceSubClass; + uaa->info.bDeviceProtocol = udev->ddesc.bDeviceProtocol; + uaa->info.bConfigIndex = udev->curr_config_index; + uaa->info.bConfigNum = udev->curr_config_no; + + return; +} + +/*------------------------------------------------------------------------* + * usb2_probe_and_attach + * + * This function is called from "uhub_explore_sub()", + * "usb2_handle_set_config()" and "usb2_handle_request()". + * + * Returns: + * 0: Success + * Else: A control transfer failed + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_probe_and_attach(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_attach_arg uaa; + struct usb2_interface *iface; + uint8_t i; + uint8_t j; + uint8_t do_unlock; + + if (udev == NULL) { + DPRINTF("udev == NULL\n"); + return (USB_ERR_INVAL); + } + /* automatic locking */ + if (sx_xlocked(udev->default_sx + 1)) { + do_unlock = 0; + } else { + do_unlock = 1; + sx_xlock(udev->default_sx + 1); + } + + if (udev->curr_config_index == USB_UNCONFIG_INDEX) { + /* do nothing - no configuration has been set */ + goto done; + } + /* setup USB attach arguments */ + + usb2_init_attach_arg(udev, &uaa); + + /* Check if only one interface should be probed: */ + if (iface_index != USB_IFACE_INDEX_ANY) { + i = iface_index; + j = i + 1; + } else { + i = 0; + j = USB_IFACE_MAX; + } + + /* Do the probe and attach */ + for (; i != j; i++) { + + iface = usb2_get_iface(udev, i); + if (iface == NULL) { + /* + * Looks like the end of the USB + * interfaces ! + */ + DPRINTFN(2, "end of interfaces " + "at %u\n", i); + break; + } + if (iface->idesc == NULL) { + /* no interface descriptor */ + continue; + } + uaa.iface = iface; + + uaa.info.bInterfaceClass = + iface->idesc->bInterfaceClass; + uaa.info.bInterfaceSubClass = + iface->idesc->bInterfaceSubClass; + uaa.info.bInterfaceProtocol = + iface->idesc->bInterfaceProtocol; + uaa.info.bIfaceIndex = i; + uaa.info.bIfaceNum = + iface->idesc->bInterfaceNumber; + uaa.use_generic = 0; + + DPRINTFN(2, "iclass=%u/%u/%u iindex=%u/%u\n", + uaa.info.bInterfaceClass, + uaa.info.bInterfaceSubClass, + uaa.info.bInterfaceProtocol, + uaa.info.bIfaceIndex, + uaa.info.bIfaceNum); + + /* try specific interface drivers first */ + + if (usb2_probe_and_attach_sub(udev, &uaa)) { + /* ignore */ + } + /* try generic interface drivers last */ + + uaa.use_generic = 1; + + if (usb2_probe_and_attach_sub(udev, &uaa)) { + /* ignore */ + } + } + + if (uaa.temp_dev) { + /* remove the last created child; it is unused */ + + if (device_delete_child(udev->parent_dev, uaa.temp_dev)) { + DPRINTFN(0, "device delete child failed!\n"); + } + } +done: + if (do_unlock) { + sx_unlock(udev->default_sx + 1); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_suspend_resume_sub + * + * This function is called when the suspend or resume methods should + * be executed on an USB device. + *------------------------------------------------------------------------*/ +static void +usb2_suspend_resume_sub(struct usb2_device *udev, device_t dev, uint8_t do_suspend) +{ + int err; + + if (dev == NULL) { + return; + } + if (!device_is_attached(dev)) { + return; + } + if (do_suspend) { + err = DEVICE_SUSPEND(dev); + } else { + err = DEVICE_RESUME(dev); + } + if (err) { + device_printf(dev, "%s failed!\n", + do_suspend ? "Suspend" : "Resume"); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_suspend_resume_device + * + * The following function will suspend or resume the USB device. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_suspend_resume(struct usb2_device *udev, uint8_t do_suspend) +{ + struct usb2_interface *iface; + uint8_t i; + + if (udev == NULL) { + /* nothing to do */ + return (0); + } + DPRINTFN(4, "udev=%p do_suspend=%d\n", udev, do_suspend); + + sx_assert(udev->default_sx + 1, SA_LOCKED); + + mtx_lock(&udev->bus->mtx); + /* filter the suspend events */ + if (udev->flags.suspended == do_suspend) { + mtx_unlock(&udev->bus->mtx); + /* nothing to do */ + return (0); + } + udev->flags.suspended = do_suspend; + mtx_unlock(&udev->bus->mtx); + + /* do the suspend or resume */ + + for (i = 0; i != USB_IFACE_MAX; i++) { + + iface = usb2_get_iface(udev, i); + if (iface == NULL) { + /* looks like the end of the USB interfaces */ + break; + } + usb2_suspend_resume_sub(udev, iface->subdev, do_suspend); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_clear_stall_proc + * + * This function performs generic USB clear stall operations. + *------------------------------------------------------------------------*/ +static void +usb2_clear_stall_proc(struct usb2_proc_msg *_pm) +{ + struct usb2_clear_stall_msg *pm = (void *)_pm; + struct usb2_device *udev = pm->udev; + + /* Change lock */ + mtx_unlock(&udev->bus->mtx); + mtx_lock(udev->default_mtx); + + /* Start clear stall callback */ + usb2_transfer_start(udev->default_xfer[1]); + + /* Change lock */ + mtx_unlock(udev->default_mtx); + mtx_lock(&udev->bus->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_alloc_device + * + * This function allocates a new USB device. This function is called + * when a new device has been put in the powered state, but not yet in + * the addressed state. Get initial descriptor, set the address, get + * full descriptor and get strings. + * + * Return values: + * 0: Failure + * Else: Success + *------------------------------------------------------------------------*/ +struct usb2_device * +usb2_alloc_device(device_t parent_dev, struct usb2_bus *bus, + struct usb2_device *parent_hub, uint8_t depth, + uint8_t port_index, uint8_t port_no, uint8_t speed, uint8_t usb2_mode) +{ + struct usb2_attach_arg uaa; + struct usb2_device *udev; + struct usb2_device *adev; + struct usb2_device *hub; + uint8_t *scratch_ptr; + uint32_t scratch_size; + usb2_error_t err; + uint8_t device_index; + + DPRINTF("parent_dev=%p, bus=%p, parent_hub=%p, depth=%u, " + "port_index=%u, port_no=%u, speed=%u, usb2_mode=%u\n", + parent_dev, bus, parent_hub, depth, port_index, port_no, + speed, usb2_mode); + + /* + * Find an unused device index. In USB Host mode this is the + * same as the device address. + * + * NOTE: Index 1 is reserved for the Root HUB. + */ + for (device_index = USB_ROOT_HUB_ADDR; device_index != + USB_MAX_DEVICES; device_index++) { + if (bus->devices[device_index] == NULL) + break; + } + + if (device_index == USB_MAX_DEVICES) { + device_printf(bus->bdev, + "No free USB device index for new device!\n"); + return (NULL); + } + if (depth > 0x10) { + device_printf(bus->bdev, + "Invalid device depth!\n"); + return (NULL); + } + udev = malloc(sizeof(*udev), M_USB, M_WAITOK | M_ZERO); + if (udev == NULL) { + return (NULL); + } + /* initialise our SX-lock */ + sx_init(udev->default_sx, "0123456789ABCDEF - USB device SX lock" + depth); + + /* initialise our SX-lock */ + sx_init(udev->default_sx + 1, "0123456789ABCDEF - USB config SX lock" + depth); + + usb2_cv_init(udev->default_cv, "WCTRL"); + usb2_cv_init(udev->default_cv + 1, "UGONE"); + + /* initialise our mutex */ + mtx_init(udev->default_mtx, "USB device mutex", NULL, MTX_DEF); + + /* initialise generic clear stall */ + udev->cs_msg[0].hdr.pm_callback = &usb2_clear_stall_proc; + udev->cs_msg[0].udev = udev; + udev->cs_msg[1].hdr.pm_callback = &usb2_clear_stall_proc; + udev->cs_msg[1].udev = udev; + + /* initialise some USB device fields */ + udev->parent_hub = parent_hub; + udev->parent_dev = parent_dev; + udev->port_index = port_index; + udev->port_no = port_no; + udev->depth = depth; + udev->bus = bus; + udev->address = USB_START_ADDR; /* default value */ + udev->plugtime = (uint32_t)ticks; + udev->power_mode = USB_POWER_MODE_ON; + + /* we are not ready yet */ + udev->refcount = 1; + + /* set up default endpoint descriptor */ + udev->default_ep_desc.bLength = sizeof(udev->default_ep_desc); + udev->default_ep_desc.bDescriptorType = UDESC_ENDPOINT; + udev->default_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; + udev->default_ep_desc.bmAttributes = UE_CONTROL; + udev->default_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET; + udev->default_ep_desc.wMaxPacketSize[1] = 0; + udev->default_ep_desc.bInterval = 0; + udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET; + + udev->speed = speed; + udev->flags.usb2_mode = usb2_mode; + + /* check speed combination */ + + hub = udev->parent_hub; + if (hub) { + if (usb2_hub_speed_combs[hub->speed][speed] == 0) { +#if USB_DEBUG + printf("%s: the selected subdevice and HUB speed " + "combination is not supported %d/%d.\n", + __FUNCTION__, speed, hub->speed); +#endif + /* reject this combination */ + err = USB_ERR_INVAL; + goto done; + } + } + /* search for our High Speed USB HUB, if any */ + + adev = udev; + hub = udev->parent_hub; + + while (hub) { + if (hub->speed == USB_SPEED_HIGH) { + udev->hs_hub_addr = hub->address; + udev->hs_port_no = adev->port_no; + break; + } + adev = hub; + hub = hub->parent_hub; + } + + /* init the default pipe */ + usb2_fill_pipe_data(udev, 0, + &udev->default_ep_desc, + &udev->default_pipe); + + /* set device index */ + udev->device_index = device_index; + + if (udev->flags.usb2_mode == USB_MODE_HOST) { + + err = usb2_req_set_address(udev, &Giant, device_index); + + /* This is the new USB device address from now on */ + + udev->address = device_index; + + /* + * We ignore any set-address errors, hence there are + * buggy USB devices out there that actually receive + * the SETUP PID, but manage to set the address before + * the STATUS stage is ACK'ed. If the device responds + * to the subsequent get-descriptor at the new + * address, then we know that the set-address command + * was successful. + */ + if (err) { + DPRINTFN(0, "set address %d failed " + "(ignored)\n", udev->address); + } + /* allow device time to set new address */ + usb2_pause_mtx(&Giant, USB_SET_ADDRESS_SETTLE); + } else { + /* We are not self powered */ + udev->flags.self_powered = 0; + + /* Set unconfigured state */ + udev->curr_config_no = USB_UNCONFIG_NO; + udev->curr_config_index = USB_UNCONFIG_INDEX; + + /* Setup USB descriptors */ + err = (usb2_temp_setup_by_index_p) (udev, usb2_template); + if (err) { + DPRINTFN(0, "setting up USB template failed maybe the USB " + "template module has not been loaded\n"); + goto done; + } + } + + /* + * Get the first 8 bytes of the device descriptor ! + * + * NOTE: "usb2_do_request" will check the device descriptor + * next time we do a request to see if the maximum packet size + * changed! The 8 first bytes of the device descriptor + * contains the maximum packet size to use on control endpoint + * 0. If this value is different from "USB_MAX_IPACKET" a new + * USB control request will be setup! + */ + err = usb2_req_get_desc(udev, &Giant, &udev->ddesc, + USB_MAX_IPACKET, USB_MAX_IPACKET, 0, UDESC_DEVICE, 0, 0); + if (err) { + DPRINTFN(0, "getting device descriptor " + "at addr %d failed!\n", udev->address); + goto done; + } + DPRINTF("adding unit addr=%d, rev=%02x, class=%d, " + "subclass=%d, protocol=%d, maxpacket=%d, len=%d, speed=%d\n", + udev->address, UGETW(udev->ddesc.bcdUSB), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + udev->ddesc.bDeviceProtocol, + udev->ddesc.bMaxPacketSize, + udev->ddesc.bLength, + udev->speed); + + /* get the full device descriptor */ + err = usb2_req_get_device_desc(udev, &Giant, &udev->ddesc); + if (err) { + DPRINTF("addr=%d, getting full desc failed\n", + udev->address); + goto done; + } + /* + * Setup temporary USB attach args so that we can figure out some + * basic quirks for this device. + */ + usb2_init_attach_arg(udev, &uaa); + + if (usb2_test_quirk(&uaa, UQ_BUS_POWERED)) { + udev->flags.uq_bus_powered = 1; + } + if (usb2_test_quirk(&uaa, UQ_POWER_CLAIM)) { + udev->flags.uq_power_claim = 1; + } + if (usb2_test_quirk(&uaa, UQ_NO_STRINGS)) { + udev->flags.no_strings = 1; + } + /* + * Workaround for buggy USB devices. + * + * It appears that some string-less USB chips will crash and + * disappear if any attempts are made to read any string + * descriptors. + * + * Try to detect such chips by checking the strings in the USB + * device descriptor. If no strings are present there we + * simply disable all USB strings. + */ + scratch_ptr = udev->bus->scratch[0].data; + scratch_size = sizeof(udev->bus->scratch[0].data); + + if (udev->ddesc.iManufacturer || + udev->ddesc.iProduct || + udev->ddesc.iSerialNumber) { + /* read out the language ID string */ + err = usb2_req_get_string_desc(udev, &Giant, + (char *)scratch_ptr, 4, scratch_size, + USB_LANGUAGE_TABLE); + } else { + err = USB_ERR_INVAL; + } + + if (err || (scratch_ptr[0] < 4)) { + udev->flags.no_strings = 1; + } else { + /* pick the first language as the default */ + udev->langid = UGETW(scratch_ptr + 2); + } + + /* assume 100mA bus powered for now. Changed when configured. */ + udev->power = USB_MIN_POWER; + + /* get serial number string */ + err = usb2_req_get_string_any + (udev, &Giant, (char *)scratch_ptr, + scratch_size, udev->ddesc.iSerialNumber); + + strlcpy(udev->serial, (char *)scratch_ptr, sizeof(udev->serial)); + + /* get manufacturer string */ + err = usb2_req_get_string_any + (udev, &Giant, (char *)scratch_ptr, + scratch_size, udev->ddesc.iManufacturer); + + strlcpy(udev->manufacturer, (char *)scratch_ptr, sizeof(udev->manufacturer)); + + /* get product string */ + err = usb2_req_get_string_any + (udev, &Giant, (char *)scratch_ptr, + scratch_size, udev->ddesc.iProduct); + + strlcpy(udev->product, (char *)scratch_ptr, sizeof(udev->product)); + + /* finish up all the strings */ + usb2_check_strings(udev); + + if (udev->flags.usb2_mode == USB_MODE_HOST) { + uint8_t config_index; + uint8_t config_quirk; + + /* + * Most USB devices should attach to config index 0 by + * default + */ + if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_0)) { + config_index = 0; + config_quirk = 1; + } else if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_1)) { + config_index = 1; + config_quirk = 1; + } else if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_2)) { + config_index = 2; + config_quirk = 1; + } else if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_3)) { + config_index = 3; + config_quirk = 1; + } else if (usb2_test_quirk(&uaa, UQ_CFG_INDEX_4)) { + config_index = 4; + config_quirk = 1; + } else { + config_index = 0; + config_quirk = 0; + } + +repeat_set_config: + + DPRINTF("setting config %u\n", config_index); + + /* get the USB device configured */ + sx_xlock(udev->default_sx + 1); + err = usb2_set_config_index(udev, config_index); + sx_unlock(udev->default_sx + 1); + if (err) { + DPRINTFN(0, "Failure selecting " + "configuration index %u: %s, port %u, addr %u\n", + config_index, usb2_errstr(err), udev->port_no, + udev->address); + + } else if (config_quirk) { + /* user quirk selects configuration index */ + } else if ((config_index + 1) < udev->ddesc.bNumConfigurations) { + + if ((udev->cdesc->bNumInterface < 2) && + (usb2_get_no_endpoints(udev->cdesc) == 0)) { + DPRINTFN(0, "Found no endpoints " + "(trying next config)!\n"); + config_index++; + goto repeat_set_config; + } + if (config_index == 0) { + /* + * Try to figure out if we have an + * auto-install disk there: + */ + if (usb2_test_autoinstall(udev, 0) == 0) { + DPRINTFN(0, "Found possible auto-install " + "disk (trying next config)\n"); + config_index++; + goto repeat_set_config; + } + } + } else if (UGETW(udev->ddesc.idVendor) == USB_VENDOR_HUAWEI) { + if (usb2_test_huawei(udev, 0) == 0) { + DPRINTFN(0, "Found Huawei auto-install disk!\n"); + err = USB_ERR_STALLED; /* fake an error */ + } + } + } else { + err = 0; /* set success */ + } + + DPRINTF("new dev (addr %d), udev=%p, parent_hub=%p\n", + udev->address, udev, udev->parent_hub); + + /* register our device - we are ready */ + usb2_bus_port_set_device(bus, parent_hub ? + parent_hub->hub->ports + port_index : NULL, udev, device_index); + + /* make a symlink for UGEN */ + if (snprintf((char *)scratch_ptr, scratch_size, + USB_DEVICE_NAME "%u.%u.0.0", + device_get_unit(udev->bus->bdev), + udev->device_index)) { + /* ignore */ + } + udev->ugen_symlink = + usb2_alloc_symlink((char *)scratch_ptr, "ugen%u.%u", + device_get_unit(udev->bus->bdev), + udev->device_index); + + printf("ugen%u.%u: <%s> at %s\n", + device_get_unit(udev->bus->bdev), + udev->device_index, udev->manufacturer, + device_get_nameunit(udev->bus->bdev)); + + usb2_notify_addq("+", udev); +done: + if (err) { + /* free device */ + usb2_free_device(udev); + udev = NULL; + } + return (udev); +} + +/*------------------------------------------------------------------------* + * usb2_free_device + * + * This function is NULL safe and will free an USB device. + *------------------------------------------------------------------------*/ +void +usb2_free_device(struct usb2_device *udev) +{ + struct usb2_bus *bus; + + if (udev == NULL) { + /* already freed */ + return; + } + DPRINTFN(4, "udev=%p port=%d\n", udev, udev->port_no); + + usb2_notify_addq("-", udev); + + bus = udev->bus; + + /* + * Destroy UGEN symlink, if any + */ + if (udev->ugen_symlink) { + usb2_free_symlink(udev->ugen_symlink); + udev->ugen_symlink = NULL; + } + /* + * Unregister our device first which will prevent any further + * references: + */ + usb2_bus_port_set_device(bus, udev->parent_hub ? + udev->parent_hub->hub->ports + udev->port_index : NULL, + NULL, USB_ROOT_HUB_ADDR); + + /* wait for all pending references to go away: */ + + mtx_lock(&usb2_ref_lock); + udev->refcount--; + while (udev->refcount != 0) { + usb2_cv_wait(udev->default_cv + 1, &usb2_ref_lock); + } + mtx_unlock(&usb2_ref_lock); + + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + /* stop receiving any control transfers (Device Side Mode) */ + usb2_transfer_unsetup(udev->default_xfer, USB_DEFAULT_XFER_MAX); + } + /* free all FIFOs */ + usb2_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, 1); + + /* + * Free all interface related data and FIFOs, if any. + */ + usb2_free_iface_data(udev); + + /* unsetup any leftover default USB transfers */ + usb2_transfer_unsetup(udev->default_xfer, USB_DEFAULT_XFER_MAX); + + (usb2_temp_unsetup_p) (udev); + + sx_destroy(udev->default_sx); + sx_destroy(udev->default_sx + 1); + + usb2_cv_destroy(udev->default_cv); + usb2_cv_destroy(udev->default_cv + 1); + + mtx_destroy(udev->default_mtx); + + /* free device */ + free(udev, M_USB); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_get_iface + * + * This function is the safe way to get the USB interface structure + * pointer by interface index. + * + * Return values: + * NULL: Interface not present. + * Else: Pointer to USB interface structure. + *------------------------------------------------------------------------*/ +struct usb2_interface * +usb2_get_iface(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_interface *iface = udev->ifaces + iface_index; + + if ((iface < udev->ifaces) || + (iface_index >= USB_IFACE_MAX) || + (udev->cdesc == NULL) || + (iface_index >= udev->cdesc->bNumInterface)) { + return (NULL); + } + return (iface); +} + +/*------------------------------------------------------------------------* + * usb2_find_descriptor + * + * This function will lookup the first descriptor that matches the + * criteria given by the arguments "type" and "subtype". Descriptors + * will only be searched within the interface having the index + * "iface_index". If the "id" argument points to an USB descriptor, + * it will be skipped before the search is started. This allows + * searching for multiple descriptors using the same criteria. Else + * the search is started after the interface descriptor. + * + * Return values: + * NULL: End of descriptors + * Else: A descriptor matching the criteria + *------------------------------------------------------------------------*/ +void * +usb2_find_descriptor(struct usb2_device *udev, void *id, uint8_t iface_index, + uint8_t type, uint8_t type_mask, + uint8_t subtype, uint8_t subtype_mask) +{ + struct usb2_descriptor *desc; + struct usb2_config_descriptor *cd; + struct usb2_interface *iface; + + cd = usb2_get_config_descriptor(udev); + if (cd == NULL) { + return (NULL); + } + if (id == NULL) { + iface = usb2_get_iface(udev, iface_index); + if (iface == NULL) { + return (NULL); + } + id = usb2_get_interface_descriptor(iface); + if (id == NULL) { + return (NULL); + } + } + desc = (void *)id; + + while ((desc = usb2_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + if (((desc->bDescriptorType & type_mask) == type) && + ((desc->bDescriptorSubtype & subtype_mask) == subtype)) { + return (desc); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_devinfo + * + * This function will dump information from the device descriptor + * belonging to the USB device pointed to by "udev", to the string + * pointed to by "dst_ptr" having a maximum length of "dst_len" bytes + * including the terminating zero. + *------------------------------------------------------------------------*/ +void +usb2_devinfo(struct usb2_device *udev, char *dst_ptr, uint16_t dst_len) +{ + struct usb2_device_descriptor *udd = &udev->ddesc; + uint16_t bcdDevice; + uint16_t bcdUSB; + + bcdUSB = UGETW(udd->bcdUSB); + bcdDevice = UGETW(udd->bcdDevice); + + if (udd->bDeviceClass != 0xFF) { + snprintf(dst_ptr, dst_len, "%s %s, class %d/%d, rev %x.%02x/" + "%x.%02x, addr %d", udev->manufacturer, udev->product, + udd->bDeviceClass, udd->bDeviceSubClass, + (bcdUSB >> 8), bcdUSB & 0xFF, + (bcdDevice >> 8), bcdDevice & 0xFF, + udev->address); + } else { + snprintf(dst_ptr, dst_len, "%s %s, rev %x.%02x/" + "%x.%02x, addr %d", udev->manufacturer, udev->product, + (bcdUSB >> 8), bcdUSB & 0xFF, + (bcdDevice >> 8), bcdDevice & 0xFF, + udev->address); + } + return; +} + +#if USB_VERBOSE +/* + * Descriptions of of known vendors and devices ("products"). + */ +struct usb_knowndev { + uint16_t vendor; + uint16_t product; + uint32_t flags; + const char *vendorname; + const char *productname; +}; + +#define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */ + +#include +#include +#endif /* USB_VERBOSE */ + +/*------------------------------------------------------------------------* + * usb2_check_strings + * + * This function checks the manufacturer and product strings and will + * fill in defaults for missing strings. + *------------------------------------------------------------------------*/ +static void +usb2_check_strings(struct usb2_device *udev) +{ + struct usb2_device_descriptor *udd = &udev->ddesc; + const char *vendor; + const char *product; + +#if USB_VERBOSE + const struct usb_knowndev *kdp; + +#endif + uint16_t vendor_id; + uint16_t product_id; + + usb2_trim_spaces(udev->manufacturer); + usb2_trim_spaces(udev->product); + + if (udev->manufacturer[0]) { + vendor = udev->manufacturer; + } else { + vendor = NULL; + } + + if (udev->product[0]) { + product = udev->product; + } else { + product = NULL; + } + + vendor_id = UGETW(udd->idVendor); + product_id = UGETW(udd->idProduct); + +#if USB_VERBOSE + if (vendor == NULL || product == NULL) { + + for (kdp = usb_knowndevs; + kdp->vendorname != NULL; + kdp++) { + if (kdp->vendor == vendor_id && + (kdp->product == product_id || + (kdp->flags & USB_KNOWNDEV_NOPROD) != 0)) + break; + } + if (kdp->vendorname != NULL) { + if (vendor == NULL) + vendor = kdp->vendorname; + if (product == NULL) + product = (kdp->flags & USB_KNOWNDEV_NOPROD) == 0 ? + kdp->productname : NULL; + } + } +#endif + if (vendor && *vendor) { + if (udev->manufacturer != vendor) { + strlcpy(udev->manufacturer, vendor, + sizeof(udev->manufacturer)); + } + } else { + snprintf(udev->manufacturer, + sizeof(udev->manufacturer), "vendor 0x%04x", vendor_id); + } + + if (product && *product) { + if (udev->product != product) { + strlcpy(udev->product, product, + sizeof(udev->product)); + } + } else { + snprintf(udev->product, + sizeof(udev->product), "product 0x%04x", product_id); + } + return; +} + +uint8_t +usb2_get_speed(struct usb2_device *udev) +{ + return (udev->speed); +} + +struct usb2_device_descriptor * +usb2_get_device_descriptor(struct usb2_device *udev) +{ + if (udev == NULL) + return (NULL); /* be NULL safe */ + return (&udev->ddesc); +} + +struct usb2_config_descriptor * +usb2_get_config_descriptor(struct usb2_device *udev) +{ + if (udev == NULL) + return (NULL); /* be NULL safe */ + return (udev->cdesc); +} + +/*------------------------------------------------------------------------* + * usb2_test_quirk - test a device for a given quirk + * + * Return values: + * 0: The USB device does not have the given quirk. + * Else: The USB device has the given quirk. + *------------------------------------------------------------------------*/ +uint8_t +usb2_test_quirk(const struct usb2_attach_arg *uaa, uint16_t quirk) +{ + uint8_t found; + + found = (usb2_test_quirk_p) (&uaa->info, quirk); + return (found); +} + +struct usb2_interface_descriptor * +usb2_get_interface_descriptor(struct usb2_interface *iface) +{ + if (iface == NULL) + return (NULL); /* be NULL safe */ + return (iface->idesc); +} + +uint8_t +usb2_get_interface_altindex(struct usb2_interface *iface) +{ + return (iface->alt_index); +} + +uint8_t +usb2_get_bus_index(struct usb2_device *udev) +{ + return ((uint8_t)device_get_unit(udev->bus->bdev)); +} + +uint8_t +usb2_get_device_index(struct usb2_device *udev) +{ + return (udev->device_index); +} + +/*------------------------------------------------------------------------* + * usb2_notify_addq + * + * This function will generate events for dev. + *------------------------------------------------------------------------*/ +static void +usb2_notify_addq(const char *type, struct usb2_device *udev) +{ + char *data = NULL; + struct malloc_type *mt; + + mtx_lock(&malloc_mtx); + mt = malloc_desc2type("bus"); /* XXX M_BUS */ + mtx_unlock(&malloc_mtx); + if (mt == NULL) + return; + + data = malloc(512, mt, M_NOWAIT); + if (data == NULL) + return; + + /* String it all together. */ + if (udev->parent_hub) { + snprintf(data, 1024, + "%s" + "ugen%u.%u " + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "at " + "port=%u " + "on " + "ugen%u.%u\n", + type, + device_get_unit(udev->bus->bdev), + udev->device_index, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + udev->serial, + udev->port_no, + device_get_unit(udev->bus->bdev), + udev->parent_hub->device_index); + } else { + snprintf(data, 1024, + "%s" + "ugen%u.%u " + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "at port=%u " + "on " + "%s\n", + type, + device_get_unit(udev->bus->bdev), + udev->device_index, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + udev->serial, + udev->port_no, + device_get_nameunit(device_get_parent(udev->bus->bdev))); + } + devctl_queue_data(data); + return; +} + +/*------------------------------------------------------------------------* + * usb2_fifo_free_wrap + * + * The function will free the FIFOs. + *------------------------------------------------------------------------*/ +static void +usb2_fifo_free_wrap(struct usb2_device *udev, + uint8_t iface_index, uint8_t free_all) +{ + struct usb2_fifo *f; + struct usb2_pipe *pipe; + uint16_t i; + + /* + * Free any USB FIFOs on the given interface: + */ + for (i = 0; i != USB_FIFO_MAX; i++) { + f = udev->fifo[i]; + if (f == NULL) { + continue; + } + pipe = f->priv_sc0; + if ((pipe == &udev->default_pipe) && (free_all == 0)) { + /* don't free UGEN control endpoint yet */ + continue; + } + /* Check if the interface index matches */ + if ((iface_index == f->iface_index) || + (iface_index == USB_IFACE_INDEX_ANY)) { + usb2_fifo_free(f); + } + } + return; +} diff --git a/sys/dev/usb2/core/usb2_device.h b/sys/dev/usb2/core/usb2_device.h new file mode 100644 index 000000000000..3c5225c226d3 --- /dev/null +++ b/sys/dev/usb2/core/usb2_device.h @@ -0,0 +1,162 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_DEVICE_H_ +#define _USB2_DEVICE_H_ + +struct usb2_symlink; + +#define USB_DEFAULT_XFER_MAX 2 + +struct usb2_clear_stall_msg { + struct usb2_proc_msg hdr; + struct usb2_device *udev; +}; + +/* + * The following structure defines an USB pipe which is equal to an + * USB endpoint. + */ +struct usb2_pipe { + struct usb2_xfer_queue pipe_q; /* queue of USB transfers */ + + struct usb2_xfer *xfer_block; /* blocking USB transfer */ + struct usb2_endpoint_descriptor *edesc; + struct usb2_pipe_methods *methods; /* set by HC driver */ + + uint16_t isoc_next; + uint16_t refcount; + + uint8_t toggle_next:1; /* next data toggle value */ + uint8_t is_stalled:1; /* set if pipe is stalled */ + uint8_t is_synced:1; /* set if we a synchronised */ + uint8_t unused:5; + uint8_t iface_index; /* not used by "default pipe" */ +}; + +/* + * The following structure defines an USB interface. + */ +struct usb2_interface { + struct usb2_perm perm; /* interface permissions */ + struct usb2_interface_descriptor *idesc; + device_t subdev; + uint8_t alt_index; + uint8_t parent_iface_index; +}; + +/* + * The following structure defines the USB device flags. + */ +struct usb2_device_flags { + uint8_t usb2_mode:1; /* USB mode (see USB_MODE_XXX) */ + uint8_t self_powered:1; /* set if USB device is self powered */ + uint8_t suspended:1; /* set if USB device is suspended */ + uint8_t no_strings:1; /* set if USB device does not support + * strings */ + uint8_t remote_wakeup:1; /* set if remote wakeup is enabled */ + uint8_t uq_bus_powered:1; /* set if BUS powered quirk is present */ + uint8_t uq_power_claim:1; /* set if power claim quirk is present */ +}; + +/* + * The following structure defines an USB device. There exists one of + * these structures for every USB device. + */ +struct usb2_device { + struct usb2_clear_stall_msg cs_msg[2]; /* generic clear stall + * messages */ + struct usb2_perm perm; + struct sx default_sx[2]; + struct mtx default_mtx[1]; + struct cv default_cv[2]; + struct usb2_interface ifaces[USB_IFACE_MAX]; + struct usb2_pipe default_pipe; /* Control Endpoint 0 */ + struct usb2_pipe pipes[USB_EP_MAX]; + + struct usb2_bus *bus; /* our USB BUS */ + device_t parent_dev; /* parent device */ + struct usb2_device *parent_hub; + struct usb2_config_descriptor *cdesc; /* full config descr */ + struct usb2_hub *hub; /* only if this is a hub */ + struct usb_device *linux_dev; + struct usb2_xfer *default_xfer[USB_DEFAULT_XFER_MAX]; + struct usb2_temp_data *usb2_template_ptr; + struct usb2_pipe *pipe_curr; /* current clear stall pipe */ + struct usb2_fifo *fifo[USB_FIFO_MAX]; + struct usb2_symlink *ugen_symlink; /* our generic symlink */ + + uint32_t plugtime; /* copy of "ticks" */ + + uint16_t refcount; +#define USB_DEV_REF_MAX 0xffff + + uint16_t power; /* mA the device uses */ + uint16_t langid; /* language for strings */ + + uint8_t address; /* device addess */ + uint8_t device_index; /* device index in "bus->devices" */ + uint8_t curr_config_index; /* current configuration index */ + uint8_t curr_config_no; /* current configuration number */ + uint8_t depth; /* distance from root HUB */ + uint8_t speed; /* low/full/high speed */ + uint8_t port_index; /* parent HUB port index */ + uint8_t port_no; /* parent HUB port number */ + uint8_t hs_hub_addr; /* high-speed HUB address */ + uint8_t hs_port_no; /* high-speed HUB port number */ + uint8_t driver_added_refcount; /* our driver added generation count */ + uint8_t power_mode; /* see USB_POWER_XXX */ + + /* the "flags" field is write-protected by "bus->mtx" */ + + struct usb2_device_flags flags; + + struct usb2_endpoint_descriptor default_ep_desc; /* for pipe 0 */ + struct usb2_device_descriptor ddesc; /* device descriptor */ + + char serial[64]; /* serial number */ + char manufacturer[64]; /* manufacturer string */ + char product[64]; /* product string */ +}; + +/* function prototypes */ + +struct usb2_device *usb2_alloc_device(device_t parent_dev, struct usb2_bus *bus, struct usb2_device *parent_hub, uint8_t depth, uint8_t port_index, uint8_t port_no, uint8_t speed, uint8_t usb2_mode); +struct usb2_pipe *usb2_get_pipe(struct usb2_device *udev, uint8_t iface_index, const struct usb2_config *setup); +struct usb2_pipe *usb2_get_pipe_by_addr(struct usb2_device *udev, uint8_t ea_val); +usb2_error_t usb2_interface_count(struct usb2_device *udev, uint8_t *count); +usb2_error_t usb2_probe_and_attach(struct usb2_device *udev, uint8_t iface_index); +usb2_error_t usb2_reset_iface_endpoints(struct usb2_device *udev, uint8_t iface_index); +usb2_error_t usb2_set_config_index(struct usb2_device *udev, uint8_t index); +usb2_error_t usb2_set_endpoint_stall(struct usb2_device *udev, struct usb2_pipe *pipe, uint8_t do_stall); +usb2_error_t usb2_suspend_resume(struct usb2_device *udev, uint8_t do_suspend); +void usb2_detach_device(struct usb2_device *udev, uint8_t iface_index, uint8_t free_subdev); +void usb2_devinfo(struct usb2_device *udev, char *dst_ptr, uint16_t dst_len); +void usb2_free_device(struct usb2_device *udev); +void *usb2_find_descriptor(struct usb2_device *udev, void *id, uint8_t iface_index, uint8_t type, uint8_t type_mask, uint8_t subtype, uint8_t subtype_mask); +void usb_linux_free_device(struct usb_device *dev); + +#endif /* _USB2_DEVICE_H_ */ diff --git a/sys/dev/usb2/core/usb2_dynamic.c b/sys/dev/usb2/core/usb2_dynamic.c new file mode 100644 index 000000000000..2bd4b01331ce --- /dev/null +++ b/sys/dev/usb2/core/usb2_dynamic.c @@ -0,0 +1,140 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* function prototypes */ +static usb2_temp_get_desc_t usb2_temp_get_desc_w; +static usb2_temp_setup_by_index_t usb2_temp_setup_by_index_w; +static usb2_temp_unsetup_t usb2_temp_unsetup_w; +static usb2_test_quirk_t usb2_test_quirk_w; +static usb2_quirk_ioctl_t usb2_quirk_ioctl_w; + +/* global variables */ +usb2_temp_get_desc_t *usb2_temp_get_desc_p = &usb2_temp_get_desc_w; +usb2_temp_setup_by_index_t *usb2_temp_setup_by_index_p = &usb2_temp_setup_by_index_w; +usb2_temp_unsetup_t *usb2_temp_unsetup_p = &usb2_temp_unsetup_w; +usb2_test_quirk_t *usb2_test_quirk_p = &usb2_test_quirk_w; +usb2_quirk_ioctl_t *usb2_quirk_ioctl_p = &usb2_quirk_ioctl_w; +devclass_t usb2_devclass_ptr = NULL; + +static usb2_error_t +usb2_temp_setup_by_index_w(struct usb2_device *udev, uint16_t index) +{ + return (USB_ERR_INVAL); +} + +static uint8_t +usb2_test_quirk_w(const struct usb2_lookup_info *info, uint16_t quirk) +{ + return (0); /* no match */ +} + +static int +usb2_quirk_ioctl_w(unsigned long cmd, caddr_t data, int fflag, struct thread *td) +{ + return (ENOIOCTL); +} + +static void +usb2_temp_get_desc_w(struct usb2_device *udev, struct usb2_device_request *req, const void **pPtr, uint16_t *pLength) +{ + /* stall */ + *pPtr = NULL; + *pLength = 0; + return; +} + +static void +usb2_temp_unsetup_w(struct usb2_device *udev) +{ + if (udev->usb2_template_ptr) { + + free(udev->usb2_template_ptr, M_USB); + + udev->usb2_template_ptr = NULL; + } + return; +} + +void +usb2_quirk_unload(void *arg) +{ + /* reset function pointers */ + + usb2_test_quirk_p = &usb2_test_quirk_w; + usb2_quirk_ioctl_p = &usb2_quirk_ioctl_w; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); + + return; +} + +void +usb2_temp_unload(void *arg) +{ + /* reset function pointers */ + + usb2_temp_get_desc_p = &usb2_temp_get_desc_w; + usb2_temp_setup_by_index_p = &usb2_temp_setup_by_index_w; + usb2_temp_unsetup_p = &usb2_temp_unsetup_w; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); + + return; +} + +void +usb2_bus_unload(void *arg) +{ + /* reset function pointers */ + + usb2_devclass_ptr = NULL; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); + + return; +} diff --git a/sys/dev/usb2/core/usb2_dynamic.h b/sys/dev/usb2/core/usb2_dynamic.h new file mode 100644 index 000000000000..ca59f96e2d00 --- /dev/null +++ b/sys/dev/usb2/core/usb2_dynamic.h @@ -0,0 +1,61 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_DYNAMIC_H_ +#define _USB2_DYNAMIC_H_ + +/* prototypes */ + +struct usb2_device; +struct usb2_lookup_info; +struct usb2_device_request; + +/* typedefs */ + +typedef usb2_error_t (usb2_temp_setup_by_index_t)(struct usb2_device *udev, uint16_t index); +typedef uint8_t (usb2_test_quirk_t)(const struct usb2_lookup_info *info, uint16_t quirk); +typedef int (usb2_quirk_ioctl_t)(unsigned long cmd, caddr_t data, int fflag, struct thread *td); +typedef void (usb2_temp_get_desc_t)(struct usb2_device *udev, struct usb2_device_request *req, const void **pPtr, uint16_t *pLength); +typedef void (usb2_temp_unsetup_t)(struct usb2_device *udev); + +/* global function pointers */ + +extern usb2_temp_get_desc_t *usb2_temp_get_desc_p; +extern usb2_temp_setup_by_index_t *usb2_temp_setup_by_index_p; +extern usb2_temp_unsetup_t *usb2_temp_unsetup_p; +extern usb2_test_quirk_t *usb2_test_quirk_p; +extern usb2_quirk_ioctl_t *usb2_quirk_ioctl_p; +extern devclass_t usb2_devclass_ptr; + +/* function prototypes */ + +void usb2_temp_unload(void *); +void usb2_quirk_unload(void *); +void usb2_bus_unload(void *); + +uint8_t usb2_test_quirk(const struct usb2_attach_arg *uaa, uint16_t quirk); + +#endif /* _USB2_DYNAMIC_H_ */ diff --git a/sys/dev/usb2/core/usb2_error.c b/sys/dev/usb2/core/usb2_error.c new file mode 100644 index 000000000000..51668599f2d8 --- /dev/null +++ b/sys/dev/usb2/core/usb2_error.c @@ -0,0 +1,44 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +#include + +USB_MAKE_DEBUG_TABLE(USB_ERR); + +/*------------------------------------------------------------------------* + * usb2_errstr + * + * This function converts an USB error code into a string. + *------------------------------------------------------------------------*/ +const char * +usb2_errstr(usb2_error_t err) +{ + return ((err < USB_ERR_MAX) ? + USB_ERR[err] : "USB_ERR_UNKNOWN"); +} diff --git a/sys/dev/usb2/core/usb2_generic.c b/sys/dev/usb2/core/usb2_generic.c new file mode 100644 index 000000000000..56ada335bad6 --- /dev/null +++ b/sys/dev/usb2/core/usb2_generic.c @@ -0,0 +1,2226 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR ugen_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* defines */ + +#define UGEN_BULK_FS_BUFFER_SIZE (64*32) /* bytes */ +#define UGEN_BULK_HS_BUFFER_SIZE (1024*32) /* bytes */ +#define UGEN_HW_FRAMES 50 /* number of milliseconds per transfer */ + +/* function prototypes */ + +static usb2_callback_t ugen_read_clear_stall_callback; +static usb2_callback_t ugen_write_clear_stall_callback; +static usb2_callback_t ugen_default_read_callback; +static usb2_callback_t ugen_default_write_callback; +static usb2_callback_t ugen_isoc_read_callback; +static usb2_callback_t ugen_isoc_write_callback; +static usb2_callback_t ugen_default_fs_callback; + +static usb2_fifo_open_t ugen_open; +static usb2_fifo_close_t ugen_close; +static usb2_fifo_ioctl_t ugen_ioctl; +static usb2_fifo_cmd_t ugen_start_read; +static usb2_fifo_cmd_t ugen_start_write; +static usb2_fifo_cmd_t ugen_stop_io; + +static int ugen_transfer_setup(struct usb2_fifo *f, const struct usb2_config *setup, uint8_t n_setup); +static int ugen_open_pipe_write(struct usb2_fifo *f); +static int ugen_open_pipe_read(struct usb2_fifo *f); +static int ugen_set_config(struct usb2_fifo *f, uint8_t index); +static int ugen_set_interface(struct usb2_fifo *f, uint8_t iface_index, uint8_t alt_index); +static int ugen_get_cdesc(struct usb2_fifo *f, struct usb2_gen_descriptor *pgd); +static int ugen_get_sdesc(struct usb2_fifo *f, struct usb2_gen_descriptor *ugd); +static int usb2_gen_fill_deviceinfo(struct usb2_fifo *f, struct usb2_device_info *di); +static int ugen_re_enumerate(struct usb2_fifo *f); +static int ugen_iface_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags); +static int ugen_ctrl_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags); +static int ugen_fs_uninit(struct usb2_fifo *f); +static uint8_t ugen_fs_get_complete(struct usb2_fifo *f, uint8_t *pindex); + + +/* structures */ + +struct usb2_fifo_methods usb2_ugen_methods = { + .f_open = &ugen_open, + .f_close = &ugen_close, + .f_ioctl = &ugen_ioctl, + .f_start_read = &ugen_start_read, + .f_stop_read = &ugen_stop_io, + .f_start_write = &ugen_start_write, + .f_stop_write = &ugen_stop_io, +}; + +#if USB_DEBUG +static int ugen_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ugen, CTLFLAG_RW, 0, "USB generic"); +SYSCTL_INT(_hw_usb2_ugen, OID_AUTO, debug, CTLFLAG_RW, &ugen_debug, + 0, "Debug level"); +#endif + + +/* prototypes */ + +static int +ugen_transfer_setup(struct usb2_fifo *f, + const struct usb2_config *setup, uint8_t n_setup) +{ + struct usb2_pipe *pipe = f->priv_sc0; + struct usb2_device *udev = f->udev; + uint8_t iface_index = pipe->iface_index; + int error; + + mtx_unlock(f->priv_mtx); + + /* + * "usb2_transfer_setup()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things + */ + error = usb2_transfer_setup(udev, &iface_index, f->xfer, + setup, n_setup, f, f->priv_mtx); + if (error == 0) { + + if (f->xfer[0]->nframes == 1) { + error = usb2_fifo_alloc_buffer(f, + f->xfer[0]->max_data_length, 2); + } else { + error = usb2_fifo_alloc_buffer(f, + f->xfer[0]->max_frame_size, + 2 * f->xfer[0]->nframes); + } + if (error) { + usb2_transfer_unsetup(f->xfer, n_setup); + } + } + mtx_lock(f->priv_mtx); + + return (error); +} + +static int +ugen_open(struct usb2_fifo *f, int fflags, struct thread *td) +{ + struct usb2_pipe *pipe = f->priv_sc0; + struct usb2_endpoint_descriptor *ed = pipe->edesc; + uint8_t type; + + DPRINTFN(6, "flag=0x%x\n", fflags); + + mtx_lock(f->priv_mtx); + if (usb2_get_speed(f->udev) == USB_SPEED_HIGH) { + f->nframes = UGEN_HW_FRAMES * 8; + f->bufsize = UGEN_BULK_HS_BUFFER_SIZE; + } else { + f->nframes = UGEN_HW_FRAMES; + f->bufsize = UGEN_BULK_FS_BUFFER_SIZE; + } + + type = ed->bmAttributes & UE_XFERTYPE; + if (type == UE_INTERRUPT) { + f->bufsize = 0; /* use "wMaxPacketSize" */ + } + f->timeout = USB_NO_TIMEOUT; + f->flag_short = 0; + f->fifo_zlp = 0; + mtx_unlock(f->priv_mtx); + + return (0); +} + +static void +ugen_close(struct usb2_fifo *f, int fflags, struct thread *td) +{ + DPRINTFN(6, "flag=0x%x\n", fflags); + + /* cleanup */ + + mtx_lock(f->priv_mtx); + usb2_transfer_stop(f->xfer[0]); + usb2_transfer_stop(f->xfer[1]); + mtx_unlock(f->priv_mtx); + + usb2_transfer_unsetup(f->xfer, 2); + usb2_fifo_free_buffer(f); + + if (ugen_fs_uninit(f)) { + /* ignore any errors - we are closing */ + } + return; +} + +static int +ugen_open_pipe_write(struct usb2_fifo *f) +{ + struct usb2_config usb2_config[2]; + struct usb2_pipe *pipe = f->priv_sc0; + struct usb2_endpoint_descriptor *ed = pipe->edesc; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->xfer[0] || f->xfer[1]) { + /* transfers are already opened */ + return (0); + } + if (f->fs_xfer) { + /* should not happen */ + return (EINVAL); + } + bzero(usb2_config, sizeof(usb2_config)); + + usb2_config[1].type = UE_CONTROL; + usb2_config[1].endpoint = 0; + usb2_config[1].direction = UE_DIR_ANY; + usb2_config[1].mh.timeout = 1000; /* 1 second */ + usb2_config[1].mh.interval = 50;/* 50 milliseconds */ + usb2_config[1].mh.bufsize = sizeof(struct usb2_device_request); + usb2_config[1].mh.callback = &ugen_write_clear_stall_callback; + + usb2_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb2_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb2_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN); + usb2_config[0].mh.interval = USB_DEFAULT_INTERVAL; + usb2_config[0].mh.flags.proxy_buffer = 1; + + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_BULK: + if (f->flag_short) { + usb2_config[0].mh.flags.force_short_xfer = 1; + } + usb2_config[0].mh.callback = &ugen_default_write_callback; + usb2_config[0].mh.timeout = f->timeout; + usb2_config[0].mh.frames = 1; + usb2_config[0].mh.bufsize = f->bufsize; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + if (ugen_transfer_setup(f, usb2_config, 2)) { + return (EIO); + } + /* first transfer does not clear stall */ + f->flag_stall = 0; + break; + + case UE_ISOCHRONOUS: + usb2_config[0].mh.flags.short_xfer_ok = 1; + usb2_config[0].mh.bufsize = 0; /* use default */ + usb2_config[0].mh.frames = f->nframes; + usb2_config[0].mh.callback = &ugen_isoc_write_callback; + usb2_config[0].mh.timeout = 0; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + + /* clone configuration */ + usb2_config[1] = usb2_config[0]; + + if (ugen_transfer_setup(f, usb2_config, 2)) { + return (EIO); + } + break; + default: + return (EINVAL); + } + return (0); +} + +static int +ugen_open_pipe_read(struct usb2_fifo *f) +{ + struct usb2_config usb2_config[2]; + struct usb2_pipe *pipe = f->priv_sc0; + struct usb2_endpoint_descriptor *ed = pipe->edesc; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->xfer[0] || f->xfer[1]) { + /* transfers are already opened */ + return (0); + } + if (f->fs_xfer) { + /* should not happen */ + return (EINVAL); + } + bzero(usb2_config, sizeof(usb2_config)); + + usb2_config[1].type = UE_CONTROL; + usb2_config[1].endpoint = 0; + usb2_config[1].direction = UE_DIR_ANY; + usb2_config[1].mh.timeout = 1000; /* 1 second */ + usb2_config[1].mh.interval = 50;/* 50 milliseconds */ + usb2_config[1].mh.bufsize = sizeof(struct usb2_device_request); + usb2_config[1].mh.callback = &ugen_read_clear_stall_callback; + + usb2_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb2_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb2_config[0].direction = UE_DIR_IN; + usb2_config[0].mh.interval = USB_DEFAULT_INTERVAL; + usb2_config[0].mh.flags.proxy_buffer = 1; + + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_BULK: + if (f->flag_short) { + usb2_config[0].mh.flags.short_xfer_ok = 1; + } + usb2_config[0].mh.timeout = f->timeout; + usb2_config[0].mh.frames = 1; + usb2_config[0].mh.callback = &ugen_default_read_callback; + usb2_config[0].mh.bufsize = f->bufsize; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + + if (ugen_transfer_setup(f, usb2_config, 2)) { + return (EIO); + } + /* first transfer does not clear stall */ + f->flag_stall = 0; + break; + + case UE_ISOCHRONOUS: + usb2_config[0].mh.flags.short_xfer_ok = 1; + usb2_config[0].mh.bufsize = 0; /* use default */ + usb2_config[0].mh.frames = f->nframes; + usb2_config[0].mh.callback = &ugen_isoc_read_callback; + usb2_config[0].mh.timeout = 0; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + + /* clone configuration */ + usb2_config[1] = usb2_config[0]; + + if (ugen_transfer_setup(f, usb2_config, 2)) { + return (EIO); + } + break; + + default: + return (EINVAL); + } + return (0); +} + +static void +ugen_start_read(struct usb2_fifo *f) +{ + /* check that pipes are open */ + if (ugen_open_pipe_read(f)) { + /* signal error */ + usb2_fifo_put_data_error(f); + } + /* start transfers */ + usb2_transfer_start(f->xfer[0]); + usb2_transfer_start(f->xfer[1]); + return; +} + +static void +ugen_start_write(struct usb2_fifo *f) +{ + /* check that pipes are open */ + if (ugen_open_pipe_write(f)) { + /* signal error */ + usb2_fifo_get_data_error(f); + } + /* start transfers */ + usb2_transfer_start(f->xfer[0]); + usb2_transfer_start(f->xfer[1]); + return; +} + +static void +ugen_stop_io(struct usb2_fifo *f) +{ + /* stop transfers */ + usb2_transfer_stop(f->xfer[0]); + usb2_transfer_stop(f->xfer[1]); + return; +} + +static void +ugen_default_read_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + struct usb2_mbuf *m; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen == 0) { + if (f->fifo_zlp != 4) { + f->fifo_zlp++; + } else { + /* + * Throttle a little bit we have multiple ZLPs + * in a row! + */ + xfer->interval = 64; /* ms */ + } + } else { + /* clear throttle */ + xfer->interval = 0; + f->fifo_zlp = 0; + } + usb2_fifo_put_data(f, xfer->frbuffers, 0, + xfer->actlen, 1); + + case USB_ST_SETUP: + if (f->flag_stall) { + usb2_transfer_start(f->xfer[1]); + break; + } + USB_IF_POLL(&f->free_q, m); + if (m) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + f->flag_stall = 1; + f->fifo_zlp = 0; + usb2_transfer_start(f->xfer[1]); + } + break; + } + return; +} + +static void +ugen_default_write_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + uint32_t actlen; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + /* + * If writing is in stall, just jump to clear stall + * callback and solve the situation. + */ + if (f->flag_stall) { + usb2_transfer_start(f->xfer[1]); + break; + } + /* + * Write data, setup and perform hardware transfer. + */ + if (usb2_fifo_get_data(f, xfer->frbuffers, 0, + xfer->max_data_length, &actlen, 0)) { + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + f->flag_stall = 1; + usb2_transfer_start(f->xfer[1]); + } + break; + } + return; +} + +static void +ugen_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + struct usb2_xfer *xfer_other = f->xfer[0]; + + if (f->flag_stall == 0) { + /* nothing to do */ + return; + } + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTFN(5, "f=%p: stall cleared\n", f); + f->flag_stall = 0; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ugen_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + struct usb2_xfer *xfer_other = f->xfer[0]; + + if (f->flag_stall == 0) { + /* nothing to do */ + return; + } + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTFN(5, "f=%p: stall cleared\n", f); + f->flag_stall = 0; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ugen_isoc_read_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + uint32_t offset; + uint16_t n; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(6, "actlen=%d\n", xfer->actlen); + + offset = 0; + + for (n = 0; n != xfer->aframes; n++) { + usb2_fifo_put_data(f, xfer->frbuffers, offset, + xfer->frlengths[n], 1); + offset += xfer->max_frame_size; + } + + case USB_ST_SETUP: +tr_setup: + for (n = 0; n != xfer->nframes; n++) { + /* setup size for next transfer */ + xfer->frlengths[n] = xfer->max_frame_size; + } + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } + return; +} + +static void +ugen_isoc_write_callback(struct usb2_xfer *xfer) +{ + struct usb2_fifo *f = xfer->priv_sc; + uint32_t actlen; + uint32_t offset; + uint16_t n; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + offset = 0; + for (n = 0; n != xfer->nframes; n++) { + if (usb2_fifo_get_data(f, xfer->frbuffers, offset, + xfer->max_frame_size, &actlen, 1)) { + xfer->frlengths[n] = actlen; + offset += actlen; + } else { + break; + } + } + + for (; n != xfer->nframes; n++) { + /* fill in zero frames */ + xfer->frlengths[n] = 0; + } + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } + return; +} + +static int +ugen_set_config(struct usb2_fifo *f, uint8_t index) +{ + DPRINTFN(2, "index %u\n", index); + + if (f->flag_no_uref) { + /* not the control endpoint - just forget it */ + return (EINVAL); + } + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + if (f->udev->curr_config_index == index) { + /* no change needed */ + return (0); + } + /* change setting - will free generic FIFOs, if any */ + if (usb2_set_config_index(f->udev, index)) { + return (EIO); + } + /* probe and attach */ + if (usb2_probe_and_attach(f->udev, USB_IFACE_INDEX_ANY)) { + return (EIO); + } + return (0); +} + +static int +ugen_set_interface(struct usb2_fifo *f, + uint8_t iface_index, uint8_t alt_index) +{ + DPRINTFN(2, "%u, %u\n", iface_index, alt_index); + + if (f->flag_no_uref) { + /* not the control endpoint - just forget it */ + return (EINVAL); + } + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + /* change setting - will free generic FIFOs, if any */ + if (usb2_set_alt_interface_index(f->udev, iface_index, alt_index)) { + return (EIO); + } + /* probe and attach */ + if (usb2_probe_and_attach(f->udev, iface_index)) { + return (EIO); + } + return (0); +} + +/*------------------------------------------------------------------------* + * ugen_get_cdesc + * + * This function will retrieve the complete configuration descriptor + * at the given index. + *------------------------------------------------------------------------*/ +static int +ugen_get_cdesc(struct usb2_fifo *f, struct usb2_gen_descriptor *ugd) +{ + struct usb2_config_descriptor *cdesc; + struct usb2_device *udev = f->udev; + int error; + uint16_t len; + uint8_t free_data; + + DPRINTFN(6, "\n"); + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + if (ugd->ugd_data == NULL) { + /* userland pointer should not be zero */ + return (EINVAL); + } + if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) || + (ugd->ugd_config_index == udev->curr_config_index)) { + cdesc = usb2_get_config_descriptor(udev); + if (cdesc == NULL) { + return (ENXIO); + } + free_data = 0; + + } else { + if (usb2_req_get_config_desc_full(udev, + &Giant, &cdesc, M_USBDEV, + ugd->ugd_config_index)) { + return (ENXIO); + } + free_data = 1; + } + + len = UGETW(cdesc->wTotalLength); + if (len > ugd->ugd_maxlen) { + len = ugd->ugd_maxlen; + } + DPRINTFN(6, "len=%u\n", len); + + ugd->ugd_actlen = len; + ugd->ugd_offset = 0; + + error = copyout(cdesc, ugd->ugd_data, len); + + if (free_data) { + free(cdesc, M_USBDEV); + } + return (error); +} + +static int +ugen_get_sdesc(struct usb2_fifo *f, struct usb2_gen_descriptor *ugd) +{ + void *ptr = f->udev->bus->scratch[0].data; + uint16_t size = sizeof(f->udev->bus->scratch[0].data); + int error; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + if (usb2_req_get_string_desc(f->udev, &Giant, ptr, + size, ugd->ugd_lang_id, ugd->ugd_string_index)) { + error = EINVAL; + } else { + + if (size > ((uint8_t *)ptr)[0]) { + size = ((uint8_t *)ptr)[0]; + } + if (size > ugd->ugd_maxlen) { + size = ugd->ugd_maxlen; + } + ugd->ugd_actlen = size; + ugd->ugd_offset = 0; + + error = copyout(ptr, ugd->ugd_data, size); + } + return (error); +} + +/*------------------------------------------------------------------------* + * usb2_gen_fill_devicenames + * + * This function dumps information about an USB device names to + * userland. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb2_gen_fill_devicenames(struct usb2_fifo *f, struct usb2_device_names *dn) +{ + struct usb2_interface *iface; + const char *ptr; + char *dst; + char buf[32]; + int error = 0; + int len; + int max_len; + uint8_t i; + uint8_t first = 1; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + max_len = dn->udn_devnames_len; + dst = dn->udn_devnames_ptr; + + if (max_len == 0) { + return (EINVAL); + } + /* put a zero there */ + error = copyout("", dst, 1); + if (error) { + return (error); + } + for (i = 0;; i++) { + iface = usb2_get_iface(f->udev, i); + if (iface == NULL) { + break; + } + if ((iface->subdev != NULL) && + device_is_attached(iface->subdev)) { + ptr = device_get_nameunit(iface->subdev); + if (!first) { + strlcpy(buf, ", ", sizeof(buf)); + } else { + buf[0] = 0; + } + strlcat(buf, ptr, sizeof(buf)); + len = strlen(buf) + 1; + if (len > max_len) { + break; + } + error = copyout(buf, dst, len); + if (error) { + return (error); + } + len--; + dst += len; + max_len -= len; + first = 0; + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_gen_fill_deviceinfo + * + * This function dumps information about an USB device to the + * structure pointed to by the "di" argument. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb2_gen_fill_deviceinfo(struct usb2_fifo *f, struct usb2_device_info *di) +{ + struct usb2_device *udev; + struct usb2_device *hub; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + udev = f->udev; + + bzero(di, sizeof(di[0])); + + di->udi_bus = device_get_unit(udev->bus->bdev); + di->udi_addr = udev->address; + di->udi_index = udev->device_index; + strlcpy(di->udi_serial, udev->serial, + sizeof(di->udi_serial)); + strlcpy(di->udi_vendor, udev->manufacturer, + sizeof(di->udi_vendor)); + strlcpy(di->udi_product, udev->product, + sizeof(di->udi_product)); + usb2_printBCD(di->udi_release, sizeof(di->udi_release), + UGETW(udev->ddesc.bcdDevice)); + di->udi_vendorNo = UGETW(udev->ddesc.idVendor); + di->udi_productNo = UGETW(udev->ddesc.idProduct); + di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice); + di->udi_class = udev->ddesc.bDeviceClass; + di->udi_subclass = udev->ddesc.bDeviceSubClass; + di->udi_protocol = udev->ddesc.bDeviceProtocol; + di->udi_config_no = udev->curr_config_no; + di->udi_config_index = udev->curr_config_index; + di->udi_power = udev->flags.self_powered ? 0 : udev->power; + di->udi_speed = udev->speed; + di->udi_mode = udev->flags.usb2_mode; + di->udi_power_mode = udev->power_mode; + if (udev->flags.suspended) { + di->udi_suspended = 1; + } else { + di->udi_suspended = 0; + } + + hub = udev->parent_hub; + if (hub) { + di->udi_hubaddr = hub->address; + di->udi_hubindex = hub->device_index; + di->udi_hubport = udev->port_no; + } + return (0); +} + +/*------------------------------------------------------------------------* + * ugen_check_request + * + * Return values: + * 0: Access allowed + * Else: No access + *------------------------------------------------------------------------*/ +static int +ugen_check_request(struct usb2_device *udev, struct usb2_device_request *req) +{ + struct usb2_pipe *pipe; + int error; + + /* + * Avoid requests that would damage the bus integrity: + */ + if (((req->bmRequestType == UT_WRITE_DEVICE) && + (req->bRequest == UR_SET_ADDRESS)) || + ((req->bmRequestType == UT_WRITE_DEVICE) && + (req->bRequest == UR_SET_CONFIG)) || + ((req->bmRequestType == UT_WRITE_INTERFACE) && + (req->bRequest == UR_SET_INTERFACE))) { + /* + * These requests can be useful for testing USB drivers. + */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + return (error); + } + } + /* + * Special case - handle clearing of stall + */ + if (req->bmRequestType == UT_WRITE_ENDPOINT) { + + pipe = usb2_get_pipe_by_addr(udev, req->wIndex[0]); + if (pipe == NULL) { + return (EINVAL); + } + if (usb2_check_thread_perm(udev, curthread, FREAD | FWRITE, + pipe->iface_index, req->wIndex[0] & UE_ADDR)) { + return (EPERM); + } + if ((req->bRequest == UR_CLEAR_FEATURE) && + (UGETW(req->wValue) == UF_ENDPOINT_HALT)) { + usb2_clear_data_toggle(udev, pipe); + } + } + /* TODO: add more checks to verify the interface index */ + + return (0); +} + +int +ugen_do_request(struct usb2_fifo *f, struct usb2_ctl_request *ur) +{ + int error; + uint16_t len; + uint16_t actlen; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + if (ugen_check_request(f->udev, &ur->ucr_request)) { + return (EPERM); + } + len = UGETW(ur->ucr_request.wLength); + + /* check if "ucr_data" is valid */ + if (len != 0) { + if (ur->ucr_data == NULL) { + return (EFAULT); + } + } + /* do the USB request */ + error = usb2_do_request_flags + (f->udev, NULL, &ur->ucr_request, ur->ucr_data, + (ur->ucr_flags & USB_SHORT_XFER_OK) | + USB_USER_DATA_PTR, &actlen, + USB_DEFAULT_TIMEOUT); + + ur->ucr_actlen = actlen; + + if (error) { + error = EIO; + } + return (error); +} + +/*------------------------------------------------------------------------ + * ugen_re_enumerate + *------------------------------------------------------------------------*/ +static int +ugen_re_enumerate(struct usb2_fifo *f) +{ + struct usb2_device *udev = f->udev; + int error; + + if (f->flag_no_uref) { + /* control endpoint only */ + return (EINVAL); + } + /* + * This request can be useful for testing USB drivers: + */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + return (error); + } + mtx_lock(f->priv_mtx); + error = usb2_req_re_enumerate(udev, f->priv_mtx); + mtx_unlock(f->priv_mtx); + + if (error) { + return (ENXIO); + } + return (0); +} + +static int +ugen_fs_uninit(struct usb2_fifo *f) +{ + if (f->fs_xfer == NULL) { + return (EINVAL); + } + usb2_transfer_unsetup(f->fs_xfer, f->fs_ep_max); + free(f->fs_xfer, M_USB); + f->fs_xfer = NULL; + f->fs_ep_max = 0; + f->fs_ep_ptr = NULL; + f->flag_iscomplete = 0; + f->flag_no_uref = 0; /* restore operation */ + usb2_fifo_free_buffer(f); + return (0); +} + +static uint8_t +ugen_fs_get_complete(struct usb2_fifo *f, uint8_t *pindex) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + *pindex = *((uint8_t *)(m->cur_data_ptr)); + + USB_IF_ENQUEUE(&f->free_q, m); + + return (0); /* success */ + } else { + + *pindex = 0; /* fix compiler warning */ + + f->flag_iscomplete = 0; + } + return (1); /* failure */ +} + +static void +ugen_fs_set_complete(struct usb2_fifo *f, uint8_t index) +{ + struct usb2_mbuf *m; + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m == NULL) { + /* can happen during close */ + DPRINTF("out of buffers\n"); + return; + } + USB_MBUF_RESET(m); + + *((uint8_t *)(m->cur_data_ptr)) = index; + + USB_IF_ENQUEUE(&f->used_q, m); + + f->flag_iscomplete = 1; + + usb2_fifo_wakeup(f); + + return; +} + +static int +ugen_fs_copy_in(struct usb2_fifo *f, uint8_t ep_index) +{ + struct usb2_device_request *req; + struct usb2_xfer *xfer; + struct usb2_fs_endpoint fs_ep; + void *uaddr; /* userland pointer */ + void *kaddr; + uint32_t offset; + uint32_t length; + uint32_t n; + uint32_t rem; + int error; + uint8_t isread; + + if (ep_index >= f->fs_ep_max) { + return (EINVAL); + } + xfer = f->fs_xfer[ep_index]; + if (xfer == NULL) { + return (EINVAL); + } + mtx_lock(f->priv_mtx); + if (usb2_transfer_pending(xfer)) { + mtx_unlock(f->priv_mtx); + return (EBUSY); /* should not happen */ + } + mtx_unlock(f->priv_mtx); + + error = copyin(f->fs_ep_ptr + + ep_index, &fs_ep, sizeof(fs_ep)); + if (error) { + return (error); + } + /* security checks */ + + if (fs_ep.nFrames > xfer->max_frame_count) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + if (fs_ep.nFrames == 0) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + error = copyin(fs_ep.ppBuffer, + &uaddr, sizeof(uaddr)); + if (error) { + return (error); + } + /* reset first frame */ + usb2_set_frame_offset(xfer, 0, 0); + + if (xfer->flags_int.control_xfr) { + + req = xfer->frbuffers[0].buffer; + + error = copyin(fs_ep.pLength, + &length, sizeof(length)); + if (error) { + return (error); + } + if (length >= sizeof(*req)) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + if (length != 0) { + error = copyin(uaddr, req, length); + if (error) { + return (error); + } + } + if (ugen_check_request(f->udev, req)) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + xfer->frlengths[0] = length; + + /* Host mode only ! */ + if ((req->bmRequestType & + (UT_READ | UT_WRITE)) == UT_READ) { + isread = 1; + } else { + isread = 0; + } + n = 1; + offset = sizeof(*req); + + } else { + /* Device and Host mode */ + if (USB_GET_DATA_ISREAD(xfer)) { + isread = 1; + } else { + isread = 0; + } + n = 0; + offset = 0; + } + + rem = xfer->max_data_length; + xfer->nframes = fs_ep.nFrames; + xfer->timeout = fs_ep.timeout; + if (xfer->timeout > 65535) { + xfer->timeout = 65535; + } + if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK) + xfer->flags.short_xfer_ok = 1; + else + xfer->flags.short_xfer_ok = 0; + + if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK) + xfer->flags.short_frames_ok = 1; + else + xfer->flags.short_frames_ok = 0; + + if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT) + xfer->flags.force_short_xfer = 1; + else + xfer->flags.force_short_xfer = 0; + + if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL) + xfer->flags.stall_pipe = 1; + else + xfer->flags.stall_pipe = 0; + + for (; n != xfer->nframes; n++) { + + error = copyin(fs_ep.pLength + n, + &length, sizeof(length)); + if (error) { + break; + } + xfer->frlengths[n] = length; + + if (length > rem) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + rem -= length; + + if (!isread) { + + /* we need to know the source buffer */ + error = copyin(fs_ep.ppBuffer + n, + &uaddr, sizeof(uaddr)); + if (error) { + break; + } + if (xfer->flags_int.isochronous_xfr) { + /* get kernel buffer address */ + kaddr = xfer->frbuffers[0].buffer; + kaddr = USB_ADD_BYTES(kaddr, offset); + } else { + /* set current frame offset */ + usb2_set_frame_offset(xfer, offset, n); + + /* get kernel buffer address */ + kaddr = xfer->frbuffers[n].buffer; + } + + /* move data */ + error = copyin(uaddr, kaddr, length); + if (error) { + break; + } + } + offset += length; + } + return (error); + +complete: + mtx_lock(f->priv_mtx); + ugen_fs_set_complete(f, ep_index); + mtx_unlock(f->priv_mtx); + return (0); +} + +static int +ugen_fs_copy_out(struct usb2_fifo *f, uint8_t ep_index) +{ + struct usb2_device_request *req; + struct usb2_xfer *xfer; + struct usb2_fs_endpoint fs_ep; + struct usb2_fs_endpoint *fs_ep_uptr; /* userland ptr */ + void *uaddr; /* userland ptr */ + void *kaddr; + uint32_t offset; + uint32_t length; + uint32_t temp; + uint32_t n; + uint32_t rem; + int error; + uint8_t isread; + + if (ep_index >= f->fs_ep_max) { + return (EINVAL); + } + xfer = f->fs_xfer[ep_index]; + if (xfer == NULL) { + return (EINVAL); + } + mtx_lock(f->priv_mtx); + if (usb2_transfer_pending(xfer)) { + mtx_unlock(f->priv_mtx); + return (EBUSY); /* should not happen */ + } + mtx_unlock(f->priv_mtx); + + fs_ep_uptr = f->fs_ep_ptr + ep_index; + error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep)); + if (error) { + return (error); + } + fs_ep.status = xfer->error; + fs_ep.aFrames = xfer->aframes; + fs_ep.isoc_time_complete = xfer->isoc_time_complete; + if (xfer->error) { + goto complete; + } + if (xfer->flags_int.control_xfr) { + req = xfer->frbuffers[0].buffer; + + /* Host mode only ! */ + if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) { + isread = 1; + } else { + isread = 0; + } + if (xfer->nframes == 0) + n = 0; /* should never happen */ + else + n = 1; + } else { + /* Device and Host mode */ + if (USB_GET_DATA_ISREAD(xfer)) { + isread = 1; + } else { + isread = 0; + } + n = 0; + } + + /* Update lengths and copy out data */ + + rem = xfer->max_data_length; + offset = 0; + + for (; n != xfer->nframes; n++) { + + /* get initial length into "temp" */ + error = copyin(fs_ep.pLength + n, + &temp, sizeof(temp)); + if (error) { + return (error); + } + if (temp > rem) { + /* the userland length has been corrupted */ + DPRINTF("corrupt userland length " + "%u > %u\n", temp, rem); + fs_ep.status = USB_ERR_INVAL; + goto complete; + } + rem -= temp; + + /* get actual transfer length */ + length = xfer->frlengths[n]; + if (length > temp) { + /* data overflow */ + fs_ep.status = USB_ERR_INVAL; + DPRINTF("data overflow %u > %u\n", + length, temp); + goto complete; + } + if (isread) { + + /* we need to know the destination buffer */ + error = copyin(fs_ep.ppBuffer + n, + &uaddr, sizeof(uaddr)); + if (error) { + return (error); + } + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + kaddr = USB_ADD_BYTES( + xfer->frbuffers[0].buffer, offset); + } else { + /* multiple frame buffers */ + kaddr = xfer->frbuffers[n].buffer; + } + + /* move data */ + error = copyout(kaddr, uaddr, length); + if (error) { + return (error); + } + } + /* + * Update offset according to initial length, which is + * needed by isochronous transfers! + */ + offset += temp; + + /* update length */ + error = copyout(&length, + fs_ep.pLength + n, sizeof(length)); + if (error) { + return (error); + } + } + +complete: + /* update "aFrames" */ + error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames, + sizeof(fs_ep.aFrames)); + if (error) + goto done; + + /* update "isoc_time_complete" */ + error = copyout(&fs_ep.isoc_time_complete, + &fs_ep_uptr->isoc_time_complete, + sizeof(fs_ep.isoc_time_complete)); + if (error) + goto done; + /* update "status" */ + error = copyout(&fs_ep.status, &fs_ep_uptr->status, + sizeof(fs_ep.status)); +done: + return (error); +} + +static uint8_t +ugen_fifo_in_use(struct usb2_fifo *f, int fflags) +{ + struct usb2_fifo *f_rx; + struct usb2_fifo *f_tx; + + f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; + f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; + + if ((fflags & FREAD) && f_rx && + (f_rx->xfer[0] || f_rx->xfer[1])) { + return (1); /* RX FIFO in use */ + } + if ((fflags & FWRITE) && f_tx && + (f_tx->xfer[0] || f_tx->xfer[1])) { + return (1); /* TX FIFO in use */ + } + return (0); /* not in use */ +} + +static int +ugen_fs_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags) +{ + struct usb2_config usb2_config[1]; + struct usb2_device_request req; + union { + struct usb2_fs_complete *pcomp; + struct usb2_fs_start *pstart; + struct usb2_fs_stop *pstop; + struct usb2_fs_init *pinit; + struct usb2_fs_uninit *puninit; + struct usb2_fs_open *popen; + struct usb2_fs_close *pclose; + struct usb2_fs_clear_stall_sync *pstall; + void *addr; + } u; + struct usb2_pipe *pipe; + struct usb2_endpoint_descriptor *ed; + int error = 0; + uint8_t iface_index; + uint8_t isread; + uint8_t ep_index; + + u.addr = addr; + + switch (cmd) { + case USB_FS_COMPLETE: + mtx_lock(f->priv_mtx); + error = ugen_fs_get_complete(f, &ep_index); + mtx_unlock(f->priv_mtx); + + if (error) { + error = EBUSY; + break; + } + u.pcomp->ep_index = ep_index; + error = ugen_fs_copy_out(f, u.pcomp->ep_index); + break; + + case USB_FS_START: + error = ugen_fs_copy_in(f, u.pstart->ep_index); + if (error) { + break; + } + mtx_lock(f->priv_mtx); + usb2_transfer_start(f->fs_xfer[u.pstart->ep_index]); + mtx_unlock(f->priv_mtx); + break; + + case USB_FS_STOP: + if (u.pstop->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + mtx_lock(f->priv_mtx); + usb2_transfer_stop(f->fs_xfer[u.pstop->ep_index]); + mtx_unlock(f->priv_mtx); + break; + + case USB_FS_INIT: + /* verify input parameters */ + if (u.pinit->pEndpoints == NULL) { + error = EINVAL; + break; + } + if (u.pinit->ep_index_max > 127) { + error = EINVAL; + break; + } + if (u.pinit->ep_index_max == 0) { + error = EINVAL; + break; + } + if (f->fs_xfer != NULL) { + error = EBUSY; + break; + } + if (f->flag_no_uref) { + error = EINVAL; + break; + } + if (f->dev_ep_index != 0) { + error = EINVAL; + break; + } + if (ugen_fifo_in_use(f, fflags)) { + error = EBUSY; + break; + } + error = usb2_fifo_alloc_buffer(f, 1, u.pinit->ep_index_max); + if (error) { + break; + } + f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) * + u.pinit->ep_index_max, M_USB, M_WAITOK | M_ZERO); + if (f->fs_xfer == NULL) { + usb2_fifo_free_buffer(f); + error = ENOMEM; + break; + } + f->fs_ep_max = u.pinit->ep_index_max; + f->fs_ep_ptr = u.pinit->pEndpoints; + break; + + case USB_FS_UNINIT: + if (u.puninit->dummy != 0) { + error = EINVAL; + break; + } + error = ugen_fs_uninit(f); + break; + + case USB_FS_OPEN: + if (u.popen->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.popen->ep_index] != NULL) { + error = EBUSY; + break; + } + if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) { + u.popen->max_bufsize = USB_FS_MAX_BUFSIZE; + } + if (u.popen->max_frames > USB_FS_MAX_FRAMES) { + u.popen->max_frames = USB_FS_MAX_FRAMES; + break; + } + if (u.popen->max_frames == 0) { + error = EINVAL; + break; + } + pipe = usb2_get_pipe_by_addr(f->udev, u.popen->ep_no); + if (pipe == NULL) { + error = EINVAL; + break; + } + ed = pipe->edesc; + if (ed == NULL) { + error = ENXIO; + break; + } + iface_index = pipe->iface_index; + + error = usb2_check_thread_perm(f->udev, curthread, fflags, + iface_index, u.popen->ep_no); + if (error) { + break; + } + bzero(usb2_config, sizeof(usb2_config)); + + usb2_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb2_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb2_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN); + usb2_config[0].mh.interval = USB_DEFAULT_INTERVAL; + usb2_config[0].mh.flags.proxy_buffer = 1; + usb2_config[0].mh.callback = &ugen_default_fs_callback; + usb2_config[0].mh.timeout = 0; /* no timeout */ + usb2_config[0].mh.frames = u.popen->max_frames; + usb2_config[0].mh.bufsize = u.popen->max_bufsize; + usb2_config[0].md = usb2_config[0].mh; /* symmetric config */ + + if (usb2_config[0].type == UE_CONTROL) { + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + error = EINVAL; + break; + } + } else { + + isread = ((usb2_config[0].endpoint & + (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN); + + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + isread = !isread; + } + /* check permissions */ + if (isread) { + if (!(fflags & FREAD)) { + error = EPERM; + break; + } + } else { + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + } + } + error = usb2_transfer_setup(f->udev, &iface_index, + f->fs_xfer + u.popen->ep_index, usb2_config, 1, + f, f->priv_mtx); + if (error == 0) { + /* update maximums */ + u.popen->max_packet_length = + f->fs_xfer[u.popen->ep_index]->max_frame_size; + u.popen->max_bufsize = + f->fs_xfer[u.popen->ep_index]->max_data_length; + f->fs_xfer[u.popen->ep_index]->priv_fifo = + ((uint8_t *)0) + u.popen->ep_index; + /* + * Increase performance by dropping locks we + * don't need: + */ + f->flag_no_uref = 1; + } else { + error = ENOMEM; + } + break; + + case USB_FS_CLOSE: + if (u.pclose->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.pclose->ep_index] == NULL) { + error = EINVAL; + break; + } + usb2_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1); + break; + + case USB_FS_CLEAR_STALL_SYNC: + if (u.pstall->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.pstall->ep_index] == NULL) { + error = EINVAL; + break; + } + if (f->udev->flags.usb2_mode != USB_MODE_HOST) { + error = EINVAL; + break; + } + mtx_lock(f->priv_mtx); + error = usb2_transfer_pending(f->fs_xfer[u.pstall->ep_index]); + mtx_unlock(f->priv_mtx); + + if (error) { + return (EBUSY); + } + pipe = f->fs_xfer[u.pstall->ep_index]->pipe; + + /* setup a clear-stall packet */ + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = pipe->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + error = usb2_do_request(f->udev, NULL, &req, NULL); + if (error == 0) { + usb2_clear_data_toggle(f->udev, pipe); + } else { + error = ENXIO; + } + break; + + default: + error = ENOTTY; + break; + } + return (error); +} + +static int +ugen_set_short_xfer(struct usb2_fifo *f, void *addr) +{ + uint8_t t; + + if (*(int *)addr) + t = 1; + else + t = 0; + + if (f->flag_short == t) { + /* same value like before - accept */ + return (0); + } + if (f->xfer[0] || f->xfer[1]) { + /* cannot change this during transfer */ + return (EBUSY); + } + f->flag_short = t; + return (0); +} + +static int +ugen_set_timeout(struct usb2_fifo *f, void *addr) +{ + f->timeout = *(int *)addr; + if (f->timeout > 65535) { + /* limit user input */ + f->timeout = 65535; + } + return (0); +} + +static int +ugen_get_frame_size(struct usb2_fifo *f, void *addr) +{ + if (f->xfer[0]) { + *(int *)addr = f->xfer[0]->max_frame_size; + } else { + return (EINVAL); + } + return (0); +} + +static int +ugen_set_buffer_size(struct usb2_fifo *f, void *addr) +{ + uint32_t t; + + if (*(int *)addr < 1024) + t = 1024; + else if (*(int *)addr < (256 * 1024)) + t = *(int *)addr; + else + t = 256 * 1024; + + if (f->bufsize == t) { + /* same value like before - accept */ + return (0); + } + if (f->xfer[0] || f->xfer[1]) { + /* cannot change this during transfer */ + return (EBUSY); + } + f->bufsize = t; + return (0); +} + +static int +ugen_get_buffer_size(struct usb2_fifo *f, void *addr) +{ + *(int *)addr = f->bufsize; + return (0); +} + +static int +ugen_get_iface_desc(struct usb2_fifo *f, + struct usb2_interface_descriptor *idesc) +{ + struct usb2_interface *iface; + + iface = usb2_get_iface(f->udev, f->iface_index); + if (iface && iface->idesc) { + *idesc = *(iface->idesc); + } else { + return (EIO); + } + return (0); +} + +static int +ugen_get_endpoint_desc(struct usb2_fifo *f, + struct usb2_endpoint_descriptor *ed) +{ + struct usb2_pipe *pipe; + + pipe = f->priv_sc0; + + if (pipe && pipe->edesc) { + *ed = *pipe->edesc; + } else { + return (EINVAL); + } + return (0); +} + +static int +ugen_set_power_mode(struct usb2_fifo *f, int mode) +{ + struct usb2_device *udev = f->udev; + int err; + + if ((udev == NULL) || + (udev->parent_hub == NULL)) { + return (EINVAL); + } + err = priv_check(curthread, PRIV_ROOT); + if (err) { + return (err); + } + switch (mode) { + case USB_POWER_MODE_OFF: + /* clear suspend */ + err = usb2_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + if (err) + break; + + /* clear port enable */ + err = usb2_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_ENABLE); + break; + + case USB_POWER_MODE_ON: + /* enable port */ + err = usb2_req_set_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_ENABLE); + + /* FALLTHROUGH */ + + case USB_POWER_MODE_SAVE: + case USB_POWER_MODE_RESUME: + /* TODO: implement USB power save */ + err = usb2_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + break; + + case USB_POWER_MODE_SUSPEND: + /* TODO: implement USB power save */ + err = usb2_req_set_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + break; + default: + return (EINVAL); + } + + if (err) + return (ENXIO); /* I/O failure */ + + udev->power_mode = mode; /* update copy of power mode */ + + return (0); /* success */ +} + +static int +ugen_get_power_mode(struct usb2_fifo *f) +{ + struct usb2_device *udev = f->udev; + + if ((udev == NULL) || + (udev->parent_hub == NULL)) { + return (USB_POWER_MODE_ON); + } + return (udev->power_mode); +} + +static int +ugen_do_port_feature(struct usb2_fifo *f, uint8_t port_no, + uint8_t set, uint16_t feature) +{ + struct usb2_device *udev = f->udev; + struct usb2_hub *hub; + int err; + + err = priv_check(curthread, PRIV_ROOT); + if (err) { + return (err); + } + if (port_no == 0) { + return (EINVAL); + } + if ((udev == NULL) || + (udev->hub == NULL)) { + return (EINVAL); + } + hub = udev->hub; + + if (port_no > hub->nports) { + return (EINVAL); + } + if (set) + err = usb2_req_set_port_feature(udev, + NULL, port_no, feature); + else + err = usb2_req_clear_port_feature(udev, + NULL, port_no, feature); + + if (err) + return (ENXIO); /* failure */ + + return (0); /* success */ +} + +static int +ugen_iface_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags) +{ + struct usb2_fifo *f_rx; + struct usb2_fifo *f_tx; + int error = 0; + + f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; + f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; + + switch (cmd) { + case USB_SET_RX_SHORT_XFER: + if (fflags & FREAD) { + error = ugen_set_short_xfer(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_FORCE_SHORT: + if (fflags & FWRITE) { + error = ugen_set_short_xfer(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_TIMEOUT: + if (fflags & FREAD) { + error = ugen_set_timeout(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_TIMEOUT: + if (fflags & FWRITE) { + error = ugen_set_timeout(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_FRAME_SIZE: + if (fflags & FREAD) { + error = ugen_get_frame_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_FRAME_SIZE: + if (fflags & FWRITE) { + error = ugen_get_frame_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_BUFFER_SIZE: + if (fflags & FREAD) { + error = ugen_set_buffer_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_BUFFER_SIZE: + if (fflags & FWRITE) { + error = ugen_set_buffer_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_BUFFER_SIZE: + if (fflags & FREAD) { + error = ugen_get_buffer_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_BUFFER_SIZE: + if (fflags & FWRITE) { + error = ugen_get_buffer_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_INTERFACE_DESC: + if (fflags & FREAD) { + error = ugen_get_iface_desc(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_INTERFACE_DESC: + if (fflags & FWRITE) { + error = ugen_get_iface_desc(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_ENDPOINT_DESC: + if (fflags & FREAD) { + error = ugen_get_endpoint_desc(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_ENDPOINT_DESC: + if (fflags & FWRITE) { + error = ugen_get_endpoint_desc(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_STALL_FLAG: + if ((fflags & FREAD) && (*(int *)addr)) { + f_rx->flag_stall = 1; + } + break; + + case USB_SET_TX_STALL_FLAG: + if ((fflags & FWRITE) && (*(int *)addr)) { + f_tx->flag_stall = 1; + } + break; + + default: + error = ENOTTY; + break; + } + return (error); +} + +static int +ugen_ctrl_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags) +{ + union { + struct usb2_interface_descriptor *idesc; + struct usb2_alt_interface *ai; + struct usb2_device_descriptor *ddesc; + struct usb2_config_descriptor *cdesc; + struct usb2_device_stats *stat; + uint32_t *ptime; + void *addr; + int *pint; + } u; + struct usb2_device_descriptor *dtemp; + struct usb2_config_descriptor *ctemp; + struct usb2_interface *iface; + int error = 0; + uint8_t n; + + u.addr = addr; + + switch (cmd) { + case USB_DISCOVER: + usb2_needs_explore_all(); + break; + + case USB_SETDEBUG: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + usb2_debug = *(int *)addr; + break; + + case USB_GET_CONFIG: + *(int *)addr = f->udev->curr_config_index; + break; + + case USB_SET_CONFIG: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_set_config(f, *(int *)addr); + break; + + case USB_GET_ALTINTERFACE: + iface = usb2_get_iface(f->udev, + u.ai->uai_interface_index); + if (iface && iface->idesc) { + u.ai->uai_alt_index = iface->alt_index; + } else { + error = EINVAL; + } + break; + + case USB_SET_ALTINTERFACE: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_set_interface(f, + u.ai->uai_interface_index, u.ai->uai_alt_index); + break; + + case USB_GET_DEVICE_DESC: + dtemp = usb2_get_device_descriptor(f->udev); + if (!dtemp) { + error = EIO; + break; + } + *u.ddesc = *dtemp; + break; + + case USB_GET_CONFIG_DESC: + ctemp = usb2_get_config_descriptor(f->udev); + if (!ctemp) { + error = EIO; + break; + } + *u.cdesc = *ctemp; + break; + + case USB_GET_FULL_DESC: + error = ugen_get_cdesc(f, addr); + break; + + case USB_GET_STRING_DESC: + error = ugen_get_sdesc(f, addr); + break; + + case USB_REQUEST: + case USB_DO_REQUEST: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_do_request(f, addr); + break; + + case USB_DEVICEINFO: + case USB_GET_DEVICEINFO: + error = usb2_gen_fill_deviceinfo(f, addr); + break; + + case USB_GET_DEVICENAMES: + error = usb2_gen_fill_devicenames(f, addr); + break; + + case USB_DEVICESTATS: + for (n = 0; n != 4; n++) { + + u.stat->uds_requests_fail[n] = + f->udev->bus->stats_err.uds_requests[n]; + + u.stat->uds_requests_ok[n] = + f->udev->bus->stats_ok.uds_requests[n]; + } + break; + + case USB_DEVICEENUMERATE: + error = ugen_re_enumerate(f); + break; + + case USB_GET_PLUGTIME: + *u.ptime = f->udev->plugtime; + break; + + case USB_CLAIM_INTERFACE: + case USB_RELEASE_INTERFACE: + /* TODO */ + break; + + case USB_IFACE_DRIVER_ACTIVE: + /* TODO */ + *u.pint = 0; + break; + + case USB_IFACE_DRIVER_DETACH: + /* TODO */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + break; + } + error = EINVAL; + break; + + case USB_SET_POWER_MODE: + error = ugen_set_power_mode(f, *u.pint); + break; + + case USB_GET_POWER_MODE: + *u.pint = ugen_get_power_mode(f); + break; + + case USB_SET_PORT_ENABLE: + error = ugen_do_port_feature(f, + *u.pint, 1, UHF_PORT_ENABLE); + break; + + case USB_SET_PORT_DISABLE: + error = ugen_do_port_feature(f, + *u.pint, 0, UHF_PORT_ENABLE); + break; + + default: + error = EINVAL; + break; + } + return (error); +} + +static int +ugen_ioctl(struct usb2_fifo *f, u_long cmd, void *addr, int fflags, + struct thread *td) +{ + int error; + + DPRINTFN(6, "cmd=%08lx\n", cmd); + error = ugen_fs_ioctl(f, cmd, addr, fflags); + if (error == ENOTTY) { + if (f->flag_no_uref) { + mtx_lock(f->priv_mtx); + error = ugen_iface_ioctl(f, cmd, addr, fflags); + mtx_unlock(f->priv_mtx); + } else { + error = ugen_ctrl_ioctl(f, cmd, addr, fflags); + } + } + DPRINTFN(6, "error=%d\n", error); + return (error); +} + +static void +ugen_default_fs_callback(struct usb2_xfer *xfer) +{ + ; /* workaround for a bug in "indent" */ + + DPRINTF("st=%u alen=%u aframes=%u\n", + USB_GET_STATE(xfer), xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + usb2_start_hardware(xfer); + break; + default: + ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo)); + break; + } + return; +} diff --git a/sys/dev/usb2/core/usb2_generic.h b/sys/dev/usb2/core/usb2_generic.h new file mode 100644 index 000000000000..3a4e7c940ea4 --- /dev/null +++ b/sys/dev/usb2/core/usb2_generic.h @@ -0,0 +1,33 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_GENERIC_H_ +#define _USB2_GENERIC_H_ + +extern struct usb2_fifo_methods usb2_ugen_methods; +int ugen_do_request(struct usb2_fifo *f, struct usb2_ctl_request *ur); + +#endif /* _USB2_GENERIC_H_ */ diff --git a/sys/dev/usb2/core/usb2_handle_request.c b/sys/dev/usb2/core/usb2_handle_request.c new file mode 100644 index 000000000000..7dc82c072699 --- /dev/null +++ b/sys/dev/usb2/core/usb2_handle_request.c @@ -0,0 +1,750 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* enum */ + +enum { + ST_DATA, + ST_POST_STATUS, +}; + +/* function prototypes */ + +static uint8_t usb2_handle_get_stall(struct usb2_device *udev, uint8_t ea_val); +static usb2_error_t usb2_handle_remote_wakeup(struct usb2_xfer *xfer, uint8_t is_on); +static usb2_error_t usb2_handle_request(struct usb2_xfer *xfer); +static usb2_error_t usb2_handle_set_config(struct usb2_xfer *xfer, uint8_t conf_no); +static usb2_error_t usb2_handle_set_stall(struct usb2_xfer *xfer, uint8_t ep, uint8_t do_stall); +static usb2_error_t usb2_handle_iface_request(struct usb2_xfer *xfer, void **ppdata, uint16_t *plen, struct usb2_device_request req, uint16_t off, uint8_t state); + +/*------------------------------------------------------------------------* + * usb2_handle_request_callback + * + * This function is the USB callback for generic USB Device control + * transfers. + *------------------------------------------------------------------------*/ +void +usb2_handle_request_callback(struct usb2_xfer *xfer) +{ + usb2_error_t err; + + /* check the current transfer state */ + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + + /* handle the request */ + err = usb2_handle_request(xfer); + + if (err) { + + if (err == USB_ERR_BAD_CONTEXT) { + /* we need to re-setup the control transfer */ + usb2_needs_explore(xfer->udev->bus, 0); + break; + } + /* + * If no control transfer is active, + * receive the next SETUP message: + */ + goto tr_restart; + } + usb2_start_hardware(xfer); + break; + + default: + if (xfer->error != USB_ERR_CANCELLED) { + /* should not happen - try stalling */ + goto tr_restart; + } + break; + } + return; + +tr_restart: + xfer->frlengths[0] = sizeof(struct usb2_device_request); + xfer->nframes = 1; + xfer->flags.manual_status = 1; + xfer->flags.force_short_xfer = 0; + xfer->flags.stall_pipe = 1; /* cancel previous transfer, if any */ + usb2_start_hardware(xfer); + return; +} + +/*------------------------------------------------------------------------* + * usb2_handle_set_config + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_set_config(struct usb2_xfer *xfer, uint8_t conf_no) +{ + usb2_error_t err = 0; + + /* + * We need to protect against other threads doing probe and + * attach: + */ + mtx_unlock(xfer->priv_mtx); + mtx_lock(&Giant); /* XXX */ + sx_xlock(xfer->udev->default_sx + 1); + + if (conf_no == USB_UNCONFIG_NO) { + conf_no = USB_UNCONFIG_INDEX; + } else { + /* + * The relationship between config number and config index + * is very simple in our case: + */ + conf_no--; + } + + if (usb2_set_config_index(xfer->udev, conf_no)) { + DPRINTF("set config %d failed\n", conf_no); + err = USB_ERR_STALLED; + goto done; + } + if (usb2_probe_and_attach(xfer->udev, USB_IFACE_INDEX_ANY)) { + DPRINTF("probe and attach failed\n"); + err = USB_ERR_STALLED; + goto done; + } +done: + mtx_unlock(&Giant); /* XXX */ + sx_unlock(xfer->udev->default_sx + 1); + mtx_lock(xfer->priv_mtx); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_handle_iface_request + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_iface_request(struct usb2_xfer *xfer, + void **ppdata, uint16_t *plen, + struct usb2_device_request req, uint16_t off, uint8_t state) +{ + struct usb2_interface *iface; + struct usb2_interface *iface_parent; /* parent interface */ + struct usb2_device *udev = xfer->udev; + int error; + uint8_t iface_index; + + if ((req.bmRequestType & 0x1F) == UT_INTERFACE) { + iface_index = req.wIndex[0]; /* unicast */ + } else { + iface_index = 0; /* broadcast */ + } + + /* + * We need to protect against other threads doing probe and + * attach: + */ + mtx_unlock(xfer->priv_mtx); + mtx_lock(&Giant); /* XXX */ + sx_xlock(udev->default_sx + 1); + + error = ENXIO; + +tr_repeat: + iface = usb2_get_iface(udev, iface_index); + if ((iface == NULL) || + (iface->idesc == NULL)) { + /* end of interfaces non-existing interface */ + goto tr_stalled; + } + /* forward request to interface, if any */ + + if ((error != 0) && + (error != ENOTTY) && + (iface->subdev != NULL) && + device_is_attached(iface->subdev)) { +#if 0 + DEVMETHOD(usb2_handle_request, NULL); /* dummy */ +#endif + error = USB2_HANDLE_REQUEST(iface->subdev, + &req, ppdata, plen, + off, (state == ST_POST_STATUS)); + } + iface_parent = usb2_get_iface(udev, iface->parent_iface_index); + + if ((iface_parent == NULL) || + (iface_parent->idesc == NULL)) { + /* non-existing interface */ + iface_parent = NULL; + } + /* forward request to parent interface, if any */ + + if ((error != 0) && + (error != ENOTTY) && + (iface_parent != NULL) && + (iface_parent->subdev != NULL) && + ((req.bmRequestType & 0x1F) == UT_INTERFACE) && + (iface_parent->subdev != iface->subdev) && + device_is_attached(iface_parent->subdev)) { + error = USB2_HANDLE_REQUEST(iface_parent->subdev, + &req, ppdata, plen, off, + (state == ST_POST_STATUS)); + } + if (error == 0) { + /* negativly adjust pointer and length */ + *ppdata = ((uint8_t *)(*ppdata)) - off; + *plen += off; + goto tr_valid; + } else if (error == ENOTTY) { + goto tr_stalled; + } + if ((req.bmRequestType & 0x1F) != UT_INTERFACE) { + iface_index++; /* iterate */ + goto tr_repeat; + } + if (state == ST_POST_STATUS) { + /* we are complete */ + goto tr_valid; + } + switch (req.bmRequestType) { + case UT_WRITE_INTERFACE: + switch (req.bRequest) { + case UR_SET_INTERFACE: + /* + * Handle special case. If we have parent interface + * we just reset the endpoints, because this is a + * multi interface device and re-attaching only a + * part of the device is not possible. Also if the + * alternate setting is the same like before we just + * reset the interface endoints. + */ + if ((iface_parent != NULL) || + (iface->alt_index == req.wValue[0])) { + error = usb2_reset_iface_endpoints(udev, + iface_index); + if (error) { + DPRINTF("alt setting failed %s\n", + usb2_errstr(error)); + goto tr_stalled; + } + break; + } + error = usb2_set_alt_interface_index(udev, + iface_index, req.wValue[0]); + if (error) { + DPRINTF("alt setting failed %s\n", + usb2_errstr(error)); + goto tr_stalled; + } + error = usb2_probe_and_attach(udev, + iface_index); + if (error) { + DPRINTF("alt setting probe failed\n"); + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req.bRequest) { + case UR_GET_INTERFACE: + *ppdata = &iface->alt_index; + *plen = 1; + break; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } +tr_valid: + mtx_unlock(&Giant); + sx_unlock(udev->default_sx + 1); + mtx_lock(xfer->priv_mtx); + return (0); + +tr_stalled: + mtx_unlock(&Giant); + sx_unlock(udev->default_sx + 1); + mtx_lock(xfer->priv_mtx); + return (USB_ERR_STALLED); +} + +/*------------------------------------------------------------------------* + * usb2_handle_stall + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_set_stall(struct usb2_xfer *xfer, uint8_t ep, uint8_t do_stall) +{ + usb2_error_t err; + + mtx_unlock(xfer->priv_mtx); + err = usb2_set_endpoint_stall(xfer->udev, + usb2_get_pipe_by_addr(xfer->udev, ep), do_stall); + mtx_lock(xfer->priv_mtx); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_handle_get_stall + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb2_handle_get_stall(struct usb2_device *udev, uint8_t ea_val) +{ + struct usb2_pipe *pipe; + uint8_t halted; + + pipe = usb2_get_pipe_by_addr(udev, ea_val); + if (pipe == NULL) { + /* nothing to do */ + return (0); + } + mtx_lock(&udev->bus->mtx); + halted = pipe->is_stalled; + mtx_unlock(&udev->bus->mtx); + + return (halted); +} + +/*------------------------------------------------------------------------* + * usb2_handle_remote_wakeup + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_remote_wakeup(struct usb2_xfer *xfer, uint8_t is_on) +{ + struct usb2_device *udev; + struct usb2_bus *bus; + + udev = xfer->udev; + bus = udev->bus; + + mtx_lock(&bus->mtx); + + if (is_on) { + udev->flags.remote_wakeup = 1; + } else { + udev->flags.remote_wakeup = 0; + } + + (bus->methods->rem_wakeup_set) (xfer->udev, is_on); + + mtx_unlock(&bus->mtx); + + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_handle_request + * + * Internal state sequence: + * + * ST_DATA -> ST_POST_STATUS + * + * Returns: + * 0: Ready to start hardware + * Else: Stall current transfer, if any + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_handle_request(struct usb2_xfer *xfer) +{ + struct usb2_device_request req; + struct usb2_device *udev; + const void *src_zcopy; /* zero-copy source pointer */ + const void *src_mcopy; /* non zero-copy source pointer */ + uint16_t off; /* data offset */ + uint16_t rem; /* data remainder */ + uint16_t max_len; /* max fragment length */ + uint16_t wValue; + uint16_t wIndex; + uint8_t state; + usb2_error_t err; + union { + uWord wStatus; + uint8_t buf[2]; + } temp; + + /* + * Filter the USB transfer state into + * something which we understand: + */ + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + state = ST_DATA; + + if (!xfer->flags_int.control_act) { + /* nothing to do */ + goto tr_stalled; + } + break; + + default: /* USB_ST_TRANSFERRED */ + if (!xfer->flags_int.control_act) { + state = ST_POST_STATUS; + } else { + state = ST_DATA; + } + break; + } + + /* reset frame stuff */ + + xfer->frlengths[0] = 0; + + usb2_set_frame_offset(xfer, 0, 0); + usb2_set_frame_offset(xfer, sizeof(req), 1); + + /* get the current request, if any */ + + usb2_copy_out(xfer->frbuffers, 0, &req, sizeof(req)); + + if (xfer->flags_int.control_rem == 0xFFFF) { + /* first time - not initialised */ + rem = UGETW(req.wLength); + off = 0; + } else { + /* not first time - initialised */ + rem = xfer->flags_int.control_rem; + off = UGETW(req.wLength) - rem; + } + + /* set some defaults */ + + max_len = 0; + src_zcopy = NULL; + src_mcopy = NULL; + udev = xfer->udev; + + /* get some request fields decoded */ + + wValue = UGETW(req.wValue); + wIndex = UGETW(req.wIndex); + + DPRINTF("req 0x%02x 0x%02x 0x%04x 0x%04x " + "off=0x%x rem=0x%x, state=%d\n", req.bmRequestType, + req.bRequest, wValue, wIndex, off, rem, state); + + /* demultiplex the control request */ + + switch (req.bmRequestType) { + case UT_READ_DEVICE: + if (state != ST_DATA) { + break; + } + switch (req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req.bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + switch (wValue) { + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (wValue) { + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req.bRequest) { + case UR_CLEAR_FEATURE: + switch (wValue) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (wValue) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req.bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + default: + /* we use "USB_ADD_BYTES" to de-const the src_zcopy */ + err = usb2_handle_iface_request(xfer, + USB_ADD_BYTES(&src_zcopy, 0), + &max_len, req, off, state); + if (err == 0) { + goto tr_valid; + } + /* + * Reset zero-copy pointer and max length + * variable in case they were unintentionally + * set: + */ + src_zcopy = NULL; + max_len = 0; + + /* + * Check if we have a vendor specific + * descriptor: + */ + goto tr_handle_get_descriptor; + } + goto tr_valid; + +tr_handle_get_descriptor: + (usb2_temp_get_desc_p) (udev, &req, &src_zcopy, &max_len); + if (src_zcopy == NULL) { + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_config: + temp.buf[0] = udev->curr_config_no; + src_mcopy = temp.buf; + max_len = 1; + goto tr_valid; + +tr_handle_get_status: + + wValue = 0; + + mtx_lock(&udev->bus->mtx); + if (udev->flags.remote_wakeup) { + wValue |= UDS_REMOTE_WAKEUP; + } + if (udev->flags.self_powered) { + wValue |= UDS_SELF_POWERED; + } + mtx_unlock(&udev->bus->mtx); + + USETW(temp.wStatus, wValue); + src_mcopy = temp.wStatus; + max_len = sizeof(temp.wStatus); + goto tr_valid; + +tr_handle_set_address: + if (state == ST_DATA) { + if (wValue >= 0x80) { + /* invalid value */ + goto tr_stalled; + } else if (udev->curr_config_no != 0) { + /* we are configured ! */ + goto tr_stalled; + } + } else if (state == ST_POST_STATUS) { + udev->address = (wValue & 0x7F); + goto tr_bad_context; + } + goto tr_valid; + +tr_handle_set_config: + if (state == ST_DATA) { + if (usb2_handle_set_config(xfer, req.wValue[0])) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_clear_halt: + if (state == ST_DATA) { + if (usb2_handle_set_stall(xfer, req.wIndex[0], 0)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_clear_wakeup: + if (state == ST_DATA) { + if (usb2_handle_remote_wakeup(xfer, 0)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_set_halt: + if (state == ST_DATA) { + if (usb2_handle_set_stall(xfer, req.wIndex[0], 1)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_set_wakeup: + if (state == ST_DATA) { + if (usb2_handle_remote_wakeup(xfer, 1)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_get_ep_status: + if (state == ST_DATA) { + temp.wStatus[0] = + usb2_handle_get_stall(udev, req.wIndex[0]); + temp.wStatus[1] = 0; + src_mcopy = temp.wStatus; + max_len = sizeof(temp.wStatus); + } + goto tr_valid; + +tr_valid: + if (state == ST_POST_STATUS) { + goto tr_stalled; + } + /* subtract offset from length */ + + max_len -= off; + + /* Compute the real maximum data length */ + + if (max_len > xfer->max_data_length) { + max_len = xfer->max_data_length; + } + if (max_len > rem) { + max_len = rem; + } + /* + * If the remainder is greater than the maximum data length, + * we need to truncate the value for the sake of the + * comparison below: + */ + if (rem > xfer->max_data_length) { + rem = xfer->max_data_length; + } + if (rem != max_len) { + /* + * If we don't transfer the data we can transfer, then + * the transfer is short ! + */ + xfer->flags.force_short_xfer = 1; + xfer->nframes = 2; + } else { + /* + * Default case + */ + xfer->flags.force_short_xfer = 0; + xfer->nframes = max_len ? 2 : 1; + } + if (max_len > 0) { + if (src_mcopy) { + src_mcopy = USB_ADD_BYTES(src_mcopy, off); + usb2_copy_in(xfer->frbuffers + 1, 0, + src_mcopy, max_len); + } else { + usb2_set_frame_data(xfer, + USB_ADD_BYTES(src_zcopy, off), 1); + } + xfer->frlengths[1] = max_len; + } else { + /* the end is reached, send status */ + xfer->flags.manual_status = 0; + xfer->frlengths[1] = 0; + } + DPRINTF("success\n"); + return (0); /* success */ + +tr_stalled: + DPRINTF("%s\n", (state == ST_POST_STATUS) ? + "complete" : "stalled"); + return (USB_ERR_STALLED); + +tr_bad_context: + DPRINTF("bad context\n"); + return (USB_ERR_BAD_CONTEXT); +} diff --git a/sys/dev/usb2/core/usb2_handle_request.h b/sys/dev/usb2/core/usb2_handle_request.h new file mode 100644 index 000000000000..6cc050321371 --- /dev/null +++ b/sys/dev/usb2/core/usb2_handle_request.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_HANDLE_REQUEST_H_ +#define _USB2_HANDLE_REQUEST_H_ + +#endif /* _USB2_HANDLE_REQUEST_H_ */ diff --git a/sys/dev/usb2/core/usb2_hid.c b/sys/dev/usb2/core/usb2_hid.c new file mode 100644 index 000000000000..04d7d4d0c0d7 --- /dev/null +++ b/sys/dev/usb2/core/usb2_hid.c @@ -0,0 +1,582 @@ +/* $NetBSD: hid.c,v 1.17 2001/11/13 06:24:53 lukem Exp $ */ + + +#include +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include + +static void hid_clear_local(struct hid_item *); + +#define MAXUSAGE 100 +struct hid_data { + const uint8_t *start; + const uint8_t *end; + const uint8_t *p; + struct hid_item cur; + int32_t usages[MAXUSAGE]; + int nu; + int minset; + int multi; + int multimax; + int kindset; +}; + +/*------------------------------------------------------------------------* + * hid_clear_local + *------------------------------------------------------------------------*/ +static void +hid_clear_local(struct hid_item *c) +{ + + c->usage = 0; + c->usage_minimum = 0; + c->usage_maximum = 0; + c->designator_index = 0; + c->designator_minimum = 0; + c->designator_maximum = 0; + c->string_index = 0; + c->string_minimum = 0; + c->string_maximum = 0; + c->set_delimiter = 0; +} + +/*------------------------------------------------------------------------* + * hid_start_parse + *------------------------------------------------------------------------*/ +struct hid_data * +hid_start_parse(const void *d, int len, int kindset) +{ + struct hid_data *s; + + s = malloc(sizeof *s, M_TEMP, M_WAITOK | M_ZERO); + s->start = s->p = d; + s->end = ((const uint8_t *)d) + len; + s->kindset = kindset; + return (s); +} + +/*------------------------------------------------------------------------* + * hid_end_parse + *------------------------------------------------------------------------*/ +void +hid_end_parse(struct hid_data *s) +{ + + while (s->cur.next != NULL) { + struct hid_item *hi = s->cur.next->next; + + free(s->cur.next, M_TEMP); + s->cur.next = hi; + } + free(s, M_TEMP); +} + +/*------------------------------------------------------------------------* + * hid_get_item + *------------------------------------------------------------------------*/ +int +hid_get_item(struct hid_data *s, struct hid_item *h) +{ + struct hid_item *c = &s->cur; + unsigned int bTag, bType, bSize; + uint32_t oldpos; + const uint8_t *data; + int32_t dval; + const uint8_t *p; + struct hid_item *hi; + int i; + +top: + if (s->multimax != 0) { + if (s->multi < s->multimax) { + c->usage = s->usages[MIN(s->multi, s->nu - 1)]; + s->multi++; + *h = *c; + c->loc.pos += c->loc.size; + h->next = 0; + return (1); + } else { + c->loc.count = s->multimax; + s->multimax = 0; + s->nu = 0; + hid_clear_local(c); + } + } + for (;;) { + p = s->p; + if (p >= s->end) + return (0); + + bSize = *p++; + if (bSize == 0xfe) { + /* long item */ + bSize = *p++; + bSize |= *p++ << 8; + bTag = *p++; + data = p; + p += bSize; + bType = 0xff; /* XXX what should it be */ + } else { + /* short item */ + bTag = bSize >> 4; + bType = (bSize >> 2) & 3; + bSize &= 3; + if (bSize == 3) + bSize = 4; + data = p; + p += bSize; + } + s->p = p; + switch (bSize) { + case 0: + dval = 0; + break; + case 1: + dval = (int8_t)*data++; + break; + case 2: + dval = *data++; + dval |= *data++ << 8; + dval = (int16_t)dval; + break; + case 4: + dval = *data++; + dval |= *data++ << 8; + dval |= *data++ << 16; + dval |= *data++ << 24; + break; + default: + printf("BAD LENGTH %d\n", bSize); + continue; + } + + switch (bType) { + case 0: /* Main */ + switch (bTag) { + case 8: /* Input */ + if (!(s->kindset & (1 << hid_input))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_input; + c->flags = dval; + ret: + if (c->flags & HIO_VARIABLE) { + s->multimax = c->loc.count; + s->multi = 0; + c->loc.count = 1; + if (s->minset) { + for (i = c->usage_minimum; + i <= c->usage_maximum; + i++) { + s->usages[s->nu] = i; + if (s->nu < MAXUSAGE - 1) + s->nu++; + } + s->minset = 0; + } + goto top; + } else { + *h = *c; + h->next = 0; + c->loc.pos += + c->loc.size * c->loc.count; + hid_clear_local(c); + s->minset = 0; + return (1); + } + case 9: /* Output */ + if (!(s->kindset & (1 << hid_output))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_output; + c->flags = dval; + goto ret; + case 10: /* Collection */ + c->kind = hid_collection; + c->collection = dval; + c->collevel++; + *h = *c; + hid_clear_local(c); + s->nu = 0; + return (1); + case 11: /* Feature */ + if (!(s->kindset & (1 << hid_feature))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_feature; + c->flags = dval; + goto ret; + case 12: /* End collection */ + c->kind = hid_endcollection; + c->collevel--; + *h = *c; + hid_clear_local(c); + s->nu = 0; + return (1); + default: + printf("Main bTag=%d\n", bTag); + break; + } + break; + case 1: /* Global */ + switch (bTag) { + case 0: + c->_usage_page = dval << 16; + break; + case 1: + c->logical_minimum = dval; + break; + case 2: + c->logical_maximum = dval; + break; + case 3: + c->physical_minimum = dval; + break; + case 4: + c->physical_maximum = dval; + break; + case 5: + c->unit_exponent = dval; + break; + case 6: + c->unit = dval; + break; + case 7: + c->loc.size = dval; + break; + case 8: + c->report_ID = dval; + break; + case 9: + c->loc.count = dval; + break; + case 10: /* Push */ + hi = malloc(sizeof *hi, M_TEMP, M_WAITOK); + *hi = s->cur; + c->next = hi; + break; + case 11: /* Pop */ + hi = c->next; + oldpos = c->loc.pos; + s->cur = *hi; + c->loc.pos = oldpos; + free(hi, M_TEMP); + break; + default: + printf("Global bTag=%d\n", bTag); + break; + } + break; + case 2: /* Local */ + switch (bTag) { + case 0: + if (bSize == 1) + dval = c->_usage_page | (dval & 0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval & 0xffff); + c->usage = dval; + if (s->nu < MAXUSAGE) + s->usages[s->nu++] = dval; + /* else XXX */ + break; + case 1: + s->minset = 1; + if (bSize == 1) + dval = c->_usage_page | (dval & 0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval & 0xffff); + c->usage_minimum = dval; + break; + case 2: + if (bSize == 1) + dval = c->_usage_page | (dval & 0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval & 0xffff); + c->usage_maximum = dval; + break; + case 3: + c->designator_index = dval; + break; + case 4: + c->designator_minimum = dval; + break; + case 5: + c->designator_maximum = dval; + break; + case 7: + c->string_index = dval; + break; + case 8: + c->string_minimum = dval; + break; + case 9: + c->string_maximum = dval; + break; + case 10: + c->set_delimiter = dval; + break; + default: + printf("Local bTag=%d\n", bTag); + break; + } + break; + default: + printf("default bType=%d\n", bType); + break; + } + } +} + +/*------------------------------------------------------------------------* + * hid_report_size + *------------------------------------------------------------------------*/ +int +hid_report_size(const void *buf, int len, enum hid_kind k, uint8_t *idp) +{ + struct hid_data *d; + struct hid_item h; + int hi, lo, size, id; + + id = 0; + hi = lo = -1; + for (d = hid_start_parse(buf, len, 1 << k); hid_get_item(d, &h);) + if (h.kind == k) { + if (h.report_ID != 0 && !id) + id = h.report_ID; + if (h.report_ID == id) { + if (lo < 0) + lo = h.loc.pos; + hi = h.loc.pos + h.loc.size * h.loc.count; + } + } + hid_end_parse(d); + size = hi - lo; + if (id != 0) { + size += 8; + *idp = id; /* XXX wrong */ + } else + *idp = 0; + return ((size + 7) / 8); +} + +/*------------------------------------------------------------------------* + * hid_locate + *------------------------------------------------------------------------*/ +int +hid_locate(const void *desc, int size, uint32_t u, enum hid_kind k, + struct hid_location *loc, uint32_t *flags) +{ + struct hid_data *d; + struct hid_item h; + + for (d = hid_start_parse(desc, size, 1 << k); hid_get_item(d, &h);) { + if (h.kind == k && !(h.flags & HIO_CONST) && h.usage == u) { + if (loc != NULL) + *loc = h.loc; + if (flags != NULL) + *flags = h.flags; + hid_end_parse(d); + return (1); + } + } + hid_end_parse(d); + loc->size = 0; + return (0); +} + +/*------------------------------------------------------------------------* + * hid_get_data + *------------------------------------------------------------------------*/ +uint32_t +hid_get_data(const uint8_t *buf, uint32_t len, struct hid_location *loc) +{ + uint32_t hpos = loc->pos; + uint32_t hsize = loc->size; + uint32_t data; + int i, s, t; + + DPRINTFN(11, "hid_get_data: loc %d/%d\n", hpos, hsize); + + if (hsize == 0) + return (0); + + data = 0; + s = hpos / 8; + for (i = hpos; i < (hpos + hsize); i += 8) { + t = (i / 8); + if (t < len) { + data |= buf[t] << ((t - s) * 8); + } + } + data >>= hpos % 8; + data &= (1 << hsize) - 1; + hsize = 32 - hsize; + /* Sign extend */ + data = ((int32_t)data << hsize) >> hsize; + DPRINTFN(11, "hid_get_data: loc %d/%d = %lu\n", + loc->pos, loc->size, (long)data); + return (data); +} + +/*------------------------------------------------------------------------* + * hid_is_collection + *------------------------------------------------------------------------*/ +int +hid_is_collection(const void *desc, int size, uint32_t usage) +{ + struct hid_data *hd; + struct hid_item hi; + int err; + + hd = hid_start_parse(desc, size, hid_input); + if (hd == NULL) + return (0); + + err = hid_get_item(hd, &hi) && + hi.kind == hid_collection && + hi.usage == usage; + hid_end_parse(hd); + return (err); +} + +/*------------------------------------------------------------------------* + * hid_get_descriptor_from_usb + * + * This function will search for a HID descriptor between two USB + * interface descriptors. + * + * Return values: + * NULL: No more HID descriptors. + * Else: Pointer to HID descriptor. + *------------------------------------------------------------------------*/ +struct usb2_hid_descriptor * +hid_get_descriptor_from_usb(struct usb2_config_descriptor *cd, + struct usb2_interface_descriptor *id) +{ + struct usb2_descriptor *desc = (void *)id; + + if (desc == NULL) { + return (NULL); + } + while ((desc = usb2_desc_foreach(cd, desc))) { + if ((desc->bDescriptorType == UDESC_HID) && + (desc->bLength >= USB_HID_DESCRIPTOR_SIZE(0))) { + return (void *)desc; + } + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_hid_desc + * + * This function will read out an USB report descriptor from the USB + * device. + * + * Return values: + * NULL: Failure. + * Else: Success. The pointer should eventually be passed to free(). + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_hid_desc(struct usb2_device *udev, struct mtx *mtx, + void **descp, uint16_t *sizep, + usb2_malloc_type mem, uint8_t iface_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_hid_descriptor *hid; + usb2_error_t err; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + hid = hid_get_descriptor_from_usb + (usb2_get_config_descriptor(udev), iface->idesc); + + if (hid == NULL) { + return (USB_ERR_IOERROR); + } + *sizep = UGETW(hid->descrs[0].wDescriptorLength); + if (*sizep == 0) { + return (USB_ERR_IOERROR); + } + if (mtx) + mtx_unlock(mtx); + + *descp = malloc(*sizep, mem, M_ZERO | M_WAITOK); + + if (mtx) + mtx_lock(mtx); + + if (*descp == NULL) { + return (USB_ERR_NOMEM); + } + err = usb2_req_get_report_descriptor + (udev, mtx, *descp, *sizep, iface_index); + + if (err) { + free(*descp, mem); + *descp = NULL; + return (err); + } + return (USB_ERR_NORMAL_COMPLETION); +} diff --git a/sys/dev/usb2/core/usb2_hid.h b/sys/dev/usb2/core/usb2_hid.h new file mode 100644 index 000000000000..9809cde9bb3b --- /dev/null +++ b/sys/dev/usb2/core/usb2_hid.h @@ -0,0 +1,89 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. 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. + */ + +#ifndef _USB2_CORE_HID_H_ +#define _USB2_CORE_HID_H_ + +struct usb2_hid_descriptor; +struct usb2_config_descriptor; + +enum hid_kind { + hid_input, hid_output, hid_feature, hid_collection, hid_endcollection +}; + +struct hid_location { + uint32_t size; + uint32_t count; + uint32_t pos; +}; + +struct hid_item { + /* Global */ + int32_t _usage_page; + int32_t logical_minimum; + int32_t logical_maximum; + int32_t physical_minimum; + int32_t physical_maximum; + int32_t unit_exponent; + int32_t unit; + int32_t report_ID; + /* Local */ + int32_t usage; + int32_t usage_minimum; + int32_t usage_maximum; + int32_t designator_index; + int32_t designator_minimum; + int32_t designator_maximum; + int32_t string_index; + int32_t string_minimum; + int32_t string_maximum; + int32_t set_delimiter; + /* Misc */ + int32_t collection; + int collevel; + enum hid_kind kind; + uint32_t flags; + /* Location */ + struct hid_location loc; + /* */ + struct hid_item *next; +}; + +/* prototypes from "usb2_hid.c" */ + +struct hid_data *hid_start_parse(const void *d, int len, int kindset); +void hid_end_parse(struct hid_data *s); +int hid_get_item(struct hid_data *s, struct hid_item *h); +int hid_report_size(const void *buf, int len, enum hid_kind k, uint8_t *id); +int hid_locate(const void *desc, int size, uint32_t usage, enum hid_kind kind, struct hid_location *loc, uint32_t *flags); +uint32_t hid_get_data(const uint8_t *buf, uint32_t len, struct hid_location *loc); +int hid_is_collection(const void *desc, int size, uint32_t usage); +struct usb2_hid_descriptor *hid_get_descriptor_from_usb(struct usb2_config_descriptor *cd, struct usb2_interface_descriptor *id); +usb2_error_t usb2_req_get_hid_desc(struct usb2_device *udev, struct mtx *mtx, void **descp, uint16_t *sizep, usb2_malloc_type mem, uint8_t iface_index); + +#endif /* _USB2_CORE_HID_H_ */ diff --git a/sys/dev/usb2/core/usb2_hub.c b/sys/dev/usb2/core/usb2_hub.c new file mode 100644 index 000000000000..b36085583a63 --- /dev/null +++ b/sys/dev/usb2/core/usb2_hub.c @@ -0,0 +1,1330 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR uhub_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define UHUB_INTR_INTERVAL 250 /* ms */ + +#if USB_DEBUG +static int uhub_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB"); +SYSCTL_INT(_hw_usb2_uhub, OID_AUTO, debug, CTLFLAG_RW, &uhub_debug, 0, + "Debug level"); +#endif + +struct uhub_current_state { + uint16_t port_change; + uint16_t port_status; +}; + +struct uhub_softc { + struct uhub_current_state sc_st;/* current state */ + device_t sc_dev; /* base device */ + struct usb2_device *sc_udev; /* USB device */ + struct usb2_xfer *sc_xfer[2]; /* interrupt xfer */ + uint8_t sc_flags; +#define UHUB_FLAG_INTR_STALL 0x02 + char sc_name[32]; +}; + +#define UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol) +#define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB) +#define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT) + +/* prototypes for type checking: */ + +static device_probe_t uhub_probe; +static device_attach_t uhub_attach; +static device_detach_t uhub_detach; + +static bus_driver_added_t uhub_driver_added; +static bus_child_location_str_t uhub_child_location_string; +static bus_child_pnpinfo_str_t uhub_child_pnpinfo_string; + +static usb2_callback_t uhub_intr_callback; +static usb2_callback_t uhub_intr_clear_stall_callback; + +static const struct usb2_config uhub_config[2] = { + + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_ANY, + .mh.timeout = 0, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &uhub_intr_callback, + .mh.interval = UHUB_INTR_INTERVAL, + }, + + [1] = { + .type = UE_CONTROL, + .endpoint = 0, + .direction = UE_DIR_ANY, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + .mh.flags = {}, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uhub_intr_clear_stall_callback, + }, +}; + +/* + * driver instance for "hub" connected to "usb" + * and "hub" connected to "hub" + */ +static devclass_t uhub_devclass; + +static driver_t uhub_driver = +{ + .name = "ushub", + .methods = (device_method_t[]){ + DEVMETHOD(device_probe, uhub_probe), + DEVMETHOD(device_attach, uhub_attach), + DEVMETHOD(device_detach, uhub_detach), + + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD(bus_child_location_str, uhub_child_location_string), + DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_string), + DEVMETHOD(bus_driver_added, uhub_driver_added), + {0, 0} + }, + .size = sizeof(struct uhub_softc) +}; + +DRIVER_MODULE(ushub, usbus, uhub_driver, uhub_devclass, 0, 0); +DRIVER_MODULE(ushub, ushub, uhub_driver, uhub_devclass, NULL, 0); + +static void +uhub_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uhub_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UHUB_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uhub_intr_callback(struct usb2_xfer *xfer) +{ + struct uhub_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(2, "\n"); + /* + * This is an indication that some port + * has changed status. Notify the bus + * event handler thread that we need + * to be explored again: + */ + usb2_needs_explore(sc->sc_udev->bus, 0); + + case USB_ST_SETUP: + if (sc->sc_flags & UHUB_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[1]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= UHUB_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[1]); + } + return; + } +} + +/*------------------------------------------------------------------------* + * uhub_explore_sub - subroutine + * + * Return values: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_explore_sub(struct uhub_softc *sc, struct usb2_port *up) +{ + struct usb2_bus *bus; + struct usb2_device *child; + uint8_t refcount; + usb2_error_t err; + + bus = sc->sc_udev->bus; + err = 0; + + /* get driver added refcount from USB bus */ + refcount = bus->driver_added_refcount; + + /* get device assosiated with the given port */ + child = usb2_bus_port_get_device(bus, up); + if (child == NULL) { + /* nothing to do */ + goto done; + } + /* check if probe and attach should be done */ + + if (child->driver_added_refcount != refcount) { + child->driver_added_refcount = refcount; + err = usb2_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + if (err) { + goto done; + } + } + /* start control transfer, if device mode */ + + if (child->flags.usb2_mode == USB_MODE_DEVICE) { + usb2_default_transfer_setup(child); + } + /* if a HUB becomes present, do a recursive HUB explore */ + + if (child->hub) { + err = (child->hub->explore) (child); + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_read_port_status - factored out code + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_read_port_status(struct uhub_softc *sc, uint8_t portno) +{ + struct usb2_port_status ps; + usb2_error_t err; + + err = usb2_req_get_port_status( + sc->sc_udev, &Giant, &ps, portno); + + /* update status regardless of error */ + + sc->sc_st.port_status = UGETW(ps.wPortStatus); + sc->sc_st.port_change = UGETW(ps.wPortChange); + + /* debugging print */ + + DPRINTFN(4, "port %d, wPortStatus=0x%04x, " + "wPortChange=0x%04x, err=%s\n", + portno, sc->sc_st.port_status, + sc->sc_st.port_change, usb2_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_reattach_port + * + * Returns: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_reattach_port(struct uhub_softc *sc, uint8_t portno) +{ + struct usb2_device *child; + struct usb2_device *udev; + usb2_error_t err; + uint8_t timeout; + uint8_t speed; + uint8_t usb2_mode; + + DPRINTF("reattaching port %d\n", portno); + + err = 0; + timeout = 0; + udev = sc->sc_udev; + child = usb2_bus_port_get_device(udev->bus, + udev->hub->ports + portno - 1); + +repeat: + + /* first clear the port connection change bit */ + + err = usb2_req_clear_port_feature + (udev, &Giant, portno, UHF_C_PORT_CONNECTION); + + if (err) { + goto error; + } + /* detach any existing devices */ + + if (child) { + usb2_detach_device(child, USB_IFACE_INDEX_ANY, 1); + usb2_free_device(child); + child = NULL; + } + /* get fresh status */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto error; + } + /* check if nothing is connected to the port */ + + if (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS)) { + goto error; + } + /* check if there is no power on the port and print a warning */ + + if (!(sc->sc_st.port_status & UPS_PORT_POWER)) { + DPRINTF("WARNING: strange, connected port %d " + "has no power\n", portno); + } + /* check if the device is in Host Mode */ + + if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) { + + DPRINTF("Port %d is in Host Mode\n", portno); + + /* USB Host Mode */ + + /* wait for maximum device power up time */ + + usb2_pause_mtx(&Giant, USB_PORT_POWERUP_DELAY); + + /* reset port, which implies enabling it */ + + err = usb2_req_reset_port + (udev, &Giant, portno); + + if (err) { + DPRINTFN(0, "port %d reset " + "failed, error=%s\n", + portno, usb2_errstr(err)); + goto error; + } + /* get port status again, it might have changed during reset */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto error; + } + /* check if something changed during port reset */ + + if ((sc->sc_st.port_change & UPS_C_CONNECT_STATUS) || + (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))) { + if (timeout) { + DPRINTFN(0, "giving up port reset " + "- device vanished!\n"); + goto error; + } + timeout = 1; + goto repeat; + } + } else { + DPRINTF("Port %d is in Device Mode\n", portno); + } + + /* + * Figure out the device speed + */ + speed = + (sc->sc_st.port_status & UPS_HIGH_SPEED) ? USB_SPEED_HIGH : + (sc->sc_st.port_status & UPS_LOW_SPEED) ? USB_SPEED_LOW : USB_SPEED_FULL; + + /* + * Figure out the device mode + * + * NOTE: This part is currently FreeBSD specific. + */ + usb2_mode = + (sc->sc_st.port_status & UPS_PORT_MODE_DEVICE) ? + USB_MODE_DEVICE : USB_MODE_HOST; + + /* need to create a new child */ + + child = usb2_alloc_device(sc->sc_dev, udev->bus, udev, + udev->depth + 1, portno - 1, portno, speed, usb2_mode); + if (child == NULL) { + DPRINTFN(0, "could not allocate new device!\n"); + goto error; + } + return (0); /* success */ + +error: + if (child) { + usb2_detach_device(child, USB_IFACE_INDEX_ANY, 1); + usb2_free_device(child); + child = NULL; + } + if (err == 0) { + if (sc->sc_st.port_status & UPS_PORT_ENABLED) { + err = usb2_req_clear_port_feature + (sc->sc_udev, &Giant, + portno, UHF_PORT_ENABLE); + } + } + if (err) { + DPRINTFN(0, "device problem (%s), " + "disabling port %d\n", usb2_errstr(err), portno); + } + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_suspend_resume_port + * + * Returns: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) +{ + struct usb2_device *child; + struct usb2_device *udev; + uint8_t is_suspend; + usb2_error_t err; + + DPRINTF("port %d\n", portno); + + udev = sc->sc_udev; + child = usb2_bus_port_get_device(udev->bus, + udev->hub->ports + portno - 1); + + /* first clear the port suspend change bit */ + + err = usb2_req_clear_port_feature + (udev, &Giant, portno, UHF_C_PORT_SUSPEND); + + if (err) { + goto done; + } + /* get fresh status */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto done; + } + /* get current state */ + + if (sc->sc_st.port_status & UPS_SUSPEND) { + is_suspend = 1; + } else { + is_suspend = 0; + } + /* do the suspend or resume */ + + if (child) { + sx_xlock(child->default_sx + 1); + err = usb2_suspend_resume(child, is_suspend); + sx_unlock(child->default_sx + 1); + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_explore + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +uhub_explore(struct usb2_device *udev) +{ + struct usb2_hub *hub; + struct uhub_softc *sc; + struct usb2_port *up; + usb2_error_t err; + uint8_t portno; + uint8_t x; + + hub = udev->hub; + sc = hub->hubsoftc; + + DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address); + + /* ignore hubs that are too deep */ + if (udev->depth > USB_HUB_MAX_DEPTH) { + return (USB_ERR_TOO_DEEP); + } + for (x = 0; x != hub->nports; x++) { + up = hub->ports + x; + portno = x + 1; + + err = uhub_read_port_status(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + if (sc->sc_st.port_change & UPS_C_PORT_ENABLED) { + err = usb2_req_clear_port_feature( + udev, &Giant, portno, UHF_C_PORT_ENABLE); + if (err) { + /* most likely the HUB is gone */ + break; + } + if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { + /* + * Ignore the port error if the device + * has vanished ! + */ + } else if (sc->sc_st.port_status & UPS_PORT_ENABLED) { + DPRINTFN(0, "illegal enable change, " + "port %d\n", portno); + } else { + + if (up->restartcnt == USB_RESTART_MAX) { + /* XXX could try another speed ? */ + DPRINTFN(0, "port error, giving up " + "port %d\n", portno); + } else { + sc->sc_st.port_change |= UPS_C_CONNECT_STATUS; + up->restartcnt++; + } + } + } + if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { + err = uhub_reattach_port(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + if (sc->sc_st.port_change & UPS_C_SUSPEND) { + err = uhub_suspend_resume_port(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + err = uhub_explore_sub(sc, up); + if (err) { + /* no device(s) present */ + continue; + } + /* explore succeeded - reset restart counter */ + up->restartcnt = 0; + } + return (USB_ERR_NORMAL_COMPLETION); +} + +static int +uhub_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* + * The subclass for USB HUBs is ignored because it is 0 for + * some and 1 for others. + */ + if ((uaa->info.bConfigIndex == 0) && + (uaa->info.bDeviceClass == UDCLASS_HUB)) { + return (0); + } + return (ENXIO); +} + +static int +uhub_attach(device_t dev) +{ + struct uhub_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_device *udev = uaa->device; + struct usb2_device *parent_hub = udev->parent_hub; + struct usb2_hub *hub; + struct usb2_hub_descriptor hubdesc; + uint16_t pwrdly; + uint8_t x; + uint8_t nports; + uint8_t portno; + uint8_t removable; + uint8_t iface_index; + usb2_error_t err; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = udev; + sc->sc_dev = dev; + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + device_set_usb2_desc(dev); + + DPRINTFN(2, "depth=%d selfpowered=%d, parent=%p, " + "parent->selfpowered=%d\n", + udev->depth, + udev->flags.self_powered, + parent_hub, + parent_hub ? + parent_hub->flags.self_powered : 0); + + if (udev->depth > USB_HUB_MAX_DEPTH) { + DPRINTFN(0, "hub depth, %d, exceeded. HUB ignored!\n", + USB_HUB_MAX_DEPTH); + goto error; + } + if (!udev->flags.self_powered && parent_hub && + (!parent_hub->flags.self_powered)) { + DPRINTFN(0, "bus powered HUB connected to " + "bus powered HUB. HUB ignored!\n"); + goto error; + } + /* get HUB descriptor */ + + DPRINTFN(2, "getting HUB descriptor\n"); + + /* assuming that there is one port */ + err = usb2_req_get_hub_descriptor(udev, &Giant, &hubdesc, 1); + + nports = hubdesc.bNbrPorts; + + if (!err && (nports >= 8)) { + /* get complete HUB descriptor */ + err = usb2_req_get_hub_descriptor(udev, &Giant, &hubdesc, nports); + } + if (err) { + DPRINTFN(0, "getting hub descriptor failed," + "error=%s\n", usb2_errstr(err)); + goto error; + } + if (hubdesc.bNbrPorts != nports) { + DPRINTFN(0, "number of ports changed!\n"); + goto error; + } + if (nports == 0) { + DPRINTFN(0, "portless HUB!\n"); + goto error; + } + hub = malloc(sizeof(hub[0]) + (sizeof(hub->ports[0]) * nports), + M_USBDEV, M_WAITOK | M_ZERO); + + if (hub == NULL) { + goto error; + } + udev->hub = hub; + + /* init FULL-speed ISOCHRONOUS schedule */ + usb2_fs_isoc_schedule_init_all(hub->fs_isoc_schedule); + + /* initialize HUB structure */ + hub->hubsoftc = sc; + hub->explore = &uhub_explore; + hub->nports = hubdesc.bNbrPorts; + hub->hubudev = udev; + + /* if self powered hub, give ports maximum current */ + if (udev->flags.self_powered) { + hub->portpower = USB_MAX_POWER; + } else { + hub->portpower = USB_MIN_POWER; + } + + /* set up interrupt pipe */ + iface_index = 0; + err = usb2_transfer_setup(udev, &iface_index, sc->sc_xfer, + uhub_config, 2, sc, &Giant); + if (err) { + DPRINTFN(0, "cannot setup interrupt transfer, " + "errstr=%s!\n", usb2_errstr(err)); + goto error; + } + /* wait with power off for a while */ + usb2_pause_mtx(&Giant, USB_POWER_DOWN_TIME); + + /* + * To have the best chance of success we do things in the exact same + * order as Windoze98. This should not be necessary, but some + * devices do not follow the USB specs to the letter. + * + * These are the events on the bus when a hub is attached: + * Get device and config descriptors (see attach code) + * Get hub descriptor (see above) + * For all ports + * turn on power + * wait for power to become stable + * (all below happens in explore code) + * For all ports + * clear C_PORT_CONNECTION + * For all ports + * get port status + * if device connected + * wait 100 ms + * turn on reset + * wait + * clear C_PORT_RESET + * get port status + * proceed with device attachment + */ + + /* XXX should check for none, individual, or ganged power? */ + + removable = 0; + pwrdly = ((hubdesc.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + + USB_EXTRA_POWER_UP_TIME); + + for (x = 0; x != nports; x++) { + /* set up data structures */ + struct usb2_port *up = hub->ports + x; + + up->device_index = 0; + up->restartcnt = 0; + portno = x + 1; + + /* check if port is removable */ + if (!UHD_NOT_REMOV(&hubdesc, portno)) { + removable++; + } + if (!err) { + /* turn the power on */ + err = usb2_req_set_port_feature + (udev, &Giant, portno, UHF_PORT_POWER); + } + if (err) { + DPRINTFN(0, "port %d power on failed, %s\n", + portno, usb2_errstr(err)); + } + DPRINTF("turn on port %d power\n", + portno); + + /* wait for stable power */ + usb2_pause_mtx(&Giant, pwrdly); + } + + device_printf(dev, "%d port%s with %d " + "removable, %s powered\n", nports, (nports != 1) ? "s" : "", + removable, udev->flags.self_powered ? "self" : "bus"); + + /* start the interrupt endpoint */ + + mtx_lock(sc->sc_xfer[0]->priv_mtx); + usb2_transfer_start(sc->sc_xfer[0]); + mtx_unlock(sc->sc_xfer[0]->priv_mtx); + + return (0); + +error: + usb2_transfer_unsetup(sc->sc_xfer, 2); + + if (udev->hub) { + free(udev->hub, M_USBDEV); + udev->hub = NULL; + } + return (ENXIO); +} + +/* + * Called from process context when the hub is gone. + * Detach all devices on active ports. + */ +static int +uhub_detach(device_t dev) +{ + struct uhub_softc *sc = device_get_softc(dev); + struct usb2_hub *hub = sc->sc_udev->hub; + struct usb2_device *child; + uint8_t x; + + /* detach all children first */ + bus_generic_detach(dev); + + if (hub == NULL) { /* must be partially working */ + return (0); + } + for (x = 0; x != hub->nports; x++) { + + child = usb2_bus_port_get_device(sc->sc_udev->bus, hub->ports + x); + + if (child == NULL) { + continue; + } + /* + * Subdevices are not freed, because the caller of + * uhub_detach() will do that. + */ + usb2_detach_device(child, USB_IFACE_INDEX_ANY, 0); + usb2_free_device(child); + child = NULL; + } + + usb2_transfer_unsetup(sc->sc_xfer, 2); + + free(hub, M_USBDEV); + sc->sc_udev->hub = NULL; + return (0); +} + +static void +uhub_driver_added(device_t dev, driver_t *driver) +{ + usb2_needs_explore_all(); + return; +} + +struct hub_result { + struct usb2_device *udev; + uint8_t portno; + uint8_t iface_index; +}; + +static void +uhub_find_iface_index(struct usb2_hub *hub, device_t child, + struct hub_result *res) +{ + struct usb2_interface *iface; + struct usb2_device *udev; + uint8_t nports; + uint8_t x; + uint8_t i; + + nports = hub->nports; + for (x = 0; x != nports; x++) { + udev = usb2_bus_port_get_device(hub->hubudev->bus, + hub->ports + x); + if (!udev) { + continue; + } + for (i = 0; i != USB_IFACE_MAX; i++) { + iface = usb2_get_iface(udev, i); + if (iface && + (iface->subdev == child)) { + res->iface_index = i; + res->udev = udev; + res->portno = x + 1; + return; + } + } + } + res->iface_index = 0; + res->udev = NULL; + res->portno = 0; + return; +} + +static int +uhub_child_location_string(device_t parent, device_t child, + char *buf, size_t buflen) +{ + struct uhub_softc *sc = device_get_softc(parent); + struct usb2_hub *hub = sc->sc_udev->hub; + struct hub_result res; + + mtx_lock(&Giant); + uhub_find_iface_index(hub, child, &res); + if (!res.udev) { + DPRINTF("device not on hub\n"); + if (buflen) { + buf[0] = '\0'; + } + goto done; + } + snprintf(buf, buflen, "port=%u interface=%u", + res.portno, res.iface_index); +done: + mtx_unlock(&Giant); + + return (0); +} + +static int +uhub_child_pnpinfo_string(device_t parent, device_t child, + char *buf, size_t buflen) +{ + struct uhub_softc *sc = device_get_softc(parent); + struct usb2_hub *hub = sc->sc_udev->hub; + struct usb2_interface *iface; + struct hub_result res; + + mtx_lock(&Giant); + uhub_find_iface_index(hub, child, &res); + if (!res.udev) { + DPRINTF("device not on hub\n"); + if (buflen) { + buf[0] = '\0'; + } + goto done; + } + iface = usb2_get_iface(res.udev, res.iface_index); + if (iface && iface->idesc) { + snprintf(buf, buflen, "vendor=0x%04x product=0x%04x " + "devclass=0x%02x devsubclass=0x%02x " + "sernum=\"%s\" " + "intclass=0x%02x intsubclass=0x%02x", + UGETW(res.udev->ddesc.idVendor), + UGETW(res.udev->ddesc.idProduct), + res.udev->ddesc.bDeviceClass, + res.udev->ddesc.bDeviceSubClass, + res.udev->serial, + iface->idesc->bInterfaceClass, + iface->idesc->bInterfaceSubClass); + } else { + if (buflen) { + buf[0] = '\0'; + } + goto done; + } +done: + mtx_unlock(&Giant); + + return (0); +} + +/* + * The USB Transaction Translator: + * =============================== + * + * When doing LOW- and FULL-speed USB transfers accross a HIGH-speed + * USB HUB, bandwidth must be allocated for ISOCHRONOUS and INTERRUPT + * USB transfers. To utilize bandwidth dynamically the "scatter and + * gather" principle must be applied. This means that bandwidth must + * be divided into equal parts of bandwidth. With regard to USB all + * data is transferred in smaller packets with length + * "wMaxPacketSize". The problem however is that "wMaxPacketSize" is + * not a constant! + * + * The bandwidth scheduler which I have implemented will simply pack + * the USB transfers back to back until there is no more space in the + * schedule. Out of the 8 microframes which the USB 2.0 standard + * provides, only 6 are available for non-HIGH-speed devices. I have + * reserved the first 4 microframes for ISOCHRONOUS transfers. The + * last 2 microframes I have reserved for INTERRUPT transfers. Without + * this division, it is very difficult to allocate and free bandwidth + * dynamically. + * + * NOTE about the Transaction Translator in USB HUBs: + * + * USB HUBs have a very simple Transaction Translator, that will + * simply pipeline all the SPLIT transactions. That means that the + * transactions will be executed in the order they are queued! + * + */ + +/*------------------------------------------------------------------------* + * usb2_intr_find_best_slot + * + * Return value: + * The best Transaction Translation slot for an interrupt endpoint. + *------------------------------------------------------------------------*/ +static uint8_t +usb2_intr_find_best_slot(uint32_t *ptr, uint8_t start, uint8_t end) +{ + uint32_t max = 0xffffffff; + uint8_t x; + uint8_t y; + + y = 0; + + /* find the last slot with lesser used bandwidth */ + + for (x = start; x < end; x++) { + if (max >= ptr[x]) { + max = ptr[x]; + y = x; + } + } + return (y); +} + +/*------------------------------------------------------------------------* + * usb2_intr_schedule_adjust + * + * This function will update the bandwith usage for the microframe + * having index "slot" by "len" bytes. "len" can be negative. If the + * "slot" argument is greater or equal to "USB_HS_MICRO_FRAMES_MAX" + * the "slot" argument will be replaced by the slot having least used + * bandwidth. + * + * Returns: + * The slot on which the bandwidth update was done. + *------------------------------------------------------------------------*/ +uint8_t +usb2_intr_schedule_adjust(struct usb2_device *udev, int16_t len, uint8_t slot) +{ + struct usb2_bus *bus = udev->bus; + struct usb2_hub *hub; + + mtx_assert(&bus->mtx, MA_OWNED); + + if (usb2_get_speed(udev) == USB_SPEED_HIGH) { + if (slot >= USB_HS_MICRO_FRAMES_MAX) { + slot = usb2_intr_find_best_slot(bus->uframe_usage, 0, + USB_HS_MICRO_FRAMES_MAX); + } + bus->uframe_usage[slot] += len; + } else { + if (usb2_get_speed(udev) == USB_SPEED_LOW) { + len *= 8; + } + /* + * The Host Controller Driver should have + * performed checks so that the lookup + * below does not result in a NULL pointer + * access. + */ + + hub = bus->devices[udev->hs_hub_addr]->hub; + if (slot >= USB_HS_MICRO_FRAMES_MAX) { + slot = usb2_intr_find_best_slot(hub->uframe_usage, + USB_FS_ISOC_UFRAME_MAX, 6); + } + hub->uframe_usage[slot] += len; + bus->uframe_usage[slot] += len; + } + return (slot); +} + +/*------------------------------------------------------------------------* + * usb2_fs_isoc_schedule_init_sub + * + * This function initialises an USB FULL speed isochronous schedule + * entry. + *------------------------------------------------------------------------*/ +static void +usb2_fs_isoc_schedule_init_sub(struct usb2_fs_isoc_schedule *fss) +{ + fss->total_bytes = (USB_FS_ISOC_UFRAME_MAX * + USB_FS_BYTES_PER_HS_UFRAME); + fss->frame_bytes = (USB_FS_BYTES_PER_HS_UFRAME); + fss->frame_slot = 0; + return; +} + +/*------------------------------------------------------------------------* + * usb2_fs_isoc_schedule_init_all + * + * This function will reset the complete USB FULL speed isochronous + * bandwidth schedule. + *------------------------------------------------------------------------*/ +void +usb2_fs_isoc_schedule_init_all(struct usb2_fs_isoc_schedule *fss) +{ + struct usb2_fs_isoc_schedule *fss_end = fss + USB_ISOC_TIME_MAX; + + while (fss != fss_end) { + usb2_fs_isoc_schedule_init_sub(fss); + fss++; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_isoc_time_expand + * + * This function will expand the time counter from 7-bit to 16-bit. + * + * Returns: + * 16-bit isochronous time counter. + *------------------------------------------------------------------------*/ +uint16_t +usb2_isoc_time_expand(struct usb2_bus *bus, uint16_t isoc_time_curr) +{ + uint16_t rem; + + mtx_assert(&bus->mtx, MA_OWNED); + + rem = bus->isoc_time_last & (USB_ISOC_TIME_MAX - 1); + + isoc_time_curr &= (USB_ISOC_TIME_MAX - 1); + + if (isoc_time_curr < rem) { + /* the time counter wrapped around */ + bus->isoc_time_last += USB_ISOC_TIME_MAX; + } + /* update the remainder */ + + bus->isoc_time_last &= ~(USB_ISOC_TIME_MAX - 1); + bus->isoc_time_last |= isoc_time_curr; + + return (bus->isoc_time_last); +} + +/*------------------------------------------------------------------------* + * usb2_fs_isoc_schedule_isoc_time_expand + * + * This function does multiple things. First of all it will expand the + * passed isochronous time, which is the return value. Then it will + * store where the current FULL speed isochronous schedule is + * positioned in time and where the end is. See "pp_start" and + * "pp_end" arguments. + * + * Returns: + * Expanded version of "isoc_time". + * + * NOTE: This function depends on being called regularly with + * intervals less than "USB_ISOC_TIME_MAX". + *------------------------------------------------------------------------*/ +uint16_t +usb2_fs_isoc_schedule_isoc_time_expand(struct usb2_device *udev, + struct usb2_fs_isoc_schedule **pp_start, + struct usb2_fs_isoc_schedule **pp_end, + uint16_t isoc_time) +{ + struct usb2_fs_isoc_schedule *fss_end; + struct usb2_fs_isoc_schedule *fss_a; + struct usb2_fs_isoc_schedule *fss_b; + struct usb2_hub *hs_hub; + + isoc_time = usb2_isoc_time_expand(udev->bus, isoc_time); + + hs_hub = udev->bus->devices[udev->hs_hub_addr]->hub; + + if (hs_hub != NULL) { + + fss_a = hs_hub->fs_isoc_schedule + + (hs_hub->isoc_last_time % USB_ISOC_TIME_MAX); + + hs_hub->isoc_last_time = isoc_time; + + fss_b = hs_hub->fs_isoc_schedule + + (isoc_time % USB_ISOC_TIME_MAX); + + fss_end = hs_hub->fs_isoc_schedule + USB_ISOC_TIME_MAX; + + *pp_start = hs_hub->fs_isoc_schedule; + *pp_end = fss_end; + + while (fss_a != fss_b) { + if (fss_a == fss_end) { + fss_a = hs_hub->fs_isoc_schedule; + continue; + } + usb2_fs_isoc_schedule_init_sub(fss_a); + fss_a++; + } + + } else { + + *pp_start = NULL; + *pp_end = NULL; + } + return (isoc_time); +} + +/*------------------------------------------------------------------------* + * usb2_fs_isoc_schedule_alloc + * + * This function will allocate bandwidth for an isochronous FULL speed + * transaction in the FULL speed schedule. The microframe slot where + * the transaction should be started is stored in the byte pointed to + * by "pstart". The "len" argument specifies the length of the + * transaction in bytes. + * + * Returns: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +uint8_t +usb2_fs_isoc_schedule_alloc(struct usb2_fs_isoc_schedule *fss, + uint8_t *pstart, uint16_t len) +{ + uint8_t slot = fss->frame_slot; + + /* Compute overhead and bit-stuffing */ + + len += 8; + + len *= 7; + len /= 6; + + if (len > fss->total_bytes) { + *pstart = 0; /* set some dummy value */ + return (1); /* error */ + } + if (len > 0) { + + fss->total_bytes -= len; + + while (len >= fss->frame_bytes) { + len -= fss->frame_bytes; + fss->frame_bytes = USB_FS_BYTES_PER_HS_UFRAME; + fss->frame_slot++; + } + + fss->frame_bytes -= len; + } + *pstart = slot; + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_bus_port_get_device + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +struct usb2_device * +usb2_bus_port_get_device(struct usb2_bus *bus, struct usb2_port *up) +{ + if ((bus == NULL) || (up == NULL)) { + /* be NULL safe */ + return (NULL); + } + if (up->device_index == 0) { + /* nothing to do */ + return (NULL); + } + return (bus->devices[up->device_index]); +} + +/*------------------------------------------------------------------------* + * usb2_bus_port_set_device + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_bus_port_set_device(struct usb2_bus *bus, struct usb2_port *up, + struct usb2_device *udev, uint8_t device_index) +{ + if (bus == NULL) { + /* be NULL safe */ + return; + } + /* + * There is only one case where we don't + * have an USB port, and that is the Root Hub! + */ + if (up) { + if (udev) { + up->device_index = device_index; + } else { + device_index = up->device_index; + up->device_index = 0; + } + } + /* + * Make relationships to our new device + */ + if (device_index != 0) { + mtx_lock(&usb2_ref_lock); + bus->devices[device_index] = udev; + mtx_unlock(&usb2_ref_lock); + } + /* + * Debug print + */ + DPRINTFN(2, "bus %p devices[%u] = %p\n", bus, device_index, udev); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_needs_explore + * + * This functions is called when the USB event thread needs to run. + *------------------------------------------------------------------------*/ +void +usb2_needs_explore(struct usb2_bus *bus, uint8_t do_probe) +{ + DPRINTF("\n"); + + if (bus == NULL) { + DPRINTF("No bus pointer!\n"); + return; + } + mtx_lock(&bus->mtx); + if (do_probe) { + bus->do_probe = 1; + } + if (usb2_proc_msignal(&bus->explore_proc, + &bus->explore_msg[0], &bus->explore_msg[1])) { + /* ignore */ + } + mtx_unlock(&bus->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_needs_explore_all + * + * This function is called whenever a new driver is loaded and will + * cause that all USB busses are re-explored. + *------------------------------------------------------------------------*/ +void +usb2_needs_explore_all(void) +{ + struct usb2_bus *bus; + devclass_t dc; + device_t dev; + int max; + + DPRINTFN(3, "\n"); + + dc = usb2_devclass_ptr; + if (dc == NULL) { + DPRINTFN(0, "no devclass\n"); + return; + } + /* + * Explore all USB busses in parallell. + */ + max = devclass_get_maxunit(dc); + while (max >= 0) { + dev = devclass_get_device(dc, max); + if (dev) { + bus = device_get_softc(dev); + if (bus) { + usb2_needs_explore(bus, 1); + } + } + max--; + } + return; +} diff --git a/sys/dev/usb2/core/usb2_hub.h b/sys/dev/usb2/core/usb2_hub.h new file mode 100644 index 000000000000..183c9e02d854 --- /dev/null +++ b/sys/dev/usb2/core/usb2_hub.h @@ -0,0 +1,75 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_HUB_H_ +#define _USB2_HUB_H_ + +/* + * The following structure defines an USB port. + */ +struct usb2_port { + uint8_t restartcnt; +#define USB_RESTART_MAX 5 + uint8_t device_index; /* zero means not valid */ + uint8_t usb2_mode:1; /* current USB mode */ + uint8_t unused:7; +}; + +/* + * The following structure defines how many bytes are + * left in an 1ms USB time slot. + */ +struct usb2_fs_isoc_schedule { + uint16_t total_bytes; + uint8_t frame_bytes; + uint8_t frame_slot; +}; + +/* + * The following structure defines an USB HUB. + */ +struct usb2_hub { + struct usb2_fs_isoc_schedule fs_isoc_schedule[USB_ISOC_TIME_MAX]; + struct usb2_device *hubudev; /* the HUB device */ + usb2_error_t (*explore) (struct usb2_device *hub); + void *hubsoftc; + uint32_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; + uint16_t portpower; /* mA per USB port */ + uint8_t isoc_last_time; + uint8_t nports; + struct usb2_port ports[0]; +}; + +/* function prototypes */ + +uint8_t usb2_intr_schedule_adjust(struct usb2_device *udev, int16_t len, uint8_t slot); +void usb2_fs_isoc_schedule_init_all(struct usb2_fs_isoc_schedule *fss); +void usb2_bus_port_set_device(struct usb2_bus *bus, struct usb2_port *up, struct usb2_device *udev, uint8_t device_index); +struct usb2_device *usb2_bus_port_get_device(struct usb2_bus *bus, struct usb2_port *up); +void usb2_needs_explore(struct usb2_bus *bus, uint8_t do_probe); +void usb2_needs_explore_all(void); + +#endif /* _USB2_HUB_H_ */ diff --git a/sys/dev/usb2/core/usb2_if.m b/sys/dev/usb2/core/usb2_if.m new file mode 100644 index 000000000000..d0db8d4445f9 --- /dev/null +++ b/sys/dev/usb2/core/usb2_if.m @@ -0,0 +1,52 @@ +#- +# Copyright (c) 2008 Hans Petter Selasky. 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, +# without modification, immediately at the beginning of the file. +# 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. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# 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. +# +# $FreeBSD$ +# + +# USB interface description +# + +#include + +INTERFACE usb2; + +# The device received a control request +# +# Return values: +# 0: Success +# ENOTTY: Transaction stalled +# Else: Use builtin request handler +# +METHOD int handle_request { + device_t dev; + const void *req; /* pointer to the device request */ + void **pptr; /* data pointer */ + uint16_t *plen; /* maximum transfer length */ + uint16_t offset; /* data offset */ + uint8_t is_complete; /* set if transfer is complete */ +}; + diff --git a/sys/dev/usb2/core/usb2_lookup.c b/sys/dev/usb2/core/usb2_lookup.c new file mode 100644 index 000000000000..eaab8a3d6b12 --- /dev/null +++ b/sys/dev/usb2/core/usb2_lookup.c @@ -0,0 +1,134 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +/*------------------------------------------------------------------------* + * usb2_lookup_id_by_info + * + * This functions takes an array of "struct usb2_device_id" and tries + * to match the entries with the information in "struct usb2_lookup_info". + * + * NOTE: The "sizeof_id" parameter must be a multiple of the + * usb2_device_id structure size. Else the behaviour of this function + * is undefined. + * + * Return values: + * NULL: No match found. + * Else: Pointer to matching entry. + *------------------------------------------------------------------------*/ +const struct usb2_device_id * +usb2_lookup_id_by_info(const struct usb2_device_id *id, uint32_t sizeof_id, + const struct usb2_lookup_info *info) +{ + const struct usb2_device_id *id_end; + + if (id == NULL) { + goto done; + } + id_end = (const void *)(((const uint8_t *)id) + sizeof_id); + + /* + * Keep on matching array entries until we find a match or + * until we reach the end of the matching array: + */ + for (; id != id_end; id++) { + + if ((id->match_flag_vendor) && + (id->idVendor != info->idVendor)) { + continue; + } + if ((id->match_flag_product) && + (id->idProduct != info->idProduct)) { + continue; + } + if ((id->match_flag_dev_lo) && + (id->bcdDevice_lo > info->bcdDevice)) { + continue; + } + if ((id->match_flag_dev_hi) && + (id->bcdDevice_hi < info->bcdDevice)) { + continue; + } + if ((id->match_flag_dev_class) && + (id->bDeviceClass != info->bDeviceClass)) { + continue; + } + if ((id->match_flag_dev_subclass) && + (id->bDeviceSubClass != info->bDeviceSubClass)) { + continue; + } + if ((id->match_flag_dev_protocol) && + (id->bDeviceProtocol != info->bDeviceProtocol)) { + continue; + } + if ((info->bDeviceClass == 0xFF) && + (!(id->match_flag_vendor)) && + ((id->match_flag_int_class) || + (id->match_flag_int_subclass) || + (id->match_flag_int_protocol))) { + continue; + } + if ((id->match_flag_int_class) && + (id->bInterfaceClass != info->bInterfaceClass)) { + continue; + } + if ((id->match_flag_int_subclass) && + (id->bInterfaceSubClass != info->bInterfaceSubClass)) { + continue; + } + if ((id->match_flag_int_protocol) && + (id->bInterfaceProtocol != info->bInterfaceProtocol)) { + continue; + } + /* We found a match! */ + return (id); + } + +done: + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_lookup_id_by_uaa - factored out code + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb2_lookup_id_by_uaa(const struct usb2_device_id *id, uint32_t sizeof_id, + struct usb2_attach_arg *uaa) +{ + id = usb2_lookup_id_by_info(id, sizeof_id, &uaa->info); + if (id) { + /* copy driver info */ + uaa->driver_info = id->driver_info; + return (0); + } + return (ENXIO); +} diff --git a/sys/dev/usb2/core/usb2_lookup.h b/sys/dev/usb2/core/usb2_lookup.h new file mode 100644 index 000000000000..d4e39fd53b6e --- /dev/null +++ b/sys/dev/usb2/core/usb2_lookup.h @@ -0,0 +1,119 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_LOOKUP_H_ +#define _USB2_LOOKUP_H_ + +struct usb2_attach_arg; + +/* + * The following structure is used when looking up an USB driver for + * an USB device. It is inspired by the Linux structure called + * "usb2_device_id". + */ +struct usb2_device_id { + + /* Hook for driver specific information */ + const void *driver_info; + + /* Used for product specific matches; the BCD range is inclusive */ + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice_lo; + uint16_t bcdDevice_hi; + + /* Used for device class matches */ + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + + /* Used for interface class matches */ + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + /* Select which fields to match against */ + uint8_t match_flag_vendor:1; + uint8_t match_flag_product:1; + uint8_t match_flag_dev_lo:1; + uint8_t match_flag_dev_hi:1; + uint8_t match_flag_dev_class:1; + uint8_t match_flag_dev_subclass:1; + uint8_t match_flag_dev_protocol:1; + uint8_t match_flag_int_class:1; + uint8_t match_flag_int_subclass:1; + uint8_t match_flag_int_protocol:1; +}; + +#define USB_VENDOR(vend) \ + .match_flag_vendor = 1, .idVendor = (vend) + +#define USB_PRODUCT(prod) \ + .match_flag_product = 1, .idProduct = (prod) + +#define USB_VP(vend,prod) \ + USB_VENDOR(vend), USB_PRODUCT(prod) + +#define USB_VPI(vend,prod,info) \ + USB_VENDOR(vend), USB_PRODUCT(prod), USB_DRIVER_INFO(info) + +#define USB_DEV_BCD_GTEQ(lo) /* greater than or equal */ \ + .match_flag_dev_lo = 1, .bcdDevice_lo = (lo) + +#define USB_DEV_BCD_LTEQ(hi) /* less than or equal */ \ + .match_flag_dev_hi = 1, .bcdDevice_hi = (hi) + +#define USB_DEV_CLASS(dc) \ + .match_flag_dev_class = 1, .bDeviceClass = (dc) + +#define USB_DEV_SUBCLASS(dsc) \ + .match_flag_dev_subclass = 1, .bDeviceSubClass = (dsc) + +#define USB_DEV_PROTOCOL(dp) \ + .match_flag_dev_protocol = 1, .bDeviceProtocol = (dp) + +#define USB_IFACE_CLASS(ic) \ + .match_flag_int_class = 1, .bInterfaceClass = (ic) + +#define USB_IFACE_SUBCLASS(isc) \ + .match_flag_int_subclass = 1, .bInterfaceSubClass = (isc) + +#define USB_IFACE_PROTOCOL(ip) \ + .match_flag_int_protocol = 1, .bInterfaceProtocol = (ip) + +#define USB_IF_CSI(class,subclass,info) \ + USB_IFACE_CLASS(class), USB_IFACE_SUBCLASS(subclass), USB_DRIVER_INFO(info) + +#define USB_DRIVER_INFO(ptr) \ + .driver_info = ((const void *)(ptr)) + +#define USB_GET_DRIVER_INFO(did) \ + (((const uint8_t *)((did)->driver_info)) - ((const uint8_t *)0)) + +const struct usb2_device_id *usb2_lookup_id_by_info(const struct usb2_device_id *id, uint32_t sizeof_id, const struct usb2_lookup_info *info); +int usb2_lookup_id_by_uaa(const struct usb2_device_id *id, uint32_t sizeof_id, struct usb2_attach_arg *uaa); + +#endif /* _USB2_LOOKUP_H_ */ diff --git a/sys/dev/usb2/core/usb2_mbuf.c b/sys/dev/usb2/core/usb2_mbuf.c new file mode 100644 index 000000000000..1f06f0f0f0dd --- /dev/null +++ b/sys/dev/usb2/core/usb2_mbuf.c @@ -0,0 +1,77 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +/*------------------------------------------------------------------------* + * usb2_alloc_mbufs - allocate mbufs to an usbd interface queue + * + * Returns: + * A pointer that should be passed to "free()" when the buffer(s) + * should be released. + *------------------------------------------------------------------------*/ +void * +usb2_alloc_mbufs(struct malloc_type *type, struct usb2_ifqueue *ifq, + uint32_t block_size, uint16_t nblocks) +{ + struct usb2_mbuf *m_ptr; + uint8_t *data_ptr; + void *free_ptr = NULL; + uint32_t alloc_size; + + /* align data */ + block_size += ((-block_size) & (USB_HOST_ALIGN - 1)); + + if (nblocks && block_size) { + + alloc_size = (block_size + sizeof(struct usb2_mbuf)) * nblocks; + + free_ptr = malloc(alloc_size, type, M_WAITOK | M_ZERO); + + if (free_ptr == NULL) { + goto done; + } + m_ptr = free_ptr; + data_ptr = (void *)(m_ptr + nblocks); + + while (nblocks--) { + + m_ptr->cur_data_ptr = + m_ptr->min_data_ptr = data_ptr; + + m_ptr->cur_data_len = + m_ptr->max_data_len = block_size; + + USB_IF_ENQUEUE(ifq, m_ptr); + + m_ptr++; + data_ptr += block_size; + } + } +done: + return (free_ptr); +} diff --git a/sys/dev/usb2/core/usb2_mbuf.h b/sys/dev/usb2/core/usb2_mbuf.h new file mode 100644 index 000000000000..521263ecab02 --- /dev/null +++ b/sys/dev/usb2/core/usb2_mbuf.h @@ -0,0 +1,100 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_MBUF_H_ +#define _USB2_MBUF_H_ + +/* + * The following structure defines a minimum re-implementation of the + * mbuf system in the kernel. + */ +struct usb2_mbuf { + uint8_t *cur_data_ptr; + uint8_t *min_data_ptr; + struct usb2_mbuf *usb2_nextpkt; + struct usb2_mbuf *usb2_next; + + uint32_t cur_data_len; + uint32_t max_data_len:31; + uint32_t last_packet:1; +}; + +/* + * The following structure defines a minimum re-implementation of the + * ifqueue structure in the kernel. + */ +struct usb2_ifqueue { + struct usb2_mbuf *ifq_head; + struct usb2_mbuf *ifq_tail; + + uint32_t ifq_len; + uint32_t ifq_maxlen; +}; + +#define USB_IF_ENQUEUE(ifq, m) do { \ + (m)->usb2_nextpkt = NULL; \ + if ((ifq)->ifq_tail == NULL) \ + (ifq)->ifq_head = (m); \ + else \ + (ifq)->ifq_tail->usb2_nextpkt = (m); \ + (ifq)->ifq_tail = (m); \ + (ifq)->ifq_len++; \ + } while (0) + +#define USB_IF_DEQUEUE(ifq, m) do { \ + (m) = (ifq)->ifq_head; \ + if (m) { \ + if (((ifq)->ifq_head = (m)->usb2_nextpkt) == NULL) { \ + (ifq)->ifq_tail = NULL; \ + } \ + (m)->usb2_nextpkt = NULL; \ + (ifq)->ifq_len--; \ + } \ + } while (0) + +#define USB_IF_PREPEND(ifq, m) do { \ + (m)->usb2_nextpkt = (ifq)->ifq_head; \ + if ((ifq)->ifq_tail == NULL) { \ + (ifq)->ifq_tail = (m); \ + } \ + (ifq)->ifq_head = (m); \ + (ifq)->ifq_len++; \ + } while (0) + +#define USB_IF_QFULL(ifq) ((ifq)->ifq_len >= (ifq)->ifq_maxlen) +#define USB_IF_QLEN(ifq) ((ifq)->ifq_len) +#define USB_IF_POLL(ifq, m) ((m) = (ifq)->ifq_head) + +#define USB_MBUF_RESET(m) do { \ + (m)->cur_data_ptr = (m)->min_data_ptr; \ + (m)->cur_data_len = (m)->max_data_len; \ + (m)->last_packet = 0; \ + } while (0) + +/* prototypes */ +void *usb2_alloc_mbufs(struct malloc_type *type, struct usb2_ifqueue *ifq, uint32_t block_size, uint16_t nblocks); + +#endif /* _USB2_MBUF_H_ */ diff --git a/sys/dev/usb2/core/usb2_msctest.c b/sys/dev/usb2/core/usb2_msctest.c new file mode 100644 index 000000000000..9db204a1523c --- /dev/null +++ b/sys/dev/usb2/core/usb2_msctest.c @@ -0,0 +1,612 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* + * The following file contains code that will detect USB autoinstall + * disks. + * + * TODO: Potentially we could add code to automatically detect USB + * mass storage quirks for not supported SCSI commands! + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +enum { + ST_COMMAND, + ST_DATA_RD, + ST_DATA_RD_CS, + ST_DATA_WR, + ST_DATA_WR_CS, + ST_STATUS, + ST_MAX, +}; + +enum { + DIR_IN, + DIR_OUT, + DIR_NONE, +}; + +#define BULK_SIZE 64 /* dummy */ + + +/* Command Block Wrapper */ +struct bbb_cbw { + uDWord dCBWSignature; +#define CBWSIGNATURE 0x43425355 + uDWord dCBWTag; + uDWord dCBWDataTransferLength; + uByte bCBWFlags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + uByte bCBWLUN; + uByte bCDBLength; +#define CBWCDBLENGTH 16 + uByte CBWCDB[CBWCDBLENGTH]; +} __packed; + +/* Command Status Wrapper */ +struct bbb_csw { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed; + +struct bbb_transfer { + struct mtx mtx; + struct cv cv; + struct bbb_cbw cbw; + struct bbb_csw csw; + + struct usb2_xfer *xfer[ST_MAX]; + + uint8_t *data_ptr; + + uint32_t data_len; /* bytes */ + uint32_t data_rem; /* bytes */ + uint32_t data_timeout; /* ms */ + uint32_t actlen; /* bytes */ + + uint8_t cmd_len; /* bytes */ + uint8_t dir; + uint8_t lun; + uint8_t state; + uint8_t error; + uint8_t status_try; + + uint8_t buffer[256]; +}; + +static usb2_callback_t bbb_command_callback; +static usb2_callback_t bbb_data_read_callback; +static usb2_callback_t bbb_data_rd_cs_callback; +static usb2_callback_t bbb_data_write_callback; +static usb2_callback_t bbb_data_wr_cs_callback; +static usb2_callback_t bbb_status_callback; + +static const struct usb2_config bbb_config[ST_MAX] = { + + [ST_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = sizeof(struct bbb_cbw), + .mh.flags = {}, + .mh.callback = &bbb_command_callback, + .mh.timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &bbb_data_read_callback, + .mh.timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &bbb_data_rd_cs_callback, + .mh.timeout = 1 * USB_MS_HZ, /* 1 second */ + }, + + [ST_DATA_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &bbb_data_write_callback, + .mh.timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &bbb_data_wr_cs_callback, + .mh.timeout = 1 * USB_MS_HZ, /* 1 second */ + }, + + [ST_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = sizeof(struct bbb_csw), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &bbb_status_callback, + .mh.timeout = 1 * USB_MS_HZ, /* 1 second */ + }, +}; + +static void +bbb_done(struct bbb_transfer *sc, uint8_t error) +{ + struct usb2_xfer *xfer; + + xfer = sc->xfer[sc->state]; + + /* verify the error code */ + + if (error) { + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + error = 1; + break; + default: + error = 2; + break; + } + } + sc->error = error; + sc->state = ST_COMMAND; + sc->status_try = 1; + usb2_cv_signal(&sc->cv); + return; +} + +static void +bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index) +{ + sc->state = xfer_index; + usb2_transfer_start(sc->xfer[xfer_index]); + return; +} + +static void +bbb_data_clear_stall_callback(struct usb2_xfer *xfer, + uint8_t next_xfer, uint8_t stall_xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + + if (usb2_clear_stall_callback(xfer, sc->xfer[stall_xfer])) { + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + bbb_transfer_start(sc, next_xfer); + break; + default: + bbb_done(sc, 1); + break; + } + } + return; +} + +static void +bbb_command_callback(struct usb2_xfer *xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + uint32_t tag; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + bbb_transfer_start + (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD : + (sc->dir == DIR_OUT) ? ST_DATA_WR : + ST_STATUS)); + break; + + case USB_ST_SETUP: + sc->status_try = 0; + tag = UGETDW(sc->cbw.dCBWTag) + 1; + USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); + USETDW(sc->cbw.dCBWTag, tag); + USETDW(sc->cbw.dCBWDataTransferLength, sc->data_len); + sc->cbw.bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT); + sc->cbw.bCBWLUN = sc->lun; + if (sc->cbw.bCDBLength > sizeof(sc->cbw.CBWCDB)) { + sc->cbw.bCDBLength = sizeof(sc->cbw.CBWCDB); + DPRINTFN(0, "Truncating long command!\n"); + } + xfer->frlengths[0] = sizeof(sc->cbw); + + usb2_set_frame_data(xfer, &sc->cbw, 0); + usb2_start_hardware(xfer); + break; + + default: /* Error */ + bbb_done(sc, 1); + break; + } + return; +} + +static void +bbb_data_read_callback(struct usb2_xfer *xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->data_rem -= xfer->actlen; + sc->data_ptr += xfer->actlen; + sc->actlen += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF("max_bulk=%d, data_rem=%d\n", + max_bulk, sc->data_rem); + + if (sc->data_rem == 0) { + bbb_transfer_start(sc, ST_STATUS); + break; + } + if (max_bulk > sc->data_rem) { + max_bulk = sc->data_rem; + } + xfer->timeout = sc->data_timeout; + xfer->frlengths[0] = max_bulk; + + usb2_set_frame_data(xfer, sc->data_ptr, 0); + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + bbb_done(sc, 1); + } else { + bbb_transfer_start(sc, ST_DATA_RD_CS); + } + break; + } + return; +} + +static void +bbb_data_rd_cs_callback(struct usb2_xfer *xfer) +{ + bbb_data_clear_stall_callback(xfer, ST_STATUS, + ST_DATA_RD); + return; +} + +static void +bbb_data_write_callback(struct usb2_xfer *xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->data_rem -= xfer->actlen; + sc->data_ptr += xfer->actlen; + sc->actlen += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF("max_bulk=%d, data_rem=%d\n", + max_bulk, sc->data_rem); + + if (sc->data_rem == 0) { + bbb_transfer_start(sc, ST_STATUS); + return; + } + if (max_bulk > sc->data_rem) { + max_bulk = sc->data_rem; + } + xfer->timeout = sc->data_timeout; + xfer->frlengths[0] = max_bulk; + + usb2_set_frame_data(xfer, sc->data_ptr, 0); + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + bbb_done(sc, 1); + } else { + bbb_transfer_start(sc, ST_DATA_WR_CS); + } + return; + + } +} + +static void +bbb_data_wr_cs_callback(struct usb2_xfer *xfer) +{ + bbb_data_clear_stall_callback(xfer, ST_STATUS, + ST_DATA_WR); + return; +} + +static void +bbb_status_callback(struct usb2_xfer *xfer) +{ + struct bbb_transfer *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* very simple status check */ + + if (xfer->actlen < sizeof(sc->csw)) { + bbb_done(sc, 1);/* error */ + } else if (sc->csw.bCSWStatus == CSWSTATUS_GOOD) { + bbb_done(sc, 0);/* success */ + } else { + bbb_done(sc, 1);/* error */ + } + break; + + case USB_ST_SETUP: + xfer->frlengths[0] = sizeof(sc->csw); + + usb2_set_frame_data(xfer, &sc->csw, 0); + usb2_start_hardware(xfer); + break; + + default: + DPRINTFN(0, "Failed to read CSW: %s, try %d\n", + usb2_errstr(xfer->error), sc->status_try); + + if ((xfer->error == USB_ERR_CANCELLED) || + (sc->status_try)) { + bbb_done(sc, 1); + } else { + sc->status_try = 1; + bbb_transfer_start(sc, ST_DATA_RD_CS); + } + break; + } + return; +} + +/*------------------------------------------------------------------------* + * bbb_command_start - execute a SCSI command synchronously + * + * Return values + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun, + void *data_ptr, uint32_t data_len, uint8_t cmd_len, + uint32_t data_timeout) +{ + sc->lun = lun; + sc->dir = data_len ? dir : DIR_NONE; + sc->data_ptr = data_ptr; + sc->data_len = data_len; + sc->data_rem = data_len; + sc->data_timeout = (data_timeout + USB_MS_HZ); + sc->actlen = 0; + sc->cmd_len = cmd_len; + + usb2_transfer_start(sc->xfer[sc->state]); + + while (usb2_transfer_pending(sc->xfer[sc->state])) { + usb2_cv_wait(&sc->cv, &sc->mtx); + } + return (sc->error); +} + +/*------------------------------------------------------------------------* + * usb2_test_autoinstall + * + * Return values: + * 0: This interface is an auto install disk (CD-ROM) + * Else: Not an auto install disk. + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_test_autoinstall(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_interface *iface; + struct usb2_interface_descriptor *id; + usb2_error_t err; + uint8_t timeout; + uint8_t sid_type; + struct bbb_transfer *sc; + + if (udev == NULL) { + return (USB_ERR_INVAL); + } + iface = usb2_get_iface(udev, iface_index); + if (iface == NULL) { + return (USB_ERR_INVAL); + } + id = iface->idesc; + if (id == NULL) { + return (USB_ERR_INVAL); + } + if (id->bInterfaceClass != UICLASS_MASS) { + return (USB_ERR_INVAL); + } + switch (id->bInterfaceSubClass) { + case UISUBCLASS_SCSI: + case UISUBCLASS_UFI: + break; + default: + return (USB_ERR_INVAL); + } + + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_BBB_OLD: + case UIPROTO_MASS_BBB: + break; + default: + return (USB_ERR_INVAL); + } + + sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO); + if (sc == NULL) { + return (USB_ERR_NOMEM); + } + mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF); + usb2_cv_init(&sc->cv, "WBBB"); + + err = usb2_transfer_setup(udev, + &iface_index, sc->xfer, bbb_config, + ST_MAX, sc, &sc->mtx); + + if (err) { + goto done; + } + mtx_lock(&sc->mtx); + + timeout = 4; /* tries */ + +repeat_inquiry: + + sc->cbw.CBWCDB[0] = 0x12; /* INQUIRY */ + + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 256, 6, USB_MS_HZ); + if (err) { + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 256, 12, USB_MS_HZ); + if (err) { + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 256, 16, USB_MS_HZ); + } + } + if ((sc->actlen != 0) && (err == 0)) { + sid_type = sc->buffer[0] & 0x1F; + if (sid_type == 0x05) { + /* CD-ROM */ + /* XXX could investigate more */ + return (0); + } + } else if (--timeout) { + usb2_pause_mtx(&sc->mtx, USB_MS_HZ); + goto repeat_inquiry; + } + err = USB_ERR_INVAL; + goto done; + +done: + mtx_unlock(&sc->mtx); + usb2_transfer_unsetup(sc->xfer, ST_MAX); + mtx_destroy(&sc->mtx); + usb2_cv_destroy(&sc->cv); + free(sc, M_USB); + return (err); +} + +/* + * Huawei Exxx radio devices have a built in flash disk which is their + * default power up configuration. This allows the device to carry its + * own installation software. + * + * Instead of following the USB spec, and create multiple + * configuration descriptors for this, the devices expects the driver + * to send UF_DEVICE_REMOTE_WAKEUP to endpoint 2 to reset the device, + * so it reprobes, now with the radio exposed. + */ + +usb2_error_t +usb2_test_huawei(struct usb2_device *udev, uint8_t iface_index) +{ + struct usb2_device_request req; + struct usb2_interface *iface; + struct usb2_interface_descriptor *id; + usb2_error_t err; + + if (udev == NULL) { + return (USB_ERR_INVAL); + } + iface = usb2_get_iface(udev, iface_index); + if (iface == NULL) { + return (USB_ERR_INVAL); + } + id = iface->idesc; + if (id == NULL) { + return (USB_ERR_INVAL); + } + if (id->bInterfaceClass != UICLASS_MASS) { + return (USB_ERR_INVAL); + } + /* Bend it like Beckham */ + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, 2); + USETW(req.wLength, 0); + + /* We get error at return, but it works */ + err = usb2_do_request_flags(udev, NULL, &req, NULL, 0, NULL, 1 * USB_MS_HZ); + + return (0); /* success */ +} diff --git a/sys/dev/usb2/core/usb2_msctest.h b/sys/dev/usb2/core/usb2_msctest.h new file mode 100644 index 000000000000..19aac3697bd1 --- /dev/null +++ b/sys/dev/usb2/core/usb2_msctest.h @@ -0,0 +1,33 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_MSCTEST_H_ +#define _USB2_MSCTEST_H_ + +usb2_error_t usb2_test_autoinstall(struct usb2_device *udev, uint8_t iface_index); +usb2_error_t usb2_test_huawei(struct usb2_device *udev, uint8_t iface_index); + +#endif /* _USB2_MSCTEST_H_ */ diff --git a/sys/dev/usb2/core/usb2_parse.c b/sys/dev/usb2/core/usb2_parse.c new file mode 100644 index 000000000000..223b3c2634da --- /dev/null +++ b/sys/dev/usb2/core/usb2_parse.c @@ -0,0 +1,208 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +#include +#include + +/*------------------------------------------------------------------------* + * usb2_desc_foreach + * + * This function is the safe way to iterate across the USB config + * descriptor. It contains several checks against invalid + * descriptors. If the "desc" argument passed to this function is + * "NULL" the first descriptor, if any, will be returned. + * + * Return values: + * NULL: End of descriptors + * Else: Next descriptor after "desc" + *------------------------------------------------------------------------*/ +struct usb2_descriptor * +usb2_desc_foreach(struct usb2_config_descriptor *cd, struct usb2_descriptor *desc) +{ + void *end; + + if (cd == NULL) { + return (NULL); + } + end = USB_ADD_BYTES(cd, UGETW(cd->wTotalLength)); + + if (desc == NULL) { + desc = USB_ADD_BYTES(cd, 0); + } else { + desc = USB_ADD_BYTES(desc, desc->bLength); + } + return (((((void *)desc) >= ((void *)cd)) && + (((void *)desc) < end) && + (USB_ADD_BYTES(desc, desc->bLength) >= ((void *)cd)) && + (USB_ADD_BYTES(desc, desc->bLength) <= end) && + (desc->bLength >= sizeof(*desc))) ? desc : NULL); +} + +/*------------------------------------------------------------------------* + * usb2_find_idesc + * + * This function will return the interface descriptor, if any, that + * has index "iface_index" and alternate index "alt_index". + * + * Return values: + * NULL: End of descriptors + * Else: A valid interface descriptor + *------------------------------------------------------------------------*/ +struct usb2_interface_descriptor * +usb2_find_idesc(struct usb2_config_descriptor *cd, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb2_descriptor *desc = NULL; + struct usb2_interface_descriptor *id; + uint8_t curidx = 0; + uint8_t lastidx = 0; + uint8_t curaidx = 0; + uint8_t first = 1; + + while ((desc = usb2_desc_foreach(cd, desc))) { + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + id = (void *)desc; + + if (first) { + first = 0; + lastidx = id->bInterfaceNumber; + + } else if (id->bInterfaceNumber != lastidx) { + + lastidx = id->bInterfaceNumber; + curidx++; + curaidx = 0; + + } else { + curaidx++; + } + + if ((iface_index == curidx) && (alt_index == curaidx)) { + return (id); + } + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_find_edesc + * + * This function will return the endpoint descriptor for the passed + * interface index, alternate index and endpoint index. + * + * Return values: + * NULL: End of descriptors + * Else: A valid endpoint descriptor + *------------------------------------------------------------------------*/ +struct usb2_endpoint_descriptor * +usb2_find_edesc(struct usb2_config_descriptor *cd, + uint8_t iface_index, uint8_t alt_index, uint8_t ep_index) +{ + struct usb2_descriptor *desc = NULL; + struct usb2_interface_descriptor *d; + uint8_t curidx = 0; + + d = usb2_find_idesc(cd, iface_index, alt_index); + if (d == NULL) + return (NULL); + + if (ep_index >= d->bNumEndpoints) /* quick exit */ + return (NULL); + + desc = ((void *)d); + + while ((desc = usb2_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + if (desc->bDescriptorType == UDESC_ENDPOINT) { + if (curidx == ep_index) { + if (desc->bLength < + sizeof(struct usb2_endpoint_descriptor)) { + /* endpoint index is invalid */ + break; + } + return ((void *)desc); + } + curidx++; + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_get_no_endpoints + * + * This function will count the total number of endpoints available. + *------------------------------------------------------------------------*/ +uint16_t +usb2_get_no_endpoints(struct usb2_config_descriptor *cd) +{ + struct usb2_descriptor *desc = NULL; + uint16_t count = 0; + + while ((desc = usb2_desc_foreach(cd, desc))) { + if (desc->bDescriptorType == UDESC_ENDPOINT) { + count++; + } + } + return (count); +} + +/*------------------------------------------------------------------------* + * usb2_get_no_alts + * + * Return value: + * Number of alternate settings for the given "ifaceno". + * + * NOTE: The returned can be larger than the actual number of + * alternate settings. + *------------------------------------------------------------------------*/ +uint16_t +usb2_get_no_alts(struct usb2_config_descriptor *cd, uint8_t ifaceno) +{ + struct usb2_descriptor *desc = NULL; + struct usb2_interface_descriptor *id; + uint16_t n = 0; + + while ((desc = usb2_desc_foreach(cd, desc))) { + + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + id = (void *)desc; + if (id->bInterfaceNumber == ifaceno) { + n++; + } + } + } + return (n); +} diff --git a/sys/dev/usb2/core/usb2_parse.h b/sys/dev/usb2/core/usb2_parse.h new file mode 100644 index 000000000000..f367f1ad757b --- /dev/null +++ b/sys/dev/usb2/core/usb2_parse.h @@ -0,0 +1,36 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_PARSE_H_ +#define _USB2_PARSE_H_ + +struct usb2_descriptor *usb2_desc_foreach(struct usb2_config_descriptor *cd, struct usb2_descriptor *desc); +struct usb2_interface_descriptor *usb2_find_idesc(struct usb2_config_descriptor *cd, uint8_t iface_index, uint8_t alt_index); +struct usb2_endpoint_descriptor *usb2_find_edesc(struct usb2_config_descriptor *cd, uint8_t iface_index, uint8_t alt_index, uint8_t ep_index); +uint16_t usb2_get_no_endpoints(struct usb2_config_descriptor *cd); +uint16_t usb2_get_no_alts(struct usb2_config_descriptor *cd, uint8_t ifaceno); + +#endif /* _USB2_PARSE_H_ */ diff --git a/sys/dev/usb2/core/usb2_process.c b/sys/dev/usb2/core/usb2_process.c new file mode 100644 index 000000000000..97365722e269 --- /dev/null +++ b/sys/dev/usb2/core/usb2_process.c @@ -0,0 +1,480 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#define USB_DEBUG_VAR usb2_proc_debug + +#include +#include +#include +#include + +#include +#include +#include + +#if (__FreeBSD_version < 700000) +#define thread_lock(td) mtx_lock_spin(&sched_lock) +#define thread_unlock(td) mtx_unlock_spin(&sched_lock) +#endif + +#if (__FreeBSD_version >= 800000) +#define USB_THREAD_CREATE(f, s, p, ...) \ + kproc_create((f), (s), (p), RFHIGHPID, 0, __VA_ARGS__) +#define USB_THREAD_SUSPEND(p) kproc_suspend(p,0) +#define USB_THREAD_EXIT(err) kproc_exit(err) +#else +#define USB_THREAD_CREATE(f, s, p, ...) \ + kthread_create((f), (s), (p), RFHIGHPID, 0, __VA_ARGS__) +#define USB_THREAD_SUSPEND(p) kthread_suspend(p,0) +#define USB_THREAD_EXIT(err) kthread_exit(err) +#endif + +#if USB_DEBUG +static int usb2_proc_debug; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, proc, CTLFLAG_RW, 0, "USB process"); +SYSCTL_INT(_hw_usb2_proc, OID_AUTO, debug, CTLFLAG_RW, &usb2_proc_debug, 0, + "Debug level"); +#endif + +/*------------------------------------------------------------------------* + * usb2_process + * + * This function is the USB process dispatcher. + *------------------------------------------------------------------------*/ +static void +usb2_process(void *arg) +{ + struct usb2_process *up = arg; + struct usb2_proc_msg *pm; + struct thread *td; + + /* adjust priority */ + td = curthread; + thread_lock(td); + sched_prio(td, up->up_prio); + thread_unlock(td); + + mtx_lock(up->up_mtx); + + up->up_curtd = td; + + while (1) { + + if (up->up_gone) { + break; + } + /* + * NOTE to reimplementors: dequeueing a command from the + * "used" queue and executing it must be atomic, with regard + * to the "up_mtx" mutex. That means any attempt to queue a + * command by another thread must be blocked until either: + * + * 1) the command sleeps + * + * 2) the command returns + * + * Here is a practical example that shows how this helps + * solving a problem: + * + * Assume that you want to set the baud rate on a USB serial + * device. During the programming of the device you don't + * want to receive nor transmit any data, because it will be + * garbage most likely anyway. The programming of our USB + * device takes 20 milliseconds and it needs to call + * functions that sleep. + * + * Non-working solution: Before we queue the programming + * command, we stop transmission and reception of data. Then + * we queue a programming command. At the end of the + * programming command we enable transmission and reception + * of data. + * + * Problem: If a second programming command is queued while the + * first one is sleeping, we end up enabling transmission + * and reception of data too early. + * + * Working solution: Before we queue the programming command, + * we stop transmission and reception of data. Then we queue + * a programming command. Then we queue a second command + * that only enables transmission and reception of data. + * + * Why it works: If a second programming command is queued + * while the first one is sleeping, then the queueing of a + * second command to enable the data transfers, will cause + * the previous one, which is still on the queue, to be + * removed from the queue, and re-inserted after the last + * baud rate programming command, which then gives the + * desired result. + */ + pm = TAILQ_FIRST(&up->up_qhead); + + if (pm) { + DPRINTF("Message pm=%p, cb=%p (enter)\n", + pm, pm->pm_callback); + + (pm->pm_callback) (pm); + + if (pm == TAILQ_FIRST(&up->up_qhead)) { + /* nothing changed */ + TAILQ_REMOVE(&up->up_qhead, pm, pm_qentry); + pm->pm_qentry.tqe_prev = NULL; + } + DPRINTF("Message pm=%p (leave)\n", pm); + + continue; + } + /* end if messages - check if anyone is waiting for sync */ + if (up->up_dsleep) { + up->up_dsleep = 0; + usb2_cv_broadcast(&up->up_drain); + } + up->up_msleep = 1; + usb2_cv_wait(&up->up_cv, up->up_mtx); + } + + up->up_ptr = NULL; + usb2_cv_signal(&up->up_cv); + mtx_unlock(up->up_mtx); + + USB_THREAD_EXIT(0); + return; +} + +/*------------------------------------------------------------------------* + * usb2_proc_setup + * + * This function will create a process using the given "prio" that can + * execute callbacks. The mutex pointed to by "p_mtx" will be applied + * before calling the callbacks and released after that the callback + * has returned. The structure pointed to by "up" is assumed to be + * zeroed before this function is called. + * + * Return values: + * 0: success + * Else: failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_proc_setup(struct usb2_process *up, struct mtx *p_mtx, uint8_t prio) +{ + up->up_mtx = p_mtx; + up->up_prio = prio; + + TAILQ_INIT(&up->up_qhead); + + usb2_cv_init(&up->up_cv, "WMSG"); + usb2_cv_init(&up->up_drain, "DMSG"); + + if (USB_THREAD_CREATE(&usb2_process, up, + &up->up_ptr, "USBPROC")) { + DPRINTFN(0, "Unable to create USB process."); + up->up_ptr = NULL; + goto error; + } + return (0); + +error: + usb2_proc_unsetup(up); + return (1); +} + +/*------------------------------------------------------------------------* + * usb2_proc_unsetup + * + * NOTE: If the structure pointed to by "up" is all zero, this + * function does nothing. + * + * NOTE: Messages that are pending on the process queue will not be + * removed nor called. + *------------------------------------------------------------------------*/ +void +usb2_proc_unsetup(struct usb2_process *up) +{ + if (!(up->up_mtx)) { + /* not initialised */ + return; + } + usb2_proc_drain(up); + + usb2_cv_destroy(&up->up_cv); + usb2_cv_destroy(&up->up_drain); + + /* make sure that we do not enter here again */ + up->up_mtx = NULL; + return; +} + +/*------------------------------------------------------------------------* + * usb2_proc_msignal + * + * This function will queue one of the passed USB process messages on + * the USB process queue. The first message that is not already queued + * will get queued. If both messages are already queued the one queued + * last will be removed from the queue and queued in the end. The USB + * process mutex must be locked when calling this function. This + * function exploits the fact that a process can only do one callback + * at a time. The message that was queued is returned. + *------------------------------------------------------------------------*/ +void * +usb2_proc_msignal(struct usb2_process *up, void *_pm0, void *_pm1) +{ + struct usb2_proc_msg *pm0 = _pm0; + struct usb2_proc_msg *pm1 = _pm1; + struct usb2_proc_msg *pm2; + uint32_t d; + uint8_t t; + + mtx_assert(up->up_mtx, MA_OWNED); + + t = 0; + + if (pm0->pm_qentry.tqe_prev) { + t |= 1; + } + if (pm1->pm_qentry.tqe_prev) { + t |= 2; + } + if (t == 0) { + /* + * No entries are queued. Queue "pm0" and use the existing + * message number. + */ + pm2 = pm0; + } else if (t == 1) { + /* Check if we need to increment the message number. */ + if (pm0->pm_num == up->up_msg_num) { + up->up_msg_num++; + } + pm2 = pm1; + } else if (t == 2) { + /* Check if we need to increment the message number. */ + if (pm1->pm_num == up->up_msg_num) { + up->up_msg_num++; + } + pm2 = pm0; + } else if (t == 3) { + /* + * Both entries are queued. Re-queue the entry closest to + * the end. + */ + d = (pm1->pm_num - pm0->pm_num); + + /* Check sign after subtraction */ + if (d & 0x80000000) { + pm2 = pm0; + } else { + pm2 = pm1; + } + + TAILQ_REMOVE(&up->up_qhead, pm2, pm_qentry); + } else { + pm2 = NULL; /* panic - should not happen */ + } + + DPRINTF(" t=%u, num=%u\n", t, up->up_msg_num); + + /* Put message last on queue */ + + pm2->pm_num = up->up_msg_num; + TAILQ_INSERT_TAIL(&up->up_qhead, pm2, pm_qentry); + + /* Check if we need to wakeup the USB process. */ + + if (up->up_msleep) { + up->up_msleep = 0; /* save "cv_signal()" calls */ + usb2_cv_signal(&up->up_cv); + } + return (pm2); +} + +/*------------------------------------------------------------------------* + * usb2_proc_is_gone + * + * Return values: + * 0: USB process is running + * Else: USB process is tearing down + *------------------------------------------------------------------------*/ +uint8_t +usb2_proc_is_gone(struct usb2_process *up) +{ + mtx_assert(up->up_mtx, MA_OWNED); + + return (up->up_gone ? 1 : 0); +} + +/*------------------------------------------------------------------------* + * usb2_proc_mwait + * + * This function will return when the USB process message pointed to + * by "pm" is no longer on a queue. This function must be called + * having "up->up_mtx" locked. + *------------------------------------------------------------------------*/ +void +usb2_proc_mwait(struct usb2_process *up, void *_pm0, void *_pm1) +{ + struct usb2_proc_msg *pm0 = _pm0; + struct usb2_proc_msg *pm1 = _pm1; + + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_curtd == curthread) { + /* Just remove the messages from the queue. */ + if (pm0->pm_qentry.tqe_prev) { + TAILQ_REMOVE(&up->up_qhead, pm0, pm_qentry); + pm0->pm_qentry.tqe_prev = NULL; + } + if (pm1->pm_qentry.tqe_prev) { + TAILQ_REMOVE(&up->up_qhead, pm1, pm_qentry); + pm1->pm_qentry.tqe_prev = NULL; + } + } else + while (pm0->pm_qentry.tqe_prev || + pm1->pm_qentry.tqe_prev) { + /* check if config thread is gone */ + if (up->up_gone) + break; + up->up_dsleep = 1; + usb2_cv_wait(&up->up_drain, up->up_mtx); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_proc_drain + * + * This function will tear down an USB process, waiting for the + * currently executing command to return. + * + * NOTE: If the structure pointed to by "up" is all zero, + * this function does nothing. + *------------------------------------------------------------------------*/ +void +usb2_proc_drain(struct usb2_process *up) +{ + if (!(up->up_mtx)) { + /* not initialised */ + return; + } + if (up->up_mtx != &Giant) { + mtx_assert(up->up_mtx, MA_NOTOWNED); + } + mtx_lock(up->up_mtx); + + /* Set the gone flag */ + + up->up_gone = 1; + + while (up->up_ptr) { + + /* Check if we need to wakeup the USB process */ + + if (up->up_msleep || up->up_csleep) { + up->up_msleep = 0; + up->up_csleep = 0; + usb2_cv_signal(&up->up_cv); + } + /* Check if we are still cold booted */ + + if (cold) { + USB_THREAD_SUSPEND(up->up_ptr); + printf("WARNING: A USB process has been left suspended!\n"); + break; + } + usb2_cv_wait(&up->up_cv, up->up_mtx); + } + /* Check if someone is waiting - should not happen */ + + if (up->up_dsleep) { + up->up_dsleep = 0; + usb2_cv_broadcast(&up->up_drain); + DPRINTF("WARNING: Someone is waiting " + "for USB process drain!\n"); + } + mtx_unlock(up->up_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_proc_cwait + * + * This function will suspend the current process until + * "usb2_proc_signal()" or "usb2_proc_drain()" is called. The + * "timeout" parameter defines the maximum wait time in system + * ticks. If "timeout" is zero that means no timeout. + * + * NOTE: This function can only be called from within an USB process. + * + * Return values: + * USB_PROC_WAIT_TIMEOUT: Timeout + * USB_PROC_WAIT_NORMAL: Success + * Else: USB process is tearing down + *------------------------------------------------------------------------*/ +uint8_t +usb2_proc_cwait(struct usb2_process *up, int timeout) +{ + int error; + + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_gone) { + return (USB_PROC_WAIT_DRAIN); + } + up->up_csleep = 1; + + if (timeout == 0) { + usb2_cv_wait(&up->up_cv, up->up_mtx); + error = 0; + } else { + error = usb2_cv_timedwait(&up->up_cv, up->up_mtx, timeout); + } + + up->up_csleep = 0; + + if (up->up_gone) { + return (USB_PROC_WAIT_DRAIN); + } + if (error == EWOULDBLOCK) { + return (USB_PROC_WAIT_TIMEOUT); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_proc_csignal + * + * This function will wakeup the given USB process. + *------------------------------------------------------------------------*/ +void +usb2_proc_csignal(struct usb2_process *up) +{ + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_csleep) { + up->up_csleep = 0; + usb2_cv_signal(&up->up_cv); + } + return; +} diff --git a/sys/dev/usb2/core/usb2_process.h b/sys/dev/usb2/core/usb2_process.h new file mode 100644 index 000000000000..7019735f3c14 --- /dev/null +++ b/sys/dev/usb2/core/usb2_process.h @@ -0,0 +1,89 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_PROCESS_H_ +#define _USB2_PROCESS_H_ + +#include + +/* defines */ +#define USB_PRI_HIGH PI_NET +#define USB_PRI_MED PI_DISK + +#define USB_PROC_WAIT_TIMEOUT 2 +#define USB_PROC_WAIT_DRAIN 1 +#define USB_PROC_WAIT_NORMAL 0 + +/* structure prototypes */ + +struct usb2_proc_msg; + +/* typedefs */ + +typedef void (usb2_proc_callback_t)(struct usb2_proc_msg *hdr); + +/* + * The following structure defines the USB process message header. + */ +struct usb2_proc_msg { + TAILQ_ENTRY(usb2_proc_msg) pm_qentry; + usb2_proc_callback_t *pm_callback; + uint32_t pm_num; +}; + +/* + * The following structure defines the USB process. + */ +struct usb2_process { + TAILQ_HEAD(, usb2_proc_msg) up_qhead; + struct cv up_cv; + struct cv up_drain; + + struct proc *up_ptr; + struct thread *up_curtd; + struct mtx *up_mtx; + + uint32_t up_msg_num; + + uint8_t up_prio; + uint8_t up_gone; + uint8_t up_msleep; + uint8_t up_csleep; + uint8_t up_dsleep; +}; + +/* prototypes */ + +uint8_t usb2_proc_cwait(struct usb2_process *up, int timeout); +uint8_t usb2_proc_is_gone(struct usb2_process *up); +uint8_t usb2_proc_setup(struct usb2_process *up, struct mtx *p_mtx, uint8_t prio); +void usb2_proc_csignal(struct usb2_process *up); +void usb2_proc_drain(struct usb2_process *up); +void usb2_proc_mwait(struct usb2_process *up, void *pm0, void *pm1); +void usb2_proc_unsetup(struct usb2_process *up); +void *usb2_proc_msignal(struct usb2_process *up, void *pm0, void *pm1); + +#endif /* _USB2_PROCESS_H_ */ diff --git a/sys/dev/usb2/core/usb2_request.c b/sys/dev/usb2/core/usb2_request.c new file mode 100644 index 000000000000..72d9f175e555 --- /dev/null +++ b/sys/dev/usb2/core/usb2_request.c @@ -0,0 +1,1373 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int usb2_pr_poll_delay = USB_PORT_RESET_DELAY; +static int usb2_pr_recovery_delay = USB_PORT_RESET_RECOVERY; +static int usb2_ss_delay = 0; + +SYSCTL_INT(_hw_usb2, OID_AUTO, pr_poll_delay, CTLFLAG_RW, + &usb2_pr_poll_delay, 0, "USB port reset poll delay in ms"); +SYSCTL_INT(_hw_usb2, OID_AUTO, pr_recovery_delay, CTLFLAG_RW, + &usb2_pr_recovery_delay, 0, "USB port reset recovery delay in ms"); +SYSCTL_INT(_hw_usb2, OID_AUTO, ss_delay, CTLFLAG_RW, + &usb2_ss_delay, 0, "USB status stage delay in ms"); +#endif + +/*------------------------------------------------------------------------* + * usb2_do_request_callback + * + * This function is the USB callback for generic USB Host control + * transfers. + *------------------------------------------------------------------------*/ +void +usb2_do_request_callback(struct usb2_xfer *xfer) +{ + ; /* workaround for a bug in "indent" */ + + DPRINTF("st=%u\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + usb2_start_hardware(xfer); + break; + default: + usb2_cv_signal(xfer->udev->default_cv); + break; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_do_clear_stall_callback + * + * This function is the USB callback for generic clear stall requests. + *------------------------------------------------------------------------*/ +void +usb2_do_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct usb2_device_request req; + struct usb2_pipe *pipe; + struct usb2_pipe *pipe_end; + struct usb2_pipe *pipe_first; + uint8_t to = USB_EP_MAX; + + mtx_lock(xfer->usb2_mtx); + + /* round robin pipe clear stall */ + + pipe = xfer->udev->pipe_curr; + pipe_end = xfer->udev->pipes + USB_EP_MAX; + pipe_first = xfer->udev->pipes; + if (pipe == NULL) { + pipe = pipe_first; + } + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (pipe->edesc && + pipe->is_stalled) { + pipe->toggle_next = 0; + pipe->is_stalled = 0; + /* start up the current or next transfer, if any */ + usb2_command_wrapper(&pipe->pipe_q, + pipe->pipe_q.curr); + } + pipe++; + + case USB_ST_SETUP: +tr_setup: + if (pipe == pipe_end) { + pipe = pipe_first; + } + if (pipe->edesc && + pipe->is_stalled) { + + /* setup a clear-stall packet */ + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = pipe->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + /* copy in the transfer */ + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + /* set length */ + xfer->frlengths[0] = sizeof(req); + xfer->nframes = 1; + mtx_unlock(xfer->usb2_mtx); + + usb2_start_hardware(xfer); + + mtx_lock(xfer->usb2_mtx); + break; + } + pipe++; + if (--to) + goto tr_setup; + break; + + default: + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } + + /* store current pipe */ + xfer->udev->pipe_curr = pipe; + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_do_request_flags and usb2_do_request + * + * Description of arguments passed to these functions: + * + * "udev" - this is the "usb2_device" structure pointer on which the + * request should be performed. It is possible to call this function + * in both Host Side mode and Device Side mode. + * + * "mtx" - if this argument is non-NULL the mutex pointed to by it + * will get dropped and picked up during the execution of this + * function, hence this function sometimes needs to sleep. If this + * argument is NULL it has no effect. + * + * "req" - this argument must always be non-NULL and points to an + * 8-byte structure holding the USB request to be done. The USB + * request structure has a bit telling the direction of the USB + * request, if it is a read or a write. + * + * "data" - if the "wLength" part of the structure pointed to by "req" + * is non-zero this argument must point to a valid kernel buffer which + * can hold at least "wLength" bytes. If "wLength" is zero "data" can + * be NULL. + * + * "flags" - here is a list of valid flags: + * + * o USB_SHORT_XFER_OK: allows the data transfer to be shorter than + * specified + * + * o USB_USE_POLLING: forces the transfer to complete from the + * current context by polling the interrupt handler. This flag can be + * used to perform USB transfers after that the kernel has crashed. + * + * o USB_DELAY_STATUS_STAGE: allows the status stage to be performed + * at a later point in time. This is tunable by the "hw.usb.ss_delay" + * sysctl. This flag is mostly useful for debugging. + * + * o USB_USER_DATA_PTR: treat the "data" pointer like a userland + * pointer. + * + * "actlen" - if non-NULL the actual transfer length will be stored in + * the 16-bit unsigned integer pointed to by "actlen". This + * information is mostly useful when the "USB_SHORT_XFER_OK" flag is + * used. + * + * "timeout" - gives the timeout for the control transfer in + * milliseconds. A "timeout" value less than 50 milliseconds is + * treated like a 50 millisecond timeout. A "timeout" value greater + * than 30 seconds is treated like a 30 second timeout. This USB stack + * does not allow control requests without a timeout. + * + * NOTE: This function is thread safe. All calls to + * "usb2_do_request_flags" will be serialised by the use of an + * internal "sx_lock". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_do_request_flags(struct usb2_device *udev, struct mtx *mtx, + struct usb2_device_request *req, void *data, uint32_t flags, + uint16_t *actlen, uint32_t timeout) +{ + struct usb2_xfer *xfer; + const void *desc; + int err = 0; + uint32_t start_ticks; + uint32_t delta_ticks; + uint32_t max_ticks; + uint16_t length; + uint16_t temp; + + if (timeout < 50) { + /* timeout is too small */ + timeout = 50; + } + if (timeout > 30000) { + /* timeout is too big */ + timeout = 30000; + } + length = UGETW(req->wLength); + + DPRINTFN(5, "udev=%p bmRequestType=0x%02x bRequest=0x%02x " + "wValue=0x%02x%02x wIndex=0x%02x%02x wLength=0x%02x%02x\n", + udev, req->bmRequestType, req->bRequest, + req->wValue[1], req->wValue[0], + req->wIndex[1], req->wIndex[0], + req->wLength[1], req->wLength[0]); + + /* + * Set "actlen" to a known value in case the caller does not + * check the return value: + */ + if (actlen) { + *actlen = 0; + } + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + DPRINTF("USB device mode\n"); + (usb2_temp_get_desc_p) (udev, req, &desc, &temp); + if (length > temp) { + if (!(flags & USB_SHORT_XFER_OK)) { + return (USB_ERR_SHORT_XFER); + } + length = temp; + } + if (actlen) { + *actlen = length; + } + if (length > 0) { + if (flags & USB_USER_DATA_PTR) { + if (copyout(desc, data, length)) { + return (USB_ERR_INVAL); + } + } else { + bcopy(desc, data, length); + } + } + return (0); /* success */ + } + if (mtx) { + mtx_unlock(mtx); + if (mtx != &Giant) { + mtx_assert(mtx, MA_NOTOWNED); + } + } + /* + * Grab the default sx-lock so that serialisation + * is achieved when multiple threads are involved: + */ + + sx_xlock(udev->default_sx); + + /* + * Setup a new USB transfer or use the existing one, if any: + */ + usb2_default_transfer_setup(udev); + + xfer = udev->default_xfer[0]; + if (xfer == NULL) { + /* most likely out of memory */ + err = USB_ERR_NOMEM; + goto done; + } + mtx_lock(xfer->priv_mtx); + + if (flags & USB_DELAY_STATUS_STAGE) { + xfer->flags.manual_status = 1; + } else { + xfer->flags.manual_status = 0; + } + + xfer->timeout = timeout; + + start_ticks = ticks; + + max_ticks = USB_MS_TO_TICKS(timeout); + + usb2_copy_in(xfer->frbuffers, 0, req, sizeof(*req)); + + xfer->frlengths[0] = sizeof(*req); + xfer->nframes = 2; + + while (1) { + temp = length; + if (temp > xfer->max_data_length) { + temp = xfer->max_data_length; + } + xfer->frlengths[1] = temp; + + if (temp > 0) { + if (!(req->bmRequestType & UT_READ)) { + if (flags & USB_USER_DATA_PTR) { + mtx_unlock(xfer->priv_mtx); + err = usb2_copy_in_user(xfer->frbuffers + 1, + 0, data, temp); + mtx_lock(xfer->priv_mtx); + if (err) { + err = USB_ERR_INVAL; + break; + } + } else { + usb2_copy_in(xfer->frbuffers + 1, 0, data, temp); + } + } + xfer->nframes = 2; + } else { + if (xfer->frlengths[0] == 0) { + if (xfer->flags.manual_status) { +#if USB_DEBUG + int temp; + + temp = usb2_ss_delay; + if (temp > 5000) { + temp = 5000; + } + if (temp > 0) { + usb2_pause_mtx( + xfer->priv_mtx, temp); + } +#endif + xfer->flags.manual_status = 0; + } else { + break; + } + } + xfer->nframes = 1; + } + + usb2_transfer_start(xfer); + + while (usb2_transfer_pending(xfer)) { + if ((flags & USB_USE_POLLING) || cold) { + usb2_do_poll(udev->default_xfer, USB_DEFAULT_XFER_MAX); + } else { + usb2_cv_wait(xfer->udev->default_cv, xfer->priv_mtx); + } + } + + err = xfer->error; + + if (err) { + break; + } + /* subtract length of SETUP packet, if any */ + + if (xfer->aframes > 0) { + xfer->actlen -= xfer->frlengths[0]; + } else { + xfer->actlen = 0; + } + + /* check for short packet */ + + if (temp > xfer->actlen) { + temp = xfer->actlen; + if (!(flags & USB_SHORT_XFER_OK)) { + err = USB_ERR_SHORT_XFER; + } + length = temp; + } + if (temp > 0) { + if (req->bmRequestType & UT_READ) { + if (flags & USB_USER_DATA_PTR) { + mtx_unlock(xfer->priv_mtx); + err = usb2_copy_out_user(xfer->frbuffers + 1, + 0, data, temp); + mtx_lock(xfer->priv_mtx); + if (err) { + err = USB_ERR_INVAL; + break; + } + } else { + usb2_copy_out(xfer->frbuffers + 1, + 0, data, temp); + } + } + } + /* + * Clear "frlengths[0]" so that we don't send the setup + * packet again: + */ + xfer->frlengths[0] = 0; + + /* update length and data pointer */ + length -= temp; + data = USB_ADD_BYTES(data, temp); + + if (actlen) { + (*actlen) += temp; + } + /* check for timeout */ + + delta_ticks = ticks - start_ticks; + if (delta_ticks > max_ticks) { + if (!err) { + err = USB_ERR_TIMEOUT; + } + } + if (err) { + break; + } + } + + if (err) { + /* + * Make sure that the control endpoint is no longer + * blocked in case of a non-transfer related error: + */ + usb2_transfer_stop(xfer); + } + mtx_unlock(xfer->priv_mtx); + +done: + sx_xunlock(udev->default_sx); + + if (mtx) { + mtx_lock(mtx); + } + return ((usb2_error_t)err); +} + +/*------------------------------------------------------------------------* + * usb2_req_reset_port + * + * This function will instruct an USB HUB to perform a reset sequence + * on the specified port number. + * + * Returns: + * 0: Success. The USB device should now be at address zero. + * Else: Failure. No USB device is present and the USB port should be + * disabled. + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_reset_port(struct usb2_device *udev, struct mtx *mtx, uint8_t port) +{ + struct usb2_port_status ps; + usb2_error_t err; + uint16_t n; + +#if USB_DEBUG + uint16_t pr_poll_delay; + uint16_t pr_recovery_delay; + +#endif + err = usb2_req_set_port_feature(udev, mtx, port, UHF_PORT_RESET); + if (err) { + goto done; + } +#if USB_DEBUG + /* range check input parameters */ + pr_poll_delay = usb2_pr_poll_delay; + if (pr_poll_delay < 1) { + pr_poll_delay = 1; + } else if (pr_poll_delay > 1000) { + pr_poll_delay = 1000; + } + pr_recovery_delay = usb2_pr_recovery_delay; + if (pr_recovery_delay > 1000) { + pr_recovery_delay = 1000; + } +#endif + n = 0; + while (1) { +#if USB_DEBUG + /* wait for the device to recover from reset */ + usb2_pause_mtx(mtx, pr_poll_delay); + n += pr_poll_delay; +#else + /* wait for the device to recover from reset */ + usb2_pause_mtx(mtx, USB_PORT_RESET_DELAY); + n += USB_PORT_RESET_DELAY; +#endif + err = usb2_req_get_port_status(udev, mtx, &ps, port); + if (err) { + goto done; + } + /* if the device disappeared, just give up */ + if (!(UGETW(ps.wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) { + goto done; + } + /* check if reset is complete */ + if (UGETW(ps.wPortChange) & UPS_C_PORT_RESET) { + break; + } + /* check for timeout */ + if (n > 1000) { + n = 0; + break; + } + } + + /* clear port reset first */ + err = usb2_req_clear_port_feature( + udev, mtx, port, UHF_C_PORT_RESET); + if (err) { + goto done; + } + /* check for timeout */ + if (n == 0) { + err = USB_ERR_TIMEOUT; + goto done; + } +#if USB_DEBUG + /* wait for the device to recover from reset */ + usb2_pause_mtx(mtx, pr_recovery_delay); +#else + /* wait for the device to recover from reset */ + usb2_pause_mtx(mtx, USB_PORT_RESET_RECOVERY); +#endif + +done: + DPRINTFN(2, "port %d reset returning error=%s\n", + port, usb2_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_desc + * + * This function can be used to retrieve USB descriptors. It contains + * some additional logic like zeroing of missing descriptor bytes and + * retrying an USB descriptor in case of failure. The "min_len" + * argument specifies the minimum descriptor length. The "max_len" + * argument specifies the maximum descriptor length. If the real + * descriptor length is less than the minimum length the missing + * byte(s) will be zeroed. The length field, first byte, of the USB + * descriptor will get overwritten in case it indicates a length that + * is too big. Also the type field, second byte, of the USB descriptor + * will get forced to the correct type. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_desc(struct usb2_device *udev, struct mtx *mtx, void *desc, + uint16_t min_len, uint16_t max_len, + uint16_t id, uint8_t type, uint8_t index, + uint8_t retries) +{ + struct usb2_device_request req; + uint8_t *buf; + usb2_error_t err; + + DPRINTFN(4, "id=%d, type=%d, index=%d, max_len=%d\n", + id, type, index, max_len); + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, type, index); + USETW(req.wIndex, id); + + while (1) { + + if ((min_len < 2) || (max_len < 2)) { + err = USB_ERR_INVAL; + goto done; + } + USETW(req.wLength, min_len); + + err = usb2_do_request(udev, mtx, &req, desc); + + if (err) { + if (!retries) { + goto done; + } + retries--; + + usb2_pause_mtx(mtx, 200); + + continue; + } + buf = desc; + + if (min_len == max_len) { + + /* enforce correct type and length */ + + if (buf[0] > min_len) { + buf[0] = min_len; + } + buf[1] = type; + + goto done; + } + /* range check */ + + if (max_len > buf[0]) { + max_len = buf[0]; + } + /* zero minimum data */ + + while (min_len > max_len) { + min_len--; + buf[min_len] = 0; + } + + /* set new minimum length */ + + min_len = max_len; + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_string_any + * + * This function will return the string given by "string_index" + * using the first language ID. The maximum length "len" includes + * the terminating zero. The "len" argument should be twice as + * big pluss 2 bytes, compared with the actual maximum string length ! + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_string_any(struct usb2_device *udev, struct mtx *mtx, char *buf, + uint16_t len, uint8_t string_index) +{ + char *s; + uint8_t *temp; + uint16_t i; + uint16_t n; + uint16_t c; + uint8_t swap; + usb2_error_t err; + + if (len == 0) { + /* should not happen */ + return (USB_ERR_NORMAL_COMPLETION); + } + buf[0] = 0; + + if (string_index == 0) { + /* this is the language table */ + return (USB_ERR_INVAL); + } + if (udev->flags.no_strings) { + return (USB_ERR_STALLED); + } + err = usb2_req_get_string_desc + (udev, mtx, buf, len, udev->langid, string_index); + if (err) { + return (err); + } + temp = (uint8_t *)buf; + + if (temp[0] < 2) { + /* string length is too short */ + return (USB_ERR_INVAL); + } + /* reserve one byte for terminating zero */ + len--; + + /* find maximum length */ + s = buf; + n = (temp[0] / 2) - 1; + if (n > len) { + n = len; + } + /* skip descriptor header */ + temp += 2; + + /* reset swap state */ + swap = 3; + + /* convert and filter */ + for (i = 0; (i != n); i++) { + c = UGETW(temp + (2 * i)); + + /* convert from Unicode, handle buggy strings */ + if (((c & 0xff00) == 0) && (swap & 1)) { + /* Little Endian, default */ + *s = c; + swap = 1; + } else if (((c & 0x00ff) == 0) && (swap & 2)) { + /* Big Endian */ + *s = c >> 8; + swap = 2; + } else { + *s = '.'; + } + + /* + * Filter by default - we don't allow greater and less than + * signs because they might confuse the dmesg printouts! + */ + if ((*s == '<') || (*s == '>') || (!isprint(*s))) { + *s = '.'; + } + s++; + } + *s = 0; + return (USB_ERR_NORMAL_COMPLETION); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_string_desc + * + * If you don't know the language ID, consider using + * "usb2_req_get_string_any()". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_string_desc(struct usb2_device *udev, struct mtx *mtx, void *sdesc, + uint16_t max_len, uint16_t lang_id, + uint8_t string_index) +{ + return (usb2_req_get_desc(udev, mtx, sdesc, 2, max_len, lang_id, + UDESC_STRING, string_index, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_config_desc + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_config_desc(struct usb2_device *udev, struct mtx *mtx, + struct usb2_config_descriptor *d, uint8_t conf_index) +{ + usb2_error_t err; + + DPRINTFN(4, "confidx=%d\n", conf_index); + + err = usb2_req_get_desc(udev, mtx, d, sizeof(*d), + sizeof(*d), 0, UDESC_CONFIG, conf_index, 0); + if (err) { + goto done; + } + /* Extra sanity checking */ + if (UGETW(d->wTotalLength) < sizeof(*d)) { + err = USB_ERR_INVAL; + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_config_desc_full + * + * This function gets the complete USB configuration descriptor and + * ensures that "wTotalLength" is correct. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_config_desc_full(struct usb2_device *udev, struct mtx *mtx, + struct usb2_config_descriptor **ppcd, struct malloc_type *mtype, + uint8_t index) +{ + struct usb2_config_descriptor cd; + struct usb2_config_descriptor *cdesc; + uint16_t len; + usb2_error_t err; + + DPRINTFN(4, "index=%d\n", index); + + *ppcd = NULL; + + err = usb2_req_get_config_desc(udev, mtx, &cd, index); + if (err) { + return (err); + } + /* get full descriptor */ + len = UGETW(cd.wTotalLength); + if (len < sizeof(*cdesc)) { + /* corrupt descriptor */ + return (USB_ERR_INVAL); + } + cdesc = malloc(len, mtype, M_WAITOK); + if (cdesc == NULL) { + return (USB_ERR_NOMEM); + } + err = usb2_req_get_desc(udev, mtx, cdesc, len, len, 0, + UDESC_CONFIG, index, 3); + if (err) { + free(cdesc, mtype); + return (err); + } + /* make sure that the device is not fooling us: */ + USETW(cdesc->wTotalLength, len); + + *ppcd = cdesc; + + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_req_get_device_desc + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_device_desc(struct usb2_device *udev, struct mtx *mtx, + struct usb2_device_descriptor *d) +{ + DPRINTFN(4, "\n"); + return (usb2_req_get_desc(udev, mtx, d, sizeof(*d), + sizeof(*d), 0, UDESC_DEVICE, 0, 3)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_alt_interface_no + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_alt_interface_no(struct usb2_device *udev, struct mtx *mtx, + uint8_t *alt_iface_no, uint8_t iface_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + return (usb2_do_request(udev, mtx, &req, alt_iface_no)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_alt_interface_no + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_alt_interface_no(struct usb2_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t alt_no) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + req.bmRequestType = UT_WRITE_INTERFACE; + req.bRequest = UR_SET_INTERFACE; + req.wValue[0] = alt_no; + req.wValue[1] = 0; + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_device_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_device_status(struct usb2_device *udev, struct mtx *mtx, + struct usb2_status *st) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(*st)); + return (usb2_do_request(udev, mtx, &req, st)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_hub_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_hub_descriptor(struct usb2_device *udev, struct mtx *mtx, + struct usb2_hub_descriptor *hd, uint8_t nports) +{ + struct usb2_device_request req; + uint16_t len = (nports + 7 + (8 * 8)) / 8; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_HUB, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + return (usb2_do_request(udev, mtx, &req, hd)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_hub_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_hub_status(struct usb2_device *udev, struct mtx *mtx, + struct usb2_hub_status *st) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(struct usb2_hub_status)); + return (usb2_do_request(udev, mtx, &req, st)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_address + * + * This function is used to set the address for an USB device. After + * port reset the USB device will respond at address zero. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_address(struct usb2_device *udev, struct mtx *mtx, uint16_t addr) +{ + struct usb2_device_request req; + + DPRINTFN(6, "setting device address=%d\n", addr); + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + /* Setting the address should not take more than 1 second ! */ + return (usb2_do_request_flags(udev, mtx, &req, NULL, + USB_DELAY_STATUS_STAGE, NULL, 1000)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_port_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_port_status(struct usb2_device *udev, struct mtx *mtx, + struct usb2_port_status *ps, uint8_t port) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_CLASS_OTHER; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof *ps); + return (usb2_do_request(udev, mtx, &req, ps)); +} + +/*------------------------------------------------------------------------* + * usb2_req_clear_hub_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_clear_hub_feature(struct usb2_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_hub_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_hub_feature(struct usb2_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_clear_port_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_clear_port_feature(struct usb2_device *udev, struct mtx *mtx, + uint8_t port, uint16_t sel) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_port_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_port_feature(struct usb2_device *udev, struct mtx *mtx, + uint8_t port, uint16_t sel) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_protocol + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_protocol(struct usb2_device *udev, struct mtx *mtx, + uint8_t iface_index, uint16_t report) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "iface=%p, report=%d, endpt=%d\n", + iface, report, iface->idesc->bInterfaceNumber); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_PROTOCOL; + USETW(req.wValue, report); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_report + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_report(struct usb2_device *udev, struct mtx *mtx, void *data, uint16_t len, + uint8_t iface_index, uint8_t type, uint8_t id) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "len=%d\n", len); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, type, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, len); + return (usb2_do_request(udev, mtx, &req, data)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_report + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_report(struct usb2_device *udev, struct mtx *mtx, void *data, + uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL) || (id == 0)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "len=%d\n", len); + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_REPORT; + USETW2(req.wValue, type, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, len); + return (usb2_do_request(udev, mtx, &req, data)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_idle + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_idle(struct usb2_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t duration, uint8_t id) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "%d %d\n", duration, id); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_IDLE; + USETW2(req.wValue, duration, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_report_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_report_descriptor(struct usb2_device *udev, struct mtx *mtx, + void *d, uint16_t size, uint8_t iface_index) +{ + struct usb2_interface *iface = usb2_get_iface(udev, iface_index); + struct usb2_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_REPORT, 0); /* report id should be 0 */ + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, size); + return (usb2_do_request(udev, mtx, &req, d)); +} + +/*------------------------------------------------------------------------* + * usb2_req_set_config + * + * This function is used to select the current configuration number in + * both USB device side mode and USB host side mode. When setting the + * configuration the function of the interfaces can change. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_set_config(struct usb2_device *udev, struct mtx *mtx, uint8_t conf) +{ + struct usb2_device_request req; + + DPRINTF("setting config %d\n", conf); + + /* do "set configuration" request */ + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_CONFIG; + req.wValue[0] = conf; + req.wValue[1] = 0; + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usb2_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usb2_req_get_config + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_get_config(struct usb2_device *udev, struct mtx *mtx, uint8_t *pconf) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + return (usb2_do_request(udev, mtx, &req, pconf)); +} + +/*------------------------------------------------------------------------* + * usb2_req_re_enumerate + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_req_re_enumerate(struct usb2_device *udev, struct mtx *mtx) +{ + struct usb2_device_descriptor ddesc; + struct usb2_device *parent_hub; + usb2_error_t err; + uint8_t old_addr; + + old_addr = udev->address; + parent_hub = udev->parent_hub; + if (parent_hub == NULL) { + err = USB_ERR_INVAL; + goto done; + } + err = usb2_req_reset_port(parent_hub, mtx, udev->port_no); + if (err) { + DPRINTFN(0, "addr=%d, port reset failed\n", old_addr); + goto done; + } + /* + * After that the port has been reset our device should be at + * address zero: + */ + udev->address = USB_START_ADDR; + + /* + * Restore device address: + */ + err = usb2_req_set_address(udev, mtx, old_addr); + if (err) { + /* XXX ignore any errors! */ + DPRINTFN(0, "addr=%d, set address failed\n", + old_addr); + err = 0; + } + /* restore device address */ + udev->address = old_addr; + + /* allow device time to set new address */ + usb2_pause_mtx(mtx, USB_SET_ADDRESS_SETTLE); + + /* get the device descriptor */ + err = usb2_req_get_device_desc(udev, mtx, &ddesc); + if (err) { + DPRINTFN(0, "addr=%d, getting device " + "descriptor failed!\n", old_addr); + goto done; + } +done: + /* restore address */ + udev->address = old_addr; + + if (err == 0) { + /* restore configuration */ + err = usb2_req_set_config(udev, mtx, udev->curr_config_no); + /* wait a little bit, just in case */ + usb2_pause_mtx(mtx, 10); + } + return (err); +} diff --git a/sys/dev/usb2/core/usb2_request.h b/sys/dev/usb2/core/usb2_request.h new file mode 100644 index 000000000000..f2b642291135 --- /dev/null +++ b/sys/dev/usb2/core/usb2_request.h @@ -0,0 +1,61 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_REQUEST_H_ +#define _USB2_REQUEST_H_ + +usb2_error_t usb2_do_request_flags(struct usb2_device *udev, struct mtx *mtx, struct usb2_device_request *req, void *data, uint32_t flags, uint16_t *actlen, uint32_t timeout); +usb2_error_t usb2_req_clear_hub_feature(struct usb2_device *udev, struct mtx *mtx, uint16_t sel); +usb2_error_t usb2_req_clear_port_feature(struct usb2_device *udev, struct mtx *mtx, uint8_t port, uint16_t sel); +usb2_error_t usb2_req_get_alt_interface_no(struct usb2_device *udev, struct mtx *mtx, uint8_t *alt_iface_no, uint8_t iface_index); +usb2_error_t usb2_req_get_config(struct usb2_device *udev, struct mtx *mtx, uint8_t *pconf); +usb2_error_t usb2_req_get_config_desc(struct usb2_device *udev, struct mtx *mtx, struct usb2_config_descriptor *d, uint8_t conf_index); +usb2_error_t usb2_req_get_config_desc_full(struct usb2_device *udev, struct mtx *mtx, struct usb2_config_descriptor **ppcd, struct malloc_type *mtype, uint8_t conf_index); +usb2_error_t usb2_req_get_desc(struct usb2_device *udev, struct mtx *mtx, void *desc, uint16_t min_len, uint16_t max_len, uint16_t id, uint8_t type, uint8_t index, uint8_t retries); +usb2_error_t usb2_req_get_device_desc(struct usb2_device *udev, struct mtx *mtx, struct usb2_device_descriptor *d); +usb2_error_t usb2_req_get_device_status(struct usb2_device *udev, struct mtx *mtx, struct usb2_status *st); +usb2_error_t usb2_req_get_hub_descriptor(struct usb2_device *udev, struct mtx *mtx, struct usb2_hub_descriptor *hd, uint8_t nports); +usb2_error_t usb2_req_get_hub_status(struct usb2_device *udev, struct mtx *mtx, struct usb2_hub_status *st); +usb2_error_t usb2_req_get_port_status(struct usb2_device *udev, struct mtx *mtx, struct usb2_port_status *ps, uint8_t port); +usb2_error_t usb2_req_get_report(struct usb2_device *udev, struct mtx *mtx, void *data, uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id); +usb2_error_t usb2_req_get_report_descriptor(struct usb2_device *udev, struct mtx *mtx, void *d, uint16_t size, uint8_t iface_index); +usb2_error_t usb2_req_get_string_any(struct usb2_device *udev, struct mtx *mtx, char *buf, uint16_t len, uint8_t string_index); +usb2_error_t usb2_req_get_string_desc(struct usb2_device *udev, struct mtx *mtx, void *sdesc, uint16_t max_len, uint16_t lang_id, uint8_t string_index); +usb2_error_t usb2_req_reset_port(struct usb2_device *udev, struct mtx *mtx, uint8_t port); +usb2_error_t usb2_req_set_address(struct usb2_device *udev, struct mtx *mtx, uint16_t addr); +usb2_error_t usb2_req_set_alt_interface_no(struct usb2_device *udev, struct mtx *mtx, uint8_t iface_index, uint8_t alt_no); +usb2_error_t usb2_req_set_config(struct usb2_device *udev, struct mtx *mtx, uint8_t conf); +usb2_error_t usb2_req_set_hub_feature(struct usb2_device *udev, struct mtx *mtx, uint16_t sel); +usb2_error_t usb2_req_set_idle(struct usb2_device *udev, struct mtx *mtx, uint8_t iface_index, uint8_t duration, uint8_t id); +usb2_error_t usb2_req_set_port_feature(struct usb2_device *udev, struct mtx *mtx, uint8_t port, uint16_t sel); +usb2_error_t usb2_req_set_protocol(struct usb2_device *udev, struct mtx *mtx, uint8_t iface_index, uint16_t report); +usb2_error_t usb2_req_set_report(struct usb2_device *udev, struct mtx *mtx, void *data, uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id); +usb2_error_t usb2_req_re_enumerate(struct usb2_device *udev, struct mtx *mtx); + +#define usb2_do_request(u,m,r,d) \ + usb2_do_request_flags(u,m,r,d,0,NULL,USB_DEFAULT_TIMEOUT) + +#endif /* _USB2_REQUEST_H_ */ diff --git a/sys/dev/usb2/core/usb2_sw_transfer.c b/sys/dev/usb2/core/usb2_sw_transfer.c new file mode 100644 index 000000000000..bb825028a907 --- /dev/null +++ b/sys/dev/usb2/core/usb2_sw_transfer.c @@ -0,0 +1,166 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include + +/*------------------------------------------------------------------------* + * usb2_sw_transfer - factored out code + * + * This function is basically used for the Virtual Root HUB, and can + * emulate control, bulk and interrupt endpoints. Data is exchanged + * using the "std->ptr" and "std->len" fields, that allows kernel + * virtual memory to be transferred. All state is kept in the + * structure pointed to by the "std" argument passed to this + * function. The "func" argument points to a function that is called + * back in the various states, so that the application using this + * function can get a chance to select the outcome. The "func" + * function is allowed to sleep, exiting all mutexes. If this function + * will sleep the "enter" and "start" methods must be marked + * non-cancelable, hence there is no extra cancelled checking in this + * function. + *------------------------------------------------------------------------*/ +void +usb2_sw_transfer(struct usb2_sw_transfer *std, + usb2_sw_transfer_func_t *func) +{ + struct usb2_xfer *xfer; + uint32_t len; + uint8_t shortpkt = 0; + + xfer = std->xfer; + if (xfer == NULL) { + /* the transfer is gone */ + DPRINTF("xfer gone\n"); + return; + } + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + std->xfer = NULL; + + /* check for control transfer */ + if (xfer->flags_int.control_xfr) { + /* check if we are transferring the SETUP packet */ + if (xfer->flags_int.control_hdr) { + + /* copy out the USB request */ + + if (xfer->frlengths[0] == sizeof(std->req)) { + usb2_copy_out(xfer->frbuffers, 0, + &std->req, sizeof(std->req)); + } else { + std->err = USB_ERR_INVAL; + goto done; + } + + xfer->aframes = 1; + + std->err = 0; + std->state = USB_SW_TR_SETUP; + + (func) (xfer, std); + + if (std->err) { + goto done; + } + } else { + /* skip the first frame in this case */ + xfer->aframes = 1; + } + } + std->err = 0; + std->state = USB_SW_TR_PRE_DATA; + + (func) (xfer, std); + + if (std->err) { + goto done; + } + /* Transfer data. Iterate accross all frames. */ + while (xfer->aframes != xfer->nframes) { + + len = xfer->frlengths[xfer->aframes]; + + if (len > std->len) { + len = std->len; + shortpkt = 1; + } + if (len > 0) { + if ((xfer->endpoint & (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN) { + usb2_copy_in(xfer->frbuffers + xfer->aframes, 0, + std->ptr, len); + } else { + usb2_copy_out(xfer->frbuffers + xfer->aframes, 0, + std->ptr, len); + } + } + std->ptr += len; + std->len -= len; + xfer->frlengths[xfer->aframes] = len; + xfer->aframes++; + + if (shortpkt) { + break; + } + } + + std->err = 0; + std->state = USB_SW_TR_POST_DATA; + + (func) (xfer, std); + + if (std->err) { + goto done; + } + /* check if the control transfer is complete */ + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + std->err = 0; + std->state = USB_SW_TR_STATUS; + + (func) (xfer, std); + + if (std->err) { + goto done; + } + } +done: + DPRINTF("done err=%s\n", usb2_errstr(std->err)); + std->state = USB_SW_TR_PRE_CALLBACK; + (func) (xfer, std); + return; +} diff --git a/sys/dev/usb2/core/usb2_sw_transfer.h b/sys/dev/usb2/core/usb2_sw_transfer.h new file mode 100644 index 000000000000..c90ba3663f77 --- /dev/null +++ b/sys/dev/usb2/core/usb2_sw_transfer.h @@ -0,0 +1,61 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_SW_TRANSFER_H_ +#define _USB2_SW_TRANSFER_H_ + +/* Software transfer function state argument values */ + +enum { + USB_SW_TR_SETUP, + USB_SW_TR_STATUS, + USB_SW_TR_PRE_DATA, + USB_SW_TR_POST_DATA, + USB_SW_TR_PRE_CALLBACK, +}; + +struct usb2_sw_transfer; + +typedef void (usb2_sw_transfer_func_t)(struct usb2_xfer *, struct usb2_sw_transfer *); + +/* + * The following structure is used to keep the state of a standard + * root transfer. + */ +struct usb2_sw_transfer { + struct usb2_device_request req; + struct usb2_xfer *xfer; + uint8_t *ptr; + uint16_t len; + uint8_t state; + usb2_error_t err; +}; + +/* prototypes */ + +void usb2_sw_transfer(struct usb2_sw_transfer *std, usb2_sw_transfer_func_t *func); + +#endif /* _USB2_SW_TRANSFER_H_ */ diff --git a/sys/dev/usb2/core/usb2_transfer.c b/sys/dev/usb2/core/usb2_transfer.c new file mode 100644 index 000000000000..21676243e6eb --- /dev/null +++ b/sys/dev/usb2/core/usb2_transfer.c @@ -0,0 +1,2833 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct usb2_std_packet_size { + struct { + uint16_t min; /* inclusive */ + uint16_t max; /* inclusive */ + } range; + + uint16_t fixed[4]; +}; + +/* + * This table stores the all the allowed packet sizes based on + * endpoint type and USB speed: + */ +static const struct usb2_std_packet_size + usb2_std_packet_size[4][USB_SPEED_MAX] = { + + [UE_INTERRUPT] = { + [USB_SPEED_LOW] = {.range = {0, 8}}, + [USB_SPEED_FULL] = {.range = {0, 64}}, + [USB_SPEED_HIGH] = {.range = {0, 1024}}, + [USB_SPEED_VARIABLE] = {.range = {0, 1024}}, + }, + + [UE_CONTROL] = { + [USB_SPEED_LOW] = {.fixed = {8, 8, 8, 8}}, + [USB_SPEED_FULL] = {.fixed = {8, 16, 32, 64}}, + [USB_SPEED_HIGH] = {.fixed = {64, 64, 64, 64}}, + [USB_SPEED_VARIABLE] = {.fixed = {512, 512, 512, 512}}, + }, + + [UE_BULK] = { + [USB_SPEED_LOW] = {.fixed = {0, 0, 0, 0}}, /* invalid */ + [USB_SPEED_FULL] = {.fixed = {8, 16, 32, 64}}, + [USB_SPEED_HIGH] = {.fixed = {512, 512, 512, 512}}, + [USB_SPEED_VARIABLE] = {.fixed = {512, 512, 1024, 1536}}, + }, + + [UE_ISOCHRONOUS] = { + [USB_SPEED_LOW] = {.fixed = {0, 0, 0, 0}}, /* invalid */ + [USB_SPEED_FULL] = {.range = {0, 1023}}, + [USB_SPEED_HIGH] = {.range = {0, 1024}}, + [USB_SPEED_VARIABLE] = {.range = {0, 3584}}, + }, +}; + +static const struct usb2_config usb2_control_ep_cfg[USB_DEFAULT_XFER_MAX] = { + + /* This transfer is used for generic control endpoint transfers */ + + [0] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control endpoint */ + .direction = UE_DIR_ANY, + .mh.bufsize = 1024, /* bytes */ + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &usb2_do_request_callback, + .md.bufsize = 1024, /* bytes */ + .md.flags = {.proxy_buffer = 1,.short_xfer_ok = 0,}, + .md.callback = &usb2_handle_request_callback, + }, + + /* This transfer is used for generic clear stall only */ + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &usb2_do_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +/* function prototypes */ + +static void usb2_update_max_frame_size(struct usb2_xfer *xfer); +static uint32_t usb2_get_dma_delay(struct usb2_bus *bus); +static void usb2_transfer_unsetup_sub(struct usb2_xfer_root *info, uint8_t needs_delay); +static void usb2_control_transfer_init(struct usb2_xfer *xfer); +static uint8_t usb2_start_hardware_sub(struct usb2_xfer *xfer); +static void usb2_callback_proc(struct usb2_proc_msg *_pm); +static void usb2_callback_ss_done_defer(struct usb2_xfer *xfer); +static void usb2_callback_wrapper(struct usb2_xfer_queue *pq); +static void usb2_dma_delay_done_cb(void *arg); +static void usb2_transfer_start_cb(void *arg); +static uint8_t usb2_callback_wrapper_sub(struct usb2_xfer *xfer); + +/*------------------------------------------------------------------------* + * usb2_update_max_frame_size + * + * This function updates the maximum frame size, hence high speed USB + * can transfer multiple consecutive packets. + *------------------------------------------------------------------------*/ +static void +usb2_update_max_frame_size(struct usb2_xfer *xfer) +{ + /* compute maximum frame size */ + + if (xfer->max_packet_count == 2) { + xfer->max_frame_size = 2 * xfer->max_packet_size; + } else if (xfer->max_packet_count == 3) { + xfer->max_frame_size = 3 * xfer->max_packet_size; + } else { + xfer->max_frame_size = xfer->max_packet_size; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_get_dma_delay + * + * The following function is called when we need to + * synchronize with DMA hardware. + * + * Returns: + * 0: no DMA delay required + * Else: milliseconds of DMA delay + *------------------------------------------------------------------------*/ +static uint32_t +usb2_get_dma_delay(struct usb2_bus *bus) +{ + uint32_t temp = 0; + + if (bus->methods->get_dma_delay) { + (bus->methods->get_dma_delay) (bus, &temp); + /* + * Round up and convert to milliseconds. Note that we use + * 1024 milliseconds per second. to save a division. + */ + temp += 0x3FF; + temp /= 0x400; + } + return (temp); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_setup_sub_malloc + * + * This function will allocate one or more DMA'able memory chunks + * according to "size", "align" and "count" arguments. "ppc" is + * pointed to a linear array of USB page caches afterwards. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb2_transfer_setup_sub_malloc(struct usb2_setup_params *parm, + struct usb2_page_cache **ppc, uint32_t size, uint32_t align, + uint32_t count) +{ + struct usb2_page_cache *pc; + struct usb2_page *pg; + void *buf; + uint32_t n_dma_pc; + uint32_t n_obj; + uint32_t x; + uint32_t y; + uint32_t r; + uint32_t z; + + USB_ASSERT(align > 1, ("Invalid alignment, 0x%08x!\n", + align)); + USB_ASSERT(size > 0, ("Invalid size = 0!\n")); + + if (count == 0) { + return (0); /* nothing to allocate */ + } + /* + * Make sure that the size is aligned properly. + */ + size = -((-size) & (-align)); + + /* + * Try multi-allocation chunks to reduce the number of DMA + * allocations, hence DMA allocations are slow. + */ + if (size >= PAGE_SIZE) { + n_dma_pc = count; + n_obj = 1; + } else { + /* compute number of objects per page */ + n_obj = (PAGE_SIZE / size); + /* + * Compute number of DMA chunks, rounded up + * to nearest one: + */ + n_dma_pc = ((count + n_obj - 1) / n_obj); + } + + if (parm->buf == NULL) { + /* for the future */ + parm->dma_page_ptr += n_dma_pc; + parm->dma_page_cache_ptr += n_dma_pc; + parm->dma_page_ptr += count; + parm->xfer_page_cache_ptr += count; + return (0); + } + for (x = 0; x != n_dma_pc; x++) { + /* need to initialize the page cache */ + parm->dma_page_cache_ptr[x].tag_parent = + &parm->curr_xfer->usb2_root->dma_parent_tag; + } + for (x = 0; x != count; x++) { + /* need to initialize the page cache */ + parm->xfer_page_cache_ptr[x].tag_parent = + &parm->curr_xfer->usb2_root->dma_parent_tag; + } + + if (ppc) { + *ppc = parm->xfer_page_cache_ptr; + } + r = count; /* set remainder count */ + z = n_obj * size; /* set allocation size */ + pc = parm->xfer_page_cache_ptr; + pg = parm->dma_page_ptr; + + for (x = 0; x != n_dma_pc; x++) { + + if (r < n_obj) { + /* compute last remainder */ + z = r * size; + n_obj = r; + } + if (usb2_pc_alloc_mem(parm->dma_page_cache_ptr, + pg, z, align)) { + return (1); /* failure */ + } + /* Set beginning of current buffer */ + buf = parm->dma_page_cache_ptr->buffer; + /* Make room for one DMA page cache and one page */ + parm->dma_page_cache_ptr++; + pg++; + + for (y = 0; (y != n_obj); y++, r--, pc++, pg++) { + + /* Load sub-chunk into DMA */ + if (usb2_pc_dmamap_create(pc, size)) { + return (1); /* failure */ + } + pc->buffer = USB_ADD_BYTES(buf, y * size); + pc->page_start = pg; + + mtx_lock(pc->tag_parent->mtx); + if (usb2_pc_load_mem(pc, size, 1 /* synchronous */ )) { + mtx_unlock(pc->tag_parent->mtx); + return (1); /* failure */ + } + mtx_unlock(pc->tag_parent->mtx); + } + } + + parm->xfer_page_cache_ptr = pc; + parm->dma_page_ptr = pg; + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_setup_sub - transfer setup subroutine + * + * This function must be called from the "xfer_setup" callback of the + * USB Host or Device controller driver when setting up an USB + * transfer. This function will setup correct packet sizes, buffer + * sizes, flags and more, that are stored in the "usb2_xfer" + * structure. + *------------------------------------------------------------------------*/ +void +usb2_transfer_setup_sub(struct usb2_setup_params *parm) +{ + enum { + REQ_SIZE = 8, + MIN_PKT = 8, + }; + struct usb2_xfer *xfer = parm->curr_xfer; + const struct usb2_config_sub *setup_sub = parm->curr_setup_sub; + struct usb2_endpoint_descriptor *edesc; + struct usb2_std_packet_size std_size; + uint32_t n_frlengths; + uint32_t n_frbuffers; + uint32_t x; + uint8_t type; + uint8_t zmps; + + /* + * Sanity check. The following parameters must be initialized before + * calling this function. + */ + if ((parm->hc_max_packet_size == 0) || + (parm->hc_max_packet_count == 0) || + (parm->hc_max_frame_size == 0)) { + parm->err = USB_ERR_INVAL; + goto done; + } + edesc = xfer->pipe->edesc; + + type = (edesc->bmAttributes & UE_XFERTYPE); + + xfer->flags = setup_sub->flags; + xfer->nframes = setup_sub->frames; + xfer->timeout = setup_sub->timeout; + xfer->callback = setup_sub->callback; + xfer->interval = setup_sub->interval; + xfer->endpoint = edesc->bEndpointAddress; + xfer->max_packet_size = UGETW(edesc->wMaxPacketSize); + xfer->max_packet_count = 1; + /* make a shadow copy: */ + xfer->flags_int.usb2_mode = parm->udev->flags.usb2_mode; + + parm->bufsize = setup_sub->bufsize; + + if (parm->speed == USB_SPEED_HIGH) { + xfer->max_packet_count += (xfer->max_packet_size >> 11) & 3; + xfer->max_packet_size &= 0x7FF; + } + /* range check "max_packet_count" */ + + if (xfer->max_packet_count > parm->hc_max_packet_count) { + xfer->max_packet_count = parm->hc_max_packet_count; + } + /* filter "wMaxPacketSize" according to HC capabilities */ + + if ((xfer->max_packet_size > parm->hc_max_packet_size) || + (xfer->max_packet_size == 0)) { + xfer->max_packet_size = parm->hc_max_packet_size; + } + /* filter "wMaxPacketSize" according to standard sizes */ + + std_size = usb2_std_packet_size[type][parm->speed]; + + if (std_size.range.min || std_size.range.max) { + + if (xfer->max_packet_size < std_size.range.min) { + xfer->max_packet_size = std_size.range.min; + } + if (xfer->max_packet_size > std_size.range.max) { + xfer->max_packet_size = std_size.range.max; + } + } else { + + if (xfer->max_packet_size >= std_size.fixed[3]) { + xfer->max_packet_size = std_size.fixed[3]; + } else if (xfer->max_packet_size >= std_size.fixed[2]) { + xfer->max_packet_size = std_size.fixed[2]; + } else if (xfer->max_packet_size >= std_size.fixed[1]) { + xfer->max_packet_size = std_size.fixed[1]; + } else { + /* only one possibility left */ + xfer->max_packet_size = std_size.fixed[0]; + } + } + + /* compute "max_frame_size" */ + + usb2_update_max_frame_size(xfer); + + /* check interrupt interval and transfer pre-delay */ + + if (type == UE_ISOCHRONOUS) { + + uint32_t frame_limit; + + xfer->interval = 0; /* not used, must be zero */ + xfer->flags_int.isochronous_xfr = 1; /* set flag */ + + if (xfer->timeout == 0) { + /* + * set a default timeout in + * case something goes wrong! + */ + xfer->timeout = 1000 / 4; + } + if (parm->speed == USB_SPEED_HIGH) { + frame_limit = USB_MAX_HS_ISOC_FRAMES_PER_XFER; + } else { + frame_limit = USB_MAX_FS_ISOC_FRAMES_PER_XFER; + } + + if (xfer->nframes > frame_limit) { + /* + * this is not going to work + * cross hardware + */ + parm->err = USB_ERR_INVAL; + goto done; + } + if (xfer->nframes == 0) { + /* + * this is not a valid value + */ + parm->err = USB_ERR_ZERO_NFRAMES; + goto done; + } + } else { + + /* + * if a value is specified use that else check the endpoint + * descriptor + */ + if (xfer->interval == 0) { + + if (type == UE_INTERRUPT) { + + xfer->interval = edesc->bInterval; + + if (parm->speed == USB_SPEED_HIGH) { + xfer->interval /= 8; /* 125us -> 1ms */ + } + if (xfer->interval == 0) { + /* + * one millisecond is the smallest + * interval + */ + xfer->interval = 1; + } + } + } + } + + /* + * NOTE: we do not allow "max_packet_size" or "max_frame_size" + * to be equal to zero when setting up USB transfers, hence + * this leads to alot of extra code in the USB kernel. + */ + + if ((xfer->max_frame_size == 0) || + (xfer->max_packet_size == 0)) { + + zmps = 1; + + if ((parm->bufsize <= MIN_PKT) && + (type != UE_CONTROL) && + (type != UE_BULK)) { + + /* workaround */ + xfer->max_packet_size = MIN_PKT; + xfer->max_packet_count = 1; + parm->bufsize = 0; /* automatic setup length */ + usb2_update_max_frame_size(xfer); + + } else { + parm->err = USB_ERR_ZERO_MAXP; + goto done; + } + + } else { + zmps = 0; + } + + /* + * check if we should setup a default + * length: + */ + + if (parm->bufsize == 0) { + + parm->bufsize = xfer->max_frame_size; + + if (type == UE_ISOCHRONOUS) { + parm->bufsize *= xfer->nframes; + } + } + /* + * check if we are about to setup a proxy + * type of buffer: + */ + + if (xfer->flags.proxy_buffer) { + + /* round bufsize up */ + + parm->bufsize += (xfer->max_frame_size - 1); + + if (parm->bufsize < xfer->max_frame_size) { + /* length wrapped around */ + parm->err = USB_ERR_INVAL; + goto done; + } + /* subtract remainder */ + + parm->bufsize -= (parm->bufsize % xfer->max_frame_size); + + /* add length of USB device request structure, if any */ + + if (type == UE_CONTROL) { + parm->bufsize += REQ_SIZE; /* SETUP message */ + } + } + xfer->max_data_length = parm->bufsize; + + /* Setup "n_frlengths" and "n_frbuffers" */ + + if (type == UE_ISOCHRONOUS) { + n_frlengths = xfer->nframes; + n_frbuffers = 1; + } else { + + if (type == UE_CONTROL) { + xfer->flags_int.control_xfr = 1; + if (xfer->nframes == 0) { + if (parm->bufsize <= REQ_SIZE) { + /* + * there will never be any data + * stage + */ + xfer->nframes = 1; + } else { + xfer->nframes = 2; + } + } + } else { + if (xfer->nframes == 0) { + xfer->nframes = 1; + } + } + + n_frlengths = xfer->nframes; + n_frbuffers = xfer->nframes; + } + + /* + * check if we have room for the + * USB device request structure: + */ + + if (type == UE_CONTROL) { + + if (xfer->max_data_length < REQ_SIZE) { + /* length wrapped around or too small bufsize */ + parm->err = USB_ERR_INVAL; + goto done; + } + xfer->max_data_length -= REQ_SIZE; + } + /* setup "frlengths" */ + + xfer->frlengths = parm->xfer_length_ptr; + + parm->xfer_length_ptr += n_frlengths; + + /* setup "frbuffers" */ + + xfer->frbuffers = parm->xfer_page_cache_ptr; + + parm->xfer_page_cache_ptr += n_frbuffers; + + /* + * check if we need to setup + * a local buffer: + */ + + if (!xfer->flags.ext_buffer) { + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + if (parm->buf) { + + xfer->local_buffer = + USB_ADD_BYTES(parm->buf, parm->size[0]); + + usb2_set_frame_offset(xfer, 0, 0); + + if ((type == UE_CONTROL) && (n_frbuffers > 1)) { + usb2_set_frame_offset(xfer, REQ_SIZE, 1); + } + } + parm->size[0] += parm->bufsize; + + /* align data again */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + } + /* + * Compute maximum buffer size + */ + + if (parm->bufsize_max < parm->bufsize) { + parm->bufsize_max = parm->bufsize; + } + if (xfer->flags_int.bdma_enable) { + /* + * Setup "dma_page_ptr". + * + * Proof for formula below: + * + * Assume there are three USB frames having length "a", "b" and + * "c". These USB frames will at maximum need "z" + * "usb2_page" structures. "z" is given by: + * + * z = ((a / USB_PAGE_SIZE) + 2) + ((b / USB_PAGE_SIZE) + 2) + + * ((c / USB_PAGE_SIZE) + 2); + * + * Constraining "a", "b" and "c" like this: + * + * (a + b + c) <= parm->bufsize + * + * We know that: + * + * z <= ((parm->bufsize / USB_PAGE_SIZE) + (3*2)); + * + * Here is the general formula: + */ + xfer->dma_page_ptr = parm->dma_page_ptr; + parm->dma_page_ptr += (2 * n_frbuffers); + parm->dma_page_ptr += (parm->bufsize / USB_PAGE_SIZE); + } + if (zmps) { + /* correct maximum data length */ + xfer->max_data_length = 0; + } + /* subtract USB frame remainder from "hc_max_frame_size" */ + + xfer->max_usb2_frame_size = + (parm->hc_max_frame_size - + (parm->hc_max_frame_size % xfer->max_frame_size)); + + if (xfer->max_usb2_frame_size == 0) { + parm->err = USB_ERR_INVAL; + goto done; + } + /* initialize max frame count */ + + xfer->max_frame_count = xfer->nframes; + + /* initialize frame buffers */ + + if (parm->buf) { + for (x = 0; x != n_frbuffers; x++) { + xfer->frbuffers[x].tag_parent = + &xfer->usb2_root->dma_parent_tag; + + if (xfer->flags_int.bdma_enable && + (parm->bufsize_max > 0)) { + + if (usb2_pc_dmamap_create( + xfer->frbuffers + x, + parm->bufsize_max)) { + parm->err = USB_ERR_NOMEM; + goto done; + } + } + } + } +done: + if (parm->err) { + /* + * Set some dummy values so that we avoid division by zero: + */ + xfer->max_usb2_frame_size = 1; + xfer->max_frame_size = 1; + xfer->max_packet_size = 1; + xfer->max_data_length = 0; + xfer->nframes = 0; + xfer->max_frame_count = 0; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_setup - setup an array of USB transfers + * + * NOTE: You must always call "usb2_transfer_unsetup" after calling + * "usb2_transfer_setup" if success was returned. + * + * The idea is that the USB device driver should pre-allocate all its + * transfers by one call to this function. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb2_error_t +usb2_transfer_setup(struct usb2_device *udev, + const uint8_t *ifaces, struct usb2_xfer **ppxfer, + const struct usb2_config *setup_start, uint16_t n_setup, + void *priv_sc, struct mtx *priv_mtx) +{ + struct usb2_xfer dummy; + struct usb2_setup_params parm; + const struct usb2_config *setup_end = setup_start + n_setup; + const struct usb2_config *setup; + struct usb2_pipe *pipe; + struct usb2_xfer_root *info; + struct usb2_xfer *xfer; + void *buf = NULL; + uint16_t n; + uint16_t refcount; + + parm.err = 0; + refcount = 0; + info = NULL; + + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usb2_transfer_setup can sleep!"); + + /* do some checking first */ + + if (n_setup == 0) { + DPRINTFN(6, "setup array has zero length!\n"); + return (USB_ERR_INVAL); + } + if (ifaces == 0) { + DPRINTFN(6, "ifaces array is NULL!\n"); + return (USB_ERR_INVAL); + } + if (priv_mtx == NULL) { + DPRINTFN(6, "using global lock\n"); + priv_mtx = &Giant; + } + /* sanity checks */ + for (setup = setup_start, n = 0; + setup != setup_end; setup++, n++) { + if ((setup->mh.bufsize == 0xffffffff) || + (setup->md.bufsize == 0xffffffff)) { + parm.err = USB_ERR_BAD_BUFSIZE; + DPRINTF("invalid bufsize\n"); + } + if ((setup->mh.callback == NULL) && + (setup->md.callback == NULL)) { + parm.err = USB_ERR_NO_CALLBACK; + DPRINTF("no callback\n"); + } + ppxfer[n] = NULL; + } + + if (parm.err) { + goto done; + } + bzero(&parm, sizeof(parm)); + + parm.udev = udev; + parm.speed = usb2_get_speed(udev); + parm.hc_max_packet_count = 1; + + if (parm.speed >= USB_SPEED_MAX) { + parm.err = USB_ERR_INVAL; + goto done; + } + /* setup all transfers */ + + while (1) { + + if (buf) { + /* + * Initialize the "usb2_xfer_root" structure, + * which is common for all our USB transfers. + */ + info = USB_ADD_BYTES(buf, 0); + + info->memory_base = buf; + info->memory_size = parm.size[0]; + + info->dma_page_cache_start = USB_ADD_BYTES(buf, parm.size[4]); + info->dma_page_cache_end = USB_ADD_BYTES(buf, parm.size[5]); + info->xfer_page_cache_start = USB_ADD_BYTES(buf, parm.size[5]); + info->xfer_page_cache_end = USB_ADD_BYTES(buf, parm.size[2]); + + usb2_cv_init(&info->cv_drain, "WDRAIN"); + + info->usb2_mtx = &udev->bus->mtx; + info->priv_mtx = priv_mtx; + + usb2_dma_tag_setup(&info->dma_parent_tag, + parm.dma_tag_p, udev->bus->dma_parent_tag[0].tag, + priv_mtx, &usb2_bdma_done_event, info, 32, parm.dma_tag_max); + + info->bus = udev->bus; + + TAILQ_INIT(&info->done_q.head); + info->done_q.command = &usb2_callback_wrapper; + + TAILQ_INIT(&info->dma_q.head); + info->dma_q.command = &usb2_bdma_work_loop; + + info->done_m[0].hdr.pm_callback = &usb2_callback_proc; + info->done_m[0].usb2_root = info; + info->done_m[1].hdr.pm_callback = &usb2_callback_proc; + info->done_m[1].usb2_root = info; + + /* create a callback thread */ + + if (usb2_proc_setup(&info->done_p, + &udev->bus->mtx, USB_PRI_HIGH)) { + parm.err = USB_ERR_NO_INTR_THREAD; + goto done; + } + } + /* reset sizes */ + + parm.size[0] = 0; + parm.buf = buf; + parm.size[0] += sizeof(info[0]); + + for (setup = setup_start, n = 0; + setup != setup_end; setup++, n++) { + + /* select mode specific structure */ + if (udev->flags.usb2_mode == USB_MODE_HOST) { + parm.curr_setup_sub = &setup->mh; + } else { + parm.curr_setup_sub = &setup->md; + } + /* skip USB transfers without callbacks: */ + if (parm.curr_setup_sub->callback == NULL) { + continue; + } + /* see if there is a matching endpoint */ + pipe = usb2_get_pipe(udev, + ifaces[setup->if_index], setup); + + if (!pipe) { + if (parm.curr_setup_sub->flags.no_pipe_ok) { + continue; + } + parm.err = USB_ERR_NO_PIPE; + goto done; + } + /* store current setup pointer */ + parm.curr_setup = setup; + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + if (buf) { + + /* + * Common initialization of the + * "usb2_xfer" structure. + */ + xfer = USB_ADD_BYTES(buf, parm.size[0]); + + ppxfer[n] = xfer; + xfer->udev = udev; + xfer->address = udev->address; + xfer->priv_sc = priv_sc; + xfer->priv_mtx = priv_mtx; + xfer->usb2_mtx = &udev->bus->mtx; + xfer->usb2_root = info; + info->setup_refcount++; + + usb2_callout_init_mtx(&xfer->timeout_handle, xfer->usb2_mtx, + CALLOUT_RETURNUNLOCKED); + } else { + /* + * Setup a dummy xfer, hence we are + * writing to the "usb2_xfer" + * structure pointed to by "xfer" + * before we have allocated any + * memory: + */ + xfer = &dummy; + bzero(&dummy, sizeof(dummy)); + refcount++; + } + + parm.size[0] += sizeof(xfer[0]); + + xfer->pipe = pipe; + + if (buf) { + /* + * Increment the pipe refcount. This + * basically prevents setting a new + * configuration and alternate setting + * when USB transfers are in use on + * the given interface. Search the USB + * code for "pipe->refcount" if you + * want more information. + */ + xfer->pipe->refcount++; + } + parm.methods = xfer->pipe->methods; + parm.curr_xfer = xfer; + + /* + * Call the Host or Device controller transfer setup + * routine: + */ + (udev->bus->methods->xfer_setup) (&parm); + + if (parm.err) { + goto done; + } + } + + if (buf || parm.err) { + goto done; + } + if (refcount == 0) { + /* no transfers - nothing to do ! */ + goto done; + } + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[1] = parm.size[0]; + + /* + * The number of DMA tags required depends on + * the number of endpoints. The current estimate + * for maximum number of DMA tags per endpoint + * is two. + */ + parm.dma_tag_max += 2 * MIN(n_setup, USB_EP_MAX); + + /* + * DMA tags for QH, TD, Data and more. + */ + parm.dma_tag_max += 8; + + parm.dma_tag_p += parm.dma_tag_max; + + parm.size[0] += ((uint8_t *)parm.dma_tag_p) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[3] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.dma_page_ptr) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[4] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.dma_page_cache_ptr) - + ((uint8_t *)0); + + /* store end offset temporarily */ + parm.size[5] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.xfer_page_cache_ptr) - + ((uint8_t *)0); + + /* store end offset temporarily */ + + parm.size[2] = parm.size[0]; + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + parm.size[6] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.xfer_length_ptr) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* allocate zeroed memory */ + buf = malloc(parm.size[0], M_USB, M_WAITOK | M_ZERO); + + if (buf == NULL) { + parm.err = USB_ERR_NOMEM; + DPRINTFN(0, "cannot allocate memory block for " + "configuration (%d bytes)\n", + parm.size[0]); + goto done; + } + parm.dma_tag_p = USB_ADD_BYTES(buf, parm.size[1]); + parm.dma_page_ptr = USB_ADD_BYTES(buf, parm.size[3]); + parm.dma_page_cache_ptr = USB_ADD_BYTES(buf, parm.size[4]); + parm.xfer_page_cache_ptr = USB_ADD_BYTES(buf, parm.size[5]); + parm.xfer_length_ptr = USB_ADD_BYTES(buf, parm.size[6]); + } + +done: + if (buf) { + if (info->setup_refcount == 0) { + /* + * "usb2_transfer_unsetup_sub" will unlock + * "usb2_mtx" before returning ! + */ + mtx_lock(info->usb2_mtx); + + /* something went wrong */ + usb2_transfer_unsetup_sub(info, 0); + } + } + if (parm.err) { + usb2_transfer_unsetup(ppxfer, n_setup); + } + return (parm.err); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_unsetup_sub - factored out code + *------------------------------------------------------------------------*/ +static void +usb2_transfer_unsetup_sub(struct usb2_xfer_root *info, uint8_t needs_delay) +{ + struct usb2_page_cache *pc; + uint32_t temp; + + mtx_assert(info->usb2_mtx, MA_OWNED); + + /* wait for any outstanding DMA operations */ + + if (needs_delay) { + temp = usb2_get_dma_delay(info->bus); + usb2_pause_mtx(info->usb2_mtx, temp); + } + mtx_unlock(info->usb2_mtx); + + /* wait for interrupt thread to exit */ + usb2_proc_unsetup(&info->done_p); + + /* free DMA'able memory, if any */ + pc = info->dma_page_cache_start; + while (pc != info->dma_page_cache_end) { + usb2_pc_free_mem(pc); + pc++; + } + + /* free DMA maps in all "xfer->frbuffers" */ + pc = info->xfer_page_cache_start; + while (pc != info->xfer_page_cache_end) { + usb2_pc_dmamap_destroy(pc); + pc++; + } + + /* free all DMA tags */ + usb2_dma_tag_unsetup(&info->dma_parent_tag); + + usb2_cv_destroy(&info->cv_drain); + + /* + * free the "memory_base" last, hence the "info" structure is + * contained within the "memory_base"! + */ + free(info->memory_base, M_USB); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_unsetup - unsetup/free an array of USB transfers + * + * NOTE: All USB transfers in progress will get called back passing + * the error code "USB_ERR_CANCELLED" before this function + * returns. + *------------------------------------------------------------------------*/ +void +usb2_transfer_unsetup(struct usb2_xfer **pxfer, uint16_t n_setup) +{ + struct usb2_xfer *xfer; + struct usb2_xfer_root *info; + uint8_t needs_delay = 0; + + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usb2_transfer_unsetup can sleep!"); + + while (n_setup--) { + xfer = pxfer[n_setup]; + + if (xfer) { + if (xfer->pipe) { + mtx_lock(xfer->priv_mtx); + mtx_lock(xfer->usb2_mtx); + + /* + * HINT: when you start/stop a transfer, it + * might be a good idea to directly use the + * "pxfer[]" structure: + * + * usb2_transfer_start(sc->pxfer[0]); + * usb2_transfer_stop(sc->pxfer[0]); + * + * That way, if your code has many parts that + * will not stop running under the same + * lock, in other words "priv_mtx", the + * usb2_transfer_start and + * usb2_transfer_stop functions will simply + * return when they detect a NULL pointer + * argument. + * + * To avoid any races we clear the "pxfer[]" + * pointer while holding the private mutex + * of the driver: + */ + pxfer[n_setup] = NULL; + + mtx_unlock(xfer->usb2_mtx); + mtx_unlock(xfer->priv_mtx); + + usb2_transfer_drain(xfer); + + if (xfer->flags_int.bdma_enable) { + needs_delay = 1; + } + /* + * NOTE: default pipe does not have an + * interface, even if pipe->iface_index == 0 + */ + xfer->pipe->refcount--; + + } else { + /* clear the transfer pointer */ + pxfer[n_setup] = NULL; + } + + usb2_callout_drain(&xfer->timeout_handle); + + if (xfer->usb2_root) { + info = xfer->usb2_root; + + mtx_lock(info->usb2_mtx); + + USB_ASSERT(info->setup_refcount != 0, + ("Invalid setup " + "reference count!\n")); + + info->setup_refcount--; + + if (info->setup_refcount == 0) { + usb2_transfer_unsetup_sub(info, + needs_delay); + } else { + mtx_unlock(info->usb2_mtx); + } + } + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_control_transfer_init - factored out code + * + * In USB Device Mode we have to wait for the SETUP packet which + * containst the "struct usb2_device_request" structure, before we can + * transfer any data. In USB Host Mode we already have the SETUP + * packet at the moment the USB transfer is started. This leads us to + * having to setup the USB transfer at two different places in + * time. This function just contains factored out control transfer + * initialisation code, so that we don't duplicate the code. + *------------------------------------------------------------------------*/ +static void +usb2_control_transfer_init(struct usb2_xfer *xfer) +{ + struct usb2_device_request req; + + /* copy out the USB request header */ + + usb2_copy_out(xfer->frbuffers, 0, &req, sizeof(req)); + + /* setup remainder */ + + xfer->flags_int.control_rem = UGETW(req.wLength); + + /* copy direction to endpoint variable */ + + xfer->endpoint &= ~(UE_DIR_IN | UE_DIR_OUT); + xfer->endpoint |= + (req.bmRequestType & UT_READ) ? UE_DIR_IN : UE_DIR_OUT; + + return; +} + +/*------------------------------------------------------------------------* + * usb2_start_hardware_sub + * + * This function handles initialisation of control transfers. Control + * transfers are special in that regard that they can both transmit + * and receive data. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb2_start_hardware_sub(struct usb2_xfer *xfer) +{ + uint32_t len; + + /* Check for control endpoint stall */ + if (xfer->flags.stall_pipe) { + /* no longer active */ + xfer->flags_int.control_act = 0; + } + /* + * Check if there is a control + * transfer in progress: + */ + if (xfer->flags_int.control_act) { + + if (xfer->flags_int.control_hdr) { + + /* clear send header flag */ + + xfer->flags_int.control_hdr = 0; + + /* setup control transfer */ + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + usb2_control_transfer_init(xfer); + } + } + /* get data length */ + + len = xfer->sumlen; + + } else { + + /* the size of the SETUP structure is hardcoded ! */ + + if (xfer->frlengths[0] != sizeof(struct usb2_device_request)) { + DPRINTFN(0, "Wrong framelength %u != %zu\n", + xfer->frlengths[0], sizeof(struct + usb2_device_request)); + goto error; + } + /* check USB mode */ + if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) { + + /* check number of frames */ + if (xfer->nframes != 1) { + /* + * We need to receive the setup + * message first so that we know the + * data direction! + */ + DPRINTF("Misconfigured transfer\n"); + goto error; + } + /* + * Set a dummy "control_rem" value. This + * variable will be overwritten later by a + * call to "usb2_control_transfer_init()" ! + */ + xfer->flags_int.control_rem = 0xFFFF; + } else { + + /* setup "endpoint" and "control_rem" */ + + usb2_control_transfer_init(xfer); + } + + /* set transfer-header flag */ + + xfer->flags_int.control_hdr = 1; + + /* get data length */ + + len = (xfer->sumlen - sizeof(struct usb2_device_request)); + } + + /* check if there is a length mismatch */ + + if (len > xfer->flags_int.control_rem) { + DPRINTFN(0, "Length greater than remaining length!\n"); + goto error; + } + /* check if we are doing a short transfer */ + + if (xfer->flags.force_short_xfer) { + xfer->flags_int.control_rem = 0; + } else { + if ((len != xfer->max_data_length) && + (len != xfer->flags_int.control_rem) && + (xfer->nframes != 1)) { + DPRINTFN(0, "Short control transfer without " + "force_short_xfer set!\n"); + goto error; + } + xfer->flags_int.control_rem -= len; + } + + /* the status part is executed when "control_act" is 0 */ + + if ((xfer->flags_int.control_rem > 0) || + (xfer->flags.manual_status)) { + /* don't execute the STATUS stage yet */ + xfer->flags_int.control_act = 1; + + /* sanity check */ + if ((!xfer->flags_int.control_hdr) && + (xfer->nframes == 1)) { + /* + * This is not a valid operation! + */ + DPRINTFN(0, "Invalid parameter " + "combination\n"); + goto error; + } + } else { + /* time to execute the STATUS stage */ + xfer->flags_int.control_act = 0; + } + return (0); /* success */ + +error: + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_start_hardware - start USB hardware for the given transfer + * + * This function should only be called from the USB callback. + *------------------------------------------------------------------------*/ +void +usb2_start_hardware(struct usb2_xfer *xfer) +{ + uint32_t x; + + DPRINTF("xfer=%p, pipe=%p, nframes=%d, dir=%s\n", + xfer, xfer->pipe, xfer->nframes, USB_GET_DATA_ISREAD(xfer) ? + "read" : "write"); + +#if USB_DEBUG + if (USB_DEBUG_VAR > 0) { + mtx_lock(xfer->usb2_mtx); + + usb2_dump_pipe(xfer->pipe); + + mtx_unlock(xfer->usb2_mtx); + } +#endif + + mtx_assert(xfer->priv_mtx, MA_OWNED); + mtx_assert(xfer->usb2_mtx, MA_NOTOWNED); + + /* Only open the USB transfer once! */ + if (!xfer->flags_int.open) { + xfer->flags_int.open = 1; + + DPRINTF("open\n"); + + mtx_lock(xfer->usb2_mtx); + (xfer->pipe->methods->open) (xfer); + mtx_unlock(xfer->usb2_mtx); + } + /* set "transferring" flag */ + xfer->flags_int.transferring = 1; + + /* + * Check if the transfer is waiting on a queue, most + * frequently the "done_q": + */ + if (xfer->wait_queue) { + mtx_lock(xfer->usb2_mtx); + usb2_transfer_dequeue(xfer); + mtx_unlock(xfer->usb2_mtx); + } + /* clear "did_dma_delay" flag */ + xfer->flags_int.did_dma_delay = 0; + + /* clear "did_close" flag */ + xfer->flags_int.did_close = 0; + + /* clear "bdma_setup" flag */ + xfer->flags_int.bdma_setup = 0; + + /* by default we cannot cancel any USB transfer immediately */ + xfer->flags_int.can_cancel_immed = 0; + + /* clear lengths and frame counts by default */ + xfer->sumlen = 0; + xfer->actlen = 0; + xfer->aframes = 0; + + /* clear any previous errors */ + xfer->error = 0; + + /* sanity check */ + + if (xfer->nframes == 0) { + if (xfer->flags.stall_pipe) { + /* + * Special case - want to stall without transferring + * any data: + */ + DPRINTF("xfer=%p nframes=0: stall " + "or clear stall!\n", xfer); + mtx_lock(xfer->usb2_mtx); + xfer->flags_int.can_cancel_immed = 1; + /* start the transfer */ + usb2_command_wrapper(&xfer->pipe->pipe_q, xfer); + mtx_unlock(xfer->usb2_mtx); + return; + } + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, USB_ERR_INVAL); + mtx_unlock(xfer->usb2_mtx); + return; + } + /* compute total transfer length */ + + for (x = 0; x != xfer->nframes; x++) { + xfer->sumlen += xfer->frlengths[x]; + if (xfer->sumlen < xfer->frlengths[x]) { + /* length wrapped around */ + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, USB_ERR_INVAL); + mtx_unlock(xfer->usb2_mtx); + return; + } + } + + /* clear some internal flags */ + + xfer->flags_int.short_xfer_ok = 0; + xfer->flags_int.short_frames_ok = 0; + + /* check if this is a control transfer */ + + if (xfer->flags_int.control_xfr) { + + if (usb2_start_hardware_sub(xfer)) { + mtx_lock(xfer->usb2_mtx); + usb2_transfer_done(xfer, USB_ERR_STALLED); + mtx_unlock(xfer->usb2_mtx); + return; + } + } + /* + * Setup filtered version of some transfer flags, + * in case of data read direction + */ + if (USB_GET_DATA_ISREAD(xfer)) { + + if (xfer->flags_int.control_xfr) { + + /* + * Control transfers do not support reception + * of multiple short USB frames ! + */ + + if (xfer->flags.short_xfer_ok) { + xfer->flags_int.short_xfer_ok = 1; + } + } else { + + if (xfer->flags.short_frames_ok) { + xfer->flags_int.short_xfer_ok = 1; + xfer->flags_int.short_frames_ok = 1; + } else if (xfer->flags.short_xfer_ok) { + xfer->flags_int.short_xfer_ok = 1; + } + } + } + /* + * Check if BUS-DMA support is enabled and try to load virtual + * buffers into DMA, if any: + */ + if (xfer->flags_int.bdma_enable) { + /* insert the USB transfer last in the BUS-DMA queue */ + usb2_command_wrapper(&xfer->usb2_root->dma_q, xfer); + return; + } + /* + * Enter the USB transfer into the Host Controller or + * Device Controller schedule: + */ + usb2_pipe_enter(xfer); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pipe_enter - factored out code + *------------------------------------------------------------------------*/ +void +usb2_pipe_enter(struct usb2_xfer *xfer) +{ + struct usb2_pipe *pipe; + + mtx_assert(xfer->priv_mtx, MA_OWNED); + + mtx_lock(xfer->usb2_mtx); + + pipe = xfer->pipe; + + DPRINTF("enter\n"); + + /* enter the transfer */ + (pipe->methods->enter) (xfer); + + /* check cancelability */ + if (pipe->methods->enter_is_cancelable) { + xfer->flags_int.can_cancel_immed = 1; + /* check for transfer error */ + if (xfer->error) { + /* some error has happened */ + usb2_transfer_done(xfer, 0); + mtx_unlock(xfer->usb2_mtx); + return; + } + } else { + xfer->flags_int.can_cancel_immed = 0; + } + + /* start the transfer */ + usb2_command_wrapper(&pipe->pipe_q, xfer); + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_start - start an USB transfer + * + * NOTE: Calling this function more than one time will only + * result in a single transfer start, until the USB transfer + * completes. + *------------------------------------------------------------------------*/ +void +usb2_transfer_start(struct usb2_xfer *xfer) +{ + if (xfer == NULL) { + /* transfer is gone */ + return; + } + mtx_assert(xfer->priv_mtx, MA_OWNED); + + /* mark the USB transfer started */ + + if (!xfer->flags_int.started) { + xfer->flags_int.started = 1; + } + /* check if the USB transfer callback is already transferring */ + + if (xfer->flags_int.transferring) { + return; + } + mtx_lock(xfer->usb2_mtx); + /* call the USB transfer callback */ + usb2_callback_ss_done_defer(xfer); + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_stop - stop an USB transfer + * + * NOTE: Calling this function more than one time will only + * result in a single transfer stop. + * NOTE: When this function returns it is not safe to free nor + * reuse any DMA buffers. See "usb2_transfer_drain()". + *------------------------------------------------------------------------*/ +void +usb2_transfer_stop(struct usb2_xfer *xfer) +{ + struct usb2_pipe *pipe; + + if (xfer == NULL) { + /* transfer is gone */ + return; + } + mtx_assert(xfer->priv_mtx, MA_OWNED); + + /* check if the USB transfer was ever opened */ + + if (!xfer->flags_int.open) { + /* nothing to do except clearing the "started" flag */ + xfer->flags_int.started = 0; + return; + } + /* try to stop the current USB transfer */ + + mtx_lock(xfer->usb2_mtx); + xfer->error = USB_ERR_CANCELLED;/* override any previous error */ + /* + * Clear "open" and "started" when both private and USB lock + * is locked so that we don't get a race updating "flags_int" + */ + xfer->flags_int.open = 0; + xfer->flags_int.started = 0; + + /* + * Check if we can cancel the USB transfer immediately. + */ + if (xfer->flags_int.transferring) { + if (xfer->flags_int.can_cancel_immed && + (!xfer->flags_int.did_close)) { + DPRINTF("close\n"); + /* + * The following will lead to an USB_ERR_CANCELLED + * error code being passed to the USB callback. + */ + (xfer->pipe->methods->close) (xfer); + /* only close once */ + xfer->flags_int.did_close = 1; + } else { + /* need to wait for the next done callback */ + } + } else { + DPRINTF("close\n"); + + /* close here and now */ + (xfer->pipe->methods->close) (xfer); + + /* + * Any additional DMA delay is done by + * "usb2_transfer_unsetup()". + */ + + /* + * Special case. Check if we need to restart a blocked + * pipe. + */ + pipe = xfer->pipe; + + /* + * If the current USB transfer is completing we need + * to start the next one: + */ + if (pipe->pipe_q.curr == xfer) { + usb2_command_wrapper(&pipe->pipe_q, NULL); + } + } + + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_pending + * + * This function will check if an USB transfer is pending which is a + * little bit complicated! + * Return values: + * 0: Not pending + * 1: Pending: The USB transfer will receive a callback in the future. + *------------------------------------------------------------------------*/ +uint8_t +usb2_transfer_pending(struct usb2_xfer *xfer) +{ + struct usb2_xfer_root *info; + struct usb2_xfer_queue *pq; + + mtx_assert(xfer->priv_mtx, MA_OWNED); + + if (xfer->flags_int.transferring) { + /* trivial case */ + return (1); + } + mtx_lock(xfer->usb2_mtx); + if (xfer->wait_queue) { + /* we are waiting on a queue somewhere */ + mtx_unlock(xfer->usb2_mtx); + return (1); + } + info = xfer->usb2_root; + pq = &info->done_q; + + if (pq->curr == xfer) { + /* we are currently scheduled for callback */ + mtx_unlock(xfer->usb2_mtx); + return (1); + } + /* we are not pending */ + mtx_unlock(xfer->usb2_mtx); + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_drain + * + * This function will stop the USB transfer and wait for any + * additional BUS-DMA and HW-DMA operations to complete. Buffers that + * are loaded into DMA can safely be freed or reused after that this + * function has returned. + *------------------------------------------------------------------------*/ +void +usb2_transfer_drain(struct usb2_xfer *xfer) +{ + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usb2_transfer_drain can sleep!"); + + if (xfer == NULL) { + /* transfer is gone */ + return; + } + if (xfer->priv_mtx != &Giant) { + mtx_assert(xfer->priv_mtx, MA_NOTOWNED); + } + mtx_lock(xfer->priv_mtx); + + usb2_transfer_stop(xfer); + + while (usb2_transfer_pending(xfer)) { + xfer->flags_int.draining = 1; + /* + * Wait until the current outstanding USB + * transfer is complete ! + */ + usb2_cv_wait(&xfer->usb2_root->cv_drain, xfer->priv_mtx); + } + mtx_unlock(xfer->priv_mtx); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_frame_data + * + * This function sets the pointer of the buffer that should + * loaded directly into DMA for the given USB frame. Passing "ptr" + * equal to NULL while the corresponding "frlength" is greater + * than zero gives undefined results! + *------------------------------------------------------------------------*/ +void +usb2_set_frame_data(struct usb2_xfer *xfer, void *ptr, uint32_t frindex) +{ + /* set virtual address to load and length */ + xfer->frbuffers[frindex].buffer = ptr; + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_frame_offset + * + * This function sets the frame data buffer offset relative to the beginning + * of the USB DMA buffer allocated for this USB transfer. + *------------------------------------------------------------------------*/ +void +usb2_set_frame_offset(struct usb2_xfer *xfer, uint32_t offset, + uint32_t frindex) +{ + USB_ASSERT(!xfer->flags.ext_buffer, ("Cannot offset data frame " + "when the USB buffer is external!\n")); + + /* set virtual address to load */ + xfer->frbuffers[frindex].buffer = + USB_ADD_BYTES(xfer->local_buffer, offset); + return; +} + +/*------------------------------------------------------------------------* + * usb2_callback_proc - factored out code + * + * This function performs USB callbacks. + *------------------------------------------------------------------------*/ +static void +usb2_callback_proc(struct usb2_proc_msg *_pm) +{ + struct usb2_done_msg *pm = (void *)_pm; + struct usb2_xfer_root *info = pm->usb2_root; + + /* Change locking order */ + mtx_unlock(info->usb2_mtx); + + /* + * We exploit the fact that the mutex is the same for all + * callbacks that will be called from this thread: + */ + mtx_lock(info->priv_mtx); + mtx_lock(info->usb2_mtx); + + /* Continue where we lost track */ + usb2_command_wrapper(&info->done_q, + info->done_q.curr); + + mtx_unlock(info->priv_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_callback_ss_done_defer + * + * This function will defer the start, stop and done callback to the + * correct thread. + *------------------------------------------------------------------------*/ +static void +usb2_callback_ss_done_defer(struct usb2_xfer *xfer) +{ + struct usb2_xfer_root *info = xfer->usb2_root; + struct usb2_xfer_queue *pq = &info->done_q; + + if (!mtx_owned(xfer->usb2_mtx)) { + panic("%s: called unlocked!\n", __FUNCTION__); + } + if (pq->curr != xfer) { + usb2_transfer_enqueue(pq, xfer); + } + if (!pq->recurse_1) { + + /* + * We have to postpone the callback due to the fact we + * will have a Lock Order Reversal, LOR, if we try to + * proceed ! + */ + if (usb2_proc_msignal(&info->done_p, + &info->done_m[0], &info->done_m[1])) { + /* ignore */ + } + } else { + /* clear second recurse flag */ + pq->recurse_2 = 0; + } + return; + +} + +/*------------------------------------------------------------------------* + * usb2_callback_wrapper + * + * This is a wrapper for USB callbacks. This wrapper does some + * auto-magic things like figuring out if we can call the callback + * directly from the current context or if we need to wakeup the + * interrupt process. + *------------------------------------------------------------------------*/ +static void +usb2_callback_wrapper(struct usb2_xfer_queue *pq) +{ + struct usb2_xfer *xfer = pq->curr; + struct usb2_xfer_root *info = xfer->usb2_root; + + if (!mtx_owned(xfer->usb2_mtx)) { + panic("%s: called unlocked!\n", __FUNCTION__); + } + if (!mtx_owned(xfer->priv_mtx)) { + /* + * Cases that end up here: + * + * 5) HW interrupt done callback or other source. + */ + DPRINTFN(3, "case 5\n"); + + /* + * We have to postpone the callback due to the fact we + * will have a Lock Order Reversal, LOR, if we try to + * proceed ! + */ + if (usb2_proc_msignal(&info->done_p, + &info->done_m[0], &info->done_m[1])) { + /* ignore */ + } + return; + } + /* + * Cases that end up here: + * + * 1) We are starting a transfer + * 2) We are prematurely calling back a transfer + * 3) We are stopping a transfer + * 4) We are doing an ordinary callback + */ + DPRINTFN(3, "case 1-4\n"); + /* get next USB transfer in the queue */ + info->done_q.curr = NULL; + + mtx_unlock(xfer->usb2_mtx); + mtx_assert(xfer->usb2_mtx, MA_NOTOWNED); + + /* set correct USB state for callback */ + if (!xfer->flags_int.transferring) { + xfer->usb2_state = USB_ST_SETUP; + if (!xfer->flags_int.started) { + /* we got stopped before we even got started */ + mtx_lock(xfer->usb2_mtx); + goto done; + } + } else { + + if (usb2_callback_wrapper_sub(xfer)) { + /* the callback has been deferred */ + mtx_lock(xfer->usb2_mtx); + goto done; + } + xfer->flags_int.transferring = 0; + + if (xfer->error) { + xfer->usb2_state = USB_ST_ERROR; + } else { + /* set transferred state */ + xfer->usb2_state = USB_ST_TRANSFERRED; + + /* sync DMA memory, if any */ + if (xfer->flags_int.bdma_enable && + (!xfer->flags_int.bdma_no_post_sync)) { + usb2_bdma_post_sync(xfer); + } + } + } + + /* call processing routine */ + (xfer->callback) (xfer); + + /* pickup the USB mutex again */ + mtx_lock(xfer->usb2_mtx); + + /* + * Check if we got started after that we got cancelled, but + * before we managed to do the callback. Check if we are + * draining. + */ + if ((!xfer->flags_int.open) && + (xfer->flags_int.started) && + (xfer->usb2_state == USB_ST_ERROR)) { + /* try to loop, but not recursivly */ + usb2_command_wrapper(&info->done_q, xfer); + return; + } else if (xfer->flags_int.draining && + (!xfer->flags_int.transferring)) { + /* "usb2_transfer_drain()" is waiting for end of transfer */ + xfer->flags_int.draining = 0; + usb2_cv_broadcast(&xfer->usb2_root->cv_drain); + } +done: + /* do the next callback, if any */ + usb2_command_wrapper(&info->done_q, + info->done_q.curr); + return; +} + +/*------------------------------------------------------------------------* + * usb2_dma_delay_done_cb + * + * This function is called when the DMA delay has been exectuded, and + * will make sure that the callback is called to complete the USB + * transfer. This code path is ususally only used when there is an USB + * error like USB_ERR_CANCELLED. + *------------------------------------------------------------------------*/ +static void +usb2_dma_delay_done_cb(void *arg) +{ + struct usb2_xfer *xfer = arg; + + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + DPRINTFN(3, "Completed %p\n", xfer); + + /* queue callback for execution, again */ + usb2_transfer_done(xfer, 0); + + mtx_unlock(xfer->usb2_mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_dequeue + * + * - This function is used to remove an USB transfer from a USB + * transfer queue. + * + * - This function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usb2_transfer_dequeue(struct usb2_xfer *xfer) +{ + struct usb2_xfer_queue *pq; + + pq = xfer->wait_queue; + if (pq) { + TAILQ_REMOVE(&pq->head, xfer, wait_entry); + xfer->wait_queue = NULL; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_enqueue + * + * - This function is used to insert an USB transfer into a USB * + * transfer queue. + * + * - This function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usb2_transfer_enqueue(struct usb2_xfer_queue *pq, struct usb2_xfer *xfer) +{ + /* + * Insert the USB transfer into the queue, if it is not + * already on a USB transfer queue: + */ + if (xfer->wait_queue == NULL) { + xfer->wait_queue = pq; + TAILQ_INSERT_TAIL(&pq->head, xfer, wait_entry); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_done + * + * - This function is used to remove an USB transfer from the busdma, + * pipe or interrupt queue. + * + * - This function is used to queue the USB transfer on the done + * queue. + * + * - This function is used to stop any USB transfer timeouts. + *------------------------------------------------------------------------*/ +void +usb2_transfer_done(struct usb2_xfer *xfer, usb2_error_t error) +{ + struct usb2_xfer_queue *pq; + + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + DPRINTF("err=%s\n", usb2_errstr(error)); + + /* + * If we are not transferring then just return. + * This can happen during transfer cancel. + */ + if (!xfer->flags_int.transferring) { + DPRINTF("not transferring\n"); + return; + } + /* only set transfer error if not already set */ + if (!xfer->error) { + xfer->error = error; + } + /* stop any callouts */ + usb2_callout_stop(&xfer->timeout_handle); + + /* + * If we are waiting on a queue, just remove the USB transfer + * from the queue, if any. We should have the required locks + * locked to do the remove when this function is called. + */ + usb2_transfer_dequeue(xfer); + + if (mtx_owned(xfer->priv_mtx)) { + /* + * If the private USB lock is not locked, then we assume + * that the BUS-DMA load stage has been passed: + */ + pq = &xfer->usb2_root->dma_q; + + if (pq->curr == xfer) { + /* start the next BUS-DMA load, if any */ + usb2_command_wrapper(pq, NULL); + } + } + /* keep some statistics */ + if (xfer->error) { + xfer->udev->bus->stats_err.uds_requests + [xfer->pipe->edesc->bmAttributes & UE_XFERTYPE]++; + } else { + xfer->udev->bus->stats_ok.uds_requests + [xfer->pipe->edesc->bmAttributes & UE_XFERTYPE]++; + } + + /* call the USB transfer callback */ + usb2_callback_ss_done_defer(xfer); + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_start_cb + * + * This function is called to start the USB transfer when + * "xfer->interval" is greater than zero, and and the endpoint type is + * BULK or CONTROL. + *------------------------------------------------------------------------*/ +static void +usb2_transfer_start_cb(void *arg) +{ + struct usb2_xfer *xfer = arg; + struct usb2_pipe *pipe = xfer->pipe; + + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + DPRINTF("start\n"); + + /* start the transfer */ + (pipe->methods->start) (xfer); + + /* check cancelability */ + if (pipe->methods->start_is_cancelable) { + xfer->flags_int.can_cancel_immed = 1; + if (xfer->error) { + /* some error has happened */ + usb2_transfer_done(xfer, 0); + } + } else { + xfer->flags_int.can_cancel_immed = 0; + } + mtx_unlock(xfer->usb2_mtx); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_set_stall + * + * This function is used to set the stall flag outside the + * callback. This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_transfer_set_stall(struct usb2_xfer *xfer) +{ + if (xfer == NULL) { + /* tearing down */ + return; + } + mtx_assert(xfer->priv_mtx, MA_OWNED); + + /* avoid any races by locking the USB mutex */ + mtx_lock(xfer->usb2_mtx); + + xfer->flags.stall_pipe = 1; + + mtx_unlock(xfer->usb2_mtx); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_clear_stall + * + * This function is used to clear the stall flag outside the + * callback. This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb2_transfer_clear_stall(struct usb2_xfer *xfer) +{ + if (xfer == NULL) { + /* tearing down */ + return; + } + mtx_assert(xfer->priv_mtx, MA_OWNED); + + /* avoid any races by locking the USB mutex */ + mtx_lock(xfer->usb2_mtx); + + xfer->flags.stall_pipe = 0; + + mtx_unlock(xfer->usb2_mtx); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_pipe_start + * + * This function is used to add an USB transfer to the pipe transfer list. + *------------------------------------------------------------------------*/ +void +usb2_pipe_start(struct usb2_xfer_queue *pq) +{ + struct usb2_pipe *pipe; + struct usb2_xfer *xfer; + uint8_t type; + + xfer = pq->curr; + pipe = xfer->pipe; + + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + /* + * If the pipe is already stalled we do nothing ! + */ + if (pipe->is_stalled) { + return; + } + /* + * Check if we are supposed to stall the pipe: + */ + if (xfer->flags.stall_pipe) { + /* clear stall command */ + xfer->flags.stall_pipe = 0; + + /* + * Only stall BULK and INTERRUPT endpoints. + */ + type = (pipe->edesc->bmAttributes & UE_XFERTYPE); + if ((type == UE_BULK) || + (type == UE_INTERRUPT)) { + struct usb2_device *udev; + struct usb2_xfer_root *info; + + udev = xfer->udev; + pipe->is_stalled = 1; + + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + (udev->bus->methods->set_stall) ( + udev, NULL, pipe); + } else if (udev->default_xfer[1]) { + info = udev->default_xfer[1]->usb2_root; + if (usb2_proc_msignal(&info->done_p, + &udev->cs_msg[0], &udev->cs_msg[1])) { + /* ignore */ + } + } else { + /* should not happen */ + DPRINTFN(0, "No stall handler!\n"); + } + /* + * We get started again when the stall is cleared! + */ + return; + } + } + /* Set or clear stall complete - special case */ + if (xfer->nframes == 0) { + /* we are complete */ + xfer->aframes = 0; + usb2_transfer_done(xfer, 0); + return; + } + /* + * Handled cases: + * + * 1) Start the first transfer queued. + * + * 2) Re-start the current USB transfer. + */ + /* + * Check if there should be any + * pre transfer start delay: + */ + if (xfer->interval > 0) { + type = (pipe->edesc->bmAttributes & UE_XFERTYPE); + if ((type == UE_BULK) || + (type == UE_CONTROL)) { + usb2_transfer_timeout_ms(xfer, + &usb2_transfer_start_cb, + xfer->interval); + return; + } + } + DPRINTF("start\n"); + + /* start USB transfer */ + (pipe->methods->start) (xfer); + + /* check cancelability */ + if (pipe->methods->start_is_cancelable) { + xfer->flags_int.can_cancel_immed = 1; + if (xfer->error) { + /* some error has happened */ + usb2_transfer_done(xfer, 0); + } + } else { + xfer->flags_int.can_cancel_immed = 0; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_transfer_timeout_ms + * + * This function is used to setup a timeout on the given USB + * transfer. If the timeout has been deferred the callback given by + * "cb" will get called after "ms" milliseconds. + *------------------------------------------------------------------------*/ +void +usb2_transfer_timeout_ms(struct usb2_xfer *xfer, + void (*cb) (void *arg), uint32_t ms) +{ + mtx_assert(xfer->usb2_mtx, MA_OWNED); + + /* defer delay */ + usb2_callout_reset(&xfer->timeout_handle, + USB_MS_TO_TICKS(ms), cb, xfer); + return; +} + +/*------------------------------------------------------------------------* + * usb2_callback_wrapper_sub + * + * - This function will update variables in an USB transfer after + * that the USB transfer is complete. + * + * - This function is used to start the next USB transfer on the + * pipe transfer queue, if any. + * + * NOTE: In some special cases the USB transfer will not be removed from + * the pipe queue, but remain first. To enforce USB transfer removal call + * this function passing the error code "USB_ERR_CANCELLED". + * + * Return values: + * 0: Success. + * Else: The callback has been deferred. + *------------------------------------------------------------------------*/ +static uint8_t +usb2_callback_wrapper_sub(struct usb2_xfer *xfer) +{ + struct usb2_pipe *pipe; + uint32_t x; + + if ((!xfer->flags_int.open) && + (!xfer->flags_int.did_close)) { + DPRINTF("close\n"); + mtx_lock(xfer->usb2_mtx); + (xfer->pipe->methods->close) (xfer); + mtx_unlock(xfer->usb2_mtx); + /* only close once */ + xfer->flags_int.did_close = 1; + return (1); /* wait for new callback */ + } + /* + * If we have a non-hardware induced error we + * need to do the DMA delay! + */ + if (((xfer->error == USB_ERR_CANCELLED) || + (xfer->error == USB_ERR_TIMEOUT)) && + (!xfer->flags_int.did_dma_delay)) { + + uint32_t temp; + + /* only delay once */ + xfer->flags_int.did_dma_delay = 1; + + /* we can not cancel this delay */ + xfer->flags_int.can_cancel_immed = 0; + + temp = usb2_get_dma_delay(xfer->udev->bus); + + DPRINTFN(3, "DMA delay, %u ms, " + "on %p\n", temp, xfer); + + if (temp != 0) { + mtx_lock(xfer->usb2_mtx); + usb2_transfer_timeout_ms(xfer, + &usb2_dma_delay_done_cb, temp); + mtx_unlock(xfer->usb2_mtx); + return (1); /* wait for new callback */ + } + } + /* check actual number of frames */ + if (xfer->aframes > xfer->nframes) { + if (xfer->error == 0) { + panic("%s: actual number of frames, %d, is " + "greater than initial number of frames, %d!\n", + __FUNCTION__, xfer->aframes, xfer->nframes); + } else { + /* just set some valid value */ + xfer->aframes = xfer->nframes; + } + } + /* compute actual length */ + xfer->actlen = 0; + + for (x = 0; x != xfer->aframes; x++) { + xfer->actlen += xfer->frlengths[x]; + } + + /* + * Frames that were not transferred get zero actual length in + * case the USB device driver does not check the actual number + * of frames transferred, "xfer->aframes": + */ + for (; x < xfer->nframes; x++) { + xfer->frlengths[x] = 0; + } + + /* check actual length */ + if (xfer->actlen > xfer->sumlen) { + if (xfer->error == 0) { + panic("%s: actual length, %d, is greater than " + "initial length, %d!\n", + __FUNCTION__, xfer->actlen, xfer->sumlen); + } else { + /* just set some valid value */ + xfer->actlen = xfer->sumlen; + } + } + DPRINTFN(6, "xfer=%p pipe=%p sts=%d alen=%d, slen=%d, afrm=%d, nfrm=%d\n", + xfer, xfer->pipe, xfer->error, xfer->actlen, xfer->sumlen, + xfer->aframes, xfer->nframes); + + if (xfer->error) { + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + + /* check if we should block the execution queue */ + if ((xfer->error != USB_ERR_CANCELLED) && + (xfer->flags.pipe_bof)) { + DPRINTFN(2, "xfer=%p: Block On Failure " + "on pipe=%p\n", xfer, xfer->pipe); + goto done; + } + } else { + /* check for short transfers */ + if (xfer->actlen < xfer->sumlen) { + + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + + if (!xfer->flags_int.short_xfer_ok) { + xfer->error = USB_ERR_SHORT_XFER; + if (xfer->flags.pipe_bof) { + DPRINTFN(2, "xfer=%p: Block On Failure on " + "Short Transfer on pipe %p.\n", + xfer, xfer->pipe); + goto done; + } + } + } else { + /* + * Check if we are in the middle of a + * control transfer: + */ + if (xfer->flags_int.control_act) { + DPRINTFN(5, "xfer=%p: Control transfer " + "active on pipe=%p\n", xfer, xfer->pipe); + goto done; + } + } + } + + pipe = xfer->pipe; + + /* + * If the current USB transfer is completing we need to start the + * next one: + */ + mtx_lock(xfer->usb2_mtx); + if (pipe->pipe_q.curr == xfer) { + usb2_command_wrapper(&pipe->pipe_q, NULL); + + if (pipe->pipe_q.curr || TAILQ_FIRST(&pipe->pipe_q.head)) { + /* there is another USB transfer waiting */ + } else { + /* this is the last USB transfer */ + /* clear isochronous sync flag */ + xfer->pipe->is_synced = 0; + } + } + mtx_unlock(xfer->usb2_mtx); +done: + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_command_wrapper + * + * This function is used to execute commands non-recursivly on an USB + * transfer. + *------------------------------------------------------------------------*/ +void +usb2_command_wrapper(struct usb2_xfer_queue *pq, struct usb2_xfer *xfer) +{ + if (xfer) { + /* + * If the transfer is not already processing, + * queue it! + */ + if (pq->curr != xfer) { + usb2_transfer_enqueue(pq, xfer); + if (pq->curr != NULL) { + /* something is already processing */ + DPRINTFN(6, "busy %p\n", pq->curr); + return; + } + } + } else { + /* Get next element in queue */ + pq->curr = NULL; + } + + if (!pq->recurse_1) { + + do { + + /* set both recurse flags */ + pq->recurse_1 = 1; + pq->recurse_2 = 1; + + if (pq->curr == NULL) { + xfer = TAILQ_FIRST(&pq->head); + if (xfer) { + TAILQ_REMOVE(&pq->head, xfer, + wait_entry); + xfer->wait_queue = NULL; + pq->curr = xfer; + } else { + break; + } + } + DPRINTFN(6, "cb %p (enter)\n", pq->curr); + (pq->command) (pq); + DPRINTFN(6, "cb %p (leave)\n", pq->curr); + + } while (!pq->recurse_2); + + /* clear first recurse flag */ + pq->recurse_1 = 0; + + } else { + /* clear second recurse flag */ + pq->recurse_2 = 0; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_default_transfer_setup + * + * This function is used to setup the default USB control endpoint + * transfer. + *------------------------------------------------------------------------*/ +void +usb2_default_transfer_setup(struct usb2_device *udev) +{ + struct usb2_xfer *xfer; + uint8_t no_resetup; + uint8_t iface_index; + +repeat: + + xfer = udev->default_xfer[0]; + if (xfer) { + mtx_lock(xfer->priv_mtx); + no_resetup = + ((xfer->address == udev->address) && + (udev->default_ep_desc.wMaxPacketSize[0] == + udev->ddesc.bMaxPacketSize)); + if (udev->flags.usb2_mode == USB_MODE_DEVICE) { + if (no_resetup) { + /* + * NOTE: checking "xfer->address" and + * starting the USB transfer must be + * atomic! + */ + usb2_transfer_start(xfer); + } + } + mtx_unlock(xfer->priv_mtx); + } else { + no_resetup = 0; + } + + if (no_resetup) { + /* + * All parameters are exactly the same like before. + * Just return. + */ + return; + } + /* + * Update wMaxPacketSize for the default control endpoint: + */ + udev->default_ep_desc.wMaxPacketSize[0] = + udev->ddesc.bMaxPacketSize; + + /* + * Unsetup any existing USB transfer: + */ + usb2_transfer_unsetup(udev->default_xfer, USB_DEFAULT_XFER_MAX); + + /* + * Try to setup a new USB transfer for the + * default control endpoint: + */ + iface_index = 0; + if (usb2_transfer_setup(udev, &iface_index, + udev->default_xfer, usb2_control_ep_cfg, USB_DEFAULT_XFER_MAX, NULL, + udev->default_mtx)) { + DPRINTFN(0, "could not setup default " + "USB transfer!\n"); + } else { + goto repeat; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_clear_data_toggle - factored out code + * + * NOTE: the intention of this function is not to reset the hardware + * data toggle. + *------------------------------------------------------------------------*/ +void +usb2_clear_data_toggle(struct usb2_device *udev, struct usb2_pipe *pipe) +{ + DPRINTFN(5, "udev=%p pipe=%p\n", udev, pipe); + + mtx_lock(&udev->bus->mtx); + pipe->toggle_next = 0; + mtx_unlock(&udev->bus->mtx); + return; +} + +/*------------------------------------------------------------------------* + * usb2_clear_stall_callback - factored out clear stall callback + * + * Input parameters: + * xfer1: Clear Stall Control Transfer + * xfer2: Stalled USB Transfer + * + * This function is NULL safe. + * + * Return values: + * 0: In progress + * Else: Finished + * + * Clear stall config example: + * + * static const struct usb2_config my_clearstall = { + * .type = UE_CONTROL, + * .endpoint = 0, + * .direction = UE_DIR_ANY, + * .interval = 50, //50 milliseconds + * .bufsize = sizeof(struct usb2_device_request), + * .mh.timeout = 1000, //1.000 seconds + * .mh.flags = { }, + * .mh.callback = &my_clear_stall_callback, // ** + * }; + * + * ** "my_clear_stall_callback" calls "usb2_clear_stall_callback" + * passing the correct parameters. + *------------------------------------------------------------------------*/ +uint8_t +usb2_clear_stall_callback(struct usb2_xfer *xfer1, + struct usb2_xfer *xfer2) +{ + struct usb2_device_request req; + + if (xfer2 == NULL) { + /* looks like we are tearing down */ + DPRINTF("NULL input parameter\n"); + return (0); + } + mtx_assert(xfer1->priv_mtx, MA_OWNED); + mtx_assert(xfer2->priv_mtx, MA_OWNED); + + switch (USB_GET_STATE(xfer1)) { + case USB_ST_SETUP: + + /* + * pre-clear the data toggle to DATA0 ("umass.c" and + * "ata-usb.c" depends on this) + */ + + usb2_clear_data_toggle(xfer2->udev, xfer2->pipe); + + /* setup a clear-stall packet */ + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = xfer2->pipe->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + /* + * "usb2_transfer_setup_sub()" will ensure that + * we have sufficient room in the buffer for + * the request structure! + */ + + /* copy in the transfer */ + + usb2_copy_in(xfer1->frbuffers, 0, &req, sizeof(req)); + + /* set length */ + xfer1->frlengths[0] = sizeof(req); + xfer1->nframes = 1; + + usb2_start_hardware(xfer1); + return (0); + + case USB_ST_TRANSFERRED: + break; + + default: /* Error */ + if (xfer1->error == USB_ERR_CANCELLED) { + return (0); + } + break; + } + return (1); /* Clear Stall Finished */ +} + +#if (USB_NO_POLL == 0) + +/*------------------------------------------------------------------------* + * usb2_callout_poll + *------------------------------------------------------------------------*/ +static void +usb2_callout_poll(struct usb2_xfer *xfer) +{ + struct usb2_callout *co; + void (*cb) (void *); + void *arg; + struct mtx *mtx; + uint32_t delta; + + if (xfer == NULL) { + return; + } + co = &xfer->timeout_handle; + +#if __FreeBSD_version >= 800000 + mtx = (void *)(co->co.c_lock); +#else + mtx = co->co.c_mtx; +#endif + mtx_lock(mtx); + + if (usb2_callout_pending(co)) { + delta = ticks - co->co.c_time; + if (!(delta & 0x80000000)) { + + cb = co->co.c_func; + arg = co->co.c_arg; + + /* timed out */ + usb2_callout_stop(co); + + (cb) (arg); + + /* the callback should drop the mutex */ + } else { + mtx_unlock(mtx); + } + } else { + mtx_unlock(mtx); + } + return; +} + + +/*------------------------------------------------------------------------* + * usb2_do_poll + * + * This function is called from keyboard driver when in polling + * mode. + *------------------------------------------------------------------------*/ +void +usb2_do_poll(struct usb2_xfer **ppxfer, uint16_t max) +{ + struct usb2_xfer *xfer; + struct usb2_xfer_root *usb2_root; + struct usb2_device *udev; + struct usb2_proc_msg *pm; + uint32_t to; + uint16_t n; + + /* compute system tick delay */ + to = ((uint32_t)(1000000)) / ((uint32_t)(hz)); + DELAY(to); + atomic_add_int((volatile int *)&ticks, 1); + + for (n = 0; n != max; n++) { + xfer = ppxfer[n]; + if (xfer) { + usb2_root = xfer->usb2_root; + udev = xfer->udev; + + /* + * Poll hardware - signal that we are polling by + * locking the private mutex: + */ + mtx_lock(xfer->priv_mtx); + (udev->bus->methods->do_poll) (udev->bus); + mtx_unlock(xfer->priv_mtx); + + /* poll clear stall start */ + mtx_lock(xfer->usb2_mtx); + pm = &udev->cs_msg[0].hdr; + (pm->pm_callback) (pm); + mtx_unlock(xfer->usb2_mtx); + + if (udev->default_xfer[1]) { + + /* poll timeout */ + usb2_callout_poll(udev->default_xfer[1]); + + /* poll clear stall done thread */ + mtx_lock(xfer->usb2_mtx); + pm = &udev->default_xfer[1]-> + usb2_root->done_m[0].hdr; + (pm->pm_callback) (pm); + mtx_unlock(xfer->usb2_mtx); + } + /* poll timeout */ + usb2_callout_poll(xfer); + + /* poll done thread */ + mtx_lock(xfer->usb2_mtx); + pm = &usb2_root->done_m[0].hdr; + (pm->pm_callback) (pm); + mtx_unlock(xfer->usb2_mtx); + } + } + return; +} + +#else + +void +usb2_do_poll(struct usb2_xfer **ppxfer, uint16_t max) +{ + /* polling not supported */ + return; +} + +#endif diff --git a/sys/dev/usb2/core/usb2_transfer.h b/sys/dev/usb2/core/usb2_transfer.h new file mode 100644 index 000000000000..83920e5fc444 --- /dev/null +++ b/sys/dev/usb2/core/usb2_transfer.h @@ -0,0 +1,123 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_TRANSFER_H_ +#define _USB2_TRANSFER_H_ + +/* + * The following structure defines the messages that is used to signal + * the "done_p" USB process. + */ +struct usb2_done_msg { + struct usb2_proc_msg hdr; + struct usb2_xfer_root *usb2_root; +}; + +/* + * The following structure is used to keep information about memory + * that should be automatically freed at the moment all USB transfers + * have been freed. + */ +struct usb2_xfer_root { + struct usb2_xfer_queue dma_q; + struct usb2_xfer_queue done_q; + struct usb2_done_msg done_m[2]; + struct cv cv_drain; + + struct usb2_dma_parent_tag dma_parent_tag; + + struct usb2_process done_p; + void *memory_base; + struct mtx *priv_mtx; + struct mtx *usb2_mtx; + struct usb2_page_cache *dma_page_cache_start; + struct usb2_page_cache *dma_page_cache_end; + struct usb2_page_cache *xfer_page_cache_start; + struct usb2_page_cache *xfer_page_cache_end; + struct usb2_bus *bus; + + uint32_t memory_size; + uint32_t setup_refcount; + uint32_t page_size; + uint32_t dma_nframes; /* number of page caches to load */ + uint32_t dma_currframe; /* currect page cache number */ + uint32_t dma_frlength_0; /* length of page cache zero */ + uint8_t dma_error; /* set if virtual memory could not be + * loaded */ + uint8_t done_sleep; /* set if done thread is sleeping */ +}; + +/* + * The following structure is used when setting up an array of USB + * transfers. + */ +struct usb2_setup_params { + struct usb2_dma_tag *dma_tag_p; + struct usb2_page *dma_page_ptr; + struct usb2_page_cache *dma_page_cache_ptr; /* these will be + * auto-freed */ + struct usb2_page_cache *xfer_page_cache_ptr; /* these will not be + * auto-freed */ + struct usb2_device *udev; + struct usb2_xfer *curr_xfer; + const struct usb2_config *curr_setup; + const struct usb2_config_sub *curr_setup_sub; + const struct usb2_pipe_methods *methods; + void *buf; + uint32_t *xfer_length_ptr; + + uint32_t size[7]; + uint32_t bufsize; + uint32_t bufsize_max; + uint32_t hc_max_frame_size; + + uint16_t hc_max_packet_size; + uint8_t hc_max_packet_count; + uint8_t speed; + uint8_t dma_tag_max; + usb2_error_t err; +}; + +/* function prototypes */ + +uint8_t usb2_transfer_pending(struct usb2_xfer *xfer); +uint8_t usb2_transfer_setup_sub_malloc(struct usb2_setup_params *parm, struct usb2_page_cache **ppc, uint32_t size, uint32_t align, uint32_t count); +void usb2_command_wrapper(struct usb2_xfer_queue *pq, struct usb2_xfer *xfer); +void usb2_pipe_enter(struct usb2_xfer *xfer); +void usb2_pipe_start(struct usb2_xfer_queue *pq); +void usb2_transfer_dequeue(struct usb2_xfer *xfer); +void usb2_transfer_done(struct usb2_xfer *xfer, usb2_error_t error); +void usb2_transfer_enqueue(struct usb2_xfer_queue *pq, struct usb2_xfer *xfer); +void usb2_transfer_setup_sub(struct usb2_setup_params *parm); +void usb2_default_transfer_setup(struct usb2_device *udev); +void usb2_clear_data_toggle(struct usb2_device *udev, struct usb2_pipe *pipe); +void usb2_do_poll(struct usb2_xfer **ppxfer, uint16_t max); +usb2_callback_t usb2_do_request_callback; +usb2_callback_t usb2_handle_request_callback; +usb2_callback_t usb2_do_clear_stall_callback; +void usb2_transfer_timeout_ms(struct usb2_xfer *xfer, void (*cb) (void *arg), uint32_t ms); + +#endif /* _USB2_TRANSFER_H_ */ diff --git a/sys/dev/usb2/core/usb2_util.c b/sys/dev/usb2/core/usb2_util.c new file mode 100644 index 000000000000..d944e441abb7 --- /dev/null +++ b/sys/dev/usb2/core/usb2_util.c @@ -0,0 +1,354 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* function prototypes */ +#if (USB_USE_CONDVAR == 0) +static int usb2_msleep(void *chan, struct mtx *mtx, int priority, const char *wmesg, int timo); + +#endif + +/*------------------------------------------------------------------------* + * device_delete_all_children - delete all children of a device + *------------------------------------------------------------------------*/ +int +device_delete_all_children(device_t dev) +{ + device_t *devlist; + int devcount; + int error; + + error = device_get_children(dev, &devlist, &devcount); + if (error == 0) { + while (devcount-- > 0) { + error = device_delete_child(dev, devlist[devcount]); + if (error) { + break; + } + } + free(devlist, M_TEMP); + } + return (error); +} + +/*------------------------------------------------------------------------* + * device_set_usb2_desc + * + * This function can be called at probe or attach to set the USB + * device supplied textual description for the given device. + *------------------------------------------------------------------------*/ +void +device_set_usb2_desc(device_t dev) +{ + struct usb2_attach_arg *uaa; + struct usb2_device *udev; + struct usb2_interface *iface; + char *temp_p; + usb2_error_t err; + + if (dev == NULL) { + /* should not happen */ + return; + } + uaa = device_get_ivars(dev); + if (uaa == NULL) { + /* can happend if called at the wrong time */ + return; + } + udev = uaa->device; + iface = uaa->iface; + + if ((iface == NULL) || + (iface->idesc == NULL) || + (iface->idesc->iInterface == 0)) { + err = USB_ERR_INVAL; + } else { + err = 0; + } + + temp_p = (char *)udev->bus->scratch[0].data; + + if (!err) { + /* try to get the interface string ! */ + err = usb2_req_get_string_any + (udev, NULL, temp_p, + sizeof(udev->bus->scratch), iface->idesc->iInterface); + } + if (err) { + /* use default description */ + usb2_devinfo(udev, temp_p, + sizeof(udev->bus->scratch)); + } + device_set_desc_copy(dev, temp_p); + device_printf(dev, "<%s> on %s\n", temp_p, + device_get_nameunit(udev->bus->bdev)); + return; +} + +/*------------------------------------------------------------------------* + * usb2_pause_mtx - factored out code + * + * This function will delay the code by the passed number of + * milliseconds. The passed mutex "mtx" will be dropped while waiting, + * if "mtx" is not NULL. The number of milliseconds per second is 1024 + * for sake of optimisation. + *------------------------------------------------------------------------*/ +void +usb2_pause_mtx(struct mtx *mtx, uint32_t ms) +{ + if (cold) { + ms = (ms + 1) * 1024; + DELAY(ms); + + } else { + + ms = USB_MS_TO_TICKS(ms); + /* + * Add one to the number of ticks so that we don't return + * too early! + */ + ms++; + + if (mtx != NULL) + mtx_unlock(mtx); + + if (pause("USBWAIT", ms)) { + /* ignore */ + } + if (mtx != NULL) + mtx_lock(mtx); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_printBCD + * + * This function will print the version number "bcd" to the string + * pointed to by "p" having a maximum length of "p_len" bytes + * including the terminating zero. + *------------------------------------------------------------------------*/ +void +usb2_printBCD(char *p, uint16_t p_len, uint16_t bcd) +{ + if (snprintf(p, p_len, "%x.%02x", bcd >> 8, bcd & 0xff)) { + /* ignore any errors */ + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_trim_spaces + * + * This function removes spaces at the beginning and the end of the string + * pointed to by the "p" argument. + *------------------------------------------------------------------------*/ +void +usb2_trim_spaces(char *p) +{ + char *q; + char *e; + + if (p == NULL) + return; + q = e = p; + while (*q == ' ') /* skip leading spaces */ + q++; + while ((*p = *q++)) /* copy string */ + if (*p++ != ' ') /* remember last non-space */ + e = p; + *e = 0; /* kill trailing spaces */ + return; +} + +/*------------------------------------------------------------------------* + * usb2_get_devid + * + * This function returns the USB Vendor and Product ID like a 32-bit + * unsigned integer. + *------------------------------------------------------------------------*/ +uint32_t +usb2_get_devid(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + return ((uaa->info.idVendor << 16) | (uaa->info.idProduct)); +} + +/*------------------------------------------------------------------------* + * usb2_make_str_desc - convert an ASCII string into a UNICODE string + *------------------------------------------------------------------------*/ +uint8_t +usb2_make_str_desc(void *ptr, uint16_t max_len, const char *s) +{ + struct usb2_string_descriptor *p = ptr; + uint8_t totlen; + int j; + + if (max_len < 2) { + /* invalid length */ + return (0); + } + max_len = ((max_len / 2) - 1); + + j = strlen(s); + + if (j < 0) { + j = 0; + } + if (j > 126) { + j = 126; + } + if (max_len > j) { + max_len = j; + } + totlen = (max_len + 1) * 2; + + p->bLength = totlen; + p->bDescriptorType = UDESC_STRING; + + while (max_len--) { + USETW2(p->bString[max_len], 0, s[max_len]); + } + return (totlen); +} + +#if (USB_USE_CONDVAR == 0) + +/*------------------------------------------------------------------------* + * usb2_cv_init - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_init(struct cv *cv, const char *desc) +{ + cv_init(cv, desc); + return; +} + +/*------------------------------------------------------------------------* + * usb2_cv_destroy - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_destroy(struct cv *cv) +{ + cv_destroy(cv); + return; +} + +/*------------------------------------------------------------------------* + * usb2_cv_wait - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_wait(struct cv *cv, struct mtx *mtx) +{ + int err; + + err = usb2_msleep(cv, mtx, 0, cv_wmesg(cv), 0); + return; +} + +/*------------------------------------------------------------------------* + * usb2_cv_wait_sig - wrapper function + *------------------------------------------------------------------------*/ +int +usb2_cv_wait_sig(struct cv *cv, struct mtx *mtx) +{ + int err; + + err = usb2_msleep(cv, mtx, PCATCH, cv_wmesg(cv), 0); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_cv_timedwait - wrapper function + *------------------------------------------------------------------------*/ +int +usb2_cv_timedwait(struct cv *cv, struct mtx *mtx, int timo) +{ + int err; + + if (timo == 0) + timo = 1; /* zero means no timeout */ + err = usb2_msleep(cv, mtx, 0, cv_wmesg(cv), timo); + return (err); +} + +/*------------------------------------------------------------------------* + * usb2_cv_signal - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_signal(struct cv *cv) +{ + wakeup_one(cv); + return; +} + +/*------------------------------------------------------------------------* + * usb2_cv_broadcast - wrapper function + *------------------------------------------------------------------------*/ +void +usb2_cv_broadcast(struct cv *cv) +{ + wakeup(cv); + return; +} + +/*------------------------------------------------------------------------* + * usb2_msleep - wrapper function + *------------------------------------------------------------------------*/ +static int +usb2_msleep(void *chan, struct mtx *mtx, int priority, const char *wmesg, + int timo) +{ + int err; + + if (mtx == &Giant) { + err = tsleep(chan, priority, wmesg, timo); + } else { +#ifdef mtx_sleep + err = mtx_sleep(chan, mtx, priority, wmesg, timo); +#else + err = msleep(chan, mtx, priority, wmesg, timo); +#endif + } + return (err); +} + +#endif diff --git a/sys/dev/usb2/core/usb2_util.h b/sys/dev/usb2/core/usb2_util.h new file mode 100644 index 000000000000..8bbae8c5d32c --- /dev/null +++ b/sys/dev/usb2/core/usb2_util.h @@ -0,0 +1,57 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_UTIL_H_ +#define _USB2_UTIL_H_ + +int device_delete_all_children(device_t dev); +uint32_t usb2_get_devid(device_t dev); +uint8_t usb2_make_str_desc(void *ptr, uint16_t max_len, const char *s); +void device_set_usb2_desc(device_t dev); +void usb2_pause_mtx(struct mtx *mtx, uint32_t ms); +void usb2_printBCD(char *p, uint16_t p_len, uint16_t bcd); +void usb2_trim_spaces(char *p); + +#if (USB_USE_CONDVAR == 0) +void usb2_cv_init(struct cv *cv, const char *desc); +void usb2_cv_destroy(struct cv *cv); +void usb2_cv_wait(struct cv *cv, struct mtx *mtx); +int usb2_cv_wait_sig(struct cv *cv, struct mtx *mtx); +int usb2_cv_timedwait(struct cv *cv, struct mtx *mtx, int timo); +void usb2_cv_signal(struct cv *cv); +void usb2_cv_broadcast(struct cv *cv); + +#else +#define usb2_cv_init cv_init +#define usb2_cv_destroy cv_destroy +#define usb2_cv_wait cv_wait +#define usb2_cv_wait_sig cv_wait_sig +#define usb2_cv_timedwait cv_timedwait +#define usb2_cv_signal cv_signal +#define usb2_cv_broadcast cv_broadcast +#endif + +#endif /* _USB2_UTIL_H_ */ diff --git a/sys/dev/usb2/core/usbdevs b/sys/dev/usb2/core/usbdevs new file mode 100644 index 000000000000..8fa1cf1f14c2 --- /dev/null +++ b/sys/dev/usb2/core/usbdevs @@ -0,0 +1,2482 @@ +$FreeBSD$ +/* $NetBSD: usbdevs,v 1.392 2004/12/29 08:38:44 imp Exp $ */ + +/*- + * Copyright (c) 1998-2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * List of known USB vendors + * + * USB.org publishes a VID list of USB-IF member companies at + * http://www.usb.org/developers/tools + * Note that it does not show companies that have obtained a Vendor ID + * without becoming full members. + * + * Please note that these IDs do not do anything. Adding an ID here and + * regenerating the usbdevs.h and usbdevs_data.h only makes a symbolic name + * available to the source code and does not change any functionality, nor + * does it make your device available to a specific driver. + * It will however make the descriptive string available if a device does not + * provide the string itself. + * + * After adding a vendor ID VNDR and a product ID PRDCT you will have the + * following extra defines: + * #define USB_VENDOR_VNDR 0x???? + * #define USB_PRODUCT_VNDR_PRDCT 0x???? + * + * You may have to add these defines to the respective probe routines to + * make the device recognised by the appropriate device driver. + */ + +vendor UNKNOWN1 0x0053 Unknown vendor +vendor UNKNOWN2 0x0105 Unknown vendor +vendor EGALAX2 0x0123 eGalax, Inc. +vendor HUMAX 0x02ad HUMAX +vendor LTS 0x0386 LTS +vendor BWCT 0x03da Bernd Walter Computer Technology +vendor AOX 0x03e8 AOX +vendor THESYS 0x03e9 Thesys +vendor DATABROADCAST 0x03ea Data Broadcasting +vendor ATMEL 0x03eb Atmel +vendor IWATSU 0x03ec Iwatsu America +vendor MITSUMI 0x03ee Mitsumi +vendor HP 0x03f0 Hewlett Packard +vendor GENOA 0x03f1 Genoa +vendor OAK 0x03f2 Oak +vendor ADAPTEC 0x03f3 Adaptec +vendor DIEBOLD 0x03f4 Diebold +vendor SIEMENSELECTRO 0x03f5 Siemens Electromechanical +vendor EPSONIMAGING 0x03f8 Epson Imaging +vendor KEYTRONIC 0x03f9 KeyTronic +vendor OPTI 0x03fb OPTi +vendor ELITEGROUP 0x03fc Elitegroup +vendor XILINX 0x03fd Xilinx +vendor FARALLON 0x03fe Farallon Communications +vendor NATIONAL 0x0400 National Semiconductor +vendor NATIONALREG 0x0401 National Registry +vendor ACERLABS 0x0402 Acer Labs +vendor FTDI 0x0403 Future Technology Devices +vendor NCR 0x0404 NCR +vendor SYNOPSYS2 0x0405 Synopsys +vendor FUJITSUICL 0x0406 Fujitsu-ICL +vendor FUJITSU2 0x0407 Fujitsu Personal Systems +vendor QUANTA 0x0408 Quanta +vendor NEC 0x0409 NEC +vendor KODAK 0x040a Eastman Kodak +vendor WELTREND 0x040b Weltrend +vendor VIA 0x040d VIA +vendor MCCI 0x040e MCCI +vendor MELCO 0x0411 Melco +vendor LEADTEK 0x0413 Leadtek +vendor WINBOND 0x0416 Winbond +vendor PHOENIX 0x041a Phoenix +vendor CREATIVE 0x041e Creative Labs +vendor NOKIA 0x0421 Nokia +vendor ADI 0x0422 ADI Systems +vendor CATC 0x0423 Computer Access Technology +vendor SMC2 0x0424 Standard Microsystems +vendor MOTOROLA_HK 0x0425 Motorola HK +vendor GRAVIS 0x0428 Advanced Gravis Computer +vendor CIRRUSLOGIC 0x0429 Cirrus Logic +vendor INNOVATIVE 0x042c Innovative Semiconductors +vendor MOLEX 0x042f Molex +vendor SUN 0x0430 Sun Microsystems +vendor UNISYS 0x0432 Unisys +vendor TAUGA 0x0436 Taugagreining HF +vendor AMD 0x0438 Advanced Micro Devices +vendor LEXMARK 0x043d Lexmark International +vendor LG 0x043e LG Electronics +vendor NANAO 0x0440 NANAO +vendor GATEWAY 0x0443 Gateway 2000 +vendor NMB 0x0446 NMB +vendor ALPS 0x044e Alps Electric +vendor THRUST 0x044f Thrustmaster +vendor TI 0x0451 Texas Instruments +vendor ANALOGDEVICES 0x0456 Analog Devices +vendor SIS 0x0457 Silicon Integrated Systems Corp. +vendor KYE 0x0458 KYE Systems +vendor DIAMOND2 0x045a Diamond (Supra) +vendor RENESAS 0x045b Renesas +vendor MICROSOFT 0x045e Microsoft +vendor PRIMAX 0x0461 Primax Electronics +vendor MGE 0x0463 MGE UPS Systems +vendor AMP 0x0464 AMP +vendor CHERRY 0x046a Cherry Mikroschalter +vendor MEGATRENDS 0x046b American Megatrends +vendor LOGITECH 0x046d Logitech +vendor BTC 0x046e Behavior Tech. Computer +vendor PHILIPS 0x0471 Philips +vendor SUN2 0x0472 Sun Microsystems (offical) +vendor SANYO 0x0474 Sanyo Electric +vendor SEAGATE 0x0477 Seagate +vendor CONNECTIX 0x0478 Connectix +vendor SEMTECH 0x047a Semtech +vendor KENSINGTON 0x047d Kensington +vendor LUCENT 0x047e Lucent +vendor PLANTRONICS 0x047f Plantronics +vendor KYOCERA 0x0482 Kyocera Wireless Corp. +vendor STMICRO 0x0483 STMicroelectronics +vendor FOXCONN 0x0489 Foxconn +vendor YAMAHA 0x0499 YAMAHA +vendor COMPAQ 0x049f Compaq +vendor HITACHI 0x04a4 Hitachi +vendor ACERP 0x04a5 Acer Peripherals +vendor DAVICOM 0x04a6 Davicom +vendor VISIONEER 0x04a7 Visioneer +vendor CANON 0x04a9 Canon +vendor NIKON 0x04b0 Nikon +vendor PAN 0x04b1 Pan International +vendor IBM 0x04b3 IBM +vendor CYPRESS 0x04b4 Cypress Semiconductor +vendor ROHM 0x04b5 ROHM +vendor COMPAL 0x04b7 Compal +vendor EPSON 0x04b8 Seiko Epson +vendor RAINBOW 0x04b9 Rainbow Technologies +vendor IODATA 0x04bb I-O Data +vendor TDK 0x04bf TDK +vendor 3COMUSR 0x04c1 U.S. Robotics +vendor METHODE 0x04c2 Methode Electronics Far East +vendor MAXISWITCH 0x04c3 Maxi Switch +vendor LOCKHEEDMER 0x04c4 Lockheed Martin Energy Research +vendor FUJITSU 0x04c5 Fujitsu +vendor TOSHIBAAM 0x04c6 Toshiba America +vendor MICROMACRO 0x04c7 Micro Macro Technologies +vendor KONICA 0x04c8 Konica +vendor LITEON 0x04ca Lite-On Technology +vendor FUJIPHOTO 0x04cb Fuji Photo Film +vendor PHILIPSSEMI 0x04cc Philips Semiconductors +vendor TATUNG 0x04cd Tatung Co. Of America +vendor SCANLOGIC 0x04ce ScanLogic +vendor MYSON 0x04cf Myson Technology +vendor DIGI2 0x04d0 Digi +vendor ITTCANON 0x04d1 ITT Canon +vendor ALTEC 0x04d2 Altec Lansing +vendor LSI 0x04d4 LSI +vendor MENTORGRAPHICS 0x04d6 Mentor Graphics +vendor ITUNERNET 0x04d8 I-Tuner Networks +vendor HOLTEK 0x04d9 Holtek Semiconductor, Inc. +vendor PANASONIC 0x04da Panasonic (Matsushita) +vendor HUANHSIN 0x04dc Huan Hsin +vendor SHARP 0x04dd Sharp +vendor IIYAMA 0x04e1 Iiyama +vendor SHUTTLE 0x04e6 Shuttle Technology +vendor ELO 0x04e7 Elo TouchSystems +vendor SAMSUNG 0x04e8 Samsung Electronics +vendor NORTHSTAR 0x04eb Northstar +vendor TOKYOELECTRON 0x04ec Tokyo Electron +vendor ANNABOOKS 0x04ed Annabooks +vendor JVC 0x04f1 JVC +vendor CHICONY 0x04f2 Chicony Electronics +vendor ELAN 0x04f3 Elan +vendor NEWNEX 0x04f7 Newnex +vendor BROTHER 0x04f9 Brother Industries +vendor DALLAS 0x04fa Dallas Semiconductor +vendor SUNPLUS 0x04fc Sunplus +vendor PFU 0x04fe PFU +vendor FUJIKURA 0x0501 Fujikura/DDK +vendor ACER 0x0502 Acer +vendor 3COM 0x0506 3Com +vendor HOSIDEN 0x0507 Hosiden Corporation +vendor AZTECH 0x0509 Aztech Systems +vendor BELKIN 0x050d Belkin Components +vendor KAWATSU 0x050f Kawatsu Semiconductor +vendor FCI 0x0514 FCI +vendor LONGWELL 0x0516 Longwell +vendor COMPOSITE 0x0518 Composite +vendor STAR 0x0519 Star Micronics +vendor APC 0x051d American Power Conversion +vendor SCIATLANTA 0x051e Scientific Atlanta +vendor TSM 0x0520 TSM +vendor CONNECTEK 0x0522 Advanced Connectek USA +vendor NETCHIP 0x0525 NetChip Technology +vendor ALTRA 0x0527 ALTRA +vendor ATI 0x0528 ATI Technologies +vendor AKS 0x0529 Aladdin Knowledge Systems +vendor TEKOM 0x052b Tekom +vendor CANONDEV 0x052c Canon +vendor WACOMTECH 0x0531 Wacom +vendor INVENTEC 0x0537 Inventec +vendor SHYHSHIUN 0x0539 Shyh Shiun Terminals +vendor PREHWERKE 0x053a Preh Werke Gmbh & Co. KG +vendor SYNOPSYS 0x053f Synopsys +vendor UNIACCESS 0x0540 Universal Access +vendor VIEWSONIC 0x0543 ViewSonic +vendor XIRLINK 0x0545 Xirlink +vendor ANCHOR 0x0547 Anchor Chips +vendor SONY 0x054c Sony +vendor FUJIXEROX 0x0550 Fuji Xerox +vendor VISION 0x0553 VLSI Vision +vendor ASAHIKASEI 0x0556 Asahi Kasei Microsystems +vendor ATEN 0x0557 ATEN International +vendor SAMSUNG2 0x055d Samsung Electronics +vendor MUSTEK 0x055f Mustek Systems +vendor TELEX 0x0562 Telex Communications +vendor CHINON 0x0564 Chinon +vendor PERACOM 0x0565 Peracom Networks +vendor ALCOR2 0x0566 Alcor Micro +vendor XYRATEX 0x0567 Xyratex +vendor WACOM 0x056a WACOM +vendor ETEK 0x056c e-TEK Labs +vendor EIZO 0x056d EIZO +vendor ELECOM 0x056e Elecom +vendor CONEXANT 0x0572 Conexant +vendor HAUPPAUGE 0x0573 Hauppauge Computer Works +vendor BAFO 0x0576 BAFO/Quality Computer Accessories +vendor YEDATA 0x057b Y-E Data +vendor AVM 0x057c AVM +vendor QUICKSHOT 0x057f Quickshot +vendor ROLAND 0x0582 Roland +vendor ROCKFIRE 0x0583 Rockfire +vendor RATOC 0x0584 RATOC Systems +vendor ZYXEL 0x0586 ZyXEL Communication +vendor INFINEON 0x058b Infineon +vendor MICREL 0x058d Micrel +vendor ALCOR 0x058f Alcor Micro +vendor OMRON 0x0590 OMRON +vendor ZORAN 0x0595 Zoran Microelectronics +vendor NIIGATA 0x0598 Niigata +vendor IOMEGA 0x059b Iomega +vendor ATREND 0x059c A-Trend Technology +vendor AID 0x059d Advanced Input Devices +vendor LACIE 0x059f LaCie +vendor FUJIFILM 0x05a2 Fuji Film +vendor ARC 0x05a3 ARC +vendor ORTEK 0x05a4 Ortek +vendor BOSE 0x05a7 Bose +vendor OMNIVISION 0x05a9 OmniVision +vendor INSYSTEM 0x05ab In-System Design +vendor APPLE 0x05ac Apple Computer +vendor YCCABLE 0x05ad Y.C. Cable +vendor DIGITALPERSONA 0x05ba DigitalPersona +vendor 3G 0x05bc 3G Green Green Globe +vendor RAFI 0x05bd RAFI +vendor TYCO 0x05be Tyco +vendor KAWASAKI 0x05c1 Kawasaki +vendor DIGI 0x05c5 Digi International +vendor QUALCOMM2 0x05c6 Qualcomm +vendor QTRONIX 0x05c7 Qtronix +vendor FOXLINK 0x05c8 Foxlink +vendor RICOH 0x05ca Ricoh +vendor ELSA 0x05cc ELSA +vendor SCIWORX 0x05ce sci-worx +vendor BRAINBOXES 0x05d1 Brainboxes Limited +vendor ULTIMA 0x05d8 Ultima +vendor AXIOHM 0x05d9 Axiohm Transaction Solutions +vendor MICROTEK 0x05da Microtek +vendor SUNTAC 0x05db SUN Corporation +vendor LEXAR 0x05dc Lexar Media +vendor ADDTRON 0x05dd Addtron +vendor SYMBOL 0x05e0 Symbol Technologies +vendor SYNTEK 0x05e1 Syntek +vendor GENESYS 0x05e3 Genesys Logic +vendor FUJI 0x05e5 Fuji Electric +vendor KEITHLEY 0x05e6 Keithley Instruments +vendor EIZONANAO 0x05e7 EIZO Nanao +vendor KLSI 0x05e9 Kawasaki LSI +vendor FFC 0x05eb FFC +vendor ANKO 0x05ef Anko Electronic +vendor PIENGINEERING 0x05f3 P.I. Engineering +vendor AOC 0x05f6 AOC International +vendor CHIC 0x05fe Chic Technology +vendor BARCO 0x0600 Barco Display Systems +vendor BRIDGE 0x0607 Bridge Information +vendor SOLIDYEAR 0x060b Solid Year +vendor BIORAD 0x0614 Bio-Rad Laboratories +vendor MACALLY 0x0618 Macally +vendor ACTLABS 0x061c Act Labs +vendor ALARIS 0x0620 Alaris +vendor APEX 0x0624 Apex +vendor CREATIVE3 0x062a Creative Labs +vendor VIVITAR 0x0636 Vivitar +vendor GUNZE 0x0637 Gunze Electronics USA +vendor AVISION 0x0638 Avision +vendor TEAC 0x0644 TEAC +vendor SGI 0x065e Silicon Graphics +vendor SANWASUPPLY 0x0663 Sanwa Supply +vendor LINKSYS 0x066b Linksys +vendor ACERSA 0x066e Acer Semiconductor America +vendor SIGMATEL 0x066f Sigmatel +vendor DRAYTEK 0x0675 DrayTek +vendor AIWA 0x0677 Aiwa +vendor ACARD 0x0678 ACARD Technology +vendor PROLIFIC 0x067b Prolific Technology +vendor SIEMENS 0x067c Siemens +vendor AVANCELOGIC 0x0680 Avance Logic +vendor SIEMENS2 0x0681 Siemens +vendor MINOLTA 0x0686 Minolta +vendor CHPRODUCTS 0x068e CH Products +vendor HAGIWARA 0x0693 Hagiwara Sys-Com +vendor CTX 0x0698 Chuntex +vendor ASKEY 0x069a Askey Computer +vendor SAITEK 0x06a3 Saitek +vendor ALCATELT 0x06b9 Alcatel Telecom +vendor AGFA 0x06bd AGFA-Gevaert +vendor ASIAMD 0x06be Asia Microelectronic Development +vendor BIZLINK 0x06c4 Bizlink International +vendor KEYSPAN 0x06cd Keyspan / InnoSys Inc. +vendor AASHIMA 0x06d6 Aashima Technology +vendor MULTITECH 0x06e0 MultiTech +vendor ADS 0x06e1 ADS Technologies +vendor ALCATELM 0x06e4 Alcatel Microelectronics +vendor SIRIUS 0x06ea Sirius Technologies +vendor GUILLEMOT 0x06f8 Guillemot +vendor BOSTON 0x06fd Boston Acoustics +vendor SMC 0x0707 Standard Microsystems +vendor PUTERCOM 0x0708 Putercom +vendor MCT 0x0711 MCT +vendor IMATION 0x0718 Imation +vendor SONYERICSSON 0x0731 Sony Ericsson +vendor EICON 0x0734 Eicon Networks +vendor SYNTECH 0x0745 Syntech Information +vendor DIGITALSTREAM 0x074e Digital Stream +vendor AUREAL 0x0755 Aureal Semiconductor +vendor MIDIMAN 0x0763 Midiman +vendor CYBERPOWER 0x0764 Cyber Power Systems, Inc. +vendor SURECOM 0x0769 Surecom Technology +vendor LINKSYS2 0x077b Linksys +vendor GRIFFIN 0x077d Griffin Technology +vendor SANDISK 0x0781 SanDisk +vendor JENOPTIK 0x0784 Jenoptik +vendor LOGITEC 0x0789 Logitec +vendor BRIMAX 0x078e Brimax +vendor AXIS 0x0792 Axis Communications +vendor ABL 0x0794 ABL Electronics +vendor SAGEM 0x079b Sagem +vendor SUNCOMM 0x079c Sun Communications, Inc. +vendor ALFADATA 0x079d Alfadata Computer +vendor NATIONALTECH 0x07a2 National Technical Systems +vendor ONNTO 0x07a3 Onnto +vendor BE 0x07a4 Be +vendor ADMTEK 0x07a6 ADMtek +vendor COREGA 0x07aa Corega +vendor FREECOM 0x07ab Freecom +vendor MICROTECH 0x07af Microtech +vendor GENERALINSTMNTS 0x07b2 General Instruments (Motorola) +vendor OLYMPUS 0x07b4 Olympus +vendor ABOCOM 0x07b8 AboCom Systems +vendor KEISOKUGIKEN 0x07c1 Keisokugiken +vendor ONSPEC 0x07c4 OnSpec +vendor APG 0x07c5 APG Cash Drawer +vendor BUG 0x07c8 B.U.G. +vendor ALLIEDTELESYN 0x07c9 Allied Telesyn International +vendor AVERMEDIA 0x07ca AVerMedia Technologies +vendor SIIG 0x07cc SIIG +vendor CASIO 0x07cf CASIO +vendor DLINK2 0x07d1 D-Link +vendor APTIO 0x07d2 Aptio Products +vendor ARASAN 0x07da Arasan Chip Systems +vendor ALLIEDCABLE 0x07e6 Allied Cable +vendor STSN 0x07ef STSN +vendor CENTURY 0x07f7 Century Corp +vendor ZOOM 0x0803 Zoom Telephonics +vendor PCS 0x0810 Personal Communication Systems +vendor BROADLOGIC 0x0827 BroadLogic +vendor HANDSPRING 0x082d Handspring +vendor PALM 0x0830 Palm Computing +vendor SOURCENEXT 0x0833 SOURCENEXT +vendor ACTIONSTAR 0x0835 Action Star Enterprise +vendor SAMSUNG_TECHWIN 0x0839 Samsung Techwin +vendor ACCTON 0x083a Accton Technology +vendor DIAMOND 0x0841 Diamond +vendor NETGEAR 0x0846 BayNETGEAR +vendor TOPRE 0x0853 Topre Corporation +vendor ACTIVEWIRE 0x0854 ActiveWire +vendor BBELECTRONICS 0x0856 B&B Electronics +vendor PORTGEAR 0x085a PortGear +vendor NETGEAR2 0x0864 Netgear +vendor SYSTEMTALKS 0x086e System Talks +vendor METRICOM 0x0870 Metricom +vendor ADESSOKBTEK 0x087c ADESSO/Kbtek America +vendor JATON 0x087d Jaton +vendor APT 0x0880 APT Technologies +vendor BOCARESEARCH 0x0885 Boca Research +vendor ANDREA 0x08a8 Andrea Electronics +vendor BURRBROWN 0x08bb Burr-Brown Japan +vendor 2WIRE 0x08c8 2Wire +vendor AIPTEK 0x08ca AIPTEK International +vendor SMARTBRIDGES 0x08d1 SmartBridges +vendor BILLIONTON 0x08dd Billionton Systems +vendor EXTENDED 0x08e9 Extended Systems +vendor MSYSTEMS 0x08ec M-Systems +vendor AUTHENTEC 0x08ff AuthenTec +vendor AUDIOTECHNICA 0x0909 Audio-Technica +vendor TRUMPION 0x090a Trumpion Microelectronics +vendor FEIYA 0x090c Feiya +vendor ALATION 0x0910 Alation Systems +vendor GLOBESPAN 0x0915 Globespan +vendor CONCORDCAMERA 0x0919 Concord Camera +vendor GARMIN 0x091e Garmin International +vendor GOHUBS 0x0921 GoHubs +vendor XEROX 0x0924 Xerox +vendor BIOMETRIC 0x0929 American Biometric Company +vendor TOSHIBA 0x0930 Toshiba +vendor PLEXTOR 0x093b Plextor +vendor INTREPIDCS 0x093c Intrepid +vendor YANO 0x094f Yano +vendor KINGSTON 0x0951 Kingston Technology +vendor BLUEWATER 0x0956 BlueWater Systems +vendor AGILENT 0x0957 Agilent Technologies +vendor GUDE 0x0959 Gude ADS +vendor PORTSMITH 0x095a Portsmith +vendor ACERW 0x0967 Acer +vendor ADIRONDACK 0x0976 Adirondack Wire & Cable +vendor BECKHOFF 0x0978 Beckhoff +vendor MINDSATWORK 0x097a Minds At Work +vendor POINTCHIPS 0x09a6 PointChips +vendor INTERSIL 0x09aa Intersil +vendor ALTIUS 0x09b3 Altius Solutions +vendor ARRIS 0x09c1 Arris Interactive +vendor ACTIVCARD 0x09c3 ACTIVCARD +vendor ACTISYS 0x09c4 ACTiSYS +vendor NOVATEL2 0x09d7 Novatel Wireless +vendor AFOURTECH 0x09da A-FOUR TECH +vendor AIMEX 0x09dc AIMEX +vendor ADDONICS 0x09df Addonics Technologies +vendor AKAI 0x09e8 AKAI professional M.I. +vendor ARESCOM 0x09f5 ARESCOM +vendor BAY 0x09f9 Bay Associates +vendor ALTERA 0x09fb Altera +vendor CSR 0x0a12 Cambridge Silicon Radio +vendor TREK 0x0a16 Trek Technology +vendor ASAHIOPTICAL 0x0a17 Asahi Optical +vendor BOCASYSTEMS 0x0a43 Boca Systems +vendor SHANTOU 0x0a46 ShanTou +vendor MEDIAGEAR 0x0a48 MediaGear +vendor BROADCOM 0x0a5c Broadcom +vendor GREENHOUSE 0x0a6b GREENHOUSE +vendor GEOCAST 0x0a79 Geocast Network Systems +vendor IDQUANTIQUE 0x0aba id Quantique +vendor ZYDAS 0x0ace Zydas Technology Corporation +vendor NEODIO 0x0aec Neodio +vendor OPTION 0x0af0 Option N.V: +vendor ASUS 0x0b05 ASUSTeK Computer +vendor TODOS 0x0b0c Todos Data System +vendor SIIG2 0x0b39 SIIG +vendor TEKRAM 0x0b3b Tekram Technology +vendor HAL 0x0b41 HAL Corporation +vendor EMS 0x0b43 EMS Production +vendor NEC2 0x0b62 NEC +vendor ATI2 0x0b6f ATI +vendor ZEEVO 0x0b7a Zeevo, Inc. +vendor KURUSUGAWA 0x0b7e Kurusugawa Electronics, Inc. +vendor ASIX 0x0b95 ASIX Electronics +vendor O2MICRO 0x0b97 O2 Micro, Inc. +vendor USR 0x0baf U.S. Robotics +vendor AMBIT 0x0bb2 Ambit Microsystems +vendor HTC 0x0bb4 HTC +vendor REALTEK 0x0bda Realtek +vendor ADDONICS2 0x0bf6 Addonics Technology +vendor FSC 0x0bf8 Fujitsu Siemens Computers +vendor AGATE 0x0c08 Agate Technologies +vendor DMI 0x0c0b DMI +vendor MICRODIA 0x0c45 Chicony +vendor SEALEVEL 0x0c52 Sealevel System +vendor LUWEN 0x0c76 Luwen +vendor KYOCERA2 0x0c88 Kyocera Wireless Corp. +vendor ZCOM 0x0cde Z-Com +vendor ATHEROS2 0x0cf3 Atheros Communications +vendor TANGTOP 0x0d3d Tangtop +vendor SMC3 0x0d5c Standard Microsystems +vendor ADDON 0x0d7d Add-on Technology +vendor ACDC 0x0d7e American Computer & Digital Components +vendor ABC 0x0d8c ABC +vendor CONCEPTRONIC 0x0d8e Conceptronic +vendor SKANHEX 0x0d96 Skanhex Technology, Inc. +vendor MSI 0x0db0 Micro Star International +vendor ELCON 0x0db7 ELCON Systemtechnik +vendor NETAC 0x0dd8 Netac +vendor SITECOMEU 0x0df6 Sitecom Europe +vendor MOBILEACTION 0x0df7 Mobile Action +vendor SPEEDDRAGON 0x0e55 Speed Dragon Multimedia +vendor HAWKING 0x0e66 Hawking +vendor FOSSIL 0x0e67 Fossil, Inc +vendor GMATE 0x0e7e G.Mate, Inc +vendor OTI 0x0ea0 Ours Technology +vendor PILOTECH 0x0eaf Pilotech +vendor NOVATECH 0x0eb0 NovaTech +vendor ITEGNO 0x0eba iTegno +vendor WINMAXGROUP 0x0ed1 WinMaxGroup +vendor TOD 0x0ede TOD +vendor EGALAX 0x0eef eGalax, Inc. +vendor AIRPRIME 0x0f3d AirPrime, Inc. +vendor MICROTUNE 0x0f4d Microtune +vendor VTECH 0x0f88 VTech +vendor FALCOM 0x0f94 Falcom Wireless Communications GmbH +vendor RIM 0x0fca Research In Motion +vendor DYNASTREAM 0x0fcf Dynastream Innovations +vendor QUALCOMM 0x1004 Qualcomm +vendor DESKNOTE 0x1019 Desknote +vendor GIGABYTE 0x1044 GIGABYTE +vendor WESTERN 0x1058 Western Digital +vendor MOTOROLA 0x1063 Motorola +vendor CCYU 0x1065 CCYU Technology +vendor CURITEL 0x106c Curitel Communications Inc +vendor SILABS2 0x10a6 SILABS2 +vendor USI 0x10ab USI +vendor PLX 0x10b5 PLX +vendor ASANTE 0x10bd Asante +vendor SILABS 0x10c4 Silicon Labs +vendor ANALOG 0x1110 Analog Devices +vendor TENX 0x1130 Ten X Technology, Inc. +vendor ISSC 0x1131 Integrated System Solution Corp. +vendor JRC 0x1145 Japan Radio Company +vendor SPHAIRON 0x114b Sphairon Access Systems GmbH +vendor DELORME 0x1163 DeLorme +vendor SERVERWORKS 0x1166 ServerWorks +vendor ACERCM 0x1189 Acer Communications & Multimedia +vendor SIERRA 0x1199 Sierra Wireless +vendor TOPFIELD 0x11db Topfield Co., Ltd +vendor SIEMENS3 0x11f5 Siemens +vendor PROLIFIC2 0x11f6 Prolific +vendor ALCATEL 0x11f7 Alcatel +vendor UNKNOWN3 0x1233 Unknown vendor +vendor TSUNAMI 0x1241 Tsunami +vendor PHEENET 0x124a Pheenet +vendor TARGUS 0x1267 Targus +vendor TWINMOS 0x126f TwinMOS +vendor TENDA 0x1286 Tenda +vendor CREATIVE2 0x1292 Creative Labs +vendor BELKIN2 0x1293 Belkin Components +vendor CYBERTAN 0x129b CyberTAN Technology +vendor HUAWEI 0x12d1 Huawei Technologies +vendor ARANEUS 0x12d8 Araneus Information Systems +vendor TAPWAVE 0x12ef Tapwave +vendor AINCOMM 0x12fd Aincomm +vendor MOBILITY 0x1342 Mobility +vendor DICKSMITH 0x1371 Dick Smith Electronics +vendor NETGEAR3 0x1385 Netgear +vendor BALTECH 0x13ad Baltech +vendor CISCOLINKSYS 0x13b1 Cisco-Linksys +vendor SHARK 0x13d2 Shark +vendor NOVATEL 0x1410 Novatel Wireless +vendor MERLIN 0x1416 Merlin +vendor WISTRONNEWEB 0x1435 Wistron NeWeb +vendor RADIOSHACK 0x1453 Radio Shack +vendor HUAWEI3COM 0x1472 Huawei-3Com +vendor SILICOM 0x1485 Silicom +vendor RALINK 0x148f Ralink Technology +vendor IMAGINATION 0x149a Imagination Technologies +vendor CONCEPTRONIC2 0x14b2 Conceptronic +vendor PLANEX3 0x14ea Planex Communications +vendor SILICONPORTALS 0x1527 Silicon Portals +vendor UBIQUAM 0x1529 UBIQUAM Co., Ltd. +vendor UBLOX 0x1546 U-blox +vendor PNY 0x154b PNY +vendor OQO 0x1557 OQO +vendor UMEDIA 0x157e U-MEDIA Communications +vendor FIBERLINE 0x1582 Fiberline +vendor SPARKLAN 0x15a9 SparkLAN +vendor SOHOWARE 0x15e8 SOHOware +vendor UMAX 0x1606 UMAX Data Systems +vendor INSIDEOUT 0x1608 Inside Out Networks +vendor GOODWAY 0x1631 Good Way Technology +vendor ENTREGA 0x1645 Entrega +vendor ACTIONTEC 0x1668 Actiontec Electronics +vendor ATHEROS 0x168c Atheros Communications +vendor GIGASET 0x1690 Gigaset +vendor GLOBALSUN 0x16ab Global Sun Technology +vendor ANYDATA 0x16d5 AnyDATA Corporation +vendor JABLOTRON 0x16d6 Jablotron +vendor CMOTECH 0x16d8 CMOTECH Co., Ltd. +vendor AXESSTEL 0x1726 Axesstel Co., Ltd. +vendor LINKSYS4 0x1737 Linksys +vendor SENAO 0x1740 Senao +vendor METAGEEK 0x1781 MetaGeek +vendor AMIT 0x18c5 AMIT +vendor QCOM 0x18e8 Qcom +vendor LINKSYS3 0x1915 Linksys +vendor QUALCOMMINC 0x19d2 Qualcomm, Incorporated +vendor DLINK 0x2001 D-Link +vendor PLANEX2 0x2019 Planex Communications +vendor ERICSSON 0x2282 Ericsson +vendor MOTOROLA2 0x22b8 Motorola +vendor TRIPPLITE 0x2478 Tripp-Lite +vendor HIROSE 0x2631 Hirose Electric +vendor NHJ 0x2770 NHJ +vendor PLANEX 0x2c02 Planex Communications +vendor VIDZMEDIA 0x3275 VidzMedia Pte Ltd +vendor AEI 0x3334 AEI +vendor HANK 0x3353 Hank Connection +vendor PQI 0x3538 PQI +vendor DAISY 0x3579 Daisy Technology +vendor NI 0x3923 National Instruments +vendor MICRONET 0x3980 Micronet Communications +vendor IODATA2 0x40bb I-O Data +vendor IRIVER 0x4102 iRiver +vendor DELL 0x413c Dell +vendor WCH 0x4348 QinHeng Electronics +vendor ACEECA 0x4766 Aceeca +vendor AVERATEC 0x50c2 Averatec +vendor SWEEX 0x5173 Sweex +vendor ONSPEC2 0x55aa OnSpec Electronic Inc. +vendor ZINWELL 0x5a57 Zinwell +vendor SITECOM 0x6189 Sitecom +vendor ARKMICRO 0x6547 Arkmicro Technologies Inc. +vendor 3COM2 0x6891 3Com +vendor INTEL 0x8086 Intel +vendor SITECOM2 0x9016 Sitecom +vendor MOSCHIP 0x9710 MosChip Semiconductor +vendor 3COM3 0xa727 3Com +vendor HP2 0xf003 Hewlett Packard +vendor USRP 0xfffe GNU Radio USRP + +/* + * List of known products. Grouped by vendor. + */ + +/* 3Com products */ +product 3COM HOMECONN 0x009d HomeConnect Camera +product 3COM 3CREB96 0x00a0 Bluetooth USB Adapter +product 3COM 3C19250 0x03e8 3C19250 Ethernet Adapter +product 3COM 3CRSHEW696 0x0a01 3CRSHEW696 Wireless Adapter +product 3COM 3C460 0x11f8 HomeConnect 3C460 +product 3COM USR56K 0x3021 U.S.Robotics 56000 Voice FaxModem Pro +product 3COM 3C460B 0x4601 HomeConnect 3C460B +product 3COM2 3CRUSB10075 0xa727 3CRUSB10075 +product 3COM3 AR5523_1 0x6893 AR5523 +product 3COM3 AR5523_2 0x6895 AR5523 +product 3COM3 AR5523_3 0x6897 AR5523 + +product 3COMUSR OFFICECONN 0x0082 3Com OfficeConnect Analog Modem +product 3COMUSR USRISDN 0x008f 3Com U.S. Robotics Pro ISDN TA +product 3COMUSR HOMECONN 0x009d 3Com HomeConnect Camera +product 3COMUSR USR56K 0x3021 U.S. Robotics 56000 Voice FaxModem Pro + +/* AboCom products */ +product ABOCOM XX1 0x110c XX1 +product ABOCOM XX2 0x200c XX2 +product ABOCOM URE450 0x4000 URE450 Ethernet Adapter +product ABOCOM UFE1000 0x4002 UFE1000 Fast Ethernet Adapter +product ABOCOM DSB650TX_PNA 0x4003 1/10/100 Ethernet Adapter +product ABOCOM XX4 0x4004 XX4 +product ABOCOM XX5 0x4007 XX5 +product ABOCOM XX6 0x400b XX6 +product ABOCOM XX7 0x400c XX7 +product ABOCOM RTL8151 0x401a RTL8151 +product ABOCOM XX8 0x4102 XX8 +product ABOCOM XX9 0x4104 XX9 +product ABOCOM UF200 0x420a UF200 Ethernet +product ABOCOM WL54 0x6001 WL54 +product ABOCOM XX10 0xabc1 XX10 +product ABOCOM BWU613 0xb000 BWU613 +product ABOCOM HWU54DM 0xb21b HWU54DM +product ABOCOM RT2573_2 0xb21c RT2573 +product ABOCOM RT2573_3 0xb21d RT2573 +product ABOCOM RT2573_4 0xb21e RT2573 +product ABOCOM WUG2700 0xb21f WUG2700 + +/* Accton products */ +product ACCTON USB320_EC 0x1046 USB320-EC Ethernet Adapter +product ACCTON 2664W 0x3501 2664W +product ACCTON 111 0x3503 T-Sinus 111 Wireless Adapter +product ACCTON SMCWUSBG 0x4505 SMCWUSB-G +product ACCTON PRISM_GT 0x4521 PrismGT USB 2.0 WLAN +product ACCTON SS1001 0x5046 SpeedStream Ethernet Adapter +product ACCTON ZD1211B 0xe501 ZD1211B + +/* Aceeca products */ +product ACEECA MEZ1000 0x0001 MEZ1000 RDA + +/* Acer Communications & Multimedia (oemd by Surecom) */ +product ACERCM EP1427X2 0x0893 EP-1427X-2 Ethernet Adapter + +/* Acer Labs products */ +product ACERLABS M5632 0x5632 USB 2.0 Data Link + +/* Acer Peripherals, Inc. products */ +product ACERP ACERSCAN_C310U 0x12a6 Acerscan C310U +product ACERP ACERSCAN_320U 0x2022 Acerscan 320U +product ACERP ACERSCAN_640U 0x2040 Acerscan 640U +product ACERP ACERSCAN_620U 0x2060 Acerscan 620U +product ACERP ACERSCAN_4300U 0x20b0 Benq 3300U/4300U +product ACERP ACERSCAN_640BT 0x20be Acerscan 640BT +product ACERP ACERSCAN_1240U 0x20c0 Acerscan 1240U +product ACERP ATAPI 0x6003 ATA/ATAPI Adapter +product ACERP AWL300 0x9000 AWL300 Wireless Adapter +product ACERP AWL400 0x9001 AWL400 Wireless Adapter + +/* Acer Warp products */ +product ACERW WARPLINK 0x0204 Warplink + +/* Actiontec, Inc. products */ +product ACTIONTEC PRISM_25 0x0408 Prism2.5 Wireless Adapter +product ACTIONTEC PRISM_25A 0x0421 Prism2.5 Wireless Adapter A +product ACTIONTEC FREELAN 0x6106 ROPEX FreeLan 802.11b +product ACTIONTEC UAT1 0x7605 UAT1 Wireless Ethernet Adapter + +/* ACTiSYS products */ +product ACTISYS IR2000U 0x0011 ACT-IR2000U FIR + +/* ActiveWire, Inc. products */ +product ACTIVEWIRE IOBOARD 0x0100 I/O Board +product ACTIVEWIRE IOBOARD_FW1 0x0101 I/O Board, rev. 1 firmware + +/* Adaptec products */ +product ADAPTEC AWN8020 0x0020 AWN-8020 WLAN + +/* Addtron products */ +product ADDTRON AWU120 0xff31 AWU-120 + +/* ADMtek products */ +product ADMTEK PEGASUSII_4 0x07c2 AN986A Ethernet +product ADMTEK PEGASUS 0x0986 AN986 Ethernet +product ADMTEK PEGASUSII 0x8511 AN8511 Ethernet +product ADMTEK PEGASUSII_2 0x8513 AN8513 Ethernet +product ADMTEK PEGASUSII_3 0x8515 AN8515 Ethernet + +/* ADDON products */ +/* PNY OEMs these */ +product ADDON ATTACHE 0x1300 USB 2.0 Flash Drive +product ADDON ATTACHE 0x1300 USB 2.0 Flash Drive +product ADDON A256MB 0x1400 Attache 256MB USB 2.0 Flash Drive +product ADDON DISKPRO512 0x1420 USB 2.0 Flash Drive (DANE-ELEC zMate 512MB USB flash drive) + +/* Addonics products */ +product ADDONICS2 CABLE_205 0xa001 Cable 205 + +/* ADS products */ +product ADS UBS10BT 0x0008 UBS-10BT Ethernet +product ADS UBS10BTX 0x0009 UBS-10BT Ethernet + +/* AEI products */ +product AEI FASTETHERNET 0x1701 Fast Ethernet + +/* Agate Technologies products */ +product AGATE QDRIVE 0x0378 Q-Drive + +/* AGFA products */ +product AGFA SNAPSCAN1212U 0x0001 SnapScan 1212U +product AGFA SNAPSCAN1236U 0x0002 SnapScan 1236U +product AGFA SNAPSCANTOUCH 0x0100 SnapScan Touch +product AGFA SNAPSCAN1212U2 0x2061 SnapScan 1212U +product AGFA SNAPSCANE40 0x208d SnapScan e40 +product AGFA SNAPSCANE50 0x208f SnapScan e50 +product AGFA SNAPSCANE20 0x2091 SnapScan e20 +product AGFA SNAPSCANE25 0x2095 SnapScan e25 +product AGFA SNAPSCANE26 0x2097 SnapScan e26 +product AGFA SNAPSCANE52 0x20fd SnapScan e52 + +/* Ain Communication Technology products */ +product AINCOMM AWU2000B 0x1001 AWU2000B Wireless Adapter + +/* AIPTEK products */ +product AIPTEK POCKETCAM3M 0x2011 PocketCAM 3Mega +product SUNPLUS PENCAM_MEGA_1_3 0x504a PenCam Mega 1.3 + +/* AirPrime products */ +product AIRPRIME PC5220 0x0112 CDMA Wireless PC Card + +/* AKS products */ +product AKS USBHASP 0x0001 USB-HASP 0.06 + +/* Alcor Micro, Inc. products */ +product ALCOR2 KBD_HUB 0x2802 Kbd Hub + +product ALCOR MA_KBD_HUB 0x9213 MacAlly Kbd Hub +product ALCOR AU9814 0x9215 AU9814 Hub +product ALCOR UMCR_9361 0x9361 USB Multimedia Card Reader +product ALCOR SM_KBD 0x9410 MicroConnectors/StrongMan Keyboard +product ALCOR NEC_KBD_HUB 0x9472 NEC Kbd Hub + +/* Altec Lansing products */ +product ALTEC ADA70 0x0070 ADA70 Speakers +product ALTEC ASC495 0xff05 ASC495 Speakers + +/* Allied Telesyn International products */ +product ALLIEDTELESYN ATUSB100 0xb100 AT-USB100 + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Ambit Microsystems products */ +product AMBIT WLAN 0x0302 WLAN +product AMBIT NTL_250 0x6098 NTL 250 cable modem + +/* AMIT products */ +product AMIT CGWLUSB2GO 0x0002 CG-WLUSB2GO + +/* Anchor products */ +product ANCHOR EZUSB 0x2131 EZUSB +product ANCHOR EZLINK 0x2720 EZLINK + +/* AnyData products */ +product ANYDATA ADU_E100X 0x6501 CDMA 2000 1xRTT/EV-DO USB Modem +product ANYDATA ADU_500A 0x6502 CDMA 2000 EV-DO USB Modem + +/* AOX, Inc. products */ +product AOX USB101 0x0008 Ethernet + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Apple Computer products */ +product APPLE EXT_KBD 0x020c Apple Extended USB Keyboard +product APPLE OPTMOUSE 0x0302 Optical mouse +product APPLE MIGHTYMOUSE 0x0304 Mighty Mouse +product APPLE EXT_KBD_HUB 0x1003 Hub in Apple Extended USB Keyboard +product APPLE SPEAKERS 0x1101 Speakers +product APPLE IPOD 0x1201 iPod +product APPLE IPOD2G 0x1202 iPod 2G +product APPLE IPOD3G 0x1203 iPod 3G +product APPLE IPOD_04 0x1204 iPod '04' +product APPLE IPODMINI 0x1205 iPod Mini +product APPLE IPOD_06 0x1206 iPod '06' +product APPLE IPOD_07 0x1207 iPod '07' +product APPLE IPOD_08 0x1208 iPod '08' +product APPLE IPODVIDEO 0x1209 iPod Video +product APPLE IPODNANO 0x120a iPod Nano +product APPLE IPHONE 0x1290 iPhone +product APPLE IPHONE_3G 0x1292 iPhone 3G +product APPLE ETHERNET 0x1402 Ethernet A1277 + +/* Arkmicro Technologies */ +product ARKMICRO ARK3116 0x0232 ARK3116 Serial + +/* Asahi Optical products */ +product ASAHIOPTICAL OPTIO230 0x0004 Digital camera +product ASAHIOPTICAL OPTIO330 0x0006 Digital camera + +/* Asante products */ +product ASANTE EA 0x1427 Ethernet + +/* ASIX Electronics products */ +product ASIX AX88172 0x1720 10/100 Ethernet +product ASIX AX88178 0x1780 AX88178 +product ASIX AX88772 0x7720 AX88772 + +/* ASUS products */ +product ASUS WL167G 0x1707 WL-167g Wireless Adapter +product ASUS WL159G 0x170c WL-159g +product ASUS A9T_WIFI 0x171b A9T wireless +product ASUS RT2573_1 0x1723 RT2573 +product ASUS RT2573_2 0x1724 RT2573 +product ASUS LCM 0x1726 LCM display +product ASUS P535 0x420f ASUS P535 PDA + +/* ATen products */ +product ATEN UC1284 0x2001 Parallel printer +product ATEN UC10T 0x2002 10Mbps Ethernet +product ATEN UC110T 0x2007 UC-110T Ethernet +product ATEN UC232A 0x2008 Serial +product ATEN UC210T 0x2009 UC-210T Ethernet +product ATEN DSB650C 0x4000 DSB-650C + +/* Atheros Communications products */ +product ATHEROS AR5523 0x0001 AR5523 +product ATHEROS AR5523_NF 0x0002 AR5523 (no firmware) +product ATHEROS2 AR5523_1 0x0001 AR5523 +product ATHEROS2 AR5523_1_NF 0x0002 AR5523 (no firmware) +product ATHEROS2 AR5523_2 0x0003 AR5523 +product ATHEROS2 AR5523_2_NF 0x0004 AR5523 (no firmware) +product ATHEROS2 AR5523_3 0x0005 AR5523 +product ATHEROS2 AR5523_3_NF 0x0006 AR5523 (no firmware) + +/* Atmel Comp. products */ +product ATMEL UHB124 0x3301 UHB124 hub +product ATMEL DWL120 0x7603 DWL-120 Wireless Adapter +product ATMEL BW002 0x7605 BW002 Wireless Adapter +product ATMEL WL1130USB 0x7613 WL-1130 USB +product ATMEL AT76C505A 0x7614 AT76c505a Wireless Adapter + +/* Avision products */ +product AVISION 1200U 0x0268 1200U scanner + +/* Axesstel products */ +product AXESSTEL DATAMODEM 0x1000 Data Modem + +/* Baltech products */ +product BALTECH CARDREADER 0x9999 Card reader + +/* B&B Electronics products */ +product BBELECTRONICS USOTL4 0xAC01 RS-422/485 + +/* Belkin products */ +/*product BELKIN F5U111 0x???? F5U111 Ethernet*/ +product BELKIN F5D6050 0x0050 F5D6050 802.11b Wireless Adapter +product BELKIN FBT001V 0x0081 FBT001v2 Bluetooth +product BELKIN FBT003V 0x0084 FBT003v2 Bluetooth +product BELKIN F5U103 0x0103 F5U103 Serial +product BELKIN F5U109 0x0109 F5U109 Serial +product BELKIN USB2SCSI 0x0115 USB to SCSI +product BELKIN USB2LAN 0x0121 USB to LAN +product BELKIN F5U208 0x0208 F5U208 VideoBus II +product BELKIN F5U237 0x0237 F5U237 USB 2.0 7-Port Hub +product BELKIN F5U257 0x0257 F5U257 Serial +product BELKIN F5U409 0x0409 F5U409 Serial +product BELKIN F6C550AVR 0x0551 F6C550-AVR UPS +product BELKIN F5U120 0x1203 F5U120-PC Hub +product BELKIN ZD1211B 0x4050 ZD1211B +product BELKIN F5D5055 0x5055 F5D5055 +product BELKIN F5D7050 0x7050 F5D7050 Wireless Adapter +product BELKIN F5D7051 0x7051 F5D7051 54g USB Network Adapter +product BELKIN F5D7050A 0x705a F5D7050A Wireless Adapter +product BELKIN F5D7050_V4000 0x705c F5D7050 v4000 Wireless Adapter +product BELKIN F5D9050V3 0x905b F5D9050 ver 3 Wireless Adapter +product BELKIN2 F5U002 0x0002 F5U002 Parallel printer + +/* Billionton products */ +product BILLIONTON USB100 0x0986 USB100N 10/100 FastEthernet +product BILLIONTON USBLP100 0x0987 USB100LP +product BILLIONTON USBEL100 0x0988 USB100EL +product BILLIONTON USBE100 0x8511 USBE100 +product BILLIONTON USB2AR 0x90ff USB2AR Ethernet + +/* Broadcom products */ +product BROADCOM BCM2033 0x2033 BCM2033 Bluetooth USB dongle + +/* Brother Industries products */ +product BROTHER HL1050 0x0002 HL-1050 laser printer + +/* Behavior Technology Computer products */ +product BTC BTC7932 0x6782 Keyboard with mouse port + +/* Canon, Inc. products */ +product CANON N656U 0x2206 CanoScan N656U +product CANON N1220U 0x2207 CanoScan N1220U +product CANON D660U 0x2208 CanoScan D660U +product CANON N676U 0x220d CanoScan N676U +product CANON N1240U 0x220e CanoScan N1240U +product CANON LIDE25 0x2220 CanoScan LIDE 25 +product CANON S10 0x3041 PowerShot S10 +product CANON S100 0x3045 PowerShot S100 +product CANON S200 0x3065 PowerShot S200 +product CANON REBELXT 0x30ef Digital Rebel XT + +/* CATC products */ +product CATC NETMATE 0x000a Netmate Ethernet +product CATC NETMATE2 0x000c Netmate2 Ethernet +product CATC CHIEF 0x000d USB Chief Bus & Protocol Analyzer +product CATC ANDROMEDA 0x1237 Andromeda hub + +/* CASIO products */ +product CASIO QV_DIGICAM 0x1001 QV DigiCam +product CASIO EXS880 0x1105 Exilim EX-S880 +product CASIO BE300 0x2002 BE-300 PDA +product CASIO NAMELAND 0x4001 CASIO Nameland EZ-USB + +/* CCYU products */ +product CCYU ED1064 0x2136 EasyDisk ED1064 + +/* Century products */ +product CENTURY EX35QUAT 0x011e Century USB Disk Enclosure + +/* Cherry products */ +product CHERRY MY3000KBD 0x0001 My3000 keyboard +product CHERRY MY3000HUB 0x0003 My3000 hub +product CHERRY CYBOARD 0x0004 CyBoard Keyboard + +/* Chic Technology products */ +product CHIC MOUSE1 0x0001 mouse +product CHIC CYPRESS 0x0003 Cypress USB Mouse + +/* Chicony products */ +product CHICONY KB8933 0x0001 KB-8933 keyboard +product MICRODIA TWINKLECAM 0x600d TwinkleCam USB camera + +/* CH Products */ +product CHPRODUCTS PROTHROTTLE 0x00f1 Pro Throttle +product CHPRODUCTS PROPEDALS 0x00f2 Pro Pedals +product CHPRODUCTS FIGHTERSTICK 0x00f3 Fighterstick +product CHPRODUCTS FLIGHTYOKE 0x00ff Flight Sim Yoke + +/* Cisco-Linksys products */ +product CISCOLINKSYS WUSB54G 0x000d WUSB54G Wireless Adapter +product CISCOLINKSYS WUSB54GP 0x0011 WUSB54GP Wireless Adapter +product CISCOLINKSYS USB200MV2 0x0018 USB200M v2 +product CISCOLINKSYS HU200TS 0x001a HU200TS Wireless Adapter +product CISCOLINKSYS WUSB54GC 0x0020 WUSB54GC +product CISCOLINKSYS WUSB54GR 0x0023 WUSB54GR +product CISCOLINKSYS WUSBF54G 0x0024 WUSBF54G + +/* CMOTECH products */ +product CMOTECH CNU510 0x5141 CMOTECH CDMA Technologies USB modem +product CMOTECH CNU550 0x5543 CDMA 2000 1xRTT/1xEVDO USB modem +product CMOTECH CDMA_MODEM1 0x6280 CMOTECH CDMA Technologies USB modem + +/* Compaq products */ +product COMPAQ IPAQPOCKETPC 0x0003 iPAQ PocketPC +product COMPAQ PJB100 0x504a Personal Jukebox PJB100 +product COMPAQ IPAQLINUX 0x505a iPAQ Linux + +/* Composite Corp products looks the same as "TANGTOP" */ +product COMPOSITE USBPS2 0x0001 USB to PS2 Adaptor + +/* Conceptronic products */ +product CONCEPTRONIC PRISM_GT 0x3762 PrismGT USB 2.0 WLAN +product CONCEPTRONIC C11U 0x7100 C11U +product CONCEPTRONIC WL210 0x7110 WL-210 +product CONCEPTRONIC AR5523_1 0x7801 AR5523 +product CONCEPTRONIC AR5523_1_NF 0x7802 AR5523 (no firmware) +product CONCEPTRONIC AR5523_2 0x7811 AR5523 +product CONCEPTRONIC AR5523_2_NF 0x7812 AR5523 (no firmware) +product CONCEPTRONIC2 C54RU 0x3c02 C54RU WLAN +product CONCEPTRONIC2 C54RU2 0x3c22 C54RU + +/* Connectix products */ +product CONNECTIX QUICKCAM 0x0001 QuickCam + +/* Corega products */ +product COREGA ETHER_USB_T 0x0001 Ether USB-T +product COREGA FETHER_USB_TX 0x0004 FEther USB-TX +product COREGA WLAN_USB_USB_11 0x000c WirelessLAN USB-11 +product COREGA FETHER_USB_TXS 0x000d FEther USB-TXS +product COREGA WLANUSB 0x0012 Wireless LAN Stick-11 +product COREGA FETHER_USB2_TX 0x0017 FEther USB2-TX +product COREGA WLUSB_11_KEY 0x001a ULUSB-11 Key +product COREGA CGWLUSB2GL 0x002d CG-WLUSB2GL +product COREGA CGWLUSB2GPX 0x002e CG-WLUSB2GPX +product COREGA WLUSB_11_STICK 0x7613 WLAN USB Stick 11 +product COREGA FETHER_USB_TXC 0x9601 FEther USB-TXC + +/* Creative products */ +product CREATIVE NOMAD_II 0x1002 Nomad II MP3 player +product CREATIVE NOMAD_IIMG 0x4004 Nomad II MG +product CREATIVE NOMAD 0x4106 Nomad +product CREATIVE2 VOIP_BLASTER 0x0258 Voip Blaster +product CREATIVE3 OPTICAL_MOUSE 0x0001 Notebook Optical Mouse + +/* Cambridge Silicon Radio Ltd. products */ +product CSR BT_DONGLE 0x0001 Bluetooth USB dongle +product CSR CSRDFU 0xffff USB Bluetooth Device in DFU State + +/* CTX products */ +product CTX EX1300 0x9999 Ex1300 hub + +/* Curitel products */ +product CURITEL HX550C 0x1101 CDMA 2000 1xRTT USB modem (HX-550C) +product CURITEL HX57XB 0x2101 CDMA 2000 1xRTT USB modem (HX-570/575B/PR-600) +product CURITEL PC5740 0x3701 Broadband Wireless modem + +/* CyberPower products */ +product CYBERPOWER 1500CAVRLCD 0x0501 1500CAVRLCD + +/* CyberTAN Technology products */ +product CYBERTAN TG54USB 0x1666 TG54USB + +/* Cypress Semiconductor products */ +product CYPRESS MOUSE 0x0001 mouse +product CYPRESS THERMO 0x0002 thermometer +product CYPRESS WISPY1A 0x0bad MetaGeek Wi-Spy +product CYPRESS KBDHUB 0x0101 Keyboard/Hub +product CYPRESS FMRADIO 0x1002 FM Radio +product CYPRESS USBRS232 0x5500 USB-RS232 Interface +product CYPRESS SLIM_HUB 0x6560 Slim Hub + +/* Daisy Technology products */ +product DAISY DMC 0x6901 USB MultiMedia Reader + +/* Dallas Semiconductor products */ +product DALLAS J6502 0x4201 J-6502 speakers + +/* Dell products */ +product DELL PORT 0x0058 Port Replicator +product DELL AIO926 0x5115 Photo AIO Printer 926 +product DELL BC02 0x8000 BC02 Bluetooth USB Adapter +product DELL PRISM_GT_1 0x8102 PrismGT USB 2.0 WLAN +product DELL TM350 0x8103 TrueMobile 350 Bluetooth USB Adapter +product DELL PRISM_GT_2 0x8104 PrismGT USB 2.0 WLAN +product DELL U740 0x8135 Dell U740 CDMA + +/* Delorme Paublishing products */ +product DELORME EARTHMATE 0x0100 Earthmate GPS + +/* Desknote products */ +product DESKNOTE UCR_61S2B 0x0c55 UCR-61S2B + +/* Diamond products */ +product DIAMOND RIO500USB 0x0001 Rio 500 USB + +/* Dick Smith Electronics (really C-Net) products */ +product DICKSMITH RT2573 0x9022 RT2573 +product DICKSMITH CWD854F 0x9032 C-Net CWD-854 rev F + +/* Digi International products */ +product DIGI ACCELEPORT2 0x0002 AccelePort USB 2 +product DIGI ACCELEPORT4 0x0004 AccelePort USB 4 +product DIGI ACCELEPORT8 0x0008 AccelePort USB 8 + +/* D-Link products */ +/*product DLINK DSBS25 0x0100 DSB-S25 serial*/ +product DLINK DUBE100 0x1a00 10/100 Ethernet +product DLINK DSB650TX4 0x200c 10/100 Ethernet +product DLINK DWL120E 0x3200 DWL-120 rev E +product DLINK DWL122 0x3700 DWL-122 +product DLINK DWLG120 0x3701 DWL-G120 +product DLINK DWL120F 0x3702 DWL-120 rev F +product DLINK DWLAG132 0x3a00 DWL-AG132 +product DLINK DWLAG132_NF 0x3a01 DWL-AG132 (no firmware) +product DLINK DWLG132 0x3a02 DWL-G132 +product DLINK DWLG132_NF 0x3a03 DWL-G132 (no firmware) +product DLINK DWLAG122 0x3a04 DWL-AG122 +product DLINK DWLAG122_NF 0x3a05 DWL-AG122 (no firmware) +product DLINK DWLG122 0x3c00 DWL-G122 b1 Wireless Adapter +product DLINK DUBE100B1 0x3c05 DUB-E100 rev B1 +product DLINK DSB650C 0x4000 10Mbps Ethernet +product DLINK DSB650TX1 0x4001 10/100 Ethernet +product DLINK DSB650TX 0x4002 10/100 Ethernet +product DLINK DSB650TX_PNA 0x4003 1/10/100 Ethernet +product DLINK DSB650TX3 0x400b 10/100 Ethernet +product DLINK DSB650TX2 0x4102 10/100 Ethernet +product DLINK DSB650 0xabc1 10/100 Ethernet +product DLINK2 DWLG122C1 0x3c03 DWL-G122 c1 +product DLINK2 WUA1340 0x3c04 WUA-1340 +product DLINK2 DWA111 0x3c06 DWA-111 +product DLINK2 DWA110 0x3c07 DWA-110 + +/* DMI products */ +product DMI CFSM_RW 0xa109 CF/SM Reader/Writer + +/* DrayTek products */ +product DRAYTEK VIGOR550 0x0550 Vigor550 + +/* Dynastream Innovations */ +product DYNASTREAM ANTDEVBOARD 0x1003 ANT dev board + +/* EIZO products */ +product EIZO HUB 0x0000 hub +product EIZO MONITOR 0x0001 monitor + +/* ELCON Systemtechnik products */ +product ELCON PLAN 0x0002 Goldpfeil P-LAN + +/* Elecom products */ +product ELECOM MOUSE29UO 0x0002 mouse 29UO +product ELECOM LDUSBTX0 0x200c LD-USB/TX +product ELECOM LDUSBTX1 0x4002 LD-USB/TX +product ELECOM LDUSBLTX 0x4005 LD-USBL/TX +product ELECOM LDUSBTX2 0x400b LD-USB/TX +product ELECOM LDUSB20 0x4010 LD-USB20 +product ELECOM UCSGT 0x5003 UC-SGT +product ELECOM UCSGT0 0x5004 UC-SGT +product ELECOM LDUSBTX3 0xabc1 LD-USB/TX + +/* Elsa products */ +product ELSA MODEM1 0x2265 ELSA Modem Board +product ELSA USB2ETHERNET 0x3000 Microlink USB2Ethernet + +/* EMS products */ +product EMS DUAL_SHOOTER 0x0003 PSX gun controller converter + +/* Entrega products */ +product ENTREGA 1S 0x0001 1S serial +product ENTREGA 2S 0x0002 2S serial +product ENTREGA 1S25 0x0003 1S25 serial +product ENTREGA 4S 0x0004 4S serial +product ENTREGA E45 0x0005 E45 Ethernet +product ENTREGA CENTRONICS 0x0006 Parallel Port +product ENTREGA XX1 0x0008 Ethernet +product ENTREGA 1S9 0x0093 1S9 serial +product ENTREGA EZUSB 0x8000 EZ-USB +/*product ENTREGA SERIAL 0x8001 DB25 Serial*/ +product ENTREGA 2U4S 0x8004 2U4S serial/usb hub +product ENTREGA XX2 0x8005 Ethernet +/*product ENTREGA SERIAL_DB9 0x8093 DB9 Serial*/ + +/* Epson products */ +product EPSON PRINTER1 0x0001 USB Printer +product EPSON PRINTER2 0x0002 ISD USB Smart Cable for Mac +product EPSON PRINTER3 0x0003 ISD USB Smart Cable +product EPSON PRINTER5 0x0005 USB Printer +product EPSON 636 0x0101 Perfection 636U / 636Photo scanner +product EPSON 610 0x0103 Perfection 610 scanner +product EPSON 1200 0x0104 Perfection 1200U / 1200Photo scanner +product EPSON 1600 0x0107 Expression 1600 scanner +product EPSON 1640 0x010a Perfection 1640SU scanner +product EPSON 1240 0x010b Perfection 1240U / 1240Photo scanner +product EPSON 640U 0x010c Perfection 640U scanner +product EPSON 1250 0x010f Perfection 1250U / 1250Photo scanner +product EPSON 1650 0x0110 Perfection 1650 scanner +product EPSON GT9700F 0x0112 GT-9700F scanner +product EPSON GT9300UF 0x011b GT-9300UF scanner +product EPSON 3200 0x011c Perfection 3200 scanner +product EPSON 1260 0x011d Perfection 1260 scanner +product EPSON 1660 0x011e Perfection 1660 scanner +product EPSON 1670 0x011f Perfection 1670 scanner +product EPSON 1270 0x0120 Perfection 1270 scanner +product EPSON 2480 0x0121 Perfection 2480 scanner +product EPSON 3590 0x0122 Perfection 3590 scanner +product EPSON 4990 0x012a Perfection 4990 Photo scanner +product EPSON STYLUS_875DC 0x0601 Stylus Photo 875DC Card Reader +product EPSON STYLUS_895 0x0602 Stylus Photo 895 Card Reader +product EPSON CX5400 0x0808 CX5400 scanner +product EPSON 3500 0x080e CX-3500/3600/3650 MFP +product EPSON RX425 0x080f Stylus Photo RX425 scanner +product EPSON 4800 0x0819 CX4800 MP scanner +product EPSON 4200 0x0820 CX4200 MP scanner +product EPSON 5000 0x082b DX-50x0 MFP scanner +product EPSON 6000 0x082e DX-60x0 MFP scanner +product EPSON DX7400 0x0838 DX7400/CX7300 scanner +product EPSON DX8400 0x0839 DX8400 scanner + +/* e-TEK Labs products */ +product ETEK 1COM 0x8007 Serial + +/* Extended Systems products */ +product EXTENDED XTNDACCESS 0x0100 XTNDAccess IrDA + +/* FEIYA products */ +product FEIYA 5IN1 0x1132 5-in-1 Card Reader + +/* Fiberline */ +product FIBERLINE WL430U 0x6003 WL-430U + +/* Fossil, Inc products */ +product FOSSIL WRISTPDA 0x0002 Wrist PDA + +/* Freecom products */ +product FREECOM DVD 0xfc01 DVD drive + +/* Fujitsu Siemens Computers products */ +product FSC E5400 0x1009 PrismGT USB 2.0 WLAN + +/* Future Technology Devices products */ +product FTDI SERIAL_8U100AX 0x8372 8U100AX Serial +product FTDI SERIAL_8U232AM 0x6001 8U232AM Serial +product FTDI SERIAL_2232C 0x6010 FT2232C Dual port Serial +/* Gude Analog- und Digitalsysteme products also uses FTDI's id: */ +product FTDI TACTRIX_OPENPORT_13M 0xcc48 OpenPort 1.3 Mitsubishi +product FTDI TACTRIX_OPENPORT_13S 0xcc49 OpenPort 1.3 Subaru +product FTDI TACTRIX_OPENPORT_13U 0xcc4a OpenPort 1.3 Universal +product FTDI EISCOU 0xe888 Expert ISDN Control USB +product FTDI UOPTBR 0xe889 USB-RS232 OptoBridge +product FTDI EMCU2D 0xe88a Expert mouseCLOCK USB II +product FTDI PCMSFU 0xe88b Precision Clock MSF USB +product FTDI EMCU2H 0xe88c Expert mouseCLOCK USB II HBG +product FTDI USBSERIAL 0xfa00 Matrix Orbital USB Serial +product FTDI MX2_3 0xfa01 Matrix Orbital MX2 or MX3 +product FTDI MX4_5 0xfa02 Matrix Orbital MX4 or MX5 +product FTDI LK202 0xfa03 Matrix Orbital VK/LK202 Family +product FTDI LK204 0xfa04 Matrix Orbital VK/LK204 Family +product FTDI CFA_632 0xfc08 Crystalfontz CFA-632 USB LCD +product FTDI CFA_634 0xfc09 Crystalfontz CFA-634 USB LCD +product FTDI CFA_633 0xfc0b Crystalfontz CFA-633 USB LCD +product FTDI CFA_631 0xfc0c Crystalfontz CFA-631 USB LCD +product FTDI CFA_635 0xfc0d Crystalfontz CFA-635 USB LCD +product FTDI SEMC_DSS20 0xfc82 SEMC DSS-20 SyncStation + +/* Fuji photo products */ +product FUJIPHOTO MASS0100 0x0100 Mass Storage + +/* Fujitsu protducts */ +product FUJITSU AH_F401U 0x105b AH-F401U Air H device + +/* Garmin products */ +product GARMIN IQUE_3600 0x0004 iQue 3600 + +/* General Instruments (Motorola) products */ +product GENERALINSTMNTS SB5100 0x5100 SURFboard SB5100 Cable modem + +/* Genesys Logic products */ +product GENESYS GL620USB 0x0501 GL620USB Host-Host interface +product GENESYS GL650 0x0604 GL650 Hub +product GENESYS GL641USB 0x0700 GL641USB CompactFlash Card Reader +product GENESYS GL641USB2IDE_2 0x0701 GL641USB USB-IDE Bridge No 2 +product GENESYS GL641USB2IDE 0x0702 GL641USB USB-IDE Bridge +product GENESYS GL641USB_2 0x0760 GL641USB 6-in-1 Card Reader + +/* GIGABYTE products */ +product GIGABYTE GN54G 0x8001 GN-54G +product GIGABYTE GNBR402W 0x8002 GN-BR402W +product GIGABYTE GNWLBM101 0x8003 GN-WLBM101 +product GIGABYTE GNWBKG 0x8007 GN-WBKG +product GIGABYTE GNWB01GS 0x8008 GN-WB01GS +product GIGABYTE GNWI05GS 0x800a GN-WI05GS + +/* Gigaset products */ +product GIGASET WLAN 0x0701 WLAN +product GIGASET SMCWUSBTG 0x0710 SMCWUSBT-G +product GIGASET SMCWUSBTG_NF 0x0711 SMCWUSBT-G (no firmware) +product GIGASET AR5523 0x0712 AR5523 +product GIGASET AR5523_NF 0x0713 AR5523 (no firmware) +product GIGASET RT2573 0x0722 RT2573 + +/* Global Sun Technology product */ +product GLOBALSUN AR5523_1 0x7801 AR5523 +product GLOBALSUN AR5523_1_NF 0x7802 AR5523 (no firmware) +product GLOBALSUN AR5523_2 0x7811 AR5523 +product GLOBALSUN AR5523_2_NF 0x7812 AR5523 (no firmware) + +/* Globespan products */ +product GLOBESPAN PRISM_GT_1 0x2000 PrismGT USB 2.0 WLAN +product GLOBESPAN PRISM_GT_2 0x2002 PrismGT USB 2.0 WLAN + +/* G.Mate, Inc products */ +product GMATE YP3X00 0x1001 YP3X00 PDA + +/* GoHubs products */ +product GOHUBS GOCOM232 0x1001 GoCOM232 Serial + +/* Good Way Technology products */ +product GOODWAY GWUSB2E 0x6200 GWUSB2E +product GOODWAY RT2573 0xc019 RT2573 + +/* Gravis products */ +product GRAVIS GAMEPADPRO 0x4001 GamePad Pro + +/* GREENHOUSE products */ +product GREENHOUSE KANA21 0x0001 CF-writer with MP3 + +/* Griffin Technology */ +product GRIFFIN IMATE 0x0405 iMate, ADB Adapter + +/* Guillemot Corporation */ +product GUILLEMOT DALEADER 0xa300 DA Leader +product GUILLEMOT HWGUSB254 0xe000 HWGUSB2-54 WLAN +product GUILLEMOT HWGUSB254LB 0xe010 HWGUSB2-54-LB +product GUILLEMOT HWGUSB254V2AP 0xe020 HWGUSB2-54V2-AP + +/* Hagiwara products */ +product HAGIWARA FGSM 0x0002 FlashGate SmartMedia Card Reader +product HAGIWARA FGCF 0x0003 FlashGate CompactFlash Card Reader +product HAGIWARA FG 0x0005 FlashGate + +/* HAL Corporation products */ +product HAL IMR001 0x0011 Crossam2+USB IR commander + +/* Handspring, Inc. */ +product HANDSPRING VISOR 0x0100 Handspring Visor +product HANDSPRING TREO 0x0200 Handspring Treo +product HANDSPRING TREO600 0x0300 Handspring Treo 600 + +/* Hauppauge Computer Works */ +product HAUPPAUGE WINTV_USB_FM 0x4d12 WinTV USB FM + +/* Hawking Technologies products */ +product HAWKING UF100 0x400c 10/100 USB Ethernet + +/* Hitachi, Ltd. products */ +product HITACHI DVDCAM_DZ_MV100A 0x0004 DVD-CAM DZ-MV100A Camcorder +product HITACHI DVDCAM_USB 0x001e DVDCAM USB HS Interface + +/* HP products */ +product HP 895C 0x0004 DeskJet 895C +product HP 4100C 0x0101 Scanjet 4100C +product HP S20 0x0102 Photosmart S20 +product HP 880C 0x0104 DeskJet 880C +product HP 4200C 0x0105 ScanJet 4200C +product HP CDWRITERPLUS 0x0107 CD-Writer Plus +product HP KBDHUB 0x010c Multimedia Keyboard Hub +product HP G55XI 0x0111 OfficeJet G55xi +product HP HN210W 0x011c HN210W 802.11b WLAN +product HP 49GPLUS 0x0121 49g+ graphing calculator +product HP 6200C 0x0201 ScanJet 6200C +product HP S20b 0x0202 PhotoSmart S20 +product HP 815C 0x0204 DeskJet 815C +product HP 3300C 0x0205 ScanJet 3300C +product HP CDW8200 0x0207 CD-Writer Plus 8200e +product HP MMKEYB 0x020c Multimedia keyboard +product HP 1220C 0x0212 DeskJet 1220C +product HP 810C 0x0304 DeskJet 810C/812C +product HP 4300C 0x0305 Scanjet 4300C +product HP CDW4E 0x0307 CD-Writer+ CD-4e +product HP G85XI 0x0311 OfficeJet G85xi +product HP 1200 0x0317 LaserJet 1200 +product HP 5200C 0x0401 Scanjet 5200C +product HP 830C 0x0404 DeskJet 830C +product HP 3400CSE 0x0405 ScanJet 3400cse +product HP 6300C 0x0601 Scanjet 6300C +product HP 840C 0x0604 DeskJet 840c +product HP 2200C 0x0605 ScanJet 2200C +product HP 5300C 0x0701 Scanjet 5300C +product HP 4400C 0x0705 Scanjet 4400C +product HP 82x0C 0x0b01 Scanjet 82x0C +product HP 2300D 0x0b17 Laserjet 2300d +product HP 970CSE 0x1004 Deskjet 970Cse +product HP 5400C 0x1005 Scanjet 5400C +product HP 2215 0x1016 iPAQ 22xx/Jornada 548 +product HP 568J 0x1116 Jornada 568 +product HP 930C 0x1204 DeskJet 930c +product HP P2000U 0x1801 Inkjet P-2000U +product HP 640C 0x2004 DeskJet 640c +product HP 4670V 0x3005 ScanJet 4670v +product HP P1100 0x3102 Photosmart P1100 +product HP HN210E 0x811c Ethernet HN210E +product HP2 C500 0x6002 PhotoSmart C500 + +/* HTC products */ +product HTC WINMOBILE 0x00ce HTC USB Sync +product HTC PPC6700MODEM 0x00cf PPC6700 Modem +product HTC SMARTPHONE 0x0a51 SmartPhone USB Sync + +/* HUAWEI products */ +product HUAWEI MOBILE 0x1001 Huawei Mobile +product HUAWEI E270 0x1003 Huawei HSPA modem + +/* HUAWEI 3com products */ +product HUAWEI3COM WUB320G 0x0009 Aolynk WUB320g + +/* IBM Corporation */ +product IBM USBCDROMDRIVE 0x4427 USB CD-ROM Drive + +/* Imagination Technologies products */ +product IMAGINATION DBX1 0x2107 DBX1 DSP core + +/* Inside Out Networks products */ +product INSIDEOUT EDGEPORT4 0x0001 EdgePort/4 serial ports + +/* In-System products */ +product INSYSTEM F5U002 0x0002 Parallel printer +product INSYSTEM ATAPI 0x0031 ATAPI Adapter +product INSYSTEM ISD110 0x0200 IDE Adapter ISD110 +product INSYSTEM ISD105 0x0202 IDE Adapter ISD105 +product INSYSTEM USBCABLE 0x081a USB cable +product INSYSTEM STORAGE_V2 0x5701 USB Storage Adapter V2 + +/* Intel products */ +product INTEL EASYPC_CAMERA 0x0110 Easy PC Camera +product INTEL TESTBOARD 0x9890 82930 test board + +/* Intersil products */ +product INTERSIL PRISM_GT 0x1000 PrismGT USB 2.0 WLAN +product INTERSIL PRISM_2X 0x3642 Prism2.x or Atmel WLAN + +/* Interpid Control Systems products */ +product INTREPIDCS VALUECAN 0x0601 ValueCAN CAN bus interface +product INTREPIDCS NEOVI 0x0701 NeoVI Blue vehicle bus interface + +/* I/O DATA products */ +product IODATA IU_CD2 0x0204 DVD Multi-plus unit iU-CD2 +product IODATA DVR_UEH8 0x0206 DVD Multi-plus unit DVR-UEH8 +product IODATA USBSSMRW 0x0314 USB-SSMRW SD-card +product IODATA USBSDRW 0x031e USB-SDRW SD-card +product IODATA USBETT 0x0901 USB ETT +product IODATA USBETTX 0x0904 USB ETTX +product IODATA USBETTXS 0x0913 USB ETTX +product IODATA USBWNB11A 0x0919 USB WN-B11 +product IODATA USBWNB11 0x0922 USB Airport WN-B11 +product IODATA ETGUS2 0x0930 ETG-US2 +product IODATA USBRSAQ 0x0a03 Serial USB-RSAQ1 +product IODATA2 USB2SC 0x0a09 USB2.0-SCSI Bridge USB2-SC + +/* Iomega products */ +product IOMEGA ZIP100 0x0001 Zip 100 +product IOMEGA ZIP250 0x0030 Zip 250 + +/* Ituner networks products */ +product ITUNERNET USBLCD2X20 0x0002 USB-LCD 2x20 + +/* Jablotron products */ +product JABLOTRON PC60B 0x0001 PC-60B + +/* Jaton products */ +product JATON EDA 0x5704 Ethernet + +/* JVC products */ +product JVC GR_DX95 0x000a GR-DX95 +product JVC MP_PRX1 0x3008 MP-PRX1 Ethernet + +/* JRC products */ +product JRC AH_J3001V_J3002V 0x0001 AirH PHONE AH-J3001V/J3002V + +/* Kawatsu products */ +product KAWATSU MH4000P 0x0003 MiniHub 4000P + +/* Keisokugiken Corp. products */ +product KEISOKUGIKEN USBDAQ 0x0068 HKS-0200 USBDAQ + +/* Kensington products */ +product KENSINGTON ORBIT 0x1003 Orbit USB/PS2 trackball +product KENSINGTON TURBOBALL 0x1005 TurboBall + +/* Keyspan products */ +product KEYSPAN USA28_NF 0x0101 USA-28 serial Adapter (no firmware) +product KEYSPAN USA28X_NF 0x0102 USA-28X serial Adapter (no firmware) +product KEYSPAN USA19_NF 0x0103 USA-19 serial Adapter (no firmware) +product KEYSPAN USA18_NF 0x0104 USA-18 serial Adapter (no firmware) +product KEYSPAN USA18X_NF 0x0105 USA-18X serial Adapter (no firmware) +product KEYSPAN USA19W_NF 0x0106 USA-19W serial Adapter (no firmware) +product KEYSPAN USA19 0x0107 USA-19 serial Adapter +product KEYSPAN USA19W 0x0108 USA-19W serial Adapter +product KEYSPAN USA49W_NF 0x0109 USA-49W serial Adapter (no firmware) +product KEYSPAN USA49W 0x010a USA-49W serial Adapter +product KEYSPAN USA19QI_NF 0x010b USA-19QI serial Adapter (no firmware) +product KEYSPAN USA19QI 0x010c USA-19QI serial Adapter +product KEYSPAN USA19Q_NF 0x010d USA-19Q serial Adapter (no firmware) +product KEYSPAN USA19Q 0x010e USA-19Q serial Adapter +product KEYSPAN USA28 0x010f USA-28 serial Adapter +product KEYSPAN USA28XXB 0x0110 USA-28X/XB serial Adapter +product KEYSPAN USA18 0x0111 USA-18 serial Adapter +product KEYSPAN USA18X 0x0112 USA-18X serial Adapter +product KEYSPAN USA28XB_NF 0x0113 USA-28XB serial Adapter (no firmware) +product KEYSPAN USA28XA_NF 0x0114 USA-28XB serial Adapter (no firmware) +product KEYSPAN USA28XA 0x0115 USA-28XA serial Adapter +product KEYSPAN USA18XA_NF 0x0116 USA-18XA serial Adapter (no firmware) +product KEYSPAN USA18XA 0x0117 USA-18XA serial Adapter +product KEYSPAN USA19QW_NF 0x0118 USA-19WQ serial Adapter (no firmware) +product KEYSPAN USA19QW 0x0119 USA-19WQ serial Adapter +product KEYSPAN USA19HA 0x0121 USA-19HS serial Adapter +product KEYSPAN UIA10 0x0201 UIA-10 remote control +product KEYSPAN UIA11 0x0202 UIA-11 remote control + +/* Kingston products */ +product KINGSTON XX1 0x0008 Ethernet +product KINGSTON KNU101TX 0x000a KNU101TX USB Ethernet + +/* Kawasaki products */ +product KLSI DUH3E10BT 0x0008 USB Ethernet +product KLSI DUH3E10BTN 0x0009 USB Ethernet + +/* Kodak products */ +product KODAK DC220 0x0100 Digital Science DC220 +product KODAK DC260 0x0110 Digital Science DC260 +product KODAK DC265 0x0111 Digital Science DC265 +product KODAK DC290 0x0112 Digital Science DC290 +product KODAK DC240 0x0120 Digital Science DC240 +product KODAK DC280 0x0130 Digital Science DC280 + +/* Konica Corp. Products */ +product KONICA CAMERA 0x0720 Digital Color Camera + +/* KYE products */ +product KYE NICHE 0x0001 Niche mouse +product KYE NETSCROLL 0x0003 Genius NetScroll mouse +product KYE FLIGHT2000 0x1004 Flight 2000 joystick +product KYE VIVIDPRO 0x2001 ColorPage Vivid-Pro scanner + +/* Kyocera products */ +product KYOCERA FINECAM_S3X 0x0100 Finecam S3x +product KYOCERA FINECAM_S4 0x0101 Finecam S4 +product KYOCERA FINECAM_S5 0x0103 Finecam S5 +product KYOCERA FINECAM_L3 0x0105 Finecam L3 +product KYOCERA AHK3001V 0x0203 AH-K3001V +product KYOCERA2 CDMA_MSM_K 0x17da Qualcomm Kyocera CDMA Technologies MSM + +/* LaCie products */ +product LACIE HD 0xa601 Hard Disk +product LACIE CDRW 0xa602 CD R/W + +/* Lexar products */ +product LEXAR JUMPSHOT 0x0001 jumpSHOT CompactFlash Reader +product LEXAR CF_READER 0xb002 USB CF Reader + +/* Lexmark products */ +product LEXMARK S2450 0x0009 Optra S 2450 + +/* Linksys products */ +product LINKSYS MAUSB2 0x0105 Camedia MAUSB-2 +product LINKSYS USB10TX1 0x200c USB10TX +product LINKSYS USB10T 0x2202 USB10T Ethernet +product LINKSYS USB100TX 0x2203 USB100TX Ethernet +product LINKSYS USB100H1 0x2204 USB100H1 Ethernet/HPNA +product LINKSYS USB10TA 0x2206 USB10TA Ethernet +product LINKSYS USB10TX2 0x400b USB10TX +product LINKSYS2 WUSB11 0x2219 WUSB11 Wireless Adapter +product LINKSYS2 USB200M 0x2226 USB 2.0 10/100 Ethernet +product LINKSYS3 WUSB11v28 0x2233 WUSB11 v2.8 Wireless Adapter +product LINKSYS4 USB1000 0x0039 USB1000 + +/* Logitech products */ +product LOGITECH M2452 0x0203 M2452 keyboard +product LOGITECH M4848 0x0301 M4848 mouse +product LOGITECH PAGESCAN 0x040f PageScan +product LOGITECH QUICKCAMWEB 0x0801 QuickCam Web +product LOGITECH QUICKCAMPRO 0x0810 QuickCam Pro +product LOGITECH QUICKCAMEXP 0x0840 QuickCam Express +product LOGITECH QUICKCAM 0x0850 QuickCam +product LOGITECH N43 0xc000 N43 +product LOGITECH N48 0xc001 N48 mouse +product LOGITECH MBA47 0xc002 M-BA47 mouse +product LOGITECH WMMOUSE 0xc004 WingMan Gaming Mouse +product LOGITECH BD58 0xc00c BD58 mouse +product LOGITECH UN58A 0xc030 iFeel Mouse +product LOGITECH UN53B 0xc032 iFeel MouseMan +product LOGITECH WMPAD 0xc208 WingMan GamePad Extreme +product LOGITECH WMRPAD 0xc20a WingMan RumblePad +product LOGITECH WMJOY 0xc281 WingMan Force joystick +product LOGITECH BB13 0xc401 USB-PS/2 Trackball +product LOGITECH RK53 0xc501 Cordless mouse +product LOGITECH RB6 0xc503 Cordless keyboard +product LOGITECH MX700 0xc506 Cordless optical mouse +product LOGITECH QUICKCAMPRO2 0xd001 QuickCam Pro + +/* Logitec Corp. products */ +product LOGITEC LDR_H443SU2 0x0033 DVD Multi-plus unit LDR-H443SU2 +product LOGITEC LDR_H443U2 0x00b3 DVD Multi-plus unit LDR-H443U2 + +/* Lucent products */ +product LUCENT EVALKIT 0x1001 USS-720 evaluation kit + +/* Luwen products */ +product LUWEN EASYDISK 0x0005 EasyDisc + +/* Macally products */ +product MACALLY MOUSE1 0x0101 mouse + +/* MCT Corp. */ +product MCT HUB0100 0x0100 Hub +product MCT DU_H3SP_USB232 0x0200 D-Link DU-H3SP USB BAY Hub +product MCT USB232 0x0210 USB-232 Interface +product MCT SITECOM_USB232 0x0230 Sitecom USB-232 Products + +/* Melco, Inc products */ +product MELCO LUATX1 0x0001 LUA-TX Ethernet +product MELCO LUATX5 0x0005 LUA-TX Ethernet +product MELCO LUA2TX5 0x0009 LUA2-TX Ethernet +product MELCO LUAKTX 0x0012 LUA-KTX Ethernet +product MELCO DUBPXXG 0x001c USB-IDE Bridge: DUB-PxxG +product MELCO LUAU2KTX 0x003d LUA-U2-KTX Ethernet +product MELCO KG54YB 0x005e WLI-U2-KG54-YB WLAN +product MELCO KG54 0x0066 WLI-U2-KG54 WLAN +product MELCO KG54AI 0x0067 WLI-U2-KG54-AI WLAN +product MELCO NINWIFI 0x008b Nintendo Wi-Fi +product MELCO PCOPRS1 0x00b3 PC-OP-RS1 RemoteStation +product MELCO SG54HP 0x00d8 WLI-U2-SG54HP +product MELCO G54HP 0x00d9 WLI-U2-G54HP +product MELCO KG54L 0x00da WLI-U2-KG54L + +/* Merlin products */ +product MERLIN V620 0x1110 Merlin V620 + +/* MetaGeek products */ +product METAGEEK WISPY1B 0x083e MetaGeek Wi-Spy +product METAGEEK WISPY24X 0x083f MetaGeek Wi-Spy 2.4x + +/* Metricom products */ +product METRICOM RICOCHET_GS 0x0001 Ricochet GS + +/* MGE UPS Systems */ +product MGE UPS1 0x0001 MGE UPS SYSTEMS PROTECTIONCENTER 1 +product MGE UPS2 0xffff MGE UPS SYSTEMS PROTECTIONCENTER 2 + +/* Micro Star International products */ +product MSI BT_DONGLE 0x1967 Bluetooth USB dongle +product MSI UB11B 0x6823 UB11B +product MSI RT2570 0x6861 RT2570 +product MSI RT2570_2 0x6865 RT2570 +product MSI RT2570_3 0x6869 RT2570 +product MSI RT2573_1 0x6874 RT2573 +product MSI RT2573_2 0x6877 RT2573 +product MSI RT2573_3 0xa861 RT2573 +product MSI RT2573_4 0xa874 RT2573 + +/* Microdia products */ +product MICRODIA TWINKLECAM 0x600d TwinkleCam USB camera + +/* Microsoft products */ +product MICROSOFT SIDEPREC 0x0008 SideWinder Precision Pro +product MICROSOFT INTELLIMOUSE 0x0009 IntelliMouse +product MICROSOFT NATURALKBD 0x000b Natural Keyboard Elite +product MICROSOFT DDS80 0x0014 Digital Sound System 80 +product MICROSOFT SIDEWINDER 0x001a Sidewinder Precision Racing Wheel +product MICROSOFT INETPRO 0x001c Internet Keyboard Pro +product MICROSOFT TBEXPLORER 0x0024 Trackball Explorer +product MICROSOFT INTELLIEYE 0x0025 IntelliEye mouse +product MICROSOFT INETPRO2 0x002b Internet Keyboard Pro +product MICROSOFT MN510 0x006e MN510 Wireless +product MICROSOFT MN110 0x007a 10/100 USB NIC +product MICROSOFT WLINTELLIMOUSE 0x008c Wireless Optical IntelliMouse +product MICROSOFT WLNOTEBOOK 0x00b9 Wireless Optical Mouse (Model 1023) +product MICROSOFT WLNOTEBOOK2 0x00e1 Wireless Optical Mouse 3000 (Model 1056) +product MICROSOFT WLNOTEBOOK3 0x00d2 Wireless Optical Mouse 3000 (Model 1049) +product MICROSOFT WLUSBMOUSE 0x00b9 Wireless USB Mouse +product MICROSOFT XBOX360 0x0292 XBOX 360 WLAN + +/* Microtech products */ +product MICROTECH SCSIDB25 0x0004 USB-SCSI-DB25 +product MICROTECH SCSIHD50 0x0005 USB-SCSI-HD50 +product MICROTECH DPCM 0x0006 USB CameraMate +product MICROTECH FREECOM 0xfc01 Freecom USB-IDE + +/* Microtek products */ +product MICROTEK 336CX 0x0094 Phantom 336CX - C3 scanner +product MICROTEK X6U 0x0099 ScanMaker X6 - X6U +product MICROTEK C6 0x009a Phantom C6 scanner +product MICROTEK 336CX2 0x00a0 Phantom 336CX - C3 scanner +product MICROTEK V6USL 0x00a3 ScanMaker V6USL +product MICROTEK V6USL2 0x80a3 ScanMaker V6USL +product MICROTEK V6UL 0x80ac ScanMaker V6UL + +/* Microtune, Inc. products */ +product MICROTUNE BT_DONGLE 0x1000 Bluetooth USB dongle + +/* Midiman products */ +product MIDIMAN MIDISPORT2X2 0x1001 Midisport 2x2 + +/* MindsAtWork products */ +product MINDSATWORK WALLET 0x0001 Digital Wallet + +/* Minolta Co., Ltd. */ +product MINOLTA 2300 0x4001 Dimage 2300 +product MINOLTA S304 0x4007 Dimage S304 +product MINOLTA X 0x4009 Dimage X +product MINOLTA 5400 0x400e Dimage 5400 +product MINOLTA F300 0x4011 Dimage F300 +product MINOLTA E223 0x4017 Dimage E223 + +/* Mitsumi products */ +product MITSUMI CDRRW 0x0000 CD-R/RW Drive +product MITSUMI BT_DONGLE 0x641f Bluetooth USB dongle +product MITSUMI FDD 0x6901 USB FDD + +/* Mobility products */ +product MOBILITY EA 0x0204 Ethernet +product MOBILITY EASIDOCK 0x0304 EasiDock Ethernet + +/* MosChip products */ +product MOSCHIP MCS7703 0x7703 MCS7703 Serial Port Adapter +product MOSCHIP MCS7830 0x7830 MCS7830 Ethernet + +/* Motorola products */ +product MOTOROLA MC141555 0x1555 MC141555 hub controller +product MOTOROLA SB4100 0x4100 SB4100 USB Cable Modem +product MOTOROLA2 A41XV32X 0x2a22 A41x/V32x Mobile Phones +product MOTOROLA2 E398 0x4810 E398 Mobile Phone +product MOTOROLA2 USBLAN 0x600c USBLAN +product MOTOROLA2 USBLAN2 0x6027 USBLAN + +/* MultiTech products */ +product MULTITECH ATLAS 0xf101 MT5634ZBA-USB modem + +/* Mustek products */ +product MUSTEK 1200CU 0x0001 1200 CU scanner +product MUSTEK 600CU 0x0002 600 CU scanner +product MUSTEK 1200USB 0x0003 1200 USB scanner +product MUSTEK 1200UB 0x0006 1200 UB scanner +product MUSTEK 1200USBPLUS 0x0007 1200 USB Plus scanner +product MUSTEK 1200CUPLUS 0x0008 1200 CU Plus scanner +product MUSTEK BEARPAW1200F 0x0010 BearPaw 1200F scanner +product MUSTEK BEARPAW1200TA 0x021e BearPaw 1200TA scanner +product MUSTEK 600USB 0x0873 600 USB scanner +product MUSTEK MDC800 0xa800 MDC-800 digital camera + +/* M-Systems products */ +product MSYSTEMS DISKONKEY 0x0010 DiskOnKey +product MSYSTEMS DISKONKEY2 0x0011 DiskOnKey + +/* Myson products */ +product MYSON HEDEN 0x8818 USB-IDE + +/* National Semiconductor */ +product NATIONAL BEARPAW1200 0x1000 BearPaw 1200 +product NATIONAL BEARPAW2400 0x1001 BearPaw 2400 + +/* NEC products */ +product NEC HUB 0x55aa hub +product NEC HUB_B 0x55ab hub + +/* NEODIO products */ +product NEODIO ND3260 0x3260 8-in-1 Multi-format Flash Controller +product NEODIO ND5010 0x5010 Multi-format Flash Controller + +/* Netac products */ +product NETAC CF_CARD 0x1060 USB-CF-Card +product NETAC ONLYDISK 0x0003 OnlyDisk + +/* NetChip Technology Products */ +product NETCHIP TURBOCONNECT 0x1080 Turbo-Connect +product NETCHIP CLIK_40 0xa140 USB Clik! 40 +product NETCHIP ETHERNETGADGET 0xa4a2 Linux Ethernet/RNDIS gadget on pxa210/25x/26x + +/* Netgear products */ +product NETGEAR EA101 0x1001 Ethernet +product NETGEAR EA101X 0x1002 Ethernet +product NETGEAR FA101 0x1020 Ethernet 10/100, USB1.1 +product NETGEAR FA120 0x1040 USB 2.0 Ethernet +product NETGEAR WG111V2_2 0x4240 PrismGT USB 2.0 WLAN +product NETGEAR WG111U 0x4300 WG111U +product NETGEAR WG111U_NF 0x4301 WG111U (no firmware) +product NETGEAR2 MA101 0x4100 MA101 +product NETGEAR2 MA101B 0x4102 MA101 Rev B +product NETGEAR3 WG111T 0x4250 WG111T +product NETGEAR3 WG111T_NF 0x4251 WG111T (no firmware) +product NETGEAR3 WPN111 0x5f00 WPN111 +product NETGEAR3 WPN111_NF 0x5f01 WPN111 (no firmware) + +/* Nikon products */ +product NIKON E990 0x0102 Digital Camera E990 +product NIKON LS40 0x4000 CoolScan LS40 ED +product NIKON D300 0x041a Digital Camera D300 + +/* NovaTech Products */ +product NOVATECH NV902 0x9020 NovaTech NV-902W +product NOVATECH RT2573 0x9021 RT2573 + +/* Novatel Wireless products */ +product NOVATEL V640 0x1100 Merlin V620 +product NOVATEL CDMA_MODEM 0x1110 Novatel Wireless Merlin CDMA +product NOVATEL V620 0x1110 Merlin V620 +product NOVATEL V740 0x1120 Merlin V740 +product NOVATEL V720 0x1130 Merlin V720 +product NOVATEL U740 0x1400 Merlin U740 +product NOVATEL U740_2 0x1410 Merlin U740 +product NOVATEL U870 0x1420 Merlin U870 +product NOVATEL XU870 0x1430 Merlin XU870 +product NOVATEL X950D 0x1450 Merlin X950D +product NOVATEL ES620 0x2100 ES620 CDMA +product NOVATEL U720 0x2110 Merlin U720 +product NOVATEL U727 0x4100 Merlin U727 CDMA +product NOVATEL U950D 0x4400 Novatel MC950D HSUPA +product NOVATEL ZEROCD 0x5010 Novatel ZeroCD +product NOVATEL2 FLEXPACKGPS 0x0100 NovAtel FlexPack GPS receiver + +/* Merlin products */ +product MERLIN V620 0x1110 Merlin V620 + +/* Olympus products */ +product OLYMPUS C1 0x0102 C-1 Digital Camera +product OLYMPUS C700 0x0105 C-700 Ultra Zoom + +/* OmniVision Technologies, Inc. products */ +product OMNIVISION OV511 0x0511 OV511 Camera +product OMNIVISION OV511PLUS 0xa511 OV511+ Camera + +/* OnSpec Electronic, Inc. */ +product ONSPEC MDCFE_B_CF_READER 0xa000 MDCFE-B USB CF Reader +product ONSPEC CFMS_RW 0xa001 SIIG/Datafab Memory Stick+CF Reader/Writer +product ONSPEC READER 0xa003 Datafab-based Reader +product ONSPEC CFSM_READER 0xa005 PNY/Datafab CF+SM Reader +product ONSPEC CFSM_READER2 0xa006 Simple Tech/Datafab CF+SM Reader +product ONSPEC MDSM_B_READER 0xa103 MDSM-B reader +product ONSPEC CFSM_COMBO 0xa109 USB to CF + SM Combo (LC1) +product ONSPEC UCF100 0xa400 FlashLink UCF-100 CompactFlash Reader +product ONSPEC2 IMAGEMATE_SDDR55 0xa103 ImageMate SDDR55 + +/* Option products */ +product OPTION VODAFONEMC3G 0x5000 Vodafone Mobile Connect 3G datacard +product OPTION GT3G 0x6000 GlobeTrotter 3G datacard +product OPTION GT3GQUAD 0x6300 GlobeTrotter 3G QUAD datacard +product OPTION GT3GPLUS 0x6600 GlobeTrotter 3G+ datacard +product OPTION GTMAX36 0x6701 GlobeTrotter Max 3.6 Modem + +/* OQO */ +product OQO WIFI01 0x0002 model 01 WiFi interface +product OQO BT01 0x0003 model 01 Bluetooth interface +product OQO ETHER01PLUS 0x7720 model 01+ Ethernet +product OQO ETHER01 0x8150 model 01 Ethernet interface + +/* Palm Computing, Inc. product */ +product PALM SERIAL 0x0080 USB Serial +product PALM M500 0x0001 Palm m500 +product PALM M505 0x0002 Palm m505 +product PALM M515 0x0003 Palm m515 +product PALM I705 0x0020 Palm i705 +product PALM TUNGSTEN_Z 0x0031 Palm Tungsten Z +product PALM M125 0x0040 Palm m125 +product PALM M130 0x0050 Palm m130 +product PALM TUNGSTEN_T 0x0060 Palm Tungsten T +product PALM ZIRE31 0x0061 Palm Zire 31 +product PALM ZIRE 0x0070 Palm Zire + +/* Panasonic products */ +product PANASONIC LS120CAM 0x0901 LS-120 Camera +product PANASONIC KXL840AN 0x0d01 CD-R Drive KXL-840AN +product PANASONIC KXLRW32AN 0x0d09 CD-R Drive KXL-RW32AN +product PANASONIC KXLCB20AN 0x0d0a CD-R Drive KXL-CB20AN +product PANASONIC KXLCB35AN 0x0d0e DVD-ROM & CD-R/RW +product PANASONIC SDCAAE 0x1b00 MultiMediaCard + +/* Peracom products */ +product PERACOM SERIAL1 0x0001 Serial +product PERACOM ENET 0x0002 Ethernet +product PERACOM ENET3 0x0003 At Home Ethernet +product PERACOM ENET2 0x0005 Ethernet + +/* Philips products */ +product PHILIPS DSS350 0x0101 DSS 350 Digital Speaker System +product PHILIPS DSS 0x0104 DSS XXX Digital Speaker System +product PHILIPS HUB 0x0201 hub +product PHILIPS PCA646VC 0x0303 PCA646VC PC Camera +product PHILIPS PCVC680K 0x0308 PCVC680K Vesta Pro PC Camera +product PHILIPS DSS150 0x0471 DSS 150 Digital Speaker System +product PHILIPS SNU5600 0x1236 SNU5600 +product PHILIPS UM10016 0x1552 ISP 1581 Hi-Speed USB MPEG2 Encoder Reference Kit +product PHILIPS DIVAUSB 0x1801 DIVA USB mp3 player + +/* Philips Semiconductor products */ +product PHILIPSSEMI HUB1122 0x1122 hub + +/* P.I. Engineering products */ +product PIENGINEERING PS2USB 0x020b PS2 to Mac USB Adapter + +/* Planex Communications products */ +product PLANEX GW_US11H 0x14ea GW-US11H WLAN +product PLANEX2 GW_US11S 0x3220 GW-US11S WLAN +product PLANEX2 GW_US54GXS 0x5303 GW-US54GXS WLAN +product PLANEX2 GWUS54HP 0xab01 GW-US54HP +product PLANEX2 GWUS54MINI2 0xab50 GW-US54Mini2 +product PLANEX2 GWUS54SG 0xc002 GW-US54SG +product PLANEX2 GWUS54GZL 0xc007 GW-US54GZL +product PLANEX2 GWUS54GD 0xed01 GW-US54GD +product PLANEX2 GWUSMM 0xed02 GW-USMM +product PLANEX3 GWUS54GZ 0xab10 GW-US54GZ +product PLANEX3 GU1000T 0xab11 GU-1000T +product PLANEX3 GWUS54MINI 0xab13 GW-US54Mini + +/* Plextor Corp. */ +product PLEXTOR 40_12_40U 0x0011 PlexWriter 40/12/40U + +/* PLX products */ +product PLX TESTBOARD 0x9060 test board + +/* PNY products */ +product PNY ATTACHE2 0x0010 USB 2.0 Flash Drive + +/* PortGear products */ +product PORTGEAR EA8 0x0008 Ethernet +product PORTGEAR EA9 0x0009 Ethernet + +/* Portsmith products */ +product PORTSMITH EEA 0x3003 Express Ethernet + +/* Primax products */ +product PRIMAX G2X300 0x0300 G2-200 scanner +product PRIMAX G2E300 0x0301 G2E-300 scanner +product PRIMAX G2300 0x0302 G2-300 scanner +product PRIMAX G2E3002 0x0303 G2E-300 scanner +product PRIMAX 9600 0x0340 Colorado USB 9600 scanner +product PRIMAX 600U 0x0341 Colorado 600u scanner +product PRIMAX 6200 0x0345 Visioneer 6200 scanner +product PRIMAX 19200 0x0360 Colorado USB 19200 scanner +product PRIMAX 1200U 0x0361 Colorado 1200u scanner +product PRIMAX G600 0x0380 G2-600 scanner +product PRIMAX 636I 0x0381 ReadyScan 636i +product PRIMAX G2600 0x0382 G2-600 scanner +product PRIMAX G2E600 0x0383 G2E-600 scanner +product PRIMAX COMFORT 0x4d01 Comfort +product PRIMAX MOUSEINABOX 0x4d02 Mouse-in-a-Box +product PRIMAX PCGAUMS1 0x4d04 Sony PCGA-UMS1 + +/* Prolific products */ +product PROLIFIC PL2301 0x0000 PL2301 Host-Host interface +product PROLIFIC PL2302 0x0001 PL2302 Host-Host interface +product PROLIFIC RSAQ2 0x04bb PL2303 Serial (IODATA USB-RSAQ2) +product PROLIFIC PL2303 0x2303 PL2303 Serial (ATEN/IOGEAR UC232A) +product PROLIFIC PL2305 0x2305 Parallel printer +product PROLIFIC ATAPI4 0x2307 ATAPI-4 Controller +product PROLIFIC PL2501 0x2501 PL2501 Host-Host interface +product PROLIFIC PHAROS 0xaaa0 Prolific Pharos +product PROLIFIC RSAQ3 0xaaa2 PL2303 Serial Adapter (IODATA USB-RSAQ3) +product PROLIFIC2 WSIM 0x2001 Willcom WSIM + +/* Putercom products */ +product PUTERCOM UPA100 0x047e USB-1284 BRIDGE + +/* Qcom products */ +product QCOM RT2573 0x6196 RT2573 +product QCOM RT2573_2 0x6229 RT2573 + +/* Qualcomm products */ +product QUALCOMM CDMA_MSM 0x6000 CDMA Technologies MSM phone +product QUALCOMM2 RWT_FCT 0x3100 RWT FCT-CDMA 2000 1xRTT modem +product QUALCOMM2 CDMA_MSM 0x3196 CDMA Technologies MSM modem +product QUALCOMMINC CDMA_MSM 0x0001 CDMA Technologies MSM modem + +/* Qtronix products */ +product QTRONIX 980N 0x2011 Scorpion-980N keyboard + +/* Quickshot products */ +product QUICKSHOT STRIKEPAD 0x6238 USB StrikePad + +/* Radio Shack */ +product RADIOSHACK USBCABLE 0x4026 USB to Serial Cable + +/* Rainbow Technologies products */ +product RAINBOW IKEY2000 0x1200 i-Key 2000 + +/* Ralink Technology products */ +product RALINK RT2570 0x1706 RT2500USB Wireless Adapter +product RALINK RT2570_2 0x2570 RT2500USB Wireless Adapter +product RALINK RT2573 0x2573 RT2501USB Wireless Adapter +product RALINK RT2671 0x2671 RT2601USB Wireless Adapter +product RALINK RT2570_3 0x9020 RT2500USB Wireless Adapter +product RALINK RT2573_2 0x9021 RT2501USB Wireless Adapter + +/* ReakTek products */ +/* Green House and CompUSA OEM this part */ +product REALTEK USBKR100 0x8150 USBKR100 USB Ethernet + +/* Ricoh products */ +product RICOH VGPVCC2 0x1830 VGP-VCC2 Camera +product RICOH VGPVCC3 0x1832 VGP-VCC3 Camera +product RICOH VGPVCC2_2 0x1833 VGP-VCC2 Camera +product RICOH VGPVCC2_3 0x1834 VGP-VCC2 Camera +product RICOH VGPVCC7 0x183a VGP-VCC7 Camera +product RICOH VGPVCC8 0x183b VGP-VCC8 Camera + +/* Roland products */ +product ROLAND UM1 0x0009 UM-1 MIDI I/F +product ROLAND UM880N 0x0014 EDIROL UM-880 MIDI I/F (native) +product ROLAND UM880G 0x0015 EDIROL UM-880 MIDI I/F (generic) + +/* Rockfire products */ +product ROCKFIRE GAMEPAD 0x2033 gamepad 203USB + +/* RATOC Systems products */ +product RATOC REXUSB60 0xb000 REX-USB60 + +/* Sagem products */ +product SAGEM USBSERIAL 0x0027 USB-Serial Controller +product SAGEM XG760A 0x004a XG-760A +product SAGEM XG76NA 0x0062 XG-76NA + +/* Samsung products */ +product SAMSUNG ML6060 0x3008 ML-6060 laser printer +product SAMSUNG YP_U2 0x5050 YP-U2 MP3 Player +product SAMSUNG I500 0x6601 I500 Palm USB Phone + +/* Samsung Techwin products */ +product SAMSUNG_TECHWIN DIGIMAX_410 0x000a Digimax 410 + +/* SanDisk products */ +product SANDISK SDDR05A 0x0001 ImageMate SDDR-05a +product SANDISK SDDR31 0x0002 ImageMate SDDR-31 +product SANDISK SDDR05 0x0005 ImageMate SDDR-05 +product SANDISK SDDR12 0x0100 ImageMate SDDR-12 +product SANDISK SDDR09 0x0200 ImageMate SDDR-09 +product SANDISK SDDR75 0x0810 ImageMate SDDR-75 +product SANDISK SDCZ2_256 0x7104 Cruzer Mini 256MB +product SANDISK SDCZ4_128 0x7112 Cruzer Micro 128MB +product SANDISK SDCZ4_256 0x7113 Cruzer Micro 256MB + +/* Sanyo Electric products */ +product SANYO SCP4900 0x0701 Sanyo SCP-4900 USB Phone + +/* ScanLogic products */ +product SCANLOGIC SL11R 0x0002 SL11R IDE Adapter +product SCANLOGIC 336CX 0x0300 Phantom 336CX - C3 scanner + +/* Senao products */ +product SENAO NUB8301 0x2000 NUB-8301 + +/* ShanTou products */ +product SHANTOU ST268 0x0268 ST268 +product SHANTOU DM9601 0x9601 DM 9601 + +/* Shark products */ +product SHARK PA 0x0400 Pocket Adapter + +/* Sharp products */ +product SHARP SL5500 0x8004 Zaurus SL-5500 PDA +product SHARP SLA300 0x8005 Zaurus SL-A300 PDA +product SHARP SL5600 0x8006 Zaurus SL-5600 PDA +product SHARP SLC700 0x8007 Zaurus SL-C700 PDA +product SHARP SLC750 0x9031 Zaurus SL-C750 PDA +product SHARP WZERO3ES 0x9123 W-ZERO3 ES Smartphone + +/* Shuttle Technology products */ +product SHUTTLE EUSB 0x0001 E-USB Bridge +product SHUTTLE EUSCSI 0x0002 eUSCSI Bridge +product SHUTTLE SDDR09 0x0003 ImageMate SDDR09 +product SHUTTLE EUSBCFSM 0x0005 eUSB SmartMedia / CompactFlash Adapter +product SHUTTLE ZIOMMC 0x0006 eUSB MultiMediaCard Adapter +product SHUTTLE HIFD 0x0007 Sony Hifd +product SHUTTLE EUSBATAPI 0x0009 eUSB ATA/ATAPI Adapter +product SHUTTLE CF 0x000a eUSB CompactFlash Adapter +product SHUTTLE EUSCSI_B 0x000b eUSCSI Bridge +product SHUTTLE EUSCSI_C 0x000c eUSCSI Bridge +product SHUTTLE CDRW 0x0101 CD-RW Device +product SHUTTLE EUSBORCA 0x0325 eUSB ORCA Quad Reader + +/* Siemens products */ +product SIEMENS SPEEDSTREAM 0x1001 SpeedStream +product SIEMENS SPEEDSTREAM22 0x1022 SpeedStream 1022 +product SIEMENS2 WLL013 0x001b WLL013 +product SIEMENS2 ES75 0x0034 GSM module MC35 +product SIEMENS2 WL54G 0x3c06 54g USB Network Adapter +product SIEMENS3 SX1 0x0001 SX1 +product SIEMENS3 X65 0x0003 X65 +product SIEMENS3 X75 0x0004 X75 + +/* Sierra Wireless products */ +product SIERRA AIRCARD580 0x0112 Sierra Wireless AirCard 580 +product SIERRA AIRCARD595 0x0019 Sierra Wireless AirCard 595 +product SIERRA AC595U 0x0120 Sierra Wireless AirCard 595U +product SIERRA AC597E 0x0021 Sierra Wireless AirCard 597E +product SIERRA C597 0x0023 Sierra Wireless Compass 597 +product SIERRA AC880 0x6850 Sierra Wireless AirCard 880 +product SIERRA AC881 0x6851 Sierra Wireless AirCard 881 +product SIERRA AC880E 0x6852 Sierra Wireless AirCard 880E +product SIERRA AC881E 0x6853 Sierra Wireless AirCard 881E +product SIERRA AC880U 0x6855 Sierra Wireless AirCard 880U +product SIERRA AC881U 0x6856 Sierra Wireless AirCard 881U +product SIERRA EM5625 0x0017 EM5625 +product SIERRA MC5720 0x0218 MC5720 Wireless Modem +product SIERRA MC5720_2 0x0018 MC5720 +product SIERRA MC5725 0x0020 MC5725 +product SIERRA MINI5725 0x0220 Sierra Wireless miniPCI 5275 +product SIERRA MC8755_2 0x6802 MC8755 +product SIERRA MC8765 0x6803 MC8765 +product SIERRA MC8755 0x6804 MC8755 +product SIERRA AC875U 0x6812 AC875U HSDPA USB Modem +product SIERRA MC8755_3 0x6813 MC8755 HSDPA +product SIERRA MC8775_2 0x6815 MC8775 +product SIERRA AIRCARD875 0x6820 Aircard 875 HSDPA +product SIERRA MC8780 0x6832 MC8780 +product SIERRA MC8781 0x6833 MC8781 +product SIERRA TRUINSTALL 0x0fff Aircard Tru Installer + +/* Sigmatel products */ +product SIGMATEL I_BEAD100 0x8008 i-Bead 100 MP3 Player + +/* SIIG products */ +/* Also: Omnidirectional Control Technology products */ +product SIIG DIGIFILMREADER 0x0004 DigiFilm-Combo Reader +product SIIG WINTERREADER 0x0330 WINTERREADER Reader +product SIIG2 USBTOETHER 0x0109 USB TO Ethernet +product SIIG2 US2308 0x0421 Serial + +/* Silicom products */ +product SILICOM U2E 0x0001 U2E +product SILICOM GPE 0x0002 Psion Gold Port Ethernet + +/* SI Labs */ +product SILABS POLOLU 0x803b Pololu Serial +product SILABS ARGUSISP 0x8066 Argussoft ISP +product SILABS CRUMB128 0x807a Crumb128 board +product SILABS DEGREE 0x80ca Degree Controls Inc +product SILABS TRAQMATE 0x80ed Track Systems Traqmate +product SILABS SUUNTO 0x80f6 Suunto Sports Instrument +product SILABS BURNSIDE 0x813d Burnside Telecon Deskmobile +product SILABS HELICOM 0x815e Helicomm IP-Link 1220-DVM +product SILABS CP2102 0xea60 SILABS USB UART +product SILABS LIPOWSKY_JTAG 0x81c8 Lipowsky Baby-JTAG +product SILABS LIPOWSKY_LIN 0x81e2 Lipowsky Baby-LIN +product SILABS LIPOWSKY_HARP 0x8218 Lipowsky HARP-1 +product SILABS CP2102 0xea60 SILABS USB UARTa +product SILABS CP210X_2 0xea61 CP210x Serial +product SILABS2 DCU11CLONE 0xaa26 DCU-11 clone + +/* Silicon Portals Inc. */ +product SILICONPORTALS YAPPH_NF 0x0200 YAP Phone (no firmware) +product SILICONPORTALS YAPPHONE 0x0201 YAP Phone + +/* Sirius Technologies products */ +product SIRIUS ROADSTER 0x0001 NetComm Roadster II 56 USB + +/* Sitecom products */ +product SITECOM LN029 0x182d USB 2.0 Ethernet +product SITECOM SERIAL 0x2068 USB to serial cable (v2) +product SITECOM2 WL022 0x182d WL-022 + +/* Sitecom Europe products */ +product SITECOMEU LN028 0x061c LN-028 +product SITECOMEU WL113 0x9071 WL-113 +product SITECOMEU ZD1211B 0x9075 ZD1211B +product SITECOMEU WL172 0x90ac WL-172 +product SITECOMEU WL113R2 0x9712 WL-113 rev 2 + +/* Skanhex Technology products */ +product SKANHEX MD_7425 0x410a MD 7425 Camera +product SKANHEX SX_520Z 0x5200 SX 520z Camera + +/* SmartBridges products */ +product SMARTBRIDGES SMARTLINK 0x0001 SmartLink USB Ethernet +product SMARTBRIDGES SMARTNIC 0x0003 smartNIC 2 PnP Ethernet + +/* SMC products */ +product SMC 2102USB 0x0100 10Mbps Ethernet +product SMC 2202USB 0x0200 10/100 Ethernet +product SMC 2206USB 0x0201 EZ Connect USB Ethernet +product SMC 2862WG 0xee13 EZ Connect Wireless Adapter +product SMC2 2020HUB 0x2020 USB Hub +product SMC3 2662WUSB 0xa002 2662W-AR Wireless + +/* SOHOware products */ +product SOHOWARE NUB100 0x9100 10/100 USB Ethernet +product SOHOWARE NUB110 0x9110 10/100 USB Ethernet + +/* SOLID YEAR products */ +product SOLIDYEAR KEYBOARD 0x2101 Solid Year USB keyboard + +/* SONY products */ +product SONY DSC 0x0010 DSC cameras +product SONY MS_NW_MS7 0x0025 Memorystick NW-MS7 +product SONY PORTABLE_HDD_V2 0x002b Portable USB Harddrive V2 +product SONY MSACUS1 0x002d Memorystick MSAC-US1 +product SONY HANDYCAM 0x002e Handycam +product SONY MSC 0x0032 MSC memory stick slot +product SONY CLIE_35 0x0038 Sony Clie v3.5 +product SONY MS_PEG_N760C 0x0058 PEG N760c Memorystick +product SONY CLIE_40 0x0066 Sony Clie v4.0 +product SONY MS_MSC_U03 0x0069 Memorystick MSC-U03 +product SONY CLIE_40_MS 0x006d Sony Clie v4.0 Memory Stick slot +product SONY CLIE_S360 0x0095 Sony Clie s360 +product SONY CLIE_41_MS 0x0099 Sony Clie v4.1 Memory Stick slot +product SONY CLIE_41 0x009a Sony Clie v4.1 +product SONY CLIE_NX60 0x00da Sony Clie nx60 +product SONY CLIE_TH55 0x0144 Sony Clie th55 +product SONY CLIE_TJ37 0x0169 Sony Clie tj37 + +/* Sony Ericsson products */ +product SONYERICSSON DCU10 0x0528 USB Cable + +/* SOURCENEXT products */ +product SOURCENEXT KEIKAI8 0x039f KeikaiDenwa 8 +product SOURCENEXT KEIKAI8_CHG 0x012e KeikaiDenwa 8 with charger + +/* SparkLAN products */ +product SPARKLAN RT2573 0x0004 RT2573 + +/* Sphairon Access Systems GmbH products */ +product SPHAIRON UB801R 0x0110 UB801R + +/* STMicroelectronics products */ +product STMICRO BIOCPU 0x2016 Biometric Coprocessor +product STMICRO COMMUNICATOR 0x7554 USB Communicator + +/* STSN products */ +product STSN STSN0001 0x0001 Internet Access Device + +/* SUN Corporation products */ +product SUNTAC DS96L 0x0003 SUNTAC U-Cable type D2 +product SUNTAC PS64P1 0x0005 SUNTAC U-Cable type P1 +product SUNTAC VS10U 0x0009 SUNTAC Slipper U +product SUNTAC IS96U 0x000a SUNTAC Ir-Trinity +product SUNTAC AS64LX 0x000b SUNTAC U-Cable type A3 +product SUNTAC AS144L4 0x0011 SUNTAC U-Cable type A4 + +/* Sun Microsystems products */ +product SUN KEYBOARD 0x0005 Type 6 USB keyboard +/* XXX The above is a North American PC style keyboard possibly */ +product SUN MOUSE 0x0100 Type 6 USB mouse + +/* Supra products */ +product DIAMOND2 SUPRAEXPRESS56K 0x07da Supra Express 56K modem +product DIAMOND2 SUPRA2890 0x0b4a SupraMax 2890 56K Modem +product DIAMOND2 RIO600USB 0x5001 Rio 600 USB +product DIAMOND2 RIO800USB 0x5002 Rio 800 USB + +/* Surecom Technology products */ +product SURECOM RT2570 0x11f3 RT2570 +product SURECOM RT2573 0x31f3 RT2573 + +/* Sweex products */ +product SWEEX ZD1211 0x1809 ZD1211 + +/* System TALKS, Inc. */ +product SYSTEMTALKS SGCX2UL 0x1920 SGC-X2UL + +/* Tapwave products */ +product TAPWAVE ZODIAC 0x0100 Zodiac + +/* Taugagreining products */ +product TAUGA CAMERAMATE 0x0005 CameraMate (DPCM_USB) + +/* TDK products */ +product TDK UPA9664 0x0115 USB-PDC Adapter UPA9664 +product TDK UCA1464 0x0116 USB-cdmaOne Adapter UCA1464 +product TDK UHA6400 0x0117 USB-PHS Adapter UHA6400 +product TDK UPA6400 0x0118 USB-PHS Adapter UPA6400 +product TDK BT_DONGLE 0x0309 Bluetooth USB dongle + +/* TEAC products */ +product TEAC FD05PUB 0x0000 FD-05PUB floppy + +/* Tekram Technology products */ +product TEKRAM QUICKWLAN 0x1630 QuickWLAN +product TEKRAM ZD1211_1 0x5630 ZD1211 +product TEKRAM ZD1211_2 0x6630 ZD1211 + +/* Telex Communications products */ +product TELEX MIC1 0x0001 Enhanced USB Microphone + +/* Ten X Technology, Inc. */ +product TENX UAUDIO0 0xf211 USB audio headset + +/* Texas Intel products */ +product TI UTUSB41 0x1446 UT-USB41 hub +product TI TUSB2046 0x2046 TUSB2046 hub + +/* Thrustmaster products */ +product THRUST FUSION_PAD 0xa0a3 Fusion Digital Gamepad + +/* Topre Corporation products */ +product TOPRE HHKB 0x0100 HHKB Professional + +/* Toshiba Corporation products */ +product TOSHIBA POCKETPC_E740 0x0706 PocketPC e740 + +/* Trek Technology products */ +product TREK THUMBDRIVE 0x1111 ThumbDrive +product TREK MEMKEY 0x8888 IBM USB Memory Key +product TREK THUMBDRIVE_8MB 0x9988 ThumbDrive_8MB + +/* Tripp-Lite products */ +product TRIPPLITE U209 0x2008 Serial + +/* Trumpion products */ +product TRUMPION T33520 0x1001 T33520 USB Flash Card Controller +product TRUMPION C3310 0x1100 Comotron C3310 MP3 player +product TRUMPION MP3 0x1200 MP3 player + +/* TwinMOS */ +product TWINMOS G240 0xa006 G240 +product TWINMOS MDIV 0x1325 Memory Disk IV + +/* Ubiquam products */ +product UBIQUAM UALL 0x3100 CDMA 1xRTT USB Modem (U-100/105/200/300/520) + +/* Ultima products */ +product ULTIMA 1200UBPLUS 0x4002 1200 UB Plus scanner + +/* UMAX products */ +product UMAX ASTRA1236U 0x0002 Astra 1236U Scanner +product UMAX ASTRA1220U 0x0010 Astra 1220U Scanner +product UMAX ASTRA2000U 0x0030 Astra 2000U Scanner +product UMAX ASTRA2100U 0x0130 Astra 2100U Scanner +product UMAX ASTRA2200U 0x0230 Astra 2200U Scanner +product UMAX ASTRA3400 0x0060 Astra 3400 Scanner + +/* U-MEDIA Communications products */ +product UMEDIA TEW444UBEU 0x3006 TEW-444UB EU +product UMEDIA TEW444UBEU_NF 0x3007 TEW-444UB EU (no firmware) +product UMEDIA TEW429UB_A 0x300a TEW-429UB_A +product UMEDIA TEW429UB 0x300b TEW-429UB +product UMEDIA TEW429UBC1 0x300d TEW-429UB C1 +product UMEDIA ALL0298V2 0x3204 ALL0298 v2 +product UMEDIA AR5523_2 0x3205 AR5523 +product UMEDIA AR5523_2_NF 0x3206 AR5523 (no firmware) + +/* Universal Access products */ +product UNIACCESS PANACHE 0x0101 Panache Surf USB ISDN Adapter + +/* U.S. Robotics products */ +product USR USR5423 0x0121 USR5423 WLAN + +/* VIA Technologies products */ +product VIA USB2IDEBRIDGE 0x6204 USB 2.0 IDE Bridge + +/* USI products */ +product USI MC60 0x10c5 MC60 Serial + +/* VidzMedia products */ +product VIDZMEDIA MONSTERTV 0x4fb1 MonsterTV P2H + +/* Vision products */ +product VISION VC6452V002 0x0002 CPiA Camera + +/* Visioneer products */ +product VISIONEER 7600 0x0211 OneTouch 7600 +product VISIONEER 5300 0x0221 OneTouch 5300 +product VISIONEER 3000 0x0224 Scanport 3000 +product VISIONEER 6100 0x0231 OneTouch 6100 +product VISIONEER 6200 0x0311 OneTouch 6200 +product VISIONEER 8100 0x0321 OneTouch 8100 +product VISIONEER 8600 0x0331 OneTouch 8600 + +/* Vivitar products */ +product VIVITAR 35XX 0x0003 Vivicam 35Xx + +/* VTech products */ +product VTECH RT2570 0x3012 RT2570 +product VTECH ZD1211B 0x3014 ZD1211B + +/* Wacom products */ +product WACOM CT0405U 0x0000 CT-0405-U Tablet +product WACOM GRAPHIRE 0x0010 Graphire +product WACOM GRAPHIRE3_4X5 0x0013 Graphire 3 4x5 +product WACOM INTUOSA5 0x0021 Intuos A5 +product WACOM GD0912U 0x0022 Intuos 9x12 Graphics Tablet +/* WCH products*/ +product WCH CH341SER 0x5523 CH341/CH340 USB-Serial Bridge +/* Western Digital products */ +product WESTERN COMBO 0x0200 Firewire USB Combo +product WESTERN EXTHDD 0x0400 External HDD +product WESTERN HUB 0x0500 USB HUB +product WESTERN MYBOOK 0x0901 MyBook External HDD + +/* Windbond Electronics */ +product WINBOND UH104 0x5518 4-port USB Hub + +/* WinMaxGroup products */ +product WINMAXGROUP FLASH64MC 0x6660 USB Flash Disk 64M-C + +/* Wistron NeWeb products */ +product WISTRONNEWEB UR045G 0x0427 PrismGT USB 2.0 WLAN +product WISTRONNEWEB UR055G 0x0711 UR055G +product WISTRONNEWEB AR5523_1 0x0826 AR5523 +product WISTRONNEWEB AR5523_1_NF 0x0827 AR5523 (no firmware) +product WISTRONNEWEB AR5523_2 0x082a AR5523 +product WISTRONNEWEB AR5523_2_NF 0x0829 AR5523 (no firmware) + +/* Xerox products */ +product XEROX WCM15 0xffef WorkCenter M15 + +/* Xirlink products */ +product XIRLINK PCCAM 0x8080 IBM PC Camera + +/* Xyratex products */ +product XYRATEX PRISM_GT_1 0x2000 PrismGT USB 2.0 WLAN +product XYRATEX PRISM_GT_2 0x2002 PrismGT USB 2.0 WLAN + +/* Y-E Data products */ +product YEDATA FLASHBUSTERU 0x0000 Flashbuster-U + +/* Yamaha products */ +product YAMAHA UX256 0x1000 UX256 MIDI I/F +product YAMAHA UX96 0x1008 UX96 MIDI I/F +product YAMAHA RTA54I 0x4000 NetVolante RTA54i Broadband&ISDN Router +product YAMAHA RTA55I 0x4004 NetVolante RTA55i Broadband VoIP Router +product YAMAHA RTW65B 0x4001 NetVolante RTW65b Broadband Wireless Router +product YAMAHA RTW65I 0x4002 NetVolante RTW65i Broadband&ISDN Wireless Router + +/* Yano products */ +product YANO U640MO 0x0101 U640MO-03 +product YANO FW800HD 0x05fc METALWEAR-HDD + +/* Z-Com products */ +product ZCOM M4Y750 0x0001 M4Y-750 +product ZCOM XI725 0x0002 XI-725/726 +product ZCOM XI735 0x0005 XI-735 +product ZCOM XG703A 0x0008 PrismGT USB 2.0 WLAN +product ZCOM ZD1211 0x0011 ZD1211 +product ZCOM AR5523 0x0012 AR5523 +product ZCOM AR5523_NF 0x0013 AR5523 driver (no firmware) +product ZCOM ZD1211B 0x001a ZD1211B + +/* Zinwell products */ +product ZINWELL RT2570 0x0260 RT2570 + +/* Zoom Telephonics, Inc. products */ +product ZOOM 2986L 0x9700 2986L Fax modem + +/* Zoran Microelectronics products */ +product ZORAN EX20DSC 0x4343 Digital Camera EX-20 DSC + +/* Zydas Technology Corporation products */ +product ZYDAS ZD1211 0x1211 ZD1211 WLAN abg +product ZYDAS ZD1211B 0x1215 ZD1211B + +/* ZyXEL Communication Co. products */ +product ZYXEL OMNI56K 0x1500 Omni 56K Plus +product ZYXEL 980N 0x2011 Scorpion-980N keyboard +product ZYXEL ZYAIRG220 0x3401 ZyAIR G-220 +product ZYXEL G200V2 0x3407 G-200 v2 +product ZYXEL AG225H 0x3409 AG-225H +product ZYXEL M202 0x340a M-202 +product ZYXEL G220V2 0x340f G-220 v2 +product ZYXEL G202 0x3410 G-202 diff --git a/sys/dev/usb2/ethernet/if_aue2.c b/sys/dev/usb2/ethernet/if_aue2.c new file mode 100644 index 000000000000..3c05eaa2d74c --- /dev/null +++ b/sys/dev/usb2/ethernet/if_aue2.c @@ -0,0 +1,1567 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * ADMtek AN986 Pegasus and AN8511 Pegasus II USB to ethernet driver. + * Datasheet is available from http://www.admtek.com.tw. + * + * Written by Bill Paul + * Electrical Engineering Department + * Columbia University, New York City + */ + +/* + * The Pegasus chip uses four USB "endpoints" to provide 10/100 ethernet + * support: the control endpoint for reading/writing registers, burst + * read endpoint for packet reception, burst write for packet transmission + * and one for "interrupts." The chip uses the same RX filter scheme + * as the other ADMtek ethernet parts: one perfect filter entry for the + * the station address and a 64-bit multicast hash table. The chip supports + * both MII and HomePNA attachments. + * + * Since the maximum data transfer speed of USB is supposed to be 12Mbps, + * you're never really going to get 100Mbps speeds from this device. I + * think the idea is to allow the device to connect to 10 or 100Mbps + * networks, not necessarily to provide 100Mbps performance. Also, since + * the controller uses an external PHY chip, it's possible that board + * designers might simply choose a 10Mbps PHY. + * + * Registers are accessed using usb2_do_request(). Packet transfers are + * done using usb2_transfer() and friends. + */ + +/* + * NOTE: all function names beginning like "aue_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include + +#define usb2_config_td_cc usb2_ether_cc +#define usb2_config_td_softc aue_softc + +#define USB_DEBUG_VAR aue_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MODULE_DEPEND(aue, usb2_ethernet, 1, 1, 1); +MODULE_DEPEND(aue, usb2_core, 1, 1, 1); +MODULE_DEPEND(aue, ether, 1, 1, 1); +MODULE_DEPEND(aue, miibus, 1, 1, 1); + +#if USB_DEBUG +static int aue_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, aue, CTLFLAG_RW, 0, "USB aue"); +SYSCTL_INT(_hw_usb2_aue, OID_AUTO, debug, CTLFLAG_RW, &aue_debug, 0, + "Debug level"); +#endif + +/* + * Various supported device vendors/products. + */ +static const struct usb2_device_id aue_devs[] = { + {USB_VPI(USB_VENDOR_3COM, USB_PRODUCT_3COM_3C460B, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_DSB650TX_PNA, 0)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_UFE1000, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX10, 0)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX1, AUE_FLAG_PNA | AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX2, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX4, AUE_FLAG_PNA)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX5, AUE_FLAG_PNA)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX6, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX7, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX8, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX9, AUE_FLAG_PNA)}, + {USB_VPI(USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_SS1001, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_USB320_EC, 0)}, + {USB_VPI(USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_2, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_3, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_4, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUS, AUE_FLAG_PNA | AUE_FLAG_DUAL_PHY)}, + {USB_VPI(USB_VENDOR_AEI, USB_PRODUCT_AEI_FASTETHERNET, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ALLIEDTELESYN, USB_PRODUCT_ALLIEDTELESYN_ATUSB100, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC110T, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_USB2LAN, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USB100, 0)}, + {USB_VPI(USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBE100, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBEL100, 0)}, + {USB_VPI(USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBLP100, AUE_FLAG_PNA)}, + {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXS, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TX, 0)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX1, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX2, AUE_FLAG_LSYS | AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX3, AUE_FLAG_LSYS | AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX4, AUE_FLAG_LSYS | AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX_PNA, AUE_FLAG_PNA)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_ELCON, USB_PRODUCT_ELCON_PLAN, AUE_FLAG_PNA | AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSB20, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBLTX, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX0, 0)}, + {USB_VPI(USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX1, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX2, 0)}, + {USB_VPI(USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX3, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_ELSA, USB_PRODUCT_ELSA_USB2ETHERNET, 0)}, + {USB_VPI(USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNBR402W, 0)}, + {USB_VPI(USB_VENDOR_HAWKING, USB_PRODUCT_HAWKING_UF100, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_HN210E, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETTXS, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETTX, 0)}, + {USB_VPI(USB_VENDOR_KINGSTON, USB_PRODUCT_KINGSTON_KNU101TX, 0)}, + {USB_VPI(USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB100H1, AUE_FLAG_LSYS | AUE_FLAG_PNA)}, + {USB_VPI(USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB100TX, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TA, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TX1, AUE_FLAG_LSYS | AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TX2, AUE_FLAG_LSYS | AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10T, AUE_FLAG_LSYS)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUA2TX5, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUATX1, 0)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUATX5, 0)}, + {USB_VPI(USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_MN110, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_FA101, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_SIEMENS, USB_PRODUCT_SIEMENS_SPEEDSTREAM, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_SIIG2, USB_PRODUCT_SIIG2_USBTOETHER, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_SMARTBRIDGES, USB_PRODUCT_SMARTBRIDGES_SMARTNIC, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_SMC, USB_PRODUCT_SMC_2202USB, 0)}, + {USB_VPI(USB_VENDOR_SMC, USB_PRODUCT_SMC_2206USB, AUE_FLAG_PII)}, + {USB_VPI(USB_VENDOR_SOHOWARE, USB_PRODUCT_SOHOWARE_NUB100, 0)}, + {USB_VPI(USB_VENDOR_SOHOWARE, USB_PRODUCT_SOHOWARE_NUB110, AUE_FLAG_PII)}, +}; + +/* prototypes */ + +static device_probe_t aue_probe; +static device_attach_t aue_attach; +static device_detach_t aue_detach; +static device_shutdown_t aue_shutdown; + +static usb2_callback_t aue_intr_clear_stall_callback; +static usb2_callback_t aue_intr_callback; +static usb2_callback_t aue_bulk_read_clear_stall_callback; +static usb2_callback_t aue_bulk_read_callback; +static usb2_callback_t aue_bulk_write_clear_stall_callback; +static usb2_callback_t aue_bulk_write_callback; + +static void aue_cfg_do_request(struct aue_softc *sc, struct usb2_device_request *req, void *data); +static uint8_t aue_cfg_csr_read_1(struct aue_softc *sc, uint16_t reg); +static uint16_t aue_cfg_csr_read_2(struct aue_softc *sc, uint16_t reg); +static void aue_cfg_csr_write_1(struct aue_softc *sc, uint16_t reg, uint8_t val); +static void aue_cfg_csr_write_2(struct aue_softc *sc, uint16_t reg, uint16_t val); +static void aue_cfg_eeprom_getword(struct aue_softc *sc, uint8_t addr, uint8_t *dest); +static void aue_cfg_read_eeprom(struct aue_softc *sc, uint8_t *dest, uint16_t off, uint16_t len); + +static miibus_readreg_t aue_cfg_miibus_readreg; +static miibus_writereg_t aue_cfg_miibus_writereg; +static miibus_statchg_t aue_cfg_miibus_statchg; + +static usb2_config_td_command_t aue_cfg_setmulti; +static usb2_config_td_command_t aue_cfg_first_time_setup; +static usb2_config_td_command_t aue_config_copy; +static usb2_config_td_command_t aue_cfg_tick; +static usb2_config_td_command_t aue_cfg_pre_init; +static usb2_config_td_command_t aue_cfg_init; +static usb2_config_td_command_t aue_cfg_promisc_upd; +static usb2_config_td_command_t aue_cfg_ifmedia_upd; +static usb2_config_td_command_t aue_cfg_pre_stop; +static usb2_config_td_command_t aue_cfg_stop; + +static void aue_cfg_reset_pegasus_II(struct aue_softc *sc); +static void aue_cfg_reset(struct aue_softc *sc); +static void aue_start_cb(struct ifnet *ifp); +static void aue_init_cb(void *arg); +static void aue_start_transfers(struct aue_softc *sc); +static int aue_ifmedia_upd_cb(struct ifnet *ifp); +static void aue_ifmedia_sts_cb(struct ifnet *ifp, struct ifmediareq *ifmr); +static int aue_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data); +static void aue_watchdog(void *arg); + +static const struct usb2_config aue_config[AUE_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = (MCLBYTES + 2), + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &aue_bulk_write_callback, + .mh.timeout = 10000, /* 10 seconds */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = (MCLBYTES + 4 + ETHER_CRC_LEN), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &aue_bulk_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &aue_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &aue_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &aue_intr_callback, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &aue_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static device_method_t aue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aue_probe), + DEVMETHOD(device_attach, aue_attach), + DEVMETHOD(device_detach, aue_detach), + DEVMETHOD(device_shutdown, aue_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, aue_cfg_miibus_readreg), + DEVMETHOD(miibus_writereg, aue_cfg_miibus_writereg), + DEVMETHOD(miibus_statchg, aue_cfg_miibus_statchg), + + {0, 0} +}; + +static driver_t aue_driver = { + .name = "aue", + .methods = aue_methods, + .size = sizeof(struct aue_softc) +}; + +static devclass_t aue_devclass; + +DRIVER_MODULE(aue, ushub, aue_driver, aue_devclass, NULL, 0); +DRIVER_MODULE(miibus, aue, miibus_driver, miibus_devclass, 0, 0); + +static void +aue_cfg_do_request(struct aue_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTF("device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +#define AUE_CFG_SETBIT(sc, reg, x) \ + aue_cfg_csr_write_1(sc, reg, aue_cfg_csr_read_1(sc, reg) | (x)) + +#define AUE_CFG_CLRBIT(sc, reg, x) \ + aue_cfg_csr_write_1(sc, reg, aue_cfg_csr_read_1(sc, reg) & ~(x)) + +static uint8_t +aue_cfg_csr_read_1(struct aue_softc *sc, uint16_t reg) +{ + struct usb2_device_request req; + uint8_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = AUE_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + aue_cfg_do_request(sc, &req, &val); + return (val); +} + +static uint16_t +aue_cfg_csr_read_2(struct aue_softc *sc, uint16_t reg) +{ + struct usb2_device_request req; + uint16_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = AUE_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + aue_cfg_do_request(sc, &req, &val); + return (le16toh(val)); +} + +static void +aue_cfg_csr_write_1(struct aue_softc *sc, uint16_t reg, uint8_t val) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = AUE_UR_WRITEREG; + req.wValue[0] = val; + req.wValue[1] = 0; + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + aue_cfg_do_request(sc, &req, &val); + return; +} + +static void +aue_cfg_csr_write_2(struct aue_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = AUE_UR_WRITEREG; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + val = htole16(val); + + aue_cfg_do_request(sc, &req, &val); + return; +} + +/* + * Read a word of data stored in the EEPROM at address 'addr.' + */ +static void +aue_cfg_eeprom_getword(struct aue_softc *sc, uint8_t addr, + uint8_t *dest) +{ + uint16_t i; + + aue_cfg_csr_write_1(sc, AUE_EE_REG, addr); + aue_cfg_csr_write_1(sc, AUE_EE_CTL, AUE_EECTL_READ); + + for (i = 0;; i++) { + + if (i < AUE_TIMEOUT) { + + if (aue_cfg_csr_read_1(sc, AUE_EE_CTL) & AUE_EECTL_DONE) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + break; + } + } else { + DPRINTF("EEPROM read timed out!\n"); + break; + } + } + + i = aue_cfg_csr_read_2(sc, AUE_EE_DATA); + + dest[0] = (i & 0xFF); + dest[1] = (i >> 8); + + return; +} + +/* + * Read a sequence of words from the EEPROM. + */ +static void +aue_cfg_read_eeprom(struct aue_softc *sc, uint8_t *dest, + uint16_t off, uint16_t len) +{ + uint16_t i; + + for (i = 0; i < len; i++) { + aue_cfg_eeprom_getword(sc, off + i, dest + (i * 2)); + } + return; +} + +static int +aue_cfg_miibus_readreg(device_t dev, int phy, int reg) +{ + struct aue_softc *sc = device_get_softc(dev); + uint16_t i; + uint8_t do_unlock; + + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + /* + * The Am79C901 HomePNA PHY actually contains + * two transceivers: a 1Mbps HomePNA PHY and a + * 10Mbps full/half duplex ethernet PHY with + * NWAY autoneg. However in the ADMtek adapter, + * only the 1Mbps PHY is actually connected to + * anything, so we ignore the 10Mbps one. It + * happens to be configured for MII address 3, + * so we filter that out. + */ + if (sc->sc_flags & AUE_FLAG_DUAL_PHY) { + + if (phy == 3) { + i = 0; + goto done; + } +#if 0 + if (phy != 1) { + i = 0; + goto done; + } +#endif + } + aue_cfg_csr_write_1(sc, AUE_PHY_ADDR, phy); + aue_cfg_csr_write_1(sc, AUE_PHY_CTL, reg | AUE_PHYCTL_READ); + + for (i = 0;; i++) { + + if (i < AUE_TIMEOUT) { + + if (aue_cfg_csr_read_1(sc, AUE_PHY_CTL) & AUE_PHYCTL_DONE) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + break; + } + } else { + DPRINTF("MII read timed out\n"); + break; + } + } + + i = aue_cfg_csr_read_2(sc, AUE_PHY_DATA); + +done: + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return (i); +} + +static int +aue_cfg_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct aue_softc *sc = device_get_softc(dev); + uint16_t i; + uint8_t do_unlock; + + if (phy == 3) { + return (0); + } + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + aue_cfg_csr_write_2(sc, AUE_PHY_DATA, data); + aue_cfg_csr_write_1(sc, AUE_PHY_ADDR, phy); + aue_cfg_csr_write_1(sc, AUE_PHY_CTL, reg | AUE_PHYCTL_WRITE); + + for (i = 0;; i++) { + + if (i < AUE_TIMEOUT) { + if (aue_cfg_csr_read_1(sc, AUE_PHY_CTL) & AUE_PHYCTL_DONE) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + break; + } + } else { + DPRINTF("MII write timed out\n"); + break; + } + } + + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return (0); +} + +static void +aue_cfg_miibus_statchg(device_t dev) +{ + struct aue_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + uint8_t do_unlock; + + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + AUE_CFG_CLRBIT(sc, AUE_CTL0, AUE_CTL0_RX_ENB | AUE_CTL0_TX_ENB); + + if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) { + AUE_CFG_SETBIT(sc, AUE_CTL1, AUE_CTL1_SPEEDSEL); + } else { + AUE_CFG_CLRBIT(sc, AUE_CTL1, AUE_CTL1_SPEEDSEL); + } + + if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) { + AUE_CFG_SETBIT(sc, AUE_CTL1, AUE_CTL1_DUPLEX); + } else { + AUE_CFG_CLRBIT(sc, AUE_CTL1, AUE_CTL1_DUPLEX); + } + + AUE_CFG_SETBIT(sc, AUE_CTL0, AUE_CTL0_RX_ENB | AUE_CTL0_TX_ENB); + + /* + * Set the LED modes on the LinkSys adapter. + * This turns on the 'dual link LED' bin in the auxmode + * register of the Broadcom PHY. + */ + if (sc->sc_flags & AUE_FLAG_LSYS) { + uint16_t auxmode; + + auxmode = aue_cfg_miibus_readreg(dev, 0, 0x1b); + aue_cfg_miibus_writereg(dev, 0, 0x1b, auxmode | 0x04); + } + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return; +} + +static void +aue_cfg_setmulti(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint16_t i; + + if ((cc->if_flags & IFF_ALLMULTI) || + (cc->if_flags & IFF_PROMISC)) { + AUE_CFG_SETBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI); + return; + } + AUE_CFG_CLRBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI); + + /* clear existing ones */ + for (i = 0; i < 8; i++) { + aue_cfg_csr_write_1(sc, AUE_MAR0 + i, 0); + } + + /* now program new ones */ + for (i = 0; i < 8; i++) { + aue_cfg_csr_write_1(sc, AUE_MAR0 + i, cc->if_hash[i]); + } + return; +} + +static void +aue_cfg_reset_pegasus_II(struct aue_softc *sc) +{ + /* Magic constants taken from Linux driver. */ + aue_cfg_csr_write_1(sc, AUE_REG_1D, 0); + aue_cfg_csr_write_1(sc, AUE_REG_7B, 2); +#if 0 + if ((sc->sc_flags & HAS_HOME_PNA) && mii_mode) + aue_cfg_csr_write_1(sc, AUE_REG_81, 6); + else +#endif + aue_cfg_csr_write_1(sc, AUE_REG_81, 2); + + return; +} + +static void +aue_cfg_reset(struct aue_softc *sc) +{ + uint16_t i; + + AUE_CFG_SETBIT(sc, AUE_CTL1, AUE_CTL1_RESETMAC); + + for (i = 0;; i++) { + + if (i < AUE_TIMEOUT) { + + if (!(aue_cfg_csr_read_1(sc, AUE_CTL1) & AUE_CTL1_RESETMAC)) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + break; + } + } else { + DPRINTF("reset timed out\n"); + break; + } + } + + /* + * The PHY(s) attached to the Pegasus chip may be held + * in reset until we flip on the GPIO outputs. Make sure + * to set the GPIO pins high so that the PHY(s) will + * be enabled. + * + * Note: We force all of the GPIO pins low first, *then* + * enable the ones we want. + */ + aue_cfg_csr_write_1(sc, AUE_GPIO0, (AUE_GPIO_OUT0 | AUE_GPIO_SEL0)); + aue_cfg_csr_write_1(sc, AUE_GPIO0, (AUE_GPIO_OUT0 | AUE_GPIO_SEL0 | + AUE_GPIO_SEL1)); + + if (sc->sc_flags & AUE_FLAG_LSYS) { + /* Grrr. LinkSys has to be different from everyone else. */ + aue_cfg_csr_write_1(sc, AUE_GPIO0, + (AUE_GPIO_SEL0 | AUE_GPIO_SEL1)); + aue_cfg_csr_write_1(sc, AUE_GPIO0, + (AUE_GPIO_SEL0 | + AUE_GPIO_SEL1 | + AUE_GPIO_OUT0)); + } + if (sc->sc_flags & AUE_FLAG_PII) { + aue_cfg_reset_pegasus_II(sc); + } + /* wait a little while for the chip to get its brains in order: */ + usb2_config_td_sleep(&sc->sc_config_td, hz / 100); + + return; +} + +/* + * Probe for a Pegasus chip. + */ +static int +aue_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != AUE_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != AUE_IFACE_IDX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(aue_devs, sizeof(aue_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +aue_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct aue_softc *sc = device_get_softc(dev); + int32_t error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + + if (uaa->info.bcdDevice >= 0x0201) { + sc->sc_flags |= AUE_FLAG_VER_2; /* XXX currently undocumented */ + } + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "aue lock", NULL, MTX_DEF | MTX_RECURSE); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + iface_index = AUE_IFACE_IDX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, aue_config, AUE_ENDPT_MAX, + sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + NULL, sizeof(struct usb2_config_td_cc), 16); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + sc->sc_flags |= AUE_FLAG_WAIT_LINK; + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &aue_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + aue_watchdog(sc); + + return (0); /* success */ + +detach: + aue_detach(dev); + return (ENXIO); /* failure */ +} + +static void +aue_cfg_first_time_setup(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp; + int error; + uint8_t eaddr[min(ETHER_ADDR_LEN, 6)]; + + /* reset the adapter */ + aue_cfg_reset(sc); + + /* set default value */ + bzero(eaddr, sizeof(eaddr)); + + /* get station address from the EEPROM */ + aue_cfg_read_eeprom(sc, eaddr, 0, 3); + + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_ETHER); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + printf("%s: could not if_alloc()\n", + sc->sc_name); + goto done; + } + sc->sc_evilhack = ifp; + + ifp->if_softc = sc; + if_initname(ifp, "aue", sc->sc_unit); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = aue_ioctl_cb; + ifp->if_start = aue_start_cb; + ifp->if_watchdog = NULL; + ifp->if_init = aue_init_cb; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + /* + * XXX need Giant when accessing the device structures ! + */ + + mtx_unlock(&sc->sc_mtx); + + mtx_lock(&Giant); + + error = mii_phy_probe(sc->sc_dev, &sc->sc_miibus, + &aue_ifmedia_upd_cb, + &aue_ifmedia_sts_cb); + + mtx_unlock(&Giant); + + mtx_lock(&sc->sc_mtx); + + /* + * Do MII setup. + * NOTE: Doing this causes child devices to be attached to us, + * which we would normally disconnect at in the detach routine + * using device_delete_child(). However the USB code is set up + * such that when this driver is removed, all children devices + * are removed as well. In effect, the USB code ends up detaching + * all of our children for us, so we don't have to do is ourselves + * in aue_detach(). It's important to point this out since if + * we *do* try to detach the child devices ourselves, we will + * end up getting the children deleted twice, which will crash + * the system. + */ + if (error) { + printf("%s: MII without any PHY!\n", + sc->sc_name); + if_free(ifp); + goto done; + } + sc->sc_ifp = ifp; + + mtx_unlock(&sc->sc_mtx); + + /* + * Call MI attach routine. + */ + ether_ifattach(ifp, eaddr); + + mtx_lock(&sc->sc_mtx); + +done: + return; +} + +static int +aue_detach(device_t dev) +{ + struct aue_softc *sc = device_get_softc(dev); + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + aue_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, AUE_ENDPT_MAX); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + ether_ifdetach(ifp); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +aue_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct aue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~AUE_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +aue_intr_callback(struct usb2_xfer *xfer) +{ + struct aue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct aue_intrpkt pkt; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (ifp && (ifp->if_drv_flags & IFF_DRV_RUNNING) && + (xfer->actlen >= sizeof(pkt))) { + + usb2_copy_out(xfer->frbuffers, 0, &pkt, sizeof(pkt)); + + if (pkt.aue_txstat0) { + ifp->if_oerrors++; + } + if (pkt.aue_txstat0 & (AUE_TXSTAT0_LATECOLL & + AUE_TXSTAT0_EXCESSCOLL)) { + ifp->if_collisions++; + } + } + case USB_ST_SETUP: + if (sc->sc_flags & AUE_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= AUE_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + return; + } +} + +static void +aue_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct aue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~AUE_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +aue_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct aue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m = NULL; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "received %d bytes\n", xfer->actlen); + + if (sc->sc_flags & AUE_FLAG_VER_2) { + + if (xfer->actlen == 0) { + ifp->if_ierrors++; + goto tr_setup; + } + } else { + + if (xfer->actlen <= (4 + ETHER_CRC_LEN)) { + ifp->if_ierrors++; + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, xfer->actlen - 4, &sc->sc_rxpkt, + sizeof(sc->sc_rxpkt)); + + /* + * turn off all the non-error bits in the rx status + * word: + */ + sc->sc_rxpkt.aue_rxstat &= AUE_RXSTAT_MASK; + + if (sc->sc_rxpkt.aue_rxstat) { + ifp->if_ierrors++; + goto tr_setup; + } + /* No errors; receive the packet. */ + xfer->actlen -= (4 + ETHER_CRC_LEN); + } + + m = usb2_ether_get_mbuf(); + + if (m == NULL) { + ifp->if_ierrors++; + goto tr_setup; + } + xfer->actlen = min(xfer->actlen, m->m_len); + + usb2_copy_out(xfer->frbuffers, 0, m->m_data, xfer->actlen); + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = xfer->actlen; + + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_flags & AUE_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "if_input" here, and not some lines up! + */ + if (m) { + mtx_unlock(&sc->sc_mtx); + (ifp->if_input) (ifp, m); + mtx_lock(&sc->sc_mtx); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= AUE_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + DPRINTF("bulk read error, %s\n", + usb2_errstr(xfer->error)); + return; + + } +} + +static void +aue_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct aue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~AUE_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +aue_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct aue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer of %d bytes complete\n", xfer->actlen); + + ifp->if_opackets++; + + case USB_ST_SETUP: + + if (sc->sc_flags & AUE_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + goto done; + } + if (sc->sc_flags & AUE_FLAG_WAIT_LINK) { + /* + * don't send anything if there is no link ! + */ + goto done; + } + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) { + goto done; + } + if (m->m_pkthdr.len > MCLBYTES) { + m->m_pkthdr.len = MCLBYTES; + } + if (sc->sc_flags & AUE_FLAG_VER_2) { + + xfer->frlengths[0] = m->m_pkthdr.len; + + usb2_m_copy_in(xfer->frbuffers, 0, + m, 0, m->m_pkthdr.len); + + } else { + + xfer->frlengths[0] = (m->m_pkthdr.len + 2); + + /* + * The ADMtek documentation says that the packet length is + * supposed to be specified in the first two bytes of the + * transfer, however it actually seems to ignore this info + * and base the frame size on the bulk transfer length. + */ + buf[0] = (uint8_t)(m->m_pkthdr.len); + buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); + + usb2_copy_in(xfer->frbuffers, 0, buf, 2); + + usb2_m_copy_in(xfer->frbuffers, 2, + m, 0, m->m_pkthdr.len); + } + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usb2_start_hardware(xfer); + +done: + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= AUE_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + ifp->if_oerrors++; + return; + + } +} + +#define AUE_BITS 6 + +static void +aue_mchash(struct usb2_config_td_cc *cc, const uint8_t *ptr) +{ + uint8_t h; + + h = ether_crc32_le(ptr, ETHER_ADDR_LEN) & + ((1 << AUE_BITS) - 1); + cc->if_hash[(h >> 3)] |= (1 << (h & 7)); + return; +} + +static void +aue_config_copy(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + bzero(cc, sizeof(*cc)); + usb2_ether_cc(sc->sc_ifp, &aue_mchash, cc); + return; +} + +static void +aue_cfg_tick(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mii_data *mii = GET_MII(sc); + + if ((ifp == NULL) || + (mii == NULL)) { + /* not ready */ + return; + } + mii_tick(mii); + + mii_pollstat(mii); + + if ((sc->sc_flags & AUE_FLAG_WAIT_LINK) && + (mii->mii_media_status & IFM_ACTIVE) && + (IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE)) { + sc->sc_flags &= ~AUE_FLAG_WAIT_LINK; + } + sc->sc_media_active = mii->mii_media_active; + sc->sc_media_status = mii->mii_media_status; + + /* start stopped transfers, if any */ + + aue_start_transfers(sc); + + return; +} + +static void +aue_start_cb(struct ifnet *ifp) +{ + struct aue_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + aue_start_transfers(sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +aue_init_cb(void *arg) +{ + struct aue_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &aue_cfg_pre_init, &aue_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +aue_start_transfers(struct aue_softc *sc) +{ + if ((sc->sc_flags & AUE_FLAG_LL_READY) && + (sc->sc_flags & AUE_FLAG_HL_READY)) { + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[4]); + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + } + return; +} + +static void +aue_cfg_pre_init(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + /* immediate configuration */ + + aue_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= AUE_FLAG_HL_READY; + return; +} + +static void +aue_cfg_init(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct mii_data *mii = GET_MII(sc); + uint8_t i; + + /* + * Cancel pending I/O + */ + aue_cfg_stop(sc, cc, 0); + + /* Set MAC address */ + for (i = 0; i < ETHER_ADDR_LEN; i++) { + aue_cfg_csr_write_1(sc, AUE_PAR0 + i, cc->if_lladdr[i]); + } + + /* update promiscuous setting */ + aue_cfg_promisc_upd(sc, cc, 0); + + /* load the multicast filter */ + aue_cfg_setmulti(sc, cc, 0); + + /* enable RX and TX */ + aue_cfg_csr_write_1(sc, AUE_CTL0, + (AUE_CTL0_RXSTAT_APPEND | + AUE_CTL0_RX_ENB)); + + AUE_CFG_SETBIT(sc, AUE_CTL0, AUE_CTL0_TX_ENB); + AUE_CFG_SETBIT(sc, AUE_CTL2, AUE_CTL2_EP3_CLR); + + mii_mediachg(mii); + + sc->sc_flags |= (AUE_FLAG_READ_STALL | + AUE_FLAG_WRITE_STALL | + AUE_FLAG_LL_READY); + + aue_start_transfers(sc); + return; +} + +static void +aue_cfg_promisc_upd(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* if we want promiscuous mode, set the allframes bit: */ + if (cc->if_flags & IFF_PROMISC) { + AUE_CFG_SETBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); + } else { + AUE_CFG_CLRBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); + } + return; +} + +/* + * Set media options. + */ +static int +aue_ifmedia_upd_cb(struct ifnet *ifp) +{ + struct aue_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &aue_cfg_ifmedia_upd, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static void +aue_cfg_ifmedia_upd(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mii_data *mii = GET_MII(sc); + + if ((ifp == NULL) || + (mii == NULL)) { + /* not ready */ + return; + } + sc->sc_flags |= AUE_FLAG_WAIT_LINK; + + if (mii->mii_instance) { + struct mii_softc *miisc; + + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { + mii_phy_reset(miisc); + } + } + mii_mediachg(mii); + + return; +} + +/* + * Report current media status. + */ +static void +aue_ifmedia_sts_cb(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct aue_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + ifmr->ifm_active = sc->sc_media_active; + ifmr->ifm_status = sc->sc_media_status; + + mtx_unlock(&sc->sc_mtx); + return; +} + +static int +aue_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct aue_softc *sc = ifp->if_softc; + struct mii_data *mii; + int error = 0; + + switch (command) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &aue_config_copy, + &aue_cfg_promisc_upd, 0, 0); + } else { + usb2_config_td_queue_command + (&sc->sc_config_td, &aue_cfg_pre_init, + &aue_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &aue_cfg_pre_stop, + &aue_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &aue_config_copy, + &aue_cfg_setmulti, 0, 0); + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + mii = GET_MII(sc); + if (mii == NULL) { + error = EINVAL; + } else { + error = ifmedia_ioctl + (ifp, (void *)data, &mii->mii_media, command); + } + break; + + default: + error = ether_ioctl(ifp, command, data); + break; + } + return (error); +} + +static void +aue_watchdog(void *arg) +{ + struct aue_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &aue_cfg_tick, 0, 0); + + usb2_callout_reset(&sc->sc_watchdog, + hz, &aue_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + return; +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + * + * NOTE: can be called when "ifp" is NULL + */ +static void +aue_cfg_pre_stop(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + aue_config_copy(sc, cc, refcount); + } + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(AUE_FLAG_HL_READY | + AUE_FLAG_LL_READY); + + sc->sc_flags |= AUE_FLAG_WAIT_LINK; + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[4]); + usb2_transfer_stop(sc->sc_xfer[5]); + return; +} + +static void +aue_cfg_stop(struct aue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + aue_cfg_csr_write_1(sc, AUE_CTL0, 0); + aue_cfg_csr_write_1(sc, AUE_CTL1, 0); + aue_cfg_reset(sc); + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +aue_shutdown(device_t dev) +{ + struct aue_softc *sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + + usb2_config_td_queue_command + (&sc->sc_config_td, &aue_cfg_pre_stop, + &aue_cfg_stop, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} diff --git a/sys/dev/usb2/ethernet/if_aue2_reg.h b/sys/dev/usb2/ethernet/if_aue2_reg.h new file mode 100644 index 000000000000..8111b7a43f41 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_aue2_reg.h @@ -0,0 +1,232 @@ +/*- + * Copyright (c) 1997, 1998, 1999 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + * + * $FreeBSD$ + */ + +/* + * Register definitions for ADMtek Pegasus AN986 USB to Ethernet + * chip. The Pegasus uses a total of four USB endpoints: the control + * endpoint (0), a bulk read endpoint for receiving packets (1), + * a bulk write endpoint for sending packets (2) and an interrupt + * endpoint for passing RX and TX status (3). Endpoint 0 is used + * to read and write the ethernet module's registers. All registers + * are 8 bits wide. + * + * Packet transfer is done in 64 byte chunks. The last chunk in a + * transfer is denoted by having a length less that 64 bytes. For + * the RX case, the data includes an optional RX status word. + */ + +#define AUE_UR_READREG 0xF0 +#define AUE_UR_WRITEREG 0xF1 + +#define AUE_CONFIG_INDEX 0 /* config number 1 */ +#define AUE_IFACE_IDX 0 + +/* + * Note that while the ADMtek technically has four endpoints, the control + * endpoint (endpoint 0) is regarded as special by the USB code and drivers + * don't have direct access to it (we access it using usb2_do_request() + * when reading/writing registers. Consequently, our endpoint indexes + * don't match those in the ADMtek Pegasus manual: we consider the RX data + * endpoint to be index 0 and work up from there. + */ +#define AUE_ENDPT_MAX 6 + +#define AUE_INTR_PKTLEN 0x8 + +#define AUE_CTL0 0x00 +#define AUE_CTL1 0x01 +#define AUE_CTL2 0x02 +#define AUE_MAR0 0x08 +#define AUE_MAR1 0x09 +#define AUE_MAR2 0x0A +#define AUE_MAR3 0x0B +#define AUE_MAR4 0x0C +#define AUE_MAR5 0x0D +#define AUE_MAR6 0x0E +#define AUE_MAR7 0x0F +#define AUE_MAR AUE_MAR0 +#define AUE_PAR0 0x10 +#define AUE_PAR1 0x11 +#define AUE_PAR2 0x12 +#define AUE_PAR3 0x13 +#define AUE_PAR4 0x14 +#define AUE_PAR5 0x15 +#define AUE_PAR AUE_PAR0 +#define AUE_PAUSE0 0x18 +#define AUE_PAUSE1 0x19 +#define AUE_PAUSE AUE_PAUSE0 +#define AUE_RX_FLOWCTL_CNT 0x1A +#define AUE_RX_FLOWCTL_FIFO 0x1B +#define AUE_REG_1D 0x1D +#define AUE_EE_REG 0x20 +#define AUE_EE_DATA0 0x21 +#define AUE_EE_DATA1 0x22 +#define AUE_EE_DATA AUE_EE_DATA0 +#define AUE_EE_CTL 0x23 +#define AUE_PHY_ADDR 0x25 +#define AUE_PHY_DATA0 0x26 +#define AUE_PHY_DATA1 0x27 +#define AUE_PHY_DATA AUE_PHY_DATA0 +#define AUE_PHY_CTL 0x28 +#define AUE_USB_STS 0x2A +#define AUE_TXSTAT0 0x2B +#define AUE_TXSTAT1 0x2C +#define AUE_TXSTAT AUE_TXSTAT0 +#define AUE_RXSTAT 0x2D +#define AUE_PKTLOST0 0x2E +#define AUE_PKTLOST1 0x2F +#define AUE_PKTLOST AUE_PKTLOST0 + +#define AUE_REG_7B 0x7B +#define AUE_GPIO0 0x7E +#define AUE_GPIO1 0x7F +#define AUE_REG_81 0x81 + +#define AUE_CTL0_INCLUDE_RXCRC 0x01 +#define AUE_CTL0_ALLMULTI 0x02 +#define AUE_CTL0_STOP_BACKOFF 0x04 +#define AUE_CTL0_RXSTAT_APPEND 0x08 +#define AUE_CTL0_WAKEON_ENB 0x10 +#define AUE_CTL0_RXPAUSE_ENB 0x20 +#define AUE_CTL0_RX_ENB 0x40 +#define AUE_CTL0_TX_ENB 0x80 + +#define AUE_CTL1_HOMELAN 0x04 +#define AUE_CTL1_RESETMAC 0x08 +#define AUE_CTL1_SPEEDSEL 0x10 /* 0 = 10mbps, 1 = 100mbps */ +#define AUE_CTL1_DUPLEX 0x20 /* 0 = half, 1 = full */ +#define AUE_CTL1_DELAYHOME 0x40 + +#define AUE_CTL2_EP3_CLR 0x01 /* reading EP3 clrs status regs */ +#define AUE_CTL2_RX_BADFRAMES 0x02 +#define AUE_CTL2_RX_PROMISC 0x04 +#define AUE_CTL2_LOOPBACK 0x08 +#define AUE_CTL2_EEPROMWR_ENB 0x10 +#define AUE_CTL2_EEPROM_LOAD 0x20 + +#define AUE_EECTL_WRITE 0x01 +#define AUE_EECTL_READ 0x02 +#define AUE_EECTL_DONE 0x04 + +#define AUE_PHYCTL_PHYREG 0x1F +#define AUE_PHYCTL_WRITE 0x20 +#define AUE_PHYCTL_READ 0x40 +#define AUE_PHYCTL_DONE 0x80 + +#define AUE_USBSTS_SUSPEND 0x01 +#define AUE_USBSTS_RESUME 0x02 + +#define AUE_TXSTAT0_JABTIMO 0x04 +#define AUE_TXSTAT0_CARLOSS 0x08 +#define AUE_TXSTAT0_NOCARRIER 0x10 +#define AUE_TXSTAT0_LATECOLL 0x20 +#define AUE_TXSTAT0_EXCESSCOLL 0x40 +#define AUE_TXSTAT0_UNDERRUN 0x80 + +#define AUE_TXSTAT1_PKTCNT 0x0F +#define AUE_TXSTAT1_FIFO_EMPTY 0x40 +#define AUE_TXSTAT1_FIFO_FULL 0x80 + +#define AUE_RXSTAT_OVERRUN 0x01 +#define AUE_RXSTAT_PAUSE 0x02 + +#define AUE_GPIO_IN0 0x01 +#define AUE_GPIO_OUT0 0x02 +#define AUE_GPIO_SEL0 0x04 +#define AUE_GPIO_IN1 0x08 +#define AUE_GPIO_OUT1 0x10 +#define AUE_GPIO_SEL1 0x20 + +#define AUE_TIMEOUT 100 /* 10*ms */ +#define AUE_MIN_FRAMELEN 60 + +#define AUE_RXSTAT_MCAST 0x01 +#define AUE_RXSTAT_GIANT 0x02 +#define AUE_RXSTAT_RUNT 0x04 +#define AUE_RXSTAT_CRCERR 0x08 +#define AUE_RXSTAT_DRIBBLE 0x10 +#define AUE_RXSTAT_MASK 0x1E + +#define GET_MII(sc) ((sc)->sc_miibus ? \ + device_get_softc((sc)->sc_miibus) : NULL) + +struct aue_intrpkt { + uint8_t aue_txstat0; + uint8_t aue_txstat1; + uint8_t aue_rxstat; + uint8_t aue_rxlostpkt0; + uint8_t aue_rxlostpkt1; + uint8_t aue_wakeupstat; + uint8_t aue_rsvd; +} __packed; + +struct aue_rxpkt { + uint16_t aue_pktlen; + uint8_t aue_rxstat; +} __packed; + + +struct aue_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct usb2_config_td sc_config_td; + struct usb2_callout sc_watchdog; + struct mtx sc_mtx; + struct aue_rxpkt sc_rxpkt; + + struct ifnet *sc_ifp; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[AUE_ENDPT_MAX]; + device_t sc_miibus; + device_t sc_dev; + + uint32_t sc_unit; + uint32_t sc_media_active; + uint32_t sc_media_status; + + uint16_t sc_flags; +#define AUE_FLAG_LSYS 0x0001 /* use Linksys reset */ +#define AUE_FLAG_PNA 0x0002 /* has Home PNA */ +#define AUE_FLAG_PII 0x0004 /* Pegasus II chip */ +#define AUE_FLAG_WAIT_LINK 0x0008 /* wait for link to come up */ +#define AUE_FLAG_READ_STALL 0x0010 /* wait for clearing of stall */ +#define AUE_FLAG_WRITE_STALL 0x0020 /* wait for clearing of stall */ +#define AUE_FLAG_LL_READY 0x0040 /* Lower Layer Ready */ +#define AUE_FLAG_HL_READY 0x0080 /* Higher Layer Ready */ +#define AUE_FLAG_INTR_STALL 0x0100 /* wait for clearing of stall */ +#define AUE_FLAG_VER_2 0x0200 /* chip is version 2 */ +#define AUE_FLAG_DUAL_PHY 0x0400 /* chip has two transcivers */ + + uint8_t sc_name[16]; +}; diff --git a/sys/dev/usb2/ethernet/if_axe2.c b/sys/dev/usb2/ethernet/if_axe2.c new file mode 100644 index 000000000000..4bd4df2b2c23 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_axe2.c @@ -0,0 +1,1522 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * ASIX Electronics AX88172/AX88178/AX88778 USB 2.0 ethernet driver. Used in the + * LinkSys USB200M and various other adapters. + * + * Manuals available from: + * http://www.asix.com.tw/datasheet/mac/Ax88172.PDF + * Note: you need the manual for the AX88170 chip (USB 1.x ethernet + * controller) to find the definitions for the RX control register. + * http://www.asix.com.tw/datasheet/mac/Ax88170.PDF + * + * Written by Bill Paul + * Senior Engineer + * Wind River Systems + */ + +/* + * The AX88172 provides USB ethernet supports at 10 and 100Mbps. + * It uses an external PHY (reference designs use a RealTek chip), + * and has a 64-bit multicast hash filter. There is some information + * missing from the manual which one needs to know in order to make + * the chip function: + * + * - You must set bit 7 in the RX control register, otherwise the + * chip won't receive any packets. + * - You must initialize all 3 IPG registers, or you won't be able + * to send any packets. + * + * Note that this device appears to only support loading the station + * address via autload from the EEPROM (i.e. there's no way to manaully + * set it). + * + * (Adam Weinberger wanted me to name this driver if_gir.c.) + */ + +/* + * Ax88178 and Ax88772 support backported from the OpenBSD driver. + * 2007/02/12, J.R. Oldroyd, fbsd@opal.com + * + * Manual here: + * http://www.asix.com.tw/FrootAttach/datasheet/AX88178_datasheet_Rev10.pdf + * http://www.asix.com.tw/FrootAttach/datasheet/AX88772_datasheet_Rev10.pdf + */ + +/* + * NOTE: all function names beginning like "axe_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include + +#define usb2_config_td_cc usb2_ether_cc +#define usb2_config_td_softc axe_softc + +#define USB_DEBUG_VAR axe_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MODULE_DEPEND(axe, usb2_ethernet, 1, 1, 1); +MODULE_DEPEND(axe, usb2_core, 1, 1, 1); +MODULE_DEPEND(axe, ether, 1, 1, 1); +MODULE_DEPEND(axe, miibus, 1, 1, 1); + +#if USB_DEBUG +static int axe_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, axe, CTLFLAG_RW, 0, "USB axe"); +SYSCTL_INT(_hw_usb2_axe, OID_AUTO, debug, CTLFLAG_RW, &axe_debug, 0, + "Debug level"); +#endif + +/* + * Various supported device vendors/products. + */ +static const struct usb2_device_id axe_devs[] = { + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_UF200, 0)}, + {USB_VPI(USB_VENDOR_ACERCM, USB_PRODUCT_ACERCM_EP1427X2, 0)}, + {USB_VPI(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_ETHERNET, AXE_FLAG_772)}, + {USB_VPI(USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88172, 0)}, + {USB_VPI(USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88178, AXE_FLAG_178)}, + {USB_VPI(USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88772, AXE_FLAG_772)}, + {USB_VPI(USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC210T, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D5055, AXE_FLAG_178)}, + {USB_VPI(USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USB2AR, 0)}, + {USB_VPI(USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_USB200MV2, AXE_FLAG_772)}, + {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB2_TX, 0)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DUBE100, 0)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DUBE100B1, AXE_FLAG_772)}, + {USB_VPI(USB_VENDOR_GOODWAY, USB_PRODUCT_GOODWAY_GWUSB2E, 0)}, + {USB_VPI(USB_VENDOR_IODATA, USB_PRODUCT_IODATA_ETGUS2, AXE_FLAG_178)}, + {USB_VPI(USB_VENDOR_JVC, USB_PRODUCT_JVC_MP_PRX1, 0)}, + {USB_VPI(USB_VENDOR_LINKSYS2, USB_PRODUCT_LINKSYS2_USB200M, 0)}, + {USB_VPI(USB_VENDOR_LINKSYS4, USB_PRODUCT_LINKSYS4_USB1000, AXE_FLAG_178)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAU2KTX, 0)}, + {USB_VPI(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_FA120, 0)}, + {USB_VPI(USB_VENDOR_OQO, USB_PRODUCT_OQO_ETHER01PLUS, AXE_FLAG_772)}, + {USB_VPI(USB_VENDOR_PLANEX3, USB_PRODUCT_PLANEX3_GU1000T, AXE_FLAG_178)}, + {USB_VPI(USB_VENDOR_SITECOM, USB_PRODUCT_SITECOM_LN029, 0)}, + {USB_VPI(USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_LN028, AXE_FLAG_178)}, + {USB_VPI(USB_VENDOR_SYSTEMTALKS, USB_PRODUCT_SYSTEMTALKS_SGCX2UL, 0)}, +}; + +static device_probe_t axe_probe; +static device_attach_t axe_attach; +static device_detach_t axe_detach; +static device_shutdown_t axe_shutdown; + +static usb2_callback_t axe_intr_clear_stall_callback; +static usb2_callback_t axe_intr_callback; +static usb2_callback_t axe_bulk_read_clear_stall_callback; +static usb2_callback_t axe_bulk_read_callback; +static usb2_callback_t axe_bulk_write_clear_stall_callback; +static usb2_callback_t axe_bulk_write_callback; + +static void axe_cfg_cmd(struct axe_softc *sc, uint16_t cmd, uint16_t index, uint16_t val, void *buf); + +static miibus_readreg_t axe_cfg_miibus_readreg; +static miibus_writereg_t axe_cfg_miibus_writereg; +static miibus_statchg_t axe_cfg_miibus_statchg; + +static usb2_config_td_command_t axe_cfg_ifmedia_upd; +static usb2_config_td_command_t axe_config_copy; +static usb2_config_td_command_t axe_cfg_setmulti; +static usb2_config_td_command_t axe_cfg_first_time_setup; +static usb2_config_td_command_t axe_cfg_tick; +static usb2_config_td_command_t axe_cfg_pre_init; +static usb2_config_td_command_t axe_cfg_init; +static usb2_config_td_command_t axe_cfg_promisc_upd; +static usb2_config_td_command_t axe_cfg_pre_stop; +static usb2_config_td_command_t axe_cfg_stop; + +static int axe_ifmedia_upd_cb(struct ifnet *ifp); +static void axe_ifmedia_sts_cb(struct ifnet *ifp, struct ifmediareq *ifmr); +static void axe_cfg_reset(struct axe_softc *sc); +static void axe_start_cb(struct ifnet *ifp); +static void axe_start_transfers(struct axe_softc *sc); +static void axe_init_cb(void *arg); +static int axe_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data); +static void axe_watchdog(void *arg); +static void axe_cfg_ax88178_init(struct axe_softc *); +static void axe_cfg_ax88772_init(struct axe_softc *); + +static const struct usb2_config axe_config[AXE_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = AXE_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &axe_bulk_write_callback, + .mh.timeout = 10000, /* 10 seconds */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, +#if (MCLBYTES < 2048) +#error "(MCLBYTES < 2048)" +#endif + .mh.bufsize = MCLBYTES, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &axe_bulk_read_callback, + .mh.timeout = 0, /* no timeout */ + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &axe_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &axe_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &axe_intr_callback, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &axe_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static device_method_t axe_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, axe_probe), + DEVMETHOD(device_attach, axe_attach), + DEVMETHOD(device_detach, axe_detach), + DEVMETHOD(device_shutdown, axe_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, axe_cfg_miibus_readreg), + DEVMETHOD(miibus_writereg, axe_cfg_miibus_writereg), + DEVMETHOD(miibus_statchg, axe_cfg_miibus_statchg), + + {0, 0} +}; + +static driver_t axe_driver = { + .name = "axe", + .methods = axe_methods, + .size = sizeof(struct axe_softc), +}; + +static devclass_t axe_devclass; + +DRIVER_MODULE(axe, ushub, axe_driver, axe_devclass, NULL, 0); +DRIVER_MODULE(miibus, axe, miibus_driver, miibus_devclass, 0, 0); + +static void +axe_cfg_cmd(struct axe_softc *sc, uint16_t cmd, uint16_t index, + uint16_t val, void *buf) +{ + struct usb2_device_request req; + usb2_error_t err; + uint16_t length = AXE_CMD_LEN(cmd); + + req.bmRequestType = (AXE_CMD_IS_WRITE(cmd) ? + UT_WRITE_VENDOR_DEVICE : + UT_READ_VENDOR_DEVICE); + req.bRequest = AXE_CMD_CMD(cmd); + USETW(req.wValue, val); + USETW(req.wIndex, index); + USETW(req.wLength, length); + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, &req, buf, 0, NULL, 1000); + + if (err) { + + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + + if ((req.bmRequestType & UT_READ) && length) { + bzero(buf, length); + } + } + return; +} + +static int +axe_cfg_miibus_readreg(device_t dev, int phy, int reg) +{ + struct axe_softc *sc = device_get_softc(dev); + uint16_t val; + uint8_t do_unlock; + + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + +#if 0 + /* + * The chip tells us the MII address of any supported + * PHYs attached to the chip, so only read from those. + */ + + if ((sc->sc_phyaddrs[0] != AXE_NOPHY) && (phy != sc->sc_phyaddrs[0])) { + val = 0; + goto done; + } + if ((sc->sc_phyaddrs[1] != AXE_NOPHY) && (phy != sc->sc_phyaddrs[1])) { + val = 0; + goto done; + } +#endif + if ((sc->sc_phyaddrs[0] != 0xFF) && (sc->sc_phyaddrs[0] != phy)) { + val = 0; + goto done; + } + axe_cfg_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); + axe_cfg_cmd(sc, AXE_CMD_MII_READ_REG, reg, phy, &val); + axe_cfg_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); + + val = le16toh(val); + + if (val && (val != 0xffff)) { + sc->sc_phyaddrs[0] = phy; + } +done: + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return (val); +} + +static int +axe_cfg_miibus_writereg(device_t dev, int phy, int reg, int val) +{ + struct axe_softc *sc = device_get_softc(dev); + uint8_t do_unlock; + + val = htole16(val); + + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + axe_cfg_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); + axe_cfg_cmd(sc, AXE_CMD_MII_WRITE_REG, reg, phy, &val); + axe_cfg_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); + + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return (0); +} + +static void +axe_cfg_miibus_statchg(device_t dev) +{ + struct axe_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + uint16_t val; + uint8_t do_unlock; + + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) + val = AXE_MEDIA_FULL_DUPLEX; + else + val = 0; + + if (sc->sc_flags & (AXE_FLAG_772 | AXE_FLAG_178)) { + + val |= (AXE_178_MEDIA_RX_EN | AXE_178_MEDIA_MAGIC); + + switch (IFM_SUBTYPE(mii->mii_media_active)) { + case IFM_1000_T: + val |= AXE_178_MEDIA_GMII | AXE_178_MEDIA_ENCK; + break; + case IFM_100_TX: + val |= AXE_178_MEDIA_100TX; + break; + case IFM_10_T: + /* doesn't need to be handled */ + break; + } + } + axe_cfg_cmd(sc, AXE_CMD_WRITE_MEDIA, 0, val, NULL); + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return; +} + +/* + * Set media options. + */ +static int +axe_ifmedia_upd_cb(struct ifnet *ifp) +{ + struct axe_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &axe_cfg_ifmedia_upd, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static void +axe_cfg_ifmedia_upd(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mii_data *mii = GET_MII(sc); + + if ((ifp == NULL) || + (mii == NULL)) { + /* not ready */ + return; + } + sc->sc_flags |= AXE_FLAG_WAIT_LINK; + + if (mii->mii_instance) { + struct mii_softc *miisc; + + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { + mii_phy_reset(miisc); + } + } + mii_mediachg(mii); + + return; +} + +/* + * Report current media status. + */ +static void +axe_ifmedia_sts_cb(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct axe_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + ifmr->ifm_active = sc->sc_media_active; + ifmr->ifm_status = sc->sc_media_status; + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +axe_mchash(struct usb2_config_td_cc *cc, const uint8_t *ptr) +{ + uint8_t h; + + h = (ether_crc32_be(ptr, ETHER_ADDR_LEN) >> 26); + cc->if_hash[(h >> 3)] |= (1 << (h & 7)); + return; +} + +static void +axe_config_copy(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + bzero(cc, sizeof(*cc)); + usb2_ether_cc(sc->sc_ifp, &axe_mchash, cc); + return; +} + +static void +axe_cfg_setmulti(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint16_t rxmode; + + axe_cfg_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, &rxmode); + + rxmode = le16toh(rxmode); + + if (cc->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) { + rxmode |= AXE_RXCMD_ALLMULTI; + axe_cfg_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + return; + } + rxmode &= ~AXE_RXCMD_ALLMULTI; + + axe_cfg_cmd(sc, AXE_CMD_WRITE_MCAST, 0, 0, cc->if_hash); + axe_cfg_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + + return; +} + +static void +axe_cfg_reset(struct axe_softc *sc) +{ + struct usb2_config_descriptor *cd; + usb2_error_t err; + + cd = usb2_get_config_descriptor(sc->sc_udev); + + err = usb2_req_set_config(sc->sc_udev, &sc->sc_mtx, + cd->bConfigurationValue); + if (err) { + DPRINTF("reset failed (ignored)\n"); + } + /* + * wait a little while for the chip to get its brains in order: + */ + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 100); + + return; +} + +/* + * Probe for a AX88172 chip. + */ +static int +axe_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != AXE_CONFIG_IDX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != AXE_IFACE_IDX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(axe_devs, sizeof(axe_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +axe_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct axe_softc *sc = device_get_softc(dev); + int32_t error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "axe lock", NULL, MTX_DEF | MTX_RECURSE); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + iface_index = AXE_IFACE_IDX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, axe_config, AXE_ENDPT_MAX, + sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + NULL, sizeof(struct usb2_config_td_cc), 16); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + sc->sc_flags |= AXE_FLAG_WAIT_LINK; + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &axe_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + axe_watchdog(sc); + + return (0); /* success */ + +detach: + axe_detach(dev); + return (ENXIO); /* failure */ +} + +static void +axe_cfg_ax88178_init(struct axe_softc *sc) +{ + uint16_t eeprom; + uint16_t phymode; + uint16_t gpio0; + uint8_t err; + + DPRINTF("\n"); + + axe_cfg_cmd(sc, AXE_CMD_SROM_WR_ENABLE, 0, 0, NULL); + /* XXX magic */ + axe_cfg_cmd(sc, AXE_CMD_SROM_READ, 0, 0x0017, &eeprom); + axe_cfg_cmd(sc, AXE_CMD_SROM_WR_DISABLE, 0, 0, NULL); + + /* For big-endian machines: */ + eeprom = le16toh(eeprom); + + /* if EEPROM is invalid we have to use to GPIO0 */ + if (eeprom == 0xffff) { + phymode = 0; + gpio0 = 1; + } else { + phymode = (eeprom & 7); + gpio0 = (eeprom & 0x80) ? 0 : 1; + } + + axe_cfg_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x008c, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 16); + + if ((eeprom >> 8) != 0x01) { + axe_cfg_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x003c, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 32); + + axe_cfg_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x001c, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 3); + + axe_cfg_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x003c, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 32); + } else { + axe_cfg_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x0004, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 32); + + axe_cfg_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x000c, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 32); + } + + /* soft reset */ + axe_cfg_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 4); + + axe_cfg_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_PRL | AXE_178_RESET_MAGIC, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 4); + + axe_cfg_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); + return; +} + +static void +axe_cfg_ax88772_init(struct axe_softc *sc) +{ + uint8_t err; + + DPRINTF("\n"); + + axe_cfg_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x00b0, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 16); + + if (sc->sc_phyaddrs[1] == AXE_INTPHY) { + /* ask for the embedded PHY */ + axe_cfg_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x01, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 64); + + /* power down and reset state, pin reset state */ + axe_cfg_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_CLEAR, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 16); + + /* power down/reset state, pin operating state */ + axe_cfg_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 4); + + /* power up, reset */ + axe_cfg_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_PRL, NULL); + + /* power up, operating */ + axe_cfg_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPRL | AXE_SW_RESET_PRL, NULL); + } else { + /* ask for external PHY */ + axe_cfg_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x00, NULL); + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 64); + + /* power down internal PHY */ + axe_cfg_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); + } + + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 4); + axe_cfg_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); + return; +} + +static void +axe_cfg_first_time_setup(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp; + int error; + uint8_t eaddr[min(ETHER_ADDR_LEN, 6)]; + + /* set default value */ + bzero(eaddr, sizeof(eaddr)); + + /* + * Load PHY indexes first. Needed by axe_xxx_init(). + */ + axe_cfg_cmd(sc, AXE_CMD_READ_PHYID, 0, 0, sc->sc_phyaddrs); + + if (sc->sc_flags & AXE_FLAG_178) { + axe_cfg_ax88178_init(sc); + } else if (sc->sc_flags & AXE_FLAG_772) { + axe_cfg_ax88772_init(sc); + } + /* + * Get station address. + */ + if (sc->sc_flags & (AXE_FLAG_178 | AXE_FLAG_772)) + axe_cfg_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, eaddr); + else + axe_cfg_cmd(sc, AXE_172_CMD_READ_NODEID, 0, 0, eaddr); + + /* + * Fetch IPG values. + */ + axe_cfg_cmd(sc, AXE_CMD_READ_IPG012, 0, 0, sc->sc_ipgs); + + /* + * Work around broken adapters that appear to lie about + * their PHY addresses. + */ + sc->sc_phyaddrs[0] = sc->sc_phyaddrs[1] = 0xFF; + + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_ETHER); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + printf("%s: could not if_alloc()\n", + sc->sc_name); + goto done; + } + sc->sc_evilhack = ifp; + + ifp->if_softc = sc; + if_initname(ifp, "axe", sc->sc_unit); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = axe_ioctl_cb; + ifp->if_start = axe_start_cb; + ifp->if_watchdog = NULL; + ifp->if_init = axe_init_cb; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + /* + * XXX need Giant when accessing the device structures ! + */ + + mtx_unlock(&sc->sc_mtx); + + mtx_lock(&Giant); + + error = mii_phy_probe(sc->sc_dev, &sc->sc_miibus, + &axe_ifmedia_upd_cb, + &axe_ifmedia_sts_cb); + mtx_unlock(&Giant); + + mtx_lock(&sc->sc_mtx); + + if (error) { + printf("%s: MII without any PHY!\n", + sc->sc_name); + if_free(ifp); + goto done; + } + sc->sc_ifp = ifp; + + mtx_unlock(&sc->sc_mtx); + + /* + * Call MI attach routine. + */ + + ether_ifattach(ifp, eaddr); + + mtx_lock(&sc->sc_mtx); + +done: + return; +} + +static int +axe_detach(device_t dev) +{ + struct axe_softc *sc = device_get_softc(dev); + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + axe_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, AXE_ENDPT_MAX); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + ether_ifdetach(ifp); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +axe_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct axe_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~AXE_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +axe_intr_callback(struct usb2_xfer *xfer) +{ + struct axe_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* do nothing */ + + case USB_ST_SETUP: + if (sc->sc_flags & AXE_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= AXE_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + return; + } +} + +static void +axe_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct axe_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~AXE_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +#if (AXE_BULK_BUF_SIZE >= 0x10000) +#error "Please update axe_bulk_read_callback()!" +#endif + +static void +axe_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct axe_softc *sc = xfer->priv_sc; + struct axe_sframe_hdr hdr; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + struct { /* mini-queue */ + struct mbuf *ifq_head; + struct mbuf *ifq_tail; + uint16_t ifq_len; + } mq = { + NULL, NULL, 0 + }; + uint16_t pos; + uint16_t len; + uint16_t adjust; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + pos = 0; + + while (1) { + + if (sc->sc_flags & (AXE_FLAG_772 | AXE_FLAG_178)) { + + if (xfer->actlen < sizeof(hdr)) { + /* too little data */ + break; + } + usb2_copy_out(xfer->frbuffers, pos, &hdr, sizeof(hdr)); + + if ((hdr.len ^ hdr.ilen) != 0xFFFF) { + /* we lost sync */ + break; + } + xfer->actlen -= sizeof(hdr); + pos += sizeof(hdr); + + len = le16toh(hdr.len); + if (len > xfer->actlen) { + /* invalid length */ + break; + } + adjust = (len & 1); + + } else { + len = xfer->actlen; + adjust = 0; + } + + if (len < sizeof(struct ether_header)) { + ifp->if_ierrors++; + goto skip; + } + m = usb2_ether_get_mbuf(); + if (m == NULL) { + /* we are out of memory */ + break; + } + if (m->m_len > len) { + m->m_len = len; + } + usb2_copy_out(xfer->frbuffers, pos, m->m_data, m->m_len); + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len; + + /* enqueue */ + _IF_ENQUEUE(&mq, m); + + skip: + + pos += len; + xfer->actlen -= len; + + if (xfer->actlen <= adjust) { + /* we are finished */ + goto tr_setup; + } + pos += adjust; + xfer->actlen -= adjust; + } + + /* count an error */ + ifp->if_ierrors++; + + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_flags & AXE_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "if_input" here, and not some lines up! + */ + if (mq.ifq_head) { + + mtx_unlock(&sc->sc_mtx); + + while (1) { + + _IF_DEQUEUE(&mq, m); + + if (m == NULL) + break; + + (ifp->if_input) (ifp, m); + } + + mtx_lock(&sc->sc_mtx); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= AXE_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + DPRINTF("bulk read error, %s\n", + usb2_errstr(xfer->error)); + return; + + } +} + +static void +axe_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct axe_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~AXE_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +#if ((AXE_BULK_BUF_SIZE >= 0x10000) || (AXE_BULK_BUF_SIZE < (MCLBYTES+4))) +#error "Please update axe_bulk_write_callback()!" +#endif + +static void +axe_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct axe_softc *sc = xfer->priv_sc; + struct axe_sframe_hdr hdr; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint16_t pos; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + + ifp->if_opackets++; + + case USB_ST_SETUP: + + if (sc->sc_flags & AXE_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + goto done; + } + if (sc->sc_flags & AXE_FLAG_WAIT_LINK) { + /* + * don't send anything if there is no link ! + */ + goto done; + } + pos = 0; + + while (1) { + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) { + if (pos > 0) + break; /* send out data */ + else + goto done; + } + if (m->m_pkthdr.len > MCLBYTES) { + m->m_pkthdr.len = MCLBYTES; + } + if (sc->sc_flags & (AXE_FLAG_772 | AXE_FLAG_178)) { + + hdr.len = htole16(m->m_pkthdr.len); + hdr.ilen = ~hdr.len; + + usb2_copy_in(xfer->frbuffers, pos, &hdr, sizeof(hdr)); + + pos += sizeof(hdr); + + /* + * NOTE: Some drivers force a short packet + * by appending a dummy header with zero + * length at then end of the USB transfer. + * This driver uses the + * USB_FORCE_SHORT_XFER flag instead. + */ + } + usb2_m_copy_in(xfer->frbuffers, pos, + m, 0, m->m_pkthdr.len); + + pos += m->m_pkthdr.len; + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + if (sc->sc_flags & (AXE_FLAG_772 | AXE_FLAG_178)) { + if (pos > (AXE_BULK_BUF_SIZE - MCLBYTES - sizeof(hdr))) { + /* send out frame(s) */ + break; + } + } else { + /* send out frame */ + break; + } + } + + xfer->frlengths[0] = pos; + usb2_start_hardware(xfer); + +done: + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= AXE_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + ifp->if_oerrors++; + return; + + } +} + +static void +axe_cfg_tick(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mii_data *mii = GET_MII(sc); + + if ((ifp == NULL) || + (mii == NULL)) { + /* not ready */ + return; + } + mii_tick(mii); + + mii_pollstat(mii); + + if ((sc->sc_flags & AXE_FLAG_WAIT_LINK) && + (mii->mii_media_status & IFM_ACTIVE) && + (IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE)) { + sc->sc_flags &= ~AXE_FLAG_WAIT_LINK; + } + sc->sc_media_active = mii->mii_media_active; + sc->sc_media_status = mii->mii_media_status; + + /* start stopped transfers, if any */ + + axe_start_transfers(sc); + + return; +} + +static void +axe_start_cb(struct ifnet *ifp) +{ + struct axe_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + axe_start_transfers(sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +axe_start_transfers(struct axe_softc *sc) +{ + if ((sc->sc_flags & AXE_FLAG_LL_READY) && + (sc->sc_flags & AXE_FLAG_HL_READY)) { + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[4]); + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + } + return; +} + +static void +axe_init_cb(void *arg) +{ + struct axe_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &axe_cfg_pre_init, &axe_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +axe_cfg_pre_init(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + /* immediate configuration */ + + axe_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= AXE_FLAG_HL_READY; + return; +} + +static void +axe_cfg_init(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct mii_data *mii = GET_MII(sc); + uint16_t rxmode; + + /* + * Cancel pending I/O + */ + + axe_cfg_stop(sc, cc, 0); + +#if 0 + /* Set MAC address */ + axe_mac(sc, cc->if_lladdr); +#endif + + /* Set transmitter IPG values */ + if (sc->sc_flags & (AXE_FLAG_178 | AXE_FLAG_772)) { + axe_cfg_cmd(sc, AXE_178_CMD_WRITE_IPG012, sc->sc_ipgs[2], + (sc->sc_ipgs[1] << 8) | (sc->sc_ipgs[0]), NULL); + } else { + axe_cfg_cmd(sc, AXE_172_CMD_WRITE_IPG0, 0, sc->sc_ipgs[0], NULL); + axe_cfg_cmd(sc, AXE_172_CMD_WRITE_IPG1, 0, sc->sc_ipgs[1], NULL); + axe_cfg_cmd(sc, AXE_172_CMD_WRITE_IPG2, 0, sc->sc_ipgs[2], NULL); + } + + /* Enable receiver, set RX mode */ + rxmode = (AXE_RXCMD_MULTICAST | AXE_RXCMD_ENABLE); + if (sc->sc_flags & (AXE_FLAG_178 | AXE_FLAG_772)) { + rxmode |= AXE_178_RXCMD_MFB_2048; /* chip default */ + } else { + rxmode |= AXE_172_RXCMD_UNICAST; + } + + /* If we want promiscuous mode, set the allframes bit. */ + if (cc->if_flags & IFF_PROMISC) { + rxmode |= AXE_RXCMD_PROMISC; + } + if (cc->if_flags & IFF_BROADCAST) { + rxmode |= AXE_RXCMD_BROADCAST; + } + axe_cfg_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + + /* Load the multicast filter. */ + axe_cfg_setmulti(sc, cc, 0); + + mii_mediachg(mii); + + sc->sc_flags |= (AXE_FLAG_READ_STALL | + AXE_FLAG_WRITE_STALL | + AXE_FLAG_LL_READY); + + axe_start_transfers(sc); + + return; +} + +static void +axe_cfg_promisc_upd(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint16_t rxmode; + + axe_cfg_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, &rxmode); + + rxmode = le16toh(rxmode); + + if (cc->if_flags & IFF_PROMISC) { + rxmode |= AXE_RXCMD_PROMISC; + } else { + rxmode &= ~AXE_RXCMD_PROMISC; + } + + axe_cfg_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + + axe_cfg_setmulti(sc, cc, 0); + + return; +} + +static int +axe_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct axe_softc *sc = ifp->if_softc; + struct mii_data *mii; + int error = 0; + + switch (command) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &axe_config_copy, + &axe_cfg_promisc_upd, 0, 0); + } else { + usb2_config_td_queue_command + (&sc->sc_config_td, &axe_cfg_pre_init, + &axe_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &axe_cfg_pre_stop, + &axe_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &axe_config_copy, + &axe_cfg_setmulti, 0, 0); + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + mii = GET_MII(sc); + if (mii == NULL) { + error = EINVAL; + } else { + error = ifmedia_ioctl + (ifp, (void *)data, &mii->mii_media, command); + } + break; + + default: + error = ether_ioctl(ifp, command, data); + break; + } + return (error); +} + +static void +axe_watchdog(void *arg) +{ + struct axe_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &axe_cfg_tick, 0, 0); + + usb2_callout_reset(&sc->sc_watchdog, + hz, &axe_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + return; +} + +/* + * NOTE: can be called when "ifp" is NULL + */ +static void +axe_cfg_pre_stop(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + axe_config_copy(sc, cc, refcount); + } + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(AXE_FLAG_HL_READY | + AXE_FLAG_LL_READY); + + sc->sc_flags |= AXE_FLAG_WAIT_LINK; + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[4]); + usb2_transfer_stop(sc->sc_xfer[5]); + return; +} + +static void +axe_cfg_stop(struct axe_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + axe_cfg_reset(sc); + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +axe_shutdown(device_t dev) +{ + struct axe_softc *sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + + usb2_config_td_queue_command + (&sc->sc_config_td, &axe_cfg_pre_stop, + &axe_cfg_stop, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} diff --git a/sys/dev/usb2/ethernet/if_axe2_reg.h b/sys/dev/usb2/ethernet/if_axe2_reg.h new file mode 100644 index 000000000000..9228f7770926 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_axe2_reg.h @@ -0,0 +1,191 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + * + * $FreeBSD$ + */ + +/* + * Definitions for the ASIX Electronics AX88172, AX88178 + * and AX88772 to ethernet controllers. + */ + +/* + * Vendor specific commands. ASIX conveniently doesn't document the 'set + * NODEID' command in their datasheet (thanks a lot guys). + * To make handling these commands easier, I added some extra data which is + * decided by the axe_cmd() routine. Commands are encoded in 16 bits, with + * the format: LDCC. L and D are both nibbles in the high byte. L represents + * the data length (0 to 15) and D represents the direction (0 for vendor read, + * 1 for vendor write). CC is the command byte, as specified in the manual. + */ + +#define AXE_CMD_IS_WRITE(x) (((x) & 0x0F00) >> 8) +#define AXE_CMD_LEN(x) (((x) & 0xF000) >> 12) +#define AXE_CMD_CMD(x) ((x) & 0x00FF) + +#define AXE_172_CMD_READ_RXTX_SRAM 0x2002 +#define AXE_182_CMD_READ_RXTX_SRAM 0x8002 +#define AXE_172_CMD_WRITE_RX_SRAM 0x0103 +#define AXE_182_CMD_WRITE_RXTX_SRAM 0x8103 +#define AXE_172_CMD_WRITE_TX_SRAM 0x0104 +#define AXE_CMD_MII_OPMODE_SW 0x0106 +#define AXE_CMD_MII_READ_REG 0x2007 +#define AXE_CMD_MII_WRITE_REG 0x2108 +#define AXE_CMD_MII_READ_OPMODE 0x1009 +#define AXE_CMD_MII_OPMODE_HW 0x010A +#define AXE_CMD_SROM_READ 0x200B +#define AXE_CMD_SROM_WRITE 0x010C +#define AXE_CMD_SROM_WR_ENABLE 0x010D +#define AXE_CMD_SROM_WR_DISABLE 0x010E +#define AXE_CMD_RXCTL_READ 0x200F +#define AXE_CMD_RXCTL_WRITE 0x0110 +#define AXE_CMD_READ_IPG012 0x3011 +#define AXE_172_CMD_WRITE_IPG0 0x0112 +#define AXE_178_CMD_WRITE_IPG012 0x0112 +#define AXE_172_CMD_WRITE_IPG1 0x0113 +#define AXE_178_CMD_READ_NODEID 0x6013 +#define AXE_172_CMD_WRITE_IPG2 0x0114 +#define AXE_178_CMD_WRITE_NODEID 0x6114 +#define AXE_CMD_READ_MCAST 0x8015 +#define AXE_CMD_WRITE_MCAST 0x8116 +#define AXE_172_CMD_READ_NODEID 0x6017 +#define AXE_172_CMD_WRITE_NODEID 0x6118 + +#define AXE_CMD_READ_PHYID 0x2019 +#define AXE_172_CMD_READ_MEDIA 0x101A +#define AXE_178_CMD_READ_MEDIA 0x201A +#define AXE_CMD_WRITE_MEDIA 0x011B +#define AXE_CMD_READ_MONITOR_MODE 0x101C +#define AXE_CMD_WRITE_MONITOR_MODE 0x011D +#define AXE_CMD_READ_GPIO 0x101E +#define AXE_CMD_WRITE_GPIO 0x011F + +#define AXE_CMD_SW_RESET_REG 0x0120 +#define AXE_CMD_SW_PHY_STATUS 0x0021 +#define AXE_CMD_SW_PHY_SELECT 0x0122 + +#define AXE_SW_RESET_CLEAR 0x00 +#define AXE_SW_RESET_RR 0x01 +#define AXE_SW_RESET_RT 0x02 +#define AXE_SW_RESET_PRTE 0x04 +#define AXE_SW_RESET_PRL 0x08 +#define AXE_SW_RESET_BZ 0x10 +#define AXE_SW_RESET_IPRL 0x20 +#define AXE_SW_RESET_IPPD 0x40 + +/* AX88178 documentation says to always write this bit... */ +#define AXE_178_RESET_MAGIC 0x40 + +#define AXE_178_MEDIA_GMII 0x0001 +#define AXE_MEDIA_FULL_DUPLEX 0x0002 +#define AXE_172_MEDIA_TX_ABORT_ALLOW 0x0004 + +/* AX88178/88772 documentation says to always write 1 to bit 2 */ +#define AXE_178_MEDIA_MAGIC 0x0004 +/* AX88772 documentation says to always write 0 to bit 3 */ +#define AXE_178_MEDIA_ENCK 0x0008 +#define AXE_172_MEDIA_FLOW_CONTROL_EN 0x0010 +#define AXE_178_MEDIA_RXFLOW_CONTROL_EN 0x0010 +#define AXE_178_MEDIA_TXFLOW_CONTROL_EN 0x0020 +#define AXE_178_MEDIA_JUMBO_EN 0x0040 +#define AXE_178_MEDIA_LTPF_ONLY 0x0080 +#define AXE_178_MEDIA_RX_EN 0x0100 +#define AXE_178_MEDIA_100TX 0x0200 +#define AXE_178_MEDIA_SBP 0x0800 +#define AXE_178_MEDIA_SUPERMAC 0x1000 + +#define AXE_RXCMD_PROMISC 0x0001 +#define AXE_RXCMD_ALLMULTI 0x0002 +#define AXE_172_RXCMD_UNICAST 0x0004 +#define AXE_178_RXCMD_KEEP_INVALID_CRC 0x0004 +#define AXE_RXCMD_BROADCAST 0x0008 +#define AXE_RXCMD_MULTICAST 0x0010 +#define AXE_RXCMD_ENABLE 0x0080 +#define AXE_178_RXCMD_MFB_MASK 0x0300 +#define AXE_178_RXCMD_MFB_2048 0x0000 +#define AXE_178_RXCMD_MFB_4096 0x0100 +#define AXE_178_RXCMD_MFB_8192 0x0200 +#define AXE_178_RXCMD_MFB_16384 0x0300 + +#define AXE_NOPHY 0xE0 +#define AXE_INTPHY 0x10 + +#define AXE_BULK_BUF_SIZE 16384 /* bytes */ + +#define AXE_CTL_READ 0x01 +#define AXE_CTL_WRITE 0x02 + +#define AXE_CONFIG_IDX 0 /* config number 1 */ +#define AXE_IFACE_IDX 0 + +/* The interrupt endpoint is currently unused by the ASIX part. */ +#define AXE_ENDPT_MAX 6 + +struct axe_sframe_hdr { + uint16_t len; + uint16_t ilen; +} __packed; + +#define GET_MII(sc) ((sc)->sc_miibus ? \ + device_get_softc((sc)->sc_miibus) : NULL) + +struct axe_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct usb2_config_td sc_config_td; + struct usb2_callout sc_watchdog; + struct mtx sc_mtx; + + struct ifnet *sc_ifp; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[AXE_ENDPT_MAX]; + device_t sc_miibus; + device_t sc_dev; + + uint32_t sc_unit; + uint32_t sc_media_active; + uint32_t sc_media_status; + + uint16_t sc_flags; +#define AXE_FLAG_WAIT_LINK 0x0001 +#define AXE_FLAG_INTR_STALL 0x0002 +#define AXE_FLAG_READ_STALL 0x0004 +#define AXE_FLAG_WRITE_STALL 0x0008 +#define AXE_FLAG_LL_READY 0x0010 +#define AXE_FLAG_HL_READY 0x0020 +#define AXE_FLAG_772 0x0040 /* AX88772 */ +#define AXE_FLAG_178 0x0080 /* AX88178 */ + + uint8_t sc_ipgs[3]; + uint8_t sc_phyaddrs[2]; + + uint8_t sc_name[16]; +}; diff --git a/sys/dev/usb2/ethernet/if_cdce2.c b/sys/dev/usb2/ethernet/if_cdce2.c new file mode 100644 index 000000000000..64eedca17e99 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_cdce2.c @@ -0,0 +1,1355 @@ +/* $NetBSD: if_cdce.c,v 1.4 2004/10/24 12:50:54 augustss Exp $ */ + +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 Bill Paul + * Copyright (c) 2003-2005 Craig Boston + * Copyright (c) 2004 Daniel Hartmeier + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul, THE VOICES IN HIS HEAD OR + * THE 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. + */ + +/* + * USB Communication Device Class (Ethernet Networking Control Model) + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#define usb2_config_td_cc usb2_ether_cc +#define usb2_config_td_softc cdce_softc + +#define USB_DEBUG_VAR cdce_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static device_probe_t cdce_probe; +static device_attach_t cdce_attach; +static device_detach_t cdce_detach; +static device_shutdown_t cdce_shutdown; +static device_suspend_t cdce_suspend; +static device_resume_t cdce_resume; +static usb2_handle_request_t cdce_handle_request; + +static usb2_callback_t cdce_bulk_write_callback; +static usb2_callback_t cdce_bulk_read_callback; +static usb2_callback_t cdce_intr_read_callback; +static usb2_callback_t cdce_intr_write_callback; + +static void cdce_start_cb(struct ifnet *ifp); +static void cdce_start_transfers(struct cdce_softc *sc); +static uint32_t cdce_m_crc32(struct mbuf *m, uint32_t src_offset, uint32_t src_len); +static void cdce_stop(struct cdce_softc *sc); +static int cdce_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data); +static void cdce_init_cb(void *arg); +static int cdce_ifmedia_upd_cb(struct ifnet *ifp); +static void cdce_ifmedia_sts_cb(struct ifnet *const ifp, struct ifmediareq *req); + +#if USB_DEBUG +static int cdce_debug = 0; +static int cdce_force_512x4 = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, cdce, CTLFLAG_RW, 0, "USB cdce"); +SYSCTL_INT(_hw_usb2_cdce, OID_AUTO, debug, CTLFLAG_RW, &cdce_debug, 0, + "cdce debug level"); +SYSCTL_INT(_hw_usb2_cdce, OID_AUTO, force_512x4, CTLFLAG_RW, + &cdce_force_512x4, 0, "cdce force 512x4 protocol"); +#endif + +static const struct usb2_config cdce_config[CDCE_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .if_index = 0, + /* Host Mode */ + .mh.frames = CDCE_512X4_FRAGS_MAX + 1, + .mh.bufsize = (CDCE_512X4_FRAMES_MAX * MCLBYTES) + sizeof(struct usb2_cdc_mf_eth_512x4_header), + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, + .mh.callback = &cdce_bulk_write_callback, + .mh.timeout = 10000, /* 10 seconds */ + /* Device Mode */ + .md.frames = CDCE_512X4_FRAGS_MAX + 1, + .md.bufsize = (CDCE_512X4_FRAMES_MAX * MCLBYTES) + sizeof(struct usb2_cdc_mf_eth_512x4_header), + .md.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.ext_buffer = 1,}, + .md.callback = &cdce_bulk_read_callback, + .md.timeout = 0, /* no timeout */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .if_index = 0, + /* Host Mode */ + .mh.frames = CDCE_512X4_FRAGS_MAX + 1, + .mh.bufsize = (CDCE_512X4_FRAMES_MAX * MCLBYTES) + sizeof(struct usb2_cdc_mf_eth_512x4_header), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.ext_buffer = 1,}, + .mh.callback = &cdce_bulk_read_callback, + .mh.timeout = 0, /* no timeout */ + /* Device Mode */ + .md.frames = CDCE_512X4_FRAGS_MAX + 1, + .md.bufsize = (CDCE_512X4_FRAMES_MAX * MCLBYTES) + sizeof(struct usb2_cdc_mf_eth_512x4_header), + .md.flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, + .md.callback = &cdce_bulk_write_callback, + .md.timeout = 10000, /* 10 seconds */ + }, + + [2] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .if_index = 1, + /* Host Mode */ + .mh.bufsize = CDCE_IND_SIZE_MAX, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .mh.callback = &cdce_intr_read_callback, + .mh.timeout = 0, + /* Device Mode */ + .md.bufsize = CDCE_IND_SIZE_MAX, + .md.flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, + .md.callback = &cdce_intr_write_callback, + .md.timeout = 10000, /* 10 seconds */ + }, +}; + +static device_method_t cdce_methods[] = { + /* USB interface */ + DEVMETHOD(usb2_handle_request, cdce_handle_request), + + /* Device interface */ + DEVMETHOD(device_probe, cdce_probe), + DEVMETHOD(device_attach, cdce_attach), + DEVMETHOD(device_detach, cdce_detach), + DEVMETHOD(device_suspend, cdce_suspend), + DEVMETHOD(device_resume, cdce_resume), + DEVMETHOD(device_shutdown, cdce_shutdown), + + {0, 0} +}; + +static driver_t cdce_driver = { + .name = "cdce", + .methods = cdce_methods, + .size = sizeof(struct cdce_softc), +}; + +static devclass_t cdce_devclass; + +DRIVER_MODULE(cdce, ushub, cdce_driver, cdce_devclass, NULL, 0); +MODULE_VERSION(cdce, 1); +MODULE_DEPEND(cdce, usb2_ethernet, 1, 1, 1); +MODULE_DEPEND(cdce, usb2_core, 1, 1, 1); +MODULE_DEPEND(cdce, ether, 1, 1, 1); + +static const struct usb2_device_id cdce_devs[] = { + {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, 0)}, + {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_MOBILE_DIRECT_LINE_MODEL, 0)}, + + {USB_VPI(USB_VENDOR_ACERLABS, USB_PRODUCT_ACERLABS_M5632, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_NTL_250, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQLINUX, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_GMATE, USB_PRODUCT_GMATE_YP3X00, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN2, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_ETHERNETGADGET, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2501, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5500, CDCE_FLAG_ZAURUS)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5600, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLA300, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC700, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC750, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, +}; + +static int +cdce_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + return (usb2_lookup_id_by_uaa(cdce_devs, sizeof(cdce_devs), uaa)); +} + +static int +cdce_attach(device_t dev) +{ + struct cdce_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_interface *iface; + const struct usb2_cdc_union_descriptor *ud; + const struct usb2_cdc_ethernet_descriptor *ue; + const struct usb2_interface_descriptor *id; + struct ifnet *ifp; + int error; + uint8_t alt_index; + uint8_t i; + uint8_t eaddr[ETHER_ADDR_LEN]; + char eaddr_str[5 * ETHER_ADDR_LEN]; /* approx */ + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + + /* search for alternate settings */ + if (uaa->usb2_mode == USB_MODE_HOST) { + + struct usb2_descriptor *desc; + struct usb2_config_descriptor *cd; + + cd = usb2_get_config_descriptor(uaa->device); + desc = (void *)(uaa->iface->idesc); + id = (void *)desc; + i = id->bInterfaceNumber; + alt_index = 0; + while ((desc = usb2_desc_foreach(cd, desc))) { + id = (void *)desc; + if ((id->bDescriptorType == UDESC_INTERFACE) && + (id->bLength >= sizeof(*id))) { + if (id->bInterfaceNumber != i) { + alt_index = 0; + break; + } + if ((id->bInterfaceClass == UICLASS_CDC) && + (id->bInterfaceSubClass == + UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL) && + (id->bInterfaceProtocol == UIPROTO_CDC_ETH_512X4)) { + + alt_index = id->bAlternateSetting; + /* + * We want this alt setting hence + * the protocol supports multi + * sub-framing ! + */ + break; + } + } + } + + if (alt_index > 0) { + + error = usb2_set_alt_interface_index(uaa->device, + uaa->info.bIfaceIndex, alt_index); + if (error) { + device_printf(dev, "Could not set alternate " + "setting, error = %s\n", usb2_errstr(error)); + return (EINVAL); + } + } + } + /* get the interface subclass we are using */ + sc->sc_iface_protocol = uaa->iface->idesc->bInterfaceProtocol; +#if USB_DEBUG + if (cdce_force_512x4) { + sc->sc_iface_protocol = UIPROTO_CDC_ETH_512X4; + } +#endif + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "cdce lock", NULL, MTX_DEF | MTX_RECURSE); + + if (sc->sc_flags & CDCE_FLAG_NO_UNION) { + sc->sc_ifaces_index[0] = uaa->info.bIfaceIndex; + sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; + sc->sc_data_iface_no = 0; /* not used */ + goto alloc_transfers; + } + ud = usb2_find_descriptor + (uaa->device, NULL, uaa->info.bIfaceIndex, + UDESC_CS_INTERFACE, 0 - 1, UDESCSUB_CDC_UNION, 0 - 1); + + if ((ud == NULL) || (ud->bLength < sizeof(*ud))) { + device_printf(dev, "no union descriptor!\n"); + goto detach; + } + sc->sc_data_iface_no = ud->bSlaveInterface[0]; + + for (i = 0;; i++) { + + iface = usb2_get_iface(uaa->device, i); + + if (iface) { + + id = usb2_get_interface_descriptor(iface); + + if (id && (id->bInterfaceNumber == + sc->sc_data_iface_no)) { + sc->sc_ifaces_index[0] = i; + sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; + usb2_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + break; + } + } else { + device_printf(dev, "no data interface found!\n"); + goto detach; + } + } + + /* + * + * + * The Data Class interface of a networking device shall have + * a minimum of two interface settings. The first setting + * (the default interface setting) includes no endpoints and + * therefore no networking traffic is exchanged whenever the + * default interface setting is selected. One or more + * additional interface settings are used for normal + * operation, and therefore each includes a pair of endpoints + * (one IN, and one OUT) to exchange network traffic. Select + * an alternate interface setting to initialize the network + * aspects of the device and to enable the exchange of + * network traffic. + * + * + * + * Some devices, most notably cable modems, include interface + * settings that have no IN or OUT endpoint, therefore loop + * through the list of all available interface settings + * looking for one with both IN and OUT endpoints. + */ + +alloc_transfers: + + for (i = 0; i < 32; i++) { + + error = usb2_set_alt_interface_index + (uaa->device, sc->sc_ifaces_index[0], i); + + if (error) { + device_printf(dev, "no valid alternate " + "setting found!\n"); + goto detach; + } + error = usb2_transfer_setup + (uaa->device, sc->sc_ifaces_index, + sc->sc_xfer, cdce_config, CDCE_N_TRANSFER, + sc, &sc->sc_mtx); + + if (error == 0) { + break; + } + } + + ifmedia_init(&sc->sc_ifmedia, 0, + &cdce_ifmedia_upd_cb, + &cdce_ifmedia_sts_cb); + + ue = usb2_find_descriptor + (uaa->device, NULL, uaa->info.bIfaceIndex, + UDESC_CS_INTERFACE, 0 - 1, UDESCSUB_CDC_ENF, 0 - 1); + + if ((ue == NULL) || (ue->bLength < sizeof(*ue))) { + error = USB_ERR_INVAL; + } else { + error = usb2_req_get_string_any + (uaa->device, &Giant, eaddr_str, + sizeof(eaddr_str), ue->iMacAddress); + } + + if (error) { + + /* fake MAC address */ + + device_printf(dev, "faking MAC address\n"); + eaddr[0] = 0x2a; + memcpy(&eaddr[1], &ticks, sizeof(uint32_t)); + eaddr[5] = sc->sc_unit; + + } else { + + bzero(eaddr, sizeof(eaddr)); + + for (i = 0; i < (ETHER_ADDR_LEN * 2); i++) { + + char c = eaddr_str[i]; + + if ((c >= '0') && (c <= '9')) { + c -= '0'; + } else if (c != 0) { + c -= 'A' - 10; + } else { + break; + } + + c &= 0xf; + + if ((i & 1) == 0) { + c <<= 4; + } + eaddr[i / 2] |= c; + } + + if (uaa->usb2_mode == USB_MODE_DEVICE) { + /* + * Do not use the same MAC address like the peer ! + */ + eaddr[5] ^= 0xFF; + } + } + + ifp = if_alloc(IFT_ETHER); + + if (ifp == NULL) { + device_printf(dev, "cannot if_alloc()\n"); + goto detach; + } + sc->sc_evilhack = ifp; + + ifp->if_softc = sc; + if_initname(ifp, "cdce", sc->sc_unit); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = cdce_ioctl_cb; + ifp->if_output = ether_output; + ifp->if_start = cdce_start_cb; + ifp->if_init = cdce_init_cb; + ifp->if_baudrate = 11000000; + if (sc->sc_iface_protocol == UIPROTO_CDC_ETH_512X4) { + IFQ_SET_MAXLEN(&ifp->if_snd, CDCE_512X4_IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = CDCE_512X4_IFQ_MAXLEN; + } else { + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + } + IFQ_SET_READY(&ifp->if_snd); + + /* no IFM type for 11Mbps USB, so go with 10baseT */ + ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_10_T, 0, 0); + ifmedia_set(&sc->sc_ifmedia, IFM_ETHER | IFM_10_T); + + sc->sc_ifp = ifp; + + ether_ifattach(ifp, eaddr); + + /* start the interrupt transfer, if any */ + mtx_lock(&sc->sc_mtx); + usb2_transfer_start(sc->sc_xfer[2]); + mtx_unlock(&sc->sc_mtx); + + return (0); /* success */ + +detach: + cdce_detach(dev); + return (ENXIO); /* failure */ +} + +static int +cdce_detach(device_t dev) +{ + struct cdce_softc *sc = device_get_softc(dev); + struct ifnet *ifp; + + mtx_lock(&sc->sc_mtx); + + cdce_stop(sc); + + ifp = sc->sc_ifp; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, CDCE_N_TRANSFER); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + ether_ifdetach(ifp); + if_free(ifp); + ifmedia_removeall(&sc->sc_ifmedia); + } + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +cdce_start_cb(struct ifnet *ifp) +{ + struct cdce_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + cdce_start_transfers(sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +cdce_start_transfers(struct cdce_softc *sc) +{ + if ((sc->sc_flags & CDCE_FLAG_LL_READY) && + (sc->sc_flags & CDCE_FLAG_HL_READY)) { + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + } + return; +} + +static uint32_t +cdce_m_frags(struct mbuf *m) +{ + uint32_t temp = 1; + + while ((m = m->m_next)) { + temp++; + } + return (temp); +} + +static void +cdce_fwd_mq(struct cdce_softc *sc, struct cdce_mq *mq) +{ + struct mbuf *m; + struct ifnet *ifp = sc->sc_ifp; + + if (mq->ifq_head) { + + mtx_unlock(&sc->sc_mtx); + + while (1) { + + _IF_DEQUEUE(mq, m); + + if (m == NULL) + break; + + (ifp->if_input) (ifp, m); + } + + mtx_lock(&sc->sc_mtx); + } + return; +} + +static void +cdce_free_mq(struct cdce_mq *mq) +{ + struct mbuf *m; + + if (mq->ifq_head) { + + while (1) { + + _IF_DEQUEUE(mq, m); + + if (m == NULL) + break; + + m_freem(m); + } + } + return; +} + +static void +cdce_bulk_write_512x4_callback(struct usb2_xfer *xfer) +{ + struct cdce_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + struct mbuf *mt; + uint16_t x; + uint16_t y; + uint16_t flen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete: " + "%u bytes in %u fragments and %u frames\n", + xfer->actlen, xfer->nframes, sc->sc_tx_mq.ifq_len); + + /* update packet counter */ + ifp->if_opackets += sc->sc_tx_mq.ifq_len; + + /* free all previous mbufs */ + cdce_free_mq(&sc->sc_tx_mq); + + case USB_ST_SETUP: +tr_setup: + x = 0; /* number of frames */ + y = 1; /* number of fragments */ + + while (x != CDCE_512X4_FRAMES_MAX) { + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) { + break; + } + if (m->m_pkthdr.len > MCLBYTES) { + m_freem(m); + ifp->if_oerrors++; + continue; + } + if (cdce_m_frags(m) > CDCE_512X4_FRAME_FRAG_MAX) { + mt = m_defrag(m, M_DONTWAIT); + if (mt == NULL) { + m_freem(m); + ifp->if_oerrors++; + continue; + } + m = mt; + } + _IF_ENQUEUE(&sc->sc_tx_mq, m); + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + +#if (CDCE_512X4_FRAG_LENGTH_MASK < MCLBYTES) +#error "(CDCE_512X4_FRAG_LENGTH_MASK < MCLBYTES)" +#endif + do { + + flen = m->m_len & CDCE_512X4_FRAG_LENGTH_MASK; + xfer->frlengths[y] = m->m_len; + usb2_set_frame_data(xfer, m->m_data, y); + + if (m->m_next == NULL) { + flen |= CDCE_512X4_FRAG_LAST_MASK; + } + USETW(sc->sc_tx.hdr.wFragLength[y - 1], flen); + + y++; + + } while ((m = m->m_next)); + + x++; + } + + if (y == 1) { + /* no data to transmit */ + break; + } + /* fill in Signature */ + sc->sc_tx.hdr.bSig[0] = 'F'; + sc->sc_tx.hdr.bSig[1] = 'L'; + + /* + * We ensure that the header results in a short packet by + * making the length odd ! + */ + USETW(sc->sc_tx.hdr.wFragLength[y - 1], 0); + xfer->frlengths[0] = CDCE_512X4_FRAG_LENGTH_OFFSET + ((y - 1) * 2) + 1; + usb2_set_frame_data(xfer, &sc->sc_tx.hdr, 0); + xfer->nframes = y; + usb2_start_hardware(xfer); + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + /* update error counter */ + ifp->if_oerrors += sc->sc_tx_mq.ifq_len; + + /* free all previous mbufs */ + cdce_free_mq(&sc->sc_tx_mq); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + break; + } + return; +} + +static void +cdce_bulk_write_std_callback(struct usb2_xfer *xfer) +{ + struct cdce_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + struct mbuf *mt; + uint32_t crc; + + DPRINTFN(1, "\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete: " + "%u bytes in %u frames\n", xfer->actlen, + xfer->aframes); + + ifp->if_opackets++; + + /* free all previous mbufs */ + cdce_free_mq(&sc->sc_tx_mq); + + case USB_ST_SETUP: +tr_setup: + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) { + break; + } + if (sc->sc_flags & CDCE_FLAG_ZAURUS) { + /* + * Zaurus wants a 32-bit CRC appended to + * every frame + */ + + crc = cdce_m_crc32(m, 0, m->m_pkthdr.len); + crc = htole32(crc); + + if (!m_append(m, 4, (void *)&crc)) { + m_freem(m); + ifp->if_oerrors++; + goto tr_setup; + } + } + if (m->m_len != m->m_pkthdr.len) { + mt = m_defrag(m, M_DONTWAIT); + if (mt == NULL) { + m_freem(m); + ifp->if_oerrors++; + goto tr_setup; + } + m = mt; + } + if (m->m_pkthdr.len > MCLBYTES) { + m->m_pkthdr.len = MCLBYTES; + } + _IF_ENQUEUE(&sc->sc_tx_mq, m); + + xfer->frlengths[0] = m->m_len; + usb2_set_frame_data(xfer, m->m_data, 0); + xfer->nframes = 1; + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + usb2_start_hardware(xfer); + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + /* free all previous mbufs */ + cdce_free_mq(&sc->sc_tx_mq); + ifp->if_oerrors++; + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + break; + } + return; +} + +static void +cdce_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct cdce_softc *sc = xfer->priv_sc; + + /* first call - set the correct callback */ + if (sc->sc_iface_protocol == UIPROTO_CDC_ETH_512X4) { + xfer->flags.force_short_xfer = 0; + xfer->callback = &cdce_bulk_write_512x4_callback; + } else { + xfer->callback = &cdce_bulk_write_std_callback; + } + (xfer->callback) (xfer); + return; +} + +static int32_t +#ifdef __FreeBSD__ +cdce_m_crc32_cb(void *arg, void *src, uint32_t count) +#else +cdce_m_crc32_cb(void *arg, caddr_t src, uint32_t count) +#endif +{ + register uint32_t *p_crc = arg; + + *p_crc = crc32_raw(src, count, *p_crc); + return (0); +} + +static uint32_t +cdce_m_crc32(struct mbuf *m, uint32_t src_offset, uint32_t src_len) +{ + register int error; + uint32_t crc = 0xFFFFFFFF; + + error = m_apply(m, src_offset, src_len, &cdce_m_crc32_cb, &crc); + return (crc ^ 0xFFFFFFFF); +} + +static void +cdce_stop(struct cdce_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(CDCE_FLAG_HL_READY | + CDCE_FLAG_LL_READY); + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static int +cdce_shutdown(device_t dev) +{ + struct cdce_softc *sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + + cdce_stop(sc); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static int +cdce_suspend(device_t dev) +{ + device_printf(dev, "Suspending\n"); + return (0); +} + +static int +cdce_resume(device_t dev) +{ + device_printf(dev, "Resuming\n"); + return (0); +} + +static int +cdce_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct cdce_softc *sc = ifp->if_softc; + int error = 0; + + switch (command) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + cdce_init_cb(sc); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + cdce_stop(sc); + } + } + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCSIFMEDIA: + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, (void *)data, + &sc->sc_ifmedia, command); + break; + + default: + error = ether_ioctl(ifp, command, data); + break; + } + return (error); +} + +static void +cdce_init_cb(void *arg) +{ + struct cdce_softc *sc = arg; + struct ifnet *ifp; + + mtx_lock(&sc->sc_mtx); + + ifp = sc->sc_ifp; + + /* immediate configuration */ + + cdce_stop(sc); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= ( + CDCE_FLAG_LL_READY | + CDCE_FLAG_HL_READY); + + usb2_transfer_set_stall(sc->sc_xfer[0]); + usb2_transfer_set_stall(sc->sc_xfer[1]); + + cdce_start_transfers(sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +cdce_bulk_read_512x4_callback(struct usb2_xfer *xfer) +{ + struct cdce_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + void *data_ptr; + uint32_t offset; + uint16_t x; + uint16_t y; + uint16_t z; + uint16_t rx_frags; + uint16_t flen; + uint8_t fwd_mq; + uint8_t free_mq; + + fwd_mq = 0; + free_mq = 0; + rx_frags = 0; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("received %u bytes in %u frames\n", + xfer->actlen, xfer->aframes); + + /* check state */ + if (!(sc->sc_flags & CDCE_FLAG_RX_DATA)) { + + /* verify the header */ + if ((xfer->actlen < CDCE_512X4_FRAG_LENGTH_OFFSET) || + (sc->sc_rx.hdr.bSig[0] != 'F') || + (sc->sc_rx.hdr.bSig[1] != 'L')) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + rx_frags = (xfer->actlen - + CDCE_512X4_FRAG_LENGTH_OFFSET) / 2; + if (rx_frags != 0) { + /* start receiving data */ + sc->sc_flags |= CDCE_FLAG_RX_DATA; + } + DPRINTF("doing %u fragments\n", rx_frags); + + } else { + /* we are done receiving data */ + sc->sc_flags &= ~CDCE_FLAG_RX_DATA; + fwd_mq = 1; + } + + case USB_ST_SETUP: +tr_setup: + if (xfer->flags.stall_pipe) { + /* we are done */ + sc->sc_flags &= ~CDCE_FLAG_RX_DATA; + } + /* we expect a Multi Frame Ethernet Header */ + if (!(sc->sc_flags & CDCE_FLAG_RX_DATA)) { + DPRINTF("expecting length header\n"); + usb2_set_frame_data(xfer, &sc->sc_rx.hdr, 0); + xfer->frlengths[0] = sizeof(sc->sc_rx.hdr); + xfer->nframes = 1; + xfer->flags.short_xfer_ok = 1; + usb2_start_hardware(xfer); + free_mq = 1; + break; + } + /* verify number of fragments */ + if (rx_frags > CDCE_512X4_FRAGS_MAX) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + /* check if the last fragment does not complete a frame */ + x = rx_frags - 1; + flen = UGETW(sc->sc_rx.hdr.wFragLength[x]); + if (!(flen & CDCE_512X4_FRAG_LAST_MASK)) { + DPRINTF("no last frag mask\n"); + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + /* + * Setup a new USB transfer chain to receive all the + * IP-frame fragments, automagically defragged : + */ + x = 0; + y = 0; + while (1) { + + z = x; + offset = 0; + + /* precompute the frame length */ + while (1) { + flen = UGETW(sc->sc_rx.hdr.wFragLength[z]); + offset += (flen & CDCE_512X4_FRAG_LENGTH_MASK); + if (flen & CDCE_512X4_FRAG_LAST_MASK) { + break; + } + z++; + } + + if (offset >= sizeof(struct ether_header)) { + /* + * allocate a suitable memory buffer, if + * possible + */ + if (offset > (MCLBYTES - ETHER_ALIGN)) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } if (offset > (MHLEN - ETHER_ALIGN)) { + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + } else { + m = m_gethdr(M_DONTWAIT, MT_DATA); + } + } else { + m = NULL; /* dump it */ + } + + DPRINTFN(17, "frame %u, length = %u \n", y, offset); + + /* check if we have a buffer */ + if (m) { + m->m_data = USB_ADD_BYTES(m->m_data, ETHER_ALIGN); + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = offset; + + /* enqueue */ + _IF_ENQUEUE(&sc->sc_rx_mq, m); + + data_ptr = m->m_data; + ifp->if_ipackets++; + } else { + data_ptr = sc->sc_rx.data; + ifp->if_ierrors++; + } + + /* setup the RX chain */ + offset = 0; + while (1) { + + flen = UGETW(sc->sc_rx.hdr.wFragLength[x]); + + usb2_set_frame_data(xfer, + USB_ADD_BYTES(data_ptr, offset), x); + + xfer->frlengths[x] = + (flen & CDCE_512X4_FRAG_LENGTH_MASK); + + DPRINTFN(17, "length[%u] = %u\n", + x, xfer->frlengths[x]); + + offset += xfer->frlengths[x]; + + x++; + + if (flen & CDCE_512X4_FRAG_LAST_MASK) { + break; + } + } + + y++; + + if (x == rx_frags) { + break; + } + if (y == CDCE_512X4_FRAMES_MAX) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + } + + DPRINTF("nframes = %u\n", x); + + xfer->nframes = x; + xfer->flags.short_xfer_ok = 0; + usb2_start_hardware(xfer); + break; + + default: /* Error */ + DPRINTF("error = %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + free_mq = 1; + break; + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! + * + * + * By safe we mean that if "usb2_transfer_stop()" is called, + * we will get a callback having the error code + * USB_ERR_CANCELLED. + */ + if (fwd_mq) { + cdce_fwd_mq(sc, &sc->sc_rx_mq); + } + if (free_mq) { + cdce_free_mq(&sc->sc_rx_mq); + } + return; +} + +static void +cdce_bulk_read_std_callback(struct usb2_xfer *xfer) +{ + struct cdce_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + struct mbuf *m_rx = NULL; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("received %u bytes in %u frames\n", + xfer->actlen, xfer->aframes); + + if (sc->sc_flags & CDCE_FLAG_ZAURUS) { + + /* Strip off CRC added by Zaurus */ + if (xfer->frlengths[0] >= MAX(4, 14)) { + xfer->frlengths[0] -= 4; + } + } + _IF_DEQUEUE(&sc->sc_rx_mq, m); + + if (m) { + + if (xfer->frlengths[0] < sizeof(struct ether_header)) { + m_freem(m); + goto tr_setup; + } + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = xfer->frlengths[0]; + m_rx = m; + } + case USB_ST_SETUP: +tr_setup: + m = usb2_ether_get_mbuf(); + if (m == NULL) { + + /* + * We are out of mbufs and need to dump all the + * received data ! + */ + usb2_set_frame_data(xfer, &sc->sc_rx.data, 0); + xfer->frlengths[0] = sizeof(sc->sc_rx.data); + + } else { + usb2_set_frame_data(xfer, m->m_data, 0); + xfer->frlengths[0] = m->m_len; + _IF_ENQUEUE(&sc->sc_rx_mq, m); + } + xfer->nframes = 1; + usb2_start_hardware(xfer); + break; + + default: /* Error */ + DPRINTF("error = %s\n", + usb2_errstr(xfer->error)); + + /* free all mbufs */ + cdce_free_mq(&sc->sc_rx_mq); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + return; + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "if_input" here, and not some lines up! + * + * By safe we mean that if "usb2_transfer_stop()" is called, + * we will get a callback having the error code + * USB_ERR_CANCELLED. + */ + if (m_rx) { + mtx_unlock(&sc->sc_mtx); + (ifp->if_input) (ifp, m_rx); + mtx_lock(&sc->sc_mtx); + } + return; +} + +static void +cdce_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct cdce_softc *sc = xfer->priv_sc; + + /* first call - set the correct callback */ + if (sc->sc_iface_protocol == UIPROTO_CDC_ETH_512X4) { + xfer->callback = &cdce_bulk_read_512x4_callback; + } else { + xfer->callback = &cdce_bulk_read_std_callback; + } + (xfer->callback) (xfer); + return; +} + +static int +cdce_ifmedia_upd_cb(struct ifnet *ifp) +{ + /* no-op, cdce has only 1 possible media type */ + return (0); +} + +static void +cdce_ifmedia_sts_cb(struct ifnet *const ifp, struct ifmediareq *req) +{ + + req->ifm_status = IFM_AVALID | IFM_ACTIVE; + req->ifm_active = IFM_ETHER | IFM_10_T; +} + +static void +cdce_intr_read_callback(struct usb2_xfer *xfer) +{ + ; /* style fix */ + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("Received %d bytes\n", + xfer->actlen); + + /* TODO: decode some indications */ + + case USB_ST_SETUP: +tr_setup: + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + break; + } + return; +} + +static void +cdce_intr_write_callback(struct usb2_xfer *xfer) +{ + ; /* style fix */ + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("Transferred %d bytes\n", xfer->actlen); + + case USB_ST_SETUP: +tr_setup: +#if 0 + xfer->frlengths[0] = XXX; + usb2_start_hardware(xfer); +#endif + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + break; + } + return; +} + +static int +cdce_handle_request(device_t dev, + const void *req, void **pptr, uint16_t *plen, + uint16_t offset, uint8_t is_complete) +{ + return (ENXIO); /* use builtin handler */ +} diff --git a/sys/dev/usb2/ethernet/if_cdce2_reg.h b/sys/dev/usb2/ethernet/if_cdce2_reg.h new file mode 100644 index 000000000000..3b5f45da101c --- /dev/null +++ b/sys/dev/usb2/ethernet/if_cdce2_reg.h @@ -0,0 +1,87 @@ +/*- + * Copyright (c) 2003-2005 Craig Boston + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul, THE VOICES IN HIS HEAD OR + * THE 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. + * + * $FreeBSD$ + */ + +#ifndef _USB_IF_CDCEREG_H_ +#define _USB_IF_CDCEREG_H_ + +#define CDCE_N_TRANSFER 3 /* units */ +#define CDCE_IND_SIZE_MAX 32 /* bytes */ +#define CDCE_512X4_IFQ_MAXLEN MAX((2*CDCE_512X4_FRAMES_MAX), IFQ_MAXLEN) + +union cdce_eth_rx { /* multiframe header */ + struct usb2_cdc_mf_eth_512x4_header hdr; + uint8_t data[MCLBYTES]; +} __aligned(USB_HOST_ALIGN); + +union cdce_eth_tx { /* multiframe header */ + struct usb2_cdc_mf_eth_512x4_header hdr; +} __aligned(USB_HOST_ALIGN); + +struct cdce_mq { /* mini-queue */ + struct mbuf *ifq_head; + struct mbuf *ifq_tail; + uint16_t ifq_len; +}; + +struct cdce_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + union cdce_eth_tx sc_tx; + union cdce_eth_rx sc_rx; + struct ifmedia sc_ifmedia; + struct mtx sc_mtx; + struct cdce_mq sc_rx_mq; + struct cdce_mq sc_tx_mq; + + struct ifnet *sc_ifp; + struct usb2_xfer *sc_xfer[CDCE_N_TRANSFER]; + struct usb2_device *sc_udev; + device_t sc_dev; + + uint32_t sc_unit; + + uint16_t sc_flags; +#define CDCE_FLAG_ZAURUS 0x0001 +#define CDCE_FLAG_NO_UNION 0x0002 +#define CDCE_FLAG_LL_READY 0x0004 +#define CDCE_FLAG_HL_READY 0x0008 +#define CDCE_FLAG_RX_DATA 0x0010 + + uint8_t sc_name[16]; + uint8_t sc_data_iface_no; + uint8_t sc_ifaces_index[2]; + uint8_t sc_iface_protocol; +}; + +#endif /* _USB_IF_CDCEREG_H_ */ diff --git a/sys/dev/usb2/ethernet/if_cue2.c b/sys/dev/usb2/ethernet/if_cue2.c new file mode 100644 index 000000000000..e0e0726af405 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_cue2.c @@ -0,0 +1,965 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * CATC USB-EL1210A USB to ethernet driver. Used in the CATC Netmate + * adapters and others. + * + * Written by Bill Paul + * Electrical Engineering Department + * Columbia University, New York City + */ + +/* + * The CATC USB-EL1210A provides USB ethernet support at 10Mbps. The + * RX filter uses a 512-bit multicast hash table, single perfect entry + * for the station address, and promiscuous mode. Unlike the ADMtek + * and KLSI chips, the CATC ASIC supports read and write combining + * mode where multiple packets can be transfered using a single bulk + * transaction, which helps performance a great deal. + */ + +/* + * NOTE: all function names beginning like "cue_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include + +#define usb2_config_td_cc usb2_ether_cc +#define usb2_config_td_softc cue_softc + +#define USB_DEBUG_VAR cue_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Various supported device vendors/products. + */ + +/* Belkin F5U111 adapter covered by NETMATE entry */ + +static const struct usb2_device_id cue_devs[] = { + {USB_VPI(USB_VENDOR_CATC, USB_PRODUCT_CATC_NETMATE, 0)}, + {USB_VPI(USB_VENDOR_CATC, USB_PRODUCT_CATC_NETMATE2, 0)}, + {USB_VPI(USB_VENDOR_SMARTBRIDGES, USB_PRODUCT_SMARTBRIDGES_SMARTLINK, 0)}, +}; + +/* prototypes */ + +static device_probe_t cue_probe; +static device_attach_t cue_attach; +static device_detach_t cue_detach; +static device_shutdown_t cue_shutdown; + +static usb2_callback_t cue_bulk_read_clear_stall_callback; +static usb2_callback_t cue_bulk_read_callback; +static usb2_callback_t cue_bulk_write_clear_stall_callback; +static usb2_callback_t cue_bulk_write_callback; + +static usb2_config_td_command_t cue_cfg_promisc_upd; +static usb2_config_td_command_t cue_config_copy; +static usb2_config_td_command_t cue_cfg_first_time_setup; +static usb2_config_td_command_t cue_cfg_tick; +static usb2_config_td_command_t cue_cfg_pre_init; +static usb2_config_td_command_t cue_cfg_init; +static usb2_config_td_command_t cue_cfg_pre_stop; +static usb2_config_td_command_t cue_cfg_stop; + +static void cue_cfg_do_request(struct cue_softc *sc, struct usb2_device_request *req, void *data); +static uint8_t cue_cfg_csr_read_1(struct cue_softc *sc, uint16_t reg); +static uint16_t cue_cfg_csr_read_2(struct cue_softc *sc, uint8_t reg); +static void cue_cfg_csr_write_1(struct cue_softc *sc, uint16_t reg, uint16_t val); +static void cue_cfg_mem(struct cue_softc *sc, uint8_t cmd, uint16_t addr, void *buf, uint16_t len); +static void cue_cfg_getmac(struct cue_softc *sc, void *buf); +static void cue_mchash(struct usb2_config_td_cc *cc, const uint8_t *addr); +static void cue_cfg_reset(struct cue_softc *sc); +static void cue_start_cb(struct ifnet *ifp); +static void cue_start_transfers(struct cue_softc *sc); +static void cue_init_cb(void *arg); +static int cue_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data); +static void cue_watchdog(void *arg); + +#if USB_DEBUG +static int cue_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, cue, CTLFLAG_RW, 0, "USB cue"); +SYSCTL_INT(_hw_usb2_cue, OID_AUTO, debug, CTLFLAG_RW, &cue_debug, 0, + "Debug level"); +#endif + +static const struct usb2_config cue_config[CUE_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = (MCLBYTES + 2), + .mh.flags = {.pipe_bof = 1,}, + .mh.callback = &cue_bulk_write_callback, + .mh.timeout = 10000, /* 10 seconds */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = (MCLBYTES + 2), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &cue_bulk_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &cue_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &cue_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static device_method_t cue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, cue_probe), + DEVMETHOD(device_attach, cue_attach), + DEVMETHOD(device_detach, cue_detach), + DEVMETHOD(device_shutdown, cue_shutdown), + + {0, 0} +}; + +static driver_t cue_driver = { + .name = "cue", + .methods = cue_methods, + .size = sizeof(struct cue_softc), +}; + +static devclass_t cue_devclass; + +DRIVER_MODULE(cue, ushub, cue_driver, cue_devclass, NULL, 0); +MODULE_DEPEND(cue, usb2_ethernet, 1, 1, 1); +MODULE_DEPEND(cue, usb2_core, 1, 1, 1); +MODULE_DEPEND(cue, ether, 1, 1, 1); + +static void +cue_cfg_do_request(struct cue_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTF("device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +#define CUE_CFG_SETBIT(sc, reg, x) \ + cue_cfg_csr_write_1(sc, reg, cue_cfg_csr_read_1(sc, reg) | (x)) + +#define CUE_CFG_CLRBIT(sc, reg, x) \ + cue_cfg_csr_write_1(sc, reg, cue_cfg_csr_read_1(sc, reg) & ~(x)) + +static uint8_t +cue_cfg_csr_read_1(struct cue_softc *sc, uint16_t reg) +{ + struct usb2_device_request req; + uint8_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + cue_cfg_do_request(sc, &req, &val); + return (val); +} + +static uint16_t +cue_cfg_csr_read_2(struct cue_softc *sc, uint8_t reg) +{ + struct usb2_device_request req; + uint16_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + cue_cfg_do_request(sc, &req, &val); + return (le16toh(val)); +} + +static void +cue_cfg_csr_write_1(struct cue_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = CUE_CMD_WRITEREG; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + cue_cfg_do_request(sc, &req, NULL); + return; +} + +static void +cue_cfg_mem(struct cue_softc *sc, uint8_t cmd, uint16_t addr, + void *buf, uint16_t len) +{ + struct usb2_device_request req; + + if (cmd == CUE_CMD_READSRAM) { + req.bmRequestType = UT_READ_VENDOR_DEVICE; + } else { + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + } + req.bRequest = cmd; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + cue_cfg_do_request(sc, &req, buf); + return; +} + +static void +cue_cfg_getmac(struct cue_softc *sc, void *buf) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_GET_MACADDR; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, ETHER_ADDR_LEN); + + cue_cfg_do_request(sc, &req, buf); + return; +} + +#define CUE_BITS 9 + +static void +cue_mchash(struct usb2_config_td_cc *cc, const uint8_t *addr) +{ + uint16_t h; + + h = ether_crc32_le(addr, ETHER_ADDR_LEN) & + ((1 << CUE_BITS) - 1); + cc->if_hash[h >> 3] |= 1 << (h & 0x7); + return; +} + +static void +cue_cfg_promisc_upd(struct cue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* if we want promiscuous mode, set the allframes bit */ + + if (cc->if_flags & IFF_PROMISC) { + CUE_CFG_SETBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); + } else { + CUE_CFG_CLRBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); + } + + /* write multicast hash-bits */ + + cue_cfg_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, + cc->if_hash, CUE_MCAST_TABLE_LEN); + return; +} + +static void +cue_config_copy(struct cue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + bzero(cc, sizeof(*cc)); + usb2_ether_cc(sc->sc_ifp, &cue_mchash, cc); + return; +} + +static void +cue_cfg_reset(struct cue_softc *sc) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = CUE_CMD_RESET; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + cue_cfg_do_request(sc, &req, NULL); + + /* + * wait a little while for the chip to get its brains in order: + */ + + (void)usb2_config_td_sleep(&sc->sc_config_td, hz / 100); + return; +} + +static int +cue_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != CUE_CONFIG_IDX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != CUE_IFACE_IDX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(cue_devs, sizeof(cue_devs), uaa)); +} + +static int +cue_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct cue_softc *sc = device_get_softc(dev); + uint8_t iface_index; + int32_t error; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "cue lock", NULL, MTX_DEF | MTX_RECURSE); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + iface_index = CUE_IFACE_IDX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, cue_config, CUE_ENDPT_MAX, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + NULL, sizeof(struct usb2_config_td_cc), 16); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &cue_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + cue_watchdog(sc); + + return (0); /* success */ + +detach: + cue_detach(dev); + return (ENXIO); /* failure */ +} + +static void +cue_cfg_first_time_setup(struct cue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint8_t eaddr[ETHER_ADDR_LEN]; + struct ifnet *ifp; + +#if 0 + /* Reset the adapter. */ + cue_cfg_reset(sc); +#endif + /* + * Get station address. + */ + cue_cfg_getmac(sc, eaddr); + + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_ETHER); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + printf("cue%d: could not if_alloc()\n", + sc->sc_unit); + goto done; + } + sc->sc_evilhack = ifp; + + ifp->if_softc = sc; + if_initname(ifp, "cue", sc->sc_unit); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = cue_ioctl_cb; + ifp->if_start = cue_start_cb; + ifp->if_watchdog = NULL; + ifp->if_init = cue_init_cb; + ifp->if_baudrate = 10000000; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + sc->sc_ifp = ifp; + + mtx_unlock(&sc->sc_mtx); + + ether_ifattach(ifp, eaddr); + + mtx_lock(&sc->sc_mtx); + +done: + return; +} + +static int +cue_detach(device_t dev) +{ + struct cue_softc *sc = device_get_softc(dev); + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + cue_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, CUE_ENDPT_MAX); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + ether_ifdetach(ifp); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +cue_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct cue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~CUE_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +cue_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct cue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m = NULL; + uint8_t buf[2]; + uint16_t len; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen <= (2 + sizeof(struct ether_header))) { + ifp->if_ierrors++; + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, 2); + + len = buf[0] | (buf[1] << 8); + + xfer->actlen -= 2; + + m = usb2_ether_get_mbuf(); + + if (m == NULL) { + ifp->if_ierrors++; + goto tr_setup; + } + xfer->actlen = min(xfer->actlen, m->m_len); + xfer->actlen = min(xfer->actlen, len); + + usb2_copy_out(xfer->frbuffers, 2, m->m_data, xfer->actlen); + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = xfer->actlen; + + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_flags & CUE_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "if_input" here, and not some lines up! + */ + if (m) { + mtx_unlock(&sc->sc_mtx); + (ifp->if_input) (ifp, m); + mtx_lock(&sc->sc_mtx); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= CUE_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + DPRINTF("bulk read error, %s\n", + usb2_errstr(xfer->error)); + return; + + } +} + +static void +cue_cfg_tick(struct cue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if ((ifp == NULL)) { + /* not ready */ + return; + } + ifp->if_collisions += cue_cfg_csr_read_2(sc, CUE_TX_SINGLECOLL); + ifp->if_collisions += cue_cfg_csr_read_2(sc, CUE_TX_MULTICOLL); + ifp->if_collisions += cue_cfg_csr_read_2(sc, CUE_TX_EXCESSCOLL); + + if (cue_cfg_csr_read_2(sc, CUE_RX_FRAMEERR)) { + ifp->if_ierrors++; + } + /* start stopped transfers, if any */ + + cue_start_transfers(sc); + + return; +} + +static void +cue_start_cb(struct ifnet *ifp) +{ + struct cue_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + cue_start_transfers(sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +cue_start_transfers(struct cue_softc *sc) +{ + if ((sc->sc_flags & CUE_FLAG_LL_READY) && + (sc->sc_flags & CUE_FLAG_HL_READY)) { + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + } + return; +} + +static void +cue_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct cue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~CUE_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +cue_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct cue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + + ifp->if_opackets++; + + case USB_ST_SETUP: + + if (sc->sc_flags & CUE_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + goto done; + } + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) { + goto done; + } + if (m->m_pkthdr.len > MCLBYTES) { + m->m_pkthdr.len = MCLBYTES; + } + xfer->frlengths[0] = (m->m_pkthdr.len + 2); + + /* the first two bytes are the frame length */ + + buf[0] = (uint8_t)(m->m_pkthdr.len); + buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); + + usb2_copy_in(xfer->frbuffers, 0, buf, 2); + + usb2_m_copy_in(xfer->frbuffers, 2, + m, 0, m->m_pkthdr.len); + + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usb2_start_hardware(xfer); + +done: + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= CUE_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + ifp->if_oerrors++; + return; + + } +} + +static void +cue_init_cb(void *arg) +{ + struct cue_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &cue_cfg_pre_init, + &cue_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +cue_cfg_pre_init(struct cue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + /* immediate configuration */ + + cue_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= CUE_FLAG_HL_READY; + + return; +} + +static void +cue_cfg_init(struct cue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint8_t i; + + /* + * Cancel pending I/O and free all RX/TX buffers. + */ + cue_cfg_stop(sc, cc, 0); +#if 0 + cue_cfg_reset(sc); +#endif + /* Set MAC address */ + + for (i = 0; i < ETHER_ADDR_LEN; i++) { + cue_cfg_csr_write_1(sc, CUE_PAR0 - i, cc->if_lladdr[i]); + } + + /* Enable RX logic. */ + cue_cfg_csr_write_1(sc, CUE_ETHCTL, CUE_ETHCTL_RX_ON | CUE_ETHCTL_MCAST_ON); + + /* Load the multicast filter */ + cue_cfg_promisc_upd(sc, cc, 0); + + /* + * Set the number of RX and TX buffers that we want + * to reserve inside the ASIC. + */ + cue_cfg_csr_write_1(sc, CUE_RX_BUFPKTS, CUE_RX_FRAMES); + cue_cfg_csr_write_1(sc, CUE_TX_BUFPKTS, CUE_TX_FRAMES); + + /* Set advanced operation modes. */ + cue_cfg_csr_write_1(sc, CUE_ADVANCED_OPMODES, + CUE_AOP_EMBED_RXLEN | 0x01);/* 1 wait state */ + + /* Program the LED operation. */ + cue_cfg_csr_write_1(sc, CUE_LEDCTL, CUE_LEDCTL_FOLLOW_LINK); + + sc->sc_flags |= (CUE_FLAG_READ_STALL | + CUE_FLAG_WRITE_STALL | + CUE_FLAG_LL_READY); + + cue_start_transfers(sc); + return; +} + +static int +cue_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct cue_softc *sc = ifp->if_softc; + int error = 0; + + switch (command) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &cue_config_copy, + &cue_cfg_promisc_upd, 0, 0); + } else { + usb2_config_td_queue_command + (&sc->sc_config_td, &cue_cfg_pre_init, + &cue_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &cue_cfg_pre_stop, + &cue_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &cue_config_copy, + &cue_cfg_promisc_upd, 0, 0); + mtx_unlock(&sc->sc_mtx); + break; + + default: + error = ether_ioctl(ifp, command, data); + break; + } + return (error); +} + +static void +cue_watchdog(void *arg) +{ + struct cue_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &cue_cfg_tick, 0, 0); + + usb2_callout_reset(&sc->sc_watchdog, + hz, &cue_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + return; +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +cue_cfg_pre_stop(struct cue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + cue_config_copy(sc, cc, refcount); + } + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(CUE_FLAG_HL_READY | + CUE_FLAG_LL_READY); + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[3]); + return; +} + +static void +cue_cfg_stop(struct cue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + cue_cfg_csr_write_1(sc, CUE_ETHCTL, 0); + cue_cfg_reset(sc); + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +cue_shutdown(device_t dev) +{ + struct cue_softc *sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + + usb2_config_td_queue_command + (&sc->sc_config_td, &cue_cfg_pre_stop, + &cue_cfg_stop, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} diff --git a/sys/dev/usb2/ethernet/if_cue2_reg.h b/sys/dev/usb2/ethernet/if_cue2_reg.h new file mode 100644 index 000000000000..55178766d81c --- /dev/null +++ b/sys/dev/usb2/ethernet/if_cue2_reg.h @@ -0,0 +1,138 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + * + * $FreeBSD$ + */ + +/* + * Definitions for the CATC Netmate II USB to ethernet controller. + */ + +/* Vendor specific control commands. */ +#define CUE_CMD_RESET 0xF4 +#define CUE_CMD_GET_MACADDR 0xF2 +#define CUE_CMD_WRITEREG 0xFA +#define CUE_CMD_READREG 0xFB +#define CUE_CMD_READSRAM 0xF1 +#define CUE_CMD_WRITESRAM 0xFC +/* Internal registers. */ +#define CUE_TX_BUFCNT 0x20 +#define CUE_RX_BUFCNT 0x21 +#define CUE_ADVANCED_OPMODES 0x22 +#define CUE_TX_BUFPKTS 0x23 +#define CUE_RX_BUFPKTS 0x24 +#define CUE_RX_MAXCHAIN 0x25 +#define CUE_ETHCTL 0x60 +#define CUE_ETHSTS 0x61 +#define CUE_PAR5 0x62 +#define CUE_PAR4 0x63 +#define CUE_PAR3 0x64 +#define CUE_PAR2 0x65 +#define CUE_PAR1 0x66 +#define CUE_PAR0 0x67 +/* Error counters, all 16 bits wide. */ +#define CUE_TX_SINGLECOLL 0x69 +#define CUE_TX_MULTICOLL 0x6B +#define CUE_TX_EXCESSCOLL 0x6D +#define CUE_RX_FRAMEERR 0x6F +#define CUE_LEDCTL 0x81 +/* Advenced operating mode register. */ +#define CUE_AOP_SRAMWAITS 0x03 +#define CUE_AOP_EMBED_RXLEN 0x08 +#define CUE_AOP_RXCOMBINE 0x10 +#define CUE_AOP_TXCOMBINE 0x20 +#define CUE_AOP_EVEN_PKT_READS 0x40 +#define CUE_AOP_LOOPBK 0x80 +/* Ethernet control register. */ +#define CUE_ETHCTL_RX_ON 0x01 +#define CUE_ETHCTL_LINK_POLARITY 0x02 +#define CUE_ETHCTL_LINK_FORCE_OK 0x04 +#define CUE_ETHCTL_MCAST_ON 0x08 +#define CUE_ETHCTL_PROMISC 0x10 +/* Ethernet status register. */ +#define CUE_ETHSTS_NO_CARRIER 0x01 +#define CUE_ETHSTS_LATECOLL 0x02 +#define CUE_ETHSTS_EXCESSCOLL 0x04 +#define CUE_ETHSTS_TXBUF_AVAIL 0x08 +#define CUE_ETHSTS_BAD_POLARITY 0x10 +#define CUE_ETHSTS_LINK_OK 0x20 +/* LED control register. */ +#define CUE_LEDCTL_BLINK_1X 0x00 +#define CUE_LEDCTL_BLINK_2X 0x01 +#define CUE_LEDCTL_BLINK_QUARTER_ON 0x02 +#define CUE_LEDCTL_BLINK_QUARTER_OFF 0x03 +#define CUE_LEDCTL_OFF 0x04 +#define CUE_LEDCTL_FOLLOW_LINK 0x08 + +/* + * Address in ASIC's internal SRAM where the multicast hash table lives. + * The table is 64 bytes long, giving us a 512-bit table. We have to set + * the bit that corresponds to the broadcast address in order to enable + * reception of broadcast frames. + */ +#define CUE_MCAST_TABLE_ADDR 0xFA80 +#define CUE_MCAST_TABLE_LEN 64 + +#define CUE_TIMEOUT 1000 +#define CUE_MIN_FRAMELEN 60 +#define CUE_RX_FRAMES 1 +#define CUE_TX_FRAMES 1 + +#define CUE_CTL_READ 0x01 +#define CUE_CTL_WRITE 0x02 + +#define CUE_CONFIG_IDX 0 /* config number 1 */ +#define CUE_IFACE_IDX 0 + +/* The interrupt endpoint is currently unused by the KLSI part. */ +#define CUE_ENDPT_MAX 4 + +struct cue_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct usb2_config_td sc_config_td; + struct usb2_callout sc_watchdog; + struct mtx sc_mtx; + + struct ifnet *sc_ifp; + device_t sc_dev; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[CUE_ENDPT_MAX]; + + uint32_t sc_unit; + + uint16_t sc_flags; +#define CUE_FLAG_READ_STALL 0x0010 /* wait for clearing of stall */ +#define CUE_FLAG_WRITE_STALL 0x0020 /* wait for clearing of stall */ +#define CUE_FLAG_LL_READY 0x0040 /* Lower Layer Ready */ +#define CUE_FLAG_HL_READY 0x0080 /* Higher Layer Ready */ +#define CUE_FLAG_INTR_STALL 0x0100 /* wait for clearing of stall */ +}; diff --git a/sys/dev/usb2/ethernet/if_kue2.c b/sys/dev/usb2/ethernet/if_kue2.c new file mode 100644 index 000000000000..88bbd7e6b013 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_kue2.c @@ -0,0 +1,1017 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Kawasaki LSI KL5KUSB101B USB to ethernet adapter driver. + * + * Written by Bill Paul + * Electrical Engineering Department + * Columbia University, New York City + */ + +/* + * The KLSI USB to ethernet adapter chip contains an USB serial interface, + * ethernet MAC and embedded microcontroller (called the QT Engine). + * The chip must have firmware loaded into it before it will operate. + * Packets are passed between the chip and host via bulk transfers. + * There is an interrupt endpoint mentioned in the software spec, however + * it's currently unused. This device is 10Mbps half-duplex only, hence + * there is no media selection logic. The MAC supports a 128 entry + * multicast filter, though the exact size of the filter can depend + * on the firmware. Curiously, while the software spec describes various + * ethernet statistics counters, my sample adapter and firmware combination + * claims not to support any statistics counters at all. + * + * Note that once we load the firmware in the device, we have to be + * careful not to load it again: if you restart your computer but + * leave the adapter attached to the USB controller, it may remain + * powered on and retain its firmware. In this case, we don't need + * to load the firmware a second time. + * + * Special thanks to Rob Furr for providing an ADS Technologies + * adapter for development and testing. No monkeys were harmed during + * the development of this driver. + */ + +/* + * NOTE: all function names beginning like "kue_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include + +#define usb2_config_td_cc usb2_ether_cc +#define usb2_config_td_softc kue_softc + +#define USB_DEBUG_VAR kue_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * Various supported device vendors/products. + */ +static const struct usb2_device_id kue_devs[] = { + {USB_VPI(USB_VENDOR_3COM, USB_PRODUCT_3COM_3C19250, 0)}, + {USB_VPI(USB_VENDOR_3COM, USB_PRODUCT_3COM_3C460, 0)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_URE450, 0)}, + {USB_VPI(USB_VENDOR_ADS, USB_PRODUCT_ADS_UBS10BT, 0)}, + {USB_VPI(USB_VENDOR_ADS, USB_PRODUCT_ADS_UBS10BTX, 0)}, + {USB_VPI(USB_VENDOR_AOX, USB_PRODUCT_AOX_USB101, 0)}, + {USB_VPI(USB_VENDOR_ASANTE, USB_PRODUCT_ASANTE_EA, 0)}, + {USB_VPI(USB_VENDOR_ATEN, USB_PRODUCT_ATEN_DSB650C, 0)}, + {USB_VPI(USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC10T, 0)}, + {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_ETHER_USB_T, 0)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650C, 0)}, + {USB_VPI(USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_E45, 0)}, + {USB_VPI(USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_XX1, 0)}, + {USB_VPI(USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_XX2, 0)}, + {USB_VPI(USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETT, 0)}, + {USB_VPI(USB_VENDOR_JATON, USB_PRODUCT_JATON_EDA, 0)}, + {USB_VPI(USB_VENDOR_KINGSTON, USB_PRODUCT_KINGSTON_XX1, 0)}, + {USB_VPI(USB_VENDOR_KLSI, USB_PRODUCT_AOX_USB101, 0)}, + {USB_VPI(USB_VENDOR_KLSI, USB_PRODUCT_KLSI_DUH3E10BT, 0)}, + {USB_VPI(USB_VENDOR_KLSI, USB_PRODUCT_KLSI_DUH3E10BTN, 0)}, + {USB_VPI(USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10T, 0)}, + {USB_VPI(USB_VENDOR_MOBILITY, USB_PRODUCT_MOBILITY_EA, 0)}, + {USB_VPI(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_EA101, 0)}, + {USB_VPI(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_EA101X, 0)}, + {USB_VPI(USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET, 0)}, + {USB_VPI(USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET2, 0)}, + {USB_VPI(USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET3, 0)}, + {USB_VPI(USB_VENDOR_PORTGEAR, USB_PRODUCT_PORTGEAR_EA8, 0)}, + {USB_VPI(USB_VENDOR_PORTGEAR, USB_PRODUCT_PORTGEAR_EA9, 0)}, + {USB_VPI(USB_VENDOR_PORTSMITH, USB_PRODUCT_PORTSMITH_EEA, 0)}, + {USB_VPI(USB_VENDOR_SHARK, USB_PRODUCT_SHARK_PA, 0)}, + {USB_VPI(USB_VENDOR_SILICOM, USB_PRODUCT_SILICOM_GPE, 0)}, + {USB_VPI(USB_VENDOR_SILICOM, USB_PRODUCT_SILICOM_U2E, 0)}, + {USB_VPI(USB_VENDOR_SMC, USB_PRODUCT_SMC_2102USB, 0)}, +}; + +/* prototypes */ + +static device_probe_t kue_probe; +static device_attach_t kue_attach; +static device_detach_t kue_detach; +static device_shutdown_t kue_shutdown; + +static usb2_callback_t kue_bulk_read_clear_stall_callback; +static usb2_callback_t kue_bulk_read_callback; +static usb2_callback_t kue_bulk_write_clear_stall_callback; +static usb2_callback_t kue_bulk_write_callback; + +static usb2_config_td_command_t kue_cfg_promisc_upd; +static usb2_config_td_command_t kue_config_copy; +static usb2_config_td_command_t kue_cfg_first_time_setup; +static usb2_config_td_command_t kue_cfg_pre_init; +static usb2_config_td_command_t kue_cfg_init; +static usb2_config_td_command_t kue_cfg_tick; +static usb2_config_td_command_t kue_cfg_pre_stop; +static usb2_config_td_command_t kue_cfg_stop; + +static void kue_cfg_do_request(struct kue_softc *sc, struct usb2_device_request *req, void *data); +static void kue_cfg_setword(struct kue_softc *sc, uint8_t breq, uint16_t word); +static void kue_cfg_ctl(struct kue_softc *sc, uint8_t rw, uint8_t breq, uint16_t val, void *data, uint16_t len); +static void kue_cfg_load_fw(struct kue_softc *sc); +static void kue_cfg_reset(struct kue_softc *sc); +static void kue_start_cb(struct ifnet *ifp); +static void kue_start_transfers(struct kue_softc *sc); +static void kue_init_cb(void *arg); +static int kue_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data); +static void kue_watchdog(void *arg); + +#if USB_DEBUG +static int kue_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, kue, CTLFLAG_RW, 0, "USB kue"); +SYSCTL_INT(_hw_usb2_kue, OID_AUTO, debug, CTLFLAG_RW, &kue_debug, 0, + "Debug level"); +#endif + +static const struct usb2_config kue_config[KUE_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = (MCLBYTES + 2 + 64), + .mh.flags = {.pipe_bof = 1,}, + .mh.callback = &kue_bulk_write_callback, + .mh.timeout = 10000, /* 10 seconds */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = (MCLBYTES + 2), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &kue_bulk_read_callback, + .mh.timeout = 0, /* no timeout */ + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &kue_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &kue_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static device_method_t kue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, kue_probe), + DEVMETHOD(device_attach, kue_attach), + DEVMETHOD(device_detach, kue_detach), + DEVMETHOD(device_shutdown, kue_shutdown), + + {0, 0} +}; + +static driver_t kue_driver = { + .name = "kue", + .methods = kue_methods, + .size = sizeof(struct kue_softc), +}; + +static devclass_t kue_devclass; + +DRIVER_MODULE(kue, ushub, kue_driver, kue_devclass, NULL, 0); +MODULE_DEPEND(kue, usb2_ethernet, 1, 1, 1); +MODULE_DEPEND(kue, usb2_core, 1, 1, 1); +MODULE_DEPEND(kue, ether, 1, 1, 1); + +/* + * We have a custom do_request function which is almost like the + * regular do_request function, except it has a much longer timeout. + * Why? Because we need to make requests over the control endpoint + * to download the firmware to the device, which can take longer + * than the default timeout. + */ +static void +kue_cfg_do_request(struct kue_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 60000); + + if (err) { + DPRINTF("device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +static void +kue_cfg_setword(struct kue_softc *sc, uint8_t breq, uint16_t word) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = breq; + USETW(req.wValue, word); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + kue_cfg_do_request(sc, &req, NULL); + return; +} + +static void +kue_cfg_ctl(struct kue_softc *sc, uint8_t rw, uint8_t breq, + uint16_t val, void *data, uint16_t len) +{ + struct usb2_device_request req; + + if (rw == KUE_CTL_WRITE) { + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + } else { + req.bmRequestType = UT_READ_VENDOR_DEVICE; + } + + req.bRequest = breq; + USETW(req.wValue, val); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + kue_cfg_do_request(sc, &req, data); + return; +} + +static void +kue_cfg_load_fw(struct kue_softc *sc) +{ + struct usb2_device_descriptor *dd; + uint16_t hwrev; + + dd = usb2_get_device_descriptor(sc->sc_udev); + hwrev = UGETW(dd->bcdDevice); + + /* + * First, check if we even need to load the firmware. + * If the device was still attached when the system was + * rebooted, it may already have firmware loaded in it. + * If this is the case, we don't need to do it again. + * And in fact, if we try to load it again, we'll hang, + * so we have to avoid this condition if we don't want + * to look stupid. + * + * We can test this quickly by checking the bcdRevision + * code. The NIC will return a different revision code if + * it's probed while the firmware is still loaded and + * running. + */ + if (hwrev == 0x0202) { + return; + } + /* load code segment */ + kue_cfg_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_code_seg, sizeof(kue_code_seg)); + + /* load fixup segment */ + kue_cfg_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_fix_seg, sizeof(kue_fix_seg)); + + /* send trigger command */ + kue_cfg_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_trig_seg, sizeof(kue_trig_seg)); + return; +} + +static void +kue_cfg_promisc_upd(struct kue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + kue_cfg_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MCAST_FILTERS, + cc->if_nhash, cc->if_hash, cc->if_nhash * ETHER_ADDR_LEN); + + kue_cfg_setword(sc, KUE_CMD_SET_PKT_FILTER, cc->if_rxfilt); + + return; +} + +static void +kue_mchash(struct usb2_config_td_cc *cc, const uint8_t *ptr) +{ + uint8_t i; + + i = cc->if_nhash; + + /* + * If there are too many addresses for the internal filter, + * switch over to allmulti mode. + */ + if (i == cc->if_mhash) { + cc->if_rxfilt |= KUE_RXFILT_ALLMULTI; + } else { + bcopy(ptr, cc->if_hash + (i * ETHER_ADDR_LEN), ETHER_ADDR_LEN); + cc->if_nhash++; + } + return; +} + +static void +kue_config_copy(struct kue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + bzero(cc, sizeof(*cc)); + + cc->if_mhash = sc->sc_mcfilt_max; + cc->if_rxfilt = (KUE_RXFILT_UNICAST | KUE_RXFILT_BROADCAST); + + usb2_ether_cc(sc->sc_ifp, &kue_mchash, cc); + + /* + * If we want promiscuous mode, set the all-frames bit: + */ + if (cc->if_flags & IFF_PROMISC) { + cc->if_rxfilt |= KUE_RXFILT_PROMISC; + } + if ((cc->if_flags & IFF_ALLMULTI) || + (cc->if_flags & IFF_PROMISC)) { + cc->if_rxfilt |= KUE_RXFILT_ALLMULTI; + } else if (cc->if_nhash) { + cc->if_rxfilt |= KUE_RXFILT_MULTICAST; + } + return; +} + +/* + * Issue a SET_CONFIGURATION command to reset the MAC. This should be + * done after the firmware is loaded into the adapter in order to + * bring it into proper operation. + */ +static void +kue_cfg_reset(struct kue_softc *sc) +{ + struct usb2_config_descriptor *cd; + usb2_error_t err; + + cd = usb2_get_config_descriptor(sc->sc_udev); + + err = usb2_req_set_config(sc->sc_udev, &sc->sc_mtx, + cd->bConfigurationValue); + if (err) { + DPRINTF("reset failed (ignored)\n"); + } + /* + * wait a little while for the chip to get its brains in order: + */ + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 100); + + return; +} + +/* + * Probe for a KLSI chip. + */ +static int +kue_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != KUE_CONFIG_IDX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != KUE_IFACE_IDX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(kue_devs, sizeof(kue_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do + * setup and ethernet/BPF attach. + */ +static int +kue_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct kue_softc *sc = device_get_softc(dev); + int32_t error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "kue lock", NULL, MTX_DEF | MTX_RECURSE); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + iface_index = KUE_IFACE_IDX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, kue_config, KUE_ENDPT_MAX, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + NULL, sizeof(struct usb2_config_td_cc), 16); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &kue_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + kue_watchdog(sc); + + return (0); /* success */ + +detach: + kue_detach(dev); + return (ENXIO); /* failure */ +} + +static void +kue_cfg_first_time_setup(struct kue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp; + + /* load the firmware into the NIC */ + + kue_cfg_load_fw(sc); + + /* reset the adapter */ + + kue_cfg_reset(sc); + + /* read ethernet descriptor */ + kue_cfg_ctl(sc, KUE_CTL_READ, KUE_CMD_GET_ETHER_DESCRIPTOR, + 0, &sc->sc_desc, sizeof(sc->sc_desc)); + + sc->sc_mcfilt_max = KUE_MCFILTCNT(sc); + if (sc->sc_mcfilt_max > KUE_MCFILT_MAX) { + sc->sc_mcfilt_max = KUE_MCFILT_MAX; + } + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_ETHER); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + printf("kue%d: could not if_alloc()\n", + sc->sc_unit); + goto done; + } + sc->sc_evilhack = ifp; + + ifp->if_softc = sc; + if_initname(ifp, "kue", sc->sc_unit); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = kue_ioctl_cb; + ifp->if_start = kue_start_cb; + ifp->if_watchdog = NULL; + ifp->if_init = kue_init_cb; + ifp->if_baudrate = 10000000; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + sc->sc_ifp = ifp; + + mtx_unlock(&sc->sc_mtx); + + ether_ifattach(ifp, sc->sc_desc.kue_macaddr); + + mtx_lock(&sc->sc_mtx); +done: + return; +} + +static int +kue_detach(device_t dev) +{ + struct kue_softc *sc = device_get_softc(dev); + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + kue_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, KUE_ENDPT_MAX); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + ether_ifdetach(ifp); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +/* + * A frame has been uploaded: pass the resulting mbuf chain up to + * the higher level protocols. + */ +static void +kue_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct kue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~KUE_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +kue_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct kue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m = NULL; + uint8_t buf[2]; + uint16_t len; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen <= (2 + sizeof(struct ether_header))) { + ifp->if_ierrors++; + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, 2); + + len = buf[0] | (buf[1] << 8); + + xfer->actlen -= 2; + + m = usb2_ether_get_mbuf(); + + if (m == NULL) { + ifp->if_ierrors++; + goto tr_setup; + } + xfer->actlen = min(xfer->actlen, m->m_len); + xfer->actlen = min(xfer->actlen, len); + + usb2_copy_out(xfer->frbuffers, 2, m->m_data, xfer->actlen); + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = xfer->actlen; + + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_flags & KUE_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "if_input" here, and not some lines up! + */ + if (m) { + mtx_unlock(&sc->sc_mtx); + (ifp->if_input) (ifp, m); + mtx_lock(&sc->sc_mtx); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= KUE_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + DPRINTF("bulk read error, %s\n", + usb2_errstr(xfer->error)); + return; + + } +} + +static void +kue_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct kue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~KUE_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +kue_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct kue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint32_t total_len; + uint32_t temp_len; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + + ifp->if_opackets++; + + case USB_ST_SETUP: + + if (sc->sc_flags & KUE_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + goto done; + } + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) { + goto done; + } + if (m->m_pkthdr.len > MCLBYTES) { + m->m_pkthdr.len = MCLBYTES; + } + temp_len = (m->m_pkthdr.len + 2); + total_len = (temp_len + (64 - (temp_len % 64))); + + /* the first two bytes are the frame length */ + + buf[0] = (uint8_t)(m->m_pkthdr.len); + buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); + + usb2_copy_in(xfer->frbuffers, 0, buf, 2); + + usb2_m_copy_in(xfer->frbuffers, 2, + m, 0, m->m_pkthdr.len); + + usb2_bzero(xfer->frbuffers, temp_len, + total_len - temp_len); + + xfer->frlengths[0] = total_len; + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usb2_start_hardware(xfer); + +done: + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= KUE_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + ifp->if_oerrors++; + return; + + } +} + +static void +kue_start_cb(struct ifnet *ifp) +{ + struct kue_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + kue_start_transfers(sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +kue_start_transfers(struct kue_softc *sc) +{ + if ((sc->sc_flags & KUE_FLAG_LL_READY) && + (sc->sc_flags & KUE_FLAG_HL_READY)) { + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + } + return; +} + +static void +kue_init_cb(void *arg) +{ + struct kue_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &kue_cfg_pre_init, + &kue_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +kue_cfg_pre_init(struct kue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + /* immediate configuration */ + + kue_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= KUE_FLAG_HL_READY; + + return; +} + +static void +kue_cfg_init(struct kue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* set MAC address */ + kue_cfg_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MAC, + 0, cc->if_lladdr, ETHER_ADDR_LEN); + + /* I'm not sure how to tune these. */ +#if 0 + /* + * Leave this one alone for now; setting it + * wrong causes lockups on some machines/controllers. + */ + kue_cfg_setword(sc, KUE_CMD_SET_SOFS, 1); +#endif + kue_cfg_setword(sc, KUE_CMD_SET_URB_SIZE, 64); + + /* load the multicast filter */ + kue_cfg_promisc_upd(sc, cc, 0); + + sc->sc_flags |= (KUE_FLAG_READ_STALL | + KUE_FLAG_WRITE_STALL | + KUE_FLAG_LL_READY); + + kue_start_transfers(sc); + return; +} + +static int +kue_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct kue_softc *sc = ifp->if_softc; + int error = 0; + + switch (command) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &kue_config_copy, + &kue_cfg_promisc_upd, 0, 0); + } else { + usb2_config_td_queue_command + (&sc->sc_config_td, &kue_cfg_pre_init, + &kue_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &kue_cfg_pre_stop, + &kue_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &kue_config_copy, + &kue_cfg_promisc_upd, 0, 0); + mtx_unlock(&sc->sc_mtx); + break; + + default: + error = ether_ioctl(ifp, command, data); + break; + } + return (error); +} + +static void +kue_cfg_tick(struct kue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if ((ifp == NULL)) { + /* not ready */ + return; + } + /* start stopped transfers, if any */ + + kue_start_transfers(sc); + + return; +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +kue_watchdog(void *arg) +{ + struct kue_softc *sc = arg; + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &kue_cfg_tick, 0, 0); + + usb2_callout_reset(&sc->sc_watchdog, + hz, &kue_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + return; +} + +static void +kue_cfg_pre_stop(struct kue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + kue_config_copy(sc, cc, refcount); + } + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(KUE_FLAG_HL_READY | + KUE_FLAG_LL_READY); + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[3]); + return; +} + +static void +kue_cfg_stop(struct kue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +kue_shutdown(device_t dev) +{ + struct kue_softc *sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + + usb2_config_td_queue_command + (&sc->sc_config_td, &kue_cfg_pre_stop, + &kue_cfg_stop, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} diff --git a/sys/dev/usb2/ethernet/if_kue2_fw.h b/sys/dev/usb2/ethernet/if_kue2_fw.h new file mode 100644 index 000000000000..2b055a92ed7b --- /dev/null +++ b/sys/dev/usb2/ethernet/if_kue2_fw.h @@ -0,0 +1,685 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + * + * $FreeBSD$ + */ + +/* + * This file contains the firmware needed to make the KLSI chip work, + * along with a few constants related to the QT Engine microcontroller + * embedded in the KLSI part. + * + * Firmware is loaded using the vendor-specific 'send scan data' + * command (0xFF). The basic operation is that we must load the + * firmware, then issue some trigger commands to fix it up and start + * it running. There are three transfers: load the binary code, + * load the 'fixup' (data segment?), then issue a command to + * start the code firmware running. The data itself is prefixed by + * a 16-bit signature word, a 16-bit length value, a type byte + * and an interrupt (command) byte. The code segment is of type + * 0x02 (replacement interrupt vector data) and the fixup segment + * is of type 0x03 (replacement interrupt fixup data). The interrupt + * code is 0x64 (load new code). The length word is the total length + * of the segment minus 7. I precomputed the values and stuck them + * into the appropriate locations within the segments to save some + * work in the driver. + */ + +/* QT controller data block types. */ +/* Write data into specific memory location. */ +#define KUE_QTBTYPE_WRITE_DATA 0x00 +/* Write data into interrupt vector location */ +#define KUE_QTBTYPE_WRITE_INTVEC 0x01 +/* Replace interrupt vector with this data */ +#define KUE_QTBTYPE_REPL_INTVEC 0x02 +/* Fixup interrupt vector code with this data */ +#define KUE_QTBTYPE_FIXUP_INTVEC 0x03 +/* Force jump to location */ +#define KUE_QTBTYPE_JUMP 0x04 +/* Force call to location */ +#define KUE_QTBTYPE_CALL 0x05 +/* Force interrupt call */ +#define KUE_QTBTYPE_CALLINTR 0x06 +/* + * Cause data to be written using the specified QT engine + * interrupt, from starting location in memory for a specified + * number of bytes. + */ +#define KUE_QTBTYPE_WRITE_WITH_INTR 0x07 +/* Cause data from stream to be written using specified QT interrupt. */ +#define KUE_QTBTYPE_WRITE_STR_WITH_INTR 0x08 +/* Cause data to be written to config locations. */ +/* Addresses assume 0xc000 offset. */ +#define KUE_QTBTYPE_WRITE_CONFIG 0x09 + +#define KUE_QTINTR_LOAD_CODE 0x64 +#define KUE_QTINTR_TRIGGER_CODE 0x3B +#define KUE_QTINTR_LOAD_CODE_HIGH 0x9C + +/* Firmware code segment */ +static unsigned char kue_code_seg[] = +{ + /******************************************/ + /* NOTE: B6/C3 is data header signature */ + /* 0xAA/0xBB is data length = total */ + /* bytes - 7, 0xCC is type, 0xDD is */ + /* interrupt to use. */ + /******************************************/ + 0xB6, 0xC3, 0xf7, 0x0e, 0x02, 0x64, + 0x9f, 0xcf, 0xbc, 0x08, 0xe7, 0x57, 0x00, 0x00, + 0x9a, 0x08, 0x97, 0xc1, 0xe7, 0x67, 0xff, 0x1f, + 0x28, 0xc0, 0xe7, 0x87, 0x00, 0x04, 0x24, 0xc0, + 0xe7, 0x67, 0xff, 0xf9, 0x22, 0xc0, 0x97, 0xcf, + 0xe7, 0x09, 0xa2, 0xc0, 0x94, 0x08, 0xd7, 0x09, + 0x00, 0xc0, 0xe7, 0x59, 0xba, 0x08, 0x94, 0x08, + 0x03, 0xc1, 0xe7, 0x67, 0xff, 0xf7, 0x24, 0xc0, + 0xe7, 0x05, 0x00, 0xc0, 0xa7, 0xcf, 0x92, 0x08, + 0xe7, 0x57, 0x00, 0x00, 0x8e, 0x08, 0xa7, 0xa1, + 0x8e, 0x08, 0x97, 0xcf, 0xe7, 0x57, 0x00, 0x00, + 0xf2, 0x09, 0x0a, 0xc0, 0xe7, 0x57, 0x00, 0x00, + 0xa4, 0xc0, 0xa7, 0xc0, 0x56, 0x08, 0x9f, 0xaf, + 0x70, 0x09, 0xe7, 0x07, 0x00, 0x00, 0xf2, 0x09, + 0xe7, 0x57, 0xff, 0xff, 0x90, 0x08, 0x9f, 0xa0, + 0x40, 0x00, 0xe7, 0x59, 0x90, 0x08, 0x94, 0x08, + 0x9f, 0xa0, 0x40, 0x00, 0xc8, 0x09, 0xa2, 0x08, + 0x08, 0x62, 0x9f, 0xa1, 0x14, 0x0a, 0xe7, 0x57, + 0x00, 0x00, 0x52, 0x08, 0xa7, 0xc0, 0x56, 0x08, + 0x9f, 0xaf, 0x04, 0x00, 0xe7, 0x57, 0x00, 0x00, + 0x8e, 0x08, 0xa7, 0xc1, 0x56, 0x08, 0xc0, 0x09, + 0xa8, 0x08, 0x00, 0x60, 0x05, 0xc4, 0xc0, 0x59, + 0x94, 0x08, 0x02, 0xc0, 0x9f, 0xaf, 0xee, 0x00, + 0xe7, 0x59, 0xae, 0x08, 0x94, 0x08, 0x02, 0xc1, + 0x9f, 0xaf, 0xf6, 0x00, 0x9f, 0xaf, 0x9e, 0x03, + 0xef, 0x57, 0x00, 0x00, 0xf0, 0x09, 0x9f, 0xa1, + 0xde, 0x01, 0xe7, 0x57, 0x00, 0x00, 0x78, 0x08, + 0x9f, 0xa0, 0xe4, 0x03, 0x9f, 0xaf, 0x2c, 0x04, + 0xa7, 0xcf, 0x56, 0x08, 0x48, 0x02, 0xe7, 0x09, + 0x94, 0x08, 0xa8, 0x08, 0xc8, 0x37, 0x04, 0x00, + 0x9f, 0xaf, 0x68, 0x04, 0x97, 0xcf, 0xe7, 0x57, + 0x00, 0x00, 0xa6, 0x08, 0x97, 0xc0, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xc8, 0x09, 0x9c, 0x08, + 0x08, 0x62, 0x1d, 0xc0, 0x27, 0x04, 0x9c, 0x08, + 0x10, 0x94, 0xf0, 0x07, 0xee, 0x09, 0x02, 0x00, + 0xc1, 0x07, 0x01, 0x00, 0x70, 0x00, 0x04, 0x00, + 0xf0, 0x07, 0x44, 0x01, 0x06, 0x00, 0x50, 0xaf, + 0xe7, 0x09, 0x94, 0x08, 0xae, 0x08, 0xe7, 0x17, + 0x14, 0x00, 0xae, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xae, 0x08, 0xe7, 0x07, 0xff, 0xff, 0xa8, 0x08, + 0xe7, 0x07, 0x00, 0x00, 0xa6, 0x08, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0xc1, 0xdf, 0x48, 0x02, 0xd0, 0x09, 0x9c, 0x08, + 0x27, 0x02, 0x9c, 0x08, 0xe7, 0x09, 0x20, 0xc0, + 0xee, 0x09, 0xe7, 0xd0, 0xee, 0x09, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x48, 0x02, 0xc8, 0x37, + 0x04, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x60, + 0x21, 0xc0, 0xc0, 0x37, 0x3e, 0x00, 0x23, 0xc9, + 0xc0, 0x57, 0xb4, 0x05, 0x1b, 0xc8, 0xc0, 0x17, + 0x3f, 0x00, 0xc0, 0x67, 0xc0, 0xff, 0x30, 0x00, + 0x08, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x02, 0xc0, 0x17, 0x4c, 0x00, 0x30, 0x00, + 0x06, 0x00, 0xf0, 0x07, 0xbe, 0x01, 0x0a, 0x00, + 0x48, 0x02, 0xc1, 0x07, 0x02, 0x00, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0x51, 0xaf, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x9f, 0xaf, 0x68, 0x04, + 0x9f, 0xaf, 0xe4, 0x03, 0x97, 0xcf, 0x9f, 0xaf, + 0xe4, 0x03, 0xc9, 0x37, 0x04, 0x00, 0xc1, 0xdf, + 0xc8, 0x09, 0x70, 0x08, 0x50, 0x02, 0x67, 0x02, + 0x70, 0x08, 0xd1, 0x07, 0x00, 0x00, 0xc0, 0xdf, + 0x9f, 0xaf, 0xde, 0x01, 0x97, 0xcf, 0xe7, 0x57, + 0x00, 0x00, 0xaa, 0x08, 0x97, 0xc1, 0xe7, 0x57, + 0x01, 0x00, 0x7a, 0x08, 0x97, 0xc0, 0xc8, 0x09, + 0x6e, 0x08, 0x08, 0x62, 0x97, 0xc0, 0x00, 0x02, + 0xc0, 0x17, 0x0e, 0x00, 0x27, 0x00, 0x34, 0x01, + 0x27, 0x0c, 0x0c, 0x00, 0x36, 0x01, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x9f, 0xc0, 0xbe, 0x02, + 0xe7, 0x57, 0x00, 0x00, 0xb0, 0x08, 0x97, 0xc1, + 0xe7, 0x07, 0x09, 0x00, 0x12, 0xc0, 0xe7, 0x77, + 0x00, 0x08, 0x20, 0xc0, 0x9f, 0xc1, 0xb6, 0x02, + 0xe7, 0x57, 0x09, 0x00, 0x12, 0xc0, 0x77, 0xc9, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x77, + 0x00, 0x08, 0x20, 0xc0, 0x2f, 0xc1, 0xe7, 0x07, + 0x00, 0x00, 0x42, 0xc0, 0xe7, 0x07, 0x05, 0x00, + 0x90, 0xc0, 0xc8, 0x07, 0x0a, 0x00, 0xe7, 0x77, + 0x04, 0x00, 0x20, 0xc0, 0x09, 0xc1, 0x08, 0xda, + 0x7a, 0xc1, 0xe7, 0x07, 0x00, 0x01, 0x42, 0xc0, + 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, 0x1a, 0xcf, + 0xe7, 0x07, 0x01, 0x00, 0x7a, 0x08, 0x00, 0xd8, + 0x27, 0x50, 0x34, 0x01, 0x17, 0xc1, 0xe7, 0x77, + 0x02, 0x00, 0x20, 0xc0, 0x79, 0xc1, 0x27, 0x50, + 0x34, 0x01, 0x10, 0xc1, 0xe7, 0x77, 0x02, 0x00, + 0x20, 0xc0, 0x79, 0xc0, 0x9f, 0xaf, 0xd8, 0x02, + 0xe7, 0x05, 0x00, 0xc0, 0x00, 0x60, 0x9f, 0xc0, + 0xde, 0x01, 0x97, 0xcf, 0xe7, 0x07, 0x01, 0x00, + 0xb8, 0x08, 0x06, 0xcf, 0xe7, 0x07, 0x30, 0x0e, + 0x02, 0x00, 0xe7, 0x07, 0x50, 0xc3, 0x12, 0xc0, + 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, 0xe7, 0x07, + 0x01, 0x00, 0xb8, 0x08, 0x97, 0xcf, 0xe7, 0x07, + 0x50, 0xc3, 0x12, 0xc0, 0xe7, 0x07, 0x30, 0x0e, + 0x02, 0x00, 0xe7, 0x07, 0x01, 0x00, 0x7a, 0x08, + 0xe7, 0x07, 0x05, 0x00, 0x90, 0xc0, 0x97, 0xcf, + 0xe7, 0x07, 0x00, 0x01, 0x42, 0xc0, 0xe7, 0x07, + 0x04, 0x00, 0x90, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x7a, 0x08, 0xe7, 0x57, 0x0f, 0x00, 0xb2, 0x08, + 0x13, 0xc1, 0x9f, 0xaf, 0x2e, 0x08, 0xca, 0x09, + 0xac, 0x08, 0xf2, 0x17, 0x01, 0x00, 0x5c, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x5e, 0x00, 0xe7, 0x07, + 0x00, 0x00, 0xb2, 0x08, 0xe7, 0x07, 0x01, 0x00, + 0xb4, 0x08, 0xc0, 0x07, 0xff, 0xff, 0x97, 0xcf, + 0x9f, 0xaf, 0x4c, 0x03, 0xc0, 0x69, 0xb4, 0x08, + 0x57, 0x00, 0x9f, 0xde, 0x33, 0x00, 0xc1, 0x05, + 0x27, 0xd8, 0xb2, 0x08, 0x27, 0xd2, 0xb4, 0x08, + 0xe7, 0x87, 0x01, 0x00, 0xb4, 0x08, 0xe7, 0x67, + 0xff, 0x03, 0xb4, 0x08, 0x00, 0x60, 0x97, 0xc0, + 0xe7, 0x07, 0x01, 0x00, 0xb0, 0x08, 0x27, 0x00, + 0x12, 0xc0, 0x97, 0xcf, 0xc0, 0x09, 0xb6, 0x08, + 0x00, 0xd2, 0x02, 0xc3, 0xc0, 0x97, 0x05, 0x80, + 0x27, 0x00, 0xb6, 0x08, 0xc0, 0x99, 0x82, 0x08, + 0xc0, 0x99, 0xa2, 0xc0, 0x97, 0xcf, 0xe7, 0x07, + 0x00, 0x00, 0xb0, 0x08, 0xc0, 0xdf, 0x97, 0xcf, + 0xc8, 0x09, 0x72, 0x08, 0x08, 0x62, 0x02, 0xc0, + 0x10, 0x64, 0x07, 0xc1, 0xe7, 0x07, 0x00, 0x00, + 0x64, 0x08, 0xe7, 0x07, 0xc8, 0x05, 0x24, 0x00, + 0x97, 0xcf, 0x27, 0x04, 0x72, 0x08, 0xc8, 0x17, + 0x0e, 0x00, 0x27, 0x02, 0x64, 0x08, 0xe7, 0x07, + 0xd6, 0x05, 0x24, 0x00, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x57, 0x00, 0x00, + 0x62, 0x08, 0x13, 0xc1, 0x9f, 0xaf, 0x70, 0x03, + 0xe7, 0x57, 0x00, 0x00, 0x64, 0x08, 0x13, 0xc0, + 0xe7, 0x09, 0x64, 0x08, 0x30, 0x01, 0xe7, 0x07, + 0xf2, 0x05, 0x32, 0x01, 0xe7, 0x07, 0x10, 0x00, + 0x96, 0xc0, 0xe7, 0x09, 0x64, 0x08, 0x62, 0x08, + 0x04, 0xcf, 0xe7, 0x57, 0x00, 0x00, 0x64, 0x08, + 0x02, 0xc1, 0x9f, 0xaf, 0x70, 0x03, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0xc1, 0xdf, 0xc8, 0x09, 0x72, 0x08, 0x27, 0x02, + 0x78, 0x08, 0x08, 0x62, 0x03, 0xc1, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x27, 0x04, 0x72, 0x08, + 0xe7, 0x05, 0x00, 0xc0, 0xf0, 0x07, 0x40, 0x00, + 0x08, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x02, 0xc0, 0x17, 0x0c, 0x00, 0x30, 0x00, + 0x06, 0x00, 0xf0, 0x07, 0x64, 0x01, 0x0a, 0x00, + 0xc8, 0x17, 0x04, 0x00, 0xc1, 0x07, 0x02, 0x00, + 0x51, 0xaf, 0x97, 0xcf, 0xe7, 0x57, 0x00, 0x00, + 0x6a, 0x08, 0x97, 0xc0, 0xc1, 0xdf, 0xc8, 0x09, + 0x6a, 0x08, 0x27, 0x04, 0x6a, 0x08, 0x27, 0x52, + 0x6c, 0x08, 0x03, 0xc1, 0xe7, 0x07, 0x6a, 0x08, + 0x6c, 0x08, 0xc0, 0xdf, 0x17, 0x02, 0xc8, 0x17, + 0x0e, 0x00, 0x9f, 0xaf, 0x16, 0x05, 0xc8, 0x05, + 0x00, 0x60, 0x03, 0xc0, 0x9f, 0xaf, 0x80, 0x04, + 0x97, 0xcf, 0x9f, 0xaf, 0x68, 0x04, 0x97, 0xcf, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x08, 0x62, + 0x1c, 0xc0, 0xd0, 0x09, 0x72, 0x08, 0x27, 0x02, + 0x72, 0x08, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0x97, 0x02, 0xca, 0x09, 0xac, 0x08, 0xf2, 0x17, + 0x01, 0x00, 0x04, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x06, 0x00, 0xca, 0x17, 0x2c, 0x00, 0xf8, 0x77, + 0x01, 0x00, 0x0e, 0x00, 0x06, 0xc0, 0xca, 0xd9, + 0xf8, 0x57, 0xff, 0x00, 0x0e, 0x00, 0x01, 0xc1, + 0xca, 0xd9, 0x22, 0x1c, 0x0c, 0x00, 0xe2, 0x27, + 0x00, 0x00, 0xe2, 0x17, 0x01, 0x00, 0xe2, 0x27, + 0x00, 0x00, 0xca, 0x05, 0x00, 0x0c, 0x0c, 0x00, + 0xc0, 0x17, 0x41, 0x00, 0xc0, 0x67, 0xc0, 0xff, + 0x30, 0x00, 0x08, 0x00, 0x00, 0x02, 0xc0, 0x17, + 0x0c, 0x00, 0x30, 0x00, 0x06, 0x00, 0xf0, 0x07, + 0xdc, 0x00, 0x0a, 0x00, 0xf0, 0x07, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x0c, 0x08, 0x00, 0x40, 0xd1, + 0x01, 0x00, 0xc0, 0x19, 0xa6, 0x08, 0xc0, 0x59, + 0x98, 0x08, 0x04, 0xc9, 0x49, 0xaf, 0x9f, 0xaf, + 0xee, 0x00, 0x4a, 0xaf, 0x67, 0x10, 0xa6, 0x08, + 0xc8, 0x17, 0x04, 0x00, 0xc1, 0x07, 0x01, 0x00, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x50, 0xaf, + 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, 0xc0, 0x07, + 0x01, 0x00, 0xc1, 0x09, 0x7c, 0x08, 0xc1, 0x77, + 0x01, 0x00, 0x97, 0xc1, 0xd8, 0x77, 0x01, 0x00, + 0x12, 0xc0, 0xc9, 0x07, 0x4c, 0x08, 0x9f, 0xaf, + 0x64, 0x05, 0x04, 0xc1, 0xc1, 0x77, 0x08, 0x00, + 0x13, 0xc0, 0x97, 0xcf, 0xc1, 0x77, 0x02, 0x00, + 0x97, 0xc1, 0xc1, 0x77, 0x10, 0x00, 0x0c, 0xc0, + 0x9f, 0xaf, 0x86, 0x05, 0x97, 0xcf, 0xc1, 0x77, + 0x04, 0x00, 0x06, 0xc0, 0xc9, 0x07, 0x7e, 0x08, + 0x9f, 0xaf, 0x64, 0x05, 0x97, 0xc0, 0x00, 0xcf, + 0x00, 0x90, 0x97, 0xcf, 0x50, 0x54, 0x97, 0xc1, + 0x70, 0x5c, 0x02, 0x00, 0x02, 0x00, 0x97, 0xc1, + 0x70, 0x5c, 0x04, 0x00, 0x04, 0x00, 0x97, 0xcf, + 0xc0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, + 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0xcb, 0x09, + 0x88, 0x08, 0xcc, 0x09, 0x8a, 0x08, 0x0b, 0x53, + 0x11, 0xc0, 0xc9, 0x02, 0xca, 0x07, 0x78, 0x05, + 0x9f, 0xaf, 0x64, 0x05, 0x97, 0xc0, 0x0a, 0xc8, + 0x82, 0x08, 0x0a, 0xcf, 0x82, 0x08, 0x9f, 0xaf, + 0x64, 0x05, 0x97, 0xc0, 0x05, 0xc2, 0x89, 0x30, + 0x82, 0x60, 0x78, 0xc1, 0x00, 0x90, 0x97, 0xcf, + 0x89, 0x10, 0x09, 0x53, 0x79, 0xc2, 0x89, 0x30, + 0x82, 0x08, 0x7a, 0xcf, 0xc0, 0xdf, 0x97, 0xcf, + 0xe7, 0x09, 0x96, 0xc0, 0x66, 0x08, 0xe7, 0x09, + 0x98, 0xc0, 0x68, 0x08, 0x0f, 0xcf, 0xe7, 0x09, + 0x96, 0xc0, 0x66, 0x08, 0xe7, 0x09, 0x98, 0xc0, + 0x68, 0x08, 0xe7, 0x09, 0x64, 0x08, 0x30, 0x01, + 0xe7, 0x07, 0xf2, 0x05, 0x32, 0x01, 0xe7, 0x07, + 0x10, 0x00, 0x96, 0xc0, 0xd7, 0x09, 0x00, 0xc0, + 0x17, 0x02, 0xc8, 0x09, 0x62, 0x08, 0xc8, 0x37, + 0x0e, 0x00, 0xe7, 0x57, 0x04, 0x00, 0x68, 0x08, + 0x3d, 0xc0, 0xe7, 0x87, 0x00, 0x08, 0x24, 0xc0, + 0xe7, 0x09, 0x94, 0x08, 0xba, 0x08, 0xe7, 0x17, + 0x64, 0x00, 0xba, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xba, 0x08, 0xe7, 0x77, 0x2a, 0x00, 0x66, 0x08, + 0x30, 0xc0, 0x97, 0x02, 0xca, 0x09, 0xac, 0x08, + 0xe7, 0x77, 0x20, 0x00, 0x66, 0x08, 0x0e, 0xc0, + 0xf2, 0x17, 0x01, 0x00, 0x10, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x12, 0x00, 0xe7, 0x77, 0x0a, 0x00, + 0x66, 0x08, 0xca, 0x05, 0x1e, 0xc0, 0x97, 0x02, + 0xca, 0x09, 0xac, 0x08, 0xf2, 0x17, 0x01, 0x00, + 0x0c, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x0e, 0x00, + 0xe7, 0x77, 0x02, 0x00, 0x66, 0x08, 0x07, 0xc0, + 0xf2, 0x17, 0x01, 0x00, 0x44, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x46, 0x00, 0x06, 0xcf, 0xf2, 0x17, + 0x01, 0x00, 0x60, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x62, 0x00, 0xca, 0x05, 0x9f, 0xaf, 0x68, 0x04, + 0x0f, 0xcf, 0x57, 0x02, 0x09, 0x02, 0xf1, 0x09, + 0x68, 0x08, 0x0c, 0x00, 0xf1, 0xda, 0x0c, 0x00, + 0xc8, 0x09, 0x6c, 0x08, 0x50, 0x02, 0x67, 0x02, + 0x6c, 0x08, 0xd1, 0x07, 0x00, 0x00, 0xc9, 0x05, + 0xe7, 0x09, 0x64, 0x08, 0x62, 0x08, 0xe7, 0x57, + 0x00, 0x00, 0x62, 0x08, 0x02, 0xc0, 0x9f, 0xaf, + 0x70, 0x03, 0xc8, 0x05, 0xe7, 0x05, 0x00, 0xc0, + 0xc0, 0xdf, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0x17, 0x00, 0x17, 0x02, 0x97, 0x02, 0xc0, 0x09, + 0x92, 0xc0, 0xe7, 0x87, 0x00, 0x08, 0x24, 0xc0, + 0xe7, 0x09, 0x94, 0x08, 0xba, 0x08, 0xe7, 0x17, + 0x64, 0x00, 0xba, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xba, 0x08, 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, + 0xca, 0x09, 0xac, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0x7a, 0x08, 0xe7, 0x07, 0x66, 0x03, 0x02, 0x00, + 0xc0, 0x77, 0x02, 0x00, 0x10, 0xc0, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x04, 0xc0, 0x9f, 0xaf, + 0xd8, 0x02, 0x9f, 0xcf, 0x12, 0x08, 0xf2, 0x17, + 0x01, 0x00, 0x50, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x52, 0x00, 0x9f, 0xcf, 0x12, 0x08, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x08, 0xc0, 0xe7, 0x57, + 0x00, 0x00, 0xb8, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0xb8, 0x08, 0x0a, 0xc0, 0x03, 0xcf, 0xc0, 0x77, + 0x10, 0x00, 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, + 0x58, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x5a, 0x00, + 0xc0, 0x77, 0x80, 0x00, 0x06, 0xc0, 0xf2, 0x17, + 0x01, 0x00, 0x70, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x72, 0x00, 0xc0, 0x77, 0x08, 0x00, 0x1d, 0xc1, + 0xf2, 0x17, 0x01, 0x00, 0x08, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x0a, 0x00, 0xc0, 0x77, 0x00, 0x02, + 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, 0x64, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x66, 0x00, 0xc0, 0x77, + 0x40, 0x00, 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, + 0x5c, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x5e, 0x00, + 0xc0, 0x77, 0x01, 0x00, 0x01, 0xc0, 0x37, 0xcf, + 0x36, 0xcf, 0xf2, 0x17, 0x01, 0x00, 0x00, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x02, 0x00, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x18, 0xc0, 0xe7, 0x57, + 0x01, 0x00, 0xb2, 0x08, 0x0e, 0xc2, 0x07, 0xc8, + 0xf2, 0x17, 0x01, 0x00, 0x50, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x52, 0x00, 0x06, 0xcf, 0xf2, 0x17, + 0x01, 0x00, 0x54, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x56, 0x00, 0xe7, 0x07, 0x00, 0x00, 0xb2, 0x08, + 0xe7, 0x07, 0x01, 0x00, 0xb4, 0x08, 0xc8, 0x09, + 0x34, 0x01, 0xca, 0x17, 0x14, 0x00, 0xd8, 0x77, + 0x01, 0x00, 0x05, 0xc0, 0xca, 0xd9, 0xd8, 0x57, + 0xff, 0x00, 0x01, 0xc0, 0xca, 0xd9, 0xe2, 0x19, + 0x94, 0xc0, 0xe2, 0x27, 0x00, 0x00, 0xe2, 0x17, + 0x01, 0x00, 0xe2, 0x27, 0x00, 0x00, 0x9f, 0xaf, + 0x2e, 0x08, 0x9f, 0xaf, 0xde, 0x01, 0xe7, 0x57, + 0x00, 0x00, 0xaa, 0x08, 0x9f, 0xa1, 0xf0, 0x0b, + 0xca, 0x05, 0xc8, 0x05, 0xc0, 0x05, 0xe7, 0x05, + 0x00, 0xc0, 0xc0, 0xdf, 0x97, 0xcf, 0xc8, 0x09, + 0x6e, 0x08, 0x08, 0x62, 0x97, 0xc0, 0x27, 0x04, + 0x6e, 0x08, 0x27, 0x52, 0x70, 0x08, 0x03, 0xc1, + 0xe7, 0x07, 0x6e, 0x08, 0x70, 0x08, 0x9f, 0xaf, + 0x68, 0x04, 0x97, 0xcf, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0xcc, + 0x00, 0x00, 0x00, 0x00, 0xe7, 0x57, 0x00, 0x80, + 0xb2, 0x00, 0x06, 0xc2, 0xe7, 0x07, 0x52, 0x0e, + 0x12, 0x00, 0xe7, 0x07, 0x98, 0x0e, 0xb2, 0x00, + 0xe7, 0x07, 0xa4, 0x09, 0xf2, 0x02, 0xc8, 0x09, + 0xb4, 0x00, 0xf8, 0x07, 0x02, 0x00, 0x0d, 0x00, + 0xd7, 0x09, 0x0e, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x0e, 0xc0, 0xc8, 0x09, 0xdc, 0x00, 0xf0, 0x07, + 0xff, 0xff, 0x09, 0x00, 0xf0, 0x07, 0xfb, 0x13, + 0x0b, 0x00, 0xe7, 0x09, 0xc0, 0x00, 0x58, 0x08, + 0xe7, 0x09, 0xbe, 0x00, 0x54, 0x08, 0xe7, 0x09, + 0x10, 0x00, 0x92, 0x08, 0xc8, 0x07, 0xb4, 0x09, + 0x9f, 0xaf, 0x8c, 0x09, 0x9f, 0xaf, 0xe2, 0x0b, + 0xc0, 0x07, 0x80, 0x01, 0x44, 0xaf, 0x27, 0x00, + 0x88, 0x08, 0x27, 0x00, 0x8a, 0x08, 0x27, 0x00, + 0x8c, 0x08, 0xc0, 0x07, 0x74, 0x00, 0x44, 0xaf, + 0x27, 0x00, 0xac, 0x08, 0x08, 0x00, 0x00, 0x90, + 0xc1, 0x07, 0x1d, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x01, 0xda, 0x7c, 0xc1, 0x9f, 0xaf, 0x8a, 0x0b, + 0xc0, 0x07, 0x4c, 0x00, 0x48, 0xaf, 0x27, 0x00, + 0x56, 0x08, 0x9f, 0xaf, 0x72, 0x0c, 0xe7, 0x07, + 0x00, 0x80, 0x96, 0x08, 0xef, 0x57, 0x00, 0x00, + 0xf0, 0x09, 0x03, 0xc0, 0xe7, 0x07, 0x01, 0x00, + 0x1c, 0xc0, 0xe7, 0x05, 0x0e, 0xc0, 0x97, 0xcf, + 0x49, 0xaf, 0xe7, 0x87, 0x43, 0x00, 0x0e, 0xc0, + 0xe7, 0x07, 0xff, 0xff, 0x94, 0x08, 0x9f, 0xaf, + 0x8a, 0x0c, 0xc0, 0x07, 0x01, 0x00, 0x60, 0xaf, + 0x4a, 0xaf, 0x97, 0xcf, 0x00, 0x08, 0x09, 0x08, + 0x11, 0x08, 0x00, 0xda, 0x7c, 0xc1, 0x97, 0xcf, + 0x67, 0x04, 0xcc, 0x02, 0xc0, 0xdf, 0x51, 0x94, + 0xb1, 0xaf, 0x06, 0x00, 0xc1, 0xdf, 0xc9, 0x09, + 0xcc, 0x02, 0x49, 0x62, 0x75, 0xc1, 0xc0, 0xdf, + 0xa7, 0xcf, 0xd6, 0x02, 0x0e, 0x00, 0x24, 0x00, + 0xd6, 0x05, 0x22, 0x00, 0xc4, 0x06, 0xd0, 0x00, + 0xf0, 0x0b, 0xaa, 0x00, 0x0e, 0x0a, 0xbe, 0x00, + 0x2c, 0x0c, 0x10, 0x00, 0x20, 0x00, 0x04, 0x00, + 0xc4, 0x05, 0x02, 0x00, 0x66, 0x03, 0x06, 0x00, + 0x00, 0x00, 0x24, 0xc0, 0x04, 0x04, 0x28, 0xc0, + 0xfe, 0xfb, 0x1e, 0xc0, 0x00, 0x04, 0x22, 0xc0, + 0xff, 0xf0, 0xc0, 0x00, 0x60, 0x0b, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x34, 0x0a, 0x3e, 0x0a, + 0x9e, 0x0a, 0xa8, 0x0a, 0xce, 0x0a, 0xd2, 0x0a, + 0xd6, 0x0a, 0x00, 0x0b, 0x10, 0x0b, 0x1e, 0x0b, + 0x20, 0x0b, 0x28, 0x0b, 0x28, 0x0b, 0x27, 0x02, + 0xa2, 0x08, 0x97, 0xcf, 0xe7, 0x07, 0x00, 0x00, + 0xa2, 0x08, 0x0a, 0x0e, 0x01, 0x00, 0xca, 0x57, + 0x0e, 0x00, 0x9f, 0xc3, 0x2a, 0x0b, 0xca, 0x37, + 0x00, 0x00, 0x9f, 0xc2, 0x2a, 0x0b, 0x0a, 0xd2, + 0xb2, 0xcf, 0xf4, 0x09, 0xc8, 0x09, 0xde, 0x00, + 0x07, 0x06, 0x9f, 0xcf, 0x3c, 0x0b, 0xf0, 0x57, + 0x80, 0x01, 0x06, 0x00, 0x9f, 0xc8, 0x2a, 0x0b, + 0x27, 0x0c, 0x02, 0x00, 0x86, 0x08, 0xc0, 0x09, + 0x88, 0x08, 0x27, 0x00, 0x8a, 0x08, 0xe7, 0x07, + 0x00, 0x00, 0x84, 0x08, 0x27, 0x00, 0x5c, 0x08, + 0x00, 0x1c, 0x06, 0x00, 0x27, 0x00, 0x8c, 0x08, + 0x41, 0x90, 0x67, 0x50, 0x86, 0x08, 0x0d, 0xc0, + 0x67, 0x00, 0x5a, 0x08, 0x27, 0x0c, 0x06, 0x00, + 0x5e, 0x08, 0xe7, 0x07, 0x8a, 0x0a, 0x60, 0x08, + 0xc8, 0x07, 0x5a, 0x08, 0x41, 0x90, 0x51, 0xaf, + 0x97, 0xcf, 0x9f, 0xaf, 0xac, 0x0e, 0xe7, 0x09, + 0x8c, 0x08, 0x8a, 0x08, 0xe7, 0x09, 0x86, 0x08, + 0x84, 0x08, 0x59, 0xaf, 0x97, 0xcf, 0x27, 0x0c, + 0x02, 0x00, 0x7c, 0x08, 0x59, 0xaf, 0x97, 0xcf, + 0x09, 0x0c, 0x02, 0x00, 0x09, 0xda, 0x49, 0xd2, + 0xc9, 0x19, 0xac, 0x08, 0xc8, 0x07, 0x5a, 0x08, + 0xe0, 0x07, 0x00, 0x00, 0x60, 0x02, 0xe0, 0x07, + 0x04, 0x00, 0xd0, 0x07, 0x9a, 0x0a, 0x48, 0xdb, + 0x41, 0x90, 0x50, 0xaf, 0x97, 0xcf, 0x59, 0xaf, + 0x97, 0xcf, 0x59, 0xaf, 0x97, 0xcf, 0xf0, 0x57, + 0x06, 0x00, 0x06, 0x00, 0x26, 0xc1, 0xe7, 0x07, + 0x7e, 0x08, 0x5c, 0x08, 0x41, 0x90, 0x67, 0x00, + 0x5a, 0x08, 0x27, 0x0c, 0x06, 0x00, 0x5e, 0x08, + 0xe7, 0x07, 0x5c, 0x0b, 0x60, 0x08, 0xc8, 0x07, + 0x5a, 0x08, 0x41, 0x90, 0x51, 0xaf, 0x97, 0xcf, + 0x07, 0x0c, 0x06, 0x00, 0xc7, 0x57, 0x06, 0x00, + 0x10, 0xc1, 0xc8, 0x07, 0x7e, 0x08, 0x16, 0xcf, + 0x00, 0x0c, 0x02, 0x00, 0x00, 0xda, 0x40, 0xd1, + 0x27, 0x00, 0x98, 0x08, 0x1f, 0xcf, 0x1e, 0xcf, + 0x27, 0x0c, 0x02, 0x00, 0xa4, 0x08, 0x1a, 0xcf, + 0x00, 0xcf, 0x27, 0x02, 0x20, 0x01, 0xe7, 0x07, + 0x08, 0x00, 0x22, 0x01, 0xe7, 0x07, 0x13, 0x00, + 0xb0, 0xc0, 0x97, 0xcf, 0x41, 0x90, 0x67, 0x00, + 0x5a, 0x08, 0xe7, 0x01, 0x5e, 0x08, 0x27, 0x02, + 0x5c, 0x08, 0xe7, 0x07, 0x5c, 0x0b, 0x60, 0x08, + 0xc8, 0x07, 0x5a, 0x08, 0xc1, 0x07, 0x00, 0x80, + 0x50, 0xaf, 0x97, 0xcf, 0x59, 0xaf, 0x97, 0xcf, + 0x00, 0x60, 0x05, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x9a, 0x08, 0xa7, 0xcf, 0x58, 0x08, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x07, 0x01, 0x00, 0x9a, 0x08, + 0x49, 0xaf, 0xd7, 0x09, 0x00, 0xc0, 0x07, 0xaf, + 0xe7, 0x05, 0x00, 0xc0, 0x4a, 0xaf, 0xa7, 0xcf, + 0x58, 0x08, 0xc0, 0x07, 0x40, 0x00, 0x44, 0xaf, + 0x27, 0x00, 0xa0, 0x08, 0x08, 0x00, 0xc0, 0x07, + 0x20, 0x00, 0x20, 0x94, 0x00, 0xda, 0x7d, 0xc1, + 0xc0, 0x07, 0xfe, 0x7f, 0x44, 0xaf, 0x40, 0x00, + 0x41, 0x90, 0xc0, 0x37, 0x08, 0x00, 0xdf, 0xde, + 0x50, 0x06, 0xc0, 0x57, 0x10, 0x00, 0x02, 0xc2, + 0xc0, 0x07, 0x10, 0x00, 0x27, 0x00, 0x76, 0x08, + 0x41, 0x90, 0x9f, 0xde, 0x40, 0x06, 0x44, 0xaf, + 0x27, 0x00, 0x74, 0x08, 0xc0, 0x09, 0x76, 0x08, + 0x41, 0x90, 0x00, 0xd2, 0x00, 0xd8, 0x9f, 0xde, + 0x08, 0x00, 0x44, 0xaf, 0x27, 0x00, 0x9e, 0x08, + 0x97, 0xcf, 0xe7, 0x87, 0x00, 0x84, 0x28, 0xc0, + 0xe7, 0x67, 0xff, 0xf3, 0x24, 0xc0, 0x97, 0xcf, + 0xe7, 0x87, 0x01, 0x00, 0xaa, 0x08, 0xe7, 0x57, + 0x00, 0x00, 0x7a, 0x08, 0x97, 0xc1, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x87, 0x00, 0x06, 0x22, 0xc0, + 0xe7, 0x07, 0x00, 0x00, 0x90, 0xc0, 0xe7, 0x67, + 0xfe, 0xff, 0x3e, 0xc0, 0xe7, 0x07, 0x2e, 0x00, + 0x0a, 0xc0, 0xe7, 0x87, 0x01, 0x00, 0x3e, 0xc0, + 0xe7, 0x07, 0xff, 0xff, 0x94, 0x08, 0x9f, 0xaf, + 0xf0, 0x0c, 0x97, 0xcf, 0x17, 0x00, 0xa7, 0xaf, + 0x54, 0x08, 0xc0, 0x05, 0x27, 0x00, 0x52, 0x08, + 0xe7, 0x87, 0x01, 0x00, 0xaa, 0x08, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x07, 0x0c, 0x00, 0x40, 0xc0, + 0x9f, 0xaf, 0xf0, 0x0c, 0xe7, 0x07, 0x00, 0x00, + 0x78, 0x08, 0x00, 0x90, 0xe7, 0x09, 0x88, 0x08, + 0x8a, 0x08, 0x27, 0x00, 0x84, 0x08, 0x27, 0x00, + 0x7c, 0x08, 0x9f, 0xaf, 0x8a, 0x0c, 0xe7, 0x07, + 0x00, 0x00, 0xb2, 0x02, 0xe7, 0x07, 0x00, 0x00, + 0xb4, 0x02, 0xc0, 0x07, 0x06, 0x00, 0xc8, 0x09, + 0xde, 0x00, 0xc8, 0x17, 0x03, 0x00, 0xc9, 0x07, + 0x7e, 0x08, 0x29, 0x0a, 0x00, 0xda, 0x7d, 0xc1, + 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, + 0x00, 0x90, 0x27, 0x00, 0x6a, 0x08, 0xe7, 0x07, + 0x6a, 0x08, 0x6c, 0x08, 0x27, 0x00, 0x6e, 0x08, + 0xe7, 0x07, 0x6e, 0x08, 0x70, 0x08, 0x27, 0x00, + 0x78, 0x08, 0x27, 0x00, 0x62, 0x08, 0x27, 0x00, + 0x64, 0x08, 0xc8, 0x09, 0x74, 0x08, 0xc1, 0x09, + 0x76, 0x08, 0xc9, 0x07, 0x72, 0x08, 0x11, 0x02, + 0x09, 0x02, 0xc8, 0x17, 0x40, 0x06, 0x01, 0xda, + 0x7a, 0xc1, 0x51, 0x94, 0xc8, 0x09, 0x9e, 0x08, + 0xc9, 0x07, 0x9c, 0x08, 0xc1, 0x09, 0x76, 0x08, + 0x01, 0xd2, 0x01, 0xd8, 0x11, 0x02, 0x09, 0x02, + 0xc8, 0x17, 0x08, 0x00, 0x01, 0xda, 0x7a, 0xc1, + 0x51, 0x94, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0xe7, 0x57, 0x00, 0x00, 0x52, 0x08, 0x97, 0xc0, + 0x9f, 0xaf, 0x04, 0x00, 0xe7, 0x09, 0x94, 0x08, + 0x90, 0x08, 0xe7, 0x57, 0xff, 0xff, 0x90, 0x08, + 0x04, 0xc1, 0xe7, 0x07, 0xf0, 0x0c, 0x8e, 0x08, + 0x97, 0xcf, 0xe7, 0x17, 0x32, 0x00, 0x90, 0x08, + 0xe7, 0x67, 0xff, 0x07, 0x90, 0x08, 0xe7, 0x07, + 0x26, 0x0d, 0x8e, 0x08, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x57, 0x00, 0x00, + 0x96, 0x08, 0x23, 0xc0, 0xe7, 0x07, 0x00, 0x80, + 0x80, 0xc0, 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, + 0xe7, 0x07, 0x00, 0x00, 0x80, 0xc0, 0xe7, 0x07, + 0x00, 0x80, 0x80, 0xc0, 0xc0, 0x07, 0x00, 0x00, + 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, + 0xe7, 0x07, 0x00, 0x00, 0x80, 0xc0, 0xe7, 0x07, + 0x00, 0x80, 0x80, 0xc0, 0xe7, 0x07, 0x00, 0x80, + 0x40, 0xc0, 0xc0, 0x07, 0x00, 0x00, 0xe7, 0x07, + 0x00, 0x00, 0x40, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x80, 0xc0, 0xef, 0x57, 0x00, 0x00, 0xf1, 0x09, + 0x9f, 0xa0, 0xc0, 0x0d, 0xe7, 0x07, 0x04, 0x00, + 0x90, 0xc0, 0xe7, 0x07, 0x00, 0x02, 0x40, 0xc0, + 0xe7, 0x07, 0x0c, 0x02, 0x40, 0xc0, 0xe7, 0x07, + 0x00, 0x00, 0x96, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0x8e, 0x08, 0xe7, 0x07, 0x00, 0x00, 0xaa, 0x08, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x9f, 0xaf, + 0x9e, 0x03, 0xe7, 0x05, 0x00, 0xc0, 0x9f, 0xaf, + 0xde, 0x01, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0x9f, 0xaf, 0xde, 0x0d, 0xef, 0x77, 0x00, 0x00, + 0xf1, 0x09, 0x97, 0xc1, 0x9f, 0xaf, 0xde, 0x0d, + 0xef, 0x77, 0x00, 0x00, 0xf1, 0x09, 0x97, 0xc1, + 0xef, 0x07, 0x01, 0x00, 0xf1, 0x09, 0xe7, 0x87, + 0x00, 0x08, 0x1e, 0xc0, 0xe7, 0x87, 0x00, 0x08, + 0x22, 0xc0, 0xe7, 0x67, 0xff, 0xf7, 0x22, 0xc0, + 0xe7, 0x77, 0x00, 0x08, 0x20, 0xc0, 0x11, 0xc0, + 0xe7, 0x67, 0xff, 0xf7, 0x1e, 0xc0, 0xe7, 0x87, + 0x00, 0x08, 0x22, 0xc0, 0xe7, 0x67, 0xff, 0xf7, + 0x22, 0xc0, 0xe7, 0x77, 0x00, 0x08, 0x20, 0xc0, + 0x04, 0xc1, 0xe7, 0x87, 0x00, 0x08, 0x22, 0xc0, + 0x97, 0xcf, 0xe7, 0x07, 0x01, 0x01, 0xf0, 0x09, + 0xef, 0x57, 0x18, 0x00, 0xfe, 0xff, 0x97, 0xc2, + 0xef, 0x07, 0x00, 0x00, 0xf0, 0x09, 0x97, 0xcf, + 0xd7, 0x09, 0x00, 0xc0, 0x17, 0x00, 0x17, 0x02, + 0x97, 0x02, 0xe7, 0x57, 0x00, 0x00, 0x7a, 0x08, + 0x06, 0xc0, 0xc0, 0x09, 0x92, 0xc0, 0xc0, 0x77, + 0x09, 0x02, 0x9f, 0xc1, 0xea, 0x06, 0x9f, 0xcf, + 0x20, 0x08, 0xd7, 0x09, 0x0e, 0xc0, 0xe7, 0x07, + 0x00, 0x00, 0x0e, 0xc0, 0x9f, 0xaf, 0x66, 0x0e, + 0xe7, 0x05, 0x0e, 0xc0, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0x17, 0x02, 0xc8, 0x09, 0xb0, 0xc0, + 0xe7, 0x67, 0xfe, 0x7f, 0xb0, 0xc0, 0xc8, 0x77, + 0x00, 0x20, 0x9f, 0xc1, 0x64, 0xeb, 0xe7, 0x57, + 0x00, 0x00, 0xc8, 0x02, 0x9f, 0xc1, 0x80, 0xeb, + 0xc8, 0x99, 0xca, 0x02, 0xc8, 0x67, 0x04, 0x00, + 0x9f, 0xc1, 0x96, 0xeb, 0x9f, 0xcf, 0x4c, 0xeb, + 0xe7, 0x07, 0x00, 0x00, 0xa6, 0xc0, 0xe7, 0x09, + 0xb0, 0xc0, 0xc8, 0x02, 0xe7, 0x07, 0x03, 0x00, + 0xb0, 0xc0, 0x97, 0xcf, 0xc0, 0x09, 0x86, 0x08, + 0xc0, 0x37, 0x01, 0x00, 0x97, 0xc9, 0xc9, 0x09, + 0x88, 0x08, 0x02, 0x00, 0x41, 0x90, 0x48, 0x02, + 0xc9, 0x17, 0x06, 0x00, 0x9f, 0xaf, 0x64, 0x05, + 0x9f, 0xa2, 0xd6, 0x0e, 0x02, 0xda, 0x77, 0xc1, + 0x41, 0x60, 0x71, 0xc1, 0x97, 0xcf, 0x17, 0x02, + 0x57, 0x02, 0x43, 0x04, 0x21, 0x04, 0xe0, 0x00, + 0x43, 0x04, 0x21, 0x04, 0xe0, 0x00, 0x43, 0x04, + 0x21, 0x04, 0xe0, 0x00, 0xc1, 0x07, 0x01, 0x00, + 0xc9, 0x05, 0xc8, 0x05, 0x97, 0xcf, + 0, 0 +}; + +/* Firmware fixup (data?) segment */ +static unsigned char kue_fix_seg[] = +{ + /******************************************/ + /* NOTE: B6/C3 is data header signature */ + /* 0xAA/0xBB is data length = total */ + /* bytes - 7, 0xCC is type, 0xDD is */ + /* interrupt to use. */ + /******************************************/ + 0xB6, 0xC3, 0xc9, 0x02, 0x03, 0x64, + 0x02, 0x00, 0x08, 0x00, 0x24, 0x00, 0x2e, 0x00, + 0x2c, 0x00, 0x3e, 0x00, 0x44, 0x00, 0x48, 0x00, + 0x50, 0x00, 0x5c, 0x00, 0x60, 0x00, 0x66, 0x00, + 0x6c, 0x00, 0x70, 0x00, 0x76, 0x00, 0x74, 0x00, + 0x7a, 0x00, 0x7e, 0x00, 0x84, 0x00, 0x8a, 0x00, + 0x8e, 0x00, 0x92, 0x00, 0x98, 0x00, 0x9c, 0x00, + 0xa0, 0x00, 0xa8, 0x00, 0xae, 0x00, 0xb4, 0x00, + 0xb2, 0x00, 0xba, 0x00, 0xbe, 0x00, 0xc4, 0x00, + 0xc8, 0x00, 0xce, 0x00, 0xd2, 0x00, 0xd6, 0x00, + 0xda, 0x00, 0xe2, 0x00, 0xe0, 0x00, 0xea, 0x00, + 0xf2, 0x00, 0xfe, 0x00, 0x06, 0x01, 0x0c, 0x01, + 0x1a, 0x01, 0x24, 0x01, 0x22, 0x01, 0x2a, 0x01, + 0x30, 0x01, 0x36, 0x01, 0x3c, 0x01, 0x4e, 0x01, + 0x52, 0x01, 0x58, 0x01, 0x5c, 0x01, 0x9c, 0x01, + 0xb6, 0x01, 0xba, 0x01, 0xc0, 0x01, 0xca, 0x01, + 0xd0, 0x01, 0xda, 0x01, 0xe2, 0x01, 0xea, 0x01, + 0xf0, 0x01, 0x0a, 0x02, 0x0e, 0x02, 0x14, 0x02, + 0x26, 0x02, 0x6c, 0x02, 0x8e, 0x02, 0x98, 0x02, + 0xa0, 0x02, 0xa6, 0x02, 0xba, 0x02, 0xc6, 0x02, + 0xce, 0x02, 0xe8, 0x02, 0xee, 0x02, 0xf4, 0x02, + 0xf8, 0x02, 0x0a, 0x03, 0x10, 0x03, 0x1a, 0x03, + 0x1e, 0x03, 0x2a, 0x03, 0x2e, 0x03, 0x34, 0x03, + 0x3a, 0x03, 0x44, 0x03, 0x4e, 0x03, 0x5a, 0x03, + 0x5e, 0x03, 0x6a, 0x03, 0x72, 0x03, 0x80, 0x03, + 0x84, 0x03, 0x8c, 0x03, 0x94, 0x03, 0x98, 0x03, + 0xa8, 0x03, 0xae, 0x03, 0xb4, 0x03, 0xba, 0x03, + 0xce, 0x03, 0xcc, 0x03, 0xd6, 0x03, 0xdc, 0x03, + 0xec, 0x03, 0xf0, 0x03, 0xfe, 0x03, 0x1c, 0x04, + 0x30, 0x04, 0x38, 0x04, 0x3c, 0x04, 0x40, 0x04, + 0x48, 0x04, 0x46, 0x04, 0x54, 0x04, 0x5e, 0x04, + 0x64, 0x04, 0x74, 0x04, 0x78, 0x04, 0x84, 0x04, + 0xd8, 0x04, 0xec, 0x04, 0xf0, 0x04, 0xf8, 0x04, + 0xfe, 0x04, 0x1c, 0x05, 0x2c, 0x05, 0x30, 0x05, + 0x4a, 0x05, 0x56, 0x05, 0x5a, 0x05, 0x88, 0x05, + 0x8c, 0x05, 0x96, 0x05, 0x9a, 0x05, 0xa8, 0x05, + 0xcc, 0x05, 0xd2, 0x05, 0xda, 0x05, 0xe0, 0x05, + 0xe4, 0x05, 0xfc, 0x05, 0x06, 0x06, 0x14, 0x06, + 0x12, 0x06, 0x1a, 0x06, 0x20, 0x06, 0x26, 0x06, + 0x2e, 0x06, 0x34, 0x06, 0x48, 0x06, 0x52, 0x06, + 0x64, 0x06, 0x86, 0x06, 0x90, 0x06, 0x9a, 0x06, + 0xa0, 0x06, 0xac, 0x06, 0xaa, 0x06, 0xb2, 0x06, + 0xb8, 0x06, 0xdc, 0x06, 0xda, 0x06, 0xe2, 0x06, + 0xe8, 0x06, 0xf2, 0x06, 0xf8, 0x06, 0xfc, 0x06, + 0x0a, 0x07, 0x10, 0x07, 0x14, 0x07, 0x24, 0x07, + 0x2a, 0x07, 0x32, 0x07, 0x38, 0x07, 0xb2, 0x07, + 0xba, 0x07, 0xde, 0x07, 0xe4, 0x07, 0x10, 0x08, + 0x14, 0x08, 0x1a, 0x08, 0x1e, 0x08, 0x30, 0x08, + 0x38, 0x08, 0x3c, 0x08, 0x44, 0x08, 0x42, 0x08, + 0x48, 0x08, 0xc6, 0x08, 0xcc, 0x08, 0xd2, 0x08, + 0xfe, 0x08, 0x04, 0x09, 0x0a, 0x09, 0x0e, 0x09, + 0x12, 0x09, 0x16, 0x09, 0x20, 0x09, 0x24, 0x09, + 0x28, 0x09, 0x32, 0x09, 0x46, 0x09, 0x4a, 0x09, + 0x50, 0x09, 0x54, 0x09, 0x5a, 0x09, 0x60, 0x09, + 0x7c, 0x09, 0x80, 0x09, 0xb8, 0x09, 0xbc, 0x09, + 0xc0, 0x09, 0xc4, 0x09, 0xc8, 0x09, 0xcc, 0x09, + 0xd0, 0x09, 0xd4, 0x09, 0xec, 0x09, 0xf4, 0x09, + 0xf6, 0x09, 0xf8, 0x09, 0xfa, 0x09, 0xfc, 0x09, + 0xfe, 0x09, 0x00, 0x0a, 0x02, 0x0a, 0x04, 0x0a, + 0x06, 0x0a, 0x08, 0x0a, 0x0a, 0x0a, 0x0c, 0x0a, + 0x10, 0x0a, 0x18, 0x0a, 0x24, 0x0a, 0x2c, 0x0a, + 0x32, 0x0a, 0x3c, 0x0a, 0x46, 0x0a, 0x4c, 0x0a, + 0x50, 0x0a, 0x54, 0x0a, 0x5a, 0x0a, 0x5e, 0x0a, + 0x66, 0x0a, 0x6c, 0x0a, 0x72, 0x0a, 0x78, 0x0a, + 0x7e, 0x0a, 0x7c, 0x0a, 0x82, 0x0a, 0x8c, 0x0a, + 0x92, 0x0a, 0x90, 0x0a, 0x98, 0x0a, 0x96, 0x0a, + 0xa2, 0x0a, 0xb2, 0x0a, 0xb6, 0x0a, 0xc4, 0x0a, + 0xe2, 0x0a, 0xe0, 0x0a, 0xe8, 0x0a, 0xee, 0x0a, + 0xf4, 0x0a, 0xf2, 0x0a, 0xf8, 0x0a, 0x0c, 0x0b, + 0x1a, 0x0b, 0x24, 0x0b, 0x40, 0x0b, 0x44, 0x0b, + 0x48, 0x0b, 0x4e, 0x0b, 0x4c, 0x0b, 0x52, 0x0b, + 0x68, 0x0b, 0x6c, 0x0b, 0x70, 0x0b, 0x76, 0x0b, + 0x88, 0x0b, 0x92, 0x0b, 0xbe, 0x0b, 0xca, 0x0b, + 0xce, 0x0b, 0xde, 0x0b, 0xf4, 0x0b, 0xfa, 0x0b, + 0x00, 0x0c, 0x24, 0x0c, 0x28, 0x0c, 0x30, 0x0c, + 0x36, 0x0c, 0x3c, 0x0c, 0x40, 0x0c, 0x4a, 0x0c, + 0x50, 0x0c, 0x58, 0x0c, 0x56, 0x0c, 0x5c, 0x0c, + 0x60, 0x0c, 0x64, 0x0c, 0x80, 0x0c, 0x94, 0x0c, + 0x9a, 0x0c, 0x98, 0x0c, 0x9e, 0x0c, 0xa4, 0x0c, + 0xa2, 0x0c, 0xa8, 0x0c, 0xac, 0x0c, 0xb0, 0x0c, + 0xb4, 0x0c, 0xb8, 0x0c, 0xbc, 0x0c, 0xce, 0x0c, + 0xd2, 0x0c, 0xd6, 0x0c, 0xf4, 0x0c, 0xfa, 0x0c, + 0x00, 0x0d, 0xfe, 0x0c, 0x06, 0x0d, 0x0e, 0x0d, + 0x0c, 0x0d, 0x16, 0x0d, 0x1c, 0x0d, 0x22, 0x0d, + 0x20, 0x0d, 0x30, 0x0d, 0x7e, 0x0d, 0x82, 0x0d, + 0x9a, 0x0d, 0xa0, 0x0d, 0xa6, 0x0d, 0xb0, 0x0d, + 0xb8, 0x0d, 0xc2, 0x0d, 0xc8, 0x0d, 0xce, 0x0d, + 0xd4, 0x0d, 0xdc, 0x0d, 0x1e, 0x0e, 0x2c, 0x0e, + 0x3e, 0x0e, 0x4c, 0x0e, 0x50, 0x0e, 0x5e, 0x0e, + 0xae, 0x0e, 0xb8, 0x0e, 0xc6, 0x0e, 0xca, 0x0e, + 0, 0 +}; + +/* Fixup command. */ +#define KUE_TRIGCMD_OFFSET 5 +static unsigned char kue_trig_seg[] = { + 0xb6, 0xc3, 0x01, 0x00, 0x06, 0x64, 0x00, 0x00 +}; diff --git a/sys/dev/usb2/ethernet/if_kue2_reg.h b/sys/dev/usb2/ethernet/if_kue2_reg.h new file mode 100644 index 000000000000..989d1254d09b --- /dev/null +++ b/sys/dev/usb2/ethernet/if_kue2_reg.h @@ -0,0 +1,142 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + * + * $FreeBSD$ + */ + +/* + * Definitions for the KLSI KL5KUSB101B USB to ethernet controller. + * The KLSI part is controlled via vendor control requests, the structure + * of which depend a bit on the firmware running on the internal + * microcontroller. The one exception is the 'send scan data' command, + * which is used to load the firmware. + */ + +#define KUE_CMD_GET_ETHER_DESCRIPTOR 0x00 +#define KUE_CMD_SET_MCAST_FILTERS 0x01 +#define KUE_CMD_SET_PKT_FILTER 0x02 +#define KUE_CMD_GET_ETHERSTATS 0x03 +#define KUE_CMD_GET_GPIO 0x04 +#define KUE_CMD_SET_GPIO 0x05 +#define KUE_CMD_SET_MAC 0x06 +#define KUE_CMD_GET_MAC 0x07 +#define KUE_CMD_SET_URB_SIZE 0x08 +#define KUE_CMD_SET_SOFS 0x09 +#define KUE_CMD_SET_EVEN_PKTS 0x0A +#define KUE_CMD_SEND_SCAN 0xFF + +struct kue_ether_desc { + uint8_t kue_len; + uint8_t kue_rsvd0; + uint8_t kue_rsvd1; + uint8_t kue_macaddr[ETHER_ADDR_LEN]; + uint8_t kue_etherstats[4]; + uint8_t kue_maxseg[2]; + uint8_t kue_mcastfilt[2]; + uint8_t kue_rsvd2; +} __packed; + +#define KUE_ETHERSTATS(x) UGETDW((x)->sc_desc.kue_etherstats) +#define KUE_MAXSEG(x) UGETW((x)->sc_desc.kue_maxseg) +#define KUE_MCFILTCNT(x) (UGETW((x)->sc_desc.kue_mcastfilt) & 0x7FFF) +#define KUE_MCFILT_MAX (USB_ETHER_HASH_MAX / ETHER_ADDR_LEN) + +#define KUE_STAT_TX_OK 0x00000001 +#define KUE_STAT_RX_OK 0x00000002 +#define KUE_STAT_TX_ERR 0x00000004 +#define KUE_STAT_RX_ERR 0x00000008 +#define KUE_STAT_RX_NOBUF 0x00000010 +#define KUE_STAT_TX_UCAST_BYTES 0x00000020 +#define KUE_STAT_TX_UCAST_FRAMES 0x00000040 +#define KUE_STAT_TX_MCAST_BYTES 0x00000080 +#define KUE_STAT_TX_MCAST_FRAMES 0x00000100 +#define KUE_STAT_TX_BCAST_BYTES 0x00000200 +#define KUE_STAT_TX_BCAST_FRAMES 0x00000400 +#define KUE_STAT_RX_UCAST_BYTES 0x00000800 +#define KUE_STAT_RX_UCAST_FRAMES 0x00001000 +#define KUE_STAT_RX_MCAST_BYTES 0x00002000 +#define KUE_STAT_RX_MCAST_FRAMES 0x00004000 +#define KUE_STAT_RX_BCAST_BYTES 0x00008000 +#define KUE_STAT_RX_BCAST_FRAMES 0x00010000 +#define KUE_STAT_RX_CRCERR 0x00020000 +#define KUE_STAT_TX_QUEUE_LENGTH 0x00040000 +#define KUE_STAT_RX_ALIGNERR 0x00080000 +#define KUE_STAT_TX_SINGLECOLL 0x00100000 +#define KUE_STAT_TX_MULTICOLL 0x00200000 +#define KUE_STAT_TX_DEFERRED 0x00400000 +#define KUE_STAT_TX_MAXCOLLS 0x00800000 +#define KUE_STAT_RX_OVERRUN 0x01000000 +#define KUE_STAT_TX_UNDERRUN 0x02000000 +#define KUE_STAT_TX_SQE_ERR 0x04000000 +#define KUE_STAT_TX_CARRLOSS 0x08000000 +#define KUE_STAT_RX_LATECOLL 0x10000000 + +#define KUE_RXFILT_PROMISC 0x0001 +#define KUE_RXFILT_ALLMULTI 0x0002 +#define KUE_RXFILT_UNICAST 0x0004 +#define KUE_RXFILT_BROADCAST 0x0008 +#define KUE_RXFILT_MULTICAST 0x0010 + +#define KUE_TIMEOUT 1000 +#define KUE_MIN_FRAMELEN 60 + +#define KUE_CTL_READ 0x01 +#define KUE_CTL_WRITE 0x02 + +#define KUE_CONFIG_IDX 0 /* config number 1 */ +#define KUE_IFACE_IDX 0 + +/* The interrupt endpoint is currently unused by the KLSI part. */ +#define KUE_ENDPT_MAX 4 + +struct kue_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct usb2_config_td sc_config_td; + struct usb2_callout sc_watchdog; + struct mtx sc_mtx; + struct kue_ether_desc sc_desc; + + struct ifnet *sc_ifp; + device_t sc_dev; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[KUE_ENDPT_MAX]; + + uint32_t sc_unit; + + uint16_t sc_mcfilt_max; + uint16_t sc_flags; +#define KUE_FLAG_READ_STALL 0x0010 /* wait for clearing of stall */ +#define KUE_FLAG_WRITE_STALL 0x0020 /* wait for clearing of stall */ +#define KUE_FLAG_LL_READY 0x0040 /* Lower Layer Ready */ +#define KUE_FLAG_HL_READY 0x0080 /* Higher Layer Ready */ +#define KUE_FLAG_INTR_STALL 0x0100 /* wait for clearing of stall */ +}; diff --git a/sys/dev/usb2/ethernet/if_rue2.c b/sys/dev/usb2/ethernet/if_rue2.c new file mode 100644 index 000000000000..72c8852ff9f2 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_rue2.c @@ -0,0 +1,1400 @@ +/*- + * Copyright (c) 2001-2003, Shunsuke Akiyama . + * Copyright (c) 1997, 1998, 1999, 2000 Bill Paul . + * 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. + */ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * RealTek RTL8150 USB to fast ethernet controller driver. + * Datasheet is available from + * ftp://ftp.realtek.com.tw/lancard/data_sheet/8150/. + */ + +/* + * NOTE: all function names beginning like "rue_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include + +#define usb2_config_td_cc usb2_ether_cc +#define usb2_config_td_softc rue_softc + +#define USB_DEBUG_VAR rue_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if USB_DEBUG +static int rue_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, rue, CTLFLAG_RW, 0, "USB rue"); +SYSCTL_INT(_hw_usb2_rue, OID_AUTO, debug, CTLFLAG_RW, + &rue_debug, 0, "Debug level"); +#endif + +/* + * Various supported device vendors/products. + */ + +static const struct usb2_device_id rue_devs[] = { + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAKTX, 0)}, + {USB_VPI(USB_VENDOR_REALTEK, USB_PRODUCT_REALTEK_USBKR100, 0)}, +}; + +/* prototypes */ + +static device_probe_t rue_probe; +static device_attach_t rue_attach; +static device_detach_t rue_detach; +static device_shutdown_t rue_shutdown; + +static usb2_callback_t rue_intr_clear_stall_callback; +static usb2_callback_t rue_intr_callback; +static usb2_callback_t rue_bulk_read_clear_stall_callback; +static usb2_callback_t rue_bulk_read_callback; +static usb2_callback_t rue_bulk_write_clear_stall_callback; +static usb2_callback_t rue_bulk_write_callback; + +static usb2_config_td_command_t rue_config_copy; +static usb2_config_td_command_t rue_cfg_promisc_upd; +static usb2_config_td_command_t rue_cfg_first_time_setup; +static usb2_config_td_command_t rue_cfg_tick; +static usb2_config_td_command_t rue_cfg_pre_init; +static usb2_config_td_command_t rue_cfg_init; +static usb2_config_td_command_t rue_cfg_ifmedia_upd; +static usb2_config_td_command_t rue_cfg_pre_stop; +static usb2_config_td_command_t rue_cfg_stop; + +static void rue_cfg_do_request(struct rue_softc *sc, struct usb2_device_request *req, void *data); +static void rue_cfg_read_mem(struct rue_softc *sc, uint16_t addr, void *buf, uint16_t len); +static void rue_cfg_write_mem(struct rue_softc *sc, uint16_t addr, void *buf, uint16_t len); +static uint8_t rue_cfg_csr_read_1(struct rue_softc *sc, uint16_t reg); +static uint16_t rue_cfg_csr_read_2(struct rue_softc *sc, uint16_t reg); +static void rue_cfg_csr_write_1(struct rue_softc *sc, uint16_t reg, uint8_t val); +static void rue_cfg_csr_write_2(struct rue_softc *sc, uint16_t reg, uint16_t val); +static void rue_cfg_csr_write_4(struct rue_softc *sc, int reg, uint32_t val); + +static miibus_readreg_t rue_cfg_miibus_readreg; +static miibus_writereg_t rue_cfg_miibus_writereg; +static miibus_statchg_t rue_cfg_miibus_statchg; + +static void rue_cfg_reset(struct rue_softc *sc); +static void rue_start_cb(struct ifnet *ifp); +static void rue_start_transfers(struct rue_softc *sc); +static void rue_init_cb(void *arg); +static int rue_ifmedia_upd_cb(struct ifnet *ifp); +static void rue_ifmedia_sts_cb(struct ifnet *ifp, struct ifmediareq *ifmr); +static int rue_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data); +static void rue_watchdog(void *arg); + +static const struct usb2_config rue_config[RUE_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = MCLBYTES, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &rue_bulk_write_callback, + .mh.timeout = 10000, /* 10 seconds */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = (MCLBYTES + 4), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &rue_bulk_read_callback, + .mh.timeout = 0, /* no timeout */ + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &rue_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &rue_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &rue_intr_callback, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &rue_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static device_method_t rue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, rue_probe), + DEVMETHOD(device_attach, rue_attach), + DEVMETHOD(device_detach, rue_detach), + DEVMETHOD(device_shutdown, rue_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, rue_cfg_miibus_readreg), + DEVMETHOD(miibus_writereg, rue_cfg_miibus_writereg), + DEVMETHOD(miibus_statchg, rue_cfg_miibus_statchg), + + {0, 0} +}; + +static driver_t rue_driver = { + .name = "rue", + .methods = rue_methods, + .size = sizeof(struct rue_softc), +}; + +static devclass_t rue_devclass; + +DRIVER_MODULE(rue, ushub, rue_driver, rue_devclass, NULL, 0); +DRIVER_MODULE(miibus, rue, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(rue, usb2_ethernet, 1, 1, 1); +MODULE_DEPEND(rue, usb2_core, 1, 1, 1); +MODULE_DEPEND(rue, ether, 1, 1, 1); +MODULE_DEPEND(rue, miibus, 1, 1, 1); + +static void +rue_cfg_do_request(struct rue_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTF("device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +#define RUE_CFG_SETBIT(sc, reg, x) \ + rue_cfg_csr_write_1(sc, reg, rue_cfg_csr_read_1(sc, reg) | (x)) + +#define RUE_CFG_CLRBIT(sc, reg, x) \ + rue_cfg_csr_write_1(sc, reg, rue_cfg_csr_read_1(sc, reg) & ~(x)) + +static void +rue_cfg_read_mem(struct rue_softc *sc, uint16_t addr, void *buf, + uint16_t len) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + rue_cfg_do_request(sc, &req, buf); + return; +} + +static void +rue_cfg_write_mem(struct rue_softc *sc, uint16_t addr, void *buf, + uint16_t len) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + rue_cfg_do_request(sc, &req, buf); + return; +} + +static uint8_t +rue_cfg_csr_read_1(struct rue_softc *sc, uint16_t reg) +{ + uint8_t val; + + rue_cfg_read_mem(sc, reg, &val, 1); + return (val); +} + +static uint16_t +rue_cfg_csr_read_2(struct rue_softc *sc, uint16_t reg) +{ + uint8_t val[2]; + + rue_cfg_read_mem(sc, reg, &val, 2); + return (UGETW(val)); +} + +static void +rue_cfg_csr_write_1(struct rue_softc *sc, uint16_t reg, uint8_t val) +{ + rue_cfg_write_mem(sc, reg, &val, 1); + return; +} + +static void +rue_cfg_csr_write_2(struct rue_softc *sc, uint16_t reg, uint16_t val) +{ + uint8_t temp[2]; + + USETW(temp, val); + rue_cfg_write_mem(sc, reg, &temp, 2); + return; +} + +static void +rue_cfg_csr_write_4(struct rue_softc *sc, int reg, uint32_t val) +{ + uint8_t temp[4]; + + USETDW(temp, val); + rue_cfg_write_mem(sc, reg, &temp, 4); + return; +} + +static int +rue_cfg_miibus_readreg(device_t dev, int phy, int reg) +{ + struct rue_softc *sc = device_get_softc(dev); + uint16_t rval; + uint16_t ruereg; + uint8_t do_unlock; + + if (phy != 0) { /* RTL8150 supports PHY == 0, only */ + return (0); + } + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + switch (reg) { + case MII_BMCR: + ruereg = RUE_BMCR; + break; + case MII_BMSR: + ruereg = RUE_BMSR; + break; + case MII_ANAR: + ruereg = RUE_ANAR; + break; + case MII_ANER: + ruereg = RUE_AER; + break; + case MII_ANLPAR: + ruereg = RUE_ANLP; + break; + case MII_PHYIDR1: + case MII_PHYIDR2: + rval = 0; + goto done; + default: + if ((RUE_REG_MIN <= reg) && (reg <= RUE_REG_MAX)) { + rval = rue_cfg_csr_read_1(sc, reg); + goto done; + } + printf("rue%d: bad phy register\n", sc->sc_unit); + rval = 0; + goto done; + } + + rval = rue_cfg_csr_read_2(sc, ruereg); +done: + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return (rval); +} + +static int +rue_cfg_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct rue_softc *sc = device_get_softc(dev); + uint16_t ruereg; + uint8_t do_unlock; + + if (phy != 0) { /* RTL8150 supports PHY == 0, only */ + return (0); + } + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + switch (reg) { + case MII_BMCR: + ruereg = RUE_BMCR; + break; + case MII_BMSR: + ruereg = RUE_BMSR; + break; + case MII_ANAR: + ruereg = RUE_ANAR; + break; + case MII_ANER: + ruereg = RUE_AER; + break; + case MII_ANLPAR: + ruereg = RUE_ANLP; + break; + case MII_PHYIDR1: + case MII_PHYIDR2: + goto done; + default: + if ((RUE_REG_MIN <= reg) && (reg <= RUE_REG_MAX)) { + rue_cfg_csr_write_1(sc, reg, data); + goto done; + } + printf("%s: bad phy register\n", + sc->sc_name); + goto done; + } + rue_cfg_csr_write_2(sc, ruereg, data); +done: + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return (0); +} + +static void +rue_cfg_miibus_statchg(device_t dev) +{ + /* + * When the code below is enabled the card starts doing weird + * things after link going from UP to DOWN and back UP. + * + * Looks like some of register writes below messes up PHY + * interface. + * + * No visible regressions were found after commenting this code + * out, so that disable it for good. + */ +#if 0 + struct rue_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + uint16_t bmcr; + uint8_t do_unlock; + + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + RUE_CFG_CLRBIT(sc, RUE_CR, (RUE_CR_RE | RUE_CR_TE)); + + bmcr = rue_cfg_csr_read_2(sc, RUE_BMCR); + + if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) + bmcr |= RUE_BMCR_SPD_SET; + else + bmcr &= ~RUE_BMCR_SPD_SET; + + if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) + bmcr |= RUE_BMCR_DUPLEX; + else + bmcr &= ~RUE_BMCR_DUPLEX; + + rue_cfg_csr_write_2(sc, RUE_BMCR, bmcr); + + RUE_CFG_SETBIT(sc, RUE_CR, (RUE_CR_RE | RUE_CR_TE)); + + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } +#endif + return; +} + +static void +rue_mchash(struct usb2_config_td_cc *cc, const uint8_t *ptr) +{ + uint8_t h; + + h = ether_crc32_be(ptr, ETHER_ADDR_LEN) >> 26; + cc->if_hash[h / 8] |= 1 << (h & 7); + cc->if_nhash = 1; + return; +} + +static void +rue_config_copy(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + bzero(cc, sizeof(*cc)); + usb2_ether_cc(sc->sc_ifp, &rue_mchash, cc); + return; +} + +/* + * Program the 64-bit multicast hash filter. + */ +static void +rue_cfg_promisc_upd(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint16_t rxcfg; + + rxcfg = rue_cfg_csr_read_2(sc, RUE_RCR); + + if ((cc->if_flags & IFF_ALLMULTI) || + (cc->if_flags & IFF_PROMISC)) { + rxcfg |= (RUE_RCR_AAM | RUE_RCR_AAP); + rxcfg &= ~RUE_RCR_AM; + rue_cfg_csr_write_2(sc, RUE_RCR, rxcfg); + rue_cfg_csr_write_4(sc, RUE_MAR0, 0xFFFFFFFF); + rue_cfg_csr_write_4(sc, RUE_MAR4, 0xFFFFFFFF); + return; + } + /* first, zero all the existing hash bits */ + rue_cfg_csr_write_4(sc, RUE_MAR0, 0); + rue_cfg_csr_write_4(sc, RUE_MAR4, 0); + + if (cc->if_nhash) + rxcfg |= RUE_RCR_AM; + else + rxcfg &= ~RUE_RCR_AM; + + rxcfg &= ~(RUE_RCR_AAM | RUE_RCR_AAP); + + rue_cfg_csr_write_2(sc, RUE_RCR, rxcfg); + rue_cfg_write_mem(sc, RUE_MAR0, cc->if_hash, 4); + rue_cfg_write_mem(sc, RUE_MAR4, cc->if_hash + 4, 4); + return; +} + +static void +rue_cfg_reset(struct rue_softc *sc) +{ + usb2_error_t err; + uint16_t to; + + rue_cfg_csr_write_1(sc, RUE_CR, RUE_CR_SOFT_RST); + + for (to = 0;; to++) { + + if (to < RUE_TIMEOUT) { + + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 100); + + if (err) { + break; + } + if (!(rue_cfg_csr_read_1(sc, RUE_CR) & RUE_CR_SOFT_RST)) { + break; + } + } else { + printf("%s: reset timeout!\n", + sc->sc_name); + break; + } + } + + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 100); + return; +} + +/* + * Probe for a RTL8150 chip. + */ +static int +rue_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != RUE_CONFIG_IDX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != RUE_IFACE_IDX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(rue_devs, sizeof(rue_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +rue_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct rue_softc *sc = device_get_softc(dev); + int32_t error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "rue lock", NULL, MTX_DEF | MTX_RECURSE); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + iface_index = RUE_IFACE_IDX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, rue_config, RUE_ENDPT_MAX, + sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + NULL, sizeof(struct usb2_config_td_cc), 16); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + sc->sc_flags |= RUE_FLAG_WAIT_LINK; + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &rue_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + rue_watchdog(sc); + + return (0); /* success */ + +detach: + rue_detach(dev); + return (ENXIO); /* failure */ +} + +static void +rue_cfg_first_time_setup(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp; + int error; + uint8_t eaddr[min(ETHER_ADDR_LEN, 6)]; + + /* reset the adapter */ + rue_cfg_reset(sc); + + /* get station address from the EEPROM */ + rue_cfg_read_mem(sc, RUE_EEPROM_IDR0, + eaddr, ETHER_ADDR_LEN); + + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_ETHER); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + printf("%s: could not if_alloc()\n", + sc->sc_name); + goto done; + } + sc->sc_evilhack = ifp; + + ifp->if_softc = sc; + if_initname(ifp, "rue", sc->sc_unit); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = rue_ioctl_cb; + ifp->if_start = rue_start_cb; + ifp->if_watchdog = NULL; + ifp->if_init = rue_init_cb; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + /* + * XXX need Giant when accessing the device structures ! + */ + + mtx_unlock(&sc->sc_mtx); + + mtx_lock(&Giant); + + /* MII setup */ + error = mii_phy_probe(sc->sc_dev, &sc->sc_miibus, + &rue_ifmedia_upd_cb, + &rue_ifmedia_sts_cb); + mtx_unlock(&Giant); + + mtx_lock(&sc->sc_mtx); + + if (error) { + printf("%s: MII without any PHY!\n", + sc->sc_name); + if_free(ifp); + goto done; + } + sc->sc_ifp = ifp; + + mtx_unlock(&sc->sc_mtx); + + /* + * Call MI attach routine. + */ + + ether_ifattach(ifp, eaddr); + + mtx_lock(&sc->sc_mtx); + +done: + return; +} + +static int +rue_detach(device_t dev) +{ + struct rue_softc *sc = device_get_softc(dev); + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + rue_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, RUE_ENDPT_MAX); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + ether_ifdetach(ifp); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +rue_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct rue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~RUE_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +rue_intr_callback(struct usb2_xfer *xfer) +{ + struct rue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct rue_intrpkt pkt; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (ifp && (ifp->if_drv_flags & IFF_DRV_RUNNING) && + (xfer->actlen >= sizeof(pkt))) { + + usb2_copy_out(xfer->frbuffers, 0, &pkt, sizeof(pkt)); + + ifp->if_ierrors += pkt.rue_rxlost_cnt; + ifp->if_ierrors += pkt.rue_crcerr_cnt; + ifp->if_collisions += pkt.rue_col_cnt; + } + case USB_ST_SETUP: + if (sc->sc_flags & RUE_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= RUE_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + return; + } +} + +static void +rue_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct rue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~RUE_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +rue_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct rue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + uint16_t status; + struct mbuf *m = NULL; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen < 4) { + ifp->if_ierrors++; + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, xfer->actlen - 4, + &status, sizeof(status)); + + status = le16toh(status); + + /* check recieve packet was valid or not */ + + if ((status & RUE_RXSTAT_VALID) == 0) { + ifp->if_ierrors++; + goto tr_setup; + } + xfer->actlen -= 4; + + if (xfer->actlen < sizeof(struct ether_header)) { + ifp->if_ierrors++; + goto tr_setup; + } + m = usb2_ether_get_mbuf(); + + if (m == NULL) { + ifp->if_ierrors++; + goto tr_setup; + } + xfer->actlen = min(xfer->actlen, m->m_len); + + usb2_copy_out(xfer->frbuffers, 0, m->m_data, xfer->actlen); + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = xfer->actlen; + + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_flags & RUE_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "if_input" here, and not some lines up! + */ + if (m) { + mtx_unlock(&sc->sc_mtx); + (ifp->if_input) (ifp, m); + mtx_lock(&sc->sc_mtx); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= RUE_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + DPRINTF("bulk read error, %s\n", + usb2_errstr(xfer->error)); + return; + + } +} + +static void +rue_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct rue_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~RUE_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +rue_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct rue_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint32_t temp_len; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + + ifp->if_opackets++; + + case USB_ST_SETUP: + + if (sc->sc_flags & RUE_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + goto done; + } + if (sc->sc_flags & RUE_FLAG_WAIT_LINK) { + /* + * don't send anything if there is no link ! + */ + goto done; + } + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) { + goto done; + } + if (m->m_pkthdr.len > MCLBYTES) { + m->m_pkthdr.len = MCLBYTES; + } + temp_len = m->m_pkthdr.len; + + usb2_m_copy_in(xfer->frbuffers, 0, + m, 0, m->m_pkthdr.len); + + /* + * This is an undocumented behavior. + * RTL8150 chip doesn't send frame length smaller than + * RUE_MIN_FRAMELEN (60) byte packet. + */ + if (temp_len < RUE_MIN_FRAMELEN) { + usb2_bzero(xfer->frbuffers, temp_len, + RUE_MIN_FRAMELEN - temp_len); + temp_len = RUE_MIN_FRAMELEN; + } + xfer->frlengths[0] = temp_len; + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usb2_start_hardware(xfer); + +done: + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= RUE_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + ifp->if_oerrors++; + return; + + } +} + +static void +rue_cfg_tick(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mii_data *mii = GET_MII(sc); + + if ((ifp == NULL) || + (mii == NULL)) { + /* not ready */ + return; + } + mii_tick(mii); + + mii_pollstat(mii); + + if ((sc->sc_flags & RUE_FLAG_WAIT_LINK) && + (mii->mii_media_status & IFM_ACTIVE) && + (IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE)) { + sc->sc_flags &= ~RUE_FLAG_WAIT_LINK; + } + sc->sc_media_active = mii->mii_media_active; + sc->sc_media_status = mii->mii_media_status; + + /* start stopped transfers, if any */ + + rue_start_transfers(sc); + + return; +} + +static void +rue_start_cb(struct ifnet *ifp) +{ + struct rue_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + rue_start_transfers(sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +rue_start_transfers(struct rue_softc *sc) +{ + if ((sc->sc_flags & RUE_FLAG_LL_READY) && + (sc->sc_flags & RUE_FLAG_HL_READY)) { + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[4]); + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + } + return; +} + +static void +rue_init_cb(void *arg) +{ + struct rue_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &rue_cfg_pre_init, + &rue_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +rue_cfg_pre_init(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + /* immediate configuration */ + + rue_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= RUE_FLAG_HL_READY; + + return; +} + +static void +rue_cfg_init(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct mii_data *mii = GET_MII(sc); + uint16_t rxcfg; + + /* + * Cancel pending I/O + */ + + rue_cfg_stop(sc, cc, 0); + + /* set MAC address */ + + rue_cfg_write_mem(sc, RUE_IDR0, cc->if_lladdr, ETHER_ADDR_LEN); + + /* + * Set the initial TX and RX configuration. + */ + rue_cfg_csr_write_1(sc, RUE_TCR, RUE_TCR_CONFIG); + + rxcfg = RUE_RCR_CONFIG; + + /* Set capture broadcast bit to capture broadcast frames. */ + if (cc->if_flags & IFF_BROADCAST) + rxcfg |= RUE_RCR_AB; + else + rxcfg &= ~RUE_RCR_AB; + + rue_cfg_csr_write_2(sc, RUE_RCR, rxcfg); + + /* Load the multicast filter */ + rue_cfg_promisc_upd(sc, cc, 0); + + /* Enable RX and TX */ + rue_cfg_csr_write_1(sc, RUE_CR, (RUE_CR_TE | RUE_CR_RE | RUE_CR_EP3CLREN)); + + mii_mediachg(mii); + + sc->sc_flags |= (RUE_FLAG_READ_STALL | + RUE_FLAG_WRITE_STALL | + RUE_FLAG_LL_READY); + + rue_start_transfers(sc); + + return; +} + +/* + * Set media options. + */ +static int +rue_ifmedia_upd_cb(struct ifnet *ifp) +{ + struct rue_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, + &rue_cfg_ifmedia_upd, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static void +rue_cfg_ifmedia_upd(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mii_data *mii = GET_MII(sc); + + if ((ifp == NULL) || + (mii == NULL)) { + /* not ready */ + return; + } + sc->sc_flags |= RUE_FLAG_WAIT_LINK; + + if (mii->mii_instance) { + struct mii_softc *miisc; + + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { + mii_phy_reset(miisc); + } + } + mii_mediachg(mii); + + return; +} + +/* + * Report current media status. + */ +static void +rue_ifmedia_sts_cb(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct rue_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + ifmr->ifm_active = sc->sc_media_active; + ifmr->ifm_status = sc->sc_media_status; + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static int +rue_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct rue_softc *sc = ifp->if_softc; + struct mii_data *mii; + int error = 0; + + switch (command) { + case SIOCSIFFLAGS: + + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &rue_config_copy, + &rue_cfg_promisc_upd, 0, 0); + } else { + usb2_config_td_queue_command + (&sc->sc_config_td, &rue_cfg_pre_init, + &rue_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &rue_cfg_pre_stop, + &rue_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &rue_config_copy, + &rue_cfg_promisc_upd, 0, 0); + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + mii = GET_MII(sc); + if (mii == NULL) { + error = EINVAL; + } else { + error = ifmedia_ioctl + (ifp, (void *)data, &mii->mii_media, command); + } + break; + + default: + error = ether_ioctl(ifp, command, data); + break; + } + return (error); +} + +static void +rue_watchdog(void *arg) +{ + struct rue_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &rue_cfg_tick, 0, 0); + + usb2_callout_reset(&sc->sc_watchdog, + hz, &rue_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + return; +} + +/* + * NOTE: can be called when "ifp" is NULL + */ +static void +rue_cfg_pre_stop(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + rue_config_copy(sc, cc, refcount); + } + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(RUE_FLAG_HL_READY | + RUE_FLAG_LL_READY); + + sc->sc_flags |= RUE_FLAG_WAIT_LINK; + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[4]); + usb2_transfer_stop(sc->sc_xfer[5]); + return; +} + +static void +rue_cfg_stop(struct rue_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + rue_cfg_csr_write_1(sc, RUE_CR, 0x00); + + rue_cfg_reset(sc); + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +rue_shutdown(device_t dev) +{ + struct rue_softc *sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + + usb2_config_td_queue_command + (&sc->sc_config_td, &rue_cfg_pre_stop, + &rue_cfg_stop, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} diff --git a/sys/dev/usb2/ethernet/if_rue2_reg.h b/sys/dev/usb2/ethernet/if_rue2_reg.h new file mode 100644 index 000000000000..2c95c226fe6c --- /dev/null +++ b/sys/dev/usb2/ethernet/if_rue2_reg.h @@ -0,0 +1,194 @@ +/*- + * Copyright (c) 2001-2003, Shunsuke Akiyama . + * 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. + * + * $FreeBSD$ + */ + +#define RUE_CONFIG_IDX 0 /* config number 1 */ +#define RUE_IFACE_IDX 0 + +#define RUE_ENDPT_MAX 6 + +#define RUE_INTR_PKTLEN 0x8 + +#define RUE_TIMEOUT 50 +#define RUE_MIN_FRAMELEN 60 + +/* Registers. */ +#define RUE_IDR0 0x0120 +#define RUE_IDR1 0x0121 +#define RUE_IDR2 0x0122 +#define RUE_IDR3 0x0123 +#define RUE_IDR4 0x0124 +#define RUE_IDR5 0x0125 + +#define RUE_MAR0 0x0126 +#define RUE_MAR1 0x0127 +#define RUE_MAR2 0x0128 +#define RUE_MAR3 0x0129 +#define RUE_MAR4 0x012A +#define RUE_MAR5 0x012B +#define RUE_MAR6 0x012C +#define RUE_MAR7 0x012D + +#define RUE_CR 0x012E /* B, R/W */ +#define RUE_CR_SOFT_RST 0x10 +#define RUE_CR_RE 0x08 +#define RUE_CR_TE 0x04 +#define RUE_CR_EP3CLREN 0x02 + +#define RUE_TCR 0x012F /* B, R/W */ +#define RUE_TCR_TXRR1 0x80 +#define RUE_TCR_TXRR0 0x40 +#define RUE_TCR_IFG1 0x10 +#define RUE_TCR_IFG0 0x08 +#define RUE_TCR_NOCRC 0x01 +#define RUE_TCR_CONFIG (RUE_TCR_TXRR1 | RUE_TCR_TXRR0 | \ + RUE_TCR_IFG1 | RUE_TCR_IFG0) + +#define RUE_RCR 0x0130 /* W, R/W */ +#define RUE_RCR_TAIL 0x80 +#define RUE_RCR_AER 0x40 +#define RUE_RCR_AR 0x20 +#define RUE_RCR_AM 0x10 +#define RUE_RCR_AB 0x08 +#define RUE_RCR_AD 0x04 +#define RUE_RCR_AAM 0x02 +#define RUE_RCR_AAP 0x01 +#define RUE_RCR_CONFIG (RUE_RCR_TAIL | RUE_RCR_AD) + +#define RUE_TSR 0x0132 +#define RUE_RSR 0x0133 +#define RUE_CON0 0x0135 +#define RUE_CON1 0x0136 +#define RUE_MSR 0x0137 +#define RUE_PHYADD 0x0138 +#define RUE_PHYDAT 0x0139 + +#define RUE_PHYCNT 0x013B /* B, R/W */ +#define RUE_PHYCNT_PHYOWN 0x40 +#define RUE_PHYCNT_RWCR 0x20 + +#define RUE_GPPC 0x013D +#define RUE_WAKECNT 0x013E + +#define RUE_BMCR 0x0140 +#define RUE_BMCR_SPD_SET 0x2000 +#define RUE_BMCR_DUPLEX 0x0100 + +#define RUE_BMSR 0x0142 + +#define RUE_ANAR 0x0144 /* W, R/W */ +#define RUE_ANAR_PAUSE 0x0400 + +#define RUE_ANLP 0x0146 /* W, R/O */ +#define RUE_ANLP_PAUSE 0x0400 + +#define RUE_AER 0x0148 + +#define RUE_NWAYT 0x014A +#define RUE_CSCR 0x014C + +#define RUE_CRC0 0x014E +#define RUE_CRC1 0x0150 +#define RUE_CRC2 0x0152 +#define RUE_CRC3 0x0154 +#define RUE_CRC4 0x0156 + +#define RUE_BYTEMASK0 0x0158 +#define RUE_BYTEMASK1 0x0160 +#define RUE_BYTEMASK2 0x0168 +#define RUE_BYTEMASK3 0x0170 +#define RUE_BYTEMASK4 0x0178 + +#define RUE_PHY1 0x0180 +#define RUE_PHY2 0x0184 + +#define RUE_TW1 0x0186 + +#define RUE_REG_MIN 0x0120 +#define RUE_REG_MAX 0x0189 + +/* EEPROM address declarations. */ +#define RUE_EEPROM_BASE 0x1200 +#define RUE_EEPROM_IDR0 (RUE_EEPROM_BASE + 0x02) +#define RUE_EEPROM_IDR1 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR2 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR3 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR4 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR5 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_INTERVAL (RUE_EEPROM_BASE + 0x17) + +#define RUE_RXSTAT_VALID (0x01 << 12) +#define RUE_RXSTAT_RUNT (0x02 << 12) +#define RUE_RXSTAT_PMATCH (0x04 << 12) +#define RUE_RXSTAT_MCAST (0x08 << 12) + +#define GET_MII(sc) ((sc)->sc_miibus ? \ + device_get_softc((sc)->sc_miibus) : NULL) + +struct rue_intrpkt { + uint8_t rue_tsr; + uint8_t rue_rsr; + uint8_t rue_gep_msr; + uint8_t rue_waksr; + uint8_t rue_txok_cnt; + uint8_t rue_rxlost_cnt; + uint8_t rue_crcerr_cnt; + uint8_t rue_col_cnt; +} __packed; + +struct rue_type { + uint16_t rue_vid; + uint16_t rue_did; +}; + +struct rue_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct usb2_config_td sc_config_td; + struct usb2_callout sc_watchdog; + struct mtx sc_mtx; + + struct ifnet *sc_ifp; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[RUE_ENDPT_MAX]; + device_t sc_miibus; + device_t sc_dev; + + uint32_t sc_unit; + uint32_t sc_media_active; + uint32_t sc_media_status; + + uint16_t sc_flags; +#define RUE_FLAG_WAIT_LINK 0x0001 +#define RUE_FLAG_INTR_STALL 0x0002 +#define RUE_FLAG_READ_STALL 0x0004 +#define RUE_FLAG_WRITE_STALL 0x0008 +#define RUE_FLAG_LL_READY 0x0010 +#define RUE_FLAG_HL_READY 0x0020 + + uint8_t sc_name[16]; +}; diff --git a/sys/dev/usb2/ethernet/if_udav2.c b/sys/dev/usb2/ethernet/if_udav2.c new file mode 100644 index 000000000000..ea7795d26ee1 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_udav2.c @@ -0,0 +1,1361 @@ +/* $NetBSD: if_udav.c,v 1.2 2003/09/04 15:17:38 tsutsui Exp $ */ +/* $nabe: if_udav.c,v 1.3 2003/08/21 16:57:19 nabe Exp $ */ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2003 + * Shingo WATANABE . 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 author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + * + */ + +/* + * DM9601(DAVICOM USB to Ethernet MAC Controller with Integrated 10/100 PHY) + * The spec can be found at the following url. + * http://www.davicom.com.tw/big5/download/Data%20Sheet/DM9601-DS-P01-930914.pdf + */ + +/* + * NOTE: all function names beginning like "udav_cfg_" can only + * be called from within the config thread function ! + */ + +/* + * TODO: + * Interrupt Endpoint support + * External PHYs + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#define usb2_config_td_cc usb2_ether_cc +#define usb2_config_td_softc udav_softc + +#define USB_DEBUG_VAR udav_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* prototypes */ + +static device_probe_t udav_probe; +static device_attach_t udav_attach; +static device_detach_t udav_detach; +static device_shutdown_t udav_shutdown; + +static usb2_callback_t udav_bulk_write_clear_stall_callback; +static usb2_callback_t udav_bulk_write_callback; +static usb2_callback_t udav_bulk_read_clear_stall_callback; +static usb2_callback_t udav_bulk_read_callback; +static usb2_callback_t udav_intr_clear_stall_callback; +static usb2_callback_t udav_intr_callback; + +static usb2_config_td_command_t udav_cfg_first_time_setup; +static usb2_config_td_command_t udav_cfg_pre_init; +static usb2_config_td_command_t udav_cfg_init; +static usb2_config_td_command_t udav_config_copy; +static usb2_config_td_command_t udav_cfg_promisc_upd; +static usb2_config_td_command_t udav_cfg_pre_stop; +static usb2_config_td_command_t udav_cfg_stop; +static usb2_config_td_command_t udav_cfg_ifmedia_change; +static usb2_config_td_command_t udav_cfg_tick; + +static void udav_cfg_do_request(struct udav_softc *sc, struct usb2_device_request *req, void *data); +static void udav_cfg_csr_read(struct udav_softc *sc, uint16_t offset, void *buf, uint16_t len); +static void udav_cfg_csr_write(struct udav_softc *sc, uint16_t offset, void *buf, uint16_t len); +static uint8_t udav_cfg_csr_read1(struct udav_softc *sc, uint16_t offset); +static void udav_cfg_csr_write1(struct udav_softc *sc, uint16_t offset, uint8_t ch); +static void udav_init_cb(void *arg); +static void udav_cfg_reset(struct udav_softc *sc); +static void udav_start_cb(struct ifnet *ifp); +static void udav_start_transfers(struct udav_softc *sc); +static int udav_ioctl_cb(struct ifnet *ifp, u_long cmd, caddr_t data); +static void udav_watchdog(void *arg); +static int udav_ifmedia_change_cb(struct ifnet *ifp); +static void udav_ifmedia_status_cb(struct ifnet *ifp, struct ifmediareq *ifmr); + +static miibus_readreg_t udav_cfg_miibus_readreg; +static miibus_writereg_t udav_cfg_miibus_writereg; +static miibus_statchg_t udav_cfg_miibus_statchg; + +static const struct usb2_config udav_config[UDAV_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = (MCLBYTES + 2), + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &udav_bulk_write_callback, + .mh.timeout = 10000, /* 10 seconds */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = (MCLBYTES + 3), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &udav_bulk_read_callback, + .mh.timeout = 0, /* no timeout */ + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &udav_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &udav_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &udav_intr_callback, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &udav_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static device_method_t udav_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, udav_probe), + DEVMETHOD(device_attach, udav_attach), + DEVMETHOD(device_detach, udav_detach), + DEVMETHOD(device_shutdown, udav_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, udav_cfg_miibus_readreg), + DEVMETHOD(miibus_writereg, udav_cfg_miibus_writereg), + DEVMETHOD(miibus_statchg, udav_cfg_miibus_statchg), + + {0, 0} +}; + +static driver_t udav_driver = { + .name = "udav", + .methods = udav_methods, + .size = sizeof(struct udav_softc), +}; + +static devclass_t udav_devclass; + +DRIVER_MODULE(udav, ushub, udav_driver, udav_devclass, NULL, 0); +DRIVER_MODULE(miibus, udav, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(udav, usb2_ethernet, 1, 1, 1); +MODULE_DEPEND(udav, usb2_core, 1, 1, 1); +MODULE_DEPEND(udav, ether, 1, 1, 1); +MODULE_DEPEND(udav, miibus, 1, 1, 1); + +#if USB_DEBUG +static int udav_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, udav, CTLFLAG_RW, 0, "USB udav"); +SYSCTL_INT(_hw_usb2_udav, OID_AUTO, debug, CTLFLAG_RW, &udav_debug, 0, + "Debug level"); +#endif + +#define UDAV_CFG_SETBIT(sc, reg, x) \ + udav_cfg_csr_write1(sc, reg, udav_cfg_csr_read1(sc, reg) | (x)) + +#define UDAV_CFG_CLRBIT(sc, reg, x) \ + udav_cfg_csr_write1(sc, reg, udav_cfg_csr_read1(sc, reg) & ~(x)) + +static const struct usb2_device_id udav_devs[] = { + /* ShanTou DM9601 USB NIC */ + {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_DM9601, 0)}, + + /* ShanTou ST268 USB NIC */ + {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ST268, 0)}, + + /* Corega USB-TXC */ + {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXC, 0)}, +}; + +static int +udav_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UDAV_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UDAV_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(udav_devs, sizeof(udav_devs), uaa)); +} + +static int +udav_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct udav_softc *sc = device_get_softc(dev); + int32_t error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "udav lock", NULL, MTX_DEF | MTX_RECURSE); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + iface_index = UDAV_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, udav_config, UDAV_ENDPT_MAX, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + NULL, sizeof(struct usb2_config_td_cc), 16); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + sc->sc_flags |= UDAV_FLAG_WAIT_LINK; + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &udav_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + udav_watchdog(sc); + + return (0); /* success */ + +detach: + udav_detach(dev); + return (ENXIO); /* failure */ +} + +static void +udav_cfg_first_time_setup(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp; + int error; + uint8_t eaddr[min(ETHER_ADDR_LEN, 6)]; + + /* reset the adapter */ + + udav_cfg_reset(sc); + + /* get Ethernet Address */ + + udav_cfg_csr_read(sc, UDAV_PAR, eaddr, ETHER_ADDR_LEN); + + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_ETHER); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + printf("%s: could not if_alloc()\n", + sc->sc_name); + goto done; + } + sc->sc_evilhack = ifp; + + ifp->if_softc = sc; + if_initname(ifp, "udav", sc->sc_unit); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_start = udav_start_cb; + ifp->if_ioctl = udav_ioctl_cb; + ifp->if_watchdog = NULL; + ifp->if_init = udav_init_cb; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + /* + * XXX need Giant when accessing the device structures ! + */ + + mtx_unlock(&sc->sc_mtx); + + mtx_lock(&Giant); + + error = mii_phy_probe(sc->sc_dev, &sc->sc_miibus, + &udav_ifmedia_change_cb, + &udav_ifmedia_status_cb); + mtx_unlock(&Giant); + + mtx_lock(&sc->sc_mtx); + + if (error) { + printf("%s: MII without any PHY!\n", + sc->sc_name); + if_free(ifp); + goto done; + } + sc->sc_ifp = ifp; + + mtx_unlock(&sc->sc_mtx); + + /* + * Call MI attach routine. + */ + + ether_ifattach(ifp, eaddr); + + mtx_lock(&sc->sc_mtx); + +done: + return; +} + +static int +udav_detach(device_t dev) +{ + struct udav_softc *sc = device_get_softc(dev); + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + udav_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, UDAV_ENDPT_MAX); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + ether_ifdetach(ifp); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +udav_cfg_do_request(struct udav_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTF("device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +#if 0 +static void +udav_cfg_mem_read(struct udav_softc *sc, uint16_t offset, void *buf, + uint16_t len) +{ + struct usb2_device_request req; + + len &= 0xff; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_READ; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + udav_cfg_do_request(sc, &req, buf); + return; +} + +static void +udav_cfg_mem_write(struct udav_softc *sc, uint16_t offset, void *buf, + uint16_t len) +{ + struct usb2_device_request req; + + len &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_WRITE; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + udav_cfg_do_request(sc, &req, buf); + return; +} + +static void +udav_cfg_mem_write1(struct udav_softc *sc, uint16_t offset, + uint8_t ch) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_WRITE1; + USETW(req.wValue, ch); + USETW(req.wIndex, offset); + USETW(req.wLength, 0x0000); + + udav_cfg_do_request(sc, &req, NULL); + return; +} + +#endif + +static void +udav_cfg_csr_read(struct udav_softc *sc, uint16_t offset, void *buf, + uint16_t len) +{ + struct usb2_device_request req; + + len &= 0xff; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_READ; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + udav_cfg_do_request(sc, &req, buf); + return; +} + +static void +udav_cfg_csr_write(struct udav_softc *sc, uint16_t offset, void *buf, + uint16_t len) +{ + struct usb2_device_request req; + + offset &= 0xff; + len &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_WRITE; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + udav_cfg_do_request(sc, &req, buf); + return; +} + +static uint8_t +udav_cfg_csr_read1(struct udav_softc *sc, uint16_t offset) +{ + uint8_t val; + + udav_cfg_csr_read(sc, offset, &val, 1); + return (val); +} + +static void +udav_cfg_csr_write1(struct udav_softc *sc, uint16_t offset, + uint8_t ch) +{ + struct usb2_device_request req; + + offset &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_WRITE1; + USETW(req.wValue, ch); + USETW(req.wIndex, offset); + USETW(req.wLength, 0x0000); + + udav_cfg_do_request(sc, &req, NULL); + return; +} + +static void +udav_init_cb(void *arg) +{ + struct udav_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &udav_cfg_pre_init, + &udav_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +udav_cfg_pre_init(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + /* immediate configuration */ + + udav_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= UDAV_FLAG_HL_READY; + + return; +} + +static void +udav_cfg_init(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct mii_data *mii = GET_MII(sc); + + /* + * Cancel pending I/O + */ + + udav_cfg_stop(sc, cc, 0); + + /* set MAC address */ + + udav_cfg_csr_write(sc, UDAV_PAR, cc->if_lladdr, ETHER_ADDR_LEN); + + /* initialize network control register */ + + /* disable loopback */ + + UDAV_CFG_CLRBIT(sc, UDAV_NCR, UDAV_NCR_LBK0 | UDAV_NCR_LBK1); + + /* Initialize RX control register */ + UDAV_CFG_SETBIT(sc, UDAV_RCR, UDAV_RCR_DIS_LONG | UDAV_RCR_DIS_CRC); + + /* load multicast filter and update promiscious mode bit */ + udav_cfg_promisc_upd(sc, cc, 0); + + /* enable RX */ + UDAV_CFG_SETBIT(sc, UDAV_RCR, UDAV_RCR_RXEN); + + /* clear POWER_DOWN state of internal PHY */ + UDAV_CFG_SETBIT(sc, UDAV_GPCR, UDAV_GPCR_GEP_CNTL0); + UDAV_CFG_CLRBIT(sc, UDAV_GPR, UDAV_GPR_GEPIO0); + + mii_mediachg(mii); + + sc->sc_flags |= (UDAV_FLAG_READ_STALL | + UDAV_FLAG_WRITE_STALL | + UDAV_FLAG_LL_READY); + + udav_start_transfers(sc); + + return; +} + +static void +udav_cfg_reset(struct udav_softc *sc) +{ + usb2_error_t err; + uint16_t to; + + /* Select PHY */ +#if 1 + /* + * XXX: force select internal phy. + * external phy routines are not tested. + */ + UDAV_CFG_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); +#else + if (sc->sc_flags & UDAV_EXT_PHY) { + UDAV_CFG_SETBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); + } else { + UDAV_CFG_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); + } +#endif + + UDAV_CFG_SETBIT(sc, UDAV_NCR, UDAV_NCR_RST); + + for (to = 0;; to++) { + + if (to < 100) { + + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 100); + + if (err) { + break; + } + if (!(udav_cfg_csr_read1(sc, UDAV_NCR) & UDAV_NCR_RST)) { + break; + } + } else { + printf("%s: reset timeout!\n", + sc->sc_name); + break; + } + } + + err = usb2_config_td_sleep(&sc->sc_config_td, hz / 100); + + return; +} + +#define UDAV_BITS 6 + +static void +udav_mchash(struct usb2_config_td_cc *cc, const uint8_t *ptr) +{ + uint8_t h; + + h = ether_crc32_le(ptr, ETHER_ADDR_LEN) & + ((1 << UDAV_BITS) - 1); + cc->if_hash[h >> 3] |= 1 << (h & 0x7); + return; +} + +static void +udav_config_copy(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + bzero(cc, sizeof(*cc)); + usb2_ether_cc(sc->sc_ifp, &udav_mchash, cc); + return; +} + +static void +udav_cfg_promisc_upd(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint8_t rxmode; + + rxmode = udav_cfg_csr_read1(sc, UDAV_RCR); + + rxmode &= ~(UDAV_RCR_ALL | UDAV_RCR_PRMSC); + + if (cc->if_flags & IFF_PROMISC) { + + rxmode |= UDAV_RCR_ALL | UDAV_RCR_PRMSC; + + } else if (cc->if_flags & IFF_ALLMULTI) { + + rxmode |= UDAV_RCR_ALL; + } + /* write hash value to the register */ + udav_cfg_csr_write(sc, UDAV_MAR, cc->if_hash, 8); + + /* write new mode bits */ + udav_cfg_csr_write1(sc, UDAV_RCR, rxmode); + + return; +} + +static void +udav_start_cb(struct ifnet *ifp) +{ + struct udav_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + udav_start_transfers(sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +udav_start_transfers(struct udav_softc *sc) +{ + if ((sc->sc_flags & UDAV_FLAG_LL_READY) && + (sc->sc_flags & UDAV_FLAG_HL_READY)) { + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[4]); + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + } + return; +} + +static void +udav_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct udav_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UDAV_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +udav_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct udav_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint32_t extra_len; + uint32_t temp_len; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + + ifp->if_opackets++; + + case USB_ST_SETUP: + + if (sc->sc_flags & UDAV_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + goto done; + } + if (sc->sc_flags & UDAV_FLAG_WAIT_LINK) { + /* + * don't send anything if there is no link ! + */ + goto done; + } + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) { + goto done; + } + if (m->m_pkthdr.len > MCLBYTES) { + m->m_pkthdr.len = MCLBYTES; + } + if (m->m_pkthdr.len < UDAV_MIN_FRAME_LEN) { + extra_len = UDAV_MIN_FRAME_LEN - m->m_pkthdr.len; + } else { + extra_len = 0; + } + + temp_len = (m->m_pkthdr.len + extra_len); + + /* + * the frame length is specified in the first 2 bytes of the + * buffer + */ + buf[0] = (uint8_t)(temp_len); + buf[1] = (uint8_t)(temp_len >> 8); + + temp_len += 2; + + usb2_copy_in(xfer->frbuffers, 0, buf, 2); + + usb2_m_copy_in(xfer->frbuffers, 2, + m, 0, m->m_pkthdr.len); + + if (extra_len) { + usb2_bzero(xfer->frbuffers, temp_len - extra_len, + extra_len); + } + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + xfer->frlengths[0] = temp_len; + usb2_start_hardware(xfer); + +done: + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UDAV_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + ifp->if_oerrors++; + return; + + } +} + +static void +udav_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct udav_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UDAV_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +udav_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct udav_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + uint8_t status; + uint16_t total_len; + struct mbuf *m = NULL; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen < 1) { + ifp->if_ierrors++; + goto tr_setup; + } + xfer->actlen -= 1; + + usb2_copy_out(xfer->frbuffers, 0, &status, 1); + + if (status & UDAV_RSR_LCS) { + ifp->if_collisions++; + goto tr_setup; + } + if ((status & UDAV_RSR_ERR) || (xfer->actlen < 2)) { + ifp->if_ierrors++; + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 1, &total_len, 2); + + total_len = le16toh(total_len); + + xfer->actlen -= 2; + + xfer->actlen = min(xfer->actlen, total_len); + + if (xfer->actlen < (sizeof(struct ether_header) + ETHER_CRC_LEN)) { + ifp->if_ierrors++; + goto tr_setup; + } + xfer->actlen -= ETHER_CRC_LEN; + + m = usb2_ether_get_mbuf(); + + if (m == NULL) { + ifp->if_ierrors++; + goto tr_setup; + } + xfer->actlen = min(xfer->actlen, m->m_len); + + usb2_copy_out(xfer->frbuffers, 3, m->m_data, xfer->actlen); + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = xfer->actlen; + + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_flags & UDAV_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "if_input" here, and not some lines up! + */ + if (m) { + mtx_unlock(&sc->sc_mtx); + (ifp->if_input) (ifp, m); + mtx_lock(&sc->sc_mtx); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UDAV_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + DPRINTF("bulk read error, %s\n", + usb2_errstr(xfer->error)); + return; + + } +} + +static void +udav_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct udav_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UDAV_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +udav_intr_callback(struct usb2_xfer *xfer) +{ + struct udav_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + case USB_ST_SETUP: + if (sc->sc_flags & UDAV_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= UDAV_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + return; + } +} + +static int +udav_ioctl_cb(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct udav_softc *sc = ifp->if_softc; + struct mii_data *mii; + int error = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &udav_config_copy, + &udav_cfg_promisc_upd, 0, 0); + } else { + usb2_config_td_queue_command + (&sc->sc_config_td, &udav_cfg_pre_init, + &udav_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &udav_cfg_pre_stop, + &udav_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &udav_config_copy, + &udav_cfg_promisc_upd, 0, 0); + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + mii = GET_MII(sc); + if (mii == NULL) { + error = EINVAL; + } else { + error = ifmedia_ioctl + (ifp, (void *)data, &mii->mii_media, cmd); + } + break; + + default: + error = ether_ioctl(ifp, cmd, data); + break; + } + return (error); +} + +static void +udav_watchdog(void *arg) +{ + struct udav_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &udav_cfg_tick, 0, 0); + + usb2_callout_reset(&sc->sc_watchdog, + hz, &udav_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + return; +} + +/* + * NOTE: can be called when "ifp" is NULL + */ +static void +udav_cfg_pre_stop(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + udav_config_copy(sc, cc, refcount); + } + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(UDAV_FLAG_HL_READY | + UDAV_FLAG_LL_READY); + + sc->sc_flags |= UDAV_FLAG_WAIT_LINK; + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[4]); + usb2_transfer_stop(sc->sc_xfer[5]); + return; +} + +/* + * NOTE: can be called when "ifp" is NULL + */ +static void +udav_cfg_stop(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + udav_cfg_reset(sc); + return; +} + +static int +udav_ifmedia_change_cb(struct ifnet *ifp) +{ + struct udav_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, + &udav_cfg_ifmedia_change, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static void +udav_cfg_ifmedia_change(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mii_data *mii = GET_MII(sc); + + if ((ifp == NULL) || + (mii == NULL)) { + /* not ready */ + return; + } + sc->sc_flags |= UDAV_FLAG_WAIT_LINK; + + if (mii->mii_instance) { + struct mii_softc *miisc; + + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { + mii_phy_reset(miisc); + } + } + mii_mediachg(mii); + + return; +} + +static void +udav_ifmedia_status_cb(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct udav_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + ifmr->ifm_active = sc->sc_media_active; + ifmr->ifm_status = sc->sc_media_status; + } else { + ifmr->ifm_active = IFM_ETHER | IFM_NONE; + ifmr->ifm_status = 0; + } + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +udav_cfg_tick(struct udav_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mii_data *mii = GET_MII(sc); + + if ((ifp == NULL) || + (mii == NULL)) { + /* not ready */ + return; + } + mii_tick(mii); + + mii_pollstat(mii); + + if ((sc->sc_flags & UDAV_FLAG_WAIT_LINK) && + (mii->mii_media_status & IFM_ACTIVE) && + (IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE)) { + sc->sc_flags &= ~UDAV_FLAG_WAIT_LINK; + } + sc->sc_media_active = mii->mii_media_active; + sc->sc_media_status = mii->mii_media_status; + + /* start stopped transfers, if any */ + + udav_start_transfers(sc); + + return; +} + +static int +udav_cfg_miibus_readreg(device_t dev, int phy, int reg) +{ + struct udav_softc *sc = device_get_softc(dev); + uint16_t data16; + uint8_t val[2]; + uint8_t do_unlock; + + /* XXX: one PHY only for the internal PHY */ + if (phy != 0) { + return (0); + } + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + /* select internal PHY and set PHY register address */ + udav_cfg_csr_write1(sc, UDAV_EPAR, + UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); + + /* select PHY operation and start read command */ + udav_cfg_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRR); + + /* XXX: should we wait? */ + + /* end read command */ + UDAV_CFG_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRR); + + /* retrieve the result from data registers */ + udav_cfg_csr_read(sc, UDAV_EPDRL, val, 2); + + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + data16 = (val[0] | (val[1] << 8)); + + DPRINTFN(11, "phy=%d reg=0x%04x => 0x%04x\n", + phy, reg, data16); + + return (data16); +} + +static int +udav_cfg_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct udav_softc *sc = device_get_softc(dev); + uint8_t val[2]; + uint8_t do_unlock; + + /* XXX: one PHY only for the internal PHY */ + if (phy != 0) { + return (0); + } + /* avoid recursive locking */ + if (mtx_owned(&sc->sc_mtx)) { + do_unlock = 0; + } else { + mtx_lock(&sc->sc_mtx); + do_unlock = 1; + } + + /* select internal PHY and set PHY register address */ + udav_cfg_csr_write1(sc, UDAV_EPAR, + UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); + + /* put the value to the data registers */ + val[0] = (data & 0xff); + val[1] = (data >> 8) & 0xff; + udav_cfg_csr_write(sc, UDAV_EPDRL, val, 2); + + /* select PHY operation and start write command */ + udav_cfg_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRW); + + /* XXX: should we wait? */ + + /* end write command */ + UDAV_CFG_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRW); + + if (do_unlock) { + mtx_unlock(&sc->sc_mtx); + } + return (0); +} + +static void +udav_cfg_miibus_statchg(device_t dev) +{ + /* nothing to do */ + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +udav_shutdown(device_t dev) +{ + struct udav_softc *sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + + usb2_config_td_queue_command + (&sc->sc_config_td, &udav_cfg_pre_stop, + &udav_cfg_stop, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} diff --git a/sys/dev/usb2/ethernet/if_udav2_reg.h b/sys/dev/usb2/ethernet/if_udav2_reg.h new file mode 100644 index 000000000000..aa211e6be319 --- /dev/null +++ b/sys/dev/usb2/ethernet/if_udav2_reg.h @@ -0,0 +1,166 @@ +/* $NetBSD: if_udavreg.h,v 1.2 2003/09/04 15:17:39 tsutsui Exp $ */ +/* $nabe: if_udavreg.h,v 1.2 2003/08/21 16:26:40 nabe Exp $ */ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2003 + * Shingo WATANABE . 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 author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + * + */ + +#define UDAV_IFACE_INDEX 0 +#define UDAV_CONFIG_INDEX 0 /* config number 1 */ + +#define UDAV_ENDPT_MAX 6 /* units */ + +/* Packet length */ +#define UDAV_MIN_FRAME_LEN 60 + +/* Request */ +#define UDAV_REQ_REG_READ 0x00 /* Read from register(s) */ +#define UDAV_REQ_REG_WRITE 0x01 /* Write to register(s) */ +#define UDAV_REQ_REG_WRITE1 0x03 /* Write to a register */ + +#define UDAV_REQ_MEM_READ 0x02 /* Read from memory */ +#define UDAV_REQ_MEM_WRITE 0x05 /* Write to memory */ +#define UDAV_REQ_MEM_WRITE1 0x07 /* Write a byte to memory */ + +/* Registers */ +#define UDAV_NCR 0x00 /* Network Control Register */ +#define UDAV_NCR_EXT_PHY (1<<7) /* Select External PHY */ +#define UDAV_NCR_WAKEEN (1<<6) /* Wakeup Event Enable */ +#define UDAV_NCR_FCOL (1<<4) /* Force Collision Mode */ +#define UDAV_NCR_FDX (1<<3) /* Full-Duplex Mode (RO on Int. PHY) */ +#define UDAV_NCR_LBK1 (1<<2) /* Lookback Mode */ +#define UDAV_NCR_LBK0 (1<<1) /* Lookback Mode */ +#define UDAV_NCR_RST (1<<0) /* Software reset */ + +#define UDAV_RCR 0x05 /* RX Control Register */ +#define UDAV_RCR_WTDIS (1<<6) /* Watchdog Timer Disable */ +#define UDAV_RCR_DIS_LONG (1<<5) /* Discard Long Packet(over 1522Byte) */ +#define UDAV_RCR_DIS_CRC (1<<4) /* Discard CRC Error Packet */ +#define UDAV_RCR_ALL (1<<3) /* Pass All Multicast */ +#define UDAV_RCR_RUNT (1<<2) /* Pass Runt Packet */ +#define UDAV_RCR_PRMSC (1<<1) /* Promiscuous Mode */ +#define UDAV_RCR_RXEN (1<<0) /* RX Enable */ + +#define UDAV_RSR 0x06 /* RX Status Register */ +#define UDAV_RSR_RF (1<<7) /* Runt Frame */ +#define UDAV_RSR_MF (1<<6) /* Multicast Frame */ +#define UDAV_RSR_LCS (1<<5) /* Late Collision Seen */ +#define UDAV_RSR_RWTO (1<<4) /* Receive Watchdog Time-Out */ +#define UDAV_RSR_PLE (1<<3) /* Physical Layer Error */ +#define UDAV_RSR_AE (1<<2) /* Alignment Error */ +#define UDAV_RSR_CE (1<<1) /* CRC Error */ +#define UDAV_RSR_FOE (1<<0) /* FIFO Overflow Error */ +#define UDAV_RSR_ERR (UDAV_RSR_RF | UDAV_RSR_LCS | \ + UDAV_RSR_RWTO | UDAV_RSR_PLE | \ + UDAV_RSR_AE | UDAV_RSR_CE | UDAV_RSR_FOE) + +#define UDAV_EPCR 0x0b /* EEPROM & PHY Control Register */ +#define UDAV_EPCR_REEP (1<<5) /* Reload EEPROM */ +#define UDAV_EPCR_WEP (1<<4) /* Write EEPROM enable */ +#define UDAV_EPCR_EPOS (1<<3) /* EEPROM or PHY Operation Select */ +#define UDAV_EPCR_ERPRR (1<<2) /* EEPROM/PHY Register Read Command */ +#define UDAV_EPCR_ERPRW (1<<1) /* EEPROM/PHY Register Write Command */ +#define UDAV_EPCR_ERRE (1<<0) /* EEPROM/PHY Access Status */ + +#define UDAV_EPAR 0x0c /* EEPROM & PHY Control Register */ +#define UDAV_EPAR_PHY_ADR1 (1<<7) /* PHY Address bit 1 */ +#define UDAV_EPAR_PHY_ADR0 (1<<6) /* PHY Address bit 0 */ +#define UDAV_EPAR_EROA (1<<0) /* EEPROM Word/PHY Register Address */ +#define UDAV_EPAR_EROA_MASK (0x1f) /* [5:0] */ + +#define UDAV_EPDRL 0x0d /* EEPROM & PHY Data Register */ +#define UDAV_EPDRH 0x0e /* EEPROM & PHY Data Register */ + +#define UDAV_PAR0 0x10 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR1 0x11 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR2 0x12 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR3 0x13 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR4 0x14 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR5 0x15 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR UDAV_PAR0 + +#define UDAV_MAR0 0x16 /* Multicast Register */ +#define UDAV_MAR1 0x17 /* Multicast Register */ +#define UDAV_MAR2 0x18 /* Multicast Register */ +#define UDAV_MAR3 0x19 /* Multicast Register */ +#define UDAV_MAR4 0x1a /* Multicast Register */ +#define UDAV_MAR5 0x1b /* Multicast Register */ +#define UDAV_MAR6 0x1c /* Multicast Register */ +#define UDAV_MAR7 0x1d /* Multicast Register */ +#define UDAV_MAR UDAV_MAR0 + +#define UDAV_GPCR 0x1e /* General purpose control register */ +#define UDAV_GPCR_GEP_CNTL6 (1<<6) /* General purpose control 6 */ +#define UDAV_GPCR_GEP_CNTL5 (1<<5) /* General purpose control 5 */ +#define UDAV_GPCR_GEP_CNTL4 (1<<4) /* General purpose control 4 */ +#define UDAV_GPCR_GEP_CNTL3 (1<<3) /* General purpose control 3 */ +#define UDAV_GPCR_GEP_CNTL2 (1<<2) /* General purpose control 2 */ +#define UDAV_GPCR_GEP_CNTL1 (1<<1) /* General purpose control 1 */ +#define UDAV_GPCR_GEP_CNTL0 (1<<0) /* General purpose control 0 */ + +#define UDAV_GPR 0x1f /* General purpose register */ +#define UDAV_GPR_GEPIO6 (1<<6) /* General purpose 6 */ +#define UDAV_GPR_GEPIO5 (1<<5) /* General purpose 5 */ +#define UDAV_GPR_GEPIO4 (1<<4) /* General purpose 4 */ +#define UDAV_GPR_GEPIO3 (1<<3) /* General purpose 3 */ +#define UDAV_GPR_GEPIO2 (1<<2) /* General purpose 2 */ +#define UDAV_GPR_GEPIO1 (1<<1) /* General purpose 1 */ +#define UDAV_GPR_GEPIO0 (1<<0) /* General purpose 0 */ + +#define GET_MII(sc) ((sc)->sc_miibus ? \ + device_get_softc((sc)->sc_miibus) : NULL) + +struct udav_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct usb2_config_td sc_config_td; + struct usb2_callout sc_watchdog; + struct mtx sc_mtx; + + struct ifnet *sc_ifp; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[UDAV_ENDPT_MAX]; + device_t sc_miibus; + device_t sc_dev; + + uint32_t sc_unit; + uint32_t sc_media_active; + uint32_t sc_media_status; + + uint16_t sc_flags; +#define UDAV_FLAG_WAIT_LINK 0x0001 +#define UDAV_FLAG_INTR_STALL 0x0002 +#define UDAV_FLAG_READ_STALL 0x0004 +#define UDAV_FLAG_WRITE_STALL 0x0008 +#define UDAV_FLAG_LL_READY 0x0010 +#define UDAV_FLAG_HL_READY 0x0020 +#define UDAV_FLAG_EXT_PHY 0x0040 + + uint8_t sc_name[16]; +}; diff --git a/sys/dev/usb2/ethernet/usb2_ethernet.c b/sys/dev/usb2/ethernet/usb2_ethernet.c new file mode 100644 index 000000000000..6651e0b5a176 --- /dev/null +++ b/sys/dev/usb2/ethernet/usb2_ethernet.c @@ -0,0 +1,101 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_ethernet, 1); +MODULE_DEPEND(usb2_ethernet, usb2_core, 1, 1, 1); + +/*------------------------------------------------------------------------* + * usb2_ether_get_mbuf - get a new ethernet aligned mbuf + *------------------------------------------------------------------------*/ +struct mbuf * +usb2_ether_get_mbuf(void) +{ + struct mbuf *m; + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m) { + m->m_len = m->m_pkthdr.len = MCLBYTES; + m_adj(m, ETHER_ALIGN); + } + return (m); +} + +/*------------------------------------------------------------------------* + * usb2_ether_cc - common ethernet config copy + *------------------------------------------------------------------------*/ +void +usb2_ether_cc(struct ifnet *ifp, usb2_ether_mchash_t *fhash, + struct usb2_ether_cc *cc) +{ + struct ifmultiaddr *ifma; + uint8_t i; + + if (ifp == NULL) { + /* Nothing to do */ + return; + } + /* Copy interface flags */ + + cc->if_flags = ifp->if_flags; + + /* Copy link layer address */ + + for (i = 0; i != ETHER_ADDR_LEN; i++) { + cc->if_lladdr[i] = IF_LLADDR(ifp)[i]; + } + + /* Check hash filter disable bits */ + + if ((ifp->if_flags & IFF_ALLMULTI) || + (ifp->if_flags & IFF_PROMISC)) { + + memset(cc->if_hash, 0xFF, sizeof(cc->if_hash)); + + } else if (fhash) { + + /* Compute hash bits for multicast filter */ + + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) { + continue; + } + fhash(cc, LLADDR((struct sockaddr_dl *) + (ifma->ifma_addr))); + } + IF_ADDR_UNLOCK(ifp); + + /* Compute hash bits for broadcast address */ + + if (ifp->if_flags & IFF_BROADCAST) { + fhash(cc, ifp->if_broadcastaddr); + } + } + return; +} diff --git a/sys/dev/usb2/ethernet/usb2_ethernet.h b/sys/dev/usb2/ethernet/usb2_ethernet.h new file mode 100644 index 000000000000..e6e039b552d9 --- /dev/null +++ b/sys/dev/usb2/ethernet/usb2_ethernet.h @@ -0,0 +1,67 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_ETHERNET_H_ +#define _USB2_ETHERNET_H_ + +#include "opt_inet.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "miibus_if.h" + +#include +#include + +#define USB_ETHER_HASH_MAX 64 /* bytes */ + +struct usb2_ether_cc { + uint32_t if_flags; + uint16_t if_rxfilt; + uint8_t if_lladdr[ETHER_ADDR_LEN]; + uint8_t if_mhash; + uint8_t if_nhash; + uint8_t if_hash[USB_ETHER_HASH_MAX]; +}; + +typedef void (usb2_ether_mchash_t)(struct usb2_ether_cc *cc, const uint8_t *ptr); + +struct mbuf *usb2_ether_get_mbuf(void); +void usb2_ether_cc(struct ifnet *ifp, usb2_ether_mchash_t *fhash, struct usb2_ether_cc *cc); + +#endif /* _USB2_ETHERNET_H_ */ diff --git a/sys/dev/usb2/image/usb2_image.c b/sys/dev/usb2/image/usb2_image.c new file mode 100644 index 000000000000..5f6badbbe2c3 --- /dev/null +++ b/sys/dev/usb2/image/usb2_image.c @@ -0,0 +1,31 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_image, 1); +MODULE_DEPEND(usb2_image, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/image/usb2_image.h b/sys/dev/usb2/image/usb2_image.h new file mode 100644 index 000000000000..ce1526ed77d8 --- /dev/null +++ b/sys/dev/usb2/image/usb2_image.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_IMAGE_H_ +#define _USB2_IMAGE_H_ + +#endif /* _USB2_IMAGE_H_ */ diff --git a/sys/dev/usb2/image/uscanner2.c b/sys/dev/usb2/image/uscanner2.c new file mode 100644 index 000000000000..4dcdde3636f6 --- /dev/null +++ b/sys/dev/usb2/image/uscanner2.c @@ -0,0 +1,642 @@ +/* $NetBSD: uscanner.c,v 1.30 2002/07/11 21:14:36 augustss Exp$ */ + +/* Also already merged from NetBSD: + * $NetBSD: uscanner.c,v 1.33 2002/09/23 05:51:24 simonb Exp $ + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology + * and Nick Hibma (n_hibma@qubesoft.com). + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR uscanner_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USB_DEBUG +static int uscanner_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uscanner, CTLFLAG_RW, 0, "USB uscanner"); +SYSCTL_INT(_hw_usb2_uscanner, OID_AUTO, uscanner, CTLFLAG_RW, &uscanner_debug, + 0, "uscanner debug level"); +#endif + +/* + * uscanner transfers macros definition. + */ +#define USCANNER_BSIZE (1 << 15) +#define USCANNER_IFQ_MAXLEN 2 +#define USCANNER_N_TRANSFER 4 + +/* + * Transfers stallings handling flags definition. + */ +#define USCANNER_FLAG_READ_STALL 0x01 +#define USCANNER_FLAG_WRITE_STALL 0x02 + +/* + * uscanner_info flags definition. + */ +#define USCANNER_FLAG_KEEP_OPEN 0x04 + +struct uscanner_softc { + struct usb2_fifo_sc sc_fifo; + struct mtx sc_mtx; + + struct usb2_xfer *sc_xfer[USCANNER_N_TRANSFER]; + + uint8_t sc_flags; /* Used to prevent stalls */ +}; + +/* + * Prototypes for driver handling routines (sorted by use). + */ +static device_probe_t uscanner_probe; +static device_attach_t uscanner_attach; +static device_detach_t uscanner_detach; + +/* + * Prototypes for xfer transfer callbacks. + */ +static usb2_callback_t uscanner_read_callback; +static usb2_callback_t uscanner_read_clear_stall_callback; +static usb2_callback_t uscanner_write_callback; +static usb2_callback_t uscanner_write_clear_stall_callback; + +/* + * Prototypes for the character device handling routines. + */ +static usb2_fifo_close_t uscanner_close; +static usb2_fifo_cmd_t uscanner_start_read; +static usb2_fifo_cmd_t uscanner_start_write; +static usb2_fifo_cmd_t uscanner_stop_read; +static usb2_fifo_cmd_t uscanner_stop_write; +static usb2_fifo_open_t uscanner_open; + +static struct usb2_fifo_methods uscanner_fifo_methods = { + .f_close = &uscanner_close, + .f_open = &uscanner_open, + .f_start_read = &uscanner_start_read, + .f_start_write = &uscanner_start_write, + .f_stop_read = &uscanner_stop_read, + .f_stop_write = &uscanner_stop_write, + .basename[0] = "uscanner", +}; + +/* + * xfer transfers array. Resolve-stalling callbacks are marked as control + * transfers. + */ +static const struct usb2_config uscanner_config[USCANNER_N_TRANSFER] = { + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = USCANNER_BSIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,.proxy_buffer = 1,.force_short_xfer = 1,}, + .mh.callback = &uscanner_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = USCANNER_BSIZE, + .mh.flags = {.pipe_bof = 1,.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &uscanner_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &uscanner_write_clear_stall_callback, + .mh.timeout = 1000, + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &uscanner_read_clear_stall_callback, + .mh.timeout = 1000, + .mh.interval = 50, /* 50ms */ + }, +}; + +static devclass_t uscanner_devclass; + +static device_method_t uscanner_methods[] = { + DEVMETHOD(device_probe, uscanner_probe), + DEVMETHOD(device_attach, uscanner_attach), + DEVMETHOD(device_detach, uscanner_detach), + {0, 0} +}; + +static driver_t uscanner_driver = { + .name = "uscanner", + .methods = uscanner_methods, + .size = sizeof(struct uscanner_softc), +}; + +DRIVER_MODULE(uscanner, ushub, uscanner_driver, uscanner_devclass, NULL, 0); +MODULE_DEPEND(uscanner, usb2_image, 1, 1, 1); +MODULE_DEPEND(uscanner, usb2_core, 1, 1, 1); + +/* + * USB scanners device IDs + */ +static const struct usb2_device_id uscanner_devs[] = { + /* Acer */ + {USB_VPI(USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_320U, 0)}, + {USB_VPI(USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_640U, 0)}, + {USB_VPI(USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_640BT, 0)}, + {USB_VPI(USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_620U, 0)}, + {USB_VPI(USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_1240U, 0)}, + {USB_VPI(USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_C310U, 0)}, + {USB_VPI(USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_4300U, 0)}, + /* AGFA */ + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1236U, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1212U, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1212U2, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANTOUCH, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE40, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE50, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE20, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE25, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE26, 0)}, + {USB_VPI(USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE52, 0)}, + /* Avision */ + {USB_VPI(USB_VENDOR_AVISION, USB_PRODUCT_AVISION_1200U, 0)}, + /* Canon */ + {USB_VPI(USB_VENDOR_CANON, USB_PRODUCT_CANON_N656U, 0)}, + {USB_VPI(USB_VENDOR_CANON, USB_PRODUCT_CANON_N676U, 0)}, + {USB_VPI(USB_VENDOR_CANON, USB_PRODUCT_CANON_N1220U, 0)}, + {USB_VPI(USB_VENDOR_CANON, USB_PRODUCT_CANON_D660U, 0)}, + {USB_VPI(USB_VENDOR_CANON, USB_PRODUCT_CANON_N1240U, 0)}, + {USB_VPI(USB_VENDOR_CANON, USB_PRODUCT_CANON_LIDE25, 0)}, + /* Epson */ + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_636, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_610, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1200, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1240, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1250, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1270, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1600, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1640, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_640U, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1650, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1660, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1670, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1260, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_RX425, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3200, USCANNER_FLAG_KEEP_OPEN)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_GT9700F, USCANNER_FLAG_KEEP_OPEN)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_CX5400, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_DX7400, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_GT9300UF, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_2480, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3500, USCANNER_FLAG_KEEP_OPEN)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3590, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4200, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4800, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4990, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_5000, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_6000, 0)}, + {USB_VPI(USB_VENDOR_EPSON, USB_PRODUCT_EPSON_DX8400, 0)}, + /* HP */ + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_2200C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_3300C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_3400CSE, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_4100C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_4200C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_4300C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_4670V, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_S20, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_5200C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_5300C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_5400C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_6200C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_6300C, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_82x0C, 0)}, + /* Kye */ + {USB_VPI(USB_VENDOR_KYE, USB_PRODUCT_KYE_VIVIDPRO, 0)}, + /* Microtek */ + {USB_VPI(USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_X6U, 0)}, + {USB_VPI(USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_336CX, 0)}, + {USB_VPI(USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_336CX2, 0)}, + {USB_VPI(USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_C6, 0)}, + {USB_VPI(USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6USL, 0)}, + {USB_VPI(USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6USL2, 0)}, + {USB_VPI(USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6UL, 0)}, + /* Minolta */ + {USB_VPI(USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_5400, 0)}, + /* Mustek */ + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200CU, 0)}, + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_BEARPAW1200F, 0)}, + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_BEARPAW1200TA, 0)}, + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_600USB, 0)}, + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_600CU, 0)}, + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200USB, 0)}, + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200UB, 0)}, + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200USBPLUS, 0)}, + {USB_VPI(USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200CUPLUS, 0)}, + /* National */ + {USB_VPI(USB_VENDOR_NATIONAL, USB_PRODUCT_NATIONAL_BEARPAW1200, 0)}, + {USB_VPI(USB_VENDOR_NATIONAL, USB_PRODUCT_NATIONAL_BEARPAW2400, 0)}, + /* Nikon */ + {USB_VPI(USB_VENDOR_NIKON, USB_PRODUCT_NIKON_LS40, 0)}, + /* Primax */ + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2X300, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E300, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2300, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E3002, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_9600, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_600U, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_6200, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_19200, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_1200U, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G600, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_636I, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2600, 0)}, + {USB_VPI(USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E600, 0)}, + /* Scanlogic */ + {USB_VPI(USB_VENDOR_SCANLOGIC, USB_PRODUCT_SCANLOGIC_336CX, 0)}, + /* Ultima */ + {USB_VPI(USB_VENDOR_ULTIMA, USB_PRODUCT_ULTIMA_1200UBPLUS, 0)}, + /* UMAX */ + {USB_VPI(USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA1220U, 0)}, + {USB_VPI(USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA1236U, 0)}, + {USB_VPI(USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2000U, 0)}, + {USB_VPI(USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2100U, 0)}, + {USB_VPI(USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2200U, 0)}, + {USB_VPI(USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA3400, 0)}, + /* Visioneer */ + {USB_VPI(USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_3000, 0)}, + {USB_VPI(USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_5300, 0)}, + {USB_VPI(USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_7600, 0)}, + {USB_VPI(USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_6100, 0)}, + {USB_VPI(USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_6200, 0)}, + {USB_VPI(USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_8100, 0)}, + {USB_VPI(USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_8600, 0)} +}; + +/* + * uscanner device probing method. + */ +static int +uscanner_probe(device_t dev) +{ + struct usb2_attach_arg *uaa; + + DPRINTFN(11, "\n"); + + uaa = device_get_ivars(dev); + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* Give other class drivers a chance for multifunctional scanners. */ + if (uaa->use_generic == 0) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(uscanner_devs, sizeof(uscanner_devs), uaa)); +} + +/* + * uscanner device attaching method. + */ +static int +uscanner_attach(device_t dev) +{ + struct usb2_attach_arg *uaa; + struct uscanner_softc *sc; + int unit; + int error; + + uaa = device_get_ivars(dev); + sc = device_get_softc(dev); + unit = device_get_unit(dev); + + /* + * A first path softc structure filling. sc_fifo and + * sc_xfer are initialised later. + */ + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + mtx_init(&sc->sc_mtx, "uscanner mutex", NULL, MTX_DEF | MTX_RECURSE); + + /* + * Announce the device: + */ + device_set_usb2_desc(dev); + + /* + * Setup the transfer. + */ + if ((error = usb2_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, + uscanner_config, USCANNER_N_TRANSFER, sc, &sc->sc_mtx))) { + device_printf(dev, "could not setup transfers, " + "error=%s\n", usb2_errstr(error)); + goto detach; + } + /* set interface permissions */ + usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &uscanner_fifo_methods, &sc->sc_fifo, + unit, 0 - 1, uaa->info.bIfaceIndex); + if (error) { + goto detach; + } + return (0); + +detach: + uscanner_detach(dev); + return (ENOMEM); +} + +/* + * uscanner device detaching method. + */ +static int +uscanner_detach(device_t dev) +{ + struct uscanner_softc *sc; + + sc = device_get_softc(dev); + + usb2_fifo_detach(&sc->sc_fifo); + usb2_transfer_unsetup(sc->sc_xfer, USCANNER_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +/* + * Reading callback. Implemented as an "in" bulk transfer. + */ +static void +uscanner_read_callback(struct usb2_xfer *xfer) +{ + struct uscanner_softc *sc; + struct usb2_fifo *f; + + sc = xfer->priv_sc; + f = sc->sc_fifo.fp[USB_FIFO_RX]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_fifo_put_data(f, xfer->frbuffers, 0, + xfer->actlen, 1); + + case USB_ST_SETUP: + /* + * If reading is in stall, just jump to clear stall callback and + * solve the situation. + */ + if (sc->sc_flags & USCANNER_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + break; + } + if (usb2_fifo_put_bytes_max(f) != 0) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= USCANNER_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + break; + } + return; +} + +/* + * Removing stall on reading callback. + */ +static void +uscanner_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uscanner_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~USCANNER_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +/* + * Writing callback. Implemented as an "out" bulk transfer. + */ +static void +uscanner_write_callback(struct usb2_xfer *xfer) +{ + struct uscanner_softc *sc; + struct usb2_fifo *f; + uint32_t actlen; + + sc = xfer->priv_sc; + f = sc->sc_fifo.fp[USB_FIFO_TX]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + /* + * If writing is in stall, just jump to clear stall callback and + * solve the situation. + */ + if (sc->sc_flags & USCANNER_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + break; + } + /* + * Write datas, setup and perform hardware transfer. + */ + if (usb2_fifo_get_data(f, xfer->frbuffers, 0, + xfer->max_data_length, &actlen, 0)) { + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= USCANNER_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + break; + } + return; +} + +/* + * Removing stall on writing callback. + */ +static void +uscanner_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uscanner_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~USCANNER_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +/* + * uscanner character device opening method. + */ +static int +uscanner_open(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct uscanner_softc *sc; + + sc = fifo->priv_sc0; + + if (!(sc->sc_flags & USCANNER_FLAG_KEEP_OPEN)) { + if (fflags & FWRITE) { + sc->sc_flags |= USCANNER_FLAG_WRITE_STALL; + } + if (fflags & FREAD) { + sc->sc_flags |= USCANNER_FLAG_READ_STALL; + } + } + if (fflags & FREAD) { + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_xfer[1]->max_data_length, + USCANNER_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + if (fflags & FWRITE) { + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_xfer[0]->max_data_length, + USCANNER_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + return (0); +} + +static void +uscanner_close(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + if (fflags & (FREAD | FWRITE)) { + usb2_fifo_free_buffer(fifo); + } + return; +} + +/* + * uscanner character device start reading method. + */ +static void +uscanner_start_read(struct usb2_fifo *fifo) +{ + struct uscanner_softc *sc; + + sc = fifo->priv_sc0; + usb2_transfer_start(sc->sc_xfer[1]); +} + +/* + * uscanner character device start writing method. + */ +static void +uscanner_start_write(struct usb2_fifo *fifo) +{ + struct uscanner_softc *sc; + + sc = fifo->priv_sc0; + usb2_transfer_start(sc->sc_xfer[0]); +} + +/* + * uscanner character device stop reading method. + */ +static void +uscanner_stop_read(struct usb2_fifo *fifo) +{ + struct uscanner_softc *sc; + + sc = fifo->priv_sc0; + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); +} + +/* + * uscanner character device stop writing method. + */ +static void +uscanner_stop_write(struct usb2_fifo *fifo) +{ + struct uscanner_softc *sc; + + sc = fifo->priv_sc0; + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); +} diff --git a/sys/dev/usb2/include/Makefile b/sys/dev/usb2/include/Makefile new file mode 100644 index 000000000000..a8f35009ea6d --- /dev/null +++ b/sys/dev/usb2/include/Makefile @@ -0,0 +1,14 @@ +# +# $FreeBSD$ +# + + +S=${.CURDIR}/../../.. + +all: + awk -f $S/tools/usbdevs2h.awk $S/dev/usb2/core/usbdevs -d ; mv usbdevs_data.h usb2_devtable.h + awk -f $S/tools/usbdevs2h.awk $S/dev/usb2/core/usbdevs -h ; mv usbdevs.h usb2_devid.h + +clean: + rm -f usb2_devtable.h usb2_devid.h + diff --git a/sys/dev/usb2/include/ufm2_ioctl.h b/sys/dev/usb2/include/ufm2_ioctl.h new file mode 100644 index 000000000000..921b3d4f37a7 --- /dev/null +++ b/sys/dev/usb2/include/ufm2_ioctl.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 2001 M. Warner Losh + * 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. + * + * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson. + * This code includes software developed by the NetBSD Foundation, Inc. and + * its contributors. + */ + +/* $FreeBSD$ */ + +#include + +#define FM_SET_FREQ _IOWR('U', 200, int) +#define FM_GET_FREQ _IOWR('U', 201, int) +#define FM_START _IOWR('U', 202, int) +#define FM_STOP _IOWR('U', 203, int) +#define FM_GET_STAT _IOWR('U', 204, int) diff --git a/sys/dev/usb2/include/urio2_ioctl.h b/sys/dev/usb2/include/urio2_ioctl.h new file mode 100644 index 000000000000..713647cf9773 --- /dev/null +++ b/sys/dev/usb2/include/urio2_ioctl.h @@ -0,0 +1,41 @@ +/*- + ---------------------------------------------------------------------- + + Copyright (C) 2000 Cesar Miquel (miquel@df.uba.ar) + + Redistribution and use in source and binary forms, with or without + modification, are permitted under any licence of your choise which + meets the open source licence definiton + http://www.opensource.org/opd.html such as the GNU licence or the + BSD licence. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License or the BSD license for more details. + + ---------------------------------------------------------------------- + + Modified for FreeBSD by Iwasa Kazmi + + ---------------------------------------------------------------------- */ + +/* $FreeBSD$ */ + +#include + +struct RioCommand { + uint16_t length; + int request; + int requesttype; + int value; + int index; + void *buffer; + int timeout; +}; + +#define RIO_SEND_COMMAND _IOWR('U', 200, struct RioCommand) +#define RIO_RECV_COMMAND _IOWR('U', 201, struct RioCommand) + +#define RIO_DIR_OUT 0x0 +#define RIO_DIR_IN 0x1 diff --git a/sys/dev/usb2/include/usb2_cdc.h b/sys/dev/usb2/include/usb2_cdc.h new file mode 100644 index 000000000000..8031990ff0cc --- /dev/null +++ b/sys/dev/usb2/include/usb2_cdc.h @@ -0,0 +1,205 @@ +/* $NetBSD: usbcdc.h,v 1.9 2004/10/23 13:24:24 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#ifndef _USB_CDC_H_ +#define _USB_CDC_H_ + +#define UDESCSUB_CDC_HEADER 0 +#define UDESCSUB_CDC_CM 1 /* Call Management */ +#define UDESCSUB_CDC_ACM 2 /* Abstract Control Model */ +#define UDESCSUB_CDC_DLM 3 /* Direct Line Management */ +#define UDESCSUB_CDC_TRF 4 /* Telephone Ringer */ +#define UDESCSUB_CDC_TCLSR 5 /* Telephone Call */ +#define UDESCSUB_CDC_UNION 6 +#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_NCT 10 +#define UDESCSUB_CDC_PUF 11 +#define UDESCSUB_CDC_EUF 12 +#define UDESCSUB_CDC_MCMF 13 +#define UDESCSUB_CDC_CCMF 14 +#define UDESCSUB_CDC_ENF 15 +#define UDESCSUB_CDC_ANF 16 + +struct usb2_cdc_header_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uWord bcdCDC; +} __packed; + +struct usb2_cdc_cm_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmCapabilities; +#define USB_CDC_CM_DOES_CM 0x01 +#define USB_CDC_CM_OVER_DATA 0x02 + uByte bDataInterface; +} __packed; + +struct usb2_cdc_acm_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmCapabilities; +#define USB_CDC_ACM_HAS_FEATURE 0x01 +#define USB_CDC_ACM_HAS_LINE 0x02 +#define USB_CDC_ACM_HAS_BREAK 0x04 +#define USB_CDC_ACM_HAS_NETWORK_CONN 0x08 +} __packed; + +struct usb2_cdc_union_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bMasterInterface; + uByte bSlaveInterface[1]; +} __packed; + +struct usb2_cdc_ethernet_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte iMacAddress; + uDWord bmEthernetStatistics; + uWord wMaxSegmentSize; + uWord wNumberMCFilters; + uByte bNumberPowerFilters; +} __packed; + +#define UCDC_SEND_ENCAPSULATED_COMMAND 0x00 +#define UCDC_GET_ENCAPSULATED_RESPONSE 0x01 +#define UCDC_SET_COMM_FEATURE 0x02 +#define UCDC_GET_COMM_FEATURE 0x03 +#define UCDC_ABSTRACT_STATE 0x01 +#define UCDC_COUNTRY_SETTING 0x02 +#define UCDC_CLEAR_COMM_FEATURE 0x04 +#define UCDC_SET_LINE_CODING 0x20 +#define UCDC_GET_LINE_CODING 0x21 +#define UCDC_SET_CONTROL_LINE_STATE 0x22 +#define UCDC_LINE_DTR 0x0001 +#define UCDC_LINE_RTS 0x0002 +#define UCDC_SEND_BREAK 0x23 +#define UCDC_BREAK_ON 0xffff +#define UCDC_BREAK_OFF 0x0000 + +struct usb2_cdc_abstract_state { + uWord wState; +#define UCDC_IDLE_SETTING 0x0001 +#define UCDC_DATA_MULTIPLEXED 0x0002 +} __packed; + +#define UCDC_ABSTRACT_STATE_LENGTH 2 + +struct usb2_cdc_line_state { + uDWord dwDTERate; + uByte bCharFormat; +#define UCDC_STOP_BIT_1 0 +#define UCDC_STOP_BIT_1_5 1 +#define UCDC_STOP_BIT_2 2 + uByte bParityType; +#define UCDC_PARITY_NONE 0 +#define UCDC_PARITY_ODD 1 +#define UCDC_PARITY_EVEN 2 +#define UCDC_PARITY_MARK 3 +#define UCDC_PARITY_SPACE 4 + uByte bDataBits; +} __packed; + +#define UCDC_LINE_STATE_LENGTH 7 + +struct usb2_cdc_notification { + uByte bmRequestType; +#define UCDC_NOTIFICATION 0xa1 + uByte bNotification; +#define UCDC_N_NETWORK_CONNECTION 0x00 +#define UCDC_N_RESPONSE_AVAILABLE 0x01 +#define UCDC_N_AUX_JACK_HOOK_STATE 0x08 +#define UCDC_N_RING_DETECT 0x09 +#define UCDC_N_SERIAL_STATE 0x20 +#define UCDC_N_CALL_STATE_CHANGED 0x28 +#define UCDC_N_LINE_STATE_CHANGED 0x29 +#define UCDC_N_CONNECTION_SPEED_CHANGE 0x2a + uWord wValue; + uWord wIndex; + uWord wLength; + uByte data[16]; +} __packed; + +#define UCDC_NOTIFICATION_LENGTH 8 + +/* + * Bits set in the SERIAL STATE notifcation (first byte of data) + */ + +#define UCDC_N_SERIAL_OVERRUN 0x40 +#define UCDC_N_SERIAL_PARITY 0x20 +#define UCDC_N_SERIAL_FRAMING 0x10 +#define UCDC_N_SERIAL_RI 0x08 +#define UCDC_N_SERIAL_BREAK 0x04 +#define UCDC_N_SERIAL_DSR 0x02 +#define UCDC_N_SERIAL_DCD 0x01 + +/* Serial state bit masks */ +#define UCDC_MDM_RXCARRIER 0x01 +#define UCDC_MDM_TXCARRIER 0x02 +#define UCDC_MDM_BREAK 0x04 +#define UCDC_MDM_RING 0x08 +#define UCDC_MDM_FRAMING_ERR 0x10 +#define UCDC_MDM_PARITY_ERR 0x20 +#define UCDC_MDM_OVERRUN_ERR 0x40 + +/* 512x4 Multi Frame Ethernet Header */ +struct usb2_cdc_mf_eth_512x4_header { + uByte bSig[2]; /* "FL" - Frag List */ + uByte bReserved[4]; + uWord wFragLength[511 * 4]; +#define CDCE_512X4_FRAG_LENGTH_OFFSET 6 /* bytes */ +#define CDCE_512X4_FRAG_LAST_MASK 0x8000 +#define CDCE_512X4_FRAG_LENGTH_MASK 0x1FFF /* bytes */ +#define CDCE_512X4_FRAME_FRAG_MAX 4 /* fragments */ +#define CDCE_512X4_FRAMES_MAX 511 /* frames */ +#define CDCE_512X4_FRAGS_MAX (511 * 4) /* fragments */ + uWord wPadding; /* used to make transfer short */ +} __packed; + +#endif /* _USB_CDC_H_ */ diff --git a/sys/dev/usb2/include/usb2_defs.h b/sys/dev/usb2/include/usb2_defs.h new file mode 100644 index 000000000000..d791f4dd84d4 --- /dev/null +++ b/sys/dev/usb2/include/usb2_defs.h @@ -0,0 +1,68 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_DEFS_H_ +#define _USB2_DEFS_H_ + +/* Definition of some USB constants */ + +#define USB_BUS_MAX 256 /* units */ +#define USB_DEV_MAX 128 /* units */ +#define USB_IFACE_MAX 32 /* units */ +#define USB_EP_MAX (2*16) /* hardcoded */ +#define USB_FIFO_MAX (4 * USB_EP_MAX) + +#define USB_MAX_DEVICES USB_DEV_MAX /* including virtual root HUB and + * address zero */ +#define USB_MAX_ENDPOINTS USB_EP_MAX /* 2 directions on 16 endpoints */ + +#define USB_UNCONFIG_INDEX 0xFF /* internal use only */ +#define USB_IFACE_INDEX_ANY 0xFF /* internal use only */ + +#define USB_START_ADDR 0 /* default USB device BUS address + * after USB bus reset */ + +#define USB_CONTROL_ENDPOINT 0 /* default control endpoint */ + +#define USB_FRAMES_PER_SECOND_FS 1000 /* full speed */ +#define USB_FRAMES_PER_SECOND_HS 8000 /* high speed */ + +#define USB_FS_BYTES_PER_HS_UFRAME 188 /* bytes */ +#define USB_HS_MICRO_FRAMES_MAX 8 /* units */ + +/* sanity checks */ + +#if (USB_FIFO_MAX < USB_EP_MAX) +#error "Misconfigured limits #1" +#endif +#if (USB_FIFO_MAX & 1) +#error "Misconfigured limits #2" +#endif +#if (USB_EP_MAX < (2*16)) +#error "Misconfigured limits #3" +#endif + +#endif /* _USB2_DEFS_H_ */ diff --git a/sys/dev/usb2/include/usb2_devid.h b/sys/dev/usb2/include/usb2_devid.h new file mode 100644 index 000000000000..7f59f5465e06 --- /dev/null +++ b/sys/dev/usb2/include/usb2_devid.h @@ -0,0 +1,2489 @@ +/* $FreeBSD$ */ + +/* + * THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + * + * generated from: + * FreeBSD: src/sys/dev/usb/usbdevs,v 1.372 2008/09/19 09:04:06 kevlo Exp + */ +/* $NetBSD: usbdevs,v 1.392 2004/12/29 08:38:44 imp Exp $ */ + +/*- + * Copyright (c) 1998-2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * List of known USB vendors + * + * USB.org publishes a VID list of USB-IF member companies at + * http://www.usb.org/developers/tools + * Note that it does not show companies that have obtained a Vendor ID + * without becoming full members. + * + * Please note that these IDs do not do anything. Adding an ID here and + * regenerating the usbdevs.h and usbdevs_data.h only makes a symbolic name + * available to the source code and does not change any functionality, nor + * does it make your device available to a specific driver. + * It will however make the descriptive string available if a device does not + * provide the string itself. + * + * After adding a vendor ID VNDR and a product ID PRDCT you will have the + * following extra defines: + * #define USB_VENDOR_VNDR 0x???? + * #define USB_PRODUCT_VNDR_PRDCT 0x???? + * + * You may have to add these defines to the respective probe routines to + * make the device recognised by the appropriate device driver. + */ + +#define USB_VENDOR_UNKNOWN1 0x0053 /* Unknown vendor */ +#define USB_VENDOR_UNKNOWN2 0x0105 /* Unknown vendor */ +#define USB_VENDOR_EGALAX2 0x0123 /* eGalax, Inc. */ +#define USB_VENDOR_HUMAX 0x02ad /* HUMAX */ +#define USB_VENDOR_LTS 0x0386 /* LTS */ +#define USB_VENDOR_BWCT 0x03da /* Bernd Walter Computer Technology */ +#define USB_VENDOR_AOX 0x03e8 /* AOX */ +#define USB_VENDOR_THESYS 0x03e9 /* Thesys */ +#define USB_VENDOR_DATABROADCAST 0x03ea /* Data Broadcasting */ +#define USB_VENDOR_ATMEL 0x03eb /* Atmel */ +#define USB_VENDOR_IWATSU 0x03ec /* Iwatsu America */ +#define USB_VENDOR_MITSUMI 0x03ee /* Mitsumi */ +#define USB_VENDOR_HP 0x03f0 /* Hewlett Packard */ +#define USB_VENDOR_GENOA 0x03f1 /* Genoa */ +#define USB_VENDOR_OAK 0x03f2 /* Oak */ +#define USB_VENDOR_ADAPTEC 0x03f3 /* Adaptec */ +#define USB_VENDOR_DIEBOLD 0x03f4 /* Diebold */ +#define USB_VENDOR_SIEMENSELECTRO 0x03f5 /* Siemens Electromechanical */ +#define USB_VENDOR_EPSONIMAGING 0x03f8 /* Epson Imaging */ +#define USB_VENDOR_KEYTRONIC 0x03f9 /* KeyTronic */ +#define USB_VENDOR_OPTI 0x03fb /* OPTi */ +#define USB_VENDOR_ELITEGROUP 0x03fc /* Elitegroup */ +#define USB_VENDOR_XILINX 0x03fd /* Xilinx */ +#define USB_VENDOR_FARALLON 0x03fe /* Farallon Communications */ +#define USB_VENDOR_NATIONAL 0x0400 /* National Semiconductor */ +#define USB_VENDOR_NATIONALREG 0x0401 /* National Registry */ +#define USB_VENDOR_ACERLABS 0x0402 /* Acer Labs */ +#define USB_VENDOR_FTDI 0x0403 /* Future Technology Devices */ +#define USB_VENDOR_NCR 0x0404 /* NCR */ +#define USB_VENDOR_SYNOPSYS2 0x0405 /* Synopsys */ +#define USB_VENDOR_FUJITSUICL 0x0406 /* Fujitsu-ICL */ +#define USB_VENDOR_FUJITSU2 0x0407 /* Fujitsu Personal Systems */ +#define USB_VENDOR_QUANTA 0x0408 /* Quanta */ +#define USB_VENDOR_NEC 0x0409 /* NEC */ +#define USB_VENDOR_KODAK 0x040a /* Eastman Kodak */ +#define USB_VENDOR_WELTREND 0x040b /* Weltrend */ +#define USB_VENDOR_VIA 0x040d /* VIA */ +#define USB_VENDOR_MCCI 0x040e /* MCCI */ +#define USB_VENDOR_MELCO 0x0411 /* Melco */ +#define USB_VENDOR_LEADTEK 0x0413 /* Leadtek */ +#define USB_VENDOR_WINBOND 0x0416 /* Winbond */ +#define USB_VENDOR_PHOENIX 0x041a /* Phoenix */ +#define USB_VENDOR_CREATIVE 0x041e /* Creative Labs */ +#define USB_VENDOR_NOKIA 0x0421 /* Nokia */ +#define USB_VENDOR_ADI 0x0422 /* ADI Systems */ +#define USB_VENDOR_CATC 0x0423 /* Computer Access Technology */ +#define USB_VENDOR_SMC2 0x0424 /* Standard Microsystems */ +#define USB_VENDOR_MOTOROLA_HK 0x0425 /* Motorola HK */ +#define USB_VENDOR_GRAVIS 0x0428 /* Advanced Gravis Computer */ +#define USB_VENDOR_CIRRUSLOGIC 0x0429 /* Cirrus Logic */ +#define USB_VENDOR_INNOVATIVE 0x042c /* Innovative Semiconductors */ +#define USB_VENDOR_MOLEX 0x042f /* Molex */ +#define USB_VENDOR_SUN 0x0430 /* Sun Microsystems */ +#define USB_VENDOR_UNISYS 0x0432 /* Unisys */ +#define USB_VENDOR_TAUGA 0x0436 /* Taugagreining HF */ +#define USB_VENDOR_AMD 0x0438 /* Advanced Micro Devices */ +#define USB_VENDOR_LEXMARK 0x043d /* Lexmark International */ +#define USB_VENDOR_LG 0x043e /* LG Electronics */ +#define USB_VENDOR_NANAO 0x0440 /* NANAO */ +#define USB_VENDOR_GATEWAY 0x0443 /* Gateway 2000 */ +#define USB_VENDOR_NMB 0x0446 /* NMB */ +#define USB_VENDOR_ALPS 0x044e /* Alps Electric */ +#define USB_VENDOR_THRUST 0x044f /* Thrustmaster */ +#define USB_VENDOR_TI 0x0451 /* Texas Instruments */ +#define USB_VENDOR_ANALOGDEVICES 0x0456 /* Analog Devices */ +#define USB_VENDOR_SIS 0x0457 /* Silicon Integrated Systems Corp. */ +#define USB_VENDOR_KYE 0x0458 /* KYE Systems */ +#define USB_VENDOR_DIAMOND2 0x045a /* Diamond (Supra) */ +#define USB_VENDOR_RENESAS 0x045b /* Renesas */ +#define USB_VENDOR_MICROSOFT 0x045e /* Microsoft */ +#define USB_VENDOR_PRIMAX 0x0461 /* Primax Electronics */ +#define USB_VENDOR_MGE 0x0463 /* MGE UPS Systems */ +#define USB_VENDOR_AMP 0x0464 /* AMP */ +#define USB_VENDOR_CHERRY 0x046a /* Cherry Mikroschalter */ +#define USB_VENDOR_MEGATRENDS 0x046b /* American Megatrends */ +#define USB_VENDOR_LOGITECH 0x046d /* Logitech */ +#define USB_VENDOR_BTC 0x046e /* Behavior Tech. Computer */ +#define USB_VENDOR_PHILIPS 0x0471 /* Philips */ +#define USB_VENDOR_SUN2 0x0472 /* Sun Microsystems (offical) */ +#define USB_VENDOR_SANYO 0x0474 /* Sanyo Electric */ +#define USB_VENDOR_SEAGATE 0x0477 /* Seagate */ +#define USB_VENDOR_CONNECTIX 0x0478 /* Connectix */ +#define USB_VENDOR_SEMTECH 0x047a /* Semtech */ +#define USB_VENDOR_KENSINGTON 0x047d /* Kensington */ +#define USB_VENDOR_LUCENT 0x047e /* Lucent */ +#define USB_VENDOR_PLANTRONICS 0x047f /* Plantronics */ +#define USB_VENDOR_KYOCERA 0x0482 /* Kyocera Wireless Corp. */ +#define USB_VENDOR_STMICRO 0x0483 /* STMicroelectronics */ +#define USB_VENDOR_FOXCONN 0x0489 /* Foxconn */ +#define USB_VENDOR_YAMAHA 0x0499 /* YAMAHA */ +#define USB_VENDOR_COMPAQ 0x049f /* Compaq */ +#define USB_VENDOR_HITACHI 0x04a4 /* Hitachi */ +#define USB_VENDOR_ACERP 0x04a5 /* Acer Peripherals */ +#define USB_VENDOR_DAVICOM 0x04a6 /* Davicom */ +#define USB_VENDOR_VISIONEER 0x04a7 /* Visioneer */ +#define USB_VENDOR_CANON 0x04a9 /* Canon */ +#define USB_VENDOR_NIKON 0x04b0 /* Nikon */ +#define USB_VENDOR_PAN 0x04b1 /* Pan International */ +#define USB_VENDOR_IBM 0x04b3 /* IBM */ +#define USB_VENDOR_CYPRESS 0x04b4 /* Cypress Semiconductor */ +#define USB_VENDOR_ROHM 0x04b5 /* ROHM */ +#define USB_VENDOR_COMPAL 0x04b7 /* Compal */ +#define USB_VENDOR_EPSON 0x04b8 /* Seiko Epson */ +#define USB_VENDOR_RAINBOW 0x04b9 /* Rainbow Technologies */ +#define USB_VENDOR_IODATA 0x04bb /* I-O Data */ +#define USB_VENDOR_TDK 0x04bf /* TDK */ +#define USB_VENDOR_3COMUSR 0x04c1 /* U.S. Robotics */ +#define USB_VENDOR_METHODE 0x04c2 /* Methode Electronics Far East */ +#define USB_VENDOR_MAXISWITCH 0x04c3 /* Maxi Switch */ +#define USB_VENDOR_LOCKHEEDMER 0x04c4 /* Lockheed Martin Energy Research */ +#define USB_VENDOR_FUJITSU 0x04c5 /* Fujitsu */ +#define USB_VENDOR_TOSHIBAAM 0x04c6 /* Toshiba America */ +#define USB_VENDOR_MICROMACRO 0x04c7 /* Micro Macro Technologies */ +#define USB_VENDOR_KONICA 0x04c8 /* Konica */ +#define USB_VENDOR_LITEON 0x04ca /* Lite-On Technology */ +#define USB_VENDOR_FUJIPHOTO 0x04cb /* Fuji Photo Film */ +#define USB_VENDOR_PHILIPSSEMI 0x04cc /* Philips Semiconductors */ +#define USB_VENDOR_TATUNG 0x04cd /* Tatung Co. Of America */ +#define USB_VENDOR_SCANLOGIC 0x04ce /* ScanLogic */ +#define USB_VENDOR_MYSON 0x04cf /* Myson Technology */ +#define USB_VENDOR_DIGI2 0x04d0 /* Digi */ +#define USB_VENDOR_ITTCANON 0x04d1 /* ITT Canon */ +#define USB_VENDOR_ALTEC 0x04d2 /* Altec Lansing */ +#define USB_VENDOR_LSI 0x04d4 /* LSI */ +#define USB_VENDOR_MENTORGRAPHICS 0x04d6 /* Mentor Graphics */ +#define USB_VENDOR_ITUNERNET 0x04d8 /* I-Tuner Networks */ +#define USB_VENDOR_HOLTEK 0x04d9 /* Holtek Semiconductor, Inc. */ +#define USB_VENDOR_PANASONIC 0x04da /* Panasonic (Matsushita) */ +#define USB_VENDOR_HUANHSIN 0x04dc /* Huan Hsin */ +#define USB_VENDOR_SHARP 0x04dd /* Sharp */ +#define USB_VENDOR_IIYAMA 0x04e1 /* Iiyama */ +#define USB_VENDOR_SHUTTLE 0x04e6 /* Shuttle Technology */ +#define USB_VENDOR_ELO 0x04e7 /* Elo TouchSystems */ +#define USB_VENDOR_SAMSUNG 0x04e8 /* Samsung Electronics */ +#define USB_VENDOR_NORTHSTAR 0x04eb /* Northstar */ +#define USB_VENDOR_TOKYOELECTRON 0x04ec /* Tokyo Electron */ +#define USB_VENDOR_ANNABOOKS 0x04ed /* Annabooks */ +#define USB_VENDOR_JVC 0x04f1 /* JVC */ +#define USB_VENDOR_CHICONY 0x04f2 /* Chicony Electronics */ +#define USB_VENDOR_ELAN 0x04f3 /* Elan */ +#define USB_VENDOR_NEWNEX 0x04f7 /* Newnex */ +#define USB_VENDOR_BROTHER 0x04f9 /* Brother Industries */ +#define USB_VENDOR_DALLAS 0x04fa /* Dallas Semiconductor */ +#define USB_VENDOR_SUNPLUS 0x04fc /* Sunplus */ +#define USB_VENDOR_PFU 0x04fe /* PFU */ +#define USB_VENDOR_FUJIKURA 0x0501 /* Fujikura/DDK */ +#define USB_VENDOR_ACER 0x0502 /* Acer */ +#define USB_VENDOR_3COM 0x0506 /* 3Com */ +#define USB_VENDOR_HOSIDEN 0x0507 /* Hosiden Corporation */ +#define USB_VENDOR_AZTECH 0x0509 /* Aztech Systems */ +#define USB_VENDOR_BELKIN 0x050d /* Belkin Components */ +#define USB_VENDOR_KAWATSU 0x050f /* Kawatsu Semiconductor */ +#define USB_VENDOR_FCI 0x0514 /* FCI */ +#define USB_VENDOR_LONGWELL 0x0516 /* Longwell */ +#define USB_VENDOR_COMPOSITE 0x0518 /* Composite */ +#define USB_VENDOR_STAR 0x0519 /* Star Micronics */ +#define USB_VENDOR_APC 0x051d /* American Power Conversion */ +#define USB_VENDOR_SCIATLANTA 0x051e /* Scientific Atlanta */ +#define USB_VENDOR_TSM 0x0520 /* TSM */ +#define USB_VENDOR_CONNECTEK 0x0522 /* Advanced Connectek USA */ +#define USB_VENDOR_NETCHIP 0x0525 /* NetChip Technology */ +#define USB_VENDOR_ALTRA 0x0527 /* ALTRA */ +#define USB_VENDOR_ATI 0x0528 /* ATI Technologies */ +#define USB_VENDOR_AKS 0x0529 /* Aladdin Knowledge Systems */ +#define USB_VENDOR_TEKOM 0x052b /* Tekom */ +#define USB_VENDOR_CANONDEV 0x052c /* Canon */ +#define USB_VENDOR_WACOMTECH 0x0531 /* Wacom */ +#define USB_VENDOR_INVENTEC 0x0537 /* Inventec */ +#define USB_VENDOR_SHYHSHIUN 0x0539 /* Shyh Shiun Terminals */ +#define USB_VENDOR_PREHWERKE 0x053a /* Preh Werke Gmbh & Co. KG */ +#define USB_VENDOR_SYNOPSYS 0x053f /* Synopsys */ +#define USB_VENDOR_UNIACCESS 0x0540 /* Universal Access */ +#define USB_VENDOR_VIEWSONIC 0x0543 /* ViewSonic */ +#define USB_VENDOR_XIRLINK 0x0545 /* Xirlink */ +#define USB_VENDOR_ANCHOR 0x0547 /* Anchor Chips */ +#define USB_VENDOR_SONY 0x054c /* Sony */ +#define USB_VENDOR_FUJIXEROX 0x0550 /* Fuji Xerox */ +#define USB_VENDOR_VISION 0x0553 /* VLSI Vision */ +#define USB_VENDOR_ASAHIKASEI 0x0556 /* Asahi Kasei Microsystems */ +#define USB_VENDOR_ATEN 0x0557 /* ATEN International */ +#define USB_VENDOR_SAMSUNG2 0x055d /* Samsung Electronics */ +#define USB_VENDOR_MUSTEK 0x055f /* Mustek Systems */ +#define USB_VENDOR_TELEX 0x0562 /* Telex Communications */ +#define USB_VENDOR_CHINON 0x0564 /* Chinon */ +#define USB_VENDOR_PERACOM 0x0565 /* Peracom Networks */ +#define USB_VENDOR_ALCOR2 0x0566 /* Alcor Micro */ +#define USB_VENDOR_XYRATEX 0x0567 /* Xyratex */ +#define USB_VENDOR_WACOM 0x056a /* WACOM */ +#define USB_VENDOR_ETEK 0x056c /* e-TEK Labs */ +#define USB_VENDOR_EIZO 0x056d /* EIZO */ +#define USB_VENDOR_ELECOM 0x056e /* Elecom */ +#define USB_VENDOR_CONEXANT 0x0572 /* Conexant */ +#define USB_VENDOR_HAUPPAUGE 0x0573 /* Hauppauge Computer Works */ +#define USB_VENDOR_BAFO 0x0576 /* BAFO/Quality Computer Accessories */ +#define USB_VENDOR_YEDATA 0x057b /* Y-E Data */ +#define USB_VENDOR_AVM 0x057c /* AVM */ +#define USB_VENDOR_QUICKSHOT 0x057f /* Quickshot */ +#define USB_VENDOR_ROLAND 0x0582 /* Roland */ +#define USB_VENDOR_ROCKFIRE 0x0583 /* Rockfire */ +#define USB_VENDOR_RATOC 0x0584 /* RATOC Systems */ +#define USB_VENDOR_ZYXEL 0x0586 /* ZyXEL Communication */ +#define USB_VENDOR_INFINEON 0x058b /* Infineon */ +#define USB_VENDOR_MICREL 0x058d /* Micrel */ +#define USB_VENDOR_ALCOR 0x058f /* Alcor Micro */ +#define USB_VENDOR_OMRON 0x0590 /* OMRON */ +#define USB_VENDOR_ZORAN 0x0595 /* Zoran Microelectronics */ +#define USB_VENDOR_NIIGATA 0x0598 /* Niigata */ +#define USB_VENDOR_IOMEGA 0x059b /* Iomega */ +#define USB_VENDOR_ATREND 0x059c /* A-Trend Technology */ +#define USB_VENDOR_AID 0x059d /* Advanced Input Devices */ +#define USB_VENDOR_LACIE 0x059f /* LaCie */ +#define USB_VENDOR_FUJIFILM 0x05a2 /* Fuji Film */ +#define USB_VENDOR_ARC 0x05a3 /* ARC */ +#define USB_VENDOR_ORTEK 0x05a4 /* Ortek */ +#define USB_VENDOR_BOSE 0x05a7 /* Bose */ +#define USB_VENDOR_OMNIVISION 0x05a9 /* OmniVision */ +#define USB_VENDOR_INSYSTEM 0x05ab /* In-System Design */ +#define USB_VENDOR_APPLE 0x05ac /* Apple Computer */ +#define USB_VENDOR_YCCABLE 0x05ad /* Y.C. Cable */ +#define USB_VENDOR_DIGITALPERSONA 0x05ba /* DigitalPersona */ +#define USB_VENDOR_3G 0x05bc /* 3G Green Green Globe */ +#define USB_VENDOR_RAFI 0x05bd /* RAFI */ +#define USB_VENDOR_TYCO 0x05be /* Tyco */ +#define USB_VENDOR_KAWASAKI 0x05c1 /* Kawasaki */ +#define USB_VENDOR_DIGI 0x05c5 /* Digi International */ +#define USB_VENDOR_QUALCOMM2 0x05c6 /* Qualcomm */ +#define USB_VENDOR_QTRONIX 0x05c7 /* Qtronix */ +#define USB_VENDOR_FOXLINK 0x05c8 /* Foxlink */ +#define USB_VENDOR_RICOH 0x05ca /* Ricoh */ +#define USB_VENDOR_ELSA 0x05cc /* ELSA */ +#define USB_VENDOR_SCIWORX 0x05ce /* sci-worx */ +#define USB_VENDOR_BRAINBOXES 0x05d1 /* Brainboxes Limited */ +#define USB_VENDOR_ULTIMA 0x05d8 /* Ultima */ +#define USB_VENDOR_AXIOHM 0x05d9 /* Axiohm Transaction Solutions */ +#define USB_VENDOR_MICROTEK 0x05da /* Microtek */ +#define USB_VENDOR_SUNTAC 0x05db /* SUN Corporation */ +#define USB_VENDOR_LEXAR 0x05dc /* Lexar Media */ +#define USB_VENDOR_ADDTRON 0x05dd /* Addtron */ +#define USB_VENDOR_SYMBOL 0x05e0 /* Symbol Technologies */ +#define USB_VENDOR_SYNTEK 0x05e1 /* Syntek */ +#define USB_VENDOR_GENESYS 0x05e3 /* Genesys Logic */ +#define USB_VENDOR_FUJI 0x05e5 /* Fuji Electric */ +#define USB_VENDOR_KEITHLEY 0x05e6 /* Keithley Instruments */ +#define USB_VENDOR_EIZONANAO 0x05e7 /* EIZO Nanao */ +#define USB_VENDOR_KLSI 0x05e9 /* Kawasaki LSI */ +#define USB_VENDOR_FFC 0x05eb /* FFC */ +#define USB_VENDOR_ANKO 0x05ef /* Anko Electronic */ +#define USB_VENDOR_PIENGINEERING 0x05f3 /* P.I. Engineering */ +#define USB_VENDOR_AOC 0x05f6 /* AOC International */ +#define USB_VENDOR_CHIC 0x05fe /* Chic Technology */ +#define USB_VENDOR_BARCO 0x0600 /* Barco Display Systems */ +#define USB_VENDOR_BRIDGE 0x0607 /* Bridge Information */ +#define USB_VENDOR_SOLIDYEAR 0x060b /* Solid Year */ +#define USB_VENDOR_BIORAD 0x0614 /* Bio-Rad Laboratories */ +#define USB_VENDOR_MACALLY 0x0618 /* Macally */ +#define USB_VENDOR_ACTLABS 0x061c /* Act Labs */ +#define USB_VENDOR_ALARIS 0x0620 /* Alaris */ +#define USB_VENDOR_APEX 0x0624 /* Apex */ +#define USB_VENDOR_CREATIVE3 0x062a /* Creative Labs */ +#define USB_VENDOR_VIVITAR 0x0636 /* Vivitar */ +#define USB_VENDOR_GUNZE 0x0637 /* Gunze Electronics USA */ +#define USB_VENDOR_AVISION 0x0638 /* Avision */ +#define USB_VENDOR_TEAC 0x0644 /* TEAC */ +#define USB_VENDOR_SGI 0x065e /* Silicon Graphics */ +#define USB_VENDOR_SANWASUPPLY 0x0663 /* Sanwa Supply */ +#define USB_VENDOR_LINKSYS 0x066b /* Linksys */ +#define USB_VENDOR_ACERSA 0x066e /* Acer Semiconductor America */ +#define USB_VENDOR_SIGMATEL 0x066f /* Sigmatel */ +#define USB_VENDOR_DRAYTEK 0x0675 /* DrayTek */ +#define USB_VENDOR_AIWA 0x0677 /* Aiwa */ +#define USB_VENDOR_ACARD 0x0678 /* ACARD Technology */ +#define USB_VENDOR_PROLIFIC 0x067b /* Prolific Technology */ +#define USB_VENDOR_SIEMENS 0x067c /* Siemens */ +#define USB_VENDOR_AVANCELOGIC 0x0680 /* Avance Logic */ +#define USB_VENDOR_SIEMENS2 0x0681 /* Siemens */ +#define USB_VENDOR_MINOLTA 0x0686 /* Minolta */ +#define USB_VENDOR_CHPRODUCTS 0x068e /* CH Products */ +#define USB_VENDOR_HAGIWARA 0x0693 /* Hagiwara Sys-Com */ +#define USB_VENDOR_CTX 0x0698 /* Chuntex */ +#define USB_VENDOR_ASKEY 0x069a /* Askey Computer */ +#define USB_VENDOR_SAITEK 0x06a3 /* Saitek */ +#define USB_VENDOR_ALCATELT 0x06b9 /* Alcatel Telecom */ +#define USB_VENDOR_AGFA 0x06bd /* AGFA-Gevaert */ +#define USB_VENDOR_ASIAMD 0x06be /* Asia Microelectronic Development */ +#define USB_VENDOR_BIZLINK 0x06c4 /* Bizlink International */ +#define USB_VENDOR_KEYSPAN 0x06cd /* Keyspan / InnoSys Inc. */ +#define USB_VENDOR_AASHIMA 0x06d6 /* Aashima Technology */ +#define USB_VENDOR_MULTITECH 0x06e0 /* MultiTech */ +#define USB_VENDOR_ADS 0x06e1 /* ADS Technologies */ +#define USB_VENDOR_ALCATELM 0x06e4 /* Alcatel Microelectronics */ +#define USB_VENDOR_SIRIUS 0x06ea /* Sirius Technologies */ +#define USB_VENDOR_GUILLEMOT 0x06f8 /* Guillemot */ +#define USB_VENDOR_BOSTON 0x06fd /* Boston Acoustics */ +#define USB_VENDOR_SMC 0x0707 /* Standard Microsystems */ +#define USB_VENDOR_PUTERCOM 0x0708 /* Putercom */ +#define USB_VENDOR_MCT 0x0711 /* MCT */ +#define USB_VENDOR_IMATION 0x0718 /* Imation */ +#define USB_VENDOR_SONYERICSSON 0x0731 /* Sony Ericsson */ +#define USB_VENDOR_EICON 0x0734 /* Eicon Networks */ +#define USB_VENDOR_SYNTECH 0x0745 /* Syntech Information */ +#define USB_VENDOR_DIGITALSTREAM 0x074e /* Digital Stream */ +#define USB_VENDOR_AUREAL 0x0755 /* Aureal Semiconductor */ +#define USB_VENDOR_MIDIMAN 0x0763 /* Midiman */ +#define USB_VENDOR_CYBERPOWER 0x0764 /* Cyber Power Systems, Inc. */ +#define USB_VENDOR_SURECOM 0x0769 /* Surecom Technology */ +#define USB_VENDOR_LINKSYS2 0x077b /* Linksys */ +#define USB_VENDOR_GRIFFIN 0x077d /* Griffin Technology */ +#define USB_VENDOR_SANDISK 0x0781 /* SanDisk */ +#define USB_VENDOR_JENOPTIK 0x0784 /* Jenoptik */ +#define USB_VENDOR_LOGITEC 0x0789 /* Logitec */ +#define USB_VENDOR_BRIMAX 0x078e /* Brimax */ +#define USB_VENDOR_AXIS 0x0792 /* Axis Communications */ +#define USB_VENDOR_ABL 0x0794 /* ABL Electronics */ +#define USB_VENDOR_SAGEM 0x079b /* Sagem */ +#define USB_VENDOR_SUNCOMM 0x079c /* Sun Communications, Inc. */ +#define USB_VENDOR_ALFADATA 0x079d /* Alfadata Computer */ +#define USB_VENDOR_NATIONALTECH 0x07a2 /* National Technical Systems */ +#define USB_VENDOR_ONNTO 0x07a3 /* Onnto */ +#define USB_VENDOR_BE 0x07a4 /* Be */ +#define USB_VENDOR_ADMTEK 0x07a6 /* ADMtek */ +#define USB_VENDOR_COREGA 0x07aa /* Corega */ +#define USB_VENDOR_FREECOM 0x07ab /* Freecom */ +#define USB_VENDOR_MICROTECH 0x07af /* Microtech */ +#define USB_VENDOR_GENERALINSTMNTS 0x07b2 /* General Instruments (Motorola) */ +#define USB_VENDOR_OLYMPUS 0x07b4 /* Olympus */ +#define USB_VENDOR_ABOCOM 0x07b8 /* AboCom Systems */ +#define USB_VENDOR_KEISOKUGIKEN 0x07c1 /* Keisokugiken */ +#define USB_VENDOR_ONSPEC 0x07c4 /* OnSpec */ +#define USB_VENDOR_APG 0x07c5 /* APG Cash Drawer */ +#define USB_VENDOR_BUG 0x07c8 /* B.U.G. */ +#define USB_VENDOR_ALLIEDTELESYN 0x07c9 /* Allied Telesyn International */ +#define USB_VENDOR_AVERMEDIA 0x07ca /* AVerMedia Technologies */ +#define USB_VENDOR_SIIG 0x07cc /* SIIG */ +#define USB_VENDOR_CASIO 0x07cf /* CASIO */ +#define USB_VENDOR_DLINK2 0x07d1 /* D-Link */ +#define USB_VENDOR_APTIO 0x07d2 /* Aptio Products */ +#define USB_VENDOR_ARASAN 0x07da /* Arasan Chip Systems */ +#define USB_VENDOR_ALLIEDCABLE 0x07e6 /* Allied Cable */ +#define USB_VENDOR_STSN 0x07ef /* STSN */ +#define USB_VENDOR_CENTURY 0x07f7 /* Century Corp */ +#define USB_VENDOR_ZOOM 0x0803 /* Zoom Telephonics */ +#define USB_VENDOR_PCS 0x0810 /* Personal Communication Systems */ +#define USB_VENDOR_BROADLOGIC 0x0827 /* BroadLogic */ +#define USB_VENDOR_HANDSPRING 0x082d /* Handspring */ +#define USB_VENDOR_PALM 0x0830 /* Palm Computing */ +#define USB_VENDOR_SOURCENEXT 0x0833 /* SOURCENEXT */ +#define USB_VENDOR_ACTIONSTAR 0x0835 /* Action Star Enterprise */ +#define USB_VENDOR_SAMSUNG_TECHWIN 0x0839 /* Samsung Techwin */ +#define USB_VENDOR_ACCTON 0x083a /* Accton Technology */ +#define USB_VENDOR_DIAMOND 0x0841 /* Diamond */ +#define USB_VENDOR_NETGEAR 0x0846 /* BayNETGEAR */ +#define USB_VENDOR_TOPRE 0x0853 /* Topre Corporation */ +#define USB_VENDOR_ACTIVEWIRE 0x0854 /* ActiveWire */ +#define USB_VENDOR_BBELECTRONICS 0x0856 /* B&B Electronics */ +#define USB_VENDOR_PORTGEAR 0x085a /* PortGear */ +#define USB_VENDOR_NETGEAR2 0x0864 /* Netgear */ +#define USB_VENDOR_SYSTEMTALKS 0x086e /* System Talks */ +#define USB_VENDOR_METRICOM 0x0870 /* Metricom */ +#define USB_VENDOR_ADESSOKBTEK 0x087c /* ADESSO/Kbtek America */ +#define USB_VENDOR_JATON 0x087d /* Jaton */ +#define USB_VENDOR_APT 0x0880 /* APT Technologies */ +#define USB_VENDOR_BOCARESEARCH 0x0885 /* Boca Research */ +#define USB_VENDOR_ANDREA 0x08a8 /* Andrea Electronics */ +#define USB_VENDOR_BURRBROWN 0x08bb /* Burr-Brown Japan */ +#define USB_VENDOR_2WIRE 0x08c8 /* 2Wire */ +#define USB_VENDOR_AIPTEK 0x08ca /* AIPTEK International */ +#define USB_VENDOR_SMARTBRIDGES 0x08d1 /* SmartBridges */ +#define USB_VENDOR_BILLIONTON 0x08dd /* Billionton Systems */ +#define USB_VENDOR_EXTENDED 0x08e9 /* Extended Systems */ +#define USB_VENDOR_MSYSTEMS 0x08ec /* M-Systems */ +#define USB_VENDOR_AUTHENTEC 0x08ff /* AuthenTec */ +#define USB_VENDOR_AUDIOTECHNICA 0x0909 /* Audio-Technica */ +#define USB_VENDOR_TRUMPION 0x090a /* Trumpion Microelectronics */ +#define USB_VENDOR_FEIYA 0x090c /* Feiya */ +#define USB_VENDOR_ALATION 0x0910 /* Alation Systems */ +#define USB_VENDOR_GLOBESPAN 0x0915 /* Globespan */ +#define USB_VENDOR_CONCORDCAMERA 0x0919 /* Concord Camera */ +#define USB_VENDOR_GARMIN 0x091e /* Garmin International */ +#define USB_VENDOR_GOHUBS 0x0921 /* GoHubs */ +#define USB_VENDOR_XEROX 0x0924 /* Xerox */ +#define USB_VENDOR_BIOMETRIC 0x0929 /* American Biometric Company */ +#define USB_VENDOR_TOSHIBA 0x0930 /* Toshiba */ +#define USB_VENDOR_PLEXTOR 0x093b /* Plextor */ +#define USB_VENDOR_INTREPIDCS 0x093c /* Intrepid */ +#define USB_VENDOR_YANO 0x094f /* Yano */ +#define USB_VENDOR_KINGSTON 0x0951 /* Kingston Technology */ +#define USB_VENDOR_BLUEWATER 0x0956 /* BlueWater Systems */ +#define USB_VENDOR_AGILENT 0x0957 /* Agilent Technologies */ +#define USB_VENDOR_GUDE 0x0959 /* Gude ADS */ +#define USB_VENDOR_PORTSMITH 0x095a /* Portsmith */ +#define USB_VENDOR_ACERW 0x0967 /* Acer */ +#define USB_VENDOR_ADIRONDACK 0x0976 /* Adirondack Wire & Cable */ +#define USB_VENDOR_BECKHOFF 0x0978 /* Beckhoff */ +#define USB_VENDOR_MINDSATWORK 0x097a /* Minds At Work */ +#define USB_VENDOR_POINTCHIPS 0x09a6 /* PointChips */ +#define USB_VENDOR_INTERSIL 0x09aa /* Intersil */ +#define USB_VENDOR_ALTIUS 0x09b3 /* Altius Solutions */ +#define USB_VENDOR_ARRIS 0x09c1 /* Arris Interactive */ +#define USB_VENDOR_ACTIVCARD 0x09c3 /* ACTIVCARD */ +#define USB_VENDOR_ACTISYS 0x09c4 /* ACTiSYS */ +#define USB_VENDOR_NOVATEL2 0x09d7 /* Novatel Wireless */ +#define USB_VENDOR_AFOURTECH 0x09da /* A-FOUR TECH */ +#define USB_VENDOR_AIMEX 0x09dc /* AIMEX */ +#define USB_VENDOR_ADDONICS 0x09df /* Addonics Technologies */ +#define USB_VENDOR_AKAI 0x09e8 /* AKAI professional M.I. */ +#define USB_VENDOR_ARESCOM 0x09f5 /* ARESCOM */ +#define USB_VENDOR_BAY 0x09f9 /* Bay Associates */ +#define USB_VENDOR_ALTERA 0x09fb /* Altera */ +#define USB_VENDOR_CSR 0x0a12 /* Cambridge Silicon Radio */ +#define USB_VENDOR_TREK 0x0a16 /* Trek Technology */ +#define USB_VENDOR_ASAHIOPTICAL 0x0a17 /* Asahi Optical */ +#define USB_VENDOR_BOCASYSTEMS 0x0a43 /* Boca Systems */ +#define USB_VENDOR_SHANTOU 0x0a46 /* ShanTou */ +#define USB_VENDOR_MEDIAGEAR 0x0a48 /* MediaGear */ +#define USB_VENDOR_BROADCOM 0x0a5c /* Broadcom */ +#define USB_VENDOR_GREENHOUSE 0x0a6b /* GREENHOUSE */ +#define USB_VENDOR_GEOCAST 0x0a79 /* Geocast Network Systems */ +#define USB_VENDOR_IDQUANTIQUE 0x0aba /* id Quantique */ +#define USB_VENDOR_ZYDAS 0x0ace /* Zydas Technology Corporation */ +#define USB_VENDOR_NEODIO 0x0aec /* Neodio */ +#define USB_VENDOR_OPTION 0x0af0 /* Option N.V: */ +#define USB_VENDOR_ASUS 0x0b05 /* ASUSTeK Computer */ +#define USB_VENDOR_TODOS 0x0b0c /* Todos Data System */ +#define USB_VENDOR_SIIG2 0x0b39 /* SIIG */ +#define USB_VENDOR_TEKRAM 0x0b3b /* Tekram Technology */ +#define USB_VENDOR_HAL 0x0b41 /* HAL Corporation */ +#define USB_VENDOR_EMS 0x0b43 /* EMS Production */ +#define USB_VENDOR_NEC2 0x0b62 /* NEC */ +#define USB_VENDOR_ATI2 0x0b6f /* ATI */ +#define USB_VENDOR_ZEEVO 0x0b7a /* Zeevo, Inc. */ +#define USB_VENDOR_KURUSUGAWA 0x0b7e /* Kurusugawa Electronics, Inc. */ +#define USB_VENDOR_ASIX 0x0b95 /* ASIX Electronics */ +#define USB_VENDOR_O2MICRO 0x0b97 /* O2 Micro, Inc. */ +#define USB_VENDOR_USR 0x0baf /* U.S. Robotics */ +#define USB_VENDOR_AMBIT 0x0bb2 /* Ambit Microsystems */ +#define USB_VENDOR_HTC 0x0bb4 /* HTC */ +#define USB_VENDOR_REALTEK 0x0bda /* Realtek */ +#define USB_VENDOR_ADDONICS2 0x0bf6 /* Addonics Technology */ +#define USB_VENDOR_FSC 0x0bf8 /* Fujitsu Siemens Computers */ +#define USB_VENDOR_AGATE 0x0c08 /* Agate Technologies */ +#define USB_VENDOR_DMI 0x0c0b /* DMI */ +#define USB_VENDOR_MICRODIA 0x0c45 /* Chicony */ +#define USB_VENDOR_SEALEVEL 0x0c52 /* Sealevel System */ +#define USB_VENDOR_LUWEN 0x0c76 /* Luwen */ +#define USB_VENDOR_KYOCERA2 0x0c88 /* Kyocera Wireless Corp. */ +#define USB_VENDOR_ZCOM 0x0cde /* Z-Com */ +#define USB_VENDOR_ATHEROS2 0x0cf3 /* Atheros Communications */ +#define USB_VENDOR_TANGTOP 0x0d3d /* Tangtop */ +#define USB_VENDOR_SMC3 0x0d5c /* Standard Microsystems */ +#define USB_VENDOR_ADDON 0x0d7d /* Add-on Technology */ +#define USB_VENDOR_ACDC 0x0d7e /* American Computer & Digital Components */ +#define USB_VENDOR_ABC 0x0d8c /* ABC */ +#define USB_VENDOR_CONCEPTRONIC 0x0d8e /* Conceptronic */ +#define USB_VENDOR_SKANHEX 0x0d96 /* Skanhex Technology, Inc. */ +#define USB_VENDOR_MSI 0x0db0 /* Micro Star International */ +#define USB_VENDOR_ELCON 0x0db7 /* ELCON Systemtechnik */ +#define USB_VENDOR_NETAC 0x0dd8 /* Netac */ +#define USB_VENDOR_SITECOMEU 0x0df6 /* Sitecom Europe */ +#define USB_VENDOR_MOBILEACTION 0x0df7 /* Mobile Action */ +#define USB_VENDOR_SPEEDDRAGON 0x0e55 /* Speed Dragon Multimedia */ +#define USB_VENDOR_HAWKING 0x0e66 /* Hawking */ +#define USB_VENDOR_FOSSIL 0x0e67 /* Fossil, Inc */ +#define USB_VENDOR_GMATE 0x0e7e /* G.Mate, Inc */ +#define USB_VENDOR_OTI 0x0ea0 /* Ours Technology */ +#define USB_VENDOR_PILOTECH 0x0eaf /* Pilotech */ +#define USB_VENDOR_NOVATECH 0x0eb0 /* NovaTech */ +#define USB_VENDOR_ITEGNO 0x0eba /* iTegno */ +#define USB_VENDOR_WINMAXGROUP 0x0ed1 /* WinMaxGroup */ +#define USB_VENDOR_TOD 0x0ede /* TOD */ +#define USB_VENDOR_EGALAX 0x0eef /* eGalax, Inc. */ +#define USB_VENDOR_AIRPRIME 0x0f3d /* AirPrime, Inc. */ +#define USB_VENDOR_MICROTUNE 0x0f4d /* Microtune */ +#define USB_VENDOR_VTECH 0x0f88 /* VTech */ +#define USB_VENDOR_FALCOM 0x0f94 /* Falcom Wireless Communications GmbH */ +#define USB_VENDOR_RIM 0x0fca /* Research In Motion */ +#define USB_VENDOR_DYNASTREAM 0x0fcf /* Dynastream Innovations */ +#define USB_VENDOR_QUALCOMM 0x1004 /* Qualcomm */ +#define USB_VENDOR_DESKNOTE 0x1019 /* Desknote */ +#define USB_VENDOR_GIGABYTE 0x1044 /* GIGABYTE */ +#define USB_VENDOR_WESTERN 0x1058 /* Western Digital */ +#define USB_VENDOR_MOTOROLA 0x1063 /* Motorola */ +#define USB_VENDOR_CCYU 0x1065 /* CCYU Technology */ +#define USB_VENDOR_CURITEL 0x106c /* Curitel Communications Inc */ +#define USB_VENDOR_SILABS2 0x10a6 /* SILABS2 */ +#define USB_VENDOR_USI 0x10ab /* USI */ +#define USB_VENDOR_PLX 0x10b5 /* PLX */ +#define USB_VENDOR_ASANTE 0x10bd /* Asante */ +#define USB_VENDOR_SILABS 0x10c4 /* Silicon Labs */ +#define USB_VENDOR_ANALOG 0x1110 /* Analog Devices */ +#define USB_VENDOR_TENX 0x1130 /* Ten X Technology, Inc. */ +#define USB_VENDOR_ISSC 0x1131 /* Integrated System Solution Corp. */ +#define USB_VENDOR_JRC 0x1145 /* Japan Radio Company */ +#define USB_VENDOR_SPHAIRON 0x114b /* Sphairon Access Systems GmbH */ +#define USB_VENDOR_DELORME 0x1163 /* DeLorme */ +#define USB_VENDOR_SERVERWORKS 0x1166 /* ServerWorks */ +#define USB_VENDOR_ACERCM 0x1189 /* Acer Communications & Multimedia */ +#define USB_VENDOR_SIERRA 0x1199 /* Sierra Wireless */ +#define USB_VENDOR_TOPFIELD 0x11db /* Topfield Co., Ltd */ +#define USB_VENDOR_SIEMENS3 0x11f5 /* Siemens */ +#define USB_VENDOR_PROLIFIC2 0x11f6 /* Prolific */ +#define USB_VENDOR_ALCATEL 0x11f7 /* Alcatel */ +#define USB_VENDOR_UNKNOWN3 0x1233 /* Unknown vendor */ +#define USB_VENDOR_TSUNAMI 0x1241 /* Tsunami */ +#define USB_VENDOR_PHEENET 0x124a /* Pheenet */ +#define USB_VENDOR_TARGUS 0x1267 /* Targus */ +#define USB_VENDOR_TWINMOS 0x126f /* TwinMOS */ +#define USB_VENDOR_TENDA 0x1286 /* Tenda */ +#define USB_VENDOR_CREATIVE2 0x1292 /* Creative Labs */ +#define USB_VENDOR_BELKIN2 0x1293 /* Belkin Components */ +#define USB_VENDOR_CYBERTAN 0x129b /* CyberTAN Technology */ +#define USB_VENDOR_HUAWEI 0x12d1 /* Huawei Technologies */ +#define USB_VENDOR_ARANEUS 0x12d8 /* Araneus Information Systems */ +#define USB_VENDOR_TAPWAVE 0x12ef /* Tapwave */ +#define USB_VENDOR_AINCOMM 0x12fd /* Aincomm */ +#define USB_VENDOR_MOBILITY 0x1342 /* Mobility */ +#define USB_VENDOR_DICKSMITH 0x1371 /* Dick Smith Electronics */ +#define USB_VENDOR_NETGEAR3 0x1385 /* Netgear */ +#define USB_VENDOR_BALTECH 0x13ad /* Baltech */ +#define USB_VENDOR_CISCOLINKSYS 0x13b1 /* Cisco-Linksys */ +#define USB_VENDOR_SHARK 0x13d2 /* Shark */ +#define USB_VENDOR_NOVATEL 0x1410 /* Novatel Wireless */ +#define USB_VENDOR_MERLIN 0x1416 /* Merlin */ +#define USB_VENDOR_WISTRONNEWEB 0x1435 /* Wistron NeWeb */ +#define USB_VENDOR_RADIOSHACK 0x1453 /* Radio Shack */ +#define USB_VENDOR_HUAWEI3COM 0x1472 /* Huawei-3Com */ +#define USB_VENDOR_SILICOM 0x1485 /* Silicom */ +#define USB_VENDOR_RALINK 0x148f /* Ralink Technology */ +#define USB_VENDOR_IMAGINATION 0x149a /* Imagination Technologies */ +#define USB_VENDOR_CONCEPTRONIC2 0x14b2 /* Conceptronic */ +#define USB_VENDOR_PLANEX3 0x14ea /* Planex Communications */ +#define USB_VENDOR_SILICONPORTALS 0x1527 /* Silicon Portals */ +#define USB_VENDOR_UBIQUAM 0x1529 /* UBIQUAM Co., Ltd. */ +#define USB_VENDOR_UBLOX 0x1546 /* U-blox */ +#define USB_VENDOR_PNY 0x154b /* PNY */ +#define USB_VENDOR_OQO 0x1557 /* OQO */ +#define USB_VENDOR_UMEDIA 0x157e /* U-MEDIA Communications */ +#define USB_VENDOR_FIBERLINE 0x1582 /* Fiberline */ +#define USB_VENDOR_SPARKLAN 0x15a9 /* SparkLAN */ +#define USB_VENDOR_SOHOWARE 0x15e8 /* SOHOware */ +#define USB_VENDOR_UMAX 0x1606 /* UMAX Data Systems */ +#define USB_VENDOR_INSIDEOUT 0x1608 /* Inside Out Networks */ +#define USB_VENDOR_GOODWAY 0x1631 /* Good Way Technology */ +#define USB_VENDOR_ENTREGA 0x1645 /* Entrega */ +#define USB_VENDOR_ACTIONTEC 0x1668 /* Actiontec Electronics */ +#define USB_VENDOR_ATHEROS 0x168c /* Atheros Communications */ +#define USB_VENDOR_GIGASET 0x1690 /* Gigaset */ +#define USB_VENDOR_GLOBALSUN 0x16ab /* Global Sun Technology */ +#define USB_VENDOR_ANYDATA 0x16d5 /* AnyDATA Corporation */ +#define USB_VENDOR_JABLOTRON 0x16d6 /* Jablotron */ +#define USB_VENDOR_CMOTECH 0x16d8 /* CMOTECH Co., Ltd. */ +#define USB_VENDOR_AXESSTEL 0x1726 /* Axesstel Co., Ltd. */ +#define USB_VENDOR_LINKSYS4 0x1737 /* Linksys */ +#define USB_VENDOR_SENAO 0x1740 /* Senao */ +#define USB_VENDOR_METAGEEK 0x1781 /* MetaGeek */ +#define USB_VENDOR_AMIT 0x18c5 /* AMIT */ +#define USB_VENDOR_QCOM 0x18e8 /* Qcom */ +#define USB_VENDOR_LINKSYS3 0x1915 /* Linksys */ +#define USB_VENDOR_QUALCOMMINC 0x19d2 /* Qualcomm, Incorporated */ +#define USB_VENDOR_DLINK 0x2001 /* D-Link */ +#define USB_VENDOR_PLANEX2 0x2019 /* Planex Communications */ +#define USB_VENDOR_ERICSSON 0x2282 /* Ericsson */ +#define USB_VENDOR_MOTOROLA2 0x22b8 /* Motorola */ +#define USB_VENDOR_TRIPPLITE 0x2478 /* Tripp-Lite */ +#define USB_VENDOR_HIROSE 0x2631 /* Hirose Electric */ +#define USB_VENDOR_NHJ 0x2770 /* NHJ */ +#define USB_VENDOR_PLANEX 0x2c02 /* Planex Communications */ +#define USB_VENDOR_VIDZMEDIA 0x3275 /* VidzMedia Pte Ltd */ +#define USB_VENDOR_AEI 0x3334 /* AEI */ +#define USB_VENDOR_HANK 0x3353 /* Hank Connection */ +#define USB_VENDOR_PQI 0x3538 /* PQI */ +#define USB_VENDOR_DAISY 0x3579 /* Daisy Technology */ +#define USB_VENDOR_NI 0x3923 /* National Instruments */ +#define USB_VENDOR_MICRONET 0x3980 /* Micronet Communications */ +#define USB_VENDOR_IODATA2 0x40bb /* I-O Data */ +#define USB_VENDOR_IRIVER 0x4102 /* iRiver */ +#define USB_VENDOR_DELL 0x413c /* Dell */ +#define USB_VENDOR_WCH 0x4348 /* QinHeng Electronics */ +#define USB_VENDOR_ACEECA 0x4766 /* Aceeca */ +#define USB_VENDOR_AVERATEC 0x50c2 /* Averatec */ +#define USB_VENDOR_SWEEX 0x5173 /* Sweex */ +#define USB_VENDOR_ONSPEC2 0x55aa /* OnSpec Electronic Inc. */ +#define USB_VENDOR_ZINWELL 0x5a57 /* Zinwell */ +#define USB_VENDOR_SITECOM 0x6189 /* Sitecom */ +#define USB_VENDOR_ARKMICRO 0x6547 /* Arkmicro Technologies Inc. */ +#define USB_VENDOR_3COM2 0x6891 /* 3Com */ +#define USB_VENDOR_INTEL 0x8086 /* Intel */ +#define USB_VENDOR_SITECOM2 0x9016 /* Sitecom */ +#define USB_VENDOR_MOSCHIP 0x9710 /* MosChip Semiconductor */ +#define USB_VENDOR_3COM3 0xa727 /* 3Com */ +#define USB_VENDOR_HP2 0xf003 /* Hewlett Packard */ +#define USB_VENDOR_USRP 0xfffe /* GNU Radio USRP */ + +/* + * List of known products. Grouped by vendor. + */ + +/* 3Com products */ +#define USB_PRODUCT_3COM_HOMECONN 0x009d /* HomeConnect Camera */ +#define USB_PRODUCT_3COM_3CREB96 0x00a0 /* Bluetooth USB Adapter */ +#define USB_PRODUCT_3COM_3C19250 0x03e8 /* 3C19250 Ethernet Adapter */ +#define USB_PRODUCT_3COM_3CRSHEW696 0x0a01 /* 3CRSHEW696 Wireless Adapter */ +#define USB_PRODUCT_3COM_3C460 0x11f8 /* HomeConnect 3C460 */ +#define USB_PRODUCT_3COM_USR56K 0x3021 /* U.S.Robotics 56000 Voice FaxModem Pro */ +#define USB_PRODUCT_3COM_3C460B 0x4601 /* HomeConnect 3C460B */ +#define USB_PRODUCT_3COM2_3CRUSB10075 0xa727 /* 3CRUSB10075 */ +#define USB_PRODUCT_3COM3_AR5523_1 0x6893 /* AR5523 */ +#define USB_PRODUCT_3COM3_AR5523_2 0x6895 /* AR5523 */ +#define USB_PRODUCT_3COM3_AR5523_3 0x6897 /* AR5523 */ + +#define USB_PRODUCT_3COMUSR_OFFICECONN 0x0082 /* 3Com OfficeConnect Analog Modem */ +#define USB_PRODUCT_3COMUSR_USRISDN 0x008f /* 3Com U.S. Robotics Pro ISDN TA */ +#define USB_PRODUCT_3COMUSR_HOMECONN 0x009d /* 3Com HomeConnect Camera */ +#define USB_PRODUCT_3COMUSR_USR56K 0x3021 /* U.S. Robotics 56000 Voice FaxModem Pro */ + +/* AboCom products */ +#define USB_PRODUCT_ABOCOM_XX1 0x110c /* XX1 */ +#define USB_PRODUCT_ABOCOM_XX2 0x200c /* XX2 */ +#define USB_PRODUCT_ABOCOM_URE450 0x4000 /* URE450 Ethernet Adapter */ +#define USB_PRODUCT_ABOCOM_UFE1000 0x4002 /* UFE1000 Fast Ethernet Adapter */ +#define USB_PRODUCT_ABOCOM_DSB650TX_PNA 0x4003 /* 1/10/100 Ethernet Adapter */ +#define USB_PRODUCT_ABOCOM_XX4 0x4004 /* XX4 */ +#define USB_PRODUCT_ABOCOM_XX5 0x4007 /* XX5 */ +#define USB_PRODUCT_ABOCOM_XX6 0x400b /* XX6 */ +#define USB_PRODUCT_ABOCOM_XX7 0x400c /* XX7 */ +#define USB_PRODUCT_ABOCOM_RTL8151 0x401a /* RTL8151 */ +#define USB_PRODUCT_ABOCOM_XX8 0x4102 /* XX8 */ +#define USB_PRODUCT_ABOCOM_XX9 0x4104 /* XX9 */ +#define USB_PRODUCT_ABOCOM_UF200 0x420a /* UF200 Ethernet */ +#define USB_PRODUCT_ABOCOM_WL54 0x6001 /* WL54 */ +#define USB_PRODUCT_ABOCOM_XX10 0xabc1 /* XX10 */ +#define USB_PRODUCT_ABOCOM_BWU613 0xb000 /* BWU613 */ +#define USB_PRODUCT_ABOCOM_HWU54DM 0xb21b /* HWU54DM */ +#define USB_PRODUCT_ABOCOM_RT2573_2 0xb21c /* RT2573 */ +#define USB_PRODUCT_ABOCOM_RT2573_3 0xb21d /* RT2573 */ +#define USB_PRODUCT_ABOCOM_RT2573_4 0xb21e /* RT2573 */ +#define USB_PRODUCT_ABOCOM_WUG2700 0xb21f /* WUG2700 */ + +/* Accton products */ +#define USB_PRODUCT_ACCTON_USB320_EC 0x1046 /* USB320-EC Ethernet Adapter */ +#define USB_PRODUCT_ACCTON_2664W 0x3501 /* 2664W */ +#define USB_PRODUCT_ACCTON_111 0x3503 /* T-Sinus 111 Wireless Adapter */ +#define USB_PRODUCT_ACCTON_SMCWUSBG 0x4505 /* SMCWUSB-G */ +#define USB_PRODUCT_ACCTON_PRISM_GT 0x4521 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_ACCTON_SS1001 0x5046 /* SpeedStream Ethernet Adapter */ +#define USB_PRODUCT_ACCTON_ZD1211B 0xe501 /* ZD1211B */ + +/* Aceeca products */ +#define USB_PRODUCT_ACEECA_MEZ1000 0x0001 /* MEZ1000 RDA */ + +/* Acer Communications & Multimedia (oemd by Surecom) */ +#define USB_PRODUCT_ACERCM_EP1427X2 0x0893 /* EP-1427X-2 Ethernet Adapter */ + +/* Acer Labs products */ +#define USB_PRODUCT_ACERLABS_M5632 0x5632 /* USB 2.0 Data Link */ + +/* Acer Peripherals, Inc. products */ +#define USB_PRODUCT_ACERP_ACERSCAN_C310U 0x12a6 /* Acerscan C310U */ +#define USB_PRODUCT_ACERP_ACERSCAN_320U 0x2022 /* Acerscan 320U */ +#define USB_PRODUCT_ACERP_ACERSCAN_640U 0x2040 /* Acerscan 640U */ +#define USB_PRODUCT_ACERP_ACERSCAN_620U 0x2060 /* Acerscan 620U */ +#define USB_PRODUCT_ACERP_ACERSCAN_4300U 0x20b0 /* Benq 3300U/4300U */ +#define USB_PRODUCT_ACERP_ACERSCAN_640BT 0x20be /* Acerscan 640BT */ +#define USB_PRODUCT_ACERP_ACERSCAN_1240U 0x20c0 /* Acerscan 1240U */ +#define USB_PRODUCT_ACERP_ATAPI 0x6003 /* ATA/ATAPI Adapter */ +#define USB_PRODUCT_ACERP_AWL300 0x9000 /* AWL300 Wireless Adapter */ +#define USB_PRODUCT_ACERP_AWL400 0x9001 /* AWL400 Wireless Adapter */ + +/* Acer Warp products */ +#define USB_PRODUCT_ACERW_WARPLINK 0x0204 /* Warplink */ + +/* Actiontec, Inc. products */ +#define USB_PRODUCT_ACTIONTEC_PRISM_25 0x0408 /* Prism2.5 Wireless Adapter */ +#define USB_PRODUCT_ACTIONTEC_PRISM_25A 0x0421 /* Prism2.5 Wireless Adapter A */ +#define USB_PRODUCT_ACTIONTEC_FREELAN 0x6106 /* ROPEX FreeLan 802.11b */ +#define USB_PRODUCT_ACTIONTEC_UAT1 0x7605 /* UAT1 Wireless Ethernet Adapter */ + +/* ACTiSYS products */ +#define USB_PRODUCT_ACTISYS_IR2000U 0x0011 /* ACT-IR2000U FIR */ + +/* ActiveWire, Inc. products */ +#define USB_PRODUCT_ACTIVEWIRE_IOBOARD 0x0100 /* I/O Board */ +#define USB_PRODUCT_ACTIVEWIRE_IOBOARD_FW1 0x0101 /* I/O Board, rev. 1 firmware */ + +/* Adaptec products */ +#define USB_PRODUCT_ADAPTEC_AWN8020 0x0020 /* AWN-8020 WLAN */ + +/* Addtron products */ +#define USB_PRODUCT_ADDTRON_AWU120 0xff31 /* AWU-120 */ + +/* ADMtek products */ +#define USB_PRODUCT_ADMTEK_PEGASUSII_4 0x07c2 /* AN986A Ethernet */ +#define USB_PRODUCT_ADMTEK_PEGASUS 0x0986 /* AN986 Ethernet */ +#define USB_PRODUCT_ADMTEK_PEGASUSII 0x8511 /* AN8511 Ethernet */ +#define USB_PRODUCT_ADMTEK_PEGASUSII_2 0x8513 /* AN8513 Ethernet */ +#define USB_PRODUCT_ADMTEK_PEGASUSII_3 0x8515 /* AN8515 Ethernet */ + +/* ADDON products */ +/* PNY OEMs these */ +#define USB_PRODUCT_ADDON_ATTACHE 0x1300 /* USB 2.0 Flash Drive */ +#define USB_PRODUCT_ADDON_ATTACHE 0x1300 /* USB 2.0 Flash Drive */ +#define USB_PRODUCT_ADDON_A256MB 0x1400 /* Attache 256MB USB 2.0 Flash Drive */ +#define USB_PRODUCT_ADDON_DISKPRO512 0x1420 /* USB 2.0 Flash Drive (DANE-ELEC zMate 512MB USB flash drive) */ + +/* Addonics products */ +#define USB_PRODUCT_ADDONICS2_CABLE_205 0xa001 /* Cable 205 */ + +/* ADS products */ +#define USB_PRODUCT_ADS_UBS10BT 0x0008 /* UBS-10BT Ethernet */ +#define USB_PRODUCT_ADS_UBS10BTX 0x0009 /* UBS-10BT Ethernet */ + +/* AEI products */ +#define USB_PRODUCT_AEI_FASTETHERNET 0x1701 /* Fast Ethernet */ + +/* Agate Technologies products */ +#define USB_PRODUCT_AGATE_QDRIVE 0x0378 /* Q-Drive */ + +/* AGFA products */ +#define USB_PRODUCT_AGFA_SNAPSCAN1212U 0x0001 /* SnapScan 1212U */ +#define USB_PRODUCT_AGFA_SNAPSCAN1236U 0x0002 /* SnapScan 1236U */ +#define USB_PRODUCT_AGFA_SNAPSCANTOUCH 0x0100 /* SnapScan Touch */ +#define USB_PRODUCT_AGFA_SNAPSCAN1212U2 0x2061 /* SnapScan 1212U */ +#define USB_PRODUCT_AGFA_SNAPSCANE40 0x208d /* SnapScan e40 */ +#define USB_PRODUCT_AGFA_SNAPSCANE50 0x208f /* SnapScan e50 */ +#define USB_PRODUCT_AGFA_SNAPSCANE20 0x2091 /* SnapScan e20 */ +#define USB_PRODUCT_AGFA_SNAPSCANE25 0x2095 /* SnapScan e25 */ +#define USB_PRODUCT_AGFA_SNAPSCANE26 0x2097 /* SnapScan e26 */ +#define USB_PRODUCT_AGFA_SNAPSCANE52 0x20fd /* SnapScan e52 */ + +/* Ain Communication Technology products */ +#define USB_PRODUCT_AINCOMM_AWU2000B 0x1001 /* AWU2000B Wireless Adapter */ + +/* AIPTEK products */ +#define USB_PRODUCT_AIPTEK_POCKETCAM3M 0x2011 /* PocketCAM 3Mega */ +#define USB_PRODUCT_SUNPLUS_PENCAM_MEGA_1_3 0x504a /* PenCam Mega 1.3 */ + +/* AirPrime products */ +#define USB_PRODUCT_AIRPRIME_PC5220 0x0112 /* CDMA Wireless PC Card */ + +/* AKS products */ +#define USB_PRODUCT_AKS_USBHASP 0x0001 /* USB-HASP 0.06 */ + +/* Alcor Micro, Inc. products */ +#define USB_PRODUCT_ALCOR2_KBD_HUB 0x2802 /* Kbd Hub */ + +#define USB_PRODUCT_ALCOR_MA_KBD_HUB 0x9213 /* MacAlly Kbd Hub */ +#define USB_PRODUCT_ALCOR_AU9814 0x9215 /* AU9814 Hub */ +#define USB_PRODUCT_ALCOR_UMCR_9361 0x9361 /* USB Multimedia Card Reader */ +#define USB_PRODUCT_ALCOR_SM_KBD 0x9410 /* MicroConnectors/StrongMan Keyboard */ +#define USB_PRODUCT_ALCOR_NEC_KBD_HUB 0x9472 /* NEC Kbd Hub */ + +/* Altec Lansing products */ +#define USB_PRODUCT_ALTEC_ADA70 0x0070 /* ADA70 Speakers */ +#define USB_PRODUCT_ALTEC_ASC495 0xff05 /* ASC495 Speakers */ + +/* Allied Telesyn International products */ +#define USB_PRODUCT_ALLIEDTELESYN_ATUSB100 0xb100 /* AT-USB100 */ + +/* American Power Conversion products */ +#define USB_PRODUCT_APC_UPS 0x0002 /* Uninterruptible Power Supply */ + +/* Ambit Microsystems products */ +#define USB_PRODUCT_AMBIT_WLAN 0x0302 /* WLAN */ +#define USB_PRODUCT_AMBIT_NTL_250 0x6098 /* NTL 250 cable modem */ + +/* AMIT products */ +#define USB_PRODUCT_AMIT_CGWLUSB2GO 0x0002 /* CG-WLUSB2GO */ + +/* Anchor products */ +#define USB_PRODUCT_ANCHOR_EZUSB 0x2131 /* EZUSB */ +#define USB_PRODUCT_ANCHOR_EZLINK 0x2720 /* EZLINK */ + +/* AnyData products */ +#define USB_PRODUCT_ANYDATA_ADU_E100X 0x6501 /* CDMA 2000 1xRTT/EV-DO USB Modem */ +#define USB_PRODUCT_ANYDATA_ADU_500A 0x6502 /* CDMA 2000 EV-DO USB Modem */ + +/* AOX, Inc. products */ +#define USB_PRODUCT_AOX_USB101 0x0008 /* Ethernet */ + +/* American Power Conversion products */ +#define USB_PRODUCT_APC_UPS 0x0002 /* Uninterruptible Power Supply */ + +/* Apple Computer products */ +#define USB_PRODUCT_APPLE_EXT_KBD 0x020c /* Apple Extended USB Keyboard */ +#define USB_PRODUCT_APPLE_OPTMOUSE 0x0302 /* Optical mouse */ +#define USB_PRODUCT_APPLE_MIGHTYMOUSE 0x0304 /* Mighty Mouse */ +#define USB_PRODUCT_APPLE_EXT_KBD_HUB 0x1003 /* Hub in Apple Extended USB Keyboard */ +#define USB_PRODUCT_APPLE_SPEAKERS 0x1101 /* Speakers */ +#define USB_PRODUCT_APPLE_IPOD 0x1201 /* iPod */ +#define USB_PRODUCT_APPLE_IPOD2G 0x1202 /* iPod 2G */ +#define USB_PRODUCT_APPLE_IPOD3G 0x1203 /* iPod 3G */ +#define USB_PRODUCT_APPLE_IPOD_04 0x1204 /* iPod '04' */ +#define USB_PRODUCT_APPLE_IPODMINI 0x1205 /* iPod Mini */ +#define USB_PRODUCT_APPLE_IPOD_06 0x1206 /* iPod '06' */ +#define USB_PRODUCT_APPLE_IPOD_07 0x1207 /* iPod '07' */ +#define USB_PRODUCT_APPLE_IPOD_08 0x1208 /* iPod '08' */ +#define USB_PRODUCT_APPLE_IPODVIDEO 0x1209 /* iPod Video */ +#define USB_PRODUCT_APPLE_IPODNANO 0x120a /* iPod Nano */ +#define USB_PRODUCT_APPLE_IPHONE 0x1290 /* iPhone */ +#define USB_PRODUCT_APPLE_IPHONE_3G 0x1292 /* iPhone 3G */ +#define USB_PRODUCT_APPLE_ETHERNET 0x1402 /* Ethernet A1277 */ + +/* Arkmicro Technologies */ +#define USB_PRODUCT_ARKMICRO_ARK3116 0x0232 /* ARK3116 Serial */ + +/* Asahi Optical products */ +#define USB_PRODUCT_ASAHIOPTICAL_OPTIO230 0x0004 /* Digital camera */ +#define USB_PRODUCT_ASAHIOPTICAL_OPTIO330 0x0006 /* Digital camera */ + +/* Asante products */ +#define USB_PRODUCT_ASANTE_EA 0x1427 /* Ethernet */ + +/* ASIX Electronics products */ +#define USB_PRODUCT_ASIX_AX88172 0x1720 /* 10/100 Ethernet */ +#define USB_PRODUCT_ASIX_AX88178 0x1780 /* AX88178 */ +#define USB_PRODUCT_ASIX_AX88772 0x7720 /* AX88772 */ + +/* ASUS products */ +#define USB_PRODUCT_ASUS_WL167G 0x1707 /* WL-167g Wireless Adapter */ +#define USB_PRODUCT_ASUS_WL159G 0x170c /* WL-159g */ +#define USB_PRODUCT_ASUS_A9T_WIFI 0x171b /* A9T wireless */ +#define USB_PRODUCT_ASUS_RT2573_1 0x1723 /* RT2573 */ +#define USB_PRODUCT_ASUS_RT2573_2 0x1724 /* RT2573 */ +#define USB_PRODUCT_ASUS_LCM 0x1726 /* LCM display */ +#define USB_PRODUCT_ASUS_P535 0x420f /* ASUS P535 PDA */ + +/* ATen products */ +#define USB_PRODUCT_ATEN_UC1284 0x2001 /* Parallel printer */ +#define USB_PRODUCT_ATEN_UC10T 0x2002 /* 10Mbps Ethernet */ +#define USB_PRODUCT_ATEN_UC110T 0x2007 /* UC-110T Ethernet */ +#define USB_PRODUCT_ATEN_UC232A 0x2008 /* Serial */ +#define USB_PRODUCT_ATEN_UC210T 0x2009 /* UC-210T Ethernet */ +#define USB_PRODUCT_ATEN_DSB650C 0x4000 /* DSB-650C */ + +/* Atheros Communications products */ +#define USB_PRODUCT_ATHEROS_AR5523 0x0001 /* AR5523 */ +#define USB_PRODUCT_ATHEROS_AR5523_NF 0x0002 /* AR5523 (no firmware) */ +#define USB_PRODUCT_ATHEROS2_AR5523_1 0x0001 /* AR5523 */ +#define USB_PRODUCT_ATHEROS2_AR5523_1_NF 0x0002 /* AR5523 (no firmware) */ +#define USB_PRODUCT_ATHEROS2_AR5523_2 0x0003 /* AR5523 */ +#define USB_PRODUCT_ATHEROS2_AR5523_2_NF 0x0004 /* AR5523 (no firmware) */ +#define USB_PRODUCT_ATHEROS2_AR5523_3 0x0005 /* AR5523 */ +#define USB_PRODUCT_ATHEROS2_AR5523_3_NF 0x0006 /* AR5523 (no firmware) */ + +/* Atmel Comp. products */ +#define USB_PRODUCT_ATMEL_UHB124 0x3301 /* UHB124 hub */ +#define USB_PRODUCT_ATMEL_DWL120 0x7603 /* DWL-120 Wireless Adapter */ +#define USB_PRODUCT_ATMEL_BW002 0x7605 /* BW002 Wireless Adapter */ +#define USB_PRODUCT_ATMEL_WL1130USB 0x7613 /* WL-1130 USB */ +#define USB_PRODUCT_ATMEL_AT76C505A 0x7614 /* AT76c505a Wireless Adapter */ + +/* Avision products */ +#define USB_PRODUCT_AVISION_1200U 0x0268 /* 1200U scanner */ + +/* Axesstel products */ +#define USB_PRODUCT_AXESSTEL_DATAMODEM 0x1000 /* Data Modem */ + +/* Baltech products */ +#define USB_PRODUCT_BALTECH_CARDREADER 0x9999 /* Card reader */ + +/* B&B Electronics products */ +#define USB_PRODUCT_BBELECTRONICS_USOTL4 0xAC01 /* RS-422/485 */ + +/* Belkin products */ +/*product BELKIN F5U111 0x???? F5U111 Ethernet*/ +#define USB_PRODUCT_BELKIN_F5D6050 0x0050 /* F5D6050 802.11b Wireless Adapter */ +#define USB_PRODUCT_BELKIN_FBT001V 0x0081 /* FBT001v2 Bluetooth */ +#define USB_PRODUCT_BELKIN_FBT003V 0x0084 /* FBT003v2 Bluetooth */ +#define USB_PRODUCT_BELKIN_F5U103 0x0103 /* F5U103 Serial */ +#define USB_PRODUCT_BELKIN_F5U109 0x0109 /* F5U109 Serial */ +#define USB_PRODUCT_BELKIN_USB2SCSI 0x0115 /* USB to SCSI */ +#define USB_PRODUCT_BELKIN_USB2LAN 0x0121 /* USB to LAN */ +#define USB_PRODUCT_BELKIN_F5U208 0x0208 /* F5U208 VideoBus II */ +#define USB_PRODUCT_BELKIN_F5U237 0x0237 /* F5U237 USB 2.0 7-Port Hub */ +#define USB_PRODUCT_BELKIN_F5U257 0x0257 /* F5U257 Serial */ +#define USB_PRODUCT_BELKIN_F5U409 0x0409 /* F5U409 Serial */ +#define USB_PRODUCT_BELKIN_F6C550AVR 0x0551 /* F6C550-AVR UPS */ +#define USB_PRODUCT_BELKIN_F5U120 0x1203 /* F5U120-PC Hub */ +#define USB_PRODUCT_BELKIN_ZD1211B 0x4050 /* ZD1211B */ +#define USB_PRODUCT_BELKIN_F5D5055 0x5055 /* F5D5055 */ +#define USB_PRODUCT_BELKIN_F5D7050 0x7050 /* F5D7050 Wireless Adapter */ +#define USB_PRODUCT_BELKIN_F5D7051 0x7051 /* F5D7051 54g USB Network Adapter */ +#define USB_PRODUCT_BELKIN_F5D7050A 0x705a /* F5D7050A Wireless Adapter */ +#define USB_PRODUCT_BELKIN_F5D7050_V4000 0x705c /* F5D7050 v4000 Wireless Adapter */ +#define USB_PRODUCT_BELKIN_F5D9050V3 0x905b /* F5D9050 ver 3 Wireless Adapter */ +#define USB_PRODUCT_BELKIN2_F5U002 0x0002 /* F5U002 Parallel printer */ + +/* Billionton products */ +#define USB_PRODUCT_BILLIONTON_USB100 0x0986 /* USB100N 10/100 FastEthernet */ +#define USB_PRODUCT_BILLIONTON_USBLP100 0x0987 /* USB100LP */ +#define USB_PRODUCT_BILLIONTON_USBEL100 0x0988 /* USB100EL */ +#define USB_PRODUCT_BILLIONTON_USBE100 0x8511 /* USBE100 */ +#define USB_PRODUCT_BILLIONTON_USB2AR 0x90ff /* USB2AR Ethernet */ + +/* Broadcom products */ +#define USB_PRODUCT_BROADCOM_BCM2033 0x2033 /* BCM2033 Bluetooth USB dongle */ + +/* Brother Industries products */ +#define USB_PRODUCT_BROTHER_HL1050 0x0002 /* HL-1050 laser printer */ + +/* Behavior Technology Computer products */ +#define USB_PRODUCT_BTC_BTC7932 0x6782 /* Keyboard with mouse port */ + +/* Canon, Inc. products */ +#define USB_PRODUCT_CANON_N656U 0x2206 /* CanoScan N656U */ +#define USB_PRODUCT_CANON_N1220U 0x2207 /* CanoScan N1220U */ +#define USB_PRODUCT_CANON_D660U 0x2208 /* CanoScan D660U */ +#define USB_PRODUCT_CANON_N676U 0x220d /* CanoScan N676U */ +#define USB_PRODUCT_CANON_N1240U 0x220e /* CanoScan N1240U */ +#define USB_PRODUCT_CANON_LIDE25 0x2220 /* CanoScan LIDE 25 */ +#define USB_PRODUCT_CANON_S10 0x3041 /* PowerShot S10 */ +#define USB_PRODUCT_CANON_S100 0x3045 /* PowerShot S100 */ +#define USB_PRODUCT_CANON_S200 0x3065 /* PowerShot S200 */ +#define USB_PRODUCT_CANON_REBELXT 0x30ef /* Digital Rebel XT */ + +/* CATC products */ +#define USB_PRODUCT_CATC_NETMATE 0x000a /* Netmate Ethernet */ +#define USB_PRODUCT_CATC_NETMATE2 0x000c /* Netmate2 Ethernet */ +#define USB_PRODUCT_CATC_CHIEF 0x000d /* USB Chief Bus & Protocol Analyzer */ +#define USB_PRODUCT_CATC_ANDROMEDA 0x1237 /* Andromeda hub */ + +/* CASIO products */ +#define USB_PRODUCT_CASIO_QV_DIGICAM 0x1001 /* QV DigiCam */ +#define USB_PRODUCT_CASIO_EXS880 0x1105 /* Exilim EX-S880 */ +#define USB_PRODUCT_CASIO_BE300 0x2002 /* BE-300 PDA */ +#define USB_PRODUCT_CASIO_NAMELAND 0x4001 /* CASIO Nameland EZ-USB */ + +/* CCYU products */ +#define USB_PRODUCT_CCYU_ED1064 0x2136 /* EasyDisk ED1064 */ + +/* Century products */ +#define USB_PRODUCT_CENTURY_EX35QUAT 0x011e /* Century USB Disk Enclosure */ + +/* Cherry products */ +#define USB_PRODUCT_CHERRY_MY3000KBD 0x0001 /* My3000 keyboard */ +#define USB_PRODUCT_CHERRY_MY3000HUB 0x0003 /* My3000 hub */ +#define USB_PRODUCT_CHERRY_CYBOARD 0x0004 /* CyBoard Keyboard */ + +/* Chic Technology products */ +#define USB_PRODUCT_CHIC_MOUSE1 0x0001 /* mouse */ +#define USB_PRODUCT_CHIC_CYPRESS 0x0003 /* Cypress USB Mouse */ + +/* Chicony products */ +#define USB_PRODUCT_CHICONY_KB8933 0x0001 /* KB-8933 keyboard */ +#define USB_PRODUCT_MICRODIA_TWINKLECAM 0x600d /* TwinkleCam USB camera */ + +/* CH Products */ +#define USB_PRODUCT_CHPRODUCTS_PROTHROTTLE 0x00f1 /* Pro Throttle */ +#define USB_PRODUCT_CHPRODUCTS_PROPEDALS 0x00f2 /* Pro Pedals */ +#define USB_PRODUCT_CHPRODUCTS_FIGHTERSTICK 0x00f3 /* Fighterstick */ +#define USB_PRODUCT_CHPRODUCTS_FLIGHTYOKE 0x00ff /* Flight Sim Yoke */ + +/* Cisco-Linksys products */ +#define USB_PRODUCT_CISCOLINKSYS_WUSB54G 0x000d /* WUSB54G Wireless Adapter */ +#define USB_PRODUCT_CISCOLINKSYS_WUSB54GP 0x0011 /* WUSB54GP Wireless Adapter */ +#define USB_PRODUCT_CISCOLINKSYS_USB200MV2 0x0018 /* USB200M v2 */ +#define USB_PRODUCT_CISCOLINKSYS_HU200TS 0x001a /* HU200TS Wireless Adapter */ +#define USB_PRODUCT_CISCOLINKSYS_WUSB54GC 0x0020 /* WUSB54GC */ +#define USB_PRODUCT_CISCOLINKSYS_WUSB54GR 0x0023 /* WUSB54GR */ +#define USB_PRODUCT_CISCOLINKSYS_WUSBF54G 0x0024 /* WUSBF54G */ + +/* CMOTECH products */ +#define USB_PRODUCT_CMOTECH_CNU510 0x5141 /* CMOTECH CDMA Technologies USB modem */ +#define USB_PRODUCT_CMOTECH_CNU550 0x5543 /* CDMA 2000 1xRTT/1xEVDO USB modem */ +#define USB_PRODUCT_CMOTECH_CDMA_MODEM1 0x6280 /* CMOTECH CDMA Technologies USB modem */ + +/* Compaq products */ +#define USB_PRODUCT_COMPAQ_IPAQPOCKETPC 0x0003 /* iPAQ PocketPC */ +#define USB_PRODUCT_COMPAQ_PJB100 0x504a /* Personal Jukebox PJB100 */ +#define USB_PRODUCT_COMPAQ_IPAQLINUX 0x505a /* iPAQ Linux */ + +/* Composite Corp products looks the same as "TANGTOP" */ +#define USB_PRODUCT_COMPOSITE_USBPS2 0x0001 /* USB to PS2 Adaptor */ + +/* Conceptronic products */ +#define USB_PRODUCT_CONCEPTRONIC_PRISM_GT 0x3762 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_CONCEPTRONIC_C11U 0x7100 /* C11U */ +#define USB_PRODUCT_CONCEPTRONIC_WL210 0x7110 /* WL-210 */ +#define USB_PRODUCT_CONCEPTRONIC_AR5523_1 0x7801 /* AR5523 */ +#define USB_PRODUCT_CONCEPTRONIC_AR5523_1_NF 0x7802 /* AR5523 (no firmware) */ +#define USB_PRODUCT_CONCEPTRONIC_AR5523_2 0x7811 /* AR5523 */ +#define USB_PRODUCT_CONCEPTRONIC_AR5523_2_NF 0x7812 /* AR5523 (no firmware) */ +#define USB_PRODUCT_CONCEPTRONIC2_C54RU 0x3c02 /* C54RU WLAN */ +#define USB_PRODUCT_CONCEPTRONIC2_C54RU2 0x3c22 /* C54RU */ + +/* Connectix products */ +#define USB_PRODUCT_CONNECTIX_QUICKCAM 0x0001 /* QuickCam */ + +/* Corega products */ +#define USB_PRODUCT_COREGA_ETHER_USB_T 0x0001 /* Ether USB-T */ +#define USB_PRODUCT_COREGA_FETHER_USB_TX 0x0004 /* FEther USB-TX */ +#define USB_PRODUCT_COREGA_WLAN_USB_USB_11 0x000c /* WirelessLAN USB-11 */ +#define USB_PRODUCT_COREGA_FETHER_USB_TXS 0x000d /* FEther USB-TXS */ +#define USB_PRODUCT_COREGA_WLANUSB 0x0012 /* Wireless LAN Stick-11 */ +#define USB_PRODUCT_COREGA_FETHER_USB2_TX 0x0017 /* FEther USB2-TX */ +#define USB_PRODUCT_COREGA_WLUSB_11_KEY 0x001a /* ULUSB-11 Key */ +#define USB_PRODUCT_COREGA_CGWLUSB2GL 0x002d /* CG-WLUSB2GL */ +#define USB_PRODUCT_COREGA_CGWLUSB2GPX 0x002e /* CG-WLUSB2GPX */ +#define USB_PRODUCT_COREGA_WLUSB_11_STICK 0x7613 /* WLAN USB Stick 11 */ +#define USB_PRODUCT_COREGA_FETHER_USB_TXC 0x9601 /* FEther USB-TXC */ + +/* Creative products */ +#define USB_PRODUCT_CREATIVE_NOMAD_II 0x1002 /* Nomad II MP3 player */ +#define USB_PRODUCT_CREATIVE_NOMAD_IIMG 0x4004 /* Nomad II MG */ +#define USB_PRODUCT_CREATIVE_NOMAD 0x4106 /* Nomad */ +#define USB_PRODUCT_CREATIVE2_VOIP_BLASTER 0x0258 /* Voip Blaster */ +#define USB_PRODUCT_CREATIVE3_OPTICAL_MOUSE 0x0001 /* Notebook Optical Mouse */ + +/* Cambridge Silicon Radio Ltd. products */ +#define USB_PRODUCT_CSR_BT_DONGLE 0x0001 /* Bluetooth USB dongle */ +#define USB_PRODUCT_CSR_CSRDFU 0xffff /* USB Bluetooth Device in DFU State */ + +/* CTX products */ +#define USB_PRODUCT_CTX_EX1300 0x9999 /* Ex1300 hub */ + +/* Curitel products */ +#define USB_PRODUCT_CURITEL_HX550C 0x1101 /* CDMA 2000 1xRTT USB modem (HX-550C) */ +#define USB_PRODUCT_CURITEL_HX57XB 0x2101 /* CDMA 2000 1xRTT USB modem (HX-570/575B/PR-600) */ +#define USB_PRODUCT_CURITEL_PC5740 0x3701 /* Broadband Wireless modem */ + +/* CyberPower products */ +#define USB_PRODUCT_CYBERPOWER_1500CAVRLCD 0x0501 /* 1500CAVRLCD */ + +/* CyberTAN Technology products */ +#define USB_PRODUCT_CYBERTAN_TG54USB 0x1666 /* TG54USB */ + +/* Cypress Semiconductor products */ +#define USB_PRODUCT_CYPRESS_MOUSE 0x0001 /* mouse */ +#define USB_PRODUCT_CYPRESS_THERMO 0x0002 /* thermometer */ +#define USB_PRODUCT_CYPRESS_WISPY1A 0x0bad /* MetaGeek Wi-Spy */ +#define USB_PRODUCT_CYPRESS_KBDHUB 0x0101 /* Keyboard/Hub */ +#define USB_PRODUCT_CYPRESS_FMRADIO 0x1002 /* FM Radio */ +#define USB_PRODUCT_CYPRESS_USBRS232 0x5500 /* USB-RS232 Interface */ +#define USB_PRODUCT_CYPRESS_SLIM_HUB 0x6560 /* Slim Hub */ + +/* Daisy Technology products */ +#define USB_PRODUCT_DAISY_DMC 0x6901 /* USB MultiMedia Reader */ + +/* Dallas Semiconductor products */ +#define USB_PRODUCT_DALLAS_J6502 0x4201 /* J-6502 speakers */ + +/* Dell products */ +#define USB_PRODUCT_DELL_PORT 0x0058 /* Port Replicator */ +#define USB_PRODUCT_DELL_AIO926 0x5115 /* Photo AIO Printer 926 */ +#define USB_PRODUCT_DELL_BC02 0x8000 /* BC02 Bluetooth USB Adapter */ +#define USB_PRODUCT_DELL_PRISM_GT_1 0x8102 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_DELL_TM350 0x8103 /* TrueMobile 350 Bluetooth USB Adapter */ +#define USB_PRODUCT_DELL_PRISM_GT_2 0x8104 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_DELL_U740 0x8135 /* Dell U740 CDMA */ + +/* Delorme Paublishing products */ +#define USB_PRODUCT_DELORME_EARTHMATE 0x0100 /* Earthmate GPS */ + +/* Desknote products */ +#define USB_PRODUCT_DESKNOTE_UCR_61S2B 0x0c55 /* UCR-61S2B */ + +/* Diamond products */ +#define USB_PRODUCT_DIAMOND_RIO500USB 0x0001 /* Rio 500 USB */ + +/* Dick Smith Electronics (really C-Net) products */ +#define USB_PRODUCT_DICKSMITH_RT2573 0x9022 /* RT2573 */ +#define USB_PRODUCT_DICKSMITH_CWD854F 0x9032 /* C-Net CWD-854 rev F */ + +/* Digi International products */ +#define USB_PRODUCT_DIGI_ACCELEPORT2 0x0002 /* AccelePort USB 2 */ +#define USB_PRODUCT_DIGI_ACCELEPORT4 0x0004 /* AccelePort USB 4 */ +#define USB_PRODUCT_DIGI_ACCELEPORT8 0x0008 /* AccelePort USB 8 */ + +/* D-Link products */ +/*product DLINK DSBS25 0x0100 DSB-S25 serial*/ +#define USB_PRODUCT_DLINK_DUBE100 0x1a00 /* 10/100 Ethernet */ +#define USB_PRODUCT_DLINK_DSB650TX4 0x200c /* 10/100 Ethernet */ +#define USB_PRODUCT_DLINK_DWL120E 0x3200 /* DWL-120 rev E */ +#define USB_PRODUCT_DLINK_DWL122 0x3700 /* DWL-122 */ +#define USB_PRODUCT_DLINK_DWLG120 0x3701 /* DWL-G120 */ +#define USB_PRODUCT_DLINK_DWL120F 0x3702 /* DWL-120 rev F */ +#define USB_PRODUCT_DLINK_DWLAG132 0x3a00 /* DWL-AG132 */ +#define USB_PRODUCT_DLINK_DWLAG132_NF 0x3a01 /* DWL-AG132 (no firmware) */ +#define USB_PRODUCT_DLINK_DWLG132 0x3a02 /* DWL-G132 */ +#define USB_PRODUCT_DLINK_DWLG132_NF 0x3a03 /* DWL-G132 (no firmware) */ +#define USB_PRODUCT_DLINK_DWLAG122 0x3a04 /* DWL-AG122 */ +#define USB_PRODUCT_DLINK_DWLAG122_NF 0x3a05 /* DWL-AG122 (no firmware) */ +#define USB_PRODUCT_DLINK_DWLG122 0x3c00 /* DWL-G122 b1 Wireless Adapter */ +#define USB_PRODUCT_DLINK_DUBE100B1 0x3c05 /* DUB-E100 rev B1 */ +#define USB_PRODUCT_DLINK_DSB650C 0x4000 /* 10Mbps Ethernet */ +#define USB_PRODUCT_DLINK_DSB650TX1 0x4001 /* 10/100 Ethernet */ +#define USB_PRODUCT_DLINK_DSB650TX 0x4002 /* 10/100 Ethernet */ +#define USB_PRODUCT_DLINK_DSB650TX_PNA 0x4003 /* 1/10/100 Ethernet */ +#define USB_PRODUCT_DLINK_DSB650TX3 0x400b /* 10/100 Ethernet */ +#define USB_PRODUCT_DLINK_DSB650TX2 0x4102 /* 10/100 Ethernet */ +#define USB_PRODUCT_DLINK_DSB650 0xabc1 /* 10/100 Ethernet */ +#define USB_PRODUCT_DLINK2_DWLG122C1 0x3c03 /* DWL-G122 c1 */ +#define USB_PRODUCT_DLINK2_WUA1340 0x3c04 /* WUA-1340 */ +#define USB_PRODUCT_DLINK2_DWA111 0x3c06 /* DWA-111 */ +#define USB_PRODUCT_DLINK2_DWA110 0x3c07 /* DWA-110 */ + +/* DMI products */ +#define USB_PRODUCT_DMI_CFSM_RW 0xa109 /* CF/SM Reader/Writer */ + +/* DrayTek products */ +#define USB_PRODUCT_DRAYTEK_VIGOR550 0x0550 /* Vigor550 */ + +/* Dynastream Innovations */ +#define USB_PRODUCT_DYNASTREAM_ANTDEVBOARD 0x1003 /* ANT dev board */ + +/* EIZO products */ +#define USB_PRODUCT_EIZO_HUB 0x0000 /* hub */ +#define USB_PRODUCT_EIZO_MONITOR 0x0001 /* monitor */ + +/* ELCON Systemtechnik products */ +#define USB_PRODUCT_ELCON_PLAN 0x0002 /* Goldpfeil P-LAN */ + +/* Elecom products */ +#define USB_PRODUCT_ELECOM_MOUSE29UO 0x0002 /* mouse 29UO */ +#define USB_PRODUCT_ELECOM_LDUSBTX0 0x200c /* LD-USB/TX */ +#define USB_PRODUCT_ELECOM_LDUSBTX1 0x4002 /* LD-USB/TX */ +#define USB_PRODUCT_ELECOM_LDUSBLTX 0x4005 /* LD-USBL/TX */ +#define USB_PRODUCT_ELECOM_LDUSBTX2 0x400b /* LD-USB/TX */ +#define USB_PRODUCT_ELECOM_LDUSB20 0x4010 /* LD-USB20 */ +#define USB_PRODUCT_ELECOM_UCSGT 0x5003 /* UC-SGT */ +#define USB_PRODUCT_ELECOM_UCSGT0 0x5004 /* UC-SGT */ +#define USB_PRODUCT_ELECOM_LDUSBTX3 0xabc1 /* LD-USB/TX */ + +/* Elsa products */ +#define USB_PRODUCT_ELSA_MODEM1 0x2265 /* ELSA Modem Board */ +#define USB_PRODUCT_ELSA_USB2ETHERNET 0x3000 /* Microlink USB2Ethernet */ + +/* EMS products */ +#define USB_PRODUCT_EMS_DUAL_SHOOTER 0x0003 /* PSX gun controller converter */ + +/* Entrega products */ +#define USB_PRODUCT_ENTREGA_1S 0x0001 /* 1S serial */ +#define USB_PRODUCT_ENTREGA_2S 0x0002 /* 2S serial */ +#define USB_PRODUCT_ENTREGA_1S25 0x0003 /* 1S25 serial */ +#define USB_PRODUCT_ENTREGA_4S 0x0004 /* 4S serial */ +#define USB_PRODUCT_ENTREGA_E45 0x0005 /* E45 Ethernet */ +#define USB_PRODUCT_ENTREGA_CENTRONICS 0x0006 /* Parallel Port */ +#define USB_PRODUCT_ENTREGA_XX1 0x0008 /* Ethernet */ +#define USB_PRODUCT_ENTREGA_1S9 0x0093 /* 1S9 serial */ +#define USB_PRODUCT_ENTREGA_EZUSB 0x8000 /* EZ-USB */ +/*product ENTREGA SERIAL 0x8001 DB25 Serial*/ +#define USB_PRODUCT_ENTREGA_2U4S 0x8004 /* 2U4S serial/usb hub */ +#define USB_PRODUCT_ENTREGA_XX2 0x8005 /* Ethernet */ +/*product ENTREGA SERIAL_DB9 0x8093 DB9 Serial*/ + +/* Epson products */ +#define USB_PRODUCT_EPSON_PRINTER1 0x0001 /* USB Printer */ +#define USB_PRODUCT_EPSON_PRINTER2 0x0002 /* ISD USB Smart Cable for Mac */ +#define USB_PRODUCT_EPSON_PRINTER3 0x0003 /* ISD USB Smart Cable */ +#define USB_PRODUCT_EPSON_PRINTER5 0x0005 /* USB Printer */ +#define USB_PRODUCT_EPSON_636 0x0101 /* Perfection 636U / 636Photo scanner */ +#define USB_PRODUCT_EPSON_610 0x0103 /* Perfection 610 scanner */ +#define USB_PRODUCT_EPSON_1200 0x0104 /* Perfection 1200U / 1200Photo scanner */ +#define USB_PRODUCT_EPSON_1600 0x0107 /* Expression 1600 scanner */ +#define USB_PRODUCT_EPSON_1640 0x010a /* Perfection 1640SU scanner */ +#define USB_PRODUCT_EPSON_1240 0x010b /* Perfection 1240U / 1240Photo scanner */ +#define USB_PRODUCT_EPSON_640U 0x010c /* Perfection 640U scanner */ +#define USB_PRODUCT_EPSON_1250 0x010f /* Perfection 1250U / 1250Photo scanner */ +#define USB_PRODUCT_EPSON_1650 0x0110 /* Perfection 1650 scanner */ +#define USB_PRODUCT_EPSON_GT9700F 0x0112 /* GT-9700F scanner */ +#define USB_PRODUCT_EPSON_GT9300UF 0x011b /* GT-9300UF scanner */ +#define USB_PRODUCT_EPSON_3200 0x011c /* Perfection 3200 scanner */ +#define USB_PRODUCT_EPSON_1260 0x011d /* Perfection 1260 scanner */ +#define USB_PRODUCT_EPSON_1660 0x011e /* Perfection 1660 scanner */ +#define USB_PRODUCT_EPSON_1670 0x011f /* Perfection 1670 scanner */ +#define USB_PRODUCT_EPSON_1270 0x0120 /* Perfection 1270 scanner */ +#define USB_PRODUCT_EPSON_2480 0x0121 /* Perfection 2480 scanner */ +#define USB_PRODUCT_EPSON_3590 0x0122 /* Perfection 3590 scanner */ +#define USB_PRODUCT_EPSON_4990 0x012a /* Perfection 4990 Photo scanner */ +#define USB_PRODUCT_EPSON_STYLUS_875DC 0x0601 /* Stylus Photo 875DC Card Reader */ +#define USB_PRODUCT_EPSON_STYLUS_895 0x0602 /* Stylus Photo 895 Card Reader */ +#define USB_PRODUCT_EPSON_CX5400 0x0808 /* CX5400 scanner */ +#define USB_PRODUCT_EPSON_3500 0x080e /* CX-3500/3600/3650 MFP */ +#define USB_PRODUCT_EPSON_RX425 0x080f /* Stylus Photo RX425 scanner */ +#define USB_PRODUCT_EPSON_4800 0x0819 /* CX4800 MP scanner */ +#define USB_PRODUCT_EPSON_4200 0x0820 /* CX4200 MP scanner */ +#define USB_PRODUCT_EPSON_5000 0x082b /* DX-50x0 MFP scanner */ +#define USB_PRODUCT_EPSON_6000 0x082e /* DX-60x0 MFP scanner */ +#define USB_PRODUCT_EPSON_DX7400 0x0838 /* DX7400/CX7300 scanner */ +#define USB_PRODUCT_EPSON_DX8400 0x0839 /* DX8400 scanner */ + +/* e-TEK Labs products */ +#define USB_PRODUCT_ETEK_1COM 0x8007 /* Serial */ + +/* Extended Systems products */ +#define USB_PRODUCT_EXTENDED_XTNDACCESS 0x0100 /* XTNDAccess IrDA */ + +/* FEIYA products */ +#define USB_PRODUCT_FEIYA_5IN1 0x1132 /* 5-in-1 Card Reader */ + +/* Fiberline */ +#define USB_PRODUCT_FIBERLINE_WL430U 0x6003 /* WL-430U */ + +/* Fossil, Inc products */ +#define USB_PRODUCT_FOSSIL_WRISTPDA 0x0002 /* Wrist PDA */ + +/* Freecom products */ +#define USB_PRODUCT_FREECOM_DVD 0xfc01 /* DVD drive */ + +/* Fujitsu Siemens Computers products */ +#define USB_PRODUCT_FSC_E5400 0x1009 /* PrismGT USB 2.0 WLAN */ + +/* Future Technology Devices products */ +#define USB_PRODUCT_FTDI_SERIAL_8U100AX 0x8372 /* 8U100AX Serial */ +#define USB_PRODUCT_FTDI_SERIAL_8U232AM 0x6001 /* 8U232AM Serial */ +#define USB_PRODUCT_FTDI_SERIAL_2232C 0x6010 /* FT2232C Dual port Serial */ +/* Gude Analog- und Digitalsysteme products also uses FTDI's id: */ +#define USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13M 0xcc48 /* OpenPort 1.3 Mitsubishi */ +#define USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13S 0xcc49 /* OpenPort 1.3 Subaru */ +#define USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13U 0xcc4a /* OpenPort 1.3 Universal */ +#define USB_PRODUCT_FTDI_EISCOU 0xe888 /* Expert ISDN Control USB */ +#define USB_PRODUCT_FTDI_UOPTBR 0xe889 /* USB-RS232 OptoBridge */ +#define USB_PRODUCT_FTDI_EMCU2D 0xe88a /* Expert mouseCLOCK USB II */ +#define USB_PRODUCT_FTDI_PCMSFU 0xe88b /* Precision Clock MSF USB */ +#define USB_PRODUCT_FTDI_EMCU2H 0xe88c /* Expert mouseCLOCK USB II HBG */ +#define USB_PRODUCT_FTDI_USBSERIAL 0xfa00 /* Matrix Orbital USB Serial */ +#define USB_PRODUCT_FTDI_MX2_3 0xfa01 /* Matrix Orbital MX2 or MX3 */ +#define USB_PRODUCT_FTDI_MX4_5 0xfa02 /* Matrix Orbital MX4 or MX5 */ +#define USB_PRODUCT_FTDI_LK202 0xfa03 /* Matrix Orbital VK/LK202 Family */ +#define USB_PRODUCT_FTDI_LK204 0xfa04 /* Matrix Orbital VK/LK204 Family */ +#define USB_PRODUCT_FTDI_CFA_632 0xfc08 /* Crystalfontz CFA-632 USB LCD */ +#define USB_PRODUCT_FTDI_CFA_634 0xfc09 /* Crystalfontz CFA-634 USB LCD */ +#define USB_PRODUCT_FTDI_CFA_633 0xfc0b /* Crystalfontz CFA-633 USB LCD */ +#define USB_PRODUCT_FTDI_CFA_631 0xfc0c /* Crystalfontz CFA-631 USB LCD */ +#define USB_PRODUCT_FTDI_CFA_635 0xfc0d /* Crystalfontz CFA-635 USB LCD */ +#define USB_PRODUCT_FTDI_SEMC_DSS20 0xfc82 /* SEMC DSS-20 SyncStation */ + +/* Fuji photo products */ +#define USB_PRODUCT_FUJIPHOTO_MASS0100 0x0100 /* Mass Storage */ + +/* Fujitsu protducts */ +#define USB_PRODUCT_FUJITSU_AH_F401U 0x105b /* AH-F401U Air H device */ + +/* Garmin products */ +#define USB_PRODUCT_GARMIN_IQUE_3600 0x0004 /* iQue 3600 */ + +/* General Instruments (Motorola) products */ +#define USB_PRODUCT_GENERALINSTMNTS_SB5100 0x5100 /* SURFboard SB5100 Cable modem */ + +/* Genesys Logic products */ +#define USB_PRODUCT_GENESYS_GL620USB 0x0501 /* GL620USB Host-Host interface */ +#define USB_PRODUCT_GENESYS_GL650 0x0604 /* GL650 Hub */ +#define USB_PRODUCT_GENESYS_GL641USB 0x0700 /* GL641USB CompactFlash Card Reader */ +#define USB_PRODUCT_GENESYS_GL641USB2IDE_2 0x0701 /* GL641USB USB-IDE Bridge No 2 */ +#define USB_PRODUCT_GENESYS_GL641USB2IDE 0x0702 /* GL641USB USB-IDE Bridge */ +#define USB_PRODUCT_GENESYS_GL641USB_2 0x0760 /* GL641USB 6-in-1 Card Reader */ + +/* GIGABYTE products */ +#define USB_PRODUCT_GIGABYTE_GN54G 0x8001 /* GN-54G */ +#define USB_PRODUCT_GIGABYTE_GNBR402W 0x8002 /* GN-BR402W */ +#define USB_PRODUCT_GIGABYTE_GNWLBM101 0x8003 /* GN-WLBM101 */ +#define USB_PRODUCT_GIGABYTE_GNWBKG 0x8007 /* GN-WBKG */ +#define USB_PRODUCT_GIGABYTE_GNWB01GS 0x8008 /* GN-WB01GS */ +#define USB_PRODUCT_GIGABYTE_GNWI05GS 0x800a /* GN-WI05GS */ + +/* Gigaset products */ +#define USB_PRODUCT_GIGASET_WLAN 0x0701 /* WLAN */ +#define USB_PRODUCT_GIGASET_SMCWUSBTG 0x0710 /* SMCWUSBT-G */ +#define USB_PRODUCT_GIGASET_SMCWUSBTG_NF 0x0711 /* SMCWUSBT-G (no firmware) */ +#define USB_PRODUCT_GIGASET_AR5523 0x0712 /* AR5523 */ +#define USB_PRODUCT_GIGASET_AR5523_NF 0x0713 /* AR5523 (no firmware) */ +#define USB_PRODUCT_GIGASET_RT2573 0x0722 /* RT2573 */ + +/* Global Sun Technology product */ +#define USB_PRODUCT_GLOBALSUN_AR5523_1 0x7801 /* AR5523 */ +#define USB_PRODUCT_GLOBALSUN_AR5523_1_NF 0x7802 /* AR5523 (no firmware) */ +#define USB_PRODUCT_GLOBALSUN_AR5523_2 0x7811 /* AR5523 */ +#define USB_PRODUCT_GLOBALSUN_AR5523_2_NF 0x7812 /* AR5523 (no firmware) */ + +/* Globespan products */ +#define USB_PRODUCT_GLOBESPAN_PRISM_GT_1 0x2000 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_GLOBESPAN_PRISM_GT_2 0x2002 /* PrismGT USB 2.0 WLAN */ + +/* G.Mate, Inc products */ +#define USB_PRODUCT_GMATE_YP3X00 0x1001 /* YP3X00 PDA */ + +/* GoHubs products */ +#define USB_PRODUCT_GOHUBS_GOCOM232 0x1001 /* GoCOM232 Serial */ + +/* Good Way Technology products */ +#define USB_PRODUCT_GOODWAY_GWUSB2E 0x6200 /* GWUSB2E */ +#define USB_PRODUCT_GOODWAY_RT2573 0xc019 /* RT2573 */ + +/* Gravis products */ +#define USB_PRODUCT_GRAVIS_GAMEPADPRO 0x4001 /* GamePad Pro */ + +/* GREENHOUSE products */ +#define USB_PRODUCT_GREENHOUSE_KANA21 0x0001 /* CF-writer with MP3 */ + +/* Griffin Technology */ +#define USB_PRODUCT_GRIFFIN_IMATE 0x0405 /* iMate, ADB Adapter */ + +/* Guillemot Corporation */ +#define USB_PRODUCT_GUILLEMOT_DALEADER 0xa300 /* DA Leader */ +#define USB_PRODUCT_GUILLEMOT_HWGUSB254 0xe000 /* HWGUSB2-54 WLAN */ +#define USB_PRODUCT_GUILLEMOT_HWGUSB254LB 0xe010 /* HWGUSB2-54-LB */ +#define USB_PRODUCT_GUILLEMOT_HWGUSB254V2AP 0xe020 /* HWGUSB2-54V2-AP */ + +/* Hagiwara products */ +#define USB_PRODUCT_HAGIWARA_FGSM 0x0002 /* FlashGate SmartMedia Card Reader */ +#define USB_PRODUCT_HAGIWARA_FGCF 0x0003 /* FlashGate CompactFlash Card Reader */ +#define USB_PRODUCT_HAGIWARA_FG 0x0005 /* FlashGate */ + +/* HAL Corporation products */ +#define USB_PRODUCT_HAL_IMR001 0x0011 /* Crossam2+USB IR commander */ + +/* Handspring, Inc. */ +#define USB_PRODUCT_HANDSPRING_VISOR 0x0100 /* Handspring Visor */ +#define USB_PRODUCT_HANDSPRING_TREO 0x0200 /* Handspring Treo */ +#define USB_PRODUCT_HANDSPRING_TREO600 0x0300 /* Handspring Treo 600 */ + +/* Hauppauge Computer Works */ +#define USB_PRODUCT_HAUPPAUGE_WINTV_USB_FM 0x4d12 /* WinTV USB FM */ + +/* Hawking Technologies products */ +#define USB_PRODUCT_HAWKING_UF100 0x400c /* 10/100 USB Ethernet */ + +/* Hitachi, Ltd. products */ +#define USB_PRODUCT_HITACHI_DVDCAM_DZ_MV100A 0x0004 /* DVD-CAM DZ-MV100A Camcorder */ +#define USB_PRODUCT_HITACHI_DVDCAM_USB 0x001e /* DVDCAM USB HS Interface */ + +/* HP products */ +#define USB_PRODUCT_HP_895C 0x0004 /* DeskJet 895C */ +#define USB_PRODUCT_HP_4100C 0x0101 /* Scanjet 4100C */ +#define USB_PRODUCT_HP_S20 0x0102 /* Photosmart S20 */ +#define USB_PRODUCT_HP_880C 0x0104 /* DeskJet 880C */ +#define USB_PRODUCT_HP_4200C 0x0105 /* ScanJet 4200C */ +#define USB_PRODUCT_HP_CDWRITERPLUS 0x0107 /* CD-Writer Plus */ +#define USB_PRODUCT_HP_KBDHUB 0x010c /* Multimedia Keyboard Hub */ +#define USB_PRODUCT_HP_G55XI 0x0111 /* OfficeJet G55xi */ +#define USB_PRODUCT_HP_HN210W 0x011c /* HN210W 802.11b WLAN */ +#define USB_PRODUCT_HP_49GPLUS 0x0121 /* 49g+ graphing calculator */ +#define USB_PRODUCT_HP_6200C 0x0201 /* ScanJet 6200C */ +#define USB_PRODUCT_HP_S20b 0x0202 /* PhotoSmart S20 */ +#define USB_PRODUCT_HP_815C 0x0204 /* DeskJet 815C */ +#define USB_PRODUCT_HP_3300C 0x0205 /* ScanJet 3300C */ +#define USB_PRODUCT_HP_CDW8200 0x0207 /* CD-Writer Plus 8200e */ +#define USB_PRODUCT_HP_MMKEYB 0x020c /* Multimedia keyboard */ +#define USB_PRODUCT_HP_1220C 0x0212 /* DeskJet 1220C */ +#define USB_PRODUCT_HP_810C 0x0304 /* DeskJet 810C/812C */ +#define USB_PRODUCT_HP_4300C 0x0305 /* Scanjet 4300C */ +#define USB_PRODUCT_HP_CDW4E 0x0307 /* CD-Writer+ CD-4e */ +#define USB_PRODUCT_HP_G85XI 0x0311 /* OfficeJet G85xi */ +#define USB_PRODUCT_HP_1200 0x0317 /* LaserJet 1200 */ +#define USB_PRODUCT_HP_5200C 0x0401 /* Scanjet 5200C */ +#define USB_PRODUCT_HP_830C 0x0404 /* DeskJet 830C */ +#define USB_PRODUCT_HP_3400CSE 0x0405 /* ScanJet 3400cse */ +#define USB_PRODUCT_HP_6300C 0x0601 /* Scanjet 6300C */ +#define USB_PRODUCT_HP_840C 0x0604 /* DeskJet 840c */ +#define USB_PRODUCT_HP_2200C 0x0605 /* ScanJet 2200C */ +#define USB_PRODUCT_HP_5300C 0x0701 /* Scanjet 5300C */ +#define USB_PRODUCT_HP_4400C 0x0705 /* Scanjet 4400C */ +#define USB_PRODUCT_HP_82x0C 0x0b01 /* Scanjet 82x0C */ +#define USB_PRODUCT_HP_2300D 0x0b17 /* Laserjet 2300d */ +#define USB_PRODUCT_HP_970CSE 0x1004 /* Deskjet 970Cse */ +#define USB_PRODUCT_HP_5400C 0x1005 /* Scanjet 5400C */ +#define USB_PRODUCT_HP_2215 0x1016 /* iPAQ 22xx/Jornada 548 */ +#define USB_PRODUCT_HP_568J 0x1116 /* Jornada 568 */ +#define USB_PRODUCT_HP_930C 0x1204 /* DeskJet 930c */ +#define USB_PRODUCT_HP_P2000U 0x1801 /* Inkjet P-2000U */ +#define USB_PRODUCT_HP_640C 0x2004 /* DeskJet 640c */ +#define USB_PRODUCT_HP_4670V 0x3005 /* ScanJet 4670v */ +#define USB_PRODUCT_HP_P1100 0x3102 /* Photosmart P1100 */ +#define USB_PRODUCT_HP_HN210E 0x811c /* Ethernet HN210E */ +#define USB_PRODUCT_HP2_C500 0x6002 /* PhotoSmart C500 */ + +/* HTC products */ +#define USB_PRODUCT_HTC_WINMOBILE 0x00ce /* HTC USB Sync */ +#define USB_PRODUCT_HTC_PPC6700MODEM 0x00cf /* PPC6700 Modem */ +#define USB_PRODUCT_HTC_SMARTPHONE 0x0a51 /* SmartPhone USB Sync */ + +/* HUAWEI products */ +#define USB_PRODUCT_HUAWEI_MOBILE 0x1001 /* Huawei Mobile */ +#define USB_PRODUCT_HUAWEI_E270 0x1003 /* Huawei HSPA modem */ + +/* HUAWEI 3com products */ +#define USB_PRODUCT_HUAWEI3COM_WUB320G 0x0009 /* Aolynk WUB320g */ + +/* IBM Corporation */ +#define USB_PRODUCT_IBM_USBCDROMDRIVE 0x4427 /* USB CD-ROM Drive */ + +/* Imagination Technologies products */ +#define USB_PRODUCT_IMAGINATION_DBX1 0x2107 /* DBX1 DSP core */ + +/* Inside Out Networks products */ +#define USB_PRODUCT_INSIDEOUT_EDGEPORT4 0x0001 /* EdgePort/4 serial ports */ + +/* In-System products */ +#define USB_PRODUCT_INSYSTEM_F5U002 0x0002 /* Parallel printer */ +#define USB_PRODUCT_INSYSTEM_ATAPI 0x0031 /* ATAPI Adapter */ +#define USB_PRODUCT_INSYSTEM_ISD110 0x0200 /* IDE Adapter ISD110 */ +#define USB_PRODUCT_INSYSTEM_ISD105 0x0202 /* IDE Adapter ISD105 */ +#define USB_PRODUCT_INSYSTEM_USBCABLE 0x081a /* USB cable */ +#define USB_PRODUCT_INSYSTEM_STORAGE_V2 0x5701 /* USB Storage Adapter V2 */ + +/* Intel products */ +#define USB_PRODUCT_INTEL_EASYPC_CAMERA 0x0110 /* Easy PC Camera */ +#define USB_PRODUCT_INTEL_TESTBOARD 0x9890 /* 82930 test board */ + +/* Intersil products */ +#define USB_PRODUCT_INTERSIL_PRISM_GT 0x1000 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_INTERSIL_PRISM_2X 0x3642 /* Prism2.x or Atmel WLAN */ + +/* Interpid Control Systems products */ +#define USB_PRODUCT_INTREPIDCS_VALUECAN 0x0601 /* ValueCAN CAN bus interface */ +#define USB_PRODUCT_INTREPIDCS_NEOVI 0x0701 /* NeoVI Blue vehicle bus interface */ + +/* I/O DATA products */ +#define USB_PRODUCT_IODATA_IU_CD2 0x0204 /* DVD Multi-plus unit iU-CD2 */ +#define USB_PRODUCT_IODATA_DVR_UEH8 0x0206 /* DVD Multi-plus unit DVR-UEH8 */ +#define USB_PRODUCT_IODATA_USBSSMRW 0x0314 /* USB-SSMRW SD-card */ +#define USB_PRODUCT_IODATA_USBSDRW 0x031e /* USB-SDRW SD-card */ +#define USB_PRODUCT_IODATA_USBETT 0x0901 /* USB ETT */ +#define USB_PRODUCT_IODATA_USBETTX 0x0904 /* USB ETTX */ +#define USB_PRODUCT_IODATA_USBETTXS 0x0913 /* USB ETTX */ +#define USB_PRODUCT_IODATA_USBWNB11A 0x0919 /* USB WN-B11 */ +#define USB_PRODUCT_IODATA_USBWNB11 0x0922 /* USB Airport WN-B11 */ +#define USB_PRODUCT_IODATA_ETGUS2 0x0930 /* ETG-US2 */ +#define USB_PRODUCT_IODATA_USBRSAQ 0x0a03 /* Serial USB-RSAQ1 */ +#define USB_PRODUCT_IODATA2_USB2SC 0x0a09 /* USB2.0-SCSI Bridge USB2-SC */ + +/* Iomega products */ +#define USB_PRODUCT_IOMEGA_ZIP100 0x0001 /* Zip 100 */ +#define USB_PRODUCT_IOMEGA_ZIP250 0x0030 /* Zip 250 */ + +/* Ituner networks products */ +#define USB_PRODUCT_ITUNERNET_USBLCD2X20 0x0002 /* USB-LCD 2x20 */ + +/* Jablotron products */ +#define USB_PRODUCT_JABLOTRON_PC60B 0x0001 /* PC-60B */ + +/* Jaton products */ +#define USB_PRODUCT_JATON_EDA 0x5704 /* Ethernet */ + +/* JVC products */ +#define USB_PRODUCT_JVC_GR_DX95 0x000a /* GR-DX95 */ +#define USB_PRODUCT_JVC_MP_PRX1 0x3008 /* MP-PRX1 Ethernet */ + +/* JRC products */ +#define USB_PRODUCT_JRC_AH_J3001V_J3002V 0x0001 /* AirH PHONE AH-J3001V/J3002V */ + +/* Kawatsu products */ +#define USB_PRODUCT_KAWATSU_MH4000P 0x0003 /* MiniHub 4000P */ + +/* Keisokugiken Corp. products */ +#define USB_PRODUCT_KEISOKUGIKEN_USBDAQ 0x0068 /* HKS-0200 USBDAQ */ + +/* Kensington products */ +#define USB_PRODUCT_KENSINGTON_ORBIT 0x1003 /* Orbit USB/PS2 trackball */ +#define USB_PRODUCT_KENSINGTON_TURBOBALL 0x1005 /* TurboBall */ + +/* Keyspan products */ +#define USB_PRODUCT_KEYSPAN_USA28_NF 0x0101 /* USA-28 serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA28X_NF 0x0102 /* USA-28X serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA19_NF 0x0103 /* USA-19 serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA18_NF 0x0104 /* USA-18 serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA18X_NF 0x0105 /* USA-18X serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA19W_NF 0x0106 /* USA-19W serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA19 0x0107 /* USA-19 serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA19W 0x0108 /* USA-19W serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA49W_NF 0x0109 /* USA-49W serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA49W 0x010a /* USA-49W serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA19QI_NF 0x010b /* USA-19QI serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA19QI 0x010c /* USA-19QI serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA19Q_NF 0x010d /* USA-19Q serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA19Q 0x010e /* USA-19Q serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA28 0x010f /* USA-28 serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA28XXB 0x0110 /* USA-28X/XB serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA18 0x0111 /* USA-18 serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA18X 0x0112 /* USA-18X serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA28XB_NF 0x0113 /* USA-28XB serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA28XA_NF 0x0114 /* USA-28XB serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA28XA 0x0115 /* USA-28XA serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA18XA_NF 0x0116 /* USA-18XA serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA18XA 0x0117 /* USA-18XA serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA19QW_NF 0x0118 /* USA-19WQ serial Adapter (no firmware) */ +#define USB_PRODUCT_KEYSPAN_USA19QW 0x0119 /* USA-19WQ serial Adapter */ +#define USB_PRODUCT_KEYSPAN_USA19HA 0x0121 /* USA-19HS serial Adapter */ +#define USB_PRODUCT_KEYSPAN_UIA10 0x0201 /* UIA-10 remote control */ +#define USB_PRODUCT_KEYSPAN_UIA11 0x0202 /* UIA-11 remote control */ + +/* Kingston products */ +#define USB_PRODUCT_KINGSTON_XX1 0x0008 /* Ethernet */ +#define USB_PRODUCT_KINGSTON_KNU101TX 0x000a /* KNU101TX USB Ethernet */ + +/* Kawasaki products */ +#define USB_PRODUCT_KLSI_DUH3E10BT 0x0008 /* USB Ethernet */ +#define USB_PRODUCT_KLSI_DUH3E10BTN 0x0009 /* USB Ethernet */ + +/* Kodak products */ +#define USB_PRODUCT_KODAK_DC220 0x0100 /* Digital Science DC220 */ +#define USB_PRODUCT_KODAK_DC260 0x0110 /* Digital Science DC260 */ +#define USB_PRODUCT_KODAK_DC265 0x0111 /* Digital Science DC265 */ +#define USB_PRODUCT_KODAK_DC290 0x0112 /* Digital Science DC290 */ +#define USB_PRODUCT_KODAK_DC240 0x0120 /* Digital Science DC240 */ +#define USB_PRODUCT_KODAK_DC280 0x0130 /* Digital Science DC280 */ + +/* Konica Corp. Products */ +#define USB_PRODUCT_KONICA_CAMERA 0x0720 /* Digital Color Camera */ + +/* KYE products */ +#define USB_PRODUCT_KYE_NICHE 0x0001 /* Niche mouse */ +#define USB_PRODUCT_KYE_NETSCROLL 0x0003 /* Genius NetScroll mouse */ +#define USB_PRODUCT_KYE_FLIGHT2000 0x1004 /* Flight 2000 joystick */ +#define USB_PRODUCT_KYE_VIVIDPRO 0x2001 /* ColorPage Vivid-Pro scanner */ + +/* Kyocera products */ +#define USB_PRODUCT_KYOCERA_FINECAM_S3X 0x0100 /* Finecam S3x */ +#define USB_PRODUCT_KYOCERA_FINECAM_S4 0x0101 /* Finecam S4 */ +#define USB_PRODUCT_KYOCERA_FINECAM_S5 0x0103 /* Finecam S5 */ +#define USB_PRODUCT_KYOCERA_FINECAM_L3 0x0105 /* Finecam L3 */ +#define USB_PRODUCT_KYOCERA_AHK3001V 0x0203 /* AH-K3001V */ +#define USB_PRODUCT_KYOCERA2_CDMA_MSM_K 0x17da /* Qualcomm Kyocera CDMA Technologies MSM */ + +/* LaCie products */ +#define USB_PRODUCT_LACIE_HD 0xa601 /* Hard Disk */ +#define USB_PRODUCT_LACIE_CDRW 0xa602 /* CD R/W */ + +/* Lexar products */ +#define USB_PRODUCT_LEXAR_JUMPSHOT 0x0001 /* jumpSHOT CompactFlash Reader */ +#define USB_PRODUCT_LEXAR_CF_READER 0xb002 /* USB CF Reader */ + +/* Lexmark products */ +#define USB_PRODUCT_LEXMARK_S2450 0x0009 /* Optra S 2450 */ + +/* Linksys products */ +#define USB_PRODUCT_LINKSYS_MAUSB2 0x0105 /* Camedia MAUSB-2 */ +#define USB_PRODUCT_LINKSYS_USB10TX1 0x200c /* USB10TX */ +#define USB_PRODUCT_LINKSYS_USB10T 0x2202 /* USB10T Ethernet */ +#define USB_PRODUCT_LINKSYS_USB100TX 0x2203 /* USB100TX Ethernet */ +#define USB_PRODUCT_LINKSYS_USB100H1 0x2204 /* USB100H1 Ethernet/HPNA */ +#define USB_PRODUCT_LINKSYS_USB10TA 0x2206 /* USB10TA Ethernet */ +#define USB_PRODUCT_LINKSYS_USB10TX2 0x400b /* USB10TX */ +#define USB_PRODUCT_LINKSYS2_WUSB11 0x2219 /* WUSB11 Wireless Adapter */ +#define USB_PRODUCT_LINKSYS2_USB200M 0x2226 /* USB 2.0 10/100 Ethernet */ +#define USB_PRODUCT_LINKSYS3_WUSB11v28 0x2233 /* WUSB11 v2.8 Wireless Adapter */ +#define USB_PRODUCT_LINKSYS4_USB1000 0x0039 /* USB1000 */ + +/* Logitech products */ +#define USB_PRODUCT_LOGITECH_M2452 0x0203 /* M2452 keyboard */ +#define USB_PRODUCT_LOGITECH_M4848 0x0301 /* M4848 mouse */ +#define USB_PRODUCT_LOGITECH_PAGESCAN 0x040f /* PageScan */ +#define USB_PRODUCT_LOGITECH_QUICKCAMWEB 0x0801 /* QuickCam Web */ +#define USB_PRODUCT_LOGITECH_QUICKCAMPRO 0x0810 /* QuickCam Pro */ +#define USB_PRODUCT_LOGITECH_QUICKCAMEXP 0x0840 /* QuickCam Express */ +#define USB_PRODUCT_LOGITECH_QUICKCAM 0x0850 /* QuickCam */ +#define USB_PRODUCT_LOGITECH_N43 0xc000 /* N43 */ +#define USB_PRODUCT_LOGITECH_N48 0xc001 /* N48 mouse */ +#define USB_PRODUCT_LOGITECH_MBA47 0xc002 /* M-BA47 mouse */ +#define USB_PRODUCT_LOGITECH_WMMOUSE 0xc004 /* WingMan Gaming Mouse */ +#define USB_PRODUCT_LOGITECH_BD58 0xc00c /* BD58 mouse */ +#define USB_PRODUCT_LOGITECH_UN58A 0xc030 /* iFeel Mouse */ +#define USB_PRODUCT_LOGITECH_UN53B 0xc032 /* iFeel MouseMan */ +#define USB_PRODUCT_LOGITECH_WMPAD 0xc208 /* WingMan GamePad Extreme */ +#define USB_PRODUCT_LOGITECH_WMRPAD 0xc20a /* WingMan RumblePad */ +#define USB_PRODUCT_LOGITECH_WMJOY 0xc281 /* WingMan Force joystick */ +#define USB_PRODUCT_LOGITECH_BB13 0xc401 /* USB-PS/2 Trackball */ +#define USB_PRODUCT_LOGITECH_RK53 0xc501 /* Cordless mouse */ +#define USB_PRODUCT_LOGITECH_RB6 0xc503 /* Cordless keyboard */ +#define USB_PRODUCT_LOGITECH_MX700 0xc506 /* Cordless optical mouse */ +#define USB_PRODUCT_LOGITECH_QUICKCAMPRO2 0xd001 /* QuickCam Pro */ + +/* Logitec Corp. products */ +#define USB_PRODUCT_LOGITEC_LDR_H443SU2 0x0033 /* DVD Multi-plus unit LDR-H443SU2 */ +#define USB_PRODUCT_LOGITEC_LDR_H443U2 0x00b3 /* DVD Multi-plus unit LDR-H443U2 */ + +/* Lucent products */ +#define USB_PRODUCT_LUCENT_EVALKIT 0x1001 /* USS-720 evaluation kit */ + +/* Luwen products */ +#define USB_PRODUCT_LUWEN_EASYDISK 0x0005 /* EasyDisc */ + +/* Macally products */ +#define USB_PRODUCT_MACALLY_MOUSE1 0x0101 /* mouse */ + +/* MCT Corp. */ +#define USB_PRODUCT_MCT_HUB0100 0x0100 /* Hub */ +#define USB_PRODUCT_MCT_DU_H3SP_USB232 0x0200 /* D-Link DU-H3SP USB BAY Hub */ +#define USB_PRODUCT_MCT_USB232 0x0210 /* USB-232 Interface */ +#define USB_PRODUCT_MCT_SITECOM_USB232 0x0230 /* Sitecom USB-232 Products */ + +/* Melco, Inc products */ +#define USB_PRODUCT_MELCO_LUATX1 0x0001 /* LUA-TX Ethernet */ +#define USB_PRODUCT_MELCO_LUATX5 0x0005 /* LUA-TX Ethernet */ +#define USB_PRODUCT_MELCO_LUA2TX5 0x0009 /* LUA2-TX Ethernet */ +#define USB_PRODUCT_MELCO_LUAKTX 0x0012 /* LUA-KTX Ethernet */ +#define USB_PRODUCT_MELCO_DUBPXXG 0x001c /* USB-IDE Bridge: DUB-PxxG */ +#define USB_PRODUCT_MELCO_LUAU2KTX 0x003d /* LUA-U2-KTX Ethernet */ +#define USB_PRODUCT_MELCO_KG54YB 0x005e /* WLI-U2-KG54-YB WLAN */ +#define USB_PRODUCT_MELCO_KG54 0x0066 /* WLI-U2-KG54 WLAN */ +#define USB_PRODUCT_MELCO_KG54AI 0x0067 /* WLI-U2-KG54-AI WLAN */ +#define USB_PRODUCT_MELCO_NINWIFI 0x008b /* Nintendo Wi-Fi */ +#define USB_PRODUCT_MELCO_PCOPRS1 0x00b3 /* PC-OP-RS1 RemoteStation */ +#define USB_PRODUCT_MELCO_SG54HP 0x00d8 /* WLI-U2-SG54HP */ +#define USB_PRODUCT_MELCO_G54HP 0x00d9 /* WLI-U2-G54HP */ +#define USB_PRODUCT_MELCO_KG54L 0x00da /* WLI-U2-KG54L */ + +/* Merlin products */ +#define USB_PRODUCT_MERLIN_V620 0x1110 /* Merlin V620 */ + +/* MetaGeek products */ +#define USB_PRODUCT_METAGEEK_WISPY1B 0x083e /* MetaGeek Wi-Spy */ +#define USB_PRODUCT_METAGEEK_WISPY24X 0x083f /* MetaGeek Wi-Spy 2.4x */ + +/* Metricom products */ +#define USB_PRODUCT_METRICOM_RICOCHET_GS 0x0001 /* Ricochet GS */ + +/* MGE UPS Systems */ +#define USB_PRODUCT_MGE_UPS1 0x0001 /* MGE UPS SYSTEMS PROTECTIONCENTER 1 */ +#define USB_PRODUCT_MGE_UPS2 0xffff /* MGE UPS SYSTEMS PROTECTIONCENTER 2 */ + +/* Micro Star International products */ +#define USB_PRODUCT_MSI_BT_DONGLE 0x1967 /* Bluetooth USB dongle */ +#define USB_PRODUCT_MSI_UB11B 0x6823 /* UB11B */ +#define USB_PRODUCT_MSI_RT2570 0x6861 /* RT2570 */ +#define USB_PRODUCT_MSI_RT2570_2 0x6865 /* RT2570 */ +#define USB_PRODUCT_MSI_RT2570_3 0x6869 /* RT2570 */ +#define USB_PRODUCT_MSI_RT2573_1 0x6874 /* RT2573 */ +#define USB_PRODUCT_MSI_RT2573_2 0x6877 /* RT2573 */ +#define USB_PRODUCT_MSI_RT2573_3 0xa861 /* RT2573 */ +#define USB_PRODUCT_MSI_RT2573_4 0xa874 /* RT2573 */ + +/* Microdia products */ +#define USB_PRODUCT_MICRODIA_TWINKLECAM 0x600d /* TwinkleCam USB camera */ + +/* Microsoft products */ +#define USB_PRODUCT_MICROSOFT_SIDEPREC 0x0008 /* SideWinder Precision Pro */ +#define USB_PRODUCT_MICROSOFT_INTELLIMOUSE 0x0009 /* IntelliMouse */ +#define USB_PRODUCT_MICROSOFT_NATURALKBD 0x000b /* Natural Keyboard Elite */ +#define USB_PRODUCT_MICROSOFT_DDS80 0x0014 /* Digital Sound System 80 */ +#define USB_PRODUCT_MICROSOFT_SIDEWINDER 0x001a /* Sidewinder Precision Racing Wheel */ +#define USB_PRODUCT_MICROSOFT_INETPRO 0x001c /* Internet Keyboard Pro */ +#define USB_PRODUCT_MICROSOFT_TBEXPLORER 0x0024 /* Trackball Explorer */ +#define USB_PRODUCT_MICROSOFT_INTELLIEYE 0x0025 /* IntelliEye mouse */ +#define USB_PRODUCT_MICROSOFT_INETPRO2 0x002b /* Internet Keyboard Pro */ +#define USB_PRODUCT_MICROSOFT_MN510 0x006e /* MN510 Wireless */ +#define USB_PRODUCT_MICROSOFT_MN110 0x007a /* 10/100 USB NIC */ +#define USB_PRODUCT_MICROSOFT_WLINTELLIMOUSE 0x008c /* Wireless Optical IntelliMouse */ +#define USB_PRODUCT_MICROSOFT_WLNOTEBOOK 0x00b9 /* Wireless Optical Mouse (Model 1023) */ +#define USB_PRODUCT_MICROSOFT_WLNOTEBOOK2 0x00e1 /* Wireless Optical Mouse 3000 (Model 1056) */ +#define USB_PRODUCT_MICROSOFT_WLNOTEBOOK3 0x00d2 /* Wireless Optical Mouse 3000 (Model 1049) */ +#define USB_PRODUCT_MICROSOFT_WLUSBMOUSE 0x00b9 /* Wireless USB Mouse */ +#define USB_PRODUCT_MICROSOFT_XBOX360 0x0292 /* XBOX 360 WLAN */ + +/* Microtech products */ +#define USB_PRODUCT_MICROTECH_SCSIDB25 0x0004 /* USB-SCSI-DB25 */ +#define USB_PRODUCT_MICROTECH_SCSIHD50 0x0005 /* USB-SCSI-HD50 */ +#define USB_PRODUCT_MICROTECH_DPCM 0x0006 /* USB CameraMate */ +#define USB_PRODUCT_MICROTECH_FREECOM 0xfc01 /* Freecom USB-IDE */ + +/* Microtek products */ +#define USB_PRODUCT_MICROTEK_336CX 0x0094 /* Phantom 336CX - C3 scanner */ +#define USB_PRODUCT_MICROTEK_X6U 0x0099 /* ScanMaker X6 - X6U */ +#define USB_PRODUCT_MICROTEK_C6 0x009a /* Phantom C6 scanner */ +#define USB_PRODUCT_MICROTEK_336CX2 0x00a0 /* Phantom 336CX - C3 scanner */ +#define USB_PRODUCT_MICROTEK_V6USL 0x00a3 /* ScanMaker V6USL */ +#define USB_PRODUCT_MICROTEK_V6USL2 0x80a3 /* ScanMaker V6USL */ +#define USB_PRODUCT_MICROTEK_V6UL 0x80ac /* ScanMaker V6UL */ + +/* Microtune, Inc. products */ +#define USB_PRODUCT_MICROTUNE_BT_DONGLE 0x1000 /* Bluetooth USB dongle */ + +/* Midiman products */ +#define USB_PRODUCT_MIDIMAN_MIDISPORT2X2 0x1001 /* Midisport 2x2 */ + +/* MindsAtWork products */ +#define USB_PRODUCT_MINDSATWORK_WALLET 0x0001 /* Digital Wallet */ + +/* Minolta Co., Ltd. */ +#define USB_PRODUCT_MINOLTA_2300 0x4001 /* Dimage 2300 */ +#define USB_PRODUCT_MINOLTA_S304 0x4007 /* Dimage S304 */ +#define USB_PRODUCT_MINOLTA_X 0x4009 /* Dimage X */ +#define USB_PRODUCT_MINOLTA_5400 0x400e /* Dimage 5400 */ +#define USB_PRODUCT_MINOLTA_F300 0x4011 /* Dimage F300 */ +#define USB_PRODUCT_MINOLTA_E223 0x4017 /* Dimage E223 */ + +/* Mitsumi products */ +#define USB_PRODUCT_MITSUMI_CDRRW 0x0000 /* CD-R/RW Drive */ +#define USB_PRODUCT_MITSUMI_BT_DONGLE 0x641f /* Bluetooth USB dongle */ +#define USB_PRODUCT_MITSUMI_FDD 0x6901 /* USB FDD */ + +/* Mobility products */ +#define USB_PRODUCT_MOBILITY_EA 0x0204 /* Ethernet */ +#define USB_PRODUCT_MOBILITY_EASIDOCK 0x0304 /* EasiDock Ethernet */ + +/* MosChip products */ +#define USB_PRODUCT_MOSCHIP_MCS7703 0x7703 /* MCS7703 Serial Port Adapter */ +#define USB_PRODUCT_MOSCHIP_MCS7830 0x7830 /* MCS7830 Ethernet */ + +/* Motorola products */ +#define USB_PRODUCT_MOTOROLA_MC141555 0x1555 /* MC141555 hub controller */ +#define USB_PRODUCT_MOTOROLA_SB4100 0x4100 /* SB4100 USB Cable Modem */ +#define USB_PRODUCT_MOTOROLA2_A41XV32X 0x2a22 /* A41x/V32x Mobile Phones */ +#define USB_PRODUCT_MOTOROLA2_E398 0x4810 /* E398 Mobile Phone */ +#define USB_PRODUCT_MOTOROLA2_USBLAN 0x600c /* USBLAN */ +#define USB_PRODUCT_MOTOROLA2_USBLAN2 0x6027 /* USBLAN */ + +/* MultiTech products */ +#define USB_PRODUCT_MULTITECH_ATLAS 0xf101 /* MT5634ZBA-USB modem */ + +/* Mustek products */ +#define USB_PRODUCT_MUSTEK_1200CU 0x0001 /* 1200 CU scanner */ +#define USB_PRODUCT_MUSTEK_600CU 0x0002 /* 600 CU scanner */ +#define USB_PRODUCT_MUSTEK_1200USB 0x0003 /* 1200 USB scanner */ +#define USB_PRODUCT_MUSTEK_1200UB 0x0006 /* 1200 UB scanner */ +#define USB_PRODUCT_MUSTEK_1200USBPLUS 0x0007 /* 1200 USB Plus scanner */ +#define USB_PRODUCT_MUSTEK_1200CUPLUS 0x0008 /* 1200 CU Plus scanner */ +#define USB_PRODUCT_MUSTEK_BEARPAW1200F 0x0010 /* BearPaw 1200F scanner */ +#define USB_PRODUCT_MUSTEK_BEARPAW1200TA 0x021e /* BearPaw 1200TA scanner */ +#define USB_PRODUCT_MUSTEK_600USB 0x0873 /* 600 USB scanner */ +#define USB_PRODUCT_MUSTEK_MDC800 0xa800 /* MDC-800 digital camera */ + +/* M-Systems products */ +#define USB_PRODUCT_MSYSTEMS_DISKONKEY 0x0010 /* DiskOnKey */ +#define USB_PRODUCT_MSYSTEMS_DISKONKEY2 0x0011 /* DiskOnKey */ + +/* Myson products */ +#define USB_PRODUCT_MYSON_HEDEN 0x8818 /* USB-IDE */ + +/* National Semiconductor */ +#define USB_PRODUCT_NATIONAL_BEARPAW1200 0x1000 /* BearPaw 1200 */ +#define USB_PRODUCT_NATIONAL_BEARPAW2400 0x1001 /* BearPaw 2400 */ + +/* NEC products */ +#define USB_PRODUCT_NEC_HUB 0x55aa /* hub */ +#define USB_PRODUCT_NEC_HUB_B 0x55ab /* hub */ + +/* NEODIO products */ +#define USB_PRODUCT_NEODIO_ND3260 0x3260 /* 8-in-1 Multi-format Flash Controller */ +#define USB_PRODUCT_NEODIO_ND5010 0x5010 /* Multi-format Flash Controller */ + +/* Netac products */ +#define USB_PRODUCT_NETAC_CF_CARD 0x1060 /* USB-CF-Card */ +#define USB_PRODUCT_NETAC_ONLYDISK 0x0003 /* OnlyDisk */ + +/* NetChip Technology Products */ +#define USB_PRODUCT_NETCHIP_TURBOCONNECT 0x1080 /* Turbo-Connect */ +#define USB_PRODUCT_NETCHIP_CLIK_40 0xa140 /* USB Clik! 40 */ +#define USB_PRODUCT_NETCHIP_ETHERNETGADGET 0xa4a2 /* Linux Ethernet/RNDIS gadget on pxa210/25x/26x */ + +/* Netgear products */ +#define USB_PRODUCT_NETGEAR_EA101 0x1001 /* Ethernet */ +#define USB_PRODUCT_NETGEAR_EA101X 0x1002 /* Ethernet */ +#define USB_PRODUCT_NETGEAR_FA101 0x1020 /* Ethernet 10/100, USB1.1 */ +#define USB_PRODUCT_NETGEAR_FA120 0x1040 /* USB 2.0 Ethernet */ +#define USB_PRODUCT_NETGEAR_WG111V2_2 0x4240 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_NETGEAR_WG111U 0x4300 /* WG111U */ +#define USB_PRODUCT_NETGEAR_WG111U_NF 0x4301 /* WG111U (no firmware) */ +#define USB_PRODUCT_NETGEAR2_MA101 0x4100 /* MA101 */ +#define USB_PRODUCT_NETGEAR2_MA101B 0x4102 /* MA101 Rev B */ +#define USB_PRODUCT_NETGEAR3_WG111T 0x4250 /* WG111T */ +#define USB_PRODUCT_NETGEAR3_WG111T_NF 0x4251 /* WG111T (no firmware) */ +#define USB_PRODUCT_NETGEAR3_WPN111 0x5f00 /* WPN111 */ +#define USB_PRODUCT_NETGEAR3_WPN111_NF 0x5f01 /* WPN111 (no firmware) */ + +/* Nikon products */ +#define USB_PRODUCT_NIKON_E990 0x0102 /* Digital Camera E990 */ +#define USB_PRODUCT_NIKON_LS40 0x4000 /* CoolScan LS40 ED */ +#define USB_PRODUCT_NIKON_D300 0x041a /* Digital Camera D300 */ + +/* NovaTech Products */ +#define USB_PRODUCT_NOVATECH_NV902 0x9020 /* NovaTech NV-902W */ +#define USB_PRODUCT_NOVATECH_RT2573 0x9021 /* RT2573 */ + +/* Novatel Wireless products */ +#define USB_PRODUCT_NOVATEL_V640 0x1100 /* Merlin V620 */ +#define USB_PRODUCT_NOVATEL_CDMA_MODEM 0x1110 /* Novatel Wireless Merlin CDMA */ +#define USB_PRODUCT_NOVATEL_V620 0x1110 /* Merlin V620 */ +#define USB_PRODUCT_NOVATEL_V740 0x1120 /* Merlin V740 */ +#define USB_PRODUCT_NOVATEL_V720 0x1130 /* Merlin V720 */ +#define USB_PRODUCT_NOVATEL_U740 0x1400 /* Merlin U740 */ +#define USB_PRODUCT_NOVATEL_U740_2 0x1410 /* Merlin U740 */ +#define USB_PRODUCT_NOVATEL_U870 0x1420 /* Merlin U870 */ +#define USB_PRODUCT_NOVATEL_XU870 0x1430 /* Merlin XU870 */ +#define USB_PRODUCT_NOVATEL_X950D 0x1450 /* Merlin X950D */ +#define USB_PRODUCT_NOVATEL_ES620 0x2100 /* ES620 CDMA */ +#define USB_PRODUCT_NOVATEL_U720 0x2110 /* Merlin U720 */ +#define USB_PRODUCT_NOVATEL_U727 0x4100 /* Merlin U727 CDMA */ +#define USB_PRODUCT_NOVATEL_U950D 0x4400 /* Novatel MC950D HSUPA */ +#define USB_PRODUCT_NOVATEL_ZEROCD 0x5010 /* Novatel ZeroCD */ +#define USB_PRODUCT_NOVATEL2_FLEXPACKGPS 0x0100 /* NovAtel FlexPack GPS receiver */ + +/* Merlin products */ +#define USB_PRODUCT_MERLIN_V620 0x1110 /* Merlin V620 */ + +/* Olympus products */ +#define USB_PRODUCT_OLYMPUS_C1 0x0102 /* C-1 Digital Camera */ +#define USB_PRODUCT_OLYMPUS_C700 0x0105 /* C-700 Ultra Zoom */ + +/* OmniVision Technologies, Inc. products */ +#define USB_PRODUCT_OMNIVISION_OV511 0x0511 /* OV511 Camera */ +#define USB_PRODUCT_OMNIVISION_OV511PLUS 0xa511 /* OV511+ Camera */ + +/* OnSpec Electronic, Inc. */ +#define USB_PRODUCT_ONSPEC_MDCFE_B_CF_READER 0xa000 /* MDCFE-B USB CF Reader */ +#define USB_PRODUCT_ONSPEC_CFMS_RW 0xa001 /* SIIG/Datafab Memory Stick+CF Reader/Writer */ +#define USB_PRODUCT_ONSPEC_READER 0xa003 /* Datafab-based Reader */ +#define USB_PRODUCT_ONSPEC_CFSM_READER 0xa005 /* PNY/Datafab CF+SM Reader */ +#define USB_PRODUCT_ONSPEC_CFSM_READER2 0xa006 /* Simple Tech/Datafab CF+SM Reader */ +#define USB_PRODUCT_ONSPEC_MDSM_B_READER 0xa103 /* MDSM-B reader */ +#define USB_PRODUCT_ONSPEC_CFSM_COMBO 0xa109 /* USB to CF + SM Combo (LC1) */ +#define USB_PRODUCT_ONSPEC_UCF100 0xa400 /* FlashLink UCF-100 CompactFlash Reader */ +#define USB_PRODUCT_ONSPEC2_IMAGEMATE_SDDR55 0xa103 /* ImageMate SDDR55 */ + +/* Option products */ +#define USB_PRODUCT_OPTION_VODAFONEMC3G 0x5000 /* Vodafone Mobile Connect 3G datacard */ +#define USB_PRODUCT_OPTION_GT3G 0x6000 /* GlobeTrotter 3G datacard */ +#define USB_PRODUCT_OPTION_GT3GQUAD 0x6300 /* GlobeTrotter 3G QUAD datacard */ +#define USB_PRODUCT_OPTION_GT3GPLUS 0x6600 /* GlobeTrotter 3G+ datacard */ +#define USB_PRODUCT_OPTION_GTMAX36 0x6701 /* GlobeTrotter Max 3.6 Modem */ + +/* OQO */ +#define USB_PRODUCT_OQO_WIFI01 0x0002 /* model 01 WiFi interface */ +#define USB_PRODUCT_OQO_BT01 0x0003 /* model 01 Bluetooth interface */ +#define USB_PRODUCT_OQO_ETHER01PLUS 0x7720 /* model 01+ Ethernet */ +#define USB_PRODUCT_OQO_ETHER01 0x8150 /* model 01 Ethernet interface */ + +/* Palm Computing, Inc. product */ +#define USB_PRODUCT_PALM_SERIAL 0x0080 /* USB Serial */ +#define USB_PRODUCT_PALM_M500 0x0001 /* Palm m500 */ +#define USB_PRODUCT_PALM_M505 0x0002 /* Palm m505 */ +#define USB_PRODUCT_PALM_M515 0x0003 /* Palm m515 */ +#define USB_PRODUCT_PALM_I705 0x0020 /* Palm i705 */ +#define USB_PRODUCT_PALM_TUNGSTEN_Z 0x0031 /* Palm Tungsten Z */ +#define USB_PRODUCT_PALM_M125 0x0040 /* Palm m125 */ +#define USB_PRODUCT_PALM_M130 0x0050 /* Palm m130 */ +#define USB_PRODUCT_PALM_TUNGSTEN_T 0x0060 /* Palm Tungsten T */ +#define USB_PRODUCT_PALM_ZIRE31 0x0061 /* Palm Zire 31 */ +#define USB_PRODUCT_PALM_ZIRE 0x0070 /* Palm Zire */ + +/* Panasonic products */ +#define USB_PRODUCT_PANASONIC_LS120CAM 0x0901 /* LS-120 Camera */ +#define USB_PRODUCT_PANASONIC_KXL840AN 0x0d01 /* CD-R Drive KXL-840AN */ +#define USB_PRODUCT_PANASONIC_KXLRW32AN 0x0d09 /* CD-R Drive KXL-RW32AN */ +#define USB_PRODUCT_PANASONIC_KXLCB20AN 0x0d0a /* CD-R Drive KXL-CB20AN */ +#define USB_PRODUCT_PANASONIC_KXLCB35AN 0x0d0e /* DVD-ROM & CD-R/RW */ +#define USB_PRODUCT_PANASONIC_SDCAAE 0x1b00 /* MultiMediaCard */ + +/* Peracom products */ +#define USB_PRODUCT_PERACOM_SERIAL1 0x0001 /* Serial */ +#define USB_PRODUCT_PERACOM_ENET 0x0002 /* Ethernet */ +#define USB_PRODUCT_PERACOM_ENET3 0x0003 /* At Home Ethernet */ +#define USB_PRODUCT_PERACOM_ENET2 0x0005 /* Ethernet */ + +/* Philips products */ +#define USB_PRODUCT_PHILIPS_DSS350 0x0101 /* DSS 350 Digital Speaker System */ +#define USB_PRODUCT_PHILIPS_DSS 0x0104 /* DSS XXX Digital Speaker System */ +#define USB_PRODUCT_PHILIPS_HUB 0x0201 /* hub */ +#define USB_PRODUCT_PHILIPS_PCA646VC 0x0303 /* PCA646VC PC Camera */ +#define USB_PRODUCT_PHILIPS_PCVC680K 0x0308 /* PCVC680K Vesta Pro PC Camera */ +#define USB_PRODUCT_PHILIPS_DSS150 0x0471 /* DSS 150 Digital Speaker System */ +#define USB_PRODUCT_PHILIPS_SNU5600 0x1236 /* SNU5600 */ +#define USB_PRODUCT_PHILIPS_UM10016 0x1552 /* ISP 1581 Hi-Speed USB MPEG2 Encoder Reference Kit */ +#define USB_PRODUCT_PHILIPS_DIVAUSB 0x1801 /* DIVA USB mp3 player */ + +/* Philips Semiconductor products */ +#define USB_PRODUCT_PHILIPSSEMI_HUB1122 0x1122 /* hub */ + +/* P.I. Engineering products */ +#define USB_PRODUCT_PIENGINEERING_PS2USB 0x020b /* PS2 to Mac USB Adapter */ + +/* Planex Communications products */ +#define USB_PRODUCT_PLANEX_GW_US11H 0x14ea /* GW-US11H WLAN */ +#define USB_PRODUCT_PLANEX2_GW_US11S 0x3220 /* GW-US11S WLAN */ +#define USB_PRODUCT_PLANEX2_GW_US54GXS 0x5303 /* GW-US54GXS WLAN */ +#define USB_PRODUCT_PLANEX2_GWUS54HP 0xab01 /* GW-US54HP */ +#define USB_PRODUCT_PLANEX2_GWUS54MINI2 0xab50 /* GW-US54Mini2 */ +#define USB_PRODUCT_PLANEX2_GWUS54SG 0xc002 /* GW-US54SG */ +#define USB_PRODUCT_PLANEX2_GWUS54GZL 0xc007 /* GW-US54GZL */ +#define USB_PRODUCT_PLANEX2_GWUS54GD 0xed01 /* GW-US54GD */ +#define USB_PRODUCT_PLANEX2_GWUSMM 0xed02 /* GW-USMM */ +#define USB_PRODUCT_PLANEX3_GWUS54GZ 0xab10 /* GW-US54GZ */ +#define USB_PRODUCT_PLANEX3_GU1000T 0xab11 /* GU-1000T */ +#define USB_PRODUCT_PLANEX3_GWUS54MINI 0xab13 /* GW-US54Mini */ + +/* Plextor Corp. */ +#define USB_PRODUCT_PLEXTOR_40_12_40U 0x0011 /* PlexWriter 40/12/40U */ + +/* PLX products */ +#define USB_PRODUCT_PLX_TESTBOARD 0x9060 /* test board */ + +/* PNY products */ +#define USB_PRODUCT_PNY_ATTACHE2 0x0010 /* USB 2.0 Flash Drive */ + +/* PortGear products */ +#define USB_PRODUCT_PORTGEAR_EA8 0x0008 /* Ethernet */ +#define USB_PRODUCT_PORTGEAR_EA9 0x0009 /* Ethernet */ + +/* Portsmith products */ +#define USB_PRODUCT_PORTSMITH_EEA 0x3003 /* Express Ethernet */ + +/* Primax products */ +#define USB_PRODUCT_PRIMAX_G2X300 0x0300 /* G2-200 scanner */ +#define USB_PRODUCT_PRIMAX_G2E300 0x0301 /* G2E-300 scanner */ +#define USB_PRODUCT_PRIMAX_G2300 0x0302 /* G2-300 scanner */ +#define USB_PRODUCT_PRIMAX_G2E3002 0x0303 /* G2E-300 scanner */ +#define USB_PRODUCT_PRIMAX_9600 0x0340 /* Colorado USB 9600 scanner */ +#define USB_PRODUCT_PRIMAX_600U 0x0341 /* Colorado 600u scanner */ +#define USB_PRODUCT_PRIMAX_6200 0x0345 /* Visioneer 6200 scanner */ +#define USB_PRODUCT_PRIMAX_19200 0x0360 /* Colorado USB 19200 scanner */ +#define USB_PRODUCT_PRIMAX_1200U 0x0361 /* Colorado 1200u scanner */ +#define USB_PRODUCT_PRIMAX_G600 0x0380 /* G2-600 scanner */ +#define USB_PRODUCT_PRIMAX_636I 0x0381 /* ReadyScan 636i */ +#define USB_PRODUCT_PRIMAX_G2600 0x0382 /* G2-600 scanner */ +#define USB_PRODUCT_PRIMAX_G2E600 0x0383 /* G2E-600 scanner */ +#define USB_PRODUCT_PRIMAX_COMFORT 0x4d01 /* Comfort */ +#define USB_PRODUCT_PRIMAX_MOUSEINABOX 0x4d02 /* Mouse-in-a-Box */ +#define USB_PRODUCT_PRIMAX_PCGAUMS1 0x4d04 /* Sony PCGA-UMS1 */ + +/* Prolific products */ +#define USB_PRODUCT_PROLIFIC_PL2301 0x0000 /* PL2301 Host-Host interface */ +#define USB_PRODUCT_PROLIFIC_PL2302 0x0001 /* PL2302 Host-Host interface */ +#define USB_PRODUCT_PROLIFIC_RSAQ2 0x04bb /* PL2303 Serial (IODATA USB-RSAQ2) */ +#define USB_PRODUCT_PROLIFIC_PL2303 0x2303 /* PL2303 Serial (ATEN/IOGEAR UC232A) */ +#define USB_PRODUCT_PROLIFIC_PL2305 0x2305 /* Parallel printer */ +#define USB_PRODUCT_PROLIFIC_ATAPI4 0x2307 /* ATAPI-4 Controller */ +#define USB_PRODUCT_PROLIFIC_PL2501 0x2501 /* PL2501 Host-Host interface */ +#define USB_PRODUCT_PROLIFIC_PHAROS 0xaaa0 /* Prolific Pharos */ +#define USB_PRODUCT_PROLIFIC_RSAQ3 0xaaa2 /* PL2303 Serial Adapter (IODATA USB-RSAQ3) */ +#define USB_PRODUCT_PROLIFIC2_WSIM 0x2001 /* Willcom WSIM */ + +/* Putercom products */ +#define USB_PRODUCT_PUTERCOM_UPA100 0x047e /* USB-1284 BRIDGE */ + +/* Qcom products */ +#define USB_PRODUCT_QCOM_RT2573 0x6196 /* RT2573 */ +#define USB_PRODUCT_QCOM_RT2573_2 0x6229 /* RT2573 */ + +/* Qualcomm products */ +#define USB_PRODUCT_QUALCOMM_CDMA_MSM 0x6000 /* CDMA Technologies MSM phone */ +#define USB_PRODUCT_QUALCOMM2_RWT_FCT 0x3100 /* RWT FCT-CDMA 2000 1xRTT modem */ +#define USB_PRODUCT_QUALCOMM2_CDMA_MSM 0x3196 /* CDMA Technologies MSM modem */ +#define USB_PRODUCT_QUALCOMMINC_CDMA_MSM 0x0001 /* CDMA Technologies MSM modem */ + +/* Qtronix products */ +#define USB_PRODUCT_QTRONIX_980N 0x2011 /* Scorpion-980N keyboard */ + +/* Quickshot products */ +#define USB_PRODUCT_QUICKSHOT_STRIKEPAD 0x6238 /* USB StrikePad */ + +/* Radio Shack */ +#define USB_PRODUCT_RADIOSHACK_USBCABLE 0x4026 /* USB to Serial Cable */ + +/* Rainbow Technologies products */ +#define USB_PRODUCT_RAINBOW_IKEY2000 0x1200 /* i-Key 2000 */ + +/* Ralink Technology products */ +#define USB_PRODUCT_RALINK_RT2570 0x1706 /* RT2500USB Wireless Adapter */ +#define USB_PRODUCT_RALINK_RT2570_2 0x2570 /* RT2500USB Wireless Adapter */ +#define USB_PRODUCT_RALINK_RT2573 0x2573 /* RT2501USB Wireless Adapter */ +#define USB_PRODUCT_RALINK_RT2671 0x2671 /* RT2601USB Wireless Adapter */ +#define USB_PRODUCT_RALINK_RT2570_3 0x9020 /* RT2500USB Wireless Adapter */ +#define USB_PRODUCT_RALINK_RT2573_2 0x9021 /* RT2501USB Wireless Adapter */ + +/* ReakTek products */ +/* Green House and CompUSA OEM this part */ +#define USB_PRODUCT_REALTEK_USBKR100 0x8150 /* USBKR100 USB Ethernet */ + +/* Ricoh products */ +#define USB_PRODUCT_RICOH_VGPVCC2 0x1830 /* VGP-VCC2 Camera */ +#define USB_PRODUCT_RICOH_VGPVCC3 0x1832 /* VGP-VCC3 Camera */ +#define USB_PRODUCT_RICOH_VGPVCC2_2 0x1833 /* VGP-VCC2 Camera */ +#define USB_PRODUCT_RICOH_VGPVCC2_3 0x1834 /* VGP-VCC2 Camera */ +#define USB_PRODUCT_RICOH_VGPVCC7 0x183a /* VGP-VCC7 Camera */ +#define USB_PRODUCT_RICOH_VGPVCC8 0x183b /* VGP-VCC8 Camera */ + +/* Roland products */ +#define USB_PRODUCT_ROLAND_UM1 0x0009 /* UM-1 MIDI I/F */ +#define USB_PRODUCT_ROLAND_UM880N 0x0014 /* EDIROL UM-880 MIDI I/F (native) */ +#define USB_PRODUCT_ROLAND_UM880G 0x0015 /* EDIROL UM-880 MIDI I/F (generic) */ + +/* Rockfire products */ +#define USB_PRODUCT_ROCKFIRE_GAMEPAD 0x2033 /* gamepad 203USB */ + +/* RATOC Systems products */ +#define USB_PRODUCT_RATOC_REXUSB60 0xb000 /* REX-USB60 */ + +/* Sagem products */ +#define USB_PRODUCT_SAGEM_USBSERIAL 0x0027 /* USB-Serial Controller */ +#define USB_PRODUCT_SAGEM_XG760A 0x004a /* XG-760A */ +#define USB_PRODUCT_SAGEM_XG76NA 0x0062 /* XG-76NA */ + +/* Samsung products */ +#define USB_PRODUCT_SAMSUNG_ML6060 0x3008 /* ML-6060 laser printer */ +#define USB_PRODUCT_SAMSUNG_YP_U2 0x5050 /* YP-U2 MP3 Player */ +#define USB_PRODUCT_SAMSUNG_I500 0x6601 /* I500 Palm USB Phone */ + +/* Samsung Techwin products */ +#define USB_PRODUCT_SAMSUNG_TECHWIN_DIGIMAX_410 0x000a /* Digimax 410 */ + +/* SanDisk products */ +#define USB_PRODUCT_SANDISK_SDDR05A 0x0001 /* ImageMate SDDR-05a */ +#define USB_PRODUCT_SANDISK_SDDR31 0x0002 /* ImageMate SDDR-31 */ +#define USB_PRODUCT_SANDISK_SDDR05 0x0005 /* ImageMate SDDR-05 */ +#define USB_PRODUCT_SANDISK_SDDR12 0x0100 /* ImageMate SDDR-12 */ +#define USB_PRODUCT_SANDISK_SDDR09 0x0200 /* ImageMate SDDR-09 */ +#define USB_PRODUCT_SANDISK_SDDR75 0x0810 /* ImageMate SDDR-75 */ +#define USB_PRODUCT_SANDISK_SDCZ2_256 0x7104 /* Cruzer Mini 256MB */ +#define USB_PRODUCT_SANDISK_SDCZ4_128 0x7112 /* Cruzer Micro 128MB */ +#define USB_PRODUCT_SANDISK_SDCZ4_256 0x7113 /* Cruzer Micro 256MB */ + +/* Sanyo Electric products */ +#define USB_PRODUCT_SANYO_SCP4900 0x0701 /* Sanyo SCP-4900 USB Phone */ + +/* ScanLogic products */ +#define USB_PRODUCT_SCANLOGIC_SL11R 0x0002 /* SL11R IDE Adapter */ +#define USB_PRODUCT_SCANLOGIC_336CX 0x0300 /* Phantom 336CX - C3 scanner */ + +/* Senao products */ +#define USB_PRODUCT_SENAO_NUB8301 0x2000 /* NUB-8301 */ + +/* ShanTou products */ +#define USB_PRODUCT_SHANTOU_ST268 0x0268 /* ST268 */ +#define USB_PRODUCT_SHANTOU_DM9601 0x9601 /* DM 9601 */ + +/* Shark products */ +#define USB_PRODUCT_SHARK_PA 0x0400 /* Pocket Adapter */ + +/* Sharp products */ +#define USB_PRODUCT_SHARP_SL5500 0x8004 /* Zaurus SL-5500 PDA */ +#define USB_PRODUCT_SHARP_SLA300 0x8005 /* Zaurus SL-A300 PDA */ +#define USB_PRODUCT_SHARP_SL5600 0x8006 /* Zaurus SL-5600 PDA */ +#define USB_PRODUCT_SHARP_SLC700 0x8007 /* Zaurus SL-C700 PDA */ +#define USB_PRODUCT_SHARP_SLC750 0x9031 /* Zaurus SL-C750 PDA */ +#define USB_PRODUCT_SHARP_WZERO3ES 0x9123 /* W-ZERO3 ES Smartphone */ + +/* Shuttle Technology products */ +#define USB_PRODUCT_SHUTTLE_EUSB 0x0001 /* E-USB Bridge */ +#define USB_PRODUCT_SHUTTLE_EUSCSI 0x0002 /* eUSCSI Bridge */ +#define USB_PRODUCT_SHUTTLE_SDDR09 0x0003 /* ImageMate SDDR09 */ +#define USB_PRODUCT_SHUTTLE_EUSBCFSM 0x0005 /* eUSB SmartMedia / CompactFlash Adapter */ +#define USB_PRODUCT_SHUTTLE_ZIOMMC 0x0006 /* eUSB MultiMediaCard Adapter */ +#define USB_PRODUCT_SHUTTLE_HIFD 0x0007 /* Sony Hifd */ +#define USB_PRODUCT_SHUTTLE_EUSBATAPI 0x0009 /* eUSB ATA/ATAPI Adapter */ +#define USB_PRODUCT_SHUTTLE_CF 0x000a /* eUSB CompactFlash Adapter */ +#define USB_PRODUCT_SHUTTLE_EUSCSI_B 0x000b /* eUSCSI Bridge */ +#define USB_PRODUCT_SHUTTLE_EUSCSI_C 0x000c /* eUSCSI Bridge */ +#define USB_PRODUCT_SHUTTLE_CDRW 0x0101 /* CD-RW Device */ +#define USB_PRODUCT_SHUTTLE_EUSBORCA 0x0325 /* eUSB ORCA Quad Reader */ + +/* Siemens products */ +#define USB_PRODUCT_SIEMENS_SPEEDSTREAM 0x1001 /* SpeedStream */ +#define USB_PRODUCT_SIEMENS_SPEEDSTREAM22 0x1022 /* SpeedStream 1022 */ +#define USB_PRODUCT_SIEMENS2_WLL013 0x001b /* WLL013 */ +#define USB_PRODUCT_SIEMENS2_ES75 0x0034 /* GSM module MC35 */ +#define USB_PRODUCT_SIEMENS2_WL54G 0x3c06 /* 54g USB Network Adapter */ +#define USB_PRODUCT_SIEMENS3_SX1 0x0001 /* SX1 */ +#define USB_PRODUCT_SIEMENS3_X65 0x0003 /* X65 */ +#define USB_PRODUCT_SIEMENS3_X75 0x0004 /* X75 */ + +/* Sierra Wireless products */ +#define USB_PRODUCT_SIERRA_AIRCARD580 0x0112 /* Sierra Wireless AirCard 580 */ +#define USB_PRODUCT_SIERRA_AIRCARD595 0x0019 /* Sierra Wireless AirCard 595 */ +#define USB_PRODUCT_SIERRA_AC595U 0x0120 /* Sierra Wireless AirCard 595U */ +#define USB_PRODUCT_SIERRA_AC597E 0x0021 /* Sierra Wireless AirCard 597E */ +#define USB_PRODUCT_SIERRA_C597 0x0023 /* Sierra Wireless Compass 597 */ +#define USB_PRODUCT_SIERRA_AC880 0x6850 /* Sierra Wireless AirCard 880 */ +#define USB_PRODUCT_SIERRA_AC881 0x6851 /* Sierra Wireless AirCard 881 */ +#define USB_PRODUCT_SIERRA_AC880E 0x6852 /* Sierra Wireless AirCard 880E */ +#define USB_PRODUCT_SIERRA_AC881E 0x6853 /* Sierra Wireless AirCard 881E */ +#define USB_PRODUCT_SIERRA_AC880U 0x6855 /* Sierra Wireless AirCard 880U */ +#define USB_PRODUCT_SIERRA_AC881U 0x6856 /* Sierra Wireless AirCard 881U */ +#define USB_PRODUCT_SIERRA_EM5625 0x0017 /* EM5625 */ +#define USB_PRODUCT_SIERRA_MC5720 0x0218 /* MC5720 Wireless Modem */ +#define USB_PRODUCT_SIERRA_MC5720_2 0x0018 /* MC5720 */ +#define USB_PRODUCT_SIERRA_MC5725 0x0020 /* MC5725 */ +#define USB_PRODUCT_SIERRA_MINI5725 0x0220 /* Sierra Wireless miniPCI 5275 */ +#define USB_PRODUCT_SIERRA_MC8755_2 0x6802 /* MC8755 */ +#define USB_PRODUCT_SIERRA_MC8765 0x6803 /* MC8765 */ +#define USB_PRODUCT_SIERRA_MC8755 0x6804 /* MC8755 */ +#define USB_PRODUCT_SIERRA_AC875U 0x6812 /* AC875U HSDPA USB Modem */ +#define USB_PRODUCT_SIERRA_MC8755_3 0x6813 /* MC8755 HSDPA */ +#define USB_PRODUCT_SIERRA_MC8775_2 0x6815 /* MC8775 */ +#define USB_PRODUCT_SIERRA_AIRCARD875 0x6820 /* Aircard 875 HSDPA */ +#define USB_PRODUCT_SIERRA_MC8780 0x6832 /* MC8780 */ +#define USB_PRODUCT_SIERRA_MC8781 0x6833 /* MC8781 */ +#define USB_PRODUCT_SIERRA_TRUINSTALL 0x0fff /* Aircard Tru Installer */ + +/* Sigmatel products */ +#define USB_PRODUCT_SIGMATEL_I_BEAD100 0x8008 /* i-Bead 100 MP3 Player */ + +/* SIIG products */ +/* Also: Omnidirectional Control Technology products */ +#define USB_PRODUCT_SIIG_DIGIFILMREADER 0x0004 /* DigiFilm-Combo Reader */ +#define USB_PRODUCT_SIIG_WINTERREADER 0x0330 /* WINTERREADER Reader */ +#define USB_PRODUCT_SIIG2_USBTOETHER 0x0109 /* USB TO Ethernet */ +#define USB_PRODUCT_SIIG2_US2308 0x0421 /* Serial */ + +/* Silicom products */ +#define USB_PRODUCT_SILICOM_U2E 0x0001 /* U2E */ +#define USB_PRODUCT_SILICOM_GPE 0x0002 /* Psion Gold Port Ethernet */ + +/* SI Labs */ +#define USB_PRODUCT_SILABS_POLOLU 0x803b /* Pololu Serial */ +#define USB_PRODUCT_SILABS_ARGUSISP 0x8066 /* Argussoft ISP */ +#define USB_PRODUCT_SILABS_CRUMB128 0x807a /* Crumb128 board */ +#define USB_PRODUCT_SILABS_DEGREE 0x80ca /* Degree Controls Inc */ +#define USB_PRODUCT_SILABS_TRAQMATE 0x80ed /* Track Systems Traqmate */ +#define USB_PRODUCT_SILABS_SUUNTO 0x80f6 /* Suunto Sports Instrument */ +#define USB_PRODUCT_SILABS_BURNSIDE 0x813d /* Burnside Telecon Deskmobile */ +#define USB_PRODUCT_SILABS_HELICOM 0x815e /* Helicomm IP-Link 1220-DVM */ +#define USB_PRODUCT_SILABS_CP2102 0xea60 /* SILABS USB UART */ +#define USB_PRODUCT_SILABS_LIPOWSKY_JTAG 0x81c8 /* Lipowsky Baby-JTAG */ +#define USB_PRODUCT_SILABS_LIPOWSKY_LIN 0x81e2 /* Lipowsky Baby-LIN */ +#define USB_PRODUCT_SILABS_LIPOWSKY_HARP 0x8218 /* Lipowsky HARP-1 */ +#define USB_PRODUCT_SILABS_CP2102 0xea60 /* SILABS USB UARTa */ +#define USB_PRODUCT_SILABS_CP210X_2 0xea61 /* CP210x Serial */ +#define USB_PRODUCT_SILABS2_DCU11CLONE 0xaa26 /* DCU-11 clone */ + +/* Silicon Portals Inc. */ +#define USB_PRODUCT_SILICONPORTALS_YAPPH_NF 0x0200 /* YAP Phone (no firmware) */ +#define USB_PRODUCT_SILICONPORTALS_YAPPHONE 0x0201 /* YAP Phone */ + +/* Sirius Technologies products */ +#define USB_PRODUCT_SIRIUS_ROADSTER 0x0001 /* NetComm Roadster II 56 USB */ + +/* Sitecom products */ +#define USB_PRODUCT_SITECOM_LN029 0x182d /* USB 2.0 Ethernet */ +#define USB_PRODUCT_SITECOM_SERIAL 0x2068 /* USB to serial cable (v2) */ +#define USB_PRODUCT_SITECOM2_WL022 0x182d /* WL-022 */ + +/* Sitecom Europe products */ +#define USB_PRODUCT_SITECOMEU_LN028 0x061c /* LN-028 */ +#define USB_PRODUCT_SITECOMEU_WL113 0x9071 /* WL-113 */ +#define USB_PRODUCT_SITECOMEU_ZD1211B 0x9075 /* ZD1211B */ +#define USB_PRODUCT_SITECOMEU_WL172 0x90ac /* WL-172 */ +#define USB_PRODUCT_SITECOMEU_WL113R2 0x9712 /* WL-113 rev 2 */ + +/* Skanhex Technology products */ +#define USB_PRODUCT_SKANHEX_MD_7425 0x410a /* MD 7425 Camera */ +#define USB_PRODUCT_SKANHEX_SX_520Z 0x5200 /* SX 520z Camera */ + +/* SmartBridges products */ +#define USB_PRODUCT_SMARTBRIDGES_SMARTLINK 0x0001 /* SmartLink USB Ethernet */ +#define USB_PRODUCT_SMARTBRIDGES_SMARTNIC 0x0003 /* smartNIC 2 PnP Ethernet */ + +/* SMC products */ +#define USB_PRODUCT_SMC_2102USB 0x0100 /* 10Mbps Ethernet */ +#define USB_PRODUCT_SMC_2202USB 0x0200 /* 10/100 Ethernet */ +#define USB_PRODUCT_SMC_2206USB 0x0201 /* EZ Connect USB Ethernet */ +#define USB_PRODUCT_SMC_2862WG 0xee13 /* EZ Connect Wireless Adapter */ +#define USB_PRODUCT_SMC2_2020HUB 0x2020 /* USB Hub */ +#define USB_PRODUCT_SMC3_2662WUSB 0xa002 /* 2662W-AR Wireless */ + +/* SOHOware products */ +#define USB_PRODUCT_SOHOWARE_NUB100 0x9100 /* 10/100 USB Ethernet */ +#define USB_PRODUCT_SOHOWARE_NUB110 0x9110 /* 10/100 USB Ethernet */ + +/* SOLID YEAR products */ +#define USB_PRODUCT_SOLIDYEAR_KEYBOARD 0x2101 /* Solid Year USB keyboard */ + +/* SONY products */ +#define USB_PRODUCT_SONY_DSC 0x0010 /* DSC cameras */ +#define USB_PRODUCT_SONY_MS_NW_MS7 0x0025 /* Memorystick NW-MS7 */ +#define USB_PRODUCT_SONY_PORTABLE_HDD_V2 0x002b /* Portable USB Harddrive V2 */ +#define USB_PRODUCT_SONY_MSACUS1 0x002d /* Memorystick MSAC-US1 */ +#define USB_PRODUCT_SONY_HANDYCAM 0x002e /* Handycam */ +#define USB_PRODUCT_SONY_MSC 0x0032 /* MSC memory stick slot */ +#define USB_PRODUCT_SONY_CLIE_35 0x0038 /* Sony Clie v3.5 */ +#define USB_PRODUCT_SONY_MS_PEG_N760C 0x0058 /* PEG N760c Memorystick */ +#define USB_PRODUCT_SONY_CLIE_40 0x0066 /* Sony Clie v4.0 */ +#define USB_PRODUCT_SONY_MS_MSC_U03 0x0069 /* Memorystick MSC-U03 */ +#define USB_PRODUCT_SONY_CLIE_40_MS 0x006d /* Sony Clie v4.0 Memory Stick slot */ +#define USB_PRODUCT_SONY_CLIE_S360 0x0095 /* Sony Clie s360 */ +#define USB_PRODUCT_SONY_CLIE_41_MS 0x0099 /* Sony Clie v4.1 Memory Stick slot */ +#define USB_PRODUCT_SONY_CLIE_41 0x009a /* Sony Clie v4.1 */ +#define USB_PRODUCT_SONY_CLIE_NX60 0x00da /* Sony Clie nx60 */ +#define USB_PRODUCT_SONY_CLIE_TH55 0x0144 /* Sony Clie th55 */ +#define USB_PRODUCT_SONY_CLIE_TJ37 0x0169 /* Sony Clie tj37 */ + +/* Sony Ericsson products */ +#define USB_PRODUCT_SONYERICSSON_DCU10 0x0528 /* USB Cable */ + +/* SOURCENEXT products */ +#define USB_PRODUCT_SOURCENEXT_KEIKAI8 0x039f /* KeikaiDenwa 8 */ +#define USB_PRODUCT_SOURCENEXT_KEIKAI8_CHG 0x012e /* KeikaiDenwa 8 with charger */ + +/* SparkLAN products */ +#define USB_PRODUCT_SPARKLAN_RT2573 0x0004 /* RT2573 */ + +/* Sphairon Access Systems GmbH products */ +#define USB_PRODUCT_SPHAIRON_UB801R 0x0110 /* UB801R */ + +/* STMicroelectronics products */ +#define USB_PRODUCT_STMICRO_BIOCPU 0x2016 /* Biometric Coprocessor */ +#define USB_PRODUCT_STMICRO_COMMUNICATOR 0x7554 /* USB Communicator */ + +/* STSN products */ +#define USB_PRODUCT_STSN_STSN0001 0x0001 /* Internet Access Device */ + +/* SUN Corporation products */ +#define USB_PRODUCT_SUNTAC_DS96L 0x0003 /* SUNTAC U-Cable type D2 */ +#define USB_PRODUCT_SUNTAC_PS64P1 0x0005 /* SUNTAC U-Cable type P1 */ +#define USB_PRODUCT_SUNTAC_VS10U 0x0009 /* SUNTAC Slipper U */ +#define USB_PRODUCT_SUNTAC_IS96U 0x000a /* SUNTAC Ir-Trinity */ +#define USB_PRODUCT_SUNTAC_AS64LX 0x000b /* SUNTAC U-Cable type A3 */ +#define USB_PRODUCT_SUNTAC_AS144L4 0x0011 /* SUNTAC U-Cable type A4 */ + +/* Sun Microsystems products */ +#define USB_PRODUCT_SUN_KEYBOARD 0x0005 /* Type 6 USB keyboard */ +/* XXX The above is a North American PC style keyboard possibly */ +#define USB_PRODUCT_SUN_MOUSE 0x0100 /* Type 6 USB mouse */ + +/* Supra products */ +#define USB_PRODUCT_DIAMOND2_SUPRAEXPRESS56K 0x07da /* Supra Express 56K modem */ +#define USB_PRODUCT_DIAMOND2_SUPRA2890 0x0b4a /* SupraMax 2890 56K Modem */ +#define USB_PRODUCT_DIAMOND2_RIO600USB 0x5001 /* Rio 600 USB */ +#define USB_PRODUCT_DIAMOND2_RIO800USB 0x5002 /* Rio 800 USB */ + +/* Surecom Technology products */ +#define USB_PRODUCT_SURECOM_RT2570 0x11f3 /* RT2570 */ +#define USB_PRODUCT_SURECOM_RT2573 0x31f3 /* RT2573 */ + +/* Sweex products */ +#define USB_PRODUCT_SWEEX_ZD1211 0x1809 /* ZD1211 */ + +/* System TALKS, Inc. */ +#define USB_PRODUCT_SYSTEMTALKS_SGCX2UL 0x1920 /* SGC-X2UL */ + +/* Tapwave products */ +#define USB_PRODUCT_TAPWAVE_ZODIAC 0x0100 /* Zodiac */ + +/* Taugagreining products */ +#define USB_PRODUCT_TAUGA_CAMERAMATE 0x0005 /* CameraMate (DPCM_USB) */ + +/* TDK products */ +#define USB_PRODUCT_TDK_UPA9664 0x0115 /* USB-PDC Adapter UPA9664 */ +#define USB_PRODUCT_TDK_UCA1464 0x0116 /* USB-cdmaOne Adapter UCA1464 */ +#define USB_PRODUCT_TDK_UHA6400 0x0117 /* USB-PHS Adapter UHA6400 */ +#define USB_PRODUCT_TDK_UPA6400 0x0118 /* USB-PHS Adapter UPA6400 */ +#define USB_PRODUCT_TDK_BT_DONGLE 0x0309 /* Bluetooth USB dongle */ + +/* TEAC products */ +#define USB_PRODUCT_TEAC_FD05PUB 0x0000 /* FD-05PUB floppy */ + +/* Tekram Technology products */ +#define USB_PRODUCT_TEKRAM_QUICKWLAN 0x1630 /* QuickWLAN */ +#define USB_PRODUCT_TEKRAM_ZD1211_1 0x5630 /* ZD1211 */ +#define USB_PRODUCT_TEKRAM_ZD1211_2 0x6630 /* ZD1211 */ + +/* Telex Communications products */ +#define USB_PRODUCT_TELEX_MIC1 0x0001 /* Enhanced USB Microphone */ + +/* Ten X Technology, Inc. */ +#define USB_PRODUCT_TENX_UAUDIO0 0xf211 /* USB audio headset */ + +/* Texas Intel products */ +#define USB_PRODUCT_TI_UTUSB41 0x1446 /* UT-USB41 hub */ +#define USB_PRODUCT_TI_TUSB2046 0x2046 /* TUSB2046 hub */ + +/* Thrustmaster products */ +#define USB_PRODUCT_THRUST_FUSION_PAD 0xa0a3 /* Fusion Digital Gamepad */ + +/* Topre Corporation products */ +#define USB_PRODUCT_TOPRE_HHKB 0x0100 /* HHKB Professional */ + +/* Toshiba Corporation products */ +#define USB_PRODUCT_TOSHIBA_POCKETPC_E740 0x0706 /* PocketPC e740 */ + +/* Trek Technology products */ +#define USB_PRODUCT_TREK_THUMBDRIVE 0x1111 /* ThumbDrive */ +#define USB_PRODUCT_TREK_MEMKEY 0x8888 /* IBM USB Memory Key */ +#define USB_PRODUCT_TREK_THUMBDRIVE_8MB 0x9988 /* ThumbDrive_8MB */ + +/* Tripp-Lite products */ +#define USB_PRODUCT_TRIPPLITE_U209 0x2008 /* Serial */ + +/* Trumpion products */ +#define USB_PRODUCT_TRUMPION_T33520 0x1001 /* T33520 USB Flash Card Controller */ +#define USB_PRODUCT_TRUMPION_C3310 0x1100 /* Comotron C3310 MP3 player */ +#define USB_PRODUCT_TRUMPION_MP3 0x1200 /* MP3 player */ + +/* TwinMOS */ +#define USB_PRODUCT_TWINMOS_G240 0xa006 /* G240 */ +#define USB_PRODUCT_TWINMOS_MDIV 0x1325 /* Memory Disk IV */ + +/* Ubiquam products */ +#define USB_PRODUCT_UBIQUAM_UALL 0x3100 /* CDMA 1xRTT USB Modem (U-100/105/200/300/520) */ + +/* Ultima products */ +#define USB_PRODUCT_ULTIMA_1200UBPLUS 0x4002 /* 1200 UB Plus scanner */ + +/* UMAX products */ +#define USB_PRODUCT_UMAX_ASTRA1236U 0x0002 /* Astra 1236U Scanner */ +#define USB_PRODUCT_UMAX_ASTRA1220U 0x0010 /* Astra 1220U Scanner */ +#define USB_PRODUCT_UMAX_ASTRA2000U 0x0030 /* Astra 2000U Scanner */ +#define USB_PRODUCT_UMAX_ASTRA2100U 0x0130 /* Astra 2100U Scanner */ +#define USB_PRODUCT_UMAX_ASTRA2200U 0x0230 /* Astra 2200U Scanner */ +#define USB_PRODUCT_UMAX_ASTRA3400 0x0060 /* Astra 3400 Scanner */ + +/* U-MEDIA Communications products */ +#define USB_PRODUCT_UMEDIA_TEW444UBEU 0x3006 /* TEW-444UB EU */ +#define USB_PRODUCT_UMEDIA_TEW444UBEU_NF 0x3007 /* TEW-444UB EU (no firmware) */ +#define USB_PRODUCT_UMEDIA_TEW429UB_A 0x300a /* TEW-429UB_A */ +#define USB_PRODUCT_UMEDIA_TEW429UB 0x300b /* TEW-429UB */ +#define USB_PRODUCT_UMEDIA_TEW429UBC1 0x300d /* TEW-429UB C1 */ +#define USB_PRODUCT_UMEDIA_ALL0298V2 0x3204 /* ALL0298 v2 */ +#define USB_PRODUCT_UMEDIA_AR5523_2 0x3205 /* AR5523 */ +#define USB_PRODUCT_UMEDIA_AR5523_2_NF 0x3206 /* AR5523 (no firmware) */ + +/* Universal Access products */ +#define USB_PRODUCT_UNIACCESS_PANACHE 0x0101 /* Panache Surf USB ISDN Adapter */ + +/* U.S. Robotics products */ +#define USB_PRODUCT_USR_USR5423 0x0121 /* USR5423 WLAN */ + +/* VIA Technologies products */ +#define USB_PRODUCT_VIA_USB2IDEBRIDGE 0x6204 /* USB 2.0 IDE Bridge */ + +/* USI products */ +#define USB_PRODUCT_USI_MC60 0x10c5 /* MC60 Serial */ + +/* VidzMedia products */ +#define USB_PRODUCT_VIDZMEDIA_MONSTERTV 0x4fb1 /* MonsterTV P2H */ + +/* Vision products */ +#define USB_PRODUCT_VISION_VC6452V002 0x0002 /* CPiA Camera */ + +/* Visioneer products */ +#define USB_PRODUCT_VISIONEER_7600 0x0211 /* OneTouch 7600 */ +#define USB_PRODUCT_VISIONEER_5300 0x0221 /* OneTouch 5300 */ +#define USB_PRODUCT_VISIONEER_3000 0x0224 /* Scanport 3000 */ +#define USB_PRODUCT_VISIONEER_6100 0x0231 /* OneTouch 6100 */ +#define USB_PRODUCT_VISIONEER_6200 0x0311 /* OneTouch 6200 */ +#define USB_PRODUCT_VISIONEER_8100 0x0321 /* OneTouch 8100 */ +#define USB_PRODUCT_VISIONEER_8600 0x0331 /* OneTouch 8600 */ + +/* Vivitar products */ +#define USB_PRODUCT_VIVITAR_35XX 0x0003 /* Vivicam 35Xx */ + +/* VTech products */ +#define USB_PRODUCT_VTECH_RT2570 0x3012 /* RT2570 */ +#define USB_PRODUCT_VTECH_ZD1211B 0x3014 /* ZD1211B */ + +/* Wacom products */ +#define USB_PRODUCT_WACOM_CT0405U 0x0000 /* CT-0405-U Tablet */ +#define USB_PRODUCT_WACOM_GRAPHIRE 0x0010 /* Graphire */ +#define USB_PRODUCT_WACOM_GRAPHIRE3_4X5 0x0013 /* Graphire 3 4x5 */ +#define USB_PRODUCT_WACOM_INTUOSA5 0x0021 /* Intuos A5 */ +#define USB_PRODUCT_WACOM_GD0912U 0x0022 /* Intuos 9x12 Graphics Tablet */ +/* WCH products*/ +#define USB_PRODUCT_WCH_CH341SER 0x5523 /* CH341/CH340 USB-Serial Bridge */ +/* Western Digital products */ +#define USB_PRODUCT_WESTERN_COMBO 0x0200 /* Firewire USB Combo */ +#define USB_PRODUCT_WESTERN_EXTHDD 0x0400 /* External HDD */ +#define USB_PRODUCT_WESTERN_HUB 0x0500 /* USB HUB */ +#define USB_PRODUCT_WESTERN_MYBOOK 0x0901 /* MyBook External HDD */ + +/* Windbond Electronics */ +#define USB_PRODUCT_WINBOND_UH104 0x5518 /* 4-port USB Hub */ + +/* WinMaxGroup products */ +#define USB_PRODUCT_WINMAXGROUP_FLASH64MC 0x6660 /* USB Flash Disk 64M-C */ + +/* Wistron NeWeb products */ +#define USB_PRODUCT_WISTRONNEWEB_UR045G 0x0427 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_WISTRONNEWEB_UR055G 0x0711 /* UR055G */ +#define USB_PRODUCT_WISTRONNEWEB_AR5523_1 0x0826 /* AR5523 */ +#define USB_PRODUCT_WISTRONNEWEB_AR5523_1_NF 0x0827 /* AR5523 (no firmware) */ +#define USB_PRODUCT_WISTRONNEWEB_AR5523_2 0x082a /* AR5523 */ +#define USB_PRODUCT_WISTRONNEWEB_AR5523_2_NF 0x0829 /* AR5523 (no firmware) */ + +/* Xerox products */ +#define USB_PRODUCT_XEROX_WCM15 0xffef /* WorkCenter M15 */ + +/* Xirlink products */ +#define USB_PRODUCT_XIRLINK_PCCAM 0x8080 /* IBM PC Camera */ + +/* Xyratex products */ +#define USB_PRODUCT_XYRATEX_PRISM_GT_1 0x2000 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_XYRATEX_PRISM_GT_2 0x2002 /* PrismGT USB 2.0 WLAN */ + +/* Y-E Data products */ +#define USB_PRODUCT_YEDATA_FLASHBUSTERU 0x0000 /* Flashbuster-U */ + +/* Yamaha products */ +#define USB_PRODUCT_YAMAHA_UX256 0x1000 /* UX256 MIDI I/F */ +#define USB_PRODUCT_YAMAHA_UX96 0x1008 /* UX96 MIDI I/F */ +#define USB_PRODUCT_YAMAHA_RTA54I 0x4000 /* NetVolante RTA54i Broadband&ISDN Router */ +#define USB_PRODUCT_YAMAHA_RTA55I 0x4004 /* NetVolante RTA55i Broadband VoIP Router */ +#define USB_PRODUCT_YAMAHA_RTW65B 0x4001 /* NetVolante RTW65b Broadband Wireless Router */ +#define USB_PRODUCT_YAMAHA_RTW65I 0x4002 /* NetVolante RTW65i Broadband&ISDN Wireless Router */ + +/* Yano products */ +#define USB_PRODUCT_YANO_U640MO 0x0101 /* U640MO-03 */ +#define USB_PRODUCT_YANO_FW800HD 0x05fc /* METALWEAR-HDD */ + +/* Z-Com products */ +#define USB_PRODUCT_ZCOM_M4Y750 0x0001 /* M4Y-750 */ +#define USB_PRODUCT_ZCOM_XI725 0x0002 /* XI-725/726 */ +#define USB_PRODUCT_ZCOM_XI735 0x0005 /* XI-735 */ +#define USB_PRODUCT_ZCOM_XG703A 0x0008 /* PrismGT USB 2.0 WLAN */ +#define USB_PRODUCT_ZCOM_ZD1211 0x0011 /* ZD1211 */ +#define USB_PRODUCT_ZCOM_AR5523 0x0012 /* AR5523 */ +#define USB_PRODUCT_ZCOM_AR5523_NF 0x0013 /* AR5523 driver (no firmware) */ +#define USB_PRODUCT_ZCOM_ZD1211B 0x001a /* ZD1211B */ + +/* Zinwell products */ +#define USB_PRODUCT_ZINWELL_RT2570 0x0260 /* RT2570 */ + +/* Zoom Telephonics, Inc. products */ +#define USB_PRODUCT_ZOOM_2986L 0x9700 /* 2986L Fax modem */ + +/* Zoran Microelectronics products */ +#define USB_PRODUCT_ZORAN_EX20DSC 0x4343 /* Digital Camera EX-20 DSC */ + +/* Zydas Technology Corporation products */ +#define USB_PRODUCT_ZYDAS_ZD1211 0x1211 /* ZD1211 WLAN abg */ +#define USB_PRODUCT_ZYDAS_ZD1211B 0x1215 /* ZD1211B */ + +/* ZyXEL Communication Co. products */ +#define USB_PRODUCT_ZYXEL_OMNI56K 0x1500 /* Omni 56K Plus */ +#define USB_PRODUCT_ZYXEL_980N 0x2011 /* Scorpion-980N keyboard */ +#define USB_PRODUCT_ZYXEL_ZYAIRG220 0x3401 /* ZyAIR G-220 */ +#define USB_PRODUCT_ZYXEL_G200V2 0x3407 /* G-200 v2 */ +#define USB_PRODUCT_ZYXEL_AG225H 0x3409 /* AG-225H */ +#define USB_PRODUCT_ZYXEL_M202 0x340a /* M-202 */ +#define USB_PRODUCT_ZYXEL_G220V2 0x340f /* G-220 v2 */ +#define USB_PRODUCT_ZYXEL_G202 0x3410 /* G-202 */ diff --git a/sys/dev/usb2/include/usb2_devtable.h b/sys/dev/usb2/include/usb2_devtable.h new file mode 100644 index 000000000000..168457261ae9 --- /dev/null +++ b/sys/dev/usb2/include/usb2_devtable.h @@ -0,0 +1,10748 @@ +/* $FreeBSD$ */ + +/* + * THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + * + * generated from: + * FreeBSD: src/sys/dev/usb/usbdevs,v 1.372 2008/09/19 09:04:06 kevlo Exp + */ +/* $NetBSD: usbdevs,v 1.392 2004/12/29 08:38:44 imp Exp $ */ + +/*- + * Copyright (c) 1998-2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +const struct usb_knowndev usb_knowndevs[] = { + { + USB_VENDOR_3COM, USB_PRODUCT_3COM_HOMECONN, + 0, + "3Com", + "HomeConnect Camera", + }, + { + USB_VENDOR_3COM, USB_PRODUCT_3COM_3CREB96, + 0, + "3Com", + "Bluetooth USB Adapter", + }, + { + USB_VENDOR_3COM, USB_PRODUCT_3COM_3C19250, + 0, + "3Com", + "3C19250 Ethernet Adapter", + }, + { + USB_VENDOR_3COM, USB_PRODUCT_3COM_3CRSHEW696, + 0, + "3Com", + "3CRSHEW696 Wireless Adapter", + }, + { + USB_VENDOR_3COM, USB_PRODUCT_3COM_3C460, + 0, + "3Com", + "HomeConnect 3C460", + }, + { + USB_VENDOR_3COM, USB_PRODUCT_3COM_USR56K, + 0, + "3Com", + "U.S.Robotics 56000 Voice FaxModem Pro", + }, + { + USB_VENDOR_3COM, USB_PRODUCT_3COM_3C460B, + 0, + "3Com", + "HomeConnect 3C460B", + }, + { + USB_VENDOR_3COM2, USB_PRODUCT_3COM2_3CRUSB10075, + 0, + "3Com", + "3CRUSB10075", + }, + { + USB_VENDOR_3COM3, USB_PRODUCT_3COM3_AR5523_1, + 0, + "3Com", + "AR5523", + }, + { + USB_VENDOR_3COM3, USB_PRODUCT_3COM3_AR5523_2, + 0, + "3Com", + "AR5523", + }, + { + USB_VENDOR_3COM3, USB_PRODUCT_3COM3_AR5523_3, + 0, + "3Com", + "AR5523", + }, + { + USB_VENDOR_3COMUSR, USB_PRODUCT_3COMUSR_OFFICECONN, + 0, + "U.S. Robotics", + "3Com OfficeConnect Analog Modem", + }, + { + USB_VENDOR_3COMUSR, USB_PRODUCT_3COMUSR_USRISDN, + 0, + "U.S. Robotics", + "3Com U.S. Robotics Pro ISDN TA", + }, + { + USB_VENDOR_3COMUSR, USB_PRODUCT_3COMUSR_HOMECONN, + 0, + "U.S. Robotics", + "3Com HomeConnect Camera", + }, + { + USB_VENDOR_3COMUSR, USB_PRODUCT_3COMUSR_USR56K, + 0, + "U.S. Robotics", + "U.S. Robotics 56000 Voice FaxModem Pro", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX1, + 0, + "AboCom Systems", + "XX1", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX2, + 0, + "AboCom Systems", + "XX2", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_URE450, + 0, + "AboCom Systems", + "URE450 Ethernet Adapter", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_UFE1000, + 0, + "AboCom Systems", + "UFE1000 Fast Ethernet Adapter", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_DSB650TX_PNA, + 0, + "AboCom Systems", + "1/10/100 Ethernet Adapter", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX4, + 0, + "AboCom Systems", + "XX4", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX5, + 0, + "AboCom Systems", + "XX5", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX6, + 0, + "AboCom Systems", + "XX6", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX7, + 0, + "AboCom Systems", + "XX7", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RTL8151, + 0, + "AboCom Systems", + "RTL8151", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX8, + 0, + "AboCom Systems", + "XX8", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX9, + 0, + "AboCom Systems", + "XX9", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_UF200, + 0, + "AboCom Systems", + "UF200 Ethernet", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_WL54, + 0, + "AboCom Systems", + "WL54", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX10, + 0, + "AboCom Systems", + "XX10", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_BWU613, + 0, + "AboCom Systems", + "BWU613", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_HWU54DM, + 0, + "AboCom Systems", + "HWU54DM", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_2, + 0, + "AboCom Systems", + "RT2573", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_3, + 0, + "AboCom Systems", + "RT2573", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_4, + 0, + "AboCom Systems", + "RT2573", + }, + { + USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_WUG2700, + 0, + "AboCom Systems", + "WUG2700", + }, + { + USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_USB320_EC, + 0, + "Accton Technology", + "USB320-EC Ethernet Adapter", + }, + { + USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_2664W, + 0, + "Accton Technology", + "2664W", + }, + { + USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_111, + 0, + "Accton Technology", + "T-Sinus 111 Wireless Adapter", + }, + { + USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_SMCWUSBG, + 0, + "Accton Technology", + "SMCWUSB-G", + }, + { + USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_PRISM_GT, + 0, + "Accton Technology", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_SS1001, + 0, + "Accton Technology", + "SpeedStream Ethernet Adapter", + }, + { + USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_ZD1211B, + 0, + "Accton Technology", + "ZD1211B", + }, + { + USB_VENDOR_ACEECA, USB_PRODUCT_ACEECA_MEZ1000, + 0, + "Aceeca", + "MEZ1000 RDA", + }, + { + USB_VENDOR_ACERCM, USB_PRODUCT_ACERCM_EP1427X2, + 0, + "Acer Communications & Multimedia", + "EP-1427X-2 Ethernet Adapter", + }, + { + USB_VENDOR_ACERLABS, USB_PRODUCT_ACERLABS_M5632, + 0, + "Acer Labs", + "USB 2.0 Data Link", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_C310U, + 0, + "Acer Peripherals", + "Acerscan C310U", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_320U, + 0, + "Acer Peripherals", + "Acerscan 320U", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_640U, + 0, + "Acer Peripherals", + "Acerscan 640U", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_620U, + 0, + "Acer Peripherals", + "Acerscan 620U", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_4300U, + 0, + "Acer Peripherals", + "Benq 3300U/4300U", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_640BT, + 0, + "Acer Peripherals", + "Acerscan 640BT", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_1240U, + 0, + "Acer Peripherals", + "Acerscan 1240U", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ATAPI, + 0, + "Acer Peripherals", + "ATA/ATAPI Adapter", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_AWL300, + 0, + "Acer Peripherals", + "AWL300 Wireless Adapter", + }, + { + USB_VENDOR_ACERP, USB_PRODUCT_ACERP_AWL400, + 0, + "Acer Peripherals", + "AWL400 Wireless Adapter", + }, + { + USB_VENDOR_ACERW, USB_PRODUCT_ACERW_WARPLINK, + 0, + "Acer", + "Warplink", + }, + { + USB_VENDOR_ACTIONTEC, USB_PRODUCT_ACTIONTEC_PRISM_25, + 0, + "Actiontec Electronics", + "Prism2.5 Wireless Adapter", + }, + { + USB_VENDOR_ACTIONTEC, USB_PRODUCT_ACTIONTEC_PRISM_25A, + 0, + "Actiontec Electronics", + "Prism2.5 Wireless Adapter A", + }, + { + USB_VENDOR_ACTIONTEC, USB_PRODUCT_ACTIONTEC_FREELAN, + 0, + "Actiontec Electronics", + "ROPEX FreeLan 802.11b", + }, + { + USB_VENDOR_ACTIONTEC, USB_PRODUCT_ACTIONTEC_UAT1, + 0, + "Actiontec Electronics", + "UAT1 Wireless Ethernet Adapter", + }, + { + USB_VENDOR_ACTISYS, USB_PRODUCT_ACTISYS_IR2000U, + 0, + "ACTiSYS", + "ACT-IR2000U FIR", + }, + { + USB_VENDOR_ACTIVEWIRE, USB_PRODUCT_ACTIVEWIRE_IOBOARD, + 0, + "ActiveWire", + "I/O Board", + }, + { + USB_VENDOR_ACTIVEWIRE, USB_PRODUCT_ACTIVEWIRE_IOBOARD_FW1, + 0, + "ActiveWire", + "I/O Board, rev. 1 firmware", + }, + { + USB_VENDOR_ADAPTEC, USB_PRODUCT_ADAPTEC_AWN8020, + 0, + "Adaptec", + "AWN-8020 WLAN", + }, + { + USB_VENDOR_ADDTRON, USB_PRODUCT_ADDTRON_AWU120, + 0, + "Addtron", + "AWU-120", + }, + { + USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_4, + 0, + "ADMtek", + "AN986A Ethernet", + }, + { + USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUS, + 0, + "ADMtek", + "AN986 Ethernet", + }, + { + USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII, + 0, + "ADMtek", + "AN8511 Ethernet", + }, + { + USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_2, + 0, + "ADMtek", + "AN8513 Ethernet", + }, + { + USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_3, + 0, + "ADMtek", + "AN8515 Ethernet", + }, + { + USB_VENDOR_ADDON, USB_PRODUCT_ADDON_ATTACHE, + 0, + "Add-on Technology", + "USB 2.0 Flash Drive", + }, + { + USB_VENDOR_ADDON, USB_PRODUCT_ADDON_ATTACHE, + 0, + "Add-on Technology", + "USB 2.0 Flash Drive", + }, + { + USB_VENDOR_ADDON, USB_PRODUCT_ADDON_A256MB, + 0, + "Add-on Technology", + "Attache 256MB USB 2.0 Flash Drive", + }, + { + USB_VENDOR_ADDON, USB_PRODUCT_ADDON_DISKPRO512, + 0, + "Add-on Technology", + "USB 2.0 Flash Drive (DANE-ELEC zMate 512MB USB flash drive)", + }, + { + USB_VENDOR_ADDONICS2, USB_PRODUCT_ADDONICS2_CABLE_205, + 0, + "Addonics Technology", + "Cable 205", + }, + { + USB_VENDOR_ADS, USB_PRODUCT_ADS_UBS10BT, + 0, + "ADS Technologies", + "UBS-10BT Ethernet", + }, + { + USB_VENDOR_ADS, USB_PRODUCT_ADS_UBS10BTX, + 0, + "ADS Technologies", + "UBS-10BT Ethernet", + }, + { + USB_VENDOR_AEI, USB_PRODUCT_AEI_FASTETHERNET, + 0, + "AEI", + "Fast Ethernet", + }, + { + USB_VENDOR_AGATE, USB_PRODUCT_AGATE_QDRIVE, + 0, + "Agate Technologies", + "Q-Drive", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1212U, + 0, + "AGFA-Gevaert", + "SnapScan 1212U", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1236U, + 0, + "AGFA-Gevaert", + "SnapScan 1236U", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANTOUCH, + 0, + "AGFA-Gevaert", + "SnapScan Touch", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1212U2, + 0, + "AGFA-Gevaert", + "SnapScan 1212U", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE40, + 0, + "AGFA-Gevaert", + "SnapScan e40", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE50, + 0, + "AGFA-Gevaert", + "SnapScan e50", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE20, + 0, + "AGFA-Gevaert", + "SnapScan e20", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE25, + 0, + "AGFA-Gevaert", + "SnapScan e25", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE26, + 0, + "AGFA-Gevaert", + "SnapScan e26", + }, + { + USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE52, + 0, + "AGFA-Gevaert", + "SnapScan e52", + }, + { + USB_VENDOR_AINCOMM, USB_PRODUCT_AINCOMM_AWU2000B, + 0, + "Aincomm", + "AWU2000B Wireless Adapter", + }, + { + USB_VENDOR_AIPTEK, USB_PRODUCT_AIPTEK_POCKETCAM3M, + 0, + "AIPTEK International", + "PocketCAM 3Mega", + }, + { + USB_VENDOR_SUNPLUS, USB_PRODUCT_SUNPLUS_PENCAM_MEGA_1_3, + 0, + "Sunplus", + "PenCam Mega 1.3", + }, + { + USB_VENDOR_AIRPRIME, USB_PRODUCT_AIRPRIME_PC5220, + 0, + "AirPrime, Inc.", + "CDMA Wireless PC Card", + }, + { + USB_VENDOR_AKS, USB_PRODUCT_AKS_USBHASP, + 0, + "Aladdin Knowledge Systems", + "USB-HASP 0.06", + }, + { + USB_VENDOR_ALCOR2, USB_PRODUCT_ALCOR2_KBD_HUB, + 0, + "Alcor Micro", + "Kbd Hub", + }, + { + USB_VENDOR_ALCOR, USB_PRODUCT_ALCOR_MA_KBD_HUB, + 0, + "Alcor Micro", + "MacAlly Kbd Hub", + }, + { + USB_VENDOR_ALCOR, USB_PRODUCT_ALCOR_AU9814, + 0, + "Alcor Micro", + "AU9814 Hub", + }, + { + USB_VENDOR_ALCOR, USB_PRODUCT_ALCOR_UMCR_9361, + 0, + "Alcor Micro", + "USB Multimedia Card Reader", + }, + { + USB_VENDOR_ALCOR, USB_PRODUCT_ALCOR_SM_KBD, + 0, + "Alcor Micro", + "MicroConnectors/StrongMan Keyboard", + }, + { + USB_VENDOR_ALCOR, USB_PRODUCT_ALCOR_NEC_KBD_HUB, + 0, + "Alcor Micro", + "NEC Kbd Hub", + }, + { + USB_VENDOR_ALTEC, USB_PRODUCT_ALTEC_ADA70, + 0, + "Altec Lansing", + "ADA70 Speakers", + }, + { + USB_VENDOR_ALTEC, USB_PRODUCT_ALTEC_ASC495, + 0, + "Altec Lansing", + "ASC495 Speakers", + }, + { + USB_VENDOR_ALLIEDTELESYN, USB_PRODUCT_ALLIEDTELESYN_ATUSB100, + 0, + "Allied Telesyn International", + "AT-USB100", + }, + { + USB_VENDOR_APC, USB_PRODUCT_APC_UPS, + 0, + "American Power Conversion", + "Uninterruptible Power Supply", + }, + { + USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_WLAN, + 0, + "Ambit Microsystems", + "WLAN", + }, + { + USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_NTL_250, + 0, + "Ambit Microsystems", + "NTL 250 cable modem", + }, + { + USB_VENDOR_AMIT, USB_PRODUCT_AMIT_CGWLUSB2GO, + 0, + "AMIT", + "CG-WLUSB2GO", + }, + { + USB_VENDOR_ANCHOR, USB_PRODUCT_ANCHOR_EZUSB, + 0, + "Anchor Chips", + "EZUSB", + }, + { + USB_VENDOR_ANCHOR, USB_PRODUCT_ANCHOR_EZLINK, + 0, + "Anchor Chips", + "EZLINK", + }, + { + USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_E100X, + 0, + "AnyDATA Corporation", + "CDMA 2000 1xRTT/EV-DO USB Modem", + }, + { + USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_500A, + 0, + "AnyDATA Corporation", + "CDMA 2000 EV-DO USB Modem", + }, + { + USB_VENDOR_AOX, USB_PRODUCT_AOX_USB101, + 0, + "AOX", + "Ethernet", + }, + { + USB_VENDOR_APC, USB_PRODUCT_APC_UPS, + 0, + "American Power Conversion", + "Uninterruptible Power Supply", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_EXT_KBD, + 0, + "Apple Computer", + "Apple Extended USB Keyboard", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_OPTMOUSE, + 0, + "Apple Computer", + "Optical mouse", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_MIGHTYMOUSE, + 0, + "Apple Computer", + "Mighty Mouse", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_EXT_KBD_HUB, + 0, + "Apple Computer", + "Hub in Apple Extended USB Keyboard", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_SPEAKERS, + 0, + "Apple Computer", + "Speakers", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD, + 0, + "Apple Computer", + "iPod", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD2G, + 0, + "Apple Computer", + "iPod 2G", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD3G, + 0, + "Apple Computer", + "iPod 3G", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD_04, + 0, + "Apple Computer", + "iPod '04'", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPODMINI, + 0, + "Apple Computer", + "iPod Mini", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD_06, + 0, + "Apple Computer", + "iPod '06'", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD_07, + 0, + "Apple Computer", + "iPod '07'", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPOD_08, + 0, + "Apple Computer", + "iPod '08'", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPODVIDEO, + 0, + "Apple Computer", + "iPod Video", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPODNANO, + 0, + "Apple Computer", + "iPod Nano", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE, + 0, + "Apple Computer", + "iPhone", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3G, + 0, + "Apple Computer", + "iPhone 3G", + }, + { + USB_VENDOR_APPLE, USB_PRODUCT_APPLE_ETHERNET, + 0, + "Apple Computer", + "Ethernet A1277", + }, + { + USB_VENDOR_ARKMICRO, USB_PRODUCT_ARKMICRO_ARK3116, + 0, + "Arkmicro Technologies Inc.", + "ARK3116 Serial", + }, + { + USB_VENDOR_ASAHIOPTICAL, USB_PRODUCT_ASAHIOPTICAL_OPTIO230, + 0, + "Asahi Optical", + "Digital camera", + }, + { + USB_VENDOR_ASAHIOPTICAL, USB_PRODUCT_ASAHIOPTICAL_OPTIO330, + 0, + "Asahi Optical", + "Digital camera", + }, + { + USB_VENDOR_ASANTE, USB_PRODUCT_ASANTE_EA, + 0, + "Asante", + "Ethernet", + }, + { + USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88172, + 0, + "ASIX Electronics", + "10/100 Ethernet", + }, + { + USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88178, + 0, + "ASIX Electronics", + "AX88178", + }, + { + USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88772, + 0, + "ASIX Electronics", + "AX88772", + }, + { + USB_VENDOR_ASUS, USB_PRODUCT_ASUS_WL167G, + 0, + "ASUSTeK Computer", + "WL-167g Wireless Adapter", + }, + { + USB_VENDOR_ASUS, USB_PRODUCT_ASUS_WL159G, + 0, + "ASUSTeK Computer", + "WL-159g", + }, + { + USB_VENDOR_ASUS, USB_PRODUCT_ASUS_A9T_WIFI, + 0, + "ASUSTeK Computer", + "A9T wireless", + }, + { + USB_VENDOR_ASUS, USB_PRODUCT_ASUS_RT2573_1, + 0, + "ASUSTeK Computer", + "RT2573", + }, + { + USB_VENDOR_ASUS, USB_PRODUCT_ASUS_RT2573_2, + 0, + "ASUSTeK Computer", + "RT2573", + }, + { + USB_VENDOR_ASUS, USB_PRODUCT_ASUS_LCM, + 0, + "ASUSTeK Computer", + "LCM display", + }, + { + USB_VENDOR_ASUS, USB_PRODUCT_ASUS_P535, + 0, + "ASUSTeK Computer", + "ASUS P535 PDA", + }, + { + USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC1284, + 0, + "ATEN International", + "Parallel printer", + }, + { + USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC10T, + 0, + "ATEN International", + "10Mbps Ethernet", + }, + { + USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC110T, + 0, + "ATEN International", + "UC-110T Ethernet", + }, + { + USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC232A, + 0, + "ATEN International", + "Serial", + }, + { + USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC210T, + 0, + "ATEN International", + "UC-210T Ethernet", + }, + { + USB_VENDOR_ATEN, USB_PRODUCT_ATEN_DSB650C, + 0, + "ATEN International", + "DSB-650C", + }, + { + USB_VENDOR_ATHEROS, USB_PRODUCT_ATHEROS_AR5523, + 0, + "Atheros Communications", + "AR5523", + }, + { + USB_VENDOR_ATHEROS, USB_PRODUCT_ATHEROS_AR5523_NF, + 0, + "Atheros Communications", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_AR5523_1, + 0, + "Atheros Communications", + "AR5523", + }, + { + USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_AR5523_1_NF, + 0, + "Atheros Communications", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_AR5523_2, + 0, + "Atheros Communications", + "AR5523", + }, + { + USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_AR5523_2_NF, + 0, + "Atheros Communications", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_AR5523_3, + 0, + "Atheros Communications", + "AR5523", + }, + { + USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_AR5523_3_NF, + 0, + "Atheros Communications", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_ATMEL, USB_PRODUCT_ATMEL_UHB124, + 0, + "Atmel", + "UHB124 hub", + }, + { + USB_VENDOR_ATMEL, USB_PRODUCT_ATMEL_DWL120, + 0, + "Atmel", + "DWL-120 Wireless Adapter", + }, + { + USB_VENDOR_ATMEL, USB_PRODUCT_ATMEL_BW002, + 0, + "Atmel", + "BW002 Wireless Adapter", + }, + { + USB_VENDOR_ATMEL, USB_PRODUCT_ATMEL_WL1130USB, + 0, + "Atmel", + "WL-1130 USB", + }, + { + USB_VENDOR_ATMEL, USB_PRODUCT_ATMEL_AT76C505A, + 0, + "Atmel", + "AT76c505a Wireless Adapter", + }, + { + USB_VENDOR_AVISION, USB_PRODUCT_AVISION_1200U, + 0, + "Avision", + "1200U scanner", + }, + { + USB_VENDOR_AXESSTEL, USB_PRODUCT_AXESSTEL_DATAMODEM, + 0, + "Axesstel Co., Ltd.", + "Data Modem", + }, + { + USB_VENDOR_BALTECH, USB_PRODUCT_BALTECH_CARDREADER, + 0, + "Baltech", + "Card reader", + }, + { + USB_VENDOR_BBELECTRONICS, USB_PRODUCT_BBELECTRONICS_USOTL4, + 0, + "B&B Electronics", + "RS-422/485", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D6050, + 0, + "Belkin Components", + "F5D6050 802.11b Wireless Adapter", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_FBT001V, + 0, + "Belkin Components", + "FBT001v2 Bluetooth", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_FBT003V, + 0, + "Belkin Components", + "FBT003v2 Bluetooth", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U103, + 0, + "Belkin Components", + "F5U103 Serial", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U109, + 0, + "Belkin Components", + "F5U109 Serial", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_USB2SCSI, + 0, + "Belkin Components", + "USB to SCSI", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_USB2LAN, + 0, + "Belkin Components", + "USB to LAN", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U208, + 0, + "Belkin Components", + "F5U208 VideoBus II", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U237, + 0, + "Belkin Components", + "F5U237 USB 2.0 7-Port Hub", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U257, + 0, + "Belkin Components", + "F5U257 Serial", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U409, + 0, + "Belkin Components", + "F5U409 Serial", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F6C550AVR, + 0, + "Belkin Components", + "F6C550-AVR UPS", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U120, + 0, + "Belkin Components", + "F5U120-PC Hub", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_ZD1211B, + 0, + "Belkin Components", + "ZD1211B", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D5055, + 0, + "Belkin Components", + "F5D5055", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050, + 0, + "Belkin Components", + "F5D7050 Wireless Adapter", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7051, + 0, + "Belkin Components", + "F5D7051 54g USB Network Adapter", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050A, + 0, + "Belkin Components", + "F5D7050A Wireless Adapter", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050_V4000, + 0, + "Belkin Components", + "F5D7050 v4000 Wireless Adapter", + }, + { + USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D9050V3, + 0, + "Belkin Components", + "F5D9050 ver 3 Wireless Adapter", + }, + { + USB_VENDOR_BELKIN2, USB_PRODUCT_BELKIN2_F5U002, + 0, + "Belkin Components", + "F5U002 Parallel printer", + }, + { + USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USB100, + 0, + "Billionton Systems", + "USB100N 10/100 FastEthernet", + }, + { + USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBLP100, + 0, + "Billionton Systems", + "USB100LP", + }, + { + USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBEL100, + 0, + "Billionton Systems", + "USB100EL", + }, + { + USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBE100, + 0, + "Billionton Systems", + "USBE100", + }, + { + USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USB2AR, + 0, + "Billionton Systems", + "USB2AR Ethernet", + }, + { + USB_VENDOR_BROADCOM, USB_PRODUCT_BROADCOM_BCM2033, + 0, + "Broadcom", + "BCM2033 Bluetooth USB dongle", + }, + { + USB_VENDOR_BROTHER, USB_PRODUCT_BROTHER_HL1050, + 0, + "Brother Industries", + "HL-1050 laser printer", + }, + { + USB_VENDOR_BTC, USB_PRODUCT_BTC_BTC7932, + 0, + "Behavior Tech. Computer", + "Keyboard with mouse port", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_N656U, + 0, + "Canon", + "CanoScan N656U", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_N1220U, + 0, + "Canon", + "CanoScan N1220U", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_D660U, + 0, + "Canon", + "CanoScan D660U", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_N676U, + 0, + "Canon", + "CanoScan N676U", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_N1240U, + 0, + "Canon", + "CanoScan N1240U", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_LIDE25, + 0, + "Canon", + "CanoScan LIDE 25", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_S10, + 0, + "Canon", + "PowerShot S10", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_S100, + 0, + "Canon", + "PowerShot S100", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_S200, + 0, + "Canon", + "PowerShot S200", + }, + { + USB_VENDOR_CANON, USB_PRODUCT_CANON_REBELXT, + 0, + "Canon", + "Digital Rebel XT", + }, + { + USB_VENDOR_CATC, USB_PRODUCT_CATC_NETMATE, + 0, + "Computer Access Technology", + "Netmate Ethernet", + }, + { + USB_VENDOR_CATC, USB_PRODUCT_CATC_NETMATE2, + 0, + "Computer Access Technology", + "Netmate2 Ethernet", + }, + { + USB_VENDOR_CATC, USB_PRODUCT_CATC_CHIEF, + 0, + "Computer Access Technology", + "USB Chief Bus & Protocol Analyzer", + }, + { + USB_VENDOR_CATC, USB_PRODUCT_CATC_ANDROMEDA, + 0, + "Computer Access Technology", + "Andromeda hub", + }, + { + USB_VENDOR_CASIO, USB_PRODUCT_CASIO_QV_DIGICAM, + 0, + "CASIO", + "QV DigiCam", + }, + { + USB_VENDOR_CASIO, USB_PRODUCT_CASIO_EXS880, + 0, + "CASIO", + "Exilim EX-S880", + }, + { + USB_VENDOR_CASIO, USB_PRODUCT_CASIO_BE300, + 0, + "CASIO", + "BE-300 PDA", + }, + { + USB_VENDOR_CASIO, USB_PRODUCT_CASIO_NAMELAND, + 0, + "CASIO", + "CASIO Nameland EZ-USB", + }, + { + USB_VENDOR_CCYU, USB_PRODUCT_CCYU_ED1064, + 0, + "CCYU Technology", + "EasyDisk ED1064", + }, + { + USB_VENDOR_CENTURY, USB_PRODUCT_CENTURY_EX35QUAT, + 0, + "Century Corp", + "Century USB Disk Enclosure", + }, + { + USB_VENDOR_CHERRY, USB_PRODUCT_CHERRY_MY3000KBD, + 0, + "Cherry Mikroschalter", + "My3000 keyboard", + }, + { + USB_VENDOR_CHERRY, USB_PRODUCT_CHERRY_MY3000HUB, + 0, + "Cherry Mikroschalter", + "My3000 hub", + }, + { + USB_VENDOR_CHERRY, USB_PRODUCT_CHERRY_CYBOARD, + 0, + "Cherry Mikroschalter", + "CyBoard Keyboard", + }, + { + USB_VENDOR_CHIC, USB_PRODUCT_CHIC_MOUSE1, + 0, + "Chic Technology", + "mouse", + }, + { + USB_VENDOR_CHIC, USB_PRODUCT_CHIC_CYPRESS, + 0, + "Chic Technology", + "Cypress USB Mouse", + }, + { + USB_VENDOR_CHICONY, USB_PRODUCT_CHICONY_KB8933, + 0, + "Chicony Electronics", + "KB-8933 keyboard", + }, + { + USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TWINKLECAM, + 0, + "Chicony", + "TwinkleCam USB camera", + }, + { + USB_VENDOR_CHPRODUCTS, USB_PRODUCT_CHPRODUCTS_PROTHROTTLE, + 0, + "CH Products", + "Pro Throttle", + }, + { + USB_VENDOR_CHPRODUCTS, USB_PRODUCT_CHPRODUCTS_PROPEDALS, + 0, + "CH Products", + "Pro Pedals", + }, + { + USB_VENDOR_CHPRODUCTS, USB_PRODUCT_CHPRODUCTS_FIGHTERSTICK, + 0, + "CH Products", + "Fighterstick", + }, + { + USB_VENDOR_CHPRODUCTS, USB_PRODUCT_CHPRODUCTS_FLIGHTYOKE, + 0, + "CH Products", + "Flight Sim Yoke", + }, + { + USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54G, + 0, + "Cisco-Linksys", + "WUSB54G Wireless Adapter", + }, + { + USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GP, + 0, + "Cisco-Linksys", + "WUSB54GP Wireless Adapter", + }, + { + USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_USB200MV2, + 0, + "Cisco-Linksys", + "USB200M v2", + }, + { + USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_HU200TS, + 0, + "Cisco-Linksys", + "HU200TS Wireless Adapter", + }, + { + USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GC, + 0, + "Cisco-Linksys", + "WUSB54GC", + }, + { + USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GR, + 0, + "Cisco-Linksys", + "WUSB54GR", + }, + { + USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSBF54G, + 0, + "Cisco-Linksys", + "WUSBF54G", + }, + { + USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_CNU510, + 0, + "CMOTECH Co., Ltd.", + "CMOTECH CDMA Technologies USB modem", + }, + { + USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_CNU550, + 0, + "CMOTECH Co., Ltd.", + "CDMA 2000 1xRTT/1xEVDO USB modem", + }, + { + USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_CDMA_MODEM1, + 0, + "CMOTECH Co., Ltd.", + "CMOTECH CDMA Technologies USB modem", + }, + { + USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQPOCKETPC, + 0, + "Compaq", + "iPAQ PocketPC", + }, + { + USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_PJB100, + 0, + "Compaq", + "Personal Jukebox PJB100", + }, + { + USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQLINUX, + 0, + "Compaq", + "iPAQ Linux", + }, + { + USB_VENDOR_COMPOSITE, USB_PRODUCT_COMPOSITE_USBPS2, + 0, + "Composite", + "USB to PS2 Adaptor", + }, + { + USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_PRISM_GT, + 0, + "Conceptronic", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_C11U, + 0, + "Conceptronic", + "C11U", + }, + { + USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_WL210, + 0, + "Conceptronic", + "WL-210", + }, + { + USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_AR5523_1, + 0, + "Conceptronic", + "AR5523", + }, + { + USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_AR5523_1_NF, + 0, + "Conceptronic", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_AR5523_2, + 0, + "Conceptronic", + "AR5523", + }, + { + USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_AR5523_2_NF, + 0, + "Conceptronic", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_CONCEPTRONIC2, USB_PRODUCT_CONCEPTRONIC2_C54RU, + 0, + "Conceptronic", + "C54RU WLAN", + }, + { + USB_VENDOR_CONCEPTRONIC2, USB_PRODUCT_CONCEPTRONIC2_C54RU2, + 0, + "Conceptronic", + "C54RU", + }, + { + USB_VENDOR_CONNECTIX, USB_PRODUCT_CONNECTIX_QUICKCAM, + 0, + "Connectix", + "QuickCam", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_ETHER_USB_T, + 0, + "Corega", + "Ether USB-T", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TX, + 0, + "Corega", + "FEther USB-TX", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_WLAN_USB_USB_11, + 0, + "Corega", + "WirelessLAN USB-11", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXS, + 0, + "Corega", + "FEther USB-TXS", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_WLANUSB, + 0, + "Corega", + "Wireless LAN Stick-11", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB2_TX, + 0, + "Corega", + "FEther USB2-TX", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_WLUSB_11_KEY, + 0, + "Corega", + "ULUSB-11 Key", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_CGWLUSB2GL, + 0, + "Corega", + "CG-WLUSB2GL", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_CGWLUSB2GPX, + 0, + "Corega", + "CG-WLUSB2GPX", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_WLUSB_11_STICK, + 0, + "Corega", + "WLAN USB Stick 11", + }, + { + USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXC, + 0, + "Corega", + "FEther USB-TXC", + }, + { + USB_VENDOR_CREATIVE, USB_PRODUCT_CREATIVE_NOMAD_II, + 0, + "Creative Labs", + "Nomad II MP3 player", + }, + { + USB_VENDOR_CREATIVE, USB_PRODUCT_CREATIVE_NOMAD_IIMG, + 0, + "Creative Labs", + "Nomad II MG", + }, + { + USB_VENDOR_CREATIVE, USB_PRODUCT_CREATIVE_NOMAD, + 0, + "Creative Labs", + "Nomad", + }, + { + USB_VENDOR_CREATIVE2, USB_PRODUCT_CREATIVE2_VOIP_BLASTER, + 0, + "Creative Labs", + "Voip Blaster", + }, + { + USB_VENDOR_CREATIVE3, USB_PRODUCT_CREATIVE3_OPTICAL_MOUSE, + 0, + "Creative Labs", + "Notebook Optical Mouse", + }, + { + USB_VENDOR_CSR, USB_PRODUCT_CSR_BT_DONGLE, + 0, + "Cambridge Silicon Radio", + "Bluetooth USB dongle", + }, + { + USB_VENDOR_CSR, USB_PRODUCT_CSR_CSRDFU, + 0, + "Cambridge Silicon Radio", + "USB Bluetooth Device in DFU State", + }, + { + USB_VENDOR_CTX, USB_PRODUCT_CTX_EX1300, + 0, + "Chuntex", + "Ex1300 hub", + }, + { + USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_HX550C, + 0, + "Curitel Communications Inc", + "CDMA 2000 1xRTT USB modem (HX-550C)", + }, + { + USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_HX57XB, + 0, + "Curitel Communications Inc", + "CDMA 2000 1xRTT USB modem (HX-570/575B/PR-600)", + }, + { + USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, + 0, + "Curitel Communications Inc", + "Broadband Wireless modem", + }, + { + USB_VENDOR_CYBERPOWER, USB_PRODUCT_CYBERPOWER_1500CAVRLCD, + 0, + "Cyber Power Systems, Inc.", + "1500CAVRLCD", + }, + { + USB_VENDOR_CYBERTAN, USB_PRODUCT_CYBERTAN_TG54USB, + 0, + "CyberTAN Technology", + "TG54USB", + }, + { + USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_MOUSE, + 0, + "Cypress Semiconductor", + "mouse", + }, + { + USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_THERMO, + 0, + "Cypress Semiconductor", + "thermometer", + }, + { + USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_WISPY1A, + 0, + "Cypress Semiconductor", + "MetaGeek Wi-Spy", + }, + { + USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_KBDHUB, + 0, + "Cypress Semiconductor", + "Keyboard/Hub", + }, + { + USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_FMRADIO, + 0, + "Cypress Semiconductor", + "FM Radio", + }, + { + USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_USBRS232, + 0, + "Cypress Semiconductor", + "USB-RS232 Interface", + }, + { + USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_SLIM_HUB, + 0, + "Cypress Semiconductor", + "Slim Hub", + }, + { + USB_VENDOR_DAISY, USB_PRODUCT_DAISY_DMC, + 0, + "Daisy Technology", + "USB MultiMedia Reader", + }, + { + USB_VENDOR_DALLAS, USB_PRODUCT_DALLAS_J6502, + 0, + "Dallas Semiconductor", + "J-6502 speakers", + }, + { + USB_VENDOR_DELL, USB_PRODUCT_DELL_PORT, + 0, + "Dell", + "Port Replicator", + }, + { + USB_VENDOR_DELL, USB_PRODUCT_DELL_AIO926, + 0, + "Dell", + "Photo AIO Printer 926", + }, + { + USB_VENDOR_DELL, USB_PRODUCT_DELL_BC02, + 0, + "Dell", + "BC02 Bluetooth USB Adapter", + }, + { + USB_VENDOR_DELL, USB_PRODUCT_DELL_PRISM_GT_1, + 0, + "Dell", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_DELL, USB_PRODUCT_DELL_TM350, + 0, + "Dell", + "TrueMobile 350 Bluetooth USB Adapter", + }, + { + USB_VENDOR_DELL, USB_PRODUCT_DELL_PRISM_GT_2, + 0, + "Dell", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_DELL, USB_PRODUCT_DELL_U740, + 0, + "Dell", + "Dell U740 CDMA", + }, + { + USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, + 0, + "DeLorme", + "Earthmate GPS", + }, + { + USB_VENDOR_DESKNOTE, USB_PRODUCT_DESKNOTE_UCR_61S2B, + 0, + "Desknote", + "UCR-61S2B", + }, + { + USB_VENDOR_DIAMOND, USB_PRODUCT_DIAMOND_RIO500USB, + 0, + "Diamond", + "Rio 500 USB", + }, + { + USB_VENDOR_DICKSMITH, USB_PRODUCT_DICKSMITH_RT2573, + 0, + "Dick Smith Electronics", + "RT2573", + }, + { + USB_VENDOR_DICKSMITH, USB_PRODUCT_DICKSMITH_CWD854F, + 0, + "Dick Smith Electronics", + "C-Net CWD-854 rev F", + }, + { + USB_VENDOR_DIGI, USB_PRODUCT_DIGI_ACCELEPORT2, + 0, + "Digi International", + "AccelePort USB 2", + }, + { + USB_VENDOR_DIGI, USB_PRODUCT_DIGI_ACCELEPORT4, + 0, + "Digi International", + "AccelePort USB 4", + }, + { + USB_VENDOR_DIGI, USB_PRODUCT_DIGI_ACCELEPORT8, + 0, + "Digi International", + "AccelePort USB 8", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DUBE100, + 0, + "D-Link", + "10/100 Ethernet", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX4, + 0, + "D-Link", + "10/100 Ethernet", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWL120E, + 0, + "D-Link", + "DWL-120 rev E", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWL122, + 0, + "D-Link", + "DWL-122", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLG120, + 0, + "D-Link", + "DWL-G120", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWL120F, + 0, + "D-Link", + "DWL-120 rev F", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLAG132, + 0, + "D-Link", + "DWL-AG132", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLAG132_NF, + 0, + "D-Link", + "DWL-AG132 (no firmware)", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLG132, + 0, + "D-Link", + "DWL-G132", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLG132_NF, + 0, + "D-Link", + "DWL-G132 (no firmware)", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLAG122, + 0, + "D-Link", + "DWL-AG122", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLAG122_NF, + 0, + "D-Link", + "DWL-AG122 (no firmware)", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLG122, + 0, + "D-Link", + "DWL-G122 b1 Wireless Adapter", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DUBE100B1, + 0, + "D-Link", + "DUB-E100 rev B1", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650C, + 0, + "D-Link", + "10Mbps Ethernet", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX1, + 0, + "D-Link", + "10/100 Ethernet", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX, + 0, + "D-Link", + "10/100 Ethernet", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX_PNA, + 0, + "D-Link", + "1/10/100 Ethernet", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX3, + 0, + "D-Link", + "10/100 Ethernet", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX2, + 0, + "D-Link", + "10/100 Ethernet", + }, + { + USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650, + 0, + "D-Link", + "10/100 Ethernet", + }, + { + USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWLG122C1, + 0, + "D-Link", + "DWL-G122 c1", + }, + { + USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_WUA1340, + 0, + "D-Link", + "WUA-1340", + }, + { + USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA111, + 0, + "D-Link", + "DWA-111", + }, + { + USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA110, + 0, + "D-Link", + "DWA-110", + }, + { + USB_VENDOR_DMI, USB_PRODUCT_DMI_CFSM_RW, + 0, + "DMI", + "CF/SM Reader/Writer", + }, + { + USB_VENDOR_DRAYTEK, USB_PRODUCT_DRAYTEK_VIGOR550, + 0, + "DrayTek", + "Vigor550", + }, + { + USB_VENDOR_DYNASTREAM, USB_PRODUCT_DYNASTREAM_ANTDEVBOARD, + 0, + "Dynastream Innovations", + "ANT dev board", + }, + { + USB_VENDOR_EIZO, USB_PRODUCT_EIZO_HUB, + 0, + "EIZO", + "hub", + }, + { + USB_VENDOR_EIZO, USB_PRODUCT_EIZO_MONITOR, + 0, + "EIZO", + "monitor", + }, + { + USB_VENDOR_ELCON, USB_PRODUCT_ELCON_PLAN, + 0, + "ELCON Systemtechnik", + "Goldpfeil P-LAN", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_MOUSE29UO, + 0, + "Elecom", + "mouse 29UO", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX0, + 0, + "Elecom", + "LD-USB/TX", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX1, + 0, + "Elecom", + "LD-USB/TX", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBLTX, + 0, + "Elecom", + "LD-USBL/TX", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX2, + 0, + "Elecom", + "LD-USB/TX", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSB20, + 0, + "Elecom", + "LD-USB20", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_UCSGT, + 0, + "Elecom", + "UC-SGT", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_UCSGT0, + 0, + "Elecom", + "UC-SGT", + }, + { + USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX3, + 0, + "Elecom", + "LD-USB/TX", + }, + { + USB_VENDOR_ELSA, USB_PRODUCT_ELSA_MODEM1, + 0, + "ELSA", + "ELSA Modem Board", + }, + { + USB_VENDOR_ELSA, USB_PRODUCT_ELSA_USB2ETHERNET, + 0, + "ELSA", + "Microlink USB2Ethernet", + }, + { + USB_VENDOR_EMS, USB_PRODUCT_EMS_DUAL_SHOOTER, + 0, + "EMS Production", + "PSX gun controller converter", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_1S, + 0, + "Entrega", + "1S serial", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_2S, + 0, + "Entrega", + "2S serial", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_1S25, + 0, + "Entrega", + "1S25 serial", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_4S, + 0, + "Entrega", + "4S serial", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_E45, + 0, + "Entrega", + "E45 Ethernet", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_CENTRONICS, + 0, + "Entrega", + "Parallel Port", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_XX1, + 0, + "Entrega", + "Ethernet", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_1S9, + 0, + "Entrega", + "1S9 serial", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_EZUSB, + 0, + "Entrega", + "EZ-USB", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_2U4S, + 0, + "Entrega", + "2U4S serial/usb hub", + }, + { + USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_XX2, + 0, + "Entrega", + "Ethernet", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_PRINTER1, + 0, + "Seiko Epson", + "USB Printer", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_PRINTER2, + 0, + "Seiko Epson", + "ISD USB Smart Cable for Mac", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_PRINTER3, + 0, + "Seiko Epson", + "ISD USB Smart Cable", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_PRINTER5, + 0, + "Seiko Epson", + "USB Printer", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_636, + 0, + "Seiko Epson", + "Perfection 636U / 636Photo scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_610, + 0, + "Seiko Epson", + "Perfection 610 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1200, + 0, + "Seiko Epson", + "Perfection 1200U / 1200Photo scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1600, + 0, + "Seiko Epson", + "Expression 1600 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1640, + 0, + "Seiko Epson", + "Perfection 1640SU scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1240, + 0, + "Seiko Epson", + "Perfection 1240U / 1240Photo scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_640U, + 0, + "Seiko Epson", + "Perfection 640U scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1250, + 0, + "Seiko Epson", + "Perfection 1250U / 1250Photo scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1650, + 0, + "Seiko Epson", + "Perfection 1650 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_GT9700F, + 0, + "Seiko Epson", + "GT-9700F scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_GT9300UF, + 0, + "Seiko Epson", + "GT-9300UF scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3200, + 0, + "Seiko Epson", + "Perfection 3200 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1260, + 0, + "Seiko Epson", + "Perfection 1260 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1660, + 0, + "Seiko Epson", + "Perfection 1660 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1670, + 0, + "Seiko Epson", + "Perfection 1670 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1270, + 0, + "Seiko Epson", + "Perfection 1270 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_2480, + 0, + "Seiko Epson", + "Perfection 2480 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3590, + 0, + "Seiko Epson", + "Perfection 3590 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4990, + 0, + "Seiko Epson", + "Perfection 4990 Photo scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_STYLUS_875DC, + 0, + "Seiko Epson", + "Stylus Photo 875DC Card Reader", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_STYLUS_895, + 0, + "Seiko Epson", + "Stylus Photo 895 Card Reader", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_CX5400, + 0, + "Seiko Epson", + "CX5400 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3500, + 0, + "Seiko Epson", + "CX-3500/3600/3650 MFP", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_RX425, + 0, + "Seiko Epson", + "Stylus Photo RX425 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4800, + 0, + "Seiko Epson", + "CX4800 MP scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4200, + 0, + "Seiko Epson", + "CX4200 MP scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_5000, + 0, + "Seiko Epson", + "DX-50x0 MFP scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_6000, + 0, + "Seiko Epson", + "DX-60x0 MFP scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_DX7400, + 0, + "Seiko Epson", + "DX7400/CX7300 scanner", + }, + { + USB_VENDOR_EPSON, USB_PRODUCT_EPSON_DX8400, + 0, + "Seiko Epson", + "DX8400 scanner", + }, + { + USB_VENDOR_ETEK, USB_PRODUCT_ETEK_1COM, + 0, + "e-TEK Labs", + "Serial", + }, + { + USB_VENDOR_EXTENDED, USB_PRODUCT_EXTENDED_XTNDACCESS, + 0, + "Extended Systems", + "XTNDAccess IrDA", + }, + { + USB_VENDOR_FEIYA, USB_PRODUCT_FEIYA_5IN1, + 0, + "Feiya", + "5-in-1 Card Reader", + }, + { + USB_VENDOR_FIBERLINE, USB_PRODUCT_FIBERLINE_WL430U, + 0, + "Fiberline", + "WL-430U", + }, + { + USB_VENDOR_FOSSIL, USB_PRODUCT_FOSSIL_WRISTPDA, + 0, + "Fossil, Inc", + "Wrist PDA", + }, + { + USB_VENDOR_FREECOM, USB_PRODUCT_FREECOM_DVD, + 0, + "Freecom", + "DVD drive", + }, + { + USB_VENDOR_FSC, USB_PRODUCT_FSC_E5400, + 0, + "Fujitsu Siemens Computers", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_8U100AX, + 0, + "Future Technology Devices", + "8U100AX Serial", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_8U232AM, + 0, + "Future Technology Devices", + "8U232AM Serial", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_2232C, + 0, + "Future Technology Devices", + "FT2232C Dual port Serial", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13M, + 0, + "Future Technology Devices", + "OpenPort 1.3 Mitsubishi", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13S, + 0, + "Future Technology Devices", + "OpenPort 1.3 Subaru", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13U, + 0, + "Future Technology Devices", + "OpenPort 1.3 Universal", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EISCOU, + 0, + "Future Technology Devices", + "Expert ISDN Control USB", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_UOPTBR, + 0, + "Future Technology Devices", + "USB-RS232 OptoBridge", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EMCU2D, + 0, + "Future Technology Devices", + "Expert mouseCLOCK USB II", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_PCMSFU, + 0, + "Future Technology Devices", + "Precision Clock MSF USB", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EMCU2H, + 0, + "Future Technology Devices", + "Expert mouseCLOCK USB II HBG", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_USBSERIAL, + 0, + "Future Technology Devices", + "Matrix Orbital USB Serial", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_MX2_3, + 0, + "Future Technology Devices", + "Matrix Orbital MX2 or MX3", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_MX4_5, + 0, + "Future Technology Devices", + "Matrix Orbital MX4 or MX5", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_LK202, + 0, + "Future Technology Devices", + "Matrix Orbital VK/LK202 Family", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_LK204, + 0, + "Future Technology Devices", + "Matrix Orbital VK/LK204 Family", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_632, + 0, + "Future Technology Devices", + "Crystalfontz CFA-632 USB LCD", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_634, + 0, + "Future Technology Devices", + "Crystalfontz CFA-634 USB LCD", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_633, + 0, + "Future Technology Devices", + "Crystalfontz CFA-633 USB LCD", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_631, + 0, + "Future Technology Devices", + "Crystalfontz CFA-631 USB LCD", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_635, + 0, + "Future Technology Devices", + "Crystalfontz CFA-635 USB LCD", + }, + { + USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SEMC_DSS20, + 0, + "Future Technology Devices", + "SEMC DSS-20 SyncStation", + }, + { + USB_VENDOR_FUJIPHOTO, USB_PRODUCT_FUJIPHOTO_MASS0100, + 0, + "Fuji Photo Film", + "Mass Storage", + }, + { + USB_VENDOR_FUJITSU, USB_PRODUCT_FUJITSU_AH_F401U, + 0, + "Fujitsu", + "AH-F401U Air H device", + }, + { + USB_VENDOR_GARMIN, USB_PRODUCT_GARMIN_IQUE_3600, + 0, + "Garmin International", + "iQue 3600", + }, + { + USB_VENDOR_GENERALINSTMNTS, USB_PRODUCT_GENERALINSTMNTS_SB5100, + 0, + "General Instruments (Motorola)", + "SURFboard SB5100 Cable modem", + }, + { + USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL620USB, + 0, + "Genesys Logic", + "GL620USB Host-Host interface", + }, + { + USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL650, + 0, + "Genesys Logic", + "GL650 Hub", + }, + { + USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB, + 0, + "Genesys Logic", + "GL641USB CompactFlash Card Reader", + }, + { + USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB2IDE_2, + 0, + "Genesys Logic", + "GL641USB USB-IDE Bridge No 2", + }, + { + USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB2IDE, + 0, + "Genesys Logic", + "GL641USB USB-IDE Bridge", + }, + { + USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB_2, + 0, + "Genesys Logic", + "GL641USB 6-in-1 Card Reader", + }, + { + USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GN54G, + 0, + "GIGABYTE", + "GN-54G", + }, + { + USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNBR402W, + 0, + "GIGABYTE", + "GN-BR402W", + }, + { + USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWLBM101, + 0, + "GIGABYTE", + "GN-WLBM101", + }, + { + USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWBKG, + 0, + "GIGABYTE", + "GN-WBKG", + }, + { + USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWB01GS, + 0, + "GIGABYTE", + "GN-WB01GS", + }, + { + USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWI05GS, + 0, + "GIGABYTE", + "GN-WI05GS", + }, + { + USB_VENDOR_GIGASET, USB_PRODUCT_GIGASET_WLAN, + 0, + "Gigaset", + "WLAN", + }, + { + USB_VENDOR_GIGASET, USB_PRODUCT_GIGASET_SMCWUSBTG, + 0, + "Gigaset", + "SMCWUSBT-G", + }, + { + USB_VENDOR_GIGASET, USB_PRODUCT_GIGASET_SMCWUSBTG_NF, + 0, + "Gigaset", + "SMCWUSBT-G (no firmware)", + }, + { + USB_VENDOR_GIGASET, USB_PRODUCT_GIGASET_AR5523, + 0, + "Gigaset", + "AR5523", + }, + { + USB_VENDOR_GIGASET, USB_PRODUCT_GIGASET_AR5523_NF, + 0, + "Gigaset", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_GIGASET, USB_PRODUCT_GIGASET_RT2573, + 0, + "Gigaset", + "RT2573", + }, + { + USB_VENDOR_GLOBALSUN, USB_PRODUCT_GLOBALSUN_AR5523_1, + 0, + "Global Sun Technology", + "AR5523", + }, + { + USB_VENDOR_GLOBALSUN, USB_PRODUCT_GLOBALSUN_AR5523_1_NF, + 0, + "Global Sun Technology", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_GLOBALSUN, USB_PRODUCT_GLOBALSUN_AR5523_2, + 0, + "Global Sun Technology", + "AR5523", + }, + { + USB_VENDOR_GLOBALSUN, USB_PRODUCT_GLOBALSUN_AR5523_2_NF, + 0, + "Global Sun Technology", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_GLOBESPAN, USB_PRODUCT_GLOBESPAN_PRISM_GT_1, + 0, + "Globespan", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_GLOBESPAN, USB_PRODUCT_GLOBESPAN_PRISM_GT_2, + 0, + "Globespan", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_GMATE, USB_PRODUCT_GMATE_YP3X00, + 0, + "G.Mate, Inc", + "YP3X00 PDA", + }, + { + USB_VENDOR_GOHUBS, USB_PRODUCT_GOHUBS_GOCOM232, + 0, + "GoHubs", + "GoCOM232 Serial", + }, + { + USB_VENDOR_GOODWAY, USB_PRODUCT_GOODWAY_GWUSB2E, + 0, + "Good Way Technology", + "GWUSB2E", + }, + { + USB_VENDOR_GOODWAY, USB_PRODUCT_GOODWAY_RT2573, + 0, + "Good Way Technology", + "RT2573", + }, + { + USB_VENDOR_GRAVIS, USB_PRODUCT_GRAVIS_GAMEPADPRO, + 0, + "Advanced Gravis Computer", + "GamePad Pro", + }, + { + USB_VENDOR_GREENHOUSE, USB_PRODUCT_GREENHOUSE_KANA21, + 0, + "GREENHOUSE", + "CF-writer with MP3", + }, + { + USB_VENDOR_GRIFFIN, USB_PRODUCT_GRIFFIN_IMATE, + 0, + "Griffin Technology", + "iMate, ADB Adapter", + }, + { + USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_DALEADER, + 0, + "Guillemot", + "DA Leader", + }, + { + USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254, + 0, + "Guillemot", + "HWGUSB2-54 WLAN", + }, + { + USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254LB, + 0, + "Guillemot", + "HWGUSB2-54-LB", + }, + { + USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254V2AP, + 0, + "Guillemot", + "HWGUSB2-54V2-AP", + }, + { + USB_VENDOR_HAGIWARA, USB_PRODUCT_HAGIWARA_FGSM, + 0, + "Hagiwara Sys-Com", + "FlashGate SmartMedia Card Reader", + }, + { + USB_VENDOR_HAGIWARA, USB_PRODUCT_HAGIWARA_FGCF, + 0, + "Hagiwara Sys-Com", + "FlashGate CompactFlash Card Reader", + }, + { + USB_VENDOR_HAGIWARA, USB_PRODUCT_HAGIWARA_FG, + 0, + "Hagiwara Sys-Com", + "FlashGate", + }, + { + USB_VENDOR_HAL, USB_PRODUCT_HAL_IMR001, + 0, + "HAL Corporation", + "Crossam2+USB IR commander", + }, + { + USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_VISOR, + 0, + "Handspring", + "Handspring Visor", + }, + { + USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_TREO, + 0, + "Handspring", + "Handspring Treo", + }, + { + USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_TREO600, + 0, + "Handspring", + "Handspring Treo 600", + }, + { + USB_VENDOR_HAUPPAUGE, USB_PRODUCT_HAUPPAUGE_WINTV_USB_FM, + 0, + "Hauppauge Computer Works", + "WinTV USB FM", + }, + { + USB_VENDOR_HAWKING, USB_PRODUCT_HAWKING_UF100, + 0, + "Hawking", + "10/100 USB Ethernet", + }, + { + USB_VENDOR_HITACHI, USB_PRODUCT_HITACHI_DVDCAM_DZ_MV100A, + 0, + "Hitachi", + "DVD-CAM DZ-MV100A Camcorder", + }, + { + USB_VENDOR_HITACHI, USB_PRODUCT_HITACHI_DVDCAM_USB, + 0, + "Hitachi", + "DVDCAM USB HS Interface", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_895C, + 0, + "Hewlett Packard", + "DeskJet 895C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_4100C, + 0, + "Hewlett Packard", + "Scanjet 4100C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_S20, + 0, + "Hewlett Packard", + "Photosmart S20", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_880C, + 0, + "Hewlett Packard", + "DeskJet 880C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_4200C, + 0, + "Hewlett Packard", + "ScanJet 4200C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_CDWRITERPLUS, + 0, + "Hewlett Packard", + "CD-Writer Plus", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_KBDHUB, + 0, + "Hewlett Packard", + "Multimedia Keyboard Hub", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_G55XI, + 0, + "Hewlett Packard", + "OfficeJet G55xi", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_HN210W, + 0, + "Hewlett Packard", + "HN210W 802.11b WLAN", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_49GPLUS, + 0, + "Hewlett Packard", + "49g+ graphing calculator", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_6200C, + 0, + "Hewlett Packard", + "ScanJet 6200C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_S20b, + 0, + "Hewlett Packard", + "PhotoSmart S20", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_815C, + 0, + "Hewlett Packard", + "DeskJet 815C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_3300C, + 0, + "Hewlett Packard", + "ScanJet 3300C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_CDW8200, + 0, + "Hewlett Packard", + "CD-Writer Plus 8200e", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_MMKEYB, + 0, + "Hewlett Packard", + "Multimedia keyboard", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_1220C, + 0, + "Hewlett Packard", + "DeskJet 1220C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_810C, + 0, + "Hewlett Packard", + "DeskJet 810C/812C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_4300C, + 0, + "Hewlett Packard", + "Scanjet 4300C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_CDW4E, + 0, + "Hewlett Packard", + "CD-Writer+ CD-4e", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_G85XI, + 0, + "Hewlett Packard", + "OfficeJet G85xi", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_1200, + 0, + "Hewlett Packard", + "LaserJet 1200", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_5200C, + 0, + "Hewlett Packard", + "Scanjet 5200C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_830C, + 0, + "Hewlett Packard", + "DeskJet 830C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_3400CSE, + 0, + "Hewlett Packard", + "ScanJet 3400cse", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_6300C, + 0, + "Hewlett Packard", + "Scanjet 6300C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_840C, + 0, + "Hewlett Packard", + "DeskJet 840c", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_2200C, + 0, + "Hewlett Packard", + "ScanJet 2200C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_5300C, + 0, + "Hewlett Packard", + "Scanjet 5300C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_4400C, + 0, + "Hewlett Packard", + "Scanjet 4400C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_82x0C, + 0, + "Hewlett Packard", + "Scanjet 82x0C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_2300D, + 0, + "Hewlett Packard", + "Laserjet 2300d", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_970CSE, + 0, + "Hewlett Packard", + "Deskjet 970Cse", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_5400C, + 0, + "Hewlett Packard", + "Scanjet 5400C", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_2215, + 0, + "Hewlett Packard", + "iPAQ 22xx/Jornada 548", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_568J, + 0, + "Hewlett Packard", + "Jornada 568", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_930C, + 0, + "Hewlett Packard", + "DeskJet 930c", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_P2000U, + 0, + "Hewlett Packard", + "Inkjet P-2000U", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_640C, + 0, + "Hewlett Packard", + "DeskJet 640c", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_4670V, + 0, + "Hewlett Packard", + "ScanJet 4670v", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_P1100, + 0, + "Hewlett Packard", + "Photosmart P1100", + }, + { + USB_VENDOR_HP, USB_PRODUCT_HP_HN210E, + 0, + "Hewlett Packard", + "Ethernet HN210E", + }, + { + USB_VENDOR_HP2, USB_PRODUCT_HP2_C500, + 0, + "Hewlett Packard", + "PhotoSmart C500", + }, + { + USB_VENDOR_HTC, USB_PRODUCT_HTC_WINMOBILE, + 0, + "HTC", + "HTC USB Sync", + }, + { + USB_VENDOR_HTC, USB_PRODUCT_HTC_PPC6700MODEM, + 0, + "HTC", + "PPC6700 Modem", + }, + { + USB_VENDOR_HTC, USB_PRODUCT_HTC_SMARTPHONE, + 0, + "HTC", + "SmartPhone USB Sync", + }, + { + USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_MOBILE, + 0, + "Huawei Technologies", + "Huawei Mobile", + }, + { + USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E270, + 0, + "Huawei Technologies", + "Huawei HSPA modem", + }, + { + USB_VENDOR_HUAWEI3COM, USB_PRODUCT_HUAWEI3COM_WUB320G, + 0, + "Huawei-3Com", + "Aolynk WUB320g", + }, + { + USB_VENDOR_IBM, USB_PRODUCT_IBM_USBCDROMDRIVE, + 0, + "IBM", + "USB CD-ROM Drive", + }, + { + USB_VENDOR_IMAGINATION, USB_PRODUCT_IMAGINATION_DBX1, + 0, + "Imagination Technologies", + "DBX1 DSP core", + }, + { + USB_VENDOR_INSIDEOUT, USB_PRODUCT_INSIDEOUT_EDGEPORT4, + 0, + "Inside Out Networks", + "EdgePort/4 serial ports", + }, + { + USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_F5U002, + 0, + "In-System Design", + "Parallel printer", + }, + { + USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_ATAPI, + 0, + "In-System Design", + "ATAPI Adapter", + }, + { + USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_ISD110, + 0, + "In-System Design", + "IDE Adapter ISD110", + }, + { + USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_ISD105, + 0, + "In-System Design", + "IDE Adapter ISD105", + }, + { + USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_USBCABLE, + 0, + "In-System Design", + "USB cable", + }, + { + USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_STORAGE_V2, + 0, + "In-System Design", + "USB Storage Adapter V2", + }, + { + USB_VENDOR_INTEL, USB_PRODUCT_INTEL_EASYPC_CAMERA, + 0, + "Intel", + "Easy PC Camera", + }, + { + USB_VENDOR_INTEL, USB_PRODUCT_INTEL_TESTBOARD, + 0, + "Intel", + "82930 test board", + }, + { + USB_VENDOR_INTERSIL, USB_PRODUCT_INTERSIL_PRISM_GT, + 0, + "Intersil", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_INTERSIL, USB_PRODUCT_INTERSIL_PRISM_2X, + 0, + "Intersil", + "Prism2.x or Atmel WLAN", + }, + { + USB_VENDOR_INTREPIDCS, USB_PRODUCT_INTREPIDCS_VALUECAN, + 0, + "Intrepid", + "ValueCAN CAN bus interface", + }, + { + USB_VENDOR_INTREPIDCS, USB_PRODUCT_INTREPIDCS_NEOVI, + 0, + "Intrepid", + "NeoVI Blue vehicle bus interface", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_IU_CD2, + 0, + "I-O Data", + "DVD Multi-plus unit iU-CD2", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_DVR_UEH8, + 0, + "I-O Data", + "DVD Multi-plus unit DVR-UEH8", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBSSMRW, + 0, + "I-O Data", + "USB-SSMRW SD-card", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBSDRW, + 0, + "I-O Data", + "USB-SDRW SD-card", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETT, + 0, + "I-O Data", + "USB ETT", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETTX, + 0, + "I-O Data", + "USB ETTX", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETTXS, + 0, + "I-O Data", + "USB ETTX", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBWNB11A, + 0, + "I-O Data", + "USB WN-B11", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBWNB11, + 0, + "I-O Data", + "USB Airport WN-B11", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_ETGUS2, + 0, + "I-O Data", + "ETG-US2", + }, + { + USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBRSAQ, + 0, + "I-O Data", + "Serial USB-RSAQ1", + }, + { + USB_VENDOR_IODATA2, USB_PRODUCT_IODATA2_USB2SC, + 0, + "I-O Data", + "USB2.0-SCSI Bridge USB2-SC", + }, + { + USB_VENDOR_IOMEGA, USB_PRODUCT_IOMEGA_ZIP100, + 0, + "Iomega", + "Zip 100", + }, + { + USB_VENDOR_IOMEGA, USB_PRODUCT_IOMEGA_ZIP250, + 0, + "Iomega", + "Zip 250", + }, + { + USB_VENDOR_ITUNERNET, USB_PRODUCT_ITUNERNET_USBLCD2X20, + 0, + "I-Tuner Networks", + "USB-LCD 2x20", + }, + { + USB_VENDOR_JABLOTRON, USB_PRODUCT_JABLOTRON_PC60B, + 0, + "Jablotron", + "PC-60B", + }, + { + USB_VENDOR_JATON, USB_PRODUCT_JATON_EDA, + 0, + "Jaton", + "Ethernet", + }, + { + USB_VENDOR_JVC, USB_PRODUCT_JVC_GR_DX95, + 0, + "JVC", + "GR-DX95", + }, + { + USB_VENDOR_JVC, USB_PRODUCT_JVC_MP_PRX1, + 0, + "JVC", + "MP-PRX1 Ethernet", + }, + { + USB_VENDOR_JRC, USB_PRODUCT_JRC_AH_J3001V_J3002V, + 0, + "Japan Radio Company", + "AirH PHONE AH-J3001V/J3002V", + }, + { + USB_VENDOR_KAWATSU, USB_PRODUCT_KAWATSU_MH4000P, + 0, + "Kawatsu Semiconductor", + "MiniHub 4000P", + }, + { + USB_VENDOR_KEISOKUGIKEN, USB_PRODUCT_KEISOKUGIKEN_USBDAQ, + 0, + "Keisokugiken", + "HKS-0200 USBDAQ", + }, + { + USB_VENDOR_KENSINGTON, USB_PRODUCT_KENSINGTON_ORBIT, + 0, + "Kensington", + "Orbit USB/PS2 trackball", + }, + { + USB_VENDOR_KENSINGTON, USB_PRODUCT_KENSINGTON_TURBOBALL, + 0, + "Kensington", + "TurboBall", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA28_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-28 serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA28X_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-28X serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-19 serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA18_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-18 serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA18X_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-18X serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19W_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-19W serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19, + 0, + "Keyspan / InnoSys Inc.", + "USA-19 serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19W, + 0, + "Keyspan / InnoSys Inc.", + "USA-19W serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA49W_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-49W serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA49W, + 0, + "Keyspan / InnoSys Inc.", + "USA-49W serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19QI_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-19QI serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19QI, + 0, + "Keyspan / InnoSys Inc.", + "USA-19QI serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19Q_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-19Q serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19Q, + 0, + "Keyspan / InnoSys Inc.", + "USA-19Q serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA28, + 0, + "Keyspan / InnoSys Inc.", + "USA-28 serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA28XXB, + 0, + "Keyspan / InnoSys Inc.", + "USA-28X/XB serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA18, + 0, + "Keyspan / InnoSys Inc.", + "USA-18 serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA18X, + 0, + "Keyspan / InnoSys Inc.", + "USA-18X serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA28XB_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-28XB serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA28XA_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-28XB serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA28XA, + 0, + "Keyspan / InnoSys Inc.", + "USA-28XA serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA18XA_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-18XA serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA18XA, + 0, + "Keyspan / InnoSys Inc.", + "USA-18XA serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19QW_NF, + 0, + "Keyspan / InnoSys Inc.", + "USA-19WQ serial Adapter (no firmware)", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19QW, + 0, + "Keyspan / InnoSys Inc.", + "USA-19WQ serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19HA, + 0, + "Keyspan / InnoSys Inc.", + "USA-19HS serial Adapter", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_UIA10, + 0, + "Keyspan / InnoSys Inc.", + "UIA-10 remote control", + }, + { + USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_UIA11, + 0, + "Keyspan / InnoSys Inc.", + "UIA-11 remote control", + }, + { + USB_VENDOR_KINGSTON, USB_PRODUCT_KINGSTON_XX1, + 0, + "Kingston Technology", + "Ethernet", + }, + { + USB_VENDOR_KINGSTON, USB_PRODUCT_KINGSTON_KNU101TX, + 0, + "Kingston Technology", + "KNU101TX USB Ethernet", + }, + { + USB_VENDOR_KLSI, USB_PRODUCT_KLSI_DUH3E10BT, + 0, + "Kawasaki LSI", + "USB Ethernet", + }, + { + USB_VENDOR_KLSI, USB_PRODUCT_KLSI_DUH3E10BTN, + 0, + "Kawasaki LSI", + "USB Ethernet", + }, + { + USB_VENDOR_KODAK, USB_PRODUCT_KODAK_DC220, + 0, + "Eastman Kodak", + "Digital Science DC220", + }, + { + USB_VENDOR_KODAK, USB_PRODUCT_KODAK_DC260, + 0, + "Eastman Kodak", + "Digital Science DC260", + }, + { + USB_VENDOR_KODAK, USB_PRODUCT_KODAK_DC265, + 0, + "Eastman Kodak", + "Digital Science DC265", + }, + { + USB_VENDOR_KODAK, USB_PRODUCT_KODAK_DC290, + 0, + "Eastman Kodak", + "Digital Science DC290", + }, + { + USB_VENDOR_KODAK, USB_PRODUCT_KODAK_DC240, + 0, + "Eastman Kodak", + "Digital Science DC240", + }, + { + USB_VENDOR_KODAK, USB_PRODUCT_KODAK_DC280, + 0, + "Eastman Kodak", + "Digital Science DC280", + }, + { + USB_VENDOR_KONICA, USB_PRODUCT_KONICA_CAMERA, + 0, + "Konica", + "Digital Color Camera", + }, + { + USB_VENDOR_KYE, USB_PRODUCT_KYE_NICHE, + 0, + "KYE Systems", + "Niche mouse", + }, + { + USB_VENDOR_KYE, USB_PRODUCT_KYE_NETSCROLL, + 0, + "KYE Systems", + "Genius NetScroll mouse", + }, + { + USB_VENDOR_KYE, USB_PRODUCT_KYE_FLIGHT2000, + 0, + "KYE Systems", + "Flight 2000 joystick", + }, + { + USB_VENDOR_KYE, USB_PRODUCT_KYE_VIVIDPRO, + 0, + "KYE Systems", + "ColorPage Vivid-Pro scanner", + }, + { + USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S3X, + 0, + "Kyocera Wireless Corp.", + "Finecam S3x", + }, + { + USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S4, + 0, + "Kyocera Wireless Corp.", + "Finecam S4", + }, + { + USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S5, + 0, + "Kyocera Wireless Corp.", + "Finecam S5", + }, + { + USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_L3, + 0, + "Kyocera Wireless Corp.", + "Finecam L3", + }, + { + USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, + 0, + "Kyocera Wireless Corp.", + "AH-K3001V", + }, + { + USB_VENDOR_KYOCERA2, USB_PRODUCT_KYOCERA2_CDMA_MSM_K, + 0, + "Kyocera Wireless Corp.", + "Qualcomm Kyocera CDMA Technologies MSM", + }, + { + USB_VENDOR_LACIE, USB_PRODUCT_LACIE_HD, + 0, + "LaCie", + "Hard Disk", + }, + { + USB_VENDOR_LACIE, USB_PRODUCT_LACIE_CDRW, + 0, + "LaCie", + "CD R/W", + }, + { + USB_VENDOR_LEXAR, USB_PRODUCT_LEXAR_JUMPSHOT, + 0, + "Lexar Media", + "jumpSHOT CompactFlash Reader", + }, + { + USB_VENDOR_LEXAR, USB_PRODUCT_LEXAR_CF_READER, + 0, + "Lexar Media", + "USB CF Reader", + }, + { + USB_VENDOR_LEXMARK, USB_PRODUCT_LEXMARK_S2450, + 0, + "Lexmark International", + "Optra S 2450", + }, + { + USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_MAUSB2, + 0, + "Linksys", + "Camedia MAUSB-2", + }, + { + USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TX1, + 0, + "Linksys", + "USB10TX", + }, + { + USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10T, + 0, + "Linksys", + "USB10T Ethernet", + }, + { + USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB100TX, + 0, + "Linksys", + "USB100TX Ethernet", + }, + { + USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB100H1, + 0, + "Linksys", + "USB100H1 Ethernet/HPNA", + }, + { + USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TA, + 0, + "Linksys", + "USB10TA Ethernet", + }, + { + USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TX2, + 0, + "Linksys", + "USB10TX", + }, + { + USB_VENDOR_LINKSYS2, USB_PRODUCT_LINKSYS2_WUSB11, + 0, + "Linksys", + "WUSB11 Wireless Adapter", + }, + { + USB_VENDOR_LINKSYS2, USB_PRODUCT_LINKSYS2_USB200M, + 0, + "Linksys", + "USB 2.0 10/100 Ethernet", + }, + { + USB_VENDOR_LINKSYS3, USB_PRODUCT_LINKSYS3_WUSB11v28, + 0, + "Linksys", + "WUSB11 v2.8 Wireless Adapter", + }, + { + USB_VENDOR_LINKSYS4, USB_PRODUCT_LINKSYS4_USB1000, + 0, + "Linksys", + "USB1000", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_M2452, + 0, + "Logitech", + "M2452 keyboard", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_M4848, + 0, + "Logitech", + "M4848 mouse", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_PAGESCAN, + 0, + "Logitech", + "PageScan", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAMWEB, + 0, + "Logitech", + "QuickCam Web", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAMPRO, + 0, + "Logitech", + "QuickCam Pro", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAMEXP, + 0, + "Logitech", + "QuickCam Express", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAM, + 0, + "Logitech", + "QuickCam", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_N43, + 0, + "Logitech", + "N43", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_N48, + 0, + "Logitech", + "N48 mouse", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_MBA47, + 0, + "Logitech", + "M-BA47 mouse", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WMMOUSE, + 0, + "Logitech", + "WingMan Gaming Mouse", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_BD58, + 0, + "Logitech", + "BD58 mouse", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_UN58A, + 0, + "Logitech", + "iFeel Mouse", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_UN53B, + 0, + "Logitech", + "iFeel MouseMan", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WMPAD, + 0, + "Logitech", + "WingMan GamePad Extreme", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WMRPAD, + 0, + "Logitech", + "WingMan RumblePad", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_WMJOY, + 0, + "Logitech", + "WingMan Force joystick", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_BB13, + 0, + "Logitech", + "USB-PS/2 Trackball", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_RK53, + 0, + "Logitech", + "Cordless mouse", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_RB6, + 0, + "Logitech", + "Cordless keyboard", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_MX700, + 0, + "Logitech", + "Cordless optical mouse", + }, + { + USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_QUICKCAMPRO2, + 0, + "Logitech", + "QuickCam Pro", + }, + { + USB_VENDOR_LOGITEC, USB_PRODUCT_LOGITEC_LDR_H443SU2, + 0, + "Logitec", + "DVD Multi-plus unit LDR-H443SU2", + }, + { + USB_VENDOR_LOGITEC, USB_PRODUCT_LOGITEC_LDR_H443U2, + 0, + "Logitec", + "DVD Multi-plus unit LDR-H443U2", + }, + { + USB_VENDOR_LUCENT, USB_PRODUCT_LUCENT_EVALKIT, + 0, + "Lucent", + "USS-720 evaluation kit", + }, + { + USB_VENDOR_LUWEN, USB_PRODUCT_LUWEN_EASYDISK, + 0, + "Luwen", + "EasyDisc", + }, + { + USB_VENDOR_MACALLY, USB_PRODUCT_MACALLY_MOUSE1, + 0, + "Macally", + "mouse", + }, + { + USB_VENDOR_MCT, USB_PRODUCT_MCT_HUB0100, + 0, + "MCT", + "Hub", + }, + { + USB_VENDOR_MCT, USB_PRODUCT_MCT_DU_H3SP_USB232, + 0, + "MCT", + "D-Link DU-H3SP USB BAY Hub", + }, + { + USB_VENDOR_MCT, USB_PRODUCT_MCT_USB232, + 0, + "MCT", + "USB-232 Interface", + }, + { + USB_VENDOR_MCT, USB_PRODUCT_MCT_SITECOM_USB232, + 0, + "MCT", + "Sitecom USB-232 Products", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUATX1, + 0, + "Melco", + "LUA-TX Ethernet", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUATX5, + 0, + "Melco", + "LUA-TX Ethernet", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUA2TX5, + 0, + "Melco", + "LUA2-TX Ethernet", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAKTX, + 0, + "Melco", + "LUA-KTX Ethernet", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_DUBPXXG, + 0, + "Melco", + "USB-IDE Bridge: DUB-PxxG", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAU2KTX, + 0, + "Melco", + "LUA-U2-KTX Ethernet", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54YB, + 0, + "Melco", + "WLI-U2-KG54-YB WLAN", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54, + 0, + "Melco", + "WLI-U2-KG54 WLAN", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54AI, + 0, + "Melco", + "WLI-U2-KG54-AI WLAN", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_NINWIFI, + 0, + "Melco", + "Nintendo Wi-Fi", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_PCOPRS1, + 0, + "Melco", + "PC-OP-RS1 RemoteStation", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_SG54HP, + 0, + "Melco", + "WLI-U2-SG54HP", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_G54HP, + 0, + "Melco", + "WLI-U2-G54HP", + }, + { + USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54L, + 0, + "Melco", + "WLI-U2-KG54L", + }, + { + USB_VENDOR_MERLIN, USB_PRODUCT_MERLIN_V620, + 0, + "Merlin", + "Merlin V620", + }, + { + USB_VENDOR_METAGEEK, USB_PRODUCT_METAGEEK_WISPY1B, + 0, + "MetaGeek", + "MetaGeek Wi-Spy", + }, + { + USB_VENDOR_METAGEEK, USB_PRODUCT_METAGEEK_WISPY24X, + 0, + "MetaGeek", + "MetaGeek Wi-Spy 2.4x", + }, + { + USB_VENDOR_METRICOM, USB_PRODUCT_METRICOM_RICOCHET_GS, + 0, + "Metricom", + "Ricochet GS", + }, + { + USB_VENDOR_MGE, USB_PRODUCT_MGE_UPS1, + 0, + "MGE UPS Systems", + "MGE UPS SYSTEMS PROTECTIONCENTER 1", + }, + { + USB_VENDOR_MGE, USB_PRODUCT_MGE_UPS2, + 0, + "MGE UPS Systems", + "MGE UPS SYSTEMS PROTECTIONCENTER 2", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_BT_DONGLE, + 0, + "Micro Star International", + "Bluetooth USB dongle", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_UB11B, + 0, + "Micro Star International", + "UB11B", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570, + 0, + "Micro Star International", + "RT2570", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570_2, + 0, + "Micro Star International", + "RT2570", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570_3, + 0, + "Micro Star International", + "RT2570", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_1, + 0, + "Micro Star International", + "RT2573", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_2, + 0, + "Micro Star International", + "RT2573", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_3, + 0, + "Micro Star International", + "RT2573", + }, + { + USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_4, + 0, + "Micro Star International", + "RT2573", + }, + { + USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TWINKLECAM, + 0, + "Chicony", + "TwinkleCam USB camera", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_SIDEPREC, + 0, + "Microsoft", + "SideWinder Precision Pro", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_INTELLIMOUSE, + 0, + "Microsoft", + "IntelliMouse", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_NATURALKBD, + 0, + "Microsoft", + "Natural Keyboard Elite", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_DDS80, + 0, + "Microsoft", + "Digital Sound System 80", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_SIDEWINDER, + 0, + "Microsoft", + "Sidewinder Precision Racing Wheel", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_INETPRO, + 0, + "Microsoft", + "Internet Keyboard Pro", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_TBEXPLORER, + 0, + "Microsoft", + "Trackball Explorer", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_INTELLIEYE, + 0, + "Microsoft", + "IntelliEye mouse", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_INETPRO2, + 0, + "Microsoft", + "Internet Keyboard Pro", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_MN510, + 0, + "Microsoft", + "MN510 Wireless", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_MN110, + 0, + "Microsoft", + "10/100 USB NIC", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLINTELLIMOUSE, + 0, + "Microsoft", + "Wireless Optical IntelliMouse", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLNOTEBOOK, + 0, + "Microsoft", + "Wireless Optical Mouse (Model 1023)", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLNOTEBOOK2, + 0, + "Microsoft", + "Wireless Optical Mouse 3000 (Model 1056)", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLNOTEBOOK3, + 0, + "Microsoft", + "Wireless Optical Mouse 3000 (Model 1049)", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLUSBMOUSE, + 0, + "Microsoft", + "Wireless USB Mouse", + }, + { + USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_XBOX360, + 0, + "Microsoft", + "XBOX 360 WLAN", + }, + { + USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_SCSIDB25, + 0, + "Microtech", + "USB-SCSI-DB25", + }, + { + USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_SCSIHD50, + 0, + "Microtech", + "USB-SCSI-HD50", + }, + { + USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_DPCM, + 0, + "Microtech", + "USB CameraMate", + }, + { + USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_FREECOM, + 0, + "Microtech", + "Freecom USB-IDE", + }, + { + USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_336CX, + 0, + "Microtek", + "Phantom 336CX - C3 scanner", + }, + { + USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_X6U, + 0, + "Microtek", + "ScanMaker X6 - X6U", + }, + { + USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_C6, + 0, + "Microtek", + "Phantom C6 scanner", + }, + { + USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_336CX2, + 0, + "Microtek", + "Phantom 336CX - C3 scanner", + }, + { + USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6USL, + 0, + "Microtek", + "ScanMaker V6USL", + }, + { + USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6USL2, + 0, + "Microtek", + "ScanMaker V6USL", + }, + { + USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6UL, + 0, + "Microtek", + "ScanMaker V6UL", + }, + { + USB_VENDOR_MICROTUNE, USB_PRODUCT_MICROTUNE_BT_DONGLE, + 0, + "Microtune", + "Bluetooth USB dongle", + }, + { + USB_VENDOR_MIDIMAN, USB_PRODUCT_MIDIMAN_MIDISPORT2X2, + 0, + "Midiman", + "Midisport 2x2", + }, + { + USB_VENDOR_MINDSATWORK, USB_PRODUCT_MINDSATWORK_WALLET, + 0, + "Minds At Work", + "Digital Wallet", + }, + { + USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_2300, + 0, + "Minolta", + "Dimage 2300", + }, + { + USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_S304, + 0, + "Minolta", + "Dimage S304", + }, + { + USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_X, + 0, + "Minolta", + "Dimage X", + }, + { + USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_5400, + 0, + "Minolta", + "Dimage 5400", + }, + { + USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_F300, + 0, + "Minolta", + "Dimage F300", + }, + { + USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_E223, + 0, + "Minolta", + "Dimage E223", + }, + { + USB_VENDOR_MITSUMI, USB_PRODUCT_MITSUMI_CDRRW, + 0, + "Mitsumi", + "CD-R/RW Drive", + }, + { + USB_VENDOR_MITSUMI, USB_PRODUCT_MITSUMI_BT_DONGLE, + 0, + "Mitsumi", + "Bluetooth USB dongle", + }, + { + USB_VENDOR_MITSUMI, USB_PRODUCT_MITSUMI_FDD, + 0, + "Mitsumi", + "USB FDD", + }, + { + USB_VENDOR_MOBILITY, USB_PRODUCT_MOBILITY_EA, + 0, + "Mobility", + "Ethernet", + }, + { + USB_VENDOR_MOBILITY, USB_PRODUCT_MOBILITY_EASIDOCK, + 0, + "Mobility", + "EasiDock Ethernet", + }, + { + USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7703, + 0, + "MosChip Semiconductor", + "MCS7703 Serial Port Adapter", + }, + { + USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7830, + 0, + "MosChip Semiconductor", + "MCS7830 Ethernet", + }, + { + USB_VENDOR_MOTOROLA, USB_PRODUCT_MOTOROLA_MC141555, + 0, + "Motorola", + "MC141555 hub controller", + }, + { + USB_VENDOR_MOTOROLA, USB_PRODUCT_MOTOROLA_SB4100, + 0, + "Motorola", + "SB4100 USB Cable Modem", + }, + { + USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_A41XV32X, + 0, + "Motorola", + "A41x/V32x Mobile Phones", + }, + { + USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_E398, + 0, + "Motorola", + "E398 Mobile Phone", + }, + { + USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN, + 0, + "Motorola", + "USBLAN", + }, + { + USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN2, + 0, + "Motorola", + "USBLAN", + }, + { + USB_VENDOR_MULTITECH, USB_PRODUCT_MULTITECH_ATLAS, + 0, + "MultiTech", + "MT5634ZBA-USB modem", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200CU, + 0, + "Mustek Systems", + "1200 CU scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_600CU, + 0, + "Mustek Systems", + "600 CU scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200USB, + 0, + "Mustek Systems", + "1200 USB scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200UB, + 0, + "Mustek Systems", + "1200 UB scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200USBPLUS, + 0, + "Mustek Systems", + "1200 USB Plus scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200CUPLUS, + 0, + "Mustek Systems", + "1200 CU Plus scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_BEARPAW1200F, + 0, + "Mustek Systems", + "BearPaw 1200F scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_BEARPAW1200TA, + 0, + "Mustek Systems", + "BearPaw 1200TA scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_600USB, + 0, + "Mustek Systems", + "600 USB scanner", + }, + { + USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_MDC800, + 0, + "Mustek Systems", + "MDC-800 digital camera", + }, + { + USB_VENDOR_MSYSTEMS, USB_PRODUCT_MSYSTEMS_DISKONKEY, + 0, + "M-Systems", + "DiskOnKey", + }, + { + USB_VENDOR_MSYSTEMS, USB_PRODUCT_MSYSTEMS_DISKONKEY2, + 0, + "M-Systems", + "DiskOnKey", + }, + { + USB_VENDOR_MYSON, USB_PRODUCT_MYSON_HEDEN, + 0, + "Myson Technology", + "USB-IDE", + }, + { + USB_VENDOR_NATIONAL, USB_PRODUCT_NATIONAL_BEARPAW1200, + 0, + "National Semiconductor", + "BearPaw 1200", + }, + { + USB_VENDOR_NATIONAL, USB_PRODUCT_NATIONAL_BEARPAW2400, + 0, + "National Semiconductor", + "BearPaw 2400", + }, + { + USB_VENDOR_NEC, USB_PRODUCT_NEC_HUB, + 0, + "NEC", + "hub", + }, + { + USB_VENDOR_NEC, USB_PRODUCT_NEC_HUB_B, + 0, + "NEC", + "hub", + }, + { + USB_VENDOR_NEODIO, USB_PRODUCT_NEODIO_ND3260, + 0, + "Neodio", + "8-in-1 Multi-format Flash Controller", + }, + { + USB_VENDOR_NEODIO, USB_PRODUCT_NEODIO_ND5010, + 0, + "Neodio", + "Multi-format Flash Controller", + }, + { + USB_VENDOR_NETAC, USB_PRODUCT_NETAC_CF_CARD, + 0, + "Netac", + "USB-CF-Card", + }, + { + USB_VENDOR_NETAC, USB_PRODUCT_NETAC_ONLYDISK, + 0, + "Netac", + "OnlyDisk", + }, + { + USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_TURBOCONNECT, + 0, + "NetChip Technology", + "Turbo-Connect", + }, + { + USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_CLIK_40, + 0, + "NetChip Technology", + "USB Clik! 40", + }, + { + USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_ETHERNETGADGET, + 0, + "NetChip Technology", + "Linux Ethernet/RNDIS gadget on pxa210/25x/26x", + }, + { + USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_EA101, + 0, + "BayNETGEAR", + "Ethernet", + }, + { + USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_EA101X, + 0, + "BayNETGEAR", + "Ethernet", + }, + { + USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_FA101, + 0, + "BayNETGEAR", + "Ethernet 10/100, USB1.1", + }, + { + USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_FA120, + 0, + "BayNETGEAR", + "USB 2.0 Ethernet", + }, + { + USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WG111V2_2, + 0, + "BayNETGEAR", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WG111U, + 0, + "BayNETGEAR", + "WG111U", + }, + { + USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WG111U_NF, + 0, + "BayNETGEAR", + "WG111U (no firmware)", + }, + { + USB_VENDOR_NETGEAR2, USB_PRODUCT_NETGEAR2_MA101, + 0, + "Netgear", + "MA101", + }, + { + USB_VENDOR_NETGEAR2, USB_PRODUCT_NETGEAR2_MA101B, + 0, + "Netgear", + "MA101 Rev B", + }, + { + USB_VENDOR_NETGEAR3, USB_PRODUCT_NETGEAR3_WG111T, + 0, + "Netgear", + "WG111T", + }, + { + USB_VENDOR_NETGEAR3, USB_PRODUCT_NETGEAR3_WG111T_NF, + 0, + "Netgear", + "WG111T (no firmware)", + }, + { + USB_VENDOR_NETGEAR3, USB_PRODUCT_NETGEAR3_WPN111, + 0, + "Netgear", + "WPN111", + }, + { + USB_VENDOR_NETGEAR3, USB_PRODUCT_NETGEAR3_WPN111_NF, + 0, + "Netgear", + "WPN111 (no firmware)", + }, + { + USB_VENDOR_NIKON, USB_PRODUCT_NIKON_E990, + 0, + "Nikon", + "Digital Camera E990", + }, + { + USB_VENDOR_NIKON, USB_PRODUCT_NIKON_LS40, + 0, + "Nikon", + "CoolScan LS40 ED", + }, + { + USB_VENDOR_NIKON, USB_PRODUCT_NIKON_D300, + 0, + "Nikon", + "Digital Camera D300", + }, + { + USB_VENDOR_NOVATECH, USB_PRODUCT_NOVATECH_NV902, + 0, + "NovaTech", + "NovaTech NV-902W", + }, + { + USB_VENDOR_NOVATECH, USB_PRODUCT_NOVATECH_RT2573, + 0, + "NovaTech", + "RT2573", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V640, + 0, + "Novatel Wireless", + "Merlin V620", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_CDMA_MODEM, + 0, + "Novatel Wireless", + "Novatel Wireless Merlin CDMA", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V620, + 0, + "Novatel Wireless", + "Merlin V620", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V740, + 0, + "Novatel Wireless", + "Merlin V740", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V720, + 0, + "Novatel Wireless", + "Merlin V720", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U740, + 0, + "Novatel Wireless", + "Merlin U740", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U740_2, + 0, + "Novatel Wireless", + "Merlin U740", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U870, + 0, + "Novatel Wireless", + "Merlin U870", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_XU870, + 0, + "Novatel Wireless", + "Merlin XU870", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_X950D, + 0, + "Novatel Wireless", + "Merlin X950D", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_ES620, + 0, + "Novatel Wireless", + "ES620 CDMA", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U720, + 0, + "Novatel Wireless", + "Merlin U720", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U727, + 0, + "Novatel Wireless", + "Merlin U727 CDMA", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U950D, + 0, + "Novatel Wireless", + "Novatel MC950D HSUPA", + }, + { + USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_ZEROCD, + 0, + "Novatel Wireless", + "Novatel ZeroCD", + }, + { + USB_VENDOR_NOVATEL2, USB_PRODUCT_NOVATEL2_FLEXPACKGPS, + 0, + "Novatel Wireless", + "NovAtel FlexPack GPS receiver", + }, + { + USB_VENDOR_MERLIN, USB_PRODUCT_MERLIN_V620, + 0, + "Merlin", + "Merlin V620", + }, + { + USB_VENDOR_OLYMPUS, USB_PRODUCT_OLYMPUS_C1, + 0, + "Olympus", + "C-1 Digital Camera", + }, + { + USB_VENDOR_OLYMPUS, USB_PRODUCT_OLYMPUS_C700, + 0, + "Olympus", + "C-700 Ultra Zoom", + }, + { + USB_VENDOR_OMNIVISION, USB_PRODUCT_OMNIVISION_OV511, + 0, + "OmniVision", + "OV511 Camera", + }, + { + USB_VENDOR_OMNIVISION, USB_PRODUCT_OMNIVISION_OV511PLUS, + 0, + "OmniVision", + "OV511+ Camera", + }, + { + USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_MDCFE_B_CF_READER, + 0, + "OnSpec", + "MDCFE-B USB CF Reader", + }, + { + USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFMS_RW, + 0, + "OnSpec", + "SIIG/Datafab Memory Stick+CF Reader/Writer", + }, + { + USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_READER, + 0, + "OnSpec", + "Datafab-based Reader", + }, + { + USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_READER, + 0, + "OnSpec", + "PNY/Datafab CF+SM Reader", + }, + { + USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_READER2, + 0, + "OnSpec", + "Simple Tech/Datafab CF+SM Reader", + }, + { + USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_MDSM_B_READER, + 0, + "OnSpec", + "MDSM-B reader", + }, + { + USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_COMBO, + 0, + "OnSpec", + "USB to CF + SM Combo (LC1)", + }, + { + USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_UCF100, + 0, + "OnSpec", + "FlashLink UCF-100 CompactFlash Reader", + }, + { + USB_VENDOR_ONSPEC2, USB_PRODUCT_ONSPEC2_IMAGEMATE_SDDR55, + 0, + "OnSpec Electronic Inc.", + "ImageMate SDDR55", + }, + { + USB_VENDOR_OPTION, USB_PRODUCT_OPTION_VODAFONEMC3G, + 0, + "Option N.V:", + "Vodafone Mobile Connect 3G datacard", + }, + { + USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3G, + 0, + "Option N.V:", + "GlobeTrotter 3G datacard", + }, + { + USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3GQUAD, + 0, + "Option N.V:", + "GlobeTrotter 3G QUAD datacard", + }, + { + USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3GPLUS, + 0, + "Option N.V:", + "GlobeTrotter 3G+ datacard", + }, + { + USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GTMAX36, + 0, + "Option N.V:", + "GlobeTrotter Max 3.6 Modem", + }, + { + USB_VENDOR_OQO, USB_PRODUCT_OQO_WIFI01, + 0, + "OQO", + "model 01 WiFi interface", + }, + { + USB_VENDOR_OQO, USB_PRODUCT_OQO_BT01, + 0, + "OQO", + "model 01 Bluetooth interface", + }, + { + USB_VENDOR_OQO, USB_PRODUCT_OQO_ETHER01PLUS, + 0, + "OQO", + "model 01+ Ethernet", + }, + { + USB_VENDOR_OQO, USB_PRODUCT_OQO_ETHER01, + 0, + "OQO", + "model 01 Ethernet interface", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_SERIAL, + 0, + "Palm Computing", + "USB Serial", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_M500, + 0, + "Palm Computing", + "Palm m500", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_M505, + 0, + "Palm Computing", + "Palm m505", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_M515, + 0, + "Palm Computing", + "Palm m515", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_I705, + 0, + "Palm Computing", + "Palm i705", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_TUNGSTEN_Z, + 0, + "Palm Computing", + "Palm Tungsten Z", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_M125, + 0, + "Palm Computing", + "Palm m125", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_M130, + 0, + "Palm Computing", + "Palm m130", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_TUNGSTEN_T, + 0, + "Palm Computing", + "Palm Tungsten T", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_ZIRE31, + 0, + "Palm Computing", + "Palm Zire 31", + }, + { + USB_VENDOR_PALM, USB_PRODUCT_PALM_ZIRE, + 0, + "Palm Computing", + "Palm Zire", + }, + { + USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_LS120CAM, + 0, + "Panasonic (Matsushita)", + "LS-120 Camera", + }, + { + USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXL840AN, + 0, + "Panasonic (Matsushita)", + "CD-R Drive KXL-840AN", + }, + { + USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXLRW32AN, + 0, + "Panasonic (Matsushita)", + "CD-R Drive KXL-RW32AN", + }, + { + USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXLCB20AN, + 0, + "Panasonic (Matsushita)", + "CD-R Drive KXL-CB20AN", + }, + { + USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXLCB35AN, + 0, + "Panasonic (Matsushita)", + "DVD-ROM & CD-R/RW", + }, + { + USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_SDCAAE, + 0, + "Panasonic (Matsushita)", + "MultiMediaCard", + }, + { + USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_SERIAL1, + 0, + "Peracom Networks", + "Serial", + }, + { + USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET, + 0, + "Peracom Networks", + "Ethernet", + }, + { + USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET3, + 0, + "Peracom Networks", + "At Home Ethernet", + }, + { + USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET2, + 0, + "Peracom Networks", + "Ethernet", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_DSS350, + 0, + "Philips", + "DSS 350 Digital Speaker System", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_DSS, + 0, + "Philips", + "DSS XXX Digital Speaker System", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_HUB, + 0, + "Philips", + "hub", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_PCA646VC, + 0, + "Philips", + "PCA646VC PC Camera", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_PCVC680K, + 0, + "Philips", + "PCVC680K Vesta Pro PC Camera", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_DSS150, + 0, + "Philips", + "DSS 150 Digital Speaker System", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_SNU5600, + 0, + "Philips", + "SNU5600", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_UM10016, + 0, + "Philips", + "ISP 1581 Hi-Speed USB MPEG2 Encoder Reference Kit", + }, + { + USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_DIVAUSB, + 0, + "Philips", + "DIVA USB mp3 player", + }, + { + USB_VENDOR_PHILIPSSEMI, USB_PRODUCT_PHILIPSSEMI_HUB1122, + 0, + "Philips Semiconductors", + "hub", + }, + { + USB_VENDOR_PIENGINEERING, USB_PRODUCT_PIENGINEERING_PS2USB, + 0, + "P.I. Engineering", + "PS2 to Mac USB Adapter", + }, + { + USB_VENDOR_PLANEX, USB_PRODUCT_PLANEX_GW_US11H, + 0, + "Planex Communications", + "GW-US11H WLAN", + }, + { + USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GW_US11S, + 0, + "Planex Communications", + "GW-US11S WLAN", + }, + { + USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GW_US54GXS, + 0, + "Planex Communications", + "GW-US54GXS WLAN", + }, + { + USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54HP, + 0, + "Planex Communications", + "GW-US54HP", + }, + { + USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54MINI2, + 0, + "Planex Communications", + "GW-US54Mini2", + }, + { + USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54SG, + 0, + "Planex Communications", + "GW-US54SG", + }, + { + USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54GZL, + 0, + "Planex Communications", + "GW-US54GZL", + }, + { + USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54GD, + 0, + "Planex Communications", + "GW-US54GD", + }, + { + USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUSMM, + 0, + "Planex Communications", + "GW-USMM", + }, + { + USB_VENDOR_PLANEX3, USB_PRODUCT_PLANEX3_GWUS54GZ, + 0, + "Planex Communications", + "GW-US54GZ", + }, + { + USB_VENDOR_PLANEX3, USB_PRODUCT_PLANEX3_GU1000T, + 0, + "Planex Communications", + "GU-1000T", + }, + { + USB_VENDOR_PLANEX3, USB_PRODUCT_PLANEX3_GWUS54MINI, + 0, + "Planex Communications", + "GW-US54Mini", + }, + { + USB_VENDOR_PLEXTOR, USB_PRODUCT_PLEXTOR_40_12_40U, + 0, + "Plextor", + "PlexWriter 40/12/40U", + }, + { + USB_VENDOR_PLX, USB_PRODUCT_PLX_TESTBOARD, + 0, + "PLX", + "test board", + }, + { + USB_VENDOR_PNY, USB_PRODUCT_PNY_ATTACHE2, + 0, + "PNY", + "USB 2.0 Flash Drive", + }, + { + USB_VENDOR_PORTGEAR, USB_PRODUCT_PORTGEAR_EA8, + 0, + "PortGear", + "Ethernet", + }, + { + USB_VENDOR_PORTGEAR, USB_PRODUCT_PORTGEAR_EA9, + 0, + "PortGear", + "Ethernet", + }, + { + USB_VENDOR_PORTSMITH, USB_PRODUCT_PORTSMITH_EEA, + 0, + "Portsmith", + "Express Ethernet", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2X300, + 0, + "Primax Electronics", + "G2-200 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E300, + 0, + "Primax Electronics", + "G2E-300 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2300, + 0, + "Primax Electronics", + "G2-300 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E3002, + 0, + "Primax Electronics", + "G2E-300 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_9600, + 0, + "Primax Electronics", + "Colorado USB 9600 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_600U, + 0, + "Primax Electronics", + "Colorado 600u scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_6200, + 0, + "Primax Electronics", + "Visioneer 6200 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_19200, + 0, + "Primax Electronics", + "Colorado USB 19200 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_1200U, + 0, + "Primax Electronics", + "Colorado 1200u scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G600, + 0, + "Primax Electronics", + "G2-600 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_636I, + 0, + "Primax Electronics", + "ReadyScan 636i", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2600, + 0, + "Primax Electronics", + "G2-600 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E600, + 0, + "Primax Electronics", + "G2E-600 scanner", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_COMFORT, + 0, + "Primax Electronics", + "Comfort", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_MOUSEINABOX, + 0, + "Primax Electronics", + "Mouse-in-a-Box", + }, + { + USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_PCGAUMS1, + 0, + "Primax Electronics", + "Sony PCGA-UMS1", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2301, + 0, + "Prolific Technology", + "PL2301 Host-Host interface", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2302, + 0, + "Prolific Technology", + "PL2302 Host-Host interface", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_RSAQ2, + 0, + "Prolific Technology", + "PL2303 Serial (IODATA USB-RSAQ2)", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2303, + 0, + "Prolific Technology", + "PL2303 Serial (ATEN/IOGEAR UC232A)", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2305, + 0, + "Prolific Technology", + "Parallel printer", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_ATAPI4, + 0, + "Prolific Technology", + "ATAPI-4 Controller", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2501, + 0, + "Prolific Technology", + "PL2501 Host-Host interface", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PHAROS, + 0, + "Prolific Technology", + "Prolific Pharos", + }, + { + USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_RSAQ3, + 0, + "Prolific Technology", + "PL2303 Serial Adapter (IODATA USB-RSAQ3)", + }, + { + USB_VENDOR_PROLIFIC2, USB_PRODUCT_PROLIFIC2_WSIM, + 0, + "Prolific", + "Willcom WSIM", + }, + { + USB_VENDOR_PUTERCOM, USB_PRODUCT_PUTERCOM_UPA100, + 0, + "Putercom", + "USB-1284 BRIDGE", + }, + { + USB_VENDOR_QCOM, USB_PRODUCT_QCOM_RT2573, + 0, + "Qcom", + "RT2573", + }, + { + USB_VENDOR_QCOM, USB_PRODUCT_QCOM_RT2573_2, + 0, + "Qcom", + "RT2573", + }, + { + USB_VENDOR_QUALCOMM, USB_PRODUCT_QUALCOMM_CDMA_MSM, + 0, + "Qualcomm", + "CDMA Technologies MSM phone", + }, + { + USB_VENDOR_QUALCOMM2, USB_PRODUCT_QUALCOMM2_RWT_FCT, + 0, + "Qualcomm", + "RWT FCT-CDMA 2000 1xRTT modem", + }, + { + USB_VENDOR_QUALCOMM2, USB_PRODUCT_QUALCOMM2_CDMA_MSM, + 0, + "Qualcomm", + "CDMA Technologies MSM modem", + }, + { + USB_VENDOR_QUALCOMMINC, USB_PRODUCT_QUALCOMMINC_CDMA_MSM, + 0, + "Qualcomm, Incorporated", + "CDMA Technologies MSM modem", + }, + { + USB_VENDOR_QTRONIX, USB_PRODUCT_QTRONIX_980N, + 0, + "Qtronix", + "Scorpion-980N keyboard", + }, + { + USB_VENDOR_QUICKSHOT, USB_PRODUCT_QUICKSHOT_STRIKEPAD, + 0, + "Quickshot", + "USB StrikePad", + }, + { + USB_VENDOR_RADIOSHACK, USB_PRODUCT_RADIOSHACK_USBCABLE, + 0, + "Radio Shack", + "USB to Serial Cable", + }, + { + USB_VENDOR_RAINBOW, USB_PRODUCT_RAINBOW_IKEY2000, + 0, + "Rainbow Technologies", + "i-Key 2000", + }, + { + USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570, + 0, + "Ralink Technology", + "RT2500USB Wireless Adapter", + }, + { + USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570_2, + 0, + "Ralink Technology", + "RT2500USB Wireless Adapter", + }, + { + USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2573, + 0, + "Ralink Technology", + "RT2501USB Wireless Adapter", + }, + { + USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2671, + 0, + "Ralink Technology", + "RT2601USB Wireless Adapter", + }, + { + USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570_3, + 0, + "Ralink Technology", + "RT2500USB Wireless Adapter", + }, + { + USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2573_2, + 0, + "Ralink Technology", + "RT2501USB Wireless Adapter", + }, + { + USB_VENDOR_REALTEK, USB_PRODUCT_REALTEK_USBKR100, + 0, + "Realtek", + "USBKR100 USB Ethernet", + }, + { + USB_VENDOR_RICOH, USB_PRODUCT_RICOH_VGPVCC2, + 0, + "Ricoh", + "VGP-VCC2 Camera", + }, + { + USB_VENDOR_RICOH, USB_PRODUCT_RICOH_VGPVCC3, + 0, + "Ricoh", + "VGP-VCC3 Camera", + }, + { + USB_VENDOR_RICOH, USB_PRODUCT_RICOH_VGPVCC2_2, + 0, + "Ricoh", + "VGP-VCC2 Camera", + }, + { + USB_VENDOR_RICOH, USB_PRODUCT_RICOH_VGPVCC2_3, + 0, + "Ricoh", + "VGP-VCC2 Camera", + }, + { + USB_VENDOR_RICOH, USB_PRODUCT_RICOH_VGPVCC7, + 0, + "Ricoh", + "VGP-VCC7 Camera", + }, + { + USB_VENDOR_RICOH, USB_PRODUCT_RICOH_VGPVCC8, + 0, + "Ricoh", + "VGP-VCC8 Camera", + }, + { + USB_VENDOR_ROLAND, USB_PRODUCT_ROLAND_UM1, + 0, + "Roland", + "UM-1 MIDI I/F", + }, + { + USB_VENDOR_ROLAND, USB_PRODUCT_ROLAND_UM880N, + 0, + "Roland", + "EDIROL UM-880 MIDI I/F (native)", + }, + { + USB_VENDOR_ROLAND, USB_PRODUCT_ROLAND_UM880G, + 0, + "Roland", + "EDIROL UM-880 MIDI I/F (generic)", + }, + { + USB_VENDOR_ROCKFIRE, USB_PRODUCT_ROCKFIRE_GAMEPAD, + 0, + "Rockfire", + "gamepad 203USB", + }, + { + USB_VENDOR_RATOC, USB_PRODUCT_RATOC_REXUSB60, + 0, + "RATOC Systems", + "REX-USB60", + }, + { + USB_VENDOR_SAGEM, USB_PRODUCT_SAGEM_USBSERIAL, + 0, + "Sagem", + "USB-Serial Controller", + }, + { + USB_VENDOR_SAGEM, USB_PRODUCT_SAGEM_XG760A, + 0, + "Sagem", + "XG-760A", + }, + { + USB_VENDOR_SAGEM, USB_PRODUCT_SAGEM_XG76NA, + 0, + "Sagem", + "XG-76NA", + }, + { + USB_VENDOR_SAMSUNG, USB_PRODUCT_SAMSUNG_ML6060, + 0, + "Samsung Electronics", + "ML-6060 laser printer", + }, + { + USB_VENDOR_SAMSUNG, USB_PRODUCT_SAMSUNG_YP_U2, + 0, + "Samsung Electronics", + "YP-U2 MP3 Player", + }, + { + USB_VENDOR_SAMSUNG, USB_PRODUCT_SAMSUNG_I500, + 0, + "Samsung Electronics", + "I500 Palm USB Phone", + }, + { + USB_VENDOR_SAMSUNG_TECHWIN, USB_PRODUCT_SAMSUNG_TECHWIN_DIGIMAX_410, + 0, + "Samsung Techwin", + "Digimax 410", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR05A, + 0, + "SanDisk", + "ImageMate SDDR-05a", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR31, + 0, + "SanDisk", + "ImageMate SDDR-31", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR05, + 0, + "SanDisk", + "ImageMate SDDR-05", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR12, + 0, + "SanDisk", + "ImageMate SDDR-12", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR09, + 0, + "SanDisk", + "ImageMate SDDR-09", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR75, + 0, + "SanDisk", + "ImageMate SDDR-75", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ2_256, + 0, + "SanDisk", + "Cruzer Mini 256MB", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ4_128, + 0, + "SanDisk", + "Cruzer Micro 128MB", + }, + { + USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ4_256, + 0, + "SanDisk", + "Cruzer Micro 256MB", + }, + { + USB_VENDOR_SANYO, USB_PRODUCT_SANYO_SCP4900, + 0, + "Sanyo Electric", + "Sanyo SCP-4900 USB Phone", + }, + { + USB_VENDOR_SCANLOGIC, USB_PRODUCT_SCANLOGIC_SL11R, + 0, + "ScanLogic", + "SL11R IDE Adapter", + }, + { + USB_VENDOR_SCANLOGIC, USB_PRODUCT_SCANLOGIC_336CX, + 0, + "ScanLogic", + "Phantom 336CX - C3 scanner", + }, + { + USB_VENDOR_SENAO, USB_PRODUCT_SENAO_NUB8301, + 0, + "Senao", + "NUB-8301", + }, + { + USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ST268, + 0, + "ShanTou", + "ST268", + }, + { + USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_DM9601, + 0, + "ShanTou", + "DM 9601", + }, + { + USB_VENDOR_SHARK, USB_PRODUCT_SHARK_PA, + 0, + "Shark", + "Pocket Adapter", + }, + { + USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5500, + 0, + "Sharp", + "Zaurus SL-5500 PDA", + }, + { + USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLA300, + 0, + "Sharp", + "Zaurus SL-A300 PDA", + }, + { + USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5600, + 0, + "Sharp", + "Zaurus SL-5600 PDA", + }, + { + USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC700, + 0, + "Sharp", + "Zaurus SL-C700 PDA", + }, + { + USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC750, + 0, + "Sharp", + "Zaurus SL-C750 PDA", + }, + { + USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ES, + 0, + "Sharp", + "W-ZERO3 ES Smartphone", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSB, + 0, + "Shuttle Technology", + "E-USB Bridge", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSCSI, + 0, + "Shuttle Technology", + "eUSCSI Bridge", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_SDDR09, + 0, + "Shuttle Technology", + "ImageMate SDDR09", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSBCFSM, + 0, + "Shuttle Technology", + "eUSB SmartMedia / CompactFlash Adapter", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_ZIOMMC, + 0, + "Shuttle Technology", + "eUSB MultiMediaCard Adapter", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_HIFD, + 0, + "Shuttle Technology", + "Sony Hifd", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSBATAPI, + 0, + "Shuttle Technology", + "eUSB ATA/ATAPI Adapter", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_CF, + 0, + "Shuttle Technology", + "eUSB CompactFlash Adapter", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSCSI_B, + 0, + "Shuttle Technology", + "eUSCSI Bridge", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSCSI_C, + 0, + "Shuttle Technology", + "eUSCSI Bridge", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_CDRW, + 0, + "Shuttle Technology", + "CD-RW Device", + }, + { + USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSBORCA, + 0, + "Shuttle Technology", + "eUSB ORCA Quad Reader", + }, + { + USB_VENDOR_SIEMENS, USB_PRODUCT_SIEMENS_SPEEDSTREAM, + 0, + "Siemens", + "SpeedStream", + }, + { + USB_VENDOR_SIEMENS, USB_PRODUCT_SIEMENS_SPEEDSTREAM22, + 0, + "Siemens", + "SpeedStream 1022", + }, + { + USB_VENDOR_SIEMENS2, USB_PRODUCT_SIEMENS2_WLL013, + 0, + "Siemens", + "WLL013", + }, + { + USB_VENDOR_SIEMENS2, USB_PRODUCT_SIEMENS2_ES75, + 0, + "Siemens", + "GSM module MC35", + }, + { + USB_VENDOR_SIEMENS2, USB_PRODUCT_SIEMENS2_WL54G, + 0, + "Siemens", + "54g USB Network Adapter", + }, + { + USB_VENDOR_SIEMENS3, USB_PRODUCT_SIEMENS3_SX1, + 0, + "Siemens", + "SX1", + }, + { + USB_VENDOR_SIEMENS3, USB_PRODUCT_SIEMENS3_X65, + 0, + "Siemens", + "X65", + }, + { + USB_VENDOR_SIEMENS3, USB_PRODUCT_SIEMENS3_X75, + 0, + "Siemens", + "X75", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD580, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 580", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD595, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 595", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC595U, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 595U", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC597E, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 597E", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_C597, + 0, + "Sierra Wireless", + "Sierra Wireless Compass 597", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 880", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 881", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880E, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 880E", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881E, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 881E", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880U, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 880U", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881U, + 0, + "Sierra Wireless", + "Sierra Wireless AirCard 881U", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_EM5625, + 0, + "Sierra Wireless", + "EM5625", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, + 0, + "Sierra Wireless", + "MC5720 Wireless Modem", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720_2, + 0, + "Sierra Wireless", + "MC5720", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5725, + 0, + "Sierra Wireless", + "MC5725", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MINI5725, + 0, + "Sierra Wireless", + "Sierra Wireless miniPCI 5275", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755_2, + 0, + "Sierra Wireless", + "MC8755", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8765, + 0, + "Sierra Wireless", + "MC8765", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755, + 0, + "Sierra Wireless", + "MC8755", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC875U, + 0, + "Sierra Wireless", + "AC875U HSDPA USB Modem", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755_3, + 0, + "Sierra Wireless", + "MC8755 HSDPA", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8775_2, + 0, + "Sierra Wireless", + "MC8775", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD875, + 0, + "Sierra Wireless", + "Aircard 875 HSDPA", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8780, + 0, + "Sierra Wireless", + "MC8780", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8781, + 0, + "Sierra Wireless", + "MC8781", + }, + { + USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_TRUINSTALL, + 0, + "Sierra Wireless", + "Aircard Tru Installer", + }, + { + USB_VENDOR_SIGMATEL, USB_PRODUCT_SIGMATEL_I_BEAD100, + 0, + "Sigmatel", + "i-Bead 100 MP3 Player", + }, + { + USB_VENDOR_SIIG, USB_PRODUCT_SIIG_DIGIFILMREADER, + 0, + "SIIG", + "DigiFilm-Combo Reader", + }, + { + USB_VENDOR_SIIG, USB_PRODUCT_SIIG_WINTERREADER, + 0, + "SIIG", + "WINTERREADER Reader", + }, + { + USB_VENDOR_SIIG2, USB_PRODUCT_SIIG2_USBTOETHER, + 0, + "SIIG", + "USB TO Ethernet", + }, + { + USB_VENDOR_SIIG2, USB_PRODUCT_SIIG2_US2308, + 0, + "SIIG", + "Serial", + }, + { + USB_VENDOR_SILICOM, USB_PRODUCT_SILICOM_U2E, + 0, + "Silicom", + "U2E", + }, + { + USB_VENDOR_SILICOM, USB_PRODUCT_SILICOM_GPE, + 0, + "Silicom", + "Psion Gold Port Ethernet", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_POLOLU, + 0, + "Silicon Labs", + "Pololu Serial", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_ARGUSISP, + 0, + "Silicon Labs", + "Argussoft ISP", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CRUMB128, + 0, + "Silicon Labs", + "Crumb128 board", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_DEGREE, + 0, + "Silicon Labs", + "Degree Controls Inc", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_TRAQMATE, + 0, + "Silicon Labs", + "Track Systems Traqmate", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_SUUNTO, + 0, + "Silicon Labs", + "Suunto Sports Instrument", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_BURNSIDE, + 0, + "Silicon Labs", + "Burnside Telecon Deskmobile", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_HELICOM, + 0, + "Silicon Labs", + "Helicomm IP-Link 1220-DVM", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2102, + 0, + "Silicon Labs", + "SILABS USB UART", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_LIPOWSKY_JTAG, + 0, + "Silicon Labs", + "Lipowsky Baby-JTAG", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_LIPOWSKY_LIN, + 0, + "Silicon Labs", + "Lipowsky Baby-LIN", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_LIPOWSKY_HARP, + 0, + "Silicon Labs", + "Lipowsky HARP-1", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2102, + 0, + "Silicon Labs", + "SILABS USB UARTa", + }, + { + USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP210X_2, + 0, + "Silicon Labs", + "CP210x Serial", + }, + { + USB_VENDOR_SILABS2, USB_PRODUCT_SILABS2_DCU11CLONE, + 0, + "SILABS2", + "DCU-11 clone", + }, + { + USB_VENDOR_SILICONPORTALS, USB_PRODUCT_SILICONPORTALS_YAPPH_NF, + 0, + "Silicon Portals", + "YAP Phone (no firmware)", + }, + { + USB_VENDOR_SILICONPORTALS, USB_PRODUCT_SILICONPORTALS_YAPPHONE, + 0, + "Silicon Portals", + "YAP Phone", + }, + { + USB_VENDOR_SIRIUS, USB_PRODUCT_SIRIUS_ROADSTER, + 0, + "Sirius Technologies", + "NetComm Roadster II 56 USB", + }, + { + USB_VENDOR_SITECOM, USB_PRODUCT_SITECOM_LN029, + 0, + "Sitecom", + "USB 2.0 Ethernet", + }, + { + USB_VENDOR_SITECOM, USB_PRODUCT_SITECOM_SERIAL, + 0, + "Sitecom", + "USB to serial cable (v2)", + }, + { + USB_VENDOR_SITECOM2, USB_PRODUCT_SITECOM2_WL022, + 0, + "Sitecom", + "WL-022", + }, + { + USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_LN028, + 0, + "Sitecom Europe", + "LN-028", + }, + { + USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_WL113, + 0, + "Sitecom Europe", + "WL-113", + }, + { + USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_ZD1211B, + 0, + "Sitecom Europe", + "ZD1211B", + }, + { + USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_WL172, + 0, + "Sitecom Europe", + "WL-172", + }, + { + USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_WL113R2, + 0, + "Sitecom Europe", + "WL-113 rev 2", + }, + { + USB_VENDOR_SKANHEX, USB_PRODUCT_SKANHEX_MD_7425, + 0, + "Skanhex Technology, Inc.", + "MD 7425 Camera", + }, + { + USB_VENDOR_SKANHEX, USB_PRODUCT_SKANHEX_SX_520Z, + 0, + "Skanhex Technology, Inc.", + "SX 520z Camera", + }, + { + USB_VENDOR_SMARTBRIDGES, USB_PRODUCT_SMARTBRIDGES_SMARTLINK, + 0, + "SmartBridges", + "SmartLink USB Ethernet", + }, + { + USB_VENDOR_SMARTBRIDGES, USB_PRODUCT_SMARTBRIDGES_SMARTNIC, + 0, + "SmartBridges", + "smartNIC 2 PnP Ethernet", + }, + { + USB_VENDOR_SMC, USB_PRODUCT_SMC_2102USB, + 0, + "Standard Microsystems", + "10Mbps Ethernet", + }, + { + USB_VENDOR_SMC, USB_PRODUCT_SMC_2202USB, + 0, + "Standard Microsystems", + "10/100 Ethernet", + }, + { + USB_VENDOR_SMC, USB_PRODUCT_SMC_2206USB, + 0, + "Standard Microsystems", + "EZ Connect USB Ethernet", + }, + { + USB_VENDOR_SMC, USB_PRODUCT_SMC_2862WG, + 0, + "Standard Microsystems", + "EZ Connect Wireless Adapter", + }, + { + USB_VENDOR_SMC2, USB_PRODUCT_SMC2_2020HUB, + 0, + "Standard Microsystems", + "USB Hub", + }, + { + USB_VENDOR_SMC3, USB_PRODUCT_SMC3_2662WUSB, + 0, + "Standard Microsystems", + "2662W-AR Wireless", + }, + { + USB_VENDOR_SOHOWARE, USB_PRODUCT_SOHOWARE_NUB100, + 0, + "SOHOware", + "10/100 USB Ethernet", + }, + { + USB_VENDOR_SOHOWARE, USB_PRODUCT_SOHOWARE_NUB110, + 0, + "SOHOware", + "10/100 USB Ethernet", + }, + { + USB_VENDOR_SOLIDYEAR, USB_PRODUCT_SOLIDYEAR_KEYBOARD, + 0, + "Solid Year", + "Solid Year USB keyboard", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_DSC, + 0, + "Sony", + "DSC cameras", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_NW_MS7, + 0, + "Sony", + "Memorystick NW-MS7", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_PORTABLE_HDD_V2, + 0, + "Sony", + "Portable USB Harddrive V2", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_MSACUS1, + 0, + "Sony", + "Memorystick MSAC-US1", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_HANDYCAM, + 0, + "Sony", + "Handycam", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_MSC, + 0, + "Sony", + "MSC memory stick slot", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_35, + 0, + "Sony", + "Sony Clie v3.5", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_PEG_N760C, + 0, + "Sony", + "PEG N760c Memorystick", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_40, + 0, + "Sony", + "Sony Clie v4.0", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_MSC_U03, + 0, + "Sony", + "Memorystick MSC-U03", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_40_MS, + 0, + "Sony", + "Sony Clie v4.0 Memory Stick slot", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_S360, + 0, + "Sony", + "Sony Clie s360", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_41_MS, + 0, + "Sony", + "Sony Clie v4.1 Memory Stick slot", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_41, + 0, + "Sony", + "Sony Clie v4.1", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_NX60, + 0, + "Sony", + "Sony Clie nx60", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_TH55, + 0, + "Sony", + "Sony Clie th55", + }, + { + USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_TJ37, + 0, + "Sony", + "Sony Clie tj37", + }, + { + USB_VENDOR_SONYERICSSON, USB_PRODUCT_SONYERICSSON_DCU10, + 0, + "Sony Ericsson", + "USB Cable", + }, + { + USB_VENDOR_SOURCENEXT, USB_PRODUCT_SOURCENEXT_KEIKAI8, + 0, + "SOURCENEXT", + "KeikaiDenwa 8", + }, + { + USB_VENDOR_SOURCENEXT, USB_PRODUCT_SOURCENEXT_KEIKAI8_CHG, + 0, + "SOURCENEXT", + "KeikaiDenwa 8 with charger", + }, + { + USB_VENDOR_SPARKLAN, USB_PRODUCT_SPARKLAN_RT2573, + 0, + "SparkLAN", + "RT2573", + }, + { + USB_VENDOR_SPHAIRON, USB_PRODUCT_SPHAIRON_UB801R, + 0, + "Sphairon Access Systems GmbH", + "UB801R", + }, + { + USB_VENDOR_STMICRO, USB_PRODUCT_STMICRO_BIOCPU, + 0, + "STMicroelectronics", + "Biometric Coprocessor", + }, + { + USB_VENDOR_STMICRO, USB_PRODUCT_STMICRO_COMMUNICATOR, + 0, + "STMicroelectronics", + "USB Communicator", + }, + { + USB_VENDOR_STSN, USB_PRODUCT_STSN_STSN0001, + 0, + "STSN", + "Internet Access Device", + }, + { + USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_DS96L, + 0, + "SUN Corporation", + "SUNTAC U-Cable type D2", + }, + { + USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_PS64P1, + 0, + "SUN Corporation", + "SUNTAC U-Cable type P1", + }, + { + USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_VS10U, + 0, + "SUN Corporation", + "SUNTAC Slipper U", + }, + { + USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_IS96U, + 0, + "SUN Corporation", + "SUNTAC Ir-Trinity", + }, + { + USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_AS64LX, + 0, + "SUN Corporation", + "SUNTAC U-Cable type A3", + }, + { + USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_AS144L4, + 0, + "SUN Corporation", + "SUNTAC U-Cable type A4", + }, + { + USB_VENDOR_SUN, USB_PRODUCT_SUN_KEYBOARD, + 0, + "Sun Microsystems", + "Type 6 USB keyboard", + }, + { + USB_VENDOR_SUN, USB_PRODUCT_SUN_MOUSE, + 0, + "Sun Microsystems", + "Type 6 USB mouse", + }, + { + USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_SUPRAEXPRESS56K, + 0, + "Diamond (Supra)", + "Supra Express 56K modem", + }, + { + USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_SUPRA2890, + 0, + "Diamond (Supra)", + "SupraMax 2890 56K Modem", + }, + { + USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_RIO600USB, + 0, + "Diamond (Supra)", + "Rio 600 USB", + }, + { + USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_RIO800USB, + 0, + "Diamond (Supra)", + "Rio 800 USB", + }, + { + USB_VENDOR_SURECOM, USB_PRODUCT_SURECOM_RT2570, + 0, + "Surecom Technology", + "RT2570", + }, + { + USB_VENDOR_SURECOM, USB_PRODUCT_SURECOM_RT2573, + 0, + "Surecom Technology", + "RT2573", + }, + { + USB_VENDOR_SWEEX, USB_PRODUCT_SWEEX_ZD1211, + 0, + "Sweex", + "ZD1211", + }, + { + USB_VENDOR_SYSTEMTALKS, USB_PRODUCT_SYSTEMTALKS_SGCX2UL, + 0, + "System Talks", + "SGC-X2UL", + }, + { + USB_VENDOR_TAPWAVE, USB_PRODUCT_TAPWAVE_ZODIAC, + 0, + "Tapwave", + "Zodiac", + }, + { + USB_VENDOR_TAUGA, USB_PRODUCT_TAUGA_CAMERAMATE, + 0, + "Taugagreining HF", + "CameraMate (DPCM_USB)", + }, + { + USB_VENDOR_TDK, USB_PRODUCT_TDK_UPA9664, + 0, + "TDK", + "USB-PDC Adapter UPA9664", + }, + { + USB_VENDOR_TDK, USB_PRODUCT_TDK_UCA1464, + 0, + "TDK", + "USB-cdmaOne Adapter UCA1464", + }, + { + USB_VENDOR_TDK, USB_PRODUCT_TDK_UHA6400, + 0, + "TDK", + "USB-PHS Adapter UHA6400", + }, + { + USB_VENDOR_TDK, USB_PRODUCT_TDK_UPA6400, + 0, + "TDK", + "USB-PHS Adapter UPA6400", + }, + { + USB_VENDOR_TDK, USB_PRODUCT_TDK_BT_DONGLE, + 0, + "TDK", + "Bluetooth USB dongle", + }, + { + USB_VENDOR_TEAC, USB_PRODUCT_TEAC_FD05PUB, + 0, + "TEAC", + "FD-05PUB floppy", + }, + { + USB_VENDOR_TEKRAM, USB_PRODUCT_TEKRAM_QUICKWLAN, + 0, + "Tekram Technology", + "QuickWLAN", + }, + { + USB_VENDOR_TEKRAM, USB_PRODUCT_TEKRAM_ZD1211_1, + 0, + "Tekram Technology", + "ZD1211", + }, + { + USB_VENDOR_TEKRAM, USB_PRODUCT_TEKRAM_ZD1211_2, + 0, + "Tekram Technology", + "ZD1211", + }, + { + USB_VENDOR_TELEX, USB_PRODUCT_TELEX_MIC1, + 0, + "Telex Communications", + "Enhanced USB Microphone", + }, + { + USB_VENDOR_TENX, USB_PRODUCT_TENX_UAUDIO0, + 0, + "Ten X Technology, Inc.", + "USB audio headset", + }, + { + USB_VENDOR_TI, USB_PRODUCT_TI_UTUSB41, + 0, + "Texas Instruments", + "UT-USB41 hub", + }, + { + USB_VENDOR_TI, USB_PRODUCT_TI_TUSB2046, + 0, + "Texas Instruments", + "TUSB2046 hub", + }, + { + USB_VENDOR_THRUST, USB_PRODUCT_THRUST_FUSION_PAD, + 0, + "Thrustmaster", + "Fusion Digital Gamepad", + }, + { + USB_VENDOR_TOPRE, USB_PRODUCT_TOPRE_HHKB, + 0, + "Topre Corporation", + "HHKB Professional", + }, + { + USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_POCKETPC_E740, + 0, + "Toshiba", + "PocketPC e740", + }, + { + USB_VENDOR_TREK, USB_PRODUCT_TREK_THUMBDRIVE, + 0, + "Trek Technology", + "ThumbDrive", + }, + { + USB_VENDOR_TREK, USB_PRODUCT_TREK_MEMKEY, + 0, + "Trek Technology", + "IBM USB Memory Key", + }, + { + USB_VENDOR_TREK, USB_PRODUCT_TREK_THUMBDRIVE_8MB, + 0, + "Trek Technology", + "ThumbDrive_8MB", + }, + { + USB_VENDOR_TRIPPLITE, USB_PRODUCT_TRIPPLITE_U209, + 0, + "Tripp-Lite", + "Serial", + }, + { + USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_T33520, + 0, + "Trumpion Microelectronics", + "T33520 USB Flash Card Controller", + }, + { + USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_C3310, + 0, + "Trumpion Microelectronics", + "Comotron C3310 MP3 player", + }, + { + USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_MP3, + 0, + "Trumpion Microelectronics", + "MP3 player", + }, + { + USB_VENDOR_TWINMOS, USB_PRODUCT_TWINMOS_G240, + 0, + "TwinMOS", + "G240", + }, + { + USB_VENDOR_TWINMOS, USB_PRODUCT_TWINMOS_MDIV, + 0, + "TwinMOS", + "Memory Disk IV", + }, + { + USB_VENDOR_UBIQUAM, USB_PRODUCT_UBIQUAM_UALL, + 0, + "UBIQUAM Co., Ltd.", + "CDMA 1xRTT USB Modem (U-100/105/200/300/520)", + }, + { + USB_VENDOR_ULTIMA, USB_PRODUCT_ULTIMA_1200UBPLUS, + 0, + "Ultima", + "1200 UB Plus scanner", + }, + { + USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA1236U, + 0, + "UMAX Data Systems", + "Astra 1236U Scanner", + }, + { + USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA1220U, + 0, + "UMAX Data Systems", + "Astra 1220U Scanner", + }, + { + USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2000U, + 0, + "UMAX Data Systems", + "Astra 2000U Scanner", + }, + { + USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2100U, + 0, + "UMAX Data Systems", + "Astra 2100U Scanner", + }, + { + USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2200U, + 0, + "UMAX Data Systems", + "Astra 2200U Scanner", + }, + { + USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA3400, + 0, + "UMAX Data Systems", + "Astra 3400 Scanner", + }, + { + USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_TEW444UBEU, + 0, + "U-MEDIA Communications", + "TEW-444UB EU", + }, + { + USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_TEW444UBEU_NF, + 0, + "U-MEDIA Communications", + "TEW-444UB EU (no firmware)", + }, + { + USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_TEW429UB_A, + 0, + "U-MEDIA Communications", + "TEW-429UB_A", + }, + { + USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_TEW429UB, + 0, + "U-MEDIA Communications", + "TEW-429UB", + }, + { + USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_TEW429UBC1, + 0, + "U-MEDIA Communications", + "TEW-429UB C1", + }, + { + USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_ALL0298V2, + 0, + "U-MEDIA Communications", + "ALL0298 v2", + }, + { + USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_AR5523_2, + 0, + "U-MEDIA Communications", + "AR5523", + }, + { + USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_AR5523_2_NF, + 0, + "U-MEDIA Communications", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_UNIACCESS, USB_PRODUCT_UNIACCESS_PANACHE, + 0, + "Universal Access", + "Panache Surf USB ISDN Adapter", + }, + { + USB_VENDOR_USR, USB_PRODUCT_USR_USR5423, + 0, + "U.S. Robotics", + "USR5423 WLAN", + }, + { + USB_VENDOR_VIA, USB_PRODUCT_VIA_USB2IDEBRIDGE, + 0, + "VIA", + "USB 2.0 IDE Bridge", + }, + { + USB_VENDOR_USI, USB_PRODUCT_USI_MC60, + 0, + "USI", + "MC60 Serial", + }, + { + USB_VENDOR_VIDZMEDIA, USB_PRODUCT_VIDZMEDIA_MONSTERTV, + 0, + "VidzMedia Pte Ltd", + "MonsterTV P2H", + }, + { + USB_VENDOR_VISION, USB_PRODUCT_VISION_VC6452V002, + 0, + "VLSI Vision", + "CPiA Camera", + }, + { + USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_7600, + 0, + "Visioneer", + "OneTouch 7600", + }, + { + USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_5300, + 0, + "Visioneer", + "OneTouch 5300", + }, + { + USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_3000, + 0, + "Visioneer", + "Scanport 3000", + }, + { + USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_6100, + 0, + "Visioneer", + "OneTouch 6100", + }, + { + USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_6200, + 0, + "Visioneer", + "OneTouch 6200", + }, + { + USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_8100, + 0, + "Visioneer", + "OneTouch 8100", + }, + { + USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_8600, + 0, + "Visioneer", + "OneTouch 8600", + }, + { + USB_VENDOR_VIVITAR, USB_PRODUCT_VIVITAR_35XX, + 0, + "Vivitar", + "Vivicam 35Xx", + }, + { + USB_VENDOR_VTECH, USB_PRODUCT_VTECH_RT2570, + 0, + "VTech", + "RT2570", + }, + { + USB_VENDOR_VTECH, USB_PRODUCT_VTECH_ZD1211B, + 0, + "VTech", + "ZD1211B", + }, + { + USB_VENDOR_WACOM, USB_PRODUCT_WACOM_CT0405U, + 0, + "WACOM", + "CT-0405-U Tablet", + }, + { + USB_VENDOR_WACOM, USB_PRODUCT_WACOM_GRAPHIRE, + 0, + "WACOM", + "Graphire", + }, + { + USB_VENDOR_WACOM, USB_PRODUCT_WACOM_GRAPHIRE3_4X5, + 0, + "WACOM", + "Graphire 3 4x5", + }, + { + USB_VENDOR_WACOM, USB_PRODUCT_WACOM_INTUOSA5, + 0, + "WACOM", + "Intuos A5", + }, + { + USB_VENDOR_WACOM, USB_PRODUCT_WACOM_GD0912U, + 0, + "WACOM", + "Intuos 9x12 Graphics Tablet", + }, + { + USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341SER, + 0, + "QinHeng Electronics", + "CH341/CH340 USB-Serial Bridge", + }, + { + USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_COMBO, + 0, + "Western Digital", + "Firewire USB Combo", + }, + { + USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_EXTHDD, + 0, + "Western Digital", + "External HDD", + }, + { + USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_HUB, + 0, + "Western Digital", + "USB HUB", + }, + { + USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_MYBOOK, + 0, + "Western Digital", + "MyBook External HDD", + }, + { + USB_VENDOR_WINBOND, USB_PRODUCT_WINBOND_UH104, + 0, + "Winbond", + "4-port USB Hub", + }, + { + USB_VENDOR_WINMAXGROUP, USB_PRODUCT_WINMAXGROUP_FLASH64MC, + 0, + "WinMaxGroup", + "USB Flash Disk 64M-C", + }, + { + USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_UR045G, + 0, + "Wistron NeWeb", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_UR055G, + 0, + "Wistron NeWeb", + "UR055G", + }, + { + USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_AR5523_1, + 0, + "Wistron NeWeb", + "AR5523", + }, + { + USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_AR5523_1_NF, + 0, + "Wistron NeWeb", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_AR5523_2, + 0, + "Wistron NeWeb", + "AR5523", + }, + { + USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_AR5523_2_NF, + 0, + "Wistron NeWeb", + "AR5523 (no firmware)", + }, + { + USB_VENDOR_XEROX, USB_PRODUCT_XEROX_WCM15, + 0, + "Xerox", + "WorkCenter M15", + }, + { + USB_VENDOR_XIRLINK, USB_PRODUCT_XIRLINK_PCCAM, + 0, + "Xirlink", + "IBM PC Camera", + }, + { + USB_VENDOR_XYRATEX, USB_PRODUCT_XYRATEX_PRISM_GT_1, + 0, + "Xyratex", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_XYRATEX, USB_PRODUCT_XYRATEX_PRISM_GT_2, + 0, + "Xyratex", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_YEDATA, USB_PRODUCT_YEDATA_FLASHBUSTERU, + 0, + "Y-E Data", + "Flashbuster-U", + }, + { + USB_VENDOR_YAMAHA, USB_PRODUCT_YAMAHA_UX256, + 0, + "YAMAHA", + "UX256 MIDI I/F", + }, + { + USB_VENDOR_YAMAHA, USB_PRODUCT_YAMAHA_UX96, + 0, + "YAMAHA", + "UX96 MIDI I/F", + }, + { + USB_VENDOR_YAMAHA, USB_PRODUCT_YAMAHA_RTA54I, + 0, + "YAMAHA", + "NetVolante RTA54i Broadband&ISDN Router", + }, + { + USB_VENDOR_YAMAHA, USB_PRODUCT_YAMAHA_RTA55I, + 0, + "YAMAHA", + "NetVolante RTA55i Broadband VoIP Router", + }, + { + USB_VENDOR_YAMAHA, USB_PRODUCT_YAMAHA_RTW65B, + 0, + "YAMAHA", + "NetVolante RTW65b Broadband Wireless Router", + }, + { + USB_VENDOR_YAMAHA, USB_PRODUCT_YAMAHA_RTW65I, + 0, + "YAMAHA", + "NetVolante RTW65i Broadband&ISDN Wireless Router", + }, + { + USB_VENDOR_YANO, USB_PRODUCT_YANO_U640MO, + 0, + "Yano", + "U640MO-03", + }, + { + USB_VENDOR_YANO, USB_PRODUCT_YANO_FW800HD, + 0, + "Yano", + "METALWEAR-HDD", + }, + { + USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_M4Y750, + 0, + "Z-Com", + "M4Y-750", + }, + { + USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_XI725, + 0, + "Z-Com", + "XI-725/726", + }, + { + USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_XI735, + 0, + "Z-Com", + "XI-735", + }, + { + USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_XG703A, + 0, + "Z-Com", + "PrismGT USB 2.0 WLAN", + }, + { + USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_ZD1211, + 0, + "Z-Com", + "ZD1211", + }, + { + USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_AR5523, + 0, + "Z-Com", + "AR5523", + }, + { + USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_AR5523_NF, + 0, + "Z-Com", + "AR5523 driver (no firmware)", + }, + { + USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_ZD1211B, + 0, + "Z-Com", + "ZD1211B", + }, + { + USB_VENDOR_ZINWELL, USB_PRODUCT_ZINWELL_RT2570, + 0, + "Zinwell", + "RT2570", + }, + { + USB_VENDOR_ZOOM, USB_PRODUCT_ZOOM_2986L, + 0, + "Zoom Telephonics", + "2986L Fax modem", + }, + { + USB_VENDOR_ZORAN, USB_PRODUCT_ZORAN_EX20DSC, + 0, + "Zoran Microelectronics", + "Digital Camera EX-20 DSC", + }, + { + USB_VENDOR_ZYDAS, USB_PRODUCT_ZYDAS_ZD1211, + 0, + "Zydas Technology Corporation", + "ZD1211 WLAN abg", + }, + { + USB_VENDOR_ZYDAS, USB_PRODUCT_ZYDAS_ZD1211B, + 0, + "Zydas Technology Corporation", + "ZD1211B", + }, + { + USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_OMNI56K, + 0, + "ZyXEL Communication", + "Omni 56K Plus", + }, + { + USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_980N, + 0, + "ZyXEL Communication", + "Scorpion-980N keyboard", + }, + { + USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_ZYAIRG220, + 0, + "ZyXEL Communication", + "ZyAIR G-220", + }, + { + USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_G200V2, + 0, + "ZyXEL Communication", + "G-200 v2", + }, + { + USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_AG225H, + 0, + "ZyXEL Communication", + "AG-225H", + }, + { + USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_M202, + 0, + "ZyXEL Communication", + "M-202", + }, + { + USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_G220V2, + 0, + "ZyXEL Communication", + "G-220 v2", + }, + { + USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_G202, + 0, + "ZyXEL Communication", + "G-202", + }, + { + USB_VENDOR_UNKNOWN1, 0, + USB_KNOWNDEV_NOPROD, + "Unknown vendor", + NULL, + }, + { + USB_VENDOR_UNKNOWN2, 0, + USB_KNOWNDEV_NOPROD, + "Unknown vendor", + NULL, + }, + { + USB_VENDOR_EGALAX2, 0, + USB_KNOWNDEV_NOPROD, + "eGalax, Inc.", + NULL, + }, + { + USB_VENDOR_HUMAX, 0, + USB_KNOWNDEV_NOPROD, + "HUMAX", + NULL, + }, + { + USB_VENDOR_LTS, 0, + USB_KNOWNDEV_NOPROD, + "LTS", + NULL, + }, + { + USB_VENDOR_BWCT, 0, + USB_KNOWNDEV_NOPROD, + "Bernd Walter Computer Technology", + NULL, + }, + { + USB_VENDOR_AOX, 0, + USB_KNOWNDEV_NOPROD, + "AOX", + NULL, + }, + { + USB_VENDOR_THESYS, 0, + USB_KNOWNDEV_NOPROD, + "Thesys", + NULL, + }, + { + USB_VENDOR_DATABROADCAST, 0, + USB_KNOWNDEV_NOPROD, + "Data Broadcasting", + NULL, + }, + { + USB_VENDOR_ATMEL, 0, + USB_KNOWNDEV_NOPROD, + "Atmel", + NULL, + }, + { + USB_VENDOR_IWATSU, 0, + USB_KNOWNDEV_NOPROD, + "Iwatsu America", + NULL, + }, + { + USB_VENDOR_MITSUMI, 0, + USB_KNOWNDEV_NOPROD, + "Mitsumi", + NULL, + }, + { + USB_VENDOR_HP, 0, + USB_KNOWNDEV_NOPROD, + "Hewlett Packard", + NULL, + }, + { + USB_VENDOR_GENOA, 0, + USB_KNOWNDEV_NOPROD, + "Genoa", + NULL, + }, + { + USB_VENDOR_OAK, 0, + USB_KNOWNDEV_NOPROD, + "Oak", + NULL, + }, + { + USB_VENDOR_ADAPTEC, 0, + USB_KNOWNDEV_NOPROD, + "Adaptec", + NULL, + }, + { + USB_VENDOR_DIEBOLD, 0, + USB_KNOWNDEV_NOPROD, + "Diebold", + NULL, + }, + { + USB_VENDOR_SIEMENSELECTRO, 0, + USB_KNOWNDEV_NOPROD, + "Siemens Electromechanical", + NULL, + }, + { + USB_VENDOR_EPSONIMAGING, 0, + USB_KNOWNDEV_NOPROD, + "Epson Imaging", + NULL, + }, + { + USB_VENDOR_KEYTRONIC, 0, + USB_KNOWNDEV_NOPROD, + "KeyTronic", + NULL, + }, + { + USB_VENDOR_OPTI, 0, + USB_KNOWNDEV_NOPROD, + "OPTi", + NULL, + }, + { + USB_VENDOR_ELITEGROUP, 0, + USB_KNOWNDEV_NOPROD, + "Elitegroup", + NULL, + }, + { + USB_VENDOR_XILINX, 0, + USB_KNOWNDEV_NOPROD, + "Xilinx", + NULL, + }, + { + USB_VENDOR_FARALLON, 0, + USB_KNOWNDEV_NOPROD, + "Farallon Communications", + NULL, + }, + { + USB_VENDOR_NATIONAL, 0, + USB_KNOWNDEV_NOPROD, + "National Semiconductor", + NULL, + }, + { + USB_VENDOR_NATIONALREG, 0, + USB_KNOWNDEV_NOPROD, + "National Registry", + NULL, + }, + { + USB_VENDOR_ACERLABS, 0, + USB_KNOWNDEV_NOPROD, + "Acer Labs", + NULL, + }, + { + USB_VENDOR_FTDI, 0, + USB_KNOWNDEV_NOPROD, + "Future Technology Devices", + NULL, + }, + { + USB_VENDOR_NCR, 0, + USB_KNOWNDEV_NOPROD, + "NCR", + NULL, + }, + { + USB_VENDOR_SYNOPSYS2, 0, + USB_KNOWNDEV_NOPROD, + "Synopsys", + NULL, + }, + { + USB_VENDOR_FUJITSUICL, 0, + USB_KNOWNDEV_NOPROD, + "Fujitsu-ICL", + NULL, + }, + { + USB_VENDOR_FUJITSU2, 0, + USB_KNOWNDEV_NOPROD, + "Fujitsu Personal Systems", + NULL, + }, + { + USB_VENDOR_QUANTA, 0, + USB_KNOWNDEV_NOPROD, + "Quanta", + NULL, + }, + { + USB_VENDOR_NEC, 0, + USB_KNOWNDEV_NOPROD, + "NEC", + NULL, + }, + { + USB_VENDOR_KODAK, 0, + USB_KNOWNDEV_NOPROD, + "Eastman Kodak", + NULL, + }, + { + USB_VENDOR_WELTREND, 0, + USB_KNOWNDEV_NOPROD, + "Weltrend", + NULL, + }, + { + USB_VENDOR_VIA, 0, + USB_KNOWNDEV_NOPROD, + "VIA", + NULL, + }, + { + USB_VENDOR_MCCI, 0, + USB_KNOWNDEV_NOPROD, + "MCCI", + NULL, + }, + { + USB_VENDOR_MELCO, 0, + USB_KNOWNDEV_NOPROD, + "Melco", + NULL, + }, + { + USB_VENDOR_LEADTEK, 0, + USB_KNOWNDEV_NOPROD, + "Leadtek", + NULL, + }, + { + USB_VENDOR_WINBOND, 0, + USB_KNOWNDEV_NOPROD, + "Winbond", + NULL, + }, + { + USB_VENDOR_PHOENIX, 0, + USB_KNOWNDEV_NOPROD, + "Phoenix", + NULL, + }, + { + USB_VENDOR_CREATIVE, 0, + USB_KNOWNDEV_NOPROD, + "Creative Labs", + NULL, + }, + { + USB_VENDOR_NOKIA, 0, + USB_KNOWNDEV_NOPROD, + "Nokia", + NULL, + }, + { + USB_VENDOR_ADI, 0, + USB_KNOWNDEV_NOPROD, + "ADI Systems", + NULL, + }, + { + USB_VENDOR_CATC, 0, + USB_KNOWNDEV_NOPROD, + "Computer Access Technology", + NULL, + }, + { + USB_VENDOR_SMC2, 0, + USB_KNOWNDEV_NOPROD, + "Standard Microsystems", + NULL, + }, + { + USB_VENDOR_MOTOROLA_HK, 0, + USB_KNOWNDEV_NOPROD, + "Motorola HK", + NULL, + }, + { + USB_VENDOR_GRAVIS, 0, + USB_KNOWNDEV_NOPROD, + "Advanced Gravis Computer", + NULL, + }, + { + USB_VENDOR_CIRRUSLOGIC, 0, + USB_KNOWNDEV_NOPROD, + "Cirrus Logic", + NULL, + }, + { + USB_VENDOR_INNOVATIVE, 0, + USB_KNOWNDEV_NOPROD, + "Innovative Semiconductors", + NULL, + }, + { + USB_VENDOR_MOLEX, 0, + USB_KNOWNDEV_NOPROD, + "Molex", + NULL, + }, + { + USB_VENDOR_SUN, 0, + USB_KNOWNDEV_NOPROD, + "Sun Microsystems", + NULL, + }, + { + USB_VENDOR_UNISYS, 0, + USB_KNOWNDEV_NOPROD, + "Unisys", + NULL, + }, + { + USB_VENDOR_TAUGA, 0, + USB_KNOWNDEV_NOPROD, + "Taugagreining HF", + NULL, + }, + { + USB_VENDOR_AMD, 0, + USB_KNOWNDEV_NOPROD, + "Advanced Micro Devices", + NULL, + }, + { + USB_VENDOR_LEXMARK, 0, + USB_KNOWNDEV_NOPROD, + "Lexmark International", + NULL, + }, + { + USB_VENDOR_LG, 0, + USB_KNOWNDEV_NOPROD, + "LG Electronics", + NULL, + }, + { + USB_VENDOR_NANAO, 0, + USB_KNOWNDEV_NOPROD, + "NANAO", + NULL, + }, + { + USB_VENDOR_GATEWAY, 0, + USB_KNOWNDEV_NOPROD, + "Gateway 2000", + NULL, + }, + { + USB_VENDOR_NMB, 0, + USB_KNOWNDEV_NOPROD, + "NMB", + NULL, + }, + { + USB_VENDOR_ALPS, 0, + USB_KNOWNDEV_NOPROD, + "Alps Electric", + NULL, + }, + { + USB_VENDOR_THRUST, 0, + USB_KNOWNDEV_NOPROD, + "Thrustmaster", + NULL, + }, + { + USB_VENDOR_TI, 0, + USB_KNOWNDEV_NOPROD, + "Texas Instruments", + NULL, + }, + { + USB_VENDOR_ANALOGDEVICES, 0, + USB_KNOWNDEV_NOPROD, + "Analog Devices", + NULL, + }, + { + USB_VENDOR_SIS, 0, + USB_KNOWNDEV_NOPROD, + "Silicon Integrated Systems Corp.", + NULL, + }, + { + USB_VENDOR_KYE, 0, + USB_KNOWNDEV_NOPROD, + "KYE Systems", + NULL, + }, + { + USB_VENDOR_DIAMOND2, 0, + USB_KNOWNDEV_NOPROD, + "Diamond (Supra)", + NULL, + }, + { + USB_VENDOR_RENESAS, 0, + USB_KNOWNDEV_NOPROD, + "Renesas", + NULL, + }, + { + USB_VENDOR_MICROSOFT, 0, + USB_KNOWNDEV_NOPROD, + "Microsoft", + NULL, + }, + { + USB_VENDOR_PRIMAX, 0, + USB_KNOWNDEV_NOPROD, + "Primax Electronics", + NULL, + }, + { + USB_VENDOR_MGE, 0, + USB_KNOWNDEV_NOPROD, + "MGE UPS Systems", + NULL, + }, + { + USB_VENDOR_AMP, 0, + USB_KNOWNDEV_NOPROD, + "AMP", + NULL, + }, + { + USB_VENDOR_CHERRY, 0, + USB_KNOWNDEV_NOPROD, + "Cherry Mikroschalter", + NULL, + }, + { + USB_VENDOR_MEGATRENDS, 0, + USB_KNOWNDEV_NOPROD, + "American Megatrends", + NULL, + }, + { + USB_VENDOR_LOGITECH, 0, + USB_KNOWNDEV_NOPROD, + "Logitech", + NULL, + }, + { + USB_VENDOR_BTC, 0, + USB_KNOWNDEV_NOPROD, + "Behavior Tech. Computer", + NULL, + }, + { + USB_VENDOR_PHILIPS, 0, + USB_KNOWNDEV_NOPROD, + "Philips", + NULL, + }, + { + USB_VENDOR_SUN2, 0, + USB_KNOWNDEV_NOPROD, + "Sun Microsystems (offical)", + NULL, + }, + { + USB_VENDOR_SANYO, 0, + USB_KNOWNDEV_NOPROD, + "Sanyo Electric", + NULL, + }, + { + USB_VENDOR_SEAGATE, 0, + USB_KNOWNDEV_NOPROD, + "Seagate", + NULL, + }, + { + USB_VENDOR_CONNECTIX, 0, + USB_KNOWNDEV_NOPROD, + "Connectix", + NULL, + }, + { + USB_VENDOR_SEMTECH, 0, + USB_KNOWNDEV_NOPROD, + "Semtech", + NULL, + }, + { + USB_VENDOR_KENSINGTON, 0, + USB_KNOWNDEV_NOPROD, + "Kensington", + NULL, + }, + { + USB_VENDOR_LUCENT, 0, + USB_KNOWNDEV_NOPROD, + "Lucent", + NULL, + }, + { + USB_VENDOR_PLANTRONICS, 0, + USB_KNOWNDEV_NOPROD, + "Plantronics", + NULL, + }, + { + USB_VENDOR_KYOCERA, 0, + USB_KNOWNDEV_NOPROD, + "Kyocera Wireless Corp.", + NULL, + }, + { + USB_VENDOR_STMICRO, 0, + USB_KNOWNDEV_NOPROD, + "STMicroelectronics", + NULL, + }, + { + USB_VENDOR_FOXCONN, 0, + USB_KNOWNDEV_NOPROD, + "Foxconn", + NULL, + }, + { + USB_VENDOR_YAMAHA, 0, + USB_KNOWNDEV_NOPROD, + "YAMAHA", + NULL, + }, + { + USB_VENDOR_COMPAQ, 0, + USB_KNOWNDEV_NOPROD, + "Compaq", + NULL, + }, + { + USB_VENDOR_HITACHI, 0, + USB_KNOWNDEV_NOPROD, + "Hitachi", + NULL, + }, + { + USB_VENDOR_ACERP, 0, + USB_KNOWNDEV_NOPROD, + "Acer Peripherals", + NULL, + }, + { + USB_VENDOR_DAVICOM, 0, + USB_KNOWNDEV_NOPROD, + "Davicom", + NULL, + }, + { + USB_VENDOR_VISIONEER, 0, + USB_KNOWNDEV_NOPROD, + "Visioneer", + NULL, + }, + { + USB_VENDOR_CANON, 0, + USB_KNOWNDEV_NOPROD, + "Canon", + NULL, + }, + { + USB_VENDOR_NIKON, 0, + USB_KNOWNDEV_NOPROD, + "Nikon", + NULL, + }, + { + USB_VENDOR_PAN, 0, + USB_KNOWNDEV_NOPROD, + "Pan International", + NULL, + }, + { + USB_VENDOR_IBM, 0, + USB_KNOWNDEV_NOPROD, + "IBM", + NULL, + }, + { + USB_VENDOR_CYPRESS, 0, + USB_KNOWNDEV_NOPROD, + "Cypress Semiconductor", + NULL, + }, + { + USB_VENDOR_ROHM, 0, + USB_KNOWNDEV_NOPROD, + "ROHM", + NULL, + }, + { + USB_VENDOR_COMPAL, 0, + USB_KNOWNDEV_NOPROD, + "Compal", + NULL, + }, + { + USB_VENDOR_EPSON, 0, + USB_KNOWNDEV_NOPROD, + "Seiko Epson", + NULL, + }, + { + USB_VENDOR_RAINBOW, 0, + USB_KNOWNDEV_NOPROD, + "Rainbow Technologies", + NULL, + }, + { + USB_VENDOR_IODATA, 0, + USB_KNOWNDEV_NOPROD, + "I-O Data", + NULL, + }, + { + USB_VENDOR_TDK, 0, + USB_KNOWNDEV_NOPROD, + "TDK", + NULL, + }, + { + USB_VENDOR_3COMUSR, 0, + USB_KNOWNDEV_NOPROD, + "U.S. Robotics", + NULL, + }, + { + USB_VENDOR_METHODE, 0, + USB_KNOWNDEV_NOPROD, + "Methode Electronics Far East", + NULL, + }, + { + USB_VENDOR_MAXISWITCH, 0, + USB_KNOWNDEV_NOPROD, + "Maxi Switch", + NULL, + }, + { + USB_VENDOR_LOCKHEEDMER, 0, + USB_KNOWNDEV_NOPROD, + "Lockheed Martin Energy Research", + NULL, + }, + { + USB_VENDOR_FUJITSU, 0, + USB_KNOWNDEV_NOPROD, + "Fujitsu", + NULL, + }, + { + USB_VENDOR_TOSHIBAAM, 0, + USB_KNOWNDEV_NOPROD, + "Toshiba America", + NULL, + }, + { + USB_VENDOR_MICROMACRO, 0, + USB_KNOWNDEV_NOPROD, + "Micro Macro Technologies", + NULL, + }, + { + USB_VENDOR_KONICA, 0, + USB_KNOWNDEV_NOPROD, + "Konica", + NULL, + }, + { + USB_VENDOR_LITEON, 0, + USB_KNOWNDEV_NOPROD, + "Lite-On Technology", + NULL, + }, + { + USB_VENDOR_FUJIPHOTO, 0, + USB_KNOWNDEV_NOPROD, + "Fuji Photo Film", + NULL, + }, + { + USB_VENDOR_PHILIPSSEMI, 0, + USB_KNOWNDEV_NOPROD, + "Philips Semiconductors", + NULL, + }, + { + USB_VENDOR_TATUNG, 0, + USB_KNOWNDEV_NOPROD, + "Tatung Co. Of America", + NULL, + }, + { + USB_VENDOR_SCANLOGIC, 0, + USB_KNOWNDEV_NOPROD, + "ScanLogic", + NULL, + }, + { + USB_VENDOR_MYSON, 0, + USB_KNOWNDEV_NOPROD, + "Myson Technology", + NULL, + }, + { + USB_VENDOR_DIGI2, 0, + USB_KNOWNDEV_NOPROD, + "Digi", + NULL, + }, + { + USB_VENDOR_ITTCANON, 0, + USB_KNOWNDEV_NOPROD, + "ITT Canon", + NULL, + }, + { + USB_VENDOR_ALTEC, 0, + USB_KNOWNDEV_NOPROD, + "Altec Lansing", + NULL, + }, + { + USB_VENDOR_LSI, 0, + USB_KNOWNDEV_NOPROD, + "LSI", + NULL, + }, + { + USB_VENDOR_MENTORGRAPHICS, 0, + USB_KNOWNDEV_NOPROD, + "Mentor Graphics", + NULL, + }, + { + USB_VENDOR_ITUNERNET, 0, + USB_KNOWNDEV_NOPROD, + "I-Tuner Networks", + NULL, + }, + { + USB_VENDOR_HOLTEK, 0, + USB_KNOWNDEV_NOPROD, + "Holtek Semiconductor, Inc.", + NULL, + }, + { + USB_VENDOR_PANASONIC, 0, + USB_KNOWNDEV_NOPROD, + "Panasonic (Matsushita)", + NULL, + }, + { + USB_VENDOR_HUANHSIN, 0, + USB_KNOWNDEV_NOPROD, + "Huan Hsin", + NULL, + }, + { + USB_VENDOR_SHARP, 0, + USB_KNOWNDEV_NOPROD, + "Sharp", + NULL, + }, + { + USB_VENDOR_IIYAMA, 0, + USB_KNOWNDEV_NOPROD, + "Iiyama", + NULL, + }, + { + USB_VENDOR_SHUTTLE, 0, + USB_KNOWNDEV_NOPROD, + "Shuttle Technology", + NULL, + }, + { + USB_VENDOR_ELO, 0, + USB_KNOWNDEV_NOPROD, + "Elo TouchSystems", + NULL, + }, + { + USB_VENDOR_SAMSUNG, 0, + USB_KNOWNDEV_NOPROD, + "Samsung Electronics", + NULL, + }, + { + USB_VENDOR_NORTHSTAR, 0, + USB_KNOWNDEV_NOPROD, + "Northstar", + NULL, + }, + { + USB_VENDOR_TOKYOELECTRON, 0, + USB_KNOWNDEV_NOPROD, + "Tokyo Electron", + NULL, + }, + { + USB_VENDOR_ANNABOOKS, 0, + USB_KNOWNDEV_NOPROD, + "Annabooks", + NULL, + }, + { + USB_VENDOR_JVC, 0, + USB_KNOWNDEV_NOPROD, + "JVC", + NULL, + }, + { + USB_VENDOR_CHICONY, 0, + USB_KNOWNDEV_NOPROD, + "Chicony Electronics", + NULL, + }, + { + USB_VENDOR_ELAN, 0, + USB_KNOWNDEV_NOPROD, + "Elan", + NULL, + }, + { + USB_VENDOR_NEWNEX, 0, + USB_KNOWNDEV_NOPROD, + "Newnex", + NULL, + }, + { + USB_VENDOR_BROTHER, 0, + USB_KNOWNDEV_NOPROD, + "Brother Industries", + NULL, + }, + { + USB_VENDOR_DALLAS, 0, + USB_KNOWNDEV_NOPROD, + "Dallas Semiconductor", + NULL, + }, + { + USB_VENDOR_SUNPLUS, 0, + USB_KNOWNDEV_NOPROD, + "Sunplus", + NULL, + }, + { + USB_VENDOR_PFU, 0, + USB_KNOWNDEV_NOPROD, + "PFU", + NULL, + }, + { + USB_VENDOR_FUJIKURA, 0, + USB_KNOWNDEV_NOPROD, + "Fujikura/DDK", + NULL, + }, + { + USB_VENDOR_ACER, 0, + USB_KNOWNDEV_NOPROD, + "Acer", + NULL, + }, + { + USB_VENDOR_3COM, 0, + USB_KNOWNDEV_NOPROD, + "3Com", + NULL, + }, + { + USB_VENDOR_HOSIDEN, 0, + USB_KNOWNDEV_NOPROD, + "Hosiden Corporation", + NULL, + }, + { + USB_VENDOR_AZTECH, 0, + USB_KNOWNDEV_NOPROD, + "Aztech Systems", + NULL, + }, + { + USB_VENDOR_BELKIN, 0, + USB_KNOWNDEV_NOPROD, + "Belkin Components", + NULL, + }, + { + USB_VENDOR_KAWATSU, 0, + USB_KNOWNDEV_NOPROD, + "Kawatsu Semiconductor", + NULL, + }, + { + USB_VENDOR_FCI, 0, + USB_KNOWNDEV_NOPROD, + "FCI", + NULL, + }, + { + USB_VENDOR_LONGWELL, 0, + USB_KNOWNDEV_NOPROD, + "Longwell", + NULL, + }, + { + USB_VENDOR_COMPOSITE, 0, + USB_KNOWNDEV_NOPROD, + "Composite", + NULL, + }, + { + USB_VENDOR_STAR, 0, + USB_KNOWNDEV_NOPROD, + "Star Micronics", + NULL, + }, + { + USB_VENDOR_APC, 0, + USB_KNOWNDEV_NOPROD, + "American Power Conversion", + NULL, + }, + { + USB_VENDOR_SCIATLANTA, 0, + USB_KNOWNDEV_NOPROD, + "Scientific Atlanta", + NULL, + }, + { + USB_VENDOR_TSM, 0, + USB_KNOWNDEV_NOPROD, + "TSM", + NULL, + }, + { + USB_VENDOR_CONNECTEK, 0, + USB_KNOWNDEV_NOPROD, + "Advanced Connectek USA", + NULL, + }, + { + USB_VENDOR_NETCHIP, 0, + USB_KNOWNDEV_NOPROD, + "NetChip Technology", + NULL, + }, + { + USB_VENDOR_ALTRA, 0, + USB_KNOWNDEV_NOPROD, + "ALTRA", + NULL, + }, + { + USB_VENDOR_ATI, 0, + USB_KNOWNDEV_NOPROD, + "ATI Technologies", + NULL, + }, + { + USB_VENDOR_AKS, 0, + USB_KNOWNDEV_NOPROD, + "Aladdin Knowledge Systems", + NULL, + }, + { + USB_VENDOR_TEKOM, 0, + USB_KNOWNDEV_NOPROD, + "Tekom", + NULL, + }, + { + USB_VENDOR_CANONDEV, 0, + USB_KNOWNDEV_NOPROD, + "Canon", + NULL, + }, + { + USB_VENDOR_WACOMTECH, 0, + USB_KNOWNDEV_NOPROD, + "Wacom", + NULL, + }, + { + USB_VENDOR_INVENTEC, 0, + USB_KNOWNDEV_NOPROD, + "Inventec", + NULL, + }, + { + USB_VENDOR_SHYHSHIUN, 0, + USB_KNOWNDEV_NOPROD, + "Shyh Shiun Terminals", + NULL, + }, + { + USB_VENDOR_PREHWERKE, 0, + USB_KNOWNDEV_NOPROD, + "Preh Werke Gmbh & Co. KG", + NULL, + }, + { + USB_VENDOR_SYNOPSYS, 0, + USB_KNOWNDEV_NOPROD, + "Synopsys", + NULL, + }, + { + USB_VENDOR_UNIACCESS, 0, + USB_KNOWNDEV_NOPROD, + "Universal Access", + NULL, + }, + { + USB_VENDOR_VIEWSONIC, 0, + USB_KNOWNDEV_NOPROD, + "ViewSonic", + NULL, + }, + { + USB_VENDOR_XIRLINK, 0, + USB_KNOWNDEV_NOPROD, + "Xirlink", + NULL, + }, + { + USB_VENDOR_ANCHOR, 0, + USB_KNOWNDEV_NOPROD, + "Anchor Chips", + NULL, + }, + { + USB_VENDOR_SONY, 0, + USB_KNOWNDEV_NOPROD, + "Sony", + NULL, + }, + { + USB_VENDOR_FUJIXEROX, 0, + USB_KNOWNDEV_NOPROD, + "Fuji Xerox", + NULL, + }, + { + USB_VENDOR_VISION, 0, + USB_KNOWNDEV_NOPROD, + "VLSI Vision", + NULL, + }, + { + USB_VENDOR_ASAHIKASEI, 0, + USB_KNOWNDEV_NOPROD, + "Asahi Kasei Microsystems", + NULL, + }, + { + USB_VENDOR_ATEN, 0, + USB_KNOWNDEV_NOPROD, + "ATEN International", + NULL, + }, + { + USB_VENDOR_SAMSUNG2, 0, + USB_KNOWNDEV_NOPROD, + "Samsung Electronics", + NULL, + }, + { + USB_VENDOR_MUSTEK, 0, + USB_KNOWNDEV_NOPROD, + "Mustek Systems", + NULL, + }, + { + USB_VENDOR_TELEX, 0, + USB_KNOWNDEV_NOPROD, + "Telex Communications", + NULL, + }, + { + USB_VENDOR_CHINON, 0, + USB_KNOWNDEV_NOPROD, + "Chinon", + NULL, + }, + { + USB_VENDOR_PERACOM, 0, + USB_KNOWNDEV_NOPROD, + "Peracom Networks", + NULL, + }, + { + USB_VENDOR_ALCOR2, 0, + USB_KNOWNDEV_NOPROD, + "Alcor Micro", + NULL, + }, + { + USB_VENDOR_XYRATEX, 0, + USB_KNOWNDEV_NOPROD, + "Xyratex", + NULL, + }, + { + USB_VENDOR_WACOM, 0, + USB_KNOWNDEV_NOPROD, + "WACOM", + NULL, + }, + { + USB_VENDOR_ETEK, 0, + USB_KNOWNDEV_NOPROD, + "e-TEK Labs", + NULL, + }, + { + USB_VENDOR_EIZO, 0, + USB_KNOWNDEV_NOPROD, + "EIZO", + NULL, + }, + { + USB_VENDOR_ELECOM, 0, + USB_KNOWNDEV_NOPROD, + "Elecom", + NULL, + }, + { + USB_VENDOR_CONEXANT, 0, + USB_KNOWNDEV_NOPROD, + "Conexant", + NULL, + }, + { + USB_VENDOR_HAUPPAUGE, 0, + USB_KNOWNDEV_NOPROD, + "Hauppauge Computer Works", + NULL, + }, + { + USB_VENDOR_BAFO, 0, + USB_KNOWNDEV_NOPROD, + "BAFO/Quality Computer Accessories", + NULL, + }, + { + USB_VENDOR_YEDATA, 0, + USB_KNOWNDEV_NOPROD, + "Y-E Data", + NULL, + }, + { + USB_VENDOR_AVM, 0, + USB_KNOWNDEV_NOPROD, + "AVM", + NULL, + }, + { + USB_VENDOR_QUICKSHOT, 0, + USB_KNOWNDEV_NOPROD, + "Quickshot", + NULL, + }, + { + USB_VENDOR_ROLAND, 0, + USB_KNOWNDEV_NOPROD, + "Roland", + NULL, + }, + { + USB_VENDOR_ROCKFIRE, 0, + USB_KNOWNDEV_NOPROD, + "Rockfire", + NULL, + }, + { + USB_VENDOR_RATOC, 0, + USB_KNOWNDEV_NOPROD, + "RATOC Systems", + NULL, + }, + { + USB_VENDOR_ZYXEL, 0, + USB_KNOWNDEV_NOPROD, + "ZyXEL Communication", + NULL, + }, + { + USB_VENDOR_INFINEON, 0, + USB_KNOWNDEV_NOPROD, + "Infineon", + NULL, + }, + { + USB_VENDOR_MICREL, 0, + USB_KNOWNDEV_NOPROD, + "Micrel", + NULL, + }, + { + USB_VENDOR_ALCOR, 0, + USB_KNOWNDEV_NOPROD, + "Alcor Micro", + NULL, + }, + { + USB_VENDOR_OMRON, 0, + USB_KNOWNDEV_NOPROD, + "OMRON", + NULL, + }, + { + USB_VENDOR_ZORAN, 0, + USB_KNOWNDEV_NOPROD, + "Zoran Microelectronics", + NULL, + }, + { + USB_VENDOR_NIIGATA, 0, + USB_KNOWNDEV_NOPROD, + "Niigata", + NULL, + }, + { + USB_VENDOR_IOMEGA, 0, + USB_KNOWNDEV_NOPROD, + "Iomega", + NULL, + }, + { + USB_VENDOR_ATREND, 0, + USB_KNOWNDEV_NOPROD, + "A-Trend Technology", + NULL, + }, + { + USB_VENDOR_AID, 0, + USB_KNOWNDEV_NOPROD, + "Advanced Input Devices", + NULL, + }, + { + USB_VENDOR_LACIE, 0, + USB_KNOWNDEV_NOPROD, + "LaCie", + NULL, + }, + { + USB_VENDOR_FUJIFILM, 0, + USB_KNOWNDEV_NOPROD, + "Fuji Film", + NULL, + }, + { + USB_VENDOR_ARC, 0, + USB_KNOWNDEV_NOPROD, + "ARC", + NULL, + }, + { + USB_VENDOR_ORTEK, 0, + USB_KNOWNDEV_NOPROD, + "Ortek", + NULL, + }, + { + USB_VENDOR_BOSE, 0, + USB_KNOWNDEV_NOPROD, + "Bose", + NULL, + }, + { + USB_VENDOR_OMNIVISION, 0, + USB_KNOWNDEV_NOPROD, + "OmniVision", + NULL, + }, + { + USB_VENDOR_INSYSTEM, 0, + USB_KNOWNDEV_NOPROD, + "In-System Design", + NULL, + }, + { + USB_VENDOR_APPLE, 0, + USB_KNOWNDEV_NOPROD, + "Apple Computer", + NULL, + }, + { + USB_VENDOR_YCCABLE, 0, + USB_KNOWNDEV_NOPROD, + "Y.C. Cable", + NULL, + }, + { + USB_VENDOR_DIGITALPERSONA, 0, + USB_KNOWNDEV_NOPROD, + "DigitalPersona", + NULL, + }, + { + USB_VENDOR_3G, 0, + USB_KNOWNDEV_NOPROD, + "3G Green Green Globe", + NULL, + }, + { + USB_VENDOR_RAFI, 0, + USB_KNOWNDEV_NOPROD, + "RAFI", + NULL, + }, + { + USB_VENDOR_TYCO, 0, + USB_KNOWNDEV_NOPROD, + "Tyco", + NULL, + }, + { + USB_VENDOR_KAWASAKI, 0, + USB_KNOWNDEV_NOPROD, + "Kawasaki", + NULL, + }, + { + USB_VENDOR_DIGI, 0, + USB_KNOWNDEV_NOPROD, + "Digi International", + NULL, + }, + { + USB_VENDOR_QUALCOMM2, 0, + USB_KNOWNDEV_NOPROD, + "Qualcomm", + NULL, + }, + { + USB_VENDOR_QTRONIX, 0, + USB_KNOWNDEV_NOPROD, + "Qtronix", + NULL, + }, + { + USB_VENDOR_FOXLINK, 0, + USB_KNOWNDEV_NOPROD, + "Foxlink", + NULL, + }, + { + USB_VENDOR_RICOH, 0, + USB_KNOWNDEV_NOPROD, + "Ricoh", + NULL, + }, + { + USB_VENDOR_ELSA, 0, + USB_KNOWNDEV_NOPROD, + "ELSA", + NULL, + }, + { + USB_VENDOR_SCIWORX, 0, + USB_KNOWNDEV_NOPROD, + "sci-worx", + NULL, + }, + { + USB_VENDOR_BRAINBOXES, 0, + USB_KNOWNDEV_NOPROD, + "Brainboxes Limited", + NULL, + }, + { + USB_VENDOR_ULTIMA, 0, + USB_KNOWNDEV_NOPROD, + "Ultima", + NULL, + }, + { + USB_VENDOR_AXIOHM, 0, + USB_KNOWNDEV_NOPROD, + "Axiohm Transaction Solutions", + NULL, + }, + { + USB_VENDOR_MICROTEK, 0, + USB_KNOWNDEV_NOPROD, + "Microtek", + NULL, + }, + { + USB_VENDOR_SUNTAC, 0, + USB_KNOWNDEV_NOPROD, + "SUN Corporation", + NULL, + }, + { + USB_VENDOR_LEXAR, 0, + USB_KNOWNDEV_NOPROD, + "Lexar Media", + NULL, + }, + { + USB_VENDOR_ADDTRON, 0, + USB_KNOWNDEV_NOPROD, + "Addtron", + NULL, + }, + { + USB_VENDOR_SYMBOL, 0, + USB_KNOWNDEV_NOPROD, + "Symbol Technologies", + NULL, + }, + { + USB_VENDOR_SYNTEK, 0, + USB_KNOWNDEV_NOPROD, + "Syntek", + NULL, + }, + { + USB_VENDOR_GENESYS, 0, + USB_KNOWNDEV_NOPROD, + "Genesys Logic", + NULL, + }, + { + USB_VENDOR_FUJI, 0, + USB_KNOWNDEV_NOPROD, + "Fuji Electric", + NULL, + }, + { + USB_VENDOR_KEITHLEY, 0, + USB_KNOWNDEV_NOPROD, + "Keithley Instruments", + NULL, + }, + { + USB_VENDOR_EIZONANAO, 0, + USB_KNOWNDEV_NOPROD, + "EIZO Nanao", + NULL, + }, + { + USB_VENDOR_KLSI, 0, + USB_KNOWNDEV_NOPROD, + "Kawasaki LSI", + NULL, + }, + { + USB_VENDOR_FFC, 0, + USB_KNOWNDEV_NOPROD, + "FFC", + NULL, + }, + { + USB_VENDOR_ANKO, 0, + USB_KNOWNDEV_NOPROD, + "Anko Electronic", + NULL, + }, + { + USB_VENDOR_PIENGINEERING, 0, + USB_KNOWNDEV_NOPROD, + "P.I. Engineering", + NULL, + }, + { + USB_VENDOR_AOC, 0, + USB_KNOWNDEV_NOPROD, + "AOC International", + NULL, + }, + { + USB_VENDOR_CHIC, 0, + USB_KNOWNDEV_NOPROD, + "Chic Technology", + NULL, + }, + { + USB_VENDOR_BARCO, 0, + USB_KNOWNDEV_NOPROD, + "Barco Display Systems", + NULL, + }, + { + USB_VENDOR_BRIDGE, 0, + USB_KNOWNDEV_NOPROD, + "Bridge Information", + NULL, + }, + { + USB_VENDOR_SOLIDYEAR, 0, + USB_KNOWNDEV_NOPROD, + "Solid Year", + NULL, + }, + { + USB_VENDOR_BIORAD, 0, + USB_KNOWNDEV_NOPROD, + "Bio-Rad Laboratories", + NULL, + }, + { + USB_VENDOR_MACALLY, 0, + USB_KNOWNDEV_NOPROD, + "Macally", + NULL, + }, + { + USB_VENDOR_ACTLABS, 0, + USB_KNOWNDEV_NOPROD, + "Act Labs", + NULL, + }, + { + USB_VENDOR_ALARIS, 0, + USB_KNOWNDEV_NOPROD, + "Alaris", + NULL, + }, + { + USB_VENDOR_APEX, 0, + USB_KNOWNDEV_NOPROD, + "Apex", + NULL, + }, + { + USB_VENDOR_CREATIVE3, 0, + USB_KNOWNDEV_NOPROD, + "Creative Labs", + NULL, + }, + { + USB_VENDOR_VIVITAR, 0, + USB_KNOWNDEV_NOPROD, + "Vivitar", + NULL, + }, + { + USB_VENDOR_GUNZE, 0, + USB_KNOWNDEV_NOPROD, + "Gunze Electronics USA", + NULL, + }, + { + USB_VENDOR_AVISION, 0, + USB_KNOWNDEV_NOPROD, + "Avision", + NULL, + }, + { + USB_VENDOR_TEAC, 0, + USB_KNOWNDEV_NOPROD, + "TEAC", + NULL, + }, + { + USB_VENDOR_SGI, 0, + USB_KNOWNDEV_NOPROD, + "Silicon Graphics", + NULL, + }, + { + USB_VENDOR_SANWASUPPLY, 0, + USB_KNOWNDEV_NOPROD, + "Sanwa Supply", + NULL, + }, + { + USB_VENDOR_LINKSYS, 0, + USB_KNOWNDEV_NOPROD, + "Linksys", + NULL, + }, + { + USB_VENDOR_ACERSA, 0, + USB_KNOWNDEV_NOPROD, + "Acer Semiconductor America", + NULL, + }, + { + USB_VENDOR_SIGMATEL, 0, + USB_KNOWNDEV_NOPROD, + "Sigmatel", + NULL, + }, + { + USB_VENDOR_DRAYTEK, 0, + USB_KNOWNDEV_NOPROD, + "DrayTek", + NULL, + }, + { + USB_VENDOR_AIWA, 0, + USB_KNOWNDEV_NOPROD, + "Aiwa", + NULL, + }, + { + USB_VENDOR_ACARD, 0, + USB_KNOWNDEV_NOPROD, + "ACARD Technology", + NULL, + }, + { + USB_VENDOR_PROLIFIC, 0, + USB_KNOWNDEV_NOPROD, + "Prolific Technology", + NULL, + }, + { + USB_VENDOR_SIEMENS, 0, + USB_KNOWNDEV_NOPROD, + "Siemens", + NULL, + }, + { + USB_VENDOR_AVANCELOGIC, 0, + USB_KNOWNDEV_NOPROD, + "Avance Logic", + NULL, + }, + { + USB_VENDOR_SIEMENS2, 0, + USB_KNOWNDEV_NOPROD, + "Siemens", + NULL, + }, + { + USB_VENDOR_MINOLTA, 0, + USB_KNOWNDEV_NOPROD, + "Minolta", + NULL, + }, + { + USB_VENDOR_CHPRODUCTS, 0, + USB_KNOWNDEV_NOPROD, + "CH Products", + NULL, + }, + { + USB_VENDOR_HAGIWARA, 0, + USB_KNOWNDEV_NOPROD, + "Hagiwara Sys-Com", + NULL, + }, + { + USB_VENDOR_CTX, 0, + USB_KNOWNDEV_NOPROD, + "Chuntex", + NULL, + }, + { + USB_VENDOR_ASKEY, 0, + USB_KNOWNDEV_NOPROD, + "Askey Computer", + NULL, + }, + { + USB_VENDOR_SAITEK, 0, + USB_KNOWNDEV_NOPROD, + "Saitek", + NULL, + }, + { + USB_VENDOR_ALCATELT, 0, + USB_KNOWNDEV_NOPROD, + "Alcatel Telecom", + NULL, + }, + { + USB_VENDOR_AGFA, 0, + USB_KNOWNDEV_NOPROD, + "AGFA-Gevaert", + NULL, + }, + { + USB_VENDOR_ASIAMD, 0, + USB_KNOWNDEV_NOPROD, + "Asia Microelectronic Development", + NULL, + }, + { + USB_VENDOR_BIZLINK, 0, + USB_KNOWNDEV_NOPROD, + "Bizlink International", + NULL, + }, + { + USB_VENDOR_KEYSPAN, 0, + USB_KNOWNDEV_NOPROD, + "Keyspan / InnoSys Inc.", + NULL, + }, + { + USB_VENDOR_AASHIMA, 0, + USB_KNOWNDEV_NOPROD, + "Aashima Technology", + NULL, + }, + { + USB_VENDOR_MULTITECH, 0, + USB_KNOWNDEV_NOPROD, + "MultiTech", + NULL, + }, + { + USB_VENDOR_ADS, 0, + USB_KNOWNDEV_NOPROD, + "ADS Technologies", + NULL, + }, + { + USB_VENDOR_ALCATELM, 0, + USB_KNOWNDEV_NOPROD, + "Alcatel Microelectronics", + NULL, + }, + { + USB_VENDOR_SIRIUS, 0, + USB_KNOWNDEV_NOPROD, + "Sirius Technologies", + NULL, + }, + { + USB_VENDOR_GUILLEMOT, 0, + USB_KNOWNDEV_NOPROD, + "Guillemot", + NULL, + }, + { + USB_VENDOR_BOSTON, 0, + USB_KNOWNDEV_NOPROD, + "Boston Acoustics", + NULL, + }, + { + USB_VENDOR_SMC, 0, + USB_KNOWNDEV_NOPROD, + "Standard Microsystems", + NULL, + }, + { + USB_VENDOR_PUTERCOM, 0, + USB_KNOWNDEV_NOPROD, + "Putercom", + NULL, + }, + { + USB_VENDOR_MCT, 0, + USB_KNOWNDEV_NOPROD, + "MCT", + NULL, + }, + { + USB_VENDOR_IMATION, 0, + USB_KNOWNDEV_NOPROD, + "Imation", + NULL, + }, + { + USB_VENDOR_SONYERICSSON, 0, + USB_KNOWNDEV_NOPROD, + "Sony Ericsson", + NULL, + }, + { + USB_VENDOR_EICON, 0, + USB_KNOWNDEV_NOPROD, + "Eicon Networks", + NULL, + }, + { + USB_VENDOR_SYNTECH, 0, + USB_KNOWNDEV_NOPROD, + "Syntech Information", + NULL, + }, + { + USB_VENDOR_DIGITALSTREAM, 0, + USB_KNOWNDEV_NOPROD, + "Digital Stream", + NULL, + }, + { + USB_VENDOR_AUREAL, 0, + USB_KNOWNDEV_NOPROD, + "Aureal Semiconductor", + NULL, + }, + { + USB_VENDOR_MIDIMAN, 0, + USB_KNOWNDEV_NOPROD, + "Midiman", + NULL, + }, + { + USB_VENDOR_CYBERPOWER, 0, + USB_KNOWNDEV_NOPROD, + "Cyber Power Systems, Inc.", + NULL, + }, + { + USB_VENDOR_SURECOM, 0, + USB_KNOWNDEV_NOPROD, + "Surecom Technology", + NULL, + }, + { + USB_VENDOR_LINKSYS2, 0, + USB_KNOWNDEV_NOPROD, + "Linksys", + NULL, + }, + { + USB_VENDOR_GRIFFIN, 0, + USB_KNOWNDEV_NOPROD, + "Griffin Technology", + NULL, + }, + { + USB_VENDOR_SANDISK, 0, + USB_KNOWNDEV_NOPROD, + "SanDisk", + NULL, + }, + { + USB_VENDOR_JENOPTIK, 0, + USB_KNOWNDEV_NOPROD, + "Jenoptik", + NULL, + }, + { + USB_VENDOR_LOGITEC, 0, + USB_KNOWNDEV_NOPROD, + "Logitec", + NULL, + }, + { + USB_VENDOR_BRIMAX, 0, + USB_KNOWNDEV_NOPROD, + "Brimax", + NULL, + }, + { + USB_VENDOR_AXIS, 0, + USB_KNOWNDEV_NOPROD, + "Axis Communications", + NULL, + }, + { + USB_VENDOR_ABL, 0, + USB_KNOWNDEV_NOPROD, + "ABL Electronics", + NULL, + }, + { + USB_VENDOR_SAGEM, 0, + USB_KNOWNDEV_NOPROD, + "Sagem", + NULL, + }, + { + USB_VENDOR_SUNCOMM, 0, + USB_KNOWNDEV_NOPROD, + "Sun Communications, Inc.", + NULL, + }, + { + USB_VENDOR_ALFADATA, 0, + USB_KNOWNDEV_NOPROD, + "Alfadata Computer", + NULL, + }, + { + USB_VENDOR_NATIONALTECH, 0, + USB_KNOWNDEV_NOPROD, + "National Technical Systems", + NULL, + }, + { + USB_VENDOR_ONNTO, 0, + USB_KNOWNDEV_NOPROD, + "Onnto", + NULL, + }, + { + USB_VENDOR_BE, 0, + USB_KNOWNDEV_NOPROD, + "Be", + NULL, + }, + { + USB_VENDOR_ADMTEK, 0, + USB_KNOWNDEV_NOPROD, + "ADMtek", + NULL, + }, + { + USB_VENDOR_COREGA, 0, + USB_KNOWNDEV_NOPROD, + "Corega", + NULL, + }, + { + USB_VENDOR_FREECOM, 0, + USB_KNOWNDEV_NOPROD, + "Freecom", + NULL, + }, + { + USB_VENDOR_MICROTECH, 0, + USB_KNOWNDEV_NOPROD, + "Microtech", + NULL, + }, + { + USB_VENDOR_GENERALINSTMNTS, 0, + USB_KNOWNDEV_NOPROD, + "General Instruments (Motorola)", + NULL, + }, + { + USB_VENDOR_OLYMPUS, 0, + USB_KNOWNDEV_NOPROD, + "Olympus", + NULL, + }, + { + USB_VENDOR_ABOCOM, 0, + USB_KNOWNDEV_NOPROD, + "AboCom Systems", + NULL, + }, + { + USB_VENDOR_KEISOKUGIKEN, 0, + USB_KNOWNDEV_NOPROD, + "Keisokugiken", + NULL, + }, + { + USB_VENDOR_ONSPEC, 0, + USB_KNOWNDEV_NOPROD, + "OnSpec", + NULL, + }, + { + USB_VENDOR_APG, 0, + USB_KNOWNDEV_NOPROD, + "APG Cash Drawer", + NULL, + }, + { + USB_VENDOR_BUG, 0, + USB_KNOWNDEV_NOPROD, + "B.U.G.", + NULL, + }, + { + USB_VENDOR_ALLIEDTELESYN, 0, + USB_KNOWNDEV_NOPROD, + "Allied Telesyn International", + NULL, + }, + { + USB_VENDOR_AVERMEDIA, 0, + USB_KNOWNDEV_NOPROD, + "AVerMedia Technologies", + NULL, + }, + { + USB_VENDOR_SIIG, 0, + USB_KNOWNDEV_NOPROD, + "SIIG", + NULL, + }, + { + USB_VENDOR_CASIO, 0, + USB_KNOWNDEV_NOPROD, + "CASIO", + NULL, + }, + { + USB_VENDOR_DLINK2, 0, + USB_KNOWNDEV_NOPROD, + "D-Link", + NULL, + }, + { + USB_VENDOR_APTIO, 0, + USB_KNOWNDEV_NOPROD, + "Aptio Products", + NULL, + }, + { + USB_VENDOR_ARASAN, 0, + USB_KNOWNDEV_NOPROD, + "Arasan Chip Systems", + NULL, + }, + { + USB_VENDOR_ALLIEDCABLE, 0, + USB_KNOWNDEV_NOPROD, + "Allied Cable", + NULL, + }, + { + USB_VENDOR_STSN, 0, + USB_KNOWNDEV_NOPROD, + "STSN", + NULL, + }, + { + USB_VENDOR_CENTURY, 0, + USB_KNOWNDEV_NOPROD, + "Century Corp", + NULL, + }, + { + USB_VENDOR_ZOOM, 0, + USB_KNOWNDEV_NOPROD, + "Zoom Telephonics", + NULL, + }, + { + USB_VENDOR_PCS, 0, + USB_KNOWNDEV_NOPROD, + "Personal Communication Systems", + NULL, + }, + { + USB_VENDOR_BROADLOGIC, 0, + USB_KNOWNDEV_NOPROD, + "BroadLogic", + NULL, + }, + { + USB_VENDOR_HANDSPRING, 0, + USB_KNOWNDEV_NOPROD, + "Handspring", + NULL, + }, + { + USB_VENDOR_PALM, 0, + USB_KNOWNDEV_NOPROD, + "Palm Computing", + NULL, + }, + { + USB_VENDOR_SOURCENEXT, 0, + USB_KNOWNDEV_NOPROD, + "SOURCENEXT", + NULL, + }, + { + USB_VENDOR_ACTIONSTAR, 0, + USB_KNOWNDEV_NOPROD, + "Action Star Enterprise", + NULL, + }, + { + USB_VENDOR_SAMSUNG_TECHWIN, 0, + USB_KNOWNDEV_NOPROD, + "Samsung Techwin", + NULL, + }, + { + USB_VENDOR_ACCTON, 0, + USB_KNOWNDEV_NOPROD, + "Accton Technology", + NULL, + }, + { + USB_VENDOR_DIAMOND, 0, + USB_KNOWNDEV_NOPROD, + "Diamond", + NULL, + }, + { + USB_VENDOR_NETGEAR, 0, + USB_KNOWNDEV_NOPROD, + "BayNETGEAR", + NULL, + }, + { + USB_VENDOR_TOPRE, 0, + USB_KNOWNDEV_NOPROD, + "Topre Corporation", + NULL, + }, + { + USB_VENDOR_ACTIVEWIRE, 0, + USB_KNOWNDEV_NOPROD, + "ActiveWire", + NULL, + }, + { + USB_VENDOR_BBELECTRONICS, 0, + USB_KNOWNDEV_NOPROD, + "B&B Electronics", + NULL, + }, + { + USB_VENDOR_PORTGEAR, 0, + USB_KNOWNDEV_NOPROD, + "PortGear", + NULL, + }, + { + USB_VENDOR_NETGEAR2, 0, + USB_KNOWNDEV_NOPROD, + "Netgear", + NULL, + }, + { + USB_VENDOR_SYSTEMTALKS, 0, + USB_KNOWNDEV_NOPROD, + "System Talks", + NULL, + }, + { + USB_VENDOR_METRICOM, 0, + USB_KNOWNDEV_NOPROD, + "Metricom", + NULL, + }, + { + USB_VENDOR_ADESSOKBTEK, 0, + USB_KNOWNDEV_NOPROD, + "ADESSO/Kbtek America", + NULL, + }, + { + USB_VENDOR_JATON, 0, + USB_KNOWNDEV_NOPROD, + "Jaton", + NULL, + }, + { + USB_VENDOR_APT, 0, + USB_KNOWNDEV_NOPROD, + "APT Technologies", + NULL, + }, + { + USB_VENDOR_BOCARESEARCH, 0, + USB_KNOWNDEV_NOPROD, + "Boca Research", + NULL, + }, + { + USB_VENDOR_ANDREA, 0, + USB_KNOWNDEV_NOPROD, + "Andrea Electronics", + NULL, + }, + { + USB_VENDOR_BURRBROWN, 0, + USB_KNOWNDEV_NOPROD, + "Burr-Brown Japan", + NULL, + }, + { + USB_VENDOR_2WIRE, 0, + USB_KNOWNDEV_NOPROD, + "2Wire", + NULL, + }, + { + USB_VENDOR_AIPTEK, 0, + USB_KNOWNDEV_NOPROD, + "AIPTEK International", + NULL, + }, + { + USB_VENDOR_SMARTBRIDGES, 0, + USB_KNOWNDEV_NOPROD, + "SmartBridges", + NULL, + }, + { + USB_VENDOR_BILLIONTON, 0, + USB_KNOWNDEV_NOPROD, + "Billionton Systems", + NULL, + }, + { + USB_VENDOR_EXTENDED, 0, + USB_KNOWNDEV_NOPROD, + "Extended Systems", + NULL, + }, + { + USB_VENDOR_MSYSTEMS, 0, + USB_KNOWNDEV_NOPROD, + "M-Systems", + NULL, + }, + { + USB_VENDOR_AUTHENTEC, 0, + USB_KNOWNDEV_NOPROD, + "AuthenTec", + NULL, + }, + { + USB_VENDOR_AUDIOTECHNICA, 0, + USB_KNOWNDEV_NOPROD, + "Audio-Technica", + NULL, + }, + { + USB_VENDOR_TRUMPION, 0, + USB_KNOWNDEV_NOPROD, + "Trumpion Microelectronics", + NULL, + }, + { + USB_VENDOR_FEIYA, 0, + USB_KNOWNDEV_NOPROD, + "Feiya", + NULL, + }, + { + USB_VENDOR_ALATION, 0, + USB_KNOWNDEV_NOPROD, + "Alation Systems", + NULL, + }, + { + USB_VENDOR_GLOBESPAN, 0, + USB_KNOWNDEV_NOPROD, + "Globespan", + NULL, + }, + { + USB_VENDOR_CONCORDCAMERA, 0, + USB_KNOWNDEV_NOPROD, + "Concord Camera", + NULL, + }, + { + USB_VENDOR_GARMIN, 0, + USB_KNOWNDEV_NOPROD, + "Garmin International", + NULL, + }, + { + USB_VENDOR_GOHUBS, 0, + USB_KNOWNDEV_NOPROD, + "GoHubs", + NULL, + }, + { + USB_VENDOR_XEROX, 0, + USB_KNOWNDEV_NOPROD, + "Xerox", + NULL, + }, + { + USB_VENDOR_BIOMETRIC, 0, + USB_KNOWNDEV_NOPROD, + "American Biometric Company", + NULL, + }, + { + USB_VENDOR_TOSHIBA, 0, + USB_KNOWNDEV_NOPROD, + "Toshiba", + NULL, + }, + { + USB_VENDOR_PLEXTOR, 0, + USB_KNOWNDEV_NOPROD, + "Plextor", + NULL, + }, + { + USB_VENDOR_INTREPIDCS, 0, + USB_KNOWNDEV_NOPROD, + "Intrepid", + NULL, + }, + { + USB_VENDOR_YANO, 0, + USB_KNOWNDEV_NOPROD, + "Yano", + NULL, + }, + { + USB_VENDOR_KINGSTON, 0, + USB_KNOWNDEV_NOPROD, + "Kingston Technology", + NULL, + }, + { + USB_VENDOR_BLUEWATER, 0, + USB_KNOWNDEV_NOPROD, + "BlueWater Systems", + NULL, + }, + { + USB_VENDOR_AGILENT, 0, + USB_KNOWNDEV_NOPROD, + "Agilent Technologies", + NULL, + }, + { + USB_VENDOR_GUDE, 0, + USB_KNOWNDEV_NOPROD, + "Gude ADS", + NULL, + }, + { + USB_VENDOR_PORTSMITH, 0, + USB_KNOWNDEV_NOPROD, + "Portsmith", + NULL, + }, + { + USB_VENDOR_ACERW, 0, + USB_KNOWNDEV_NOPROD, + "Acer", + NULL, + }, + { + USB_VENDOR_ADIRONDACK, 0, + USB_KNOWNDEV_NOPROD, + "Adirondack Wire & Cable", + NULL, + }, + { + USB_VENDOR_BECKHOFF, 0, + USB_KNOWNDEV_NOPROD, + "Beckhoff", + NULL, + }, + { + USB_VENDOR_MINDSATWORK, 0, + USB_KNOWNDEV_NOPROD, + "Minds At Work", + NULL, + }, + { + USB_VENDOR_POINTCHIPS, 0, + USB_KNOWNDEV_NOPROD, + "PointChips", + NULL, + }, + { + USB_VENDOR_INTERSIL, 0, + USB_KNOWNDEV_NOPROD, + "Intersil", + NULL, + }, + { + USB_VENDOR_ALTIUS, 0, + USB_KNOWNDEV_NOPROD, + "Altius Solutions", + NULL, + }, + { + USB_VENDOR_ARRIS, 0, + USB_KNOWNDEV_NOPROD, + "Arris Interactive", + NULL, + }, + { + USB_VENDOR_ACTIVCARD, 0, + USB_KNOWNDEV_NOPROD, + "ACTIVCARD", + NULL, + }, + { + USB_VENDOR_ACTISYS, 0, + USB_KNOWNDEV_NOPROD, + "ACTiSYS", + NULL, + }, + { + USB_VENDOR_NOVATEL2, 0, + USB_KNOWNDEV_NOPROD, + "Novatel Wireless", + NULL, + }, + { + USB_VENDOR_AFOURTECH, 0, + USB_KNOWNDEV_NOPROD, + "A-FOUR TECH", + NULL, + }, + { + USB_VENDOR_AIMEX, 0, + USB_KNOWNDEV_NOPROD, + "AIMEX", + NULL, + }, + { + USB_VENDOR_ADDONICS, 0, + USB_KNOWNDEV_NOPROD, + "Addonics Technologies", + NULL, + }, + { + USB_VENDOR_AKAI, 0, + USB_KNOWNDEV_NOPROD, + "AKAI professional M.I.", + NULL, + }, + { + USB_VENDOR_ARESCOM, 0, + USB_KNOWNDEV_NOPROD, + "ARESCOM", + NULL, + }, + { + USB_VENDOR_BAY, 0, + USB_KNOWNDEV_NOPROD, + "Bay Associates", + NULL, + }, + { + USB_VENDOR_ALTERA, 0, + USB_KNOWNDEV_NOPROD, + "Altera", + NULL, + }, + { + USB_VENDOR_CSR, 0, + USB_KNOWNDEV_NOPROD, + "Cambridge Silicon Radio", + NULL, + }, + { + USB_VENDOR_TREK, 0, + USB_KNOWNDEV_NOPROD, + "Trek Technology", + NULL, + }, + { + USB_VENDOR_ASAHIOPTICAL, 0, + USB_KNOWNDEV_NOPROD, + "Asahi Optical", + NULL, + }, + { + USB_VENDOR_BOCASYSTEMS, 0, + USB_KNOWNDEV_NOPROD, + "Boca Systems", + NULL, + }, + { + USB_VENDOR_SHANTOU, 0, + USB_KNOWNDEV_NOPROD, + "ShanTou", + NULL, + }, + { + USB_VENDOR_MEDIAGEAR, 0, + USB_KNOWNDEV_NOPROD, + "MediaGear", + NULL, + }, + { + USB_VENDOR_BROADCOM, 0, + USB_KNOWNDEV_NOPROD, + "Broadcom", + NULL, + }, + { + USB_VENDOR_GREENHOUSE, 0, + USB_KNOWNDEV_NOPROD, + "GREENHOUSE", + NULL, + }, + { + USB_VENDOR_GEOCAST, 0, + USB_KNOWNDEV_NOPROD, + "Geocast Network Systems", + NULL, + }, + { + USB_VENDOR_IDQUANTIQUE, 0, + USB_KNOWNDEV_NOPROD, + "id Quantique", + NULL, + }, + { + USB_VENDOR_ZYDAS, 0, + USB_KNOWNDEV_NOPROD, + "Zydas Technology Corporation", + NULL, + }, + { + USB_VENDOR_NEODIO, 0, + USB_KNOWNDEV_NOPROD, + "Neodio", + NULL, + }, + { + USB_VENDOR_OPTION, 0, + USB_KNOWNDEV_NOPROD, + "Option N.V:", + NULL, + }, + { + USB_VENDOR_ASUS, 0, + USB_KNOWNDEV_NOPROD, + "ASUSTeK Computer", + NULL, + }, + { + USB_VENDOR_TODOS, 0, + USB_KNOWNDEV_NOPROD, + "Todos Data System", + NULL, + }, + { + USB_VENDOR_SIIG2, 0, + USB_KNOWNDEV_NOPROD, + "SIIG", + NULL, + }, + { + USB_VENDOR_TEKRAM, 0, + USB_KNOWNDEV_NOPROD, + "Tekram Technology", + NULL, + }, + { + USB_VENDOR_HAL, 0, + USB_KNOWNDEV_NOPROD, + "HAL Corporation", + NULL, + }, + { + USB_VENDOR_EMS, 0, + USB_KNOWNDEV_NOPROD, + "EMS Production", + NULL, + }, + { + USB_VENDOR_NEC2, 0, + USB_KNOWNDEV_NOPROD, + "NEC", + NULL, + }, + { + USB_VENDOR_ATI2, 0, + USB_KNOWNDEV_NOPROD, + "ATI", + NULL, + }, + { + USB_VENDOR_ZEEVO, 0, + USB_KNOWNDEV_NOPROD, + "Zeevo, Inc.", + NULL, + }, + { + USB_VENDOR_KURUSUGAWA, 0, + USB_KNOWNDEV_NOPROD, + "Kurusugawa Electronics, Inc.", + NULL, + }, + { + USB_VENDOR_ASIX, 0, + USB_KNOWNDEV_NOPROD, + "ASIX Electronics", + NULL, + }, + { + USB_VENDOR_O2MICRO, 0, + USB_KNOWNDEV_NOPROD, + "O2 Micro, Inc.", + NULL, + }, + { + USB_VENDOR_USR, 0, + USB_KNOWNDEV_NOPROD, + "U.S. Robotics", + NULL, + }, + { + USB_VENDOR_AMBIT, 0, + USB_KNOWNDEV_NOPROD, + "Ambit Microsystems", + NULL, + }, + { + USB_VENDOR_HTC, 0, + USB_KNOWNDEV_NOPROD, + "HTC", + NULL, + }, + { + USB_VENDOR_REALTEK, 0, + USB_KNOWNDEV_NOPROD, + "Realtek", + NULL, + }, + { + USB_VENDOR_ADDONICS2, 0, + USB_KNOWNDEV_NOPROD, + "Addonics Technology", + NULL, + }, + { + USB_VENDOR_FSC, 0, + USB_KNOWNDEV_NOPROD, + "Fujitsu Siemens Computers", + NULL, + }, + { + USB_VENDOR_AGATE, 0, + USB_KNOWNDEV_NOPROD, + "Agate Technologies", + NULL, + }, + { + USB_VENDOR_DMI, 0, + USB_KNOWNDEV_NOPROD, + "DMI", + NULL, + }, + { + USB_VENDOR_MICRODIA, 0, + USB_KNOWNDEV_NOPROD, + "Chicony", + NULL, + }, + { + USB_VENDOR_SEALEVEL, 0, + USB_KNOWNDEV_NOPROD, + "Sealevel System", + NULL, + }, + { + USB_VENDOR_LUWEN, 0, + USB_KNOWNDEV_NOPROD, + "Luwen", + NULL, + }, + { + USB_VENDOR_KYOCERA2, 0, + USB_KNOWNDEV_NOPROD, + "Kyocera Wireless Corp.", + NULL, + }, + { + USB_VENDOR_ZCOM, 0, + USB_KNOWNDEV_NOPROD, + "Z-Com", + NULL, + }, + { + USB_VENDOR_ATHEROS2, 0, + USB_KNOWNDEV_NOPROD, + "Atheros Communications", + NULL, + }, + { + USB_VENDOR_TANGTOP, 0, + USB_KNOWNDEV_NOPROD, + "Tangtop", + NULL, + }, + { + USB_VENDOR_SMC3, 0, + USB_KNOWNDEV_NOPROD, + "Standard Microsystems", + NULL, + }, + { + USB_VENDOR_ADDON, 0, + USB_KNOWNDEV_NOPROD, + "Add-on Technology", + NULL, + }, + { + USB_VENDOR_ACDC, 0, + USB_KNOWNDEV_NOPROD, + "American Computer & Digital Components", + NULL, + }, + { + USB_VENDOR_ABC, 0, + USB_KNOWNDEV_NOPROD, + "ABC", + NULL, + }, + { + USB_VENDOR_CONCEPTRONIC, 0, + USB_KNOWNDEV_NOPROD, + "Conceptronic", + NULL, + }, + { + USB_VENDOR_SKANHEX, 0, + USB_KNOWNDEV_NOPROD, + "Skanhex Technology, Inc.", + NULL, + }, + { + USB_VENDOR_MSI, 0, + USB_KNOWNDEV_NOPROD, + "Micro Star International", + NULL, + }, + { + USB_VENDOR_ELCON, 0, + USB_KNOWNDEV_NOPROD, + "ELCON Systemtechnik", + NULL, + }, + { + USB_VENDOR_NETAC, 0, + USB_KNOWNDEV_NOPROD, + "Netac", + NULL, + }, + { + USB_VENDOR_SITECOMEU, 0, + USB_KNOWNDEV_NOPROD, + "Sitecom Europe", + NULL, + }, + { + USB_VENDOR_MOBILEACTION, 0, + USB_KNOWNDEV_NOPROD, + "Mobile Action", + NULL, + }, + { + USB_VENDOR_SPEEDDRAGON, 0, + USB_KNOWNDEV_NOPROD, + "Speed Dragon Multimedia", + NULL, + }, + { + USB_VENDOR_HAWKING, 0, + USB_KNOWNDEV_NOPROD, + "Hawking", + NULL, + }, + { + USB_VENDOR_FOSSIL, 0, + USB_KNOWNDEV_NOPROD, + "Fossil, Inc", + NULL, + }, + { + USB_VENDOR_GMATE, 0, + USB_KNOWNDEV_NOPROD, + "G.Mate, Inc", + NULL, + }, + { + USB_VENDOR_OTI, 0, + USB_KNOWNDEV_NOPROD, + "Ours Technology", + NULL, + }, + { + USB_VENDOR_PILOTECH, 0, + USB_KNOWNDEV_NOPROD, + "Pilotech", + NULL, + }, + { + USB_VENDOR_NOVATECH, 0, + USB_KNOWNDEV_NOPROD, + "NovaTech", + NULL, + }, + { + USB_VENDOR_ITEGNO, 0, + USB_KNOWNDEV_NOPROD, + "iTegno", + NULL, + }, + { + USB_VENDOR_WINMAXGROUP, 0, + USB_KNOWNDEV_NOPROD, + "WinMaxGroup", + NULL, + }, + { + USB_VENDOR_TOD, 0, + USB_KNOWNDEV_NOPROD, + "TOD", + NULL, + }, + { + USB_VENDOR_EGALAX, 0, + USB_KNOWNDEV_NOPROD, + "eGalax, Inc.", + NULL, + }, + { + USB_VENDOR_AIRPRIME, 0, + USB_KNOWNDEV_NOPROD, + "AirPrime, Inc.", + NULL, + }, + { + USB_VENDOR_MICROTUNE, 0, + USB_KNOWNDEV_NOPROD, + "Microtune", + NULL, + }, + { + USB_VENDOR_VTECH, 0, + USB_KNOWNDEV_NOPROD, + "VTech", + NULL, + }, + { + USB_VENDOR_FALCOM, 0, + USB_KNOWNDEV_NOPROD, + "Falcom Wireless Communications GmbH", + NULL, + }, + { + USB_VENDOR_RIM, 0, + USB_KNOWNDEV_NOPROD, + "Research In Motion", + NULL, + }, + { + USB_VENDOR_DYNASTREAM, 0, + USB_KNOWNDEV_NOPROD, + "Dynastream Innovations", + NULL, + }, + { + USB_VENDOR_QUALCOMM, 0, + USB_KNOWNDEV_NOPROD, + "Qualcomm", + NULL, + }, + { + USB_VENDOR_DESKNOTE, 0, + USB_KNOWNDEV_NOPROD, + "Desknote", + NULL, + }, + { + USB_VENDOR_GIGABYTE, 0, + USB_KNOWNDEV_NOPROD, + "GIGABYTE", + NULL, + }, + { + USB_VENDOR_WESTERN, 0, + USB_KNOWNDEV_NOPROD, + "Western Digital", + NULL, + }, + { + USB_VENDOR_MOTOROLA, 0, + USB_KNOWNDEV_NOPROD, + "Motorola", + NULL, + }, + { + USB_VENDOR_CCYU, 0, + USB_KNOWNDEV_NOPROD, + "CCYU Technology", + NULL, + }, + { + USB_VENDOR_CURITEL, 0, + USB_KNOWNDEV_NOPROD, + "Curitel Communications Inc", + NULL, + }, + { + USB_VENDOR_SILABS2, 0, + USB_KNOWNDEV_NOPROD, + "SILABS2", + NULL, + }, + { + USB_VENDOR_USI, 0, + USB_KNOWNDEV_NOPROD, + "USI", + NULL, + }, + { + USB_VENDOR_PLX, 0, + USB_KNOWNDEV_NOPROD, + "PLX", + NULL, + }, + { + USB_VENDOR_ASANTE, 0, + USB_KNOWNDEV_NOPROD, + "Asante", + NULL, + }, + { + USB_VENDOR_SILABS, 0, + USB_KNOWNDEV_NOPROD, + "Silicon Labs", + NULL, + }, + { + USB_VENDOR_ANALOG, 0, + USB_KNOWNDEV_NOPROD, + "Analog Devices", + NULL, + }, + { + USB_VENDOR_TENX, 0, + USB_KNOWNDEV_NOPROD, + "Ten X Technology, Inc.", + NULL, + }, + { + USB_VENDOR_ISSC, 0, + USB_KNOWNDEV_NOPROD, + "Integrated System Solution Corp.", + NULL, + }, + { + USB_VENDOR_JRC, 0, + USB_KNOWNDEV_NOPROD, + "Japan Radio Company", + NULL, + }, + { + USB_VENDOR_SPHAIRON, 0, + USB_KNOWNDEV_NOPROD, + "Sphairon Access Systems GmbH", + NULL, + }, + { + USB_VENDOR_DELORME, 0, + USB_KNOWNDEV_NOPROD, + "DeLorme", + NULL, + }, + { + USB_VENDOR_SERVERWORKS, 0, + USB_KNOWNDEV_NOPROD, + "ServerWorks", + NULL, + }, + { + USB_VENDOR_ACERCM, 0, + USB_KNOWNDEV_NOPROD, + "Acer Communications & Multimedia", + NULL, + }, + { + USB_VENDOR_SIERRA, 0, + USB_KNOWNDEV_NOPROD, + "Sierra Wireless", + NULL, + }, + { + USB_VENDOR_TOPFIELD, 0, + USB_KNOWNDEV_NOPROD, + "Topfield Co., Ltd", + NULL, + }, + { + USB_VENDOR_SIEMENS3, 0, + USB_KNOWNDEV_NOPROD, + "Siemens", + NULL, + }, + { + USB_VENDOR_PROLIFIC2, 0, + USB_KNOWNDEV_NOPROD, + "Prolific", + NULL, + }, + { + USB_VENDOR_ALCATEL, 0, + USB_KNOWNDEV_NOPROD, + "Alcatel", + NULL, + }, + { + USB_VENDOR_UNKNOWN3, 0, + USB_KNOWNDEV_NOPROD, + "Unknown vendor", + NULL, + }, + { + USB_VENDOR_TSUNAMI, 0, + USB_KNOWNDEV_NOPROD, + "Tsunami", + NULL, + }, + { + USB_VENDOR_PHEENET, 0, + USB_KNOWNDEV_NOPROD, + "Pheenet", + NULL, + }, + { + USB_VENDOR_TARGUS, 0, + USB_KNOWNDEV_NOPROD, + "Targus", + NULL, + }, + { + USB_VENDOR_TWINMOS, 0, + USB_KNOWNDEV_NOPROD, + "TwinMOS", + NULL, + }, + { + USB_VENDOR_TENDA, 0, + USB_KNOWNDEV_NOPROD, + "Tenda", + NULL, + }, + { + USB_VENDOR_CREATIVE2, 0, + USB_KNOWNDEV_NOPROD, + "Creative Labs", + NULL, + }, + { + USB_VENDOR_BELKIN2, 0, + USB_KNOWNDEV_NOPROD, + "Belkin Components", + NULL, + }, + { + USB_VENDOR_CYBERTAN, 0, + USB_KNOWNDEV_NOPROD, + "CyberTAN Technology", + NULL, + }, + { + USB_VENDOR_HUAWEI, 0, + USB_KNOWNDEV_NOPROD, + "Huawei Technologies", + NULL, + }, + { + USB_VENDOR_ARANEUS, 0, + USB_KNOWNDEV_NOPROD, + "Araneus Information Systems", + NULL, + }, + { + USB_VENDOR_TAPWAVE, 0, + USB_KNOWNDEV_NOPROD, + "Tapwave", + NULL, + }, + { + USB_VENDOR_AINCOMM, 0, + USB_KNOWNDEV_NOPROD, + "Aincomm", + NULL, + }, + { + USB_VENDOR_MOBILITY, 0, + USB_KNOWNDEV_NOPROD, + "Mobility", + NULL, + }, + { + USB_VENDOR_DICKSMITH, 0, + USB_KNOWNDEV_NOPROD, + "Dick Smith Electronics", + NULL, + }, + { + USB_VENDOR_NETGEAR3, 0, + USB_KNOWNDEV_NOPROD, + "Netgear", + NULL, + }, + { + USB_VENDOR_BALTECH, 0, + USB_KNOWNDEV_NOPROD, + "Baltech", + NULL, + }, + { + USB_VENDOR_CISCOLINKSYS, 0, + USB_KNOWNDEV_NOPROD, + "Cisco-Linksys", + NULL, + }, + { + USB_VENDOR_SHARK, 0, + USB_KNOWNDEV_NOPROD, + "Shark", + NULL, + }, + { + USB_VENDOR_NOVATEL, 0, + USB_KNOWNDEV_NOPROD, + "Novatel Wireless", + NULL, + }, + { + USB_VENDOR_MERLIN, 0, + USB_KNOWNDEV_NOPROD, + "Merlin", + NULL, + }, + { + USB_VENDOR_WISTRONNEWEB, 0, + USB_KNOWNDEV_NOPROD, + "Wistron NeWeb", + NULL, + }, + { + USB_VENDOR_RADIOSHACK, 0, + USB_KNOWNDEV_NOPROD, + "Radio Shack", + NULL, + }, + { + USB_VENDOR_HUAWEI3COM, 0, + USB_KNOWNDEV_NOPROD, + "Huawei-3Com", + NULL, + }, + { + USB_VENDOR_SILICOM, 0, + USB_KNOWNDEV_NOPROD, + "Silicom", + NULL, + }, + { + USB_VENDOR_RALINK, 0, + USB_KNOWNDEV_NOPROD, + "Ralink Technology", + NULL, + }, + { + USB_VENDOR_IMAGINATION, 0, + USB_KNOWNDEV_NOPROD, + "Imagination Technologies", + NULL, + }, + { + USB_VENDOR_CONCEPTRONIC2, 0, + USB_KNOWNDEV_NOPROD, + "Conceptronic", + NULL, + }, + { + USB_VENDOR_PLANEX3, 0, + USB_KNOWNDEV_NOPROD, + "Planex Communications", + NULL, + }, + { + USB_VENDOR_SILICONPORTALS, 0, + USB_KNOWNDEV_NOPROD, + "Silicon Portals", + NULL, + }, + { + USB_VENDOR_UBIQUAM, 0, + USB_KNOWNDEV_NOPROD, + "UBIQUAM Co., Ltd.", + NULL, + }, + { + USB_VENDOR_UBLOX, 0, + USB_KNOWNDEV_NOPROD, + "U-blox", + NULL, + }, + { + USB_VENDOR_PNY, 0, + USB_KNOWNDEV_NOPROD, + "PNY", + NULL, + }, + { + USB_VENDOR_OQO, 0, + USB_KNOWNDEV_NOPROD, + "OQO", + NULL, + }, + { + USB_VENDOR_UMEDIA, 0, + USB_KNOWNDEV_NOPROD, + "U-MEDIA Communications", + NULL, + }, + { + USB_VENDOR_FIBERLINE, 0, + USB_KNOWNDEV_NOPROD, + "Fiberline", + NULL, + }, + { + USB_VENDOR_SPARKLAN, 0, + USB_KNOWNDEV_NOPROD, + "SparkLAN", + NULL, + }, + { + USB_VENDOR_SOHOWARE, 0, + USB_KNOWNDEV_NOPROD, + "SOHOware", + NULL, + }, + { + USB_VENDOR_UMAX, 0, + USB_KNOWNDEV_NOPROD, + "UMAX Data Systems", + NULL, + }, + { + USB_VENDOR_INSIDEOUT, 0, + USB_KNOWNDEV_NOPROD, + "Inside Out Networks", + NULL, + }, + { + USB_VENDOR_GOODWAY, 0, + USB_KNOWNDEV_NOPROD, + "Good Way Technology", + NULL, + }, + { + USB_VENDOR_ENTREGA, 0, + USB_KNOWNDEV_NOPROD, + "Entrega", + NULL, + }, + { + USB_VENDOR_ACTIONTEC, 0, + USB_KNOWNDEV_NOPROD, + "Actiontec Electronics", + NULL, + }, + { + USB_VENDOR_ATHEROS, 0, + USB_KNOWNDEV_NOPROD, + "Atheros Communications", + NULL, + }, + { + USB_VENDOR_GIGASET, 0, + USB_KNOWNDEV_NOPROD, + "Gigaset", + NULL, + }, + { + USB_VENDOR_GLOBALSUN, 0, + USB_KNOWNDEV_NOPROD, + "Global Sun Technology", + NULL, + }, + { + USB_VENDOR_ANYDATA, 0, + USB_KNOWNDEV_NOPROD, + "AnyDATA Corporation", + NULL, + }, + { + USB_VENDOR_JABLOTRON, 0, + USB_KNOWNDEV_NOPROD, + "Jablotron", + NULL, + }, + { + USB_VENDOR_CMOTECH, 0, + USB_KNOWNDEV_NOPROD, + "CMOTECH Co., Ltd.", + NULL, + }, + { + USB_VENDOR_AXESSTEL, 0, + USB_KNOWNDEV_NOPROD, + "Axesstel Co., Ltd.", + NULL, + }, + { + USB_VENDOR_LINKSYS4, 0, + USB_KNOWNDEV_NOPROD, + "Linksys", + NULL, + }, + { + USB_VENDOR_SENAO, 0, + USB_KNOWNDEV_NOPROD, + "Senao", + NULL, + }, + { + USB_VENDOR_METAGEEK, 0, + USB_KNOWNDEV_NOPROD, + "MetaGeek", + NULL, + }, + { + USB_VENDOR_AMIT, 0, + USB_KNOWNDEV_NOPROD, + "AMIT", + NULL, + }, + { + USB_VENDOR_QCOM, 0, + USB_KNOWNDEV_NOPROD, + "Qcom", + NULL, + }, + { + USB_VENDOR_LINKSYS3, 0, + USB_KNOWNDEV_NOPROD, + "Linksys", + NULL, + }, + { + USB_VENDOR_QUALCOMMINC, 0, + USB_KNOWNDEV_NOPROD, + "Qualcomm, Incorporated", + NULL, + }, + { + USB_VENDOR_DLINK, 0, + USB_KNOWNDEV_NOPROD, + "D-Link", + NULL, + }, + { + USB_VENDOR_PLANEX2, 0, + USB_KNOWNDEV_NOPROD, + "Planex Communications", + NULL, + }, + { + USB_VENDOR_ERICSSON, 0, + USB_KNOWNDEV_NOPROD, + "Ericsson", + NULL, + }, + { + USB_VENDOR_MOTOROLA2, 0, + USB_KNOWNDEV_NOPROD, + "Motorola", + NULL, + }, + { + USB_VENDOR_TRIPPLITE, 0, + USB_KNOWNDEV_NOPROD, + "Tripp-Lite", + NULL, + }, + { + USB_VENDOR_HIROSE, 0, + USB_KNOWNDEV_NOPROD, + "Hirose Electric", + NULL, + }, + { + USB_VENDOR_NHJ, 0, + USB_KNOWNDEV_NOPROD, + "NHJ", + NULL, + }, + { + USB_VENDOR_PLANEX, 0, + USB_KNOWNDEV_NOPROD, + "Planex Communications", + NULL, + }, + { + USB_VENDOR_VIDZMEDIA, 0, + USB_KNOWNDEV_NOPROD, + "VidzMedia Pte Ltd", + NULL, + }, + { + USB_VENDOR_AEI, 0, + USB_KNOWNDEV_NOPROD, + "AEI", + NULL, + }, + { + USB_VENDOR_HANK, 0, + USB_KNOWNDEV_NOPROD, + "Hank Connection", + NULL, + }, + { + USB_VENDOR_PQI, 0, + USB_KNOWNDEV_NOPROD, + "PQI", + NULL, + }, + { + USB_VENDOR_DAISY, 0, + USB_KNOWNDEV_NOPROD, + "Daisy Technology", + NULL, + }, + { + USB_VENDOR_NI, 0, + USB_KNOWNDEV_NOPROD, + "National Instruments", + NULL, + }, + { + USB_VENDOR_MICRONET, 0, + USB_KNOWNDEV_NOPROD, + "Micronet Communications", + NULL, + }, + { + USB_VENDOR_IODATA2, 0, + USB_KNOWNDEV_NOPROD, + "I-O Data", + NULL, + }, + { + USB_VENDOR_IRIVER, 0, + USB_KNOWNDEV_NOPROD, + "iRiver", + NULL, + }, + { + USB_VENDOR_DELL, 0, + USB_KNOWNDEV_NOPROD, + "Dell", + NULL, + }, + { + USB_VENDOR_WCH, 0, + USB_KNOWNDEV_NOPROD, + "QinHeng Electronics", + NULL, + }, + { + USB_VENDOR_ACEECA, 0, + USB_KNOWNDEV_NOPROD, + "Aceeca", + NULL, + }, + { + USB_VENDOR_AVERATEC, 0, + USB_KNOWNDEV_NOPROD, + "Averatec", + NULL, + }, + { + USB_VENDOR_SWEEX, 0, + USB_KNOWNDEV_NOPROD, + "Sweex", + NULL, + }, + { + USB_VENDOR_ONSPEC2, 0, + USB_KNOWNDEV_NOPROD, + "OnSpec Electronic Inc.", + NULL, + }, + { + USB_VENDOR_ZINWELL, 0, + USB_KNOWNDEV_NOPROD, + "Zinwell", + NULL, + }, + { + USB_VENDOR_SITECOM, 0, + USB_KNOWNDEV_NOPROD, + "Sitecom", + NULL, + }, + { + USB_VENDOR_ARKMICRO, 0, + USB_KNOWNDEV_NOPROD, + "Arkmicro Technologies Inc.", + NULL, + }, + { + USB_VENDOR_3COM2, 0, + USB_KNOWNDEV_NOPROD, + "3Com", + NULL, + }, + { + USB_VENDOR_INTEL, 0, + USB_KNOWNDEV_NOPROD, + "Intel", + NULL, + }, + { + USB_VENDOR_SITECOM2, 0, + USB_KNOWNDEV_NOPROD, + "Sitecom", + NULL, + }, + { + USB_VENDOR_MOSCHIP, 0, + USB_KNOWNDEV_NOPROD, + "MosChip Semiconductor", + NULL, + }, + { + USB_VENDOR_3COM3, 0, + USB_KNOWNDEV_NOPROD, + "3Com", + NULL, + }, + { + USB_VENDOR_HP2, 0, + USB_KNOWNDEV_NOPROD, + "Hewlett Packard", + NULL, + }, + { + USB_VENDOR_USRP, 0, + USB_KNOWNDEV_NOPROD, + "GNU Radio USRP", + NULL, + }, + { 0, 0, 0, NULL, NULL, } +}; diff --git a/sys/dev/usb2/include/usb2_endian.h b/sys/dev/usb2/include/usb2_endian.h new file mode 100644 index 000000000000..2f4900870b70 --- /dev/null +++ b/sys/dev/usb2/include/usb2_endian.h @@ -0,0 +1,119 @@ +/* $FreeBSD$ */ +/* + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_ENDIAN_H_ +#define _USB2_ENDIAN_H_ + +#include +#include + +/* + * Declare the basic USB record types. USB records have an alignment + * of 1 byte and are always packed. + */ +typedef uint8_t uByte; +typedef uint8_t uWord[2]; +typedef uint8_t uDWord[4]; +typedef uint8_t uQWord[8]; + +/* + * Define a set of macros that can get and set data independent of + * CPU endianness and CPU alignment requirements: + */ +#define UGETB(w) \ + ((w)[0]) + +#define UGETW(w) \ + ((w)[0] | \ + ((w)[1] << 8)) + +#define UGETDW(w) \ + ((w)[0] | \ + ((w)[1] << 8) | \ + ((w)[2] << 16) | \ + ((w)[3] << 24)) + +#define UGETQW(w) \ + ((w)[0] | \ + ((w)[1] << 8) | \ + ((w)[2] << 16) | \ + ((w)[3] << 24) | \ + (((uint64_t)((w)[4])) << 32) | \ + (((uint64_t)((w)[5])) << 40) | \ + (((uint64_t)((w)[6])) << 48) | \ + (((uint64_t)((w)[7])) << 56)) + +#define USETB(w,v) do { \ + (w)[0] = (uint8_t)(v); \ +} while (0) + +#define USETW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ +} while (0) + +#define USETDW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ + (w)[2] = (uint8_t)((v) >> 16); \ + (w)[3] = (uint8_t)((v) >> 24); \ +} while (0) + +#define USETQW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ + (w)[2] = (uint8_t)((v) >> 16); \ + (w)[3] = (uint8_t)((v) >> 24); \ + (w)[4] = (uint8_t)((v) >> 32); \ + (w)[5] = (uint8_t)((v) >> 40); \ + (w)[6] = (uint8_t)((v) >> 48); \ + (w)[7] = (uint8_t)((v) >> 56); \ +} while (0) + +#define USETW2(w,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ +} while (0) + +#define USETW4(w,b3,b2,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ + (w)[2] = (uint8_t)(b2); \ + (w)[3] = (uint8_t)(b3); \ +} while (0) + +#define USETW8(w,b7,b6,b5,b4,b3,b2,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ + (w)[2] = (uint8_t)(b2); \ + (w)[3] = (uint8_t)(b3); \ + (w)[4] = (uint8_t)(b4); \ + (w)[5] = (uint8_t)(b5); \ + (w)[6] = (uint8_t)(b6); \ + (w)[7] = (uint8_t)(b7); \ +} while (0) + +#endif /* _USB2_ENDIAN_H_ */ diff --git a/sys/dev/usb2/include/usb2_error.h b/sys/dev/usb2/include/usb2_error.h new file mode 100644 index 000000000000..86c62c2b56a7 --- /dev/null +++ b/sys/dev/usb2/include/usb2_error.h @@ -0,0 +1,68 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_ERROR_H_ +#define _USB2_ERROR_H_ + +/* + * The "USB_STATUS" macro defines all the USB error codes. + * NOTE: "USB_ERR_NORMAL_COMPLETION" is not an error code. + * NOTE: "USB_ERR_STARTING" is not an error code. + */ +#define USB_ERR(m,n)\ +m(n, USB_ERR_NORMAL_COMPLETION)\ +m(n, USB_ERR_PENDING_REQUESTS)\ +m(n, USB_ERR_NOT_STARTED)\ +m(n, USB_ERR_INVAL)\ +m(n, USB_ERR_NOMEM)\ +m(n, USB_ERR_CANCELLED)\ +m(n, USB_ERR_BAD_ADDRESS)\ +m(n, USB_ERR_BAD_BUFSIZE)\ +m(n, USB_ERR_BAD_FLAG)\ +m(n, USB_ERR_NO_CALLBACK)\ +m(n, USB_ERR_IN_USE)\ +m(n, USB_ERR_NO_ADDR)\ +m(n, USB_ERR_NO_PIPE)\ +m(n, USB_ERR_ZERO_NFRAMES)\ +m(n, USB_ERR_ZERO_MAXP)\ +m(n, USB_ERR_SET_ADDR_FAILED)\ +m(n, USB_ERR_NO_POWER)\ +m(n, USB_ERR_TOO_DEEP)\ +m(n, USB_ERR_IOERROR)\ +m(n, USB_ERR_NOT_CONFIGURED)\ +m(n, USB_ERR_TIMEOUT)\ +m(n, USB_ERR_SHORT_XFER)\ +m(n, USB_ERR_STALLED)\ +m(n, USB_ERR_INTERRUPTED)\ +m(n, USB_ERR_DMA_LOAD_FAILED)\ +m(n, USB_ERR_BAD_CONTEXT)\ +m(n, USB_ERR_NO_ROOT_HUB)\ +m(n, USB_ERR_NO_INTR_THREAD)\ +m(n, USB_ERR_NOT_LOCKED)\ + +USB_MAKE_ENUM(USB_ERR); + +#endif /* _USB2_ERROR_H_ */ diff --git a/sys/dev/usb2/include/usb2_hid.h b/sys/dev/usb2/include/usb2_hid.h new file mode 100644 index 000000000000..ee07a0386c1b --- /dev/null +++ b/sys/dev/usb2/include/usb2_hid.h @@ -0,0 +1,173 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. 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. + */ + +#ifndef _USB2_HID_H_ +#define _USB2_HID_H_ + +#define UR_GET_HID_DESCRIPTOR 0x06 +#define UDESC_HID 0x21 +#define UDESC_REPORT 0x22 +#define UDESC_PHYSICAL 0x23 +#define UR_SET_HID_DESCRIPTOR 0x07 +#define UR_GET_REPORT 0x01 +#define UR_SET_REPORT 0x09 +#define UR_GET_IDLE 0x02 +#define UR_SET_IDLE 0x0a +#define UR_GET_PROTOCOL 0x03 +#define UR_SET_PROTOCOL 0x0b + +struct usb2_hid_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bcdHID; + uByte bCountryCode; + uByte bNumDescriptors; + struct { + uByte bDescriptorType; + uWord wDescriptorLength; + } descrs[1]; +} __packed; + +#define USB_HID_DESCRIPTOR_SIZE(n) (9+((n)*3)) + +/* Usage pages */ +#define HUP_UNDEFINED 0x0000 +#define HUP_GENERIC_DESKTOP 0x0001 +#define HUP_SIMULATION 0x0002 +#define HUP_VR_CONTROLS 0x0003 +#define HUP_SPORTS_CONTROLS 0x0004 +#define HUP_GAMING_CONTROLS 0x0005 +#define HUP_KEYBOARD 0x0007 +#define HUP_LEDS 0x0008 +#define HUP_BUTTON 0x0009 +#define HUP_ORDINALS 0x000a +#define HUP_TELEPHONY 0x000b +#define HUP_CONSUMER 0x000c +#define HUP_DIGITIZERS 0x000d +#define HUP_PHYSICAL_IFACE 0x000e +#define HUP_UNICODE 0x0010 +#define HUP_ALPHANUM_DISPLAY 0x0014 +#define HUP_MONITOR 0x0080 +#define HUP_MONITOR_ENUM_VAL 0x0081 +#define HUP_VESA_VC 0x0082 +#define HUP_VESA_CMD 0x0083 +#define HUP_POWER 0x0084 +#define HUP_BATTERY_SYSTEM 0x0085 +#define HUP_BARCODE_SCANNER 0x008b +#define HUP_SCALE 0x008c +#define HUP_CAMERA_CONTROL 0x0090 +#define HUP_ARCADE 0x0091 +#define HUP_MICROSOFT 0xff00 + +/* Usages, generic desktop */ +#define HUG_POINTER 0x0001 +#define HUG_MOUSE 0x0002 +#define HUG_JOYSTICK 0x0004 +#define HUG_GAME_PAD 0x0005 +#define HUG_KEYBOARD 0x0006 +#define HUG_KEYPAD 0x0007 +#define HUG_X 0x0030 +#define HUG_Y 0x0031 +#define HUG_Z 0x0032 +#define HUG_RX 0x0033 +#define HUG_RY 0x0034 +#define HUG_RZ 0x0035 +#define HUG_SLIDER 0x0036 +#define HUG_DIAL 0x0037 +#define HUG_WHEEL 0x0038 +#define HUG_HAT_SWITCH 0x0039 +#define HUG_COUNTED_BUFFER 0x003a +#define HUG_BYTE_COUNT 0x003b +#define HUG_MOTION_WAKEUP 0x003c +#define HUG_VX 0x0040 +#define HUG_VY 0x0041 +#define HUG_VZ 0x0042 +#define HUG_VBRX 0x0043 +#define HUG_VBRY 0x0044 +#define HUG_VBRZ 0x0045 +#define HUG_VNO 0x0046 +#define HUG_TWHEEL 0x0048 /* M$ Wireless Intellimouse Wheel */ +#define HUG_SYSTEM_CONTROL 0x0080 +#define HUG_SYSTEM_POWER_DOWN 0x0081 +#define HUG_SYSTEM_SLEEP 0x0082 +#define HUG_SYSTEM_WAKEUP 0x0083 +#define HUG_SYSTEM_CONTEXT_MENU 0x0084 +#define HUG_SYSTEM_MAIN_MENU 0x0085 +#define HUG_SYSTEM_APP_MENU 0x0086 +#define HUG_SYSTEM_MENU_HELP 0x0087 +#define HUG_SYSTEM_MENU_EXIT 0x0088 +#define HUG_SYSTEM_MENU_SELECT 0x0089 +#define HUG_SYSTEM_MENU_RIGHT 0x008a +#define HUG_SYSTEM_MENU_LEFT 0x008b +#define HUG_SYSTEM_MENU_UP 0x008c +#define HUG_SYSTEM_MENU_DOWN 0x008d + +/* Usages Digitizers */ +#define HUD_UNDEFINED 0x0000 +#define HUD_TIP_PRESSURE 0x0030 +#define HUD_BARREL_PRESSURE 0x0031 +#define HUD_IN_RANGE 0x0032 +#define HUD_TOUCH 0x0033 +#define HUD_UNTOUCH 0x0034 +#define HUD_TAP 0x0035 +#define HUD_QUALITY 0x0036 +#define HUD_DATA_VALID 0x0037 +#define HUD_TRANSDUCER_INDEX 0x0038 +#define HUD_TABLET_FKEYS 0x0039 +#define HUD_PROGRAM_CHANGE_KEYS 0x003a +#define HUD_BATTERY_STRENGTH 0x003b +#define HUD_INVERT 0x003c +#define HUD_X_TILT 0x003d +#define HUD_Y_TILT 0x003e +#define HUD_AZIMUTH 0x003f +#define HUD_ALTITUDE 0x0040 +#define HUD_TWIST 0x0041 +#define HUD_TIP_SWITCH 0x0042 +#define HUD_SEC_TIP_SWITCH 0x0043 +#define HUD_BARREL_SWITCH 0x0044 +#define HUD_ERASER 0x0045 +#define HUD_TABLET_PICK 0x0046 + +#define HID_USAGE2(p,u) (((p) << 16) | (u)) + +#define UHID_INPUT_REPORT 0x01 +#define UHID_OUTPUT_REPORT 0x02 +#define UHID_FEATURE_REPORT 0x03 + +/* Bits in the input/output/feature items */ +#define HIO_CONST 0x001 +#define HIO_VARIABLE 0x002 +#define HIO_RELATIVE 0x004 +#define HIO_WRAP 0x008 +#define HIO_NONLINEAR 0x010 +#define HIO_NOPREF 0x020 +#define HIO_NULLSTATE 0x040 +#define HIO_VOLATILE 0x080 +#define HIO_BUFBYTES 0x100 + +#endif /* _USB2_HID_H_ */ diff --git a/sys/dev/usb2/include/usb2_ioctl.h b/sys/dev/usb2/include/usb2_ioctl.h new file mode 100644 index 000000000000..0cab67a4b08b --- /dev/null +++ b/sys/dev/usb2/include/usb2_ioctl.h @@ -0,0 +1,301 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. 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. + */ + +#ifndef _USB2_IOCTL_H_ +#define _USB2_IOCTL_H_ + +#include + +#define USB_DEVICE_NAME "usb" +#define USB_GENERIC_NAME "ugen" + +/* definition of USB power mode */ +#define USB_POWER_MODE_OFF 0 /* turn off device */ +#define USB_POWER_MODE_ON 1 /* always on */ +#define USB_POWER_MODE_SAVE 2 /* automatic suspend and resume */ +#define USB_POWER_MODE_SUSPEND 3 /* force suspend */ +#define USB_POWER_MODE_RESUME 4 /* force resume */ + +struct usb2_read_dir { + void *urd_data; + uint32_t urd_startentry; + uint32_t urd_maxlen; +}; + +struct usb2_ctl_request { + void *ucr_data; + uint16_t ucr_flags; +#define USB_USE_POLLING 0x0001 /* internal flag */ +#define USB_SHORT_XFER_OK 0x0004 /* allow short reads */ +#define USB_DELAY_STATUS_STAGE 0x0010 /* insert delay before STATUS stage */ +#define USB_USER_DATA_PTR 0x0020 /* internal flag */ + uint16_t ucr_actlen; /* actual length transferred */ + uint8_t ucr_addr; /* zero - currently not used */ + struct usb2_device_request ucr_request; +}; + +struct usb2_alt_interface { + uint8_t uai_interface_index; + uint8_t uai_alt_index; +}; + +struct usb2_gen_descriptor { + void *ugd_data; + uint16_t ugd_lang_id; + uint16_t ugd_maxlen; + uint16_t ugd_actlen; + uint16_t ugd_offset; + uint8_t ugd_config_index; + uint8_t ugd_string_index; + uint8_t ugd_iface_index; + uint8_t ugd_altif_index; + uint8_t ugd_endpt_index; + uint8_t ugd_report_type; + uint8_t reserved[8]; +}; + +struct usb2_device_names { + char *udn_devnames_ptr; /* userland pointer to comma separated + * list of device names */ + uint16_t udn_devnames_len; /* maximum string length including + * terminating zero */ +}; + +struct usb2_device_info { + uint16_t udi_productNo; + uint16_t udi_vendorNo; + uint16_t udi_releaseNo; + uint16_t udi_power; /* power consumption in mA, 0 if + * selfpowered */ + uint8_t udi_bus; + uint8_t udi_addr; /* device address */ + uint8_t udi_index; /* device index */ + uint8_t udi_class; + uint8_t udi_subclass; + uint8_t udi_protocol; + uint8_t udi_config_no; /* current config number */ + uint8_t udi_config_index; /* current config index */ + uint8_t udi_speed; /* see "USB_SPEED_XXX" */ + uint8_t udi_mode; /* see "USB_MODE_XXX" */ + uint8_t udi_nports; + uint8_t udi_hubaddr; /* parent HUB address */ + uint8_t udi_hubindex; /* parent HUB device index */ + uint8_t udi_hubport; /* parent HUB port */ + uint8_t udi_power_mode; /* see "USB_POWER_MODE_XXX" */ + uint8_t udi_suspended; /* set if device is suspended */ + uint8_t udi_reserved[16]; /* leave space for the future */ + char udi_product[128]; + char udi_vendor[128]; + char udi_serial[64]; + char udi_release[8]; +}; + +struct usb2_device_stats { + uint32_t uds_requests_ok[4]; /* Indexed by transfer type UE_XXX */ + uint32_t uds_requests_fail[4]; /* Indexed by transfer type UE_XXX */ +}; + +struct usb2_fs_start { + uint8_t ep_index; +}; + +struct usb2_fs_stop { + uint8_t ep_index; +}; + +struct usb2_fs_complete { + uint8_t ep_index; +}; + +/* This structure is used for all endpoint types */ +struct usb2_fs_endpoint { + /* + * NOTE: isochronous USB transfer only use one buffer, but can have + * multiple frame lengths ! + */ + void **ppBuffer; /* pointer to userland buffers */ + uint32_t *pLength; /* pointer to frame lengths, updated + * to actual length */ + uint32_t nFrames; /* number of frames */ + uint32_t aFrames; /* actual number of frames */ + uint16_t flags; + /* a single short frame will terminate */ +#define USB_FS_FLAG_SINGLE_SHORT_OK 0x0001 + /* multiple short frames are allowed */ +#define USB_FS_FLAG_MULTI_SHORT_OK 0x0002 + /* all frame(s) transmitted are short terminated */ +#define USB_FS_FLAG_FORCE_SHORT 0x0004 + /* will do a clear-stall before xfer */ +#define USB_FS_FLAG_CLEAR_STALL 0x0008 + uint16_t timeout; /* in milliseconds */ + /* isocronous completion time in milliseconds - used for echo cancel */ + uint16_t isoc_time_complete; + /* timeout value for no timeout */ +#define USB_FS_TIMEOUT_NONE 0 + uint8_t status; /* see USB_ERR_XXX */ +}; + +struct usb2_fs_init { + /* userland pointer to endpoints structure */ + struct usb2_fs_endpoint *pEndpoints; + /* maximum number of endpoints */ + uint8_t ep_index_max; +}; + +struct usb2_fs_uninit { + uint8_t dummy; /* zero */ +}; + +struct usb2_fs_open { +#define USB_FS_MAX_BUFSIZE (1 << 18) + uint32_t max_bufsize; +#define USB_FS_MAX_FRAMES (1 << 12) + uint32_t max_frames; + uint16_t max_packet_length; /* read only */ + uint8_t dev_index; /* currently unused */ + uint8_t ep_index; + uint8_t ep_no; /* bEndpointNumber */ +}; + +struct usb2_fs_close { + uint8_t ep_index; +}; + +struct usb2_fs_clear_stall_sync { + uint8_t ep_index; +}; + +struct usb2_dev_perm { + /* Access information */ + uint32_t user_id; + uint32_t group_id; + uint16_t mode; + + /* Device location */ + uint16_t bus_index; + uint16_t dev_index; + uint16_t iface_index; +}; + +struct usb2_gen_quirk { + uint16_t index; /* Quirk Index */ + uint16_t vid; /* Vendor ID */ + uint16_t pid; /* Product ID */ + uint16_t bcdDeviceLow; /* Low Device Revision */ + uint16_t bcdDeviceHigh; /* High Device Revision */ + uint16_t reserved[2]; + /* + * String version of quirk including terminating zero. See UQ_XXX in + * "usb2_quirk.h". + */ + char quirkname[64 - 14]; +}; + +/* USB controller */ +#define USB_REQUEST _IOWR('U', 1, struct usb2_ctl_request) +#define USB_SETDEBUG _IOW ('U', 2, int) +#define USB_DISCOVER _IO ('U', 3) +#define USB_DEVICEINFO _IOWR('U', 4, struct usb2_device_info) +#define USB_DEVICESTATS _IOR ('U', 5, struct usb2_device_stats) +#define USB_DEVICEENUMERATE _IOW ('U', 6, int) + +/* Generic HID device */ +#define USB_GET_REPORT_DESC _IOR ('U', 21, struct usb2_gen_descriptor) +#define USB_SET_IMMED _IOW ('U', 22, int) +#define USB_GET_REPORT _IOWR('U', 23, struct usb2_gen_descriptor) +#define USB_SET_REPORT _IOW ('U', 24, struct usb2_gen_descriptor) +#define USB_GET_REPORT_ID _IOR ('U', 25, int) + +/* Generic USB device */ +#define USB_GET_CONFIG _IOR ('U', 100, int) +#define USB_SET_CONFIG _IOW ('U', 101, int) +#define USB_GET_ALTINTERFACE _IOWR('U', 102, struct usb2_alt_interface) +#define USB_SET_ALTINTERFACE _IOWR('U', 103, struct usb2_alt_interface) +#define USB_GET_DEVICE_DESC _IOR ('U', 105, struct usb2_device_descriptor) +#define USB_GET_CONFIG_DESC _IOR ('U', 106, struct usb2_config_descriptor) +#define USB_GET_RX_INTERFACE_DESC _IOR ('U', 107, struct usb2_interface_descriptor) +#define USB_GET_RX_ENDPOINT_DESC _IOR ('U', 108, struct usb2_endpoint_descriptor) +#define USB_GET_FULL_DESC _IOWR('U', 109, struct usb2_gen_descriptor) +#define USB_GET_STRING_DESC _IOWR('U', 110, struct usb2_gen_descriptor) +#define USB_DO_REQUEST _IOWR('U', 111, struct usb2_ctl_request) +#define USB_GET_DEVICEINFO _IOR ('U', 112, struct usb2_device_info) +#define USB_SET_RX_SHORT_XFER _IOW ('U', 113, int) +#define USB_SET_RX_TIMEOUT _IOW ('U', 114, int) +#define USB_GET_RX_FRAME_SIZE _IOR ('U', 115, int) +#define USB_GET_RX_BUFFER_SIZE _IOR ('U', 117, int) +#define USB_SET_RX_BUFFER_SIZE _IOW ('U', 118, int) +#define USB_SET_RX_STALL_FLAG _IOW ('U', 119, int) +#define USB_SET_TX_STALL_FLAG _IOW ('U', 120, int) +#define USB_GET_DEVICENAMES _IOW ('U', 121, struct usb2_device_names) +#define USB_CLAIM_INTERFACE _IOW ('U', 122, int) +#define USB_RELEASE_INTERFACE _IOW ('U', 123, int) +#define USB_IFACE_DRIVER_ACTIVE _IOW ('U', 124, int) +#define USB_IFACE_DRIVER_DETACH _IOW ('U', 125, int) +#define USB_GET_PLUGTIME _IOR ('U', 126, uint32_t) +#define USB_READ_DIR _IOW ('U', 127, struct usb2_read_dir) +#define USB_SET_ROOT_PERM _IOW ('U', 128, struct usb2_dev_perm) +#define USB_SET_BUS_PERM _IOW ('U', 129, struct usb2_dev_perm) +#define USB_SET_DEVICE_PERM _IOW ('U', 130, struct usb2_dev_perm) +#define USB_SET_IFACE_PERM _IOW ('U', 131, struct usb2_dev_perm) +#define USB_GET_ROOT_PERM _IOWR('U', 132, struct usb2_dev_perm) +#define USB_GET_BUS_PERM _IOWR('U', 133, struct usb2_dev_perm) +#define USB_GET_DEVICE_PERM _IOWR('U', 134, struct usb2_dev_perm) +#define USB_GET_IFACE_PERM _IOWR('U', 135, struct usb2_dev_perm) +#define USB_SET_TX_FORCE_SHORT _IOW ('U', 136, int) +#define USB_SET_TX_TIMEOUT _IOW ('U', 137, int) +#define USB_GET_TX_FRAME_SIZE _IOR ('U', 138, int) +#define USB_GET_TX_BUFFER_SIZE _IOR ('U', 139, int) +#define USB_SET_TX_BUFFER_SIZE _IOW ('U', 140, int) +#define USB_GET_TX_INTERFACE_DESC _IOR ('U', 141, struct usb2_interface_descriptor) +#define USB_GET_TX_ENDPOINT_DESC _IOR ('U', 142, struct usb2_endpoint_descriptor) +#define USB_SET_PORT_ENABLE _IOW ('U', 143, int) +#define USB_SET_PORT_DISABLE _IOW ('U', 144, int) +#define USB_SET_POWER_MODE _IOW ('U', 145, int) +#define USB_GET_POWER_MODE _IOR ('U', 146, int) + +/* Modem device */ +#define USB_GET_CM_OVER_DATA _IOR ('U', 180, int) +#define USB_SET_CM_OVER_DATA _IOW ('U', 181, int) + +/* USB file system interface */ +#define USB_FS_START _IOW ('U', 192, struct usb2_fs_start) +#define USB_FS_STOP _IOW ('U', 193, struct usb2_fs_stop) +#define USB_FS_COMPLETE _IOR ('U', 194, struct usb2_fs_complete) +#define USB_FS_INIT _IOW ('U', 195, struct usb2_fs_init) +#define USB_FS_UNINIT _IOW ('U', 196, struct usb2_fs_uninit) +#define USB_FS_OPEN _IOWR('U', 197, struct usb2_fs_open) +#define USB_FS_CLOSE _IOW ('U', 198, struct usb2_fs_close) +#define USB_FS_CLEAR_STALL_SYNC _IOW ('U', 199, struct usb2_fs_clear_stall_sync) + +/* USB quirk system interface */ +#define USB_DEV_QUIRK_GET _IOWR('Q', 0, struct usb2_gen_quirk) +#define USB_QUIRK_NAME_GET _IOWR('Q', 1, struct usb2_gen_quirk) +#define USB_DEV_QUIRK_ADD _IOW ('Q', 2, struct usb2_gen_quirk) +#define USB_DEV_QUIRK_REMOVE _IOW ('Q', 3, struct usb2_gen_quirk) + +#endif /* _USB2_IOCTL_H_ */ diff --git a/sys/dev/usb2/include/usb2_mfunc.h b/sys/dev/usb2/include/usb2_mfunc.h new file mode 100644 index 000000000000..37be051c5586 --- /dev/null +++ b/sys/dev/usb2/include/usb2_mfunc.h @@ -0,0 +1,86 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +/* This file contains various macro functions. */ + +#ifndef _USB2_MFUNC_H_ +#define _USB2_MFUNC_H_ + +#define USB_MAKE_001(n,ENUM) ENUM, +#define USB_MAKE_ENUM(m) \ +enum { m(USB_MAKE_001,) m##_MAX } + +#define USB_MAKE_002(n,ENUM) #ENUM, +#define USB_MAKE_DEBUG_TABLE(m) \ +static const char * m[m##_MAX] = { m(USB_MAKE_002,) } + +#define USB_LOG2(n) ( \ +((x) <= (1<<0x00)) ? 0x00 : \ +((x) <= (1<<0x01)) ? 0x01 : \ +((x) <= (1<<0x02)) ? 0x02 : \ +((x) <= (1<<0x03)) ? 0x03 : \ +((x) <= (1<<0x04)) ? 0x04 : \ +((x) <= (1<<0x05)) ? 0x05 : \ +((x) <= (1<<0x06)) ? 0x06 : \ +((x) <= (1<<0x07)) ? 0x07 : \ +((x) <= (1<<0x08)) ? 0x08 : \ +((x) <= (1<<0x09)) ? 0x09 : \ +((x) <= (1<<0x0A)) ? 0x0A : \ +((x) <= (1<<0x0B)) ? 0x0B : \ +((x) <= (1<<0x0C)) ? 0x0C : \ +((x) <= (1<<0x0D)) ? 0x0D : \ +((x) <= (1<<0x0E)) ? 0x0E : \ +((x) <= (1<<0x0F)) ? 0x0F : \ +((x) <= (1<<0x10)) ? 0x10 : \ +((x) <= (1<<0x11)) ? 0x11 : \ +((x) <= (1<<0x12)) ? 0x12 : \ +((x) <= (1<<0x13)) ? 0x13 : \ +((x) <= (1<<0x14)) ? 0x14 : \ +((x) <= (1<<0x15)) ? 0x15 : \ +((x) <= (1<<0x16)) ? 0x16 : \ +((x) <= (1<<0x17)) ? 0x17 : \ +((x) <= (1<<0x18)) ? 0x18 : \ +((x) <= (1<<0x19)) ? 0x19 : \ +((x) <= (1<<0x1A)) ? 0x1A : \ +((x) <= (1<<0x1B)) ? 0x1B : \ +((x) <= (1<<0x1C)) ? 0x1C : \ +((x) <= (1<<0x1D)) ? 0x1D : \ +((x) <= (1<<0x1E)) ? 0x1E : \ +0x1F) + + +/* helper for converting pointers to integers */ +#define USB_P2U(ptr) \ + (((const uint8_t *)(ptr)) - ((const uint8_t *)0)) + +/* helper for computing offsets */ +#define USB_ADD_BYTES(ptr,size) \ + ((void *)(USB_P2U(ptr) + (size))) + +/* debug macro */ +#define USB_ASSERT KASSERT + +#endif /* _USB2_MFUNC_H_ */ diff --git a/sys/dev/usb2/include/usb2_revision.h b/sys/dev/usb2/include/usb2_revision.h new file mode 100644 index 000000000000..b57946c00c59 --- /dev/null +++ b/sys/dev/usb2/include/usb2_revision.h @@ -0,0 +1,67 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_REVISION_H_ +#define _USB2_REVISION_H_ + +#include + +/* + * The "USB_SPEED" macro defines all the supported USB speeds. + */ +#define USB_SPEED(m,n)\ +m(n, USB_SPEED_VARIABLE)\ +m(n, USB_SPEED_LOW)\ +m(n, USB_SPEED_FULL)\ +m(n, USB_SPEED_HIGH)\ +m(n, USB_SPEED_SUPER)\ + +USB_MAKE_ENUM(USB_SPEED); + +/* + * The "USB_REV" macro defines all the supported USB revisions. + */ +#define USB_REV(m,n)\ +m(n, USB_REV_UNKNOWN)\ +m(n, USB_REV_PRE_1_0)\ +m(n, USB_REV_1_0)\ +m(n, USB_REV_1_1)\ +m(n, USB_REV_2_0)\ +m(n, USB_REV_2_5)\ +m(n, USB_REV_3_0)\ + +USB_MAKE_ENUM(USB_REV); + +/* + * The "USB_MODE" macro defines all the supported USB modes. + */ +#define USB_MODE(m,n)\ +m(n, USB_MODE_HOST)\ +m(n, USB_MODE_DEVICE)\ + +USB_MAKE_ENUM(USB_MODE); + +#endif /* _USB2_REVISION_H_ */ diff --git a/sys/dev/usb2/include/usb2_standard.h b/sys/dev/usb2/include/usb2_standard.h new file mode 100644 index 000000000000..05ca31496d1d --- /dev/null +++ b/sys/dev/usb2/include/usb2_standard.h @@ -0,0 +1,497 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. 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. + */ + +#ifndef _USB2_STANDARD_H_ +#define _USB2_STANDARD_H_ + +#include + +/* + * Minimum time a device needs to be powered down to go through a + * power cycle. These values are not in the USB specification. + */ +#define USB_POWER_DOWN_TIME 200 /* ms */ +#define USB_PORT_POWER_DOWN_TIME 100 /* ms */ + +#if 0 +/* These are the values from the USB specification. */ +#define USB_PORT_RESET_DELAY 10 /* ms */ +#define USB_PORT_ROOT_RESET_DELAY 50 /* ms */ +#define USB_PORT_RESET_RECOVERY 10 /* ms */ +#define USB_PORT_POWERUP_DELAY 100 /* ms */ +#define USB_SET_ADDRESS_SETTLE 2 /* ms */ +#define USB_RESUME_DELAY (20*5) /* ms */ +#define USB_RESUME_WAIT 10 /* ms */ +#define USB_RESUME_RECOVERY 10 /* ms */ +#define USB_EXTRA_POWER_UP_TIME 0 /* ms */ +#else +/* Allow for marginal and non-conforming devices. */ +#define USB_PORT_RESET_DELAY 50 /* ms */ +#define USB_PORT_ROOT_RESET_DELAY 250 /* ms */ +#define USB_PORT_RESET_RECOVERY 250 /* ms */ +#define USB_PORT_POWERUP_DELAY 300 /* ms */ +#define USB_SET_ADDRESS_SETTLE 10 /* ms */ +#define USB_RESUME_DELAY (50*5) /* ms */ +#define USB_RESUME_WAIT 50 /* ms */ +#define USB_RESUME_RECOVERY 50 /* ms */ +#define USB_EXTRA_POWER_UP_TIME 20 /* ms */ +#endif + +#define USB_MIN_POWER 100 /* mA */ +#define USB_MAX_POWER 500 /* mA */ + +#define USB_BUS_RESET_DELAY 100 /* ms */ + +/* + * USB record layout in memory: + * + * - USB config 0 + * - USB interfaces + * - USB alternative interfaces + * - USB pipes + * + * - USB config 1 + * - USB interfaces + * - USB alternative interfaces + * - USB pipes + */ + +/* Declaration of USB records */ + +struct usb2_device_request { + uByte bmRequestType; + uByte bRequest; + uWord wValue; + uWord wIndex; + uWord wLength; +} __packed; + +#define UT_WRITE 0x00 +#define UT_READ 0x80 +#define UT_STANDARD 0x00 +#define UT_CLASS 0x20 +#define UT_VENDOR 0x40 +#define UT_DEVICE 0x00 +#define UT_INTERFACE 0x01 +#define UT_ENDPOINT 0x02 +#define UT_OTHER 0x03 + +#define UT_READ_DEVICE (UT_READ | UT_STANDARD | UT_DEVICE) +#define UT_READ_INTERFACE (UT_READ | UT_STANDARD | UT_INTERFACE) +#define UT_READ_ENDPOINT (UT_READ | UT_STANDARD | UT_ENDPOINT) +#define UT_WRITE_DEVICE (UT_WRITE | UT_STANDARD | UT_DEVICE) +#define UT_WRITE_INTERFACE (UT_WRITE | UT_STANDARD | UT_INTERFACE) +#define UT_WRITE_ENDPOINT (UT_WRITE | UT_STANDARD | UT_ENDPOINT) +#define UT_READ_CLASS_DEVICE (UT_READ | UT_CLASS | UT_DEVICE) +#define UT_READ_CLASS_INTERFACE (UT_READ | UT_CLASS | UT_INTERFACE) +#define UT_READ_CLASS_OTHER (UT_READ | UT_CLASS | UT_OTHER) +#define UT_READ_CLASS_ENDPOINT (UT_READ | UT_CLASS | UT_ENDPOINT) +#define UT_WRITE_CLASS_DEVICE (UT_WRITE | UT_CLASS | UT_DEVICE) +#define UT_WRITE_CLASS_INTERFACE (UT_WRITE | UT_CLASS | UT_INTERFACE) +#define UT_WRITE_CLASS_OTHER (UT_WRITE | UT_CLASS | UT_OTHER) +#define UT_WRITE_CLASS_ENDPOINT (UT_WRITE | UT_CLASS | UT_ENDPOINT) +#define UT_READ_VENDOR_DEVICE (UT_READ | UT_VENDOR | UT_DEVICE) +#define UT_READ_VENDOR_INTERFACE (UT_READ | UT_VENDOR | UT_INTERFACE) +#define UT_READ_VENDOR_OTHER (UT_READ | UT_VENDOR | UT_OTHER) +#define UT_READ_VENDOR_ENDPOINT (UT_READ | UT_VENDOR | UT_ENDPOINT) +#define UT_WRITE_VENDOR_DEVICE (UT_WRITE | UT_VENDOR | UT_DEVICE) +#define UT_WRITE_VENDOR_INTERFACE (UT_WRITE | UT_VENDOR | UT_INTERFACE) +#define UT_WRITE_VENDOR_OTHER (UT_WRITE | UT_VENDOR | UT_OTHER) +#define UT_WRITE_VENDOR_ENDPOINT (UT_WRITE | UT_VENDOR | UT_ENDPOINT) + +/* Requests */ +#define UR_GET_STATUS 0x00 +#define UR_CLEAR_FEATURE 0x01 +#define UR_SET_FEATURE 0x03 +#define UR_SET_ADDRESS 0x05 +#define UR_GET_DESCRIPTOR 0x06 +#define UDESC_DEVICE 0x01 +#define UDESC_CONFIG 0x02 +#define UDESC_STRING 0x03 +#define USB_LANGUAGE_TABLE 0x00 /* Index of the language ID table + * string */ +#define UDESC_INTERFACE 0x04 +#define UDESC_ENDPOINT 0x05 +#define UDESC_DEVICE_QUALIFIER 0x06 +#define UDESC_OTHER_SPEED_CONFIGURATION 0x07 +#define UDESC_INTERFACE_POWER 0x08 +#define UDESC_OTG 0x09 +#define UDESC_CS_DEVICE 0x21 /* class specific */ +#define UDESC_CS_CONFIG 0x22 +#define UDESC_CS_STRING 0x23 +#define UDESC_CS_INTERFACE 0x24 +#define UDESC_CS_ENDPOINT 0x25 +#define UDESC_HUB 0x29 +#define UR_SET_DESCRIPTOR 0x07 +#define UR_GET_CONFIG 0x08 +#define UR_SET_CONFIG 0x09 +#define UR_GET_INTERFACE 0x0a +#define UR_SET_INTERFACE 0x0b +#define UR_SYNCH_FRAME 0x0c + +/* HUB specific request */ +#define UR_GET_BUS_STATE 0x02 +#define UR_CLEAR_TT_BUFFER 0x08 +#define UR_RESET_TT 0x09 +#define UR_GET_TT_STATE 0x0a +#define UR_STOP_TT 0x0b + +/* Feature numbers */ +#define UF_ENDPOINT_HALT 0 +#define UF_DEVICE_REMOTE_WAKEUP 1 +#define UF_TEST_MODE 2 + +/* HUB specific features */ +#define UHF_C_HUB_LOCAL_POWER 0 +#define UHF_C_HUB_OVER_CURRENT 1 +#define UHF_PORT_CONNECTION 0 +#define UHF_PORT_ENABLE 1 +#define UHF_PORT_SUSPEND 2 +#define UHF_PORT_OVER_CURRENT 3 +#define UHF_PORT_RESET 4 +#define UHF_PORT_POWER 8 +#define UHF_PORT_LOW_SPEED 9 +#define UHF_C_PORT_CONNECTION 16 +#define UHF_C_PORT_ENABLE 17 +#define UHF_C_PORT_SUSPEND 18 +#define UHF_C_PORT_OVER_CURRENT 19 +#define UHF_C_PORT_RESET 20 +#define UHF_PORT_TEST 21 +#define UHF_PORT_INDICATOR 22 + +struct usb2_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; +} __packed; + +struct usb2_device_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; +#define UD_USB_2_0 0x0200 +#define UD_IS_USB2(d) (UGETW((d)->bcdUSB) >= UD_USB_2_0) + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize; + /* The fields below are not part of the initial descriptor. */ + uWord idVendor; + uWord idProduct; + uWord bcdDevice; + uByte iManufacturer; + uByte iProduct; + uByte iSerialNumber; + uByte bNumConfigurations; +} __packed; + +/* Device class codes */ +#define UDCLASS_IN_INTERFACE 0x00 +#define UDCLASS_COMM 0x02 +#define UDCLASS_HUB 0x09 +#define UDSUBCLASS_HUB 0x00 +#define UDPROTO_FSHUB 0x00 +#define UDPROTO_HSHUBSTT 0x01 +#define UDPROTO_HSHUBMTT 0x02 +#define UDCLASS_DIAGNOSTIC 0xdc +#define UDCLASS_WIRELESS 0xe0 +#define UDSUBCLASS_RF 0x01 +#define UDPROTO_BLUETOOTH 0x01 +#define UDCLASS_VENDOR 0xff + +struct usb2_config_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord wTotalLength; + uByte bNumInterface; + uByte bConfigurationValue; +#define USB_UNCONFIG_NO 0 + uByte iConfiguration; + uByte bmAttributes; +#define UC_BUS_POWERED 0x80 +#define UC_SELF_POWERED 0x40 +#define UC_REMOTE_WAKEUP 0x20 + uByte bMaxPower; /* max current in 2 mA units */ +#define UC_POWER_FACTOR 2 +} __packed; + +struct usb2_interface_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bInterfaceNumber; + uByte bAlternateSetting; + uByte bNumEndpoints; + uByte bInterfaceClass; + uByte bInterfaceSubClass; + uByte bInterfaceProtocol; + uByte iInterface; +} __packed; + +/* Interface class codes */ +#define UICLASS_UNSPEC 0x00 +#define UICLASS_AUDIO 0x01 /* audio */ +#define UISUBCLASS_AUDIOCONTROL 1 +#define UISUBCLASS_AUDIOSTREAM 2 +#define UISUBCLASS_MIDISTREAM 3 + +#define UICLASS_CDC 0x02 /* communication */ +#define UISUBCLASS_DIRECT_LINE_CONTROL_MODEL 1 +#define UISUBCLASS_ABSTRACT_CONTROL_MODEL 2 +#define UISUBCLASS_TELEPHONE_CONTROL_MODEL 3 +#define UISUBCLASS_MULTICHANNEL_CONTROL_MODEL 4 +#define UISUBCLASS_CAPI_CONTROLMODEL 5 +#define UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL 6 +#define UISUBCLASS_ATM_NETWORKING_CONTROL_MODEL 7 +#define UISUBCLASS_WIRELESS_HANDSET_CM 8 +#define UISUBCLASS_DEVICE_MGMT 9 +#define UISUBCLASS_MOBILE_DIRECT_LINE_MODEL 10 +#define UISUBCLASS_OBEX 11 +#define UISUBCLASS_ETHERNET_EMULATION_MODEL 12 + +#define UIPROTO_CDC_AT 1 +#define UIPROTO_CDC_ETH_512X4 0x76 /* FreeBSD specific */ + +#define UICLASS_HID 0x03 +#define UISUBCLASS_BOOT 1 +#define UIPROTO_BOOT_KEYBOARD 1 +#define UIPROTO_MOUSE 2 + +#define UICLASS_PHYSICAL 0x05 +#define UICLASS_IMAGE 0x06 +#define UISUBCLASS_SIC 1 /* still image class */ +#define UICLASS_PRINTER 0x07 +#define UISUBCLASS_PRINTER 1 +#define UIPROTO_PRINTER_UNI 1 +#define UIPROTO_PRINTER_BI 2 +#define UIPROTO_PRINTER_1284 3 + +#define UICLASS_MASS 0x08 +#define UISUBCLASS_RBC 1 +#define UISUBCLASS_SFF8020I 2 +#define UISUBCLASS_QIC157 3 +#define UISUBCLASS_UFI 4 +#define UISUBCLASS_SFF8070I 5 +#define UISUBCLASS_SCSI 6 +#define UIPROTO_MASS_CBI_I 0 +#define UIPROTO_MASS_CBI 1 +#define UIPROTO_MASS_BBB_OLD 2 /* Not in the spec anymore */ +#define UIPROTO_MASS_BBB 80 /* 'P' for the Iomega Zip drive */ + +#define UICLASS_HUB 0x09 +#define UISUBCLASS_HUB 0 +#define UIPROTO_FSHUB 0 +#define UIPROTO_HSHUBSTT 0 /* Yes, same as previous */ +#define UIPROTO_HSHUBMTT 1 + +#define UICLASS_CDC_DATA 0x0a +#define UISUBCLASS_DATA 0 +#define UIPROTO_DATA_ISDNBRI 0x30 /* Physical iface */ +#define UIPROTO_DATA_HDLC 0x31 /* HDLC */ +#define UIPROTO_DATA_TRANSPARENT 0x32 /* Transparent */ +#define UIPROTO_DATA_Q921M 0x50 /* Management for Q921 */ +#define UIPROTO_DATA_Q921 0x51 /* Data for Q921 */ +#define UIPROTO_DATA_Q921TM 0x52 /* TEI multiplexer for Q921 */ +#define UIPROTO_DATA_V42BIS 0x90 /* Data compression */ +#define UIPROTO_DATA_Q931 0x91 /* Euro-ISDN */ +#define UIPROTO_DATA_V120 0x92 /* V.24 rate adaption */ +#define UIPROTO_DATA_CAPI 0x93 /* CAPI 2.0 commands */ +#define UIPROTO_DATA_HOST_BASED 0xfd /* Host based driver */ +#define UIPROTO_DATA_PUF 0xfe /* see Prot. Unit Func. Desc. */ +#define UIPROTO_DATA_VENDOR 0xff /* Vendor specific */ + +#define UICLASS_SMARTCARD 0x0b +#define UICLASS_FIRM_UPD 0x0c +#define UICLASS_SECURITY 0x0d +#define UICLASS_DIAGNOSTIC 0xdc +#define UICLASS_WIRELESS 0xe0 +#define UISUBCLASS_RF 0x01 +#define UIPROTO_BLUETOOTH 0x01 + +#define UICLASS_APPL_SPEC 0xfe +#define UISUBCLASS_FIRMWARE_DOWNLOAD 1 +#define UISUBCLASS_IRDA 2 +#define UIPROTO_IRDA 0 + +#define UICLASS_VENDOR 0xff +#define UISUBCLASS_XBOX360_CONTROLLER 0x5d +#define UIPROTO_XBOX360_GAMEPAD 0x01 + +struct usb2_endpoint_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bEndpointAddress; +#define UE_GET_DIR(a) ((a) & 0x80) +#define UE_SET_DIR(a,d) ((a) | (((d)&1) << 7)) +#define UE_DIR_IN 0x80 +#define UE_DIR_OUT 0x00 +#define UE_DIR_ANY 0xff /* for internal use only! */ +#define UE_ADDR 0x0f +#define UE_ADDR_ANY 0xff /* for internal use only! */ +#define UE_GET_ADDR(a) ((a) & UE_ADDR) + uByte bmAttributes; +#define UE_XFERTYPE 0x03 +#define UE_CONTROL 0x00 +#define UE_ISOCHRONOUS 0x01 +#define UE_BULK 0x02 +#define UE_INTERRUPT 0x03 +#define UE_BULK_INTR 0xfe /* for internal use only! */ +#define UE_TYPE_ANY 0xff /* for internal use only! */ +#define UE_GET_XFERTYPE(a) ((a) & UE_XFERTYPE) +#define UE_ISO_TYPE 0x0c +#define UE_ISO_ASYNC 0x04 +#define UE_ISO_ADAPT 0x08 +#define UE_ISO_SYNC 0x0c +#define UE_GET_ISO_TYPE(a) ((a) & UE_ISO_TYPE) + uWord wMaxPacketSize; +#define UE_ZERO_MPS 0xFFFF /* for internal use only */ + uByte bInterval; +} __packed; + +struct usb2_string_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bString[126]; + uByte bUnused; +} __packed; + +#define USB_MAKE_STRING_DESC(m,name) \ +struct name { \ + uByte bLength; \ + uByte bDescriptorType; \ + uByte bData[sizeof((uint8_t []){m})]; \ +} __packed; \ +static const struct name name = { \ + .bLength = sizeof(struct name), \ + .bDescriptorType = UDESC_STRING, \ + .bData = { m }, \ +} + +struct usb2_hub_descriptor { + uByte bDescLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; +#define UHD_PWR 0x0003 +#define UHD_PWR_GANGED 0x0000 +#define UHD_PWR_INDIVIDUAL 0x0001 +#define UHD_PWR_NO_SWITCH 0x0002 +#define UHD_COMPOUND 0x0004 +#define UHD_OC 0x0018 +#define UHD_OC_GLOBAL 0x0000 +#define UHD_OC_INDIVIDUAL 0x0008 +#define UHD_OC_NONE 0x0010 +#define UHD_TT_THINK 0x0060 +#define UHD_TT_THINK_8 0x0000 +#define UHD_TT_THINK_16 0x0020 +#define UHD_TT_THINK_24 0x0040 +#define UHD_TT_THINK_32 0x0060 +#define UHD_PORT_IND 0x0080 + uByte bPwrOn2PwrGood; /* delay in 2 ms units */ +#define UHD_PWRON_FACTOR 2 + uByte bHubContrCurrent; + uByte DeviceRemovable[32]; /* max 255 ports */ +#define UHD_NOT_REMOV(desc, i) \ + (((desc)->DeviceRemovable[(i)/8] >> ((i) % 8)) & 1) + /* deprecated */ uByte PortPowerCtrlMask[1]; +} __packed; + +/* minimum HUB descriptor (8-ports maximum) */ +struct usb2_hub_descriptor_min { + uByte bDescLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; + uByte bPwrOn2PwrGood; + uByte bHubContrCurrent; + uByte DeviceRemovable[1]; + uByte PortPowerCtrlMask[1]; +} __packed; + +struct usb2_device_qualifier { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize0; + uByte bNumConfigurations; + uByte bReserved; +} __packed; + +struct usb2_otg_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bmAttributes; +#define UOTG_SRP 0x01 +#define UOTG_HNP 0x02 +} __packed; + +/* OTG feature selectors */ +#define UOTG_B_HNP_ENABLE 3 +#define UOTG_A_HNP_SUPPORT 4 +#define UOTG_A_ALT_HNP_SUPPORT 5 + +struct usb2_status { + uWord wStatus; +/* Device status flags */ +#define UDS_SELF_POWERED 0x0001 +#define UDS_REMOTE_WAKEUP 0x0002 +/* Endpoint status flags */ +#define UES_HALT 0x0001 +} __packed; + +struct usb2_hub_status { + uWord wHubStatus; +#define UHS_LOCAL_POWER 0x0001 +#define UHS_OVER_CURRENT 0x0002 + uWord wHubChange; +} __packed; + +struct usb2_port_status { + uWord wPortStatus; +#define UPS_CURRENT_CONNECT_STATUS 0x0001 +#define UPS_PORT_ENABLED 0x0002 +#define UPS_SUSPEND 0x0004 +#define UPS_OVERCURRENT_INDICATOR 0x0008 +#define UPS_RESET 0x0010 +#define UPS_PORT_MODE_DEVICE 0x0020 /* currently FreeBSD specific */ +#define UPS_PORT_POWER 0x0100 +#define UPS_LOW_SPEED 0x0200 +#define UPS_HIGH_SPEED 0x0400 +#define UPS_PORT_TEST 0x0800 +#define UPS_PORT_INDICATOR 0x1000 + uWord wPortChange; +#define UPS_C_CONNECT_STATUS 0x0001 +#define UPS_C_PORT_ENABLED 0x0002 +#define UPS_C_SUSPEND 0x0004 +#define UPS_C_OVERCURRENT_INDICATOR 0x0008 +#define UPS_C_PORT_RESET 0x0010 +} __packed; + +#endif /* _USB2_STANDARD_H_ */ diff --git a/sys/dev/usb2/input/uhid2.c b/sys/dev/usb2/input/uhid2.c new file mode 100644 index 000000000000..f5111f5e79e4 --- /dev/null +++ b/sys/dev/usb2/input/uhid2.c @@ -0,0 +1,822 @@ +/* $NetBSD: uhid.c,v 1.46 2001/11/13 06:24:55 lukem Exp $ */ + +/* Also already merged from NetBSD: + * $NetBSD: uhid.c,v 1.54 2002/09/23 05:51:21 simonb Exp $ + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR uhid_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#if USB_DEBUG +static int uhid_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uhid, CTLFLAG_RW, 0, "USB uhid"); +SYSCTL_INT(_hw_usb2_uhid, OID_AUTO, debug, CTLFLAG_RW, + &uhid_debug, 0, "Debug level"); +#endif + +#define UHID_N_TRANSFER 4 /* units */ +#define UHID_BSIZE 1024 /* bytes, buffer size */ +#define UHID_FRAME_NUM 50 /* bytes, frame number */ + +struct uhid_softc { + struct usb2_fifo_sc sc_fifo; + struct mtx sc_mtx; + + struct usb2_xfer *sc_xfer[UHID_N_TRANSFER]; + struct usb2_device *sc_udev; + void *sc_repdesc_ptr; + + uint32_t sc_isize; + uint32_t sc_osize; + uint32_t sc_fsize; + + uint16_t sc_repdesc_size; + + uint8_t sc_iface_no; + uint8_t sc_iface_index; + uint8_t sc_iid; + uint8_t sc_oid; + uint8_t sc_fid; + uint8_t sc_flags; +#define UHID_FLAG_IMMED 0x01 /* set if read should be immediate */ +#define UHID_FLAG_INTR_STALL 0x02 /* set if interrupt transfer stalled */ +#define UHID_FLAG_STATIC_DESC 0x04 /* set if report descriptors are + * static */ +}; + +static const uint8_t uhid_xb360gp_report_descr[] = {UHID_XB360GP_REPORT_DESCR()}; +static const uint8_t uhid_graphire_report_descr[] = {UHID_GRAPHIRE_REPORT_DESCR()}; +static const uint8_t uhid_graphire3_4x5_report_descr[] = {UHID_GRAPHIRE3_4X5_REPORT_DESCR()}; + +/* prototypes */ + +static device_probe_t uhid_probe; +static device_attach_t uhid_attach; +static device_detach_t uhid_detach; + +static usb2_callback_t uhid_intr_callback; +static usb2_callback_t uhid_intr_clear_stall_callback; +static usb2_callback_t uhid_write_callback; +static usb2_callback_t uhid_read_callback; + +static usb2_fifo_cmd_t uhid_start_read; +static usb2_fifo_cmd_t uhid_stop_read; +static usb2_fifo_cmd_t uhid_start_write; +static usb2_fifo_cmd_t uhid_stop_write; +static usb2_fifo_open_t uhid_open; +static usb2_fifo_close_t uhid_close; +static usb2_fifo_ioctl_t uhid_ioctl; + +static struct usb2_fifo_methods uhid_fifo_methods = { + .f_open = &uhid_open, + .f_close = &uhid_close, + .f_ioctl = &uhid_ioctl, + .f_start_read = &uhid_start_read, + .f_stop_read = &uhid_stop_read, + .f_start_write = &uhid_start_write, + .f_stop_write = &uhid_stop_write, + .basename[0] = "uhid", +}; + +static void +uhid_intr_callback(struct usb2_xfer *xfer) +{ + struct uhid_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("transferred!\n"); + + if (xfer->actlen >= sc->sc_isize) { + usb2_fifo_put_data( + sc->sc_fifo.fp[USB_FIFO_RX], + xfer->frbuffers, + 0, sc->sc_isize, 1); + } else { + /* ignore it */ + DPRINTF("ignored short transfer, " + "%d bytes\n", xfer->actlen); + } + + case USB_ST_SETUP: + if (sc->sc_flags & UHID_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[1]); + } else { + if (usb2_fifo_put_bytes_max( + sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UHID_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[1]); + } + return; + } +} + +static void +uhid_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uhid_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UHID_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uhid_fill_set_report(struct usb2_device_request *req, uint8_t iface_no, + uint8_t type, uint8_t id, uint16_t size) +{ + req->bmRequestType = UT_WRITE_CLASS_INTERFACE; + req->bRequest = UR_SET_REPORT; + USETW2(req->wValue, type, id); + req->wIndex[0] = iface_no; + req->wIndex[1] = 0; + USETW(req->wLength, size); + return; +} + +static void +uhid_fill_get_report(struct usb2_device_request *req, uint8_t iface_no, + uint8_t type, uint8_t id, uint16_t size) +{ + req->bmRequestType = UT_READ_CLASS_INTERFACE; + req->bRequest = UR_GET_REPORT; + USETW2(req->wValue, type, id); + req->wIndex[0] = iface_no; + req->wIndex[1] = 0; + USETW(req->wLength, size); + return; +} + +static void +uhid_write_callback(struct usb2_xfer *xfer) +{ + struct uhid_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + uint32_t size = sc->sc_osize; + uint32_t actlen; + uint8_t id; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: + /* try to extract the ID byte */ + if (sc->sc_oid) { + + if (usb2_fifo_get_data( + sc->sc_fifo.fp[USB_FIFO_TX], + xfer->frbuffers, + 0, 1, &actlen, 0)) { + if (actlen != 1) { + goto tr_error; + } + usb2_copy_out(xfer->frbuffers, 0, &id, 1); + + } else { + return; + } + if (size) { + size--; + } + } else { + id = 0; + } + + if (usb2_fifo_get_data( + sc->sc_fifo.fp[USB_FIFO_TX], + xfer->frbuffers + 1, + 0, UHID_BSIZE, &actlen, 1)) { + if (actlen != size) { + goto tr_error; + } + uhid_fill_set_report + (&req, sc->sc_iface_no, + UHID_OUTPUT_REPORT, id, size); + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = size; + xfer->nframes = xfer->frlengths[1] ? 2 : 1; + usb2_start_hardware(xfer); + } + return; + + default: +tr_error: + /* bomb out */ + usb2_fifo_get_data_error(sc->sc_fifo.fp[USB_FIFO_TX]); + return; + } +} + +static void +uhid_read_callback(struct usb2_xfer *xfer) +{ + struct uhid_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], xfer->frbuffers, + sizeof(req), sc->sc_isize, 1); + return; + + case USB_ST_SETUP: + + if (usb2_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) > 0) { + + uhid_fill_get_report + (&req, sc->sc_iface_no, UHID_INPUT_REPORT, + sc->sc_iid, sc->sc_isize); + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = sc->sc_isize; + xfer->nframes = xfer->frlengths[1] ? 2 : 1; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + /* bomb out */ + usb2_fifo_put_data_error(sc->sc_fifo.fp[USB_FIFO_RX]); + return; + } +} + +static const struct usb2_config uhid_config[UHID_N_TRANSFER] = { + + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &uhid_intr_callback, + }, + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uhid_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request) + UHID_BSIZE, + .mh.callback = &uhid_write_callback, + .mh.timeout = 1000, /* 1 second */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request) + UHID_BSIZE, + .mh.callback = &uhid_read_callback, + .mh.timeout = 1000, /* 1 second */ + }, +}; + +static void +uhid_start_read(struct usb2_fifo *fifo) +{ + struct uhid_softc *sc = fifo->priv_sc0; + + if (sc->sc_flags & UHID_FLAG_IMMED) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + usb2_transfer_start(sc->sc_xfer[0]); + } + return; +} + +static void +uhid_stop_read(struct usb2_fifo *fifo) +{ + struct uhid_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static void +uhid_start_write(struct usb2_fifo *fifo) +{ + struct uhid_softc *sc = fifo->priv_sc0; + + usb2_transfer_start(sc->sc_xfer[2]); + return; +} + +static void +uhid_stop_write(struct usb2_fifo *fifo) +{ + struct uhid_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[2]); + return; +} + +static int +uhid_get_report(struct uhid_softc *sc, uint8_t type, + uint8_t id, void *kern_data, void *user_data, + uint16_t len) +{ + int err; + uint8_t free_data = 0; + + if (kern_data == NULL) { + kern_data = malloc(len, M_USBDEV, M_WAITOK); + if (kern_data == NULL) { + err = ENOMEM; + goto done; + } + free_data = 1; + } + err = usb2_req_get_report(sc->sc_udev, NULL, kern_data, + len, sc->sc_iface_index, type, id); + if (err) { + err = ENXIO; + goto done; + } + if (user_data) { + /* dummy buffer */ + err = copyout(kern_data, user_data, len); + if (err) { + goto done; + } + } +done: + if (free_data) { + free(kern_data, M_USBDEV); + } + return (err); +} + +static int +uhid_set_report(struct uhid_softc *sc, uint8_t type, + uint8_t id, void *kern_data, void *user_data, + uint16_t len) +{ + int err; + uint8_t free_data = 0; + + if (kern_data == NULL) { + kern_data = malloc(len, M_USBDEV, M_WAITOK); + if (kern_data == NULL) { + err = ENOMEM; + goto done; + } + free_data = 1; + err = copyin(user_data, kern_data, len); + if (err) { + goto done; + } + } + err = usb2_req_set_report(sc->sc_udev, NULL, kern_data, + len, sc->sc_iface_index, type, id); + if (err) { + err = ENXIO; + goto done; + } +done: + if (free_data) { + free(kern_data, M_USBDEV); + } + return (err); +} + +static int +uhid_open(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct uhid_softc *sc = fifo->priv_sc0; + + /* + * The buffers are one byte larger than maximum so that one + * can detect too large read/writes and short transfers: + */ + if (fflags & FREAD) { + /* reset flags */ + sc->sc_flags &= ~UHID_FLAG_IMMED; + + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_isize + 1, UHID_FRAME_NUM)) { + return (ENOMEM); + } + } + if (fflags & FWRITE) { + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_osize + 1, UHID_FRAME_NUM)) { + return (ENOMEM); + } + } + return (0); +} + +static void +uhid_close(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + if (fflags & (FREAD | FWRITE)) { + usb2_fifo_free_buffer(fifo); + } + return; +} + +static int +uhid_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr, + int fflags, struct thread *td) +{ + struct uhid_softc *sc = fifo->priv_sc0; + struct usb2_gen_descriptor *ugd; + uint32_t size; + int error = 0; + uint8_t id; + + switch (cmd) { + case USB_GET_REPORT_DESC: + ugd = addr; + if (sc->sc_repdesc_size > ugd->ugd_maxlen) { + size = ugd->ugd_maxlen; + } else { + size = sc->sc_repdesc_size; + } + ugd->ugd_actlen = size; + error = copyout(sc->sc_repdesc_ptr, ugd->ugd_data, size); + break; + + case USB_SET_IMMED: + if (!(fflags & FREAD)) { + error = EPERM; + break; + } + if (*(int *)addr) { + + /* do a test read */ + + error = uhid_get_report(sc, UHID_INPUT_REPORT, + sc->sc_iid, NULL, NULL, sc->sc_isize); + if (error) { + break; + } + mtx_lock(&sc->sc_mtx); + sc->sc_flags |= UHID_FLAG_IMMED; + mtx_unlock(&sc->sc_mtx); + } else { + mtx_lock(&sc->sc_mtx); + sc->sc_flags &= ~UHID_FLAG_IMMED; + mtx_unlock(&sc->sc_mtx); + } + break; + + case USB_GET_REPORT: + if (!(fflags & FREAD)) { + error = EPERM; + break; + } + ugd = addr; + switch (ugd->ugd_report_type) { + case UHID_INPUT_REPORT: + size = sc->sc_isize; + id = sc->sc_iid; + break; + case UHID_OUTPUT_REPORT: + size = sc->sc_osize; + id = sc->sc_oid; + break; + case UHID_FEATURE_REPORT: + size = sc->sc_fsize; + id = sc->sc_fid; + break; + default: + return (EINVAL); + } + error = uhid_get_report(sc, ugd->ugd_report_type, id, + NULL, ugd->ugd_data, size); + break; + + case USB_SET_REPORT: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + ugd = addr; + switch (ugd->ugd_report_type) { + case UHID_INPUT_REPORT: + size = sc->sc_isize; + id = sc->sc_iid; + break; + case UHID_OUTPUT_REPORT: + size = sc->sc_osize; + id = sc->sc_oid; + break; + case UHID_FEATURE_REPORT: + size = sc->sc_fsize; + id = sc->sc_fid; + break; + default: + return (EINVAL); + } + error = uhid_set_report(sc, ugd->ugd_report_type, id, + NULL, ugd->ugd_data, size); + break; + + case USB_GET_REPORT_ID: + *(int *)addr = 0; /* XXX: we only support reportid 0? */ + break; + + default: + error = EINVAL; + break; + } + return (error); +} + +static int +uhid_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->use_generic == 0) { + /* give Mouse and Keyboard drivers a try first */ + return (ENXIO); + } + if (uaa->info.bInterfaceClass != UICLASS_HID) { + + /* the Xbox 360 gamepad doesn't use the HID class */ + + if ((uaa->info.bInterfaceClass != UICLASS_VENDOR) || + (uaa->info.bInterfaceSubClass != UISUBCLASS_XBOX360_CONTROLLER) || + (uaa->info.bInterfaceProtocol != UIPROTO_XBOX360_GAMEPAD)) { + return (ENXIO); + } + } + if (usb2_test_quirk(uaa, UQ_HID_IGNORE)) { + return (ENXIO); + } + return (0); +} + +static int +uhid_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uhid_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int error = 0; + + DPRINTFN(10, "sc=%p\n", sc); + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "uhid lock", NULL, MTX_DEF | MTX_RECURSE); + + sc->sc_udev = uaa->device; + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = uaa->info.bIfaceIndex; + + error = usb2_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, uhid_config, + UHID_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("error=%s\n", usb2_errstr(error)); + goto detach; + } + if (uaa->info.idVendor == USB_VENDOR_WACOM) { + + /* the report descriptor for the Wacom Graphire is broken */ + + if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE) { + + sc->sc_repdesc_size = sizeof(uhid_graphire_report_descr); + sc->sc_repdesc_ptr = USB_ADD_BYTES(uhid_graphire_report_descr, 0); + sc->sc_flags |= UHID_FLAG_STATIC_DESC; + + } else if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE3_4X5) { + + static uint8_t reportbuf[] = {2, 2, 2}; + + /* + * The Graphire3 needs 0x0202 to be written to + * feature report ID 2 before it'll start + * returning digitizer data. + */ + error = usb2_req_set_report + (uaa->device, &Giant, reportbuf, sizeof(reportbuf), + uaa->info.bIfaceIndex, UHID_FEATURE_REPORT, 2); + + if (error) { + DPRINTF("set report failed, error=%s (ignored)\n", + usb2_errstr(error)); + } + sc->sc_repdesc_size = sizeof(uhid_graphire3_4x5_report_descr); + sc->sc_repdesc_ptr = USB_ADD_BYTES(uhid_graphire3_4x5_report_descr, 0); + sc->sc_flags |= UHID_FLAG_STATIC_DESC; + } + } else if ((uaa->info.bInterfaceClass == UICLASS_VENDOR) && + (uaa->info.bInterfaceSubClass == UISUBCLASS_XBOX360_CONTROLLER) && + (uaa->info.bInterfaceProtocol == UIPROTO_XBOX360_GAMEPAD)) { + + /* the Xbox 360 gamepad has no report descriptor */ + sc->sc_repdesc_size = sizeof(uhid_xb360gp_report_descr); + sc->sc_repdesc_ptr = USB_ADD_BYTES(uhid_xb360gp_report_descr, 0); + sc->sc_flags |= UHID_FLAG_STATIC_DESC; + } + if (sc->sc_repdesc_ptr == NULL) { + + error = usb2_req_get_hid_desc + (uaa->device, &Giant, &sc->sc_repdesc_ptr, + &sc->sc_repdesc_size, M_USBDEV, uaa->info.bIfaceIndex); + + if (error) { + device_printf(dev, "no report descriptor\n"); + goto detach; + } + } + error = usb2_req_set_idle(uaa->device, &Giant, + uaa->info.bIfaceIndex, 0, 0); + + if (error) { + DPRINTF("set idle failed, error=%s (ignored)\n", + usb2_errstr(error)); + } + sc->sc_isize = hid_report_size + (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_input, &sc->sc_iid); + + sc->sc_osize = hid_report_size + (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_output, &sc->sc_oid); + + sc->sc_fsize = hid_report_size + (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_feature, &sc->sc_fid); + + if (sc->sc_isize > UHID_BSIZE) { + DPRINTF("input size is too large, " + "%d bytes (truncating)\n", + sc->sc_isize); + sc->sc_isize = UHID_BSIZE; + } + if (sc->sc_osize > UHID_BSIZE) { + DPRINTF("output size is too large, " + "%d bytes (truncating)\n", + sc->sc_osize); + sc->sc_osize = UHID_BSIZE; + } + if (sc->sc_fsize > UHID_BSIZE) { + DPRINTF("feature size is too large, " + "%d bytes (truncating)\n", + sc->sc_fsize); + sc->sc_fsize = UHID_BSIZE; + } + /* set interface permissions */ + usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &uhid_fifo_methods, &sc->sc_fifo, + unit, 0 - 1, uaa->info.bIfaceIndex); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + uhid_detach(dev); + return (ENOMEM); +} + +static int +uhid_detach(device_t dev) +{ + struct uhid_softc *sc = device_get_softc(dev); + + usb2_fifo_detach(&sc->sc_fifo); + + usb2_transfer_unsetup(sc->sc_xfer, UHID_N_TRANSFER); + + if (sc->sc_repdesc_ptr) { + if (!(sc->sc_flags & UHID_FLAG_STATIC_DESC)) { + free(sc->sc_repdesc_ptr, M_USBDEV); + } + } + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static devclass_t uhid_devclass; + +static device_method_t uhid_methods[] = { + DEVMETHOD(device_probe, uhid_probe), + DEVMETHOD(device_attach, uhid_attach), + DEVMETHOD(device_detach, uhid_detach), + {0, 0} +}; + +static driver_t uhid_driver = { + .name = "uhid", + .methods = uhid_methods, + .size = sizeof(struct uhid_softc), +}; + +DRIVER_MODULE(uhid, ushub, uhid_driver, uhid_devclass, NULL, 0); +MODULE_DEPEND(uhid, usb2_input, 1, 1, 1); +MODULE_DEPEND(uhid, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/input/ukbd2.c b/sys/dev/usb2/input/ukbd2.c new file mode 100644 index 000000000000..ed7546558abf --- /dev/null +++ b/sys/dev/usb2/input/ukbd2.c @@ -0,0 +1,1503 @@ +#include +__FBSDID("$FreeBSD$"); + + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + * + */ + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +#include "opt_compat.h" +#include "opt_kbd.h" +#include "opt_ukbd.h" + +#include +#include +#include +#include + +#define USB_DEBUG_VAR ukbd_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +/* the initial key map, accent map and fkey strings */ +#if defined(UKBD_DFLT_KEYMAP) && !defined(KLD_MODULE) +#define KBD_DFLT_KEYMAP +#include "ukbdmap.h" +#endif + +/* the following file must be included after "ukbdmap.h" */ +#include + +#if USB_DEBUG +static int ukbd_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ukbd, CTLFLAG_RW, 0, "USB ukbd"); +SYSCTL_INT(_hw_usb2_ukbd, OID_AUTO, debug, CTLFLAG_RW, + &ukbd_debug, 0, "Debug level"); +#endif + +#define UPROTO_BOOT_KEYBOARD 1 + +#define UKBD_EMULATE_ATSCANCODE 1 +#define UKBD_DRIVER_NAME "ukbd" +#define UKBD_NMOD 8 /* units */ +#define UKBD_NKEYCODE 6 /* units */ +#define UKBD_N_TRANSFER 3 /* units */ +#define UKBD_IN_BUF_SIZE (2*(UKBD_NMOD + (2*UKBD_NKEYCODE))) /* bytes */ +#define UKBD_IN_BUF_FULL (UKBD_IN_BUF_SIZE / 2) /* bytes */ +#define UKBD_NFKEY (sizeof(fkey_tab)/sizeof(fkey_tab[0])) /* units */ + +struct ukbd_data { + uint8_t modifiers; +#define MOD_CONTROL_L 0x01 +#define MOD_CONTROL_R 0x10 +#define MOD_SHIFT_L 0x02 +#define MOD_SHIFT_R 0x20 +#define MOD_ALT_L 0x04 +#define MOD_ALT_R 0x40 +#define MOD_WIN_L 0x08 +#define MOD_WIN_R 0x80 + uint8_t reserved; + uint8_t keycode[UKBD_NKEYCODE]; +} __packed; + +struct ukbd_softc { + keyboard_t sc_kbd; + keymap_t sc_keymap; + accentmap_t sc_accmap; + fkeytab_t sc_fkeymap[UKBD_NFKEY]; + struct usb2_callout sc_callout; + struct ukbd_data sc_ndata; + struct ukbd_data sc_odata; + + struct usb2_device *sc_udev; + struct usb2_interface *sc_iface; + struct usb2_xfer *sc_xfer[UKBD_N_TRANSFER]; + + uint32_t sc_ntime[UKBD_NKEYCODE]; + uint32_t sc_otime[UKBD_NKEYCODE]; + uint32_t sc_input[UKBD_IN_BUF_SIZE]; /* input buffer */ + uint32_t sc_time_ms; + uint32_t sc_composed_char; /* composed char code, if non-zero */ +#ifdef UKBD_EMULATE_ATSCANCODE + uint32_t sc_buffered_char[2]; +#endif + uint32_t sc_flags; /* flags */ +#define UKBD_FLAG_COMPOSE 0x0001 +#define UKBD_FLAG_POLLING 0x0002 +#define UKBD_FLAG_SET_LEDS 0x0004 +#define UKBD_FLAG_INTR_STALL 0x0008 +#define UKBD_FLAG_ATTACHED 0x0010 +#define UKBD_FLAG_GONE 0x0020 + + int32_t sc_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */ + int32_t sc_state; /* shift/lock key state */ + int32_t sc_accents; /* accent key index (> 0) */ + + uint16_t sc_inputs; + uint16_t sc_inputhead; + uint16_t sc_inputtail; + + uint8_t sc_leds; /* store for async led requests */ + uint8_t sc_iface_index; + uint8_t sc_iface_no; +}; + +#define KEY_ERROR 0x01 + +#define KEY_PRESS 0 +#define KEY_RELEASE 0x400 +#define KEY_INDEX(c) ((c) & 0xFF) + +#define SCAN_PRESS 0 +#define SCAN_RELEASE 0x80 +#define SCAN_PREFIX_E0 0x100 +#define SCAN_PREFIX_E1 0x200 +#define SCAN_PREFIX_CTL 0x400 +#define SCAN_PREFIX_SHIFT 0x800 +#define SCAN_PREFIX (SCAN_PREFIX_E0 | SCAN_PREFIX_E1 | \ + SCAN_PREFIX_CTL | SCAN_PREFIX_SHIFT) +#define SCAN_CHAR(c) ((c) & 0x7f) + +struct ukbd_mods { + uint32_t mask, key; +}; + +static const struct ukbd_mods ukbd_mods[UKBD_NMOD] = { + {MOD_CONTROL_L, 0xe0}, + {MOD_CONTROL_R, 0xe4}, + {MOD_SHIFT_L, 0xe1}, + {MOD_SHIFT_R, 0xe5}, + {MOD_ALT_L, 0xe2}, + {MOD_ALT_R, 0xe6}, + {MOD_WIN_L, 0xe3}, + {MOD_WIN_R, 0xe7}, +}; + +#define NN 0 /* no translation */ +/* + * Translate USB keycodes to AT keyboard scancodes. + */ +/* + * FIXME: Mac USB keyboard generates: + * 0x53: keypad NumLock/Clear + * 0x66: Power + * 0x67: keypad = + * 0x68: F13 + * 0x69: F14 + * 0x6a: F15 + */ +static const uint8_t ukbd_trtab[256] = { + 0, 0, 0, 0, 30, 48, 46, 32, /* 00 - 07 */ + 18, 33, 34, 35, 23, 36, 37, 38, /* 08 - 0F */ + 50, 49, 24, 25, 16, 19, 31, 20, /* 10 - 17 */ + 22, 47, 17, 45, 21, 44, 2, 3, /* 18 - 1F */ + 4, 5, 6, 7, 8, 9, 10, 11, /* 20 - 27 */ + 28, 1, 14, 15, 57, 12, 13, 26, /* 28 - 2F */ + 27, 43, 43, 39, 40, 41, 51, 52, /* 30 - 37 */ + 53, 58, 59, 60, 61, 62, 63, 64, /* 38 - 3F */ + 65, 66, 67, 68, 87, 88, 92, 70, /* 40 - 47 */ + 104, 102, 94, 96, 103, 99, 101, 98, /* 48 - 4F */ + 97, 100, 95, 69, 91, 55, 74, 78,/* 50 - 57 */ + 89, 79, 80, 81, 75, 76, 77, 71, /* 58 - 5F */ + 72, 73, 82, 83, 86, 107, 122, NN, /* 60 - 67 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 68 - 6F */ + NN, NN, NN, NN, 115, 108, 111, 113, /* 70 - 77 */ + 109, 110, 112, 118, 114, 116, 117, 119, /* 78 - 7F */ + 121, 120, NN, NN, NN, NN, NN, 115, /* 80 - 87 */ + 112, 125, 121, 123, NN, NN, NN, NN, /* 88 - 8F */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 90 - 97 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 98 - 9F */ + NN, NN, NN, NN, NN, NN, NN, NN, /* A0 - A7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* A8 - AF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* B0 - B7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* B8 - BF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* C0 - C7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* C8 - CF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* D0 - D7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* D8 - DF */ + 29, 42, 56, 105, 90, 54, 93, 106, /* E0 - E7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* E8 - EF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* F0 - F7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* F8 - FF */ +}; + +/* prototypes */ +static void ukbd_timeout(void *arg); +static void ukbd_set_leds(struct ukbd_softc *sc, uint8_t leds); +static int ukbd_set_typematic(keyboard_t *kbd, int code); + +#ifdef UKBD_EMULATE_ATSCANCODE +static int +ukbd_key2scan(struct ukbd_softc *sc, int keycode, + int shift, int up); + +#endif +static uint32_t ukbd_read_char(keyboard_t *kbd, int wait); +static void ukbd_clear_state(keyboard_t *kbd); +static int ukbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg); +static int ukbd_enable(keyboard_t *kbd); +static int ukbd_disable(keyboard_t *kbd); +static void ukbd_interrupt(struct ukbd_softc *sc); + +static device_probe_t ukbd_probe; +static device_attach_t ukbd_attach; +static device_detach_t ukbd_detach; +static device_resume_t ukbd_resume; + +static void +ukbd_put_key(struct ukbd_softc *sc, uint32_t key) +{ + mtx_assert(&Giant, MA_OWNED); + + DPRINTF("0x%02x (%d) %s\n", key, key, + (key & KEY_RELEASE) ? "released" : "pressed"); + + if (sc->sc_inputs < UKBD_IN_BUF_SIZE) { + sc->sc_input[sc->sc_inputtail] = key; + ++(sc->sc_inputs); + ++(sc->sc_inputtail); + if (sc->sc_inputtail >= UKBD_IN_BUF_SIZE) { + sc->sc_inputtail = 0; + } + } else { + DPRINTF("input buffer is full\n"); + } + return; +} + +static int32_t +ukbd_get_key(struct ukbd_softc *sc, uint8_t wait) +{ + int32_t c; + + mtx_assert(&Giant, MA_OWNED); + + if (sc->sc_inputs == 0) { + /* start transfer, if not already started */ + usb2_transfer_start(sc->sc_xfer[0]); + } + if (sc->sc_flags & UKBD_FLAG_POLLING) { + DPRINTFN(2, "polling\n"); + + while (sc->sc_inputs == 0) { + + usb2_do_poll(sc->sc_xfer, UKBD_N_TRANSFER); + + DELAY(1000); /* delay 1 ms */ + + sc->sc_time_ms++; + + /* support repetition of keys: */ + + ukbd_interrupt(sc); + + if (!wait) { + break; + } + } + } + if (sc->sc_inputs == 0) { + c = -1; + } else { + c = sc->sc_input[sc->sc_inputhead]; + --(sc->sc_inputs); + ++(sc->sc_inputhead); + if (sc->sc_inputhead >= UKBD_IN_BUF_SIZE) { + sc->sc_inputhead = 0; + } + } + return (c); +} + +static void +ukbd_interrupt(struct ukbd_softc *sc) +{ + uint32_t n_mod; + uint32_t o_mod; + uint32_t now = sc->sc_time_ms; + uint32_t dtime; + uint32_t c; + uint8_t key; + uint8_t i; + uint8_t j; + + if (sc->sc_ndata.keycode[0] == KEY_ERROR) { + goto done; + } + n_mod = sc->sc_ndata.modifiers; + o_mod = sc->sc_odata.modifiers; + if (n_mod != o_mod) { + for (i = 0; i < UKBD_NMOD; i++) { + if ((n_mod & ukbd_mods[i].mask) != + (o_mod & ukbd_mods[i].mask)) { + ukbd_put_key(sc, ukbd_mods[i].key | + ((n_mod & ukbd_mods[i].mask) ? + KEY_PRESS : KEY_RELEASE)); + } + } + } + /* Check for released keys. */ + for (i = 0; i < UKBD_NKEYCODE; i++) { + key = sc->sc_odata.keycode[i]; + if (key == 0) { + continue; + } + for (j = 0; j < UKBD_NKEYCODE; j++) { + if (sc->sc_ndata.keycode[j] == 0) { + continue; + } + if (key == sc->sc_ndata.keycode[j]) { + goto rfound; + } + } + ukbd_put_key(sc, key | KEY_RELEASE); +rfound: ; + } + + /* Check for pressed keys. */ + for (i = 0; i < UKBD_NKEYCODE; i++) { + key = sc->sc_ndata.keycode[i]; + if (key == 0) { + continue; + } + sc->sc_ntime[i] = now + sc->sc_kbd.kb_delay1; + for (j = 0; j < UKBD_NKEYCODE; j++) { + if (sc->sc_odata.keycode[j] == 0) { + continue; + } + if (key == sc->sc_odata.keycode[j]) { + + /* key is still pressed */ + + sc->sc_ntime[i] = sc->sc_otime[j]; + dtime = (sc->sc_otime[j] - now); + + if (!(dtime & 0x80000000)) { + /* time has not elapsed */ + goto pfound; + } + sc->sc_ntime[i] = now + sc->sc_kbd.kb_delay2; + break; + } + } + ukbd_put_key(sc, key | KEY_PRESS); + + /* + * If any other key is presently down, force its repeat to be + * well in the future (100s). This makes the last key to be + * pressed do the autorepeat. + */ + for (j = 0; j != UKBD_NKEYCODE; j++) { + if (j != i) + sc->sc_ntime[j] = now + (100 * 1000); + } +pfound: ; + } + + sc->sc_odata = sc->sc_ndata; + + bcopy(sc->sc_ntime, sc->sc_otime, sizeof(sc->sc_otime)); + + if (sc->sc_inputs == 0) { + goto done; + } + if (sc->sc_flags & UKBD_FLAG_POLLING) { + goto done; + } + if (KBD_IS_ACTIVE(&sc->sc_kbd) && + KBD_IS_BUSY(&sc->sc_kbd)) { + /* let the callback function process the input */ + (sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT, + sc->sc_kbd.kb_callback.kc_arg); + } else { + /* read and discard the input, no one is waiting for it */ + do { + c = ukbd_read_char(&sc->sc_kbd, 0); + } while (c != NOKEY); + } +done: + return; +} + +static void +ukbd_timeout(void *arg) +{ + struct ukbd_softc *sc = arg; + + mtx_assert(&Giant, MA_OWNED); + + if (!(sc->sc_flags & UKBD_FLAG_POLLING)) { + sc->sc_time_ms += 25; /* milliseconds */ + } + ukbd_interrupt(sc); + + usb2_callout_reset(&sc->sc_callout, hz / 40, &ukbd_timeout, sc); + + mtx_unlock(&Giant); + + return; +} + +static void +ukbd_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ukbd_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UKBD_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ukbd_intr_callback(struct usb2_xfer *xfer) +{ + struct ukbd_softc *sc = xfer->priv_sc; + uint16_t len = xfer->actlen; + uint8_t i; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("actlen=%d bytes\n", len); + + if (len > sizeof(sc->sc_ndata)) { + len = sizeof(sc->sc_ndata); + } + if (len) { + bzero(&sc->sc_ndata, sizeof(sc->sc_ndata)); + usb2_copy_out(xfer->frbuffers, 0, &sc->sc_ndata, len); +#if USB_DEBUG + if (sc->sc_ndata.modifiers) { + DPRINTF("mod: 0x%04x\n", sc->sc_ndata.modifiers); + } + for (i = 0; i < UKBD_NKEYCODE; i++) { + if (sc->sc_ndata.keycode[i]) { + DPRINTF("[%d] = %d\n", i, sc->sc_ndata.keycode[i]); + } + } +#endif /* USB_DEBUG */ + ukbd_interrupt(sc); + } + case USB_ST_SETUP: + if (sc->sc_flags & UKBD_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[1]); + return; + } + if (sc->sc_inputs < UKBD_IN_BUF_FULL) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } else { + DPRINTF("input queue is full!\n"); + } + return; + + default: /* Error */ + DPRINTF("error=%s\n", usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UKBD_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[1]); + } + return; + } +} + +static void +ukbd_set_leds_callback(struct usb2_xfer *xfer) +{ + struct usb2_device_request req; + uint8_t buf[1]; + struct ukbd_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: + if (sc->sc_flags & UKBD_FLAG_SET_LEDS) { + sc->sc_flags &= ~UKBD_FLAG_SET_LEDS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, UHID_OUTPUT_REPORT, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + + buf[0] = sc->sc_leds; + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + usb2_copy_in(xfer->frbuffers + 1, 0, buf, sizeof(buf)); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = sizeof(buf); + xfer->nframes = 2; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + DPRINTFN(0, "error=%s\n", usb2_errstr(xfer->error)); + return; + } +} + +static const struct usb2_config ukbd_config[UKBD_N_TRANSFER] = { + + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &ukbd_intr_callback, + }, + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ukbd_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request) + 1, + .mh.callback = &ukbd_set_leds_callback, + .mh.timeout = 1000, /* 1 second */ + }, +}; + +static int +ukbd_probe(device_t dev) +{ + keyboard_switch_t *sw = kbd_get_switch(UKBD_DRIVER_NAME); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (sw == NULL) { + return (ENXIO); + } + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* check that the keyboard speaks the boot protocol: */ + if ((uaa->info.bInterfaceClass == UICLASS_HID) + && (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) + && (uaa->info.bInterfaceProtocol == UPROTO_BOOT_KEYBOARD)) { + if (usb2_test_quirk(uaa, UQ_KBD_IGNORE)) + return (ENXIO); + else + return (0); + } + return (ENXIO); +} + +static int +ukbd_attach(device_t dev) +{ + struct ukbd_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + int32_t unit = device_get_unit(dev); + keyboard_t *kbd = &sc->sc_kbd; + usb2_error_t err; + uint16_t n; + + if (sc == NULL) { + return (ENOMEM); + } + mtx_assert(&Giant, MA_OWNED); + + kbd_init_struct(kbd, UKBD_DRIVER_NAME, KB_OTHER, unit, 0, 0, 0); + + kbd->kb_data = (void *)sc; + + device_set_usb2_desc(dev); + + sc->sc_udev = uaa->device; + sc->sc_iface = uaa->iface; + sc->sc_iface_index = uaa->info.bIfaceIndex; + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_mode = K_XLATE; + sc->sc_iface = uaa->iface; + + usb2_callout_init_mtx(&sc->sc_callout, &Giant, + CALLOUT_RETURNUNLOCKED); + + err = usb2_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, ukbd_config, + UKBD_N_TRANSFER, sc, &Giant); + + if (err) { + DPRINTF("error=%s\n", usb2_errstr(err)); + goto detach; + } + /* setup default keyboard maps */ + + sc->sc_keymap = key_map; + sc->sc_accmap = accent_map; + for (n = 0; n < UKBD_NFKEY; n++) { + sc->sc_fkeymap[n] = fkey_tab[n]; + } + + kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap, + sc->sc_fkeymap, UKBD_NFKEY); + + KBD_FOUND_DEVICE(kbd); + + ukbd_clear_state(kbd); + + /* + * FIXME: set the initial value for lock keys in "sc_state" + * according to the BIOS data? + */ + KBD_PROBE_DONE(kbd); + + /* ignore if SETIDLE fails, hence it is not crucial */ + err = usb2_req_set_idle(sc->sc_udev, &Giant, sc->sc_iface_index, 0, 0); + + ukbd_ioctl(kbd, KDSETLED, (caddr_t)&sc->sc_state); + + KBD_INIT_DONE(kbd); + + if (kbd_register(kbd) < 0) { + goto detach; + } + KBD_CONFIG_DONE(kbd); + + ukbd_enable(kbd); + +#ifdef KBD_INSTALL_CDEV + if (kbd_attach(kbd)) { + goto detach; + } +#endif + sc->sc_flags |= UKBD_FLAG_ATTACHED; + + if (bootverbose) { + genkbd_diag(kbd, bootverbose); + } + /* lock keyboard mutex */ + + mtx_lock(&Giant); + + /* start the keyboard */ + + usb2_transfer_start(sc->sc_xfer[0]); + + /* start the timer */ + + ukbd_timeout(sc); /* will unlock mutex */ + + return (0); /* success */ + +detach: + ukbd_detach(dev); + return (ENXIO); /* error */ +} + +int +ukbd_detach(device_t dev) +{ + struct ukbd_softc *sc = device_get_softc(dev); + int error; + + mtx_assert(&Giant, MA_OWNED); + + DPRINTF("\n"); + + if (sc->sc_flags & UKBD_FLAG_POLLING) { + panic("cannot detach polled keyboard!\n"); + } + sc->sc_flags |= UKBD_FLAG_GONE; + + usb2_callout_stop(&sc->sc_callout); + + ukbd_disable(&sc->sc_kbd); + +#ifdef KBD_INSTALL_CDEV + if (sc->sc_flags & UKBD_FLAG_ATTACHED) { + error = kbd_detach(&sc->sc_kbd); + if (error) { + /* usb attach cannot return an error */ + device_printf(dev, "WARNING: kbd_detach() " + "returned non-zero! (ignored)\n"); + } + } +#endif + if (KBD_IS_CONFIGURED(&sc->sc_kbd)) { + error = kbd_unregister(&sc->sc_kbd); + if (error) { + /* usb attach cannot return an error */ + device_printf(dev, "WARNING: kbd_unregister() " + "returned non-zero! (ignored)\n"); + } + } + sc->sc_kbd.kb_flags = 0; + + usb2_transfer_unsetup(sc->sc_xfer, UKBD_N_TRANSFER); + + usb2_callout_drain(&sc->sc_callout); + + DPRINTF("%s: disconnected\n", + device_get_nameunit(dev)); + + return (0); +} + +static int +ukbd_resume(device_t dev) +{ + struct ukbd_softc *sc = device_get_softc(dev); + + mtx_assert(&Giant, MA_OWNED); + + ukbd_clear_state(&sc->sc_kbd); + + return (0); +} + +/* early keyboard probe, not supported */ +static int +ukbd_configure(int flags) +{ + return (0); +} + +/* detect a keyboard, not used */ +static int +ukbd__probe(int unit, void *arg, int flags) +{ + mtx_assert(&Giant, MA_OWNED); + return (ENXIO); +} + +/* reset and initialize the device, not used */ +static int +ukbd_init(int unit, keyboard_t **kbdp, void *arg, int flags) +{ + mtx_assert(&Giant, MA_OWNED); + return (ENXIO); +} + +/* test the interface to the device, not used */ +static int +ukbd_test_if(keyboard_t *kbd) +{ + mtx_assert(&Giant, MA_OWNED); + return (0); +} + +/* finish using this keyboard, not used */ +static int +ukbd_term(keyboard_t *kbd) +{ + mtx_assert(&Giant, MA_OWNED); + return (ENXIO); +} + +/* keyboard interrupt routine, not used */ +static int +ukbd_intr(keyboard_t *kbd, void *arg) +{ + mtx_assert(&Giant, MA_OWNED); + return (0); +} + +/* lock the access to the keyboard, not used */ +static int +ukbd_lock(keyboard_t *kbd, int lock) +{ + mtx_assert(&Giant, MA_OWNED); + return (1); +} + +/* + * Enable the access to the device; until this function is called, + * the client cannot read from the keyboard. + */ +static int +ukbd_enable(keyboard_t *kbd) +{ + mtx_assert(&Giant, MA_OWNED); + KBD_ACTIVATE(kbd); + return (0); +} + +/* disallow the access to the device */ +static int +ukbd_disable(keyboard_t *kbd) +{ + mtx_assert(&Giant, MA_OWNED); + KBD_DEACTIVATE(kbd); + return (0); +} + +/* check if data is waiting */ +static int +ukbd_check(keyboard_t *kbd) +{ + struct ukbd_softc *sc = kbd->kb_data; + + if (!mtx_owned(&Giant)) { + return (0); /* XXX */ + } + mtx_assert(&Giant, MA_OWNED); + + if (!KBD_IS_ACTIVE(kbd)) { + return (0); + } +#ifdef UKBD_EMULATE_ATSCANCODE + if (sc->sc_buffered_char[0]) { + return (1); + } +#endif + if (sc->sc_inputs > 0) { + return (1); + } + return (0); +} + +/* check if char is waiting */ +static int +ukbd_check_char(keyboard_t *kbd) +{ + struct ukbd_softc *sc = kbd->kb_data; + + if (!mtx_owned(&Giant)) { + return (0); /* XXX */ + } + mtx_assert(&Giant, MA_OWNED); + + if (!KBD_IS_ACTIVE(kbd)) { + return (0); + } + if ((sc->sc_composed_char > 0) && + (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) { + return (1); + } + return (ukbd_check(kbd)); +} + + +/* read one byte from the keyboard if it's allowed */ +static int +ukbd_read(keyboard_t *kbd, int wait) +{ + struct ukbd_softc *sc = kbd->kb_data; + int32_t usbcode; + +#ifdef UKBD_EMULATE_ATSCANCODE + uint32_t keycode; + uint32_t scancode; + +#endif + + if (!mtx_owned(&Giant)) { + return -1; /* XXX */ + } + mtx_assert(&Giant, MA_OWNED); + +#ifdef UKBD_EMULATE_ATSCANCODE + if (sc->sc_buffered_char[0]) { + scancode = sc->sc_buffered_char[0]; + if (scancode & SCAN_PREFIX) { + sc->sc_buffered_char[0] &= ~SCAN_PREFIX; + return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } + sc->sc_buffered_char[0] = sc->sc_buffered_char[1]; + sc->sc_buffered_char[1] = 0; + return (scancode); + } +#endif /* UKBD_EMULATE_ATSCANCODE */ + + /* XXX */ + usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1); + if (!KBD_IS_ACTIVE(kbd) || (usbcode == -1)) { + return -1; + } + ++(kbd->kb_count); + +#ifdef UKBD_EMULATE_ATSCANCODE + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) { + return -1; + } + return (ukbd_key2scan(sc, keycode, sc->sc_ndata.modifiers, + (usbcode & KEY_RELEASE))); +#else /* !UKBD_EMULATE_ATSCANCODE */ + return (usbcode); +#endif /* UKBD_EMULATE_ATSCANCODE */ +} + +/* read char from the keyboard */ +static uint32_t +ukbd_read_char(keyboard_t *kbd, int wait) +{ + struct ukbd_softc *sc = kbd->kb_data; + uint32_t action; + uint32_t keycode; + int32_t usbcode; + +#ifdef UKBD_EMULATE_ATSCANCODE + uint32_t scancode; + +#endif + if (!mtx_owned(&Giant)) { + return (NOKEY); /* XXX */ + } + mtx_assert(&Giant, MA_OWNED); + +next_code: + + /* do we have a composed char to return ? */ + + if ((sc->sc_composed_char > 0) && + (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) { + + action = sc->sc_composed_char; + sc->sc_composed_char = 0; + + if (action > 0xFF) { + goto errkey; + } + goto done; + } +#ifdef UKBD_EMULATE_ATSCANCODE + + /* do we have a pending raw scan code? */ + + if (sc->sc_mode == K_RAW) { + scancode = sc->sc_buffered_char[0]; + if (scancode) { + if (scancode & SCAN_PREFIX) { + sc->sc_buffered_char[0] = (scancode & ~SCAN_PREFIX); + return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } + sc->sc_buffered_char[0] = sc->sc_buffered_char[1]; + sc->sc_buffered_char[1] = 0; + return (scancode); + } + } +#endif /* UKBD_EMULATE_ATSCANCODE */ + + /* see if there is something in the keyboard port */ + /* XXX */ + usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1); + if (usbcode == -1) { + return (NOKEY); + } + ++kbd->kb_count; + +#ifdef UKBD_EMULATE_ATSCANCODE + /* USB key index -> key code -> AT scan code */ + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) { + return (NOKEY); + } + /* return an AT scan code for the K_RAW mode */ + if (sc->sc_mode == K_RAW) { + return (ukbd_key2scan(sc, keycode, sc->sc_ndata.modifiers, + (usbcode & KEY_RELEASE))); + } +#else /* !UKBD_EMULATE_ATSCANCODE */ + + /* return the byte as is for the K_RAW mode */ + if (sc->sc_mode == K_RAW) { + return (usbcode); + } + /* USB key index -> key code */ + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) { + return (NOKEY); + } +#endif /* UKBD_EMULATE_ATSCANCODE */ + + switch (keycode) { + case 0x38: /* left alt (compose key) */ + if (usbcode & KEY_RELEASE) { + if (sc->sc_flags & UKBD_FLAG_COMPOSE) { + sc->sc_flags &= ~UKBD_FLAG_COMPOSE; + + if (sc->sc_composed_char > 0xFF) { + sc->sc_composed_char = 0; + } + } + } else { + if (!(sc->sc_flags & UKBD_FLAG_COMPOSE)) { + sc->sc_flags |= UKBD_FLAG_COMPOSE; + sc->sc_composed_char = 0; + } + } + break; + /* XXX: I don't like these... */ + case 0x5c: /* print screen */ + if (sc->sc_flags & ALTS) { + keycode = 0x54; /* sysrq */ + } + break; + case 0x68: /* pause/break */ + if (sc->sc_flags & CTLS) { + keycode = 0x6c; /* break */ + } + break; + } + + /* return the key code in the K_CODE mode */ + if (usbcode & KEY_RELEASE) { + keycode |= SCAN_RELEASE; + } + if (sc->sc_mode == K_CODE) { + return (keycode); + } + /* compose a character code */ + if (sc->sc_flags & UKBD_FLAG_COMPOSE) { + switch (keycode) { + /* key pressed, process it */ + case 0x47: + case 0x48: + case 0x49: /* keypad 7,8,9 */ + sc->sc_composed_char *= 10; + sc->sc_composed_char += keycode - 0x40; + goto check_composed; + + case 0x4B: + case 0x4C: + case 0x4D: /* keypad 4,5,6 */ + sc->sc_composed_char *= 10; + sc->sc_composed_char += keycode - 0x47; + goto check_composed; + + case 0x4F: + case 0x50: + case 0x51: /* keypad 1,2,3 */ + sc->sc_composed_char *= 10; + sc->sc_composed_char += keycode - 0x4E; + goto check_composed; + + case 0x52: /* keypad 0 */ + sc->sc_composed_char *= 10; + goto check_composed; + + /* key released, no interest here */ + case SCAN_RELEASE | 0x47: + case SCAN_RELEASE | 0x48: + case SCAN_RELEASE | 0x49: /* keypad 7,8,9 */ + case SCAN_RELEASE | 0x4B: + case SCAN_RELEASE | 0x4C: + case SCAN_RELEASE | 0x4D: /* keypad 4,5,6 */ + case SCAN_RELEASE | 0x4F: + case SCAN_RELEASE | 0x50: + case SCAN_RELEASE | 0x51: /* keypad 1,2,3 */ + case SCAN_RELEASE | 0x52: /* keypad 0 */ + goto next_code; + + case 0x38: /* left alt key */ + break; + + default: + if (sc->sc_composed_char > 0) { + sc->sc_flags &= ~UKBD_FLAG_COMPOSE; + sc->sc_composed_char = 0; + goto errkey; + } + break; + } + } + /* keycode to key action */ + action = genkbd_keyaction(kbd, SCAN_CHAR(keycode), + (keycode & SCAN_RELEASE), + &sc->sc_state, &sc->sc_accents); + if (action == NOKEY) { + goto next_code; + } +done: + return (action); + +check_composed: + if (sc->sc_composed_char <= 0xFF) { + goto next_code; + } +errkey: + return (ERRKEY); +} + +/* some useful control functions */ +static int +ukbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) +{ + /* translate LED_XXX bits into the device specific bits */ + static const uint8_t ledmap[8] = { + 0, 2, 1, 3, 4, 6, 5, 7, + }; + struct ukbd_softc *sc = kbd->kb_data; + int i; + +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + int ival; + +#endif + if (!mtx_owned(&Giant)) { + /* + * XXX big problem: If scroll lock is pressed and "printf()" + * is called, the CPU will get here, to un-scroll lock the + * keyboard. But if "printf()" acquires the "Giant" lock, + * there will be a locking order reversal problem, so the + * keyboard system must get out of "Giant" first, before the + * CPU can proceed here ... + */ + return (EINVAL); + } + mtx_assert(&Giant, MA_OWNED); + + switch (cmd) { + case KDGKBMODE: /* get keyboard mode */ + *(int *)arg = sc->sc_mode; + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 7): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSKBMODE: /* set keyboard mode */ + switch (*(int *)arg) { + case K_XLATE: + if (sc->sc_mode != K_XLATE) { + /* make lock key state and LED state match */ + sc->sc_state &= ~LOCK_MASK; + sc->sc_state |= KBD_LED_VAL(kbd); + } + /* FALLTHROUGH */ + case K_RAW: + case K_CODE: + if (sc->sc_mode != *(int *)arg) { + ukbd_clear_state(kbd); + sc->sc_mode = *(int *)arg; + } + break; + default: + return (EINVAL); + } + break; + + case KDGETLED: /* get keyboard LED */ + *(int *)arg = KBD_LED_VAL(kbd); + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 66): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSETLED: /* set keyboard LED */ + /* NOTE: lock key state in "sc_state" won't be changed */ + if (*(int *)arg & ~LOCK_MASK) { + return (EINVAL); + } + i = *(int *)arg; + /* replace CAPS LED with ALTGR LED for ALTGR keyboards */ + if (sc->sc_mode == K_XLATE && + kbd->kb_keymap->n_keys > ALTGR_OFFSET) { + if (i & ALKED) + i |= CLKED; + else + i &= ~CLKED; + } + if (KBD_HAS_DEVICE(kbd)) { + ukbd_set_leds(sc, ledmap[i & LED_MASK]); + } + KBD_LED_VAL(kbd) = *(int *)arg; + break; + case KDGKBSTATE: /* get lock key state */ + *(int *)arg = sc->sc_state & LOCK_MASK; + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 20): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSKBSTATE: /* set lock key state */ + if (*(int *)arg & ~LOCK_MASK) { + return (EINVAL); + } + sc->sc_state &= ~LOCK_MASK; + sc->sc_state |= *(int *)arg; + + /* set LEDs and quit */ + return (ukbd_ioctl(kbd, KDSETLED, arg)); + + case KDSETREPEAT: /* set keyboard repeat rate (new + * interface) */ + if (!KBD_HAS_DEVICE(kbd)) { + return (0); + } + if (((int *)arg)[1] < 0) { + return (EINVAL); + } + if (((int *)arg)[0] < 0) { + return (EINVAL); + } + if (((int *)arg)[0] < 200) /* fastest possible value */ + kbd->kb_delay1 = 200; + else + kbd->kb_delay1 = ((int *)arg)[0]; + kbd->kb_delay2 = ((int *)arg)[1]; + return (0); + +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 67): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSETRAD: /* set keyboard repeat rate (old + * interface) */ + return (ukbd_set_typematic(kbd, *(int *)arg)); + + case PIO_KEYMAP: /* set keyboard translation table */ + case PIO_KEYMAPENT: /* set keyboard translation table + * entry */ + case PIO_DEADKEYMAP: /* set accent key translation table */ + sc->sc_accents = 0; + /* FALLTHROUGH */ + default: + return (genkbd_commonioctl(kbd, cmd, arg)); + } + + return (0); +} + +/* clear the internal state of the keyboard */ +static void +ukbd_clear_state(keyboard_t *kbd) +{ + struct ukbd_softc *sc = kbd->kb_data; + + if (!mtx_owned(&Giant)) { + return; /* XXX */ + } + mtx_assert(&Giant, MA_OWNED); + + sc->sc_flags &= ~(UKBD_FLAG_COMPOSE | UKBD_FLAG_POLLING); + sc->sc_state &= LOCK_MASK; /* preserve locking key state */ + sc->sc_accents = 0; + sc->sc_composed_char = 0; +#ifdef UKBD_EMULATE_ATSCANCODE + sc->sc_buffered_char[0] = 0; + sc->sc_buffered_char[1] = 0; +#endif + bzero(&sc->sc_ndata, sizeof(sc->sc_ndata)); + bzero(&sc->sc_odata, sizeof(sc->sc_odata)); + bzero(&sc->sc_ntime, sizeof(sc->sc_ntime)); + bzero(&sc->sc_otime, sizeof(sc->sc_otime)); + return; +} + +/* save the internal state, not used */ +static int +ukbd_get_state(keyboard_t *kbd, void *buf, size_t len) +{ + mtx_assert(&Giant, MA_OWNED); + return (len == 0) ? 1 : -1; +} + +/* set the internal state, not used */ +static int +ukbd_set_state(keyboard_t *kbd, void *buf, size_t len) +{ + mtx_assert(&Giant, MA_OWNED); + return (EINVAL); +} + +static int +ukbd_poll(keyboard_t *kbd, int on) +{ + struct ukbd_softc *sc = kbd->kb_data; + + if (!mtx_owned(&Giant)) { + return (0); /* XXX */ + } + mtx_assert(&Giant, MA_OWNED); + + if (on) { + sc->sc_flags |= UKBD_FLAG_POLLING; + } else { + sc->sc_flags &= ~UKBD_FLAG_POLLING; + } + return (0); +} + +/* local functions */ + +static void +ukbd_set_leds(struct ukbd_softc *sc, uint8_t leds) +{ + DPRINTF("leds=0x%02x\n", leds); + + sc->sc_leds = leds; + sc->sc_flags |= UKBD_FLAG_SET_LEDS; + + /* start transfer, if not already started */ + + usb2_transfer_start(sc->sc_xfer[2]); + + return; +} + +static int +ukbd_set_typematic(keyboard_t *kbd, int code) +{ + static const int delays[] = {250, 500, 750, 1000}; + static const int rates[] = {34, 38, 42, 46, 50, 55, 59, 63, + 68, 76, 84, 92, 100, 110, 118, 126, + 136, 152, 168, 184, 200, 220, 236, 252, + 272, 304, 336, 368, 400, 440, 472, 504}; + + if (code & ~0x7f) { + return (EINVAL); + } + kbd->kb_delay1 = delays[(code >> 5) & 3]; + kbd->kb_delay2 = rates[code & 0x1f]; + return (0); +} + +#ifdef UKBD_EMULATE_ATSCANCODE +static int +ukbd_key2scan(struct ukbd_softc *sc, int code, int shift, int up) +{ + static const int scan[] = { + 0x1c, 0x1d, 0x35, + 0x37 | SCAN_PREFIX_SHIFT, /* PrintScreen */ + 0x38, 0x47, 0x48, 0x49, 0x4b, 0x4d, 0x4f, + 0x50, 0x51, 0x52, 0x53, + 0x46, /* XXX Pause/Break */ + 0x5b, 0x5c, 0x5d, + /* SUN TYPE 6 USB KEYBOARD */ + 0x68, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x25, 0x1f, 0x1e, + 0x20, + }; + + if ((code >= 89) && (code < (89 + (sizeof(scan) / sizeof(scan[0]))))) { + code = scan[code - 89] | SCAN_PREFIX_E0; + } + /* Pause/Break */ + if ((code == 104) && (!(shift & (MOD_CONTROL_L | MOD_CONTROL_R)))) { + code = (0x45 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL); + } + if (shift & (MOD_SHIFT_L | MOD_SHIFT_R)) { + code &= ~SCAN_PREFIX_SHIFT; + } + code |= (up ? SCAN_RELEASE : SCAN_PRESS); + + if (code & SCAN_PREFIX) { + if (code & SCAN_PREFIX_CTL) { + /* Ctrl */ + sc->sc_buffered_char[0] = (0x1d | (code & SCAN_RELEASE)); + sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX); + } else if (code & SCAN_PREFIX_SHIFT) { + /* Shift */ + sc->sc_buffered_char[0] = (0x2a | (code & SCAN_RELEASE)); + sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX_SHIFT); + } else { + sc->sc_buffered_char[0] = (code & ~SCAN_PREFIX); + sc->sc_buffered_char[1] = 0; + } + return ((code & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } + return (code); + +} + +#endif /* UKBD_EMULATE_ATSCANCODE */ + +keyboard_switch_t ukbdsw = { + .probe = &ukbd__probe, + .init = &ukbd_init, + .term = &ukbd_term, + .intr = &ukbd_intr, + .test_if = &ukbd_test_if, + .enable = &ukbd_enable, + .disable = &ukbd_disable, + .read = &ukbd_read, + .check = &ukbd_check, + .read_char = &ukbd_read_char, + .check_char = &ukbd_check_char, + .ioctl = &ukbd_ioctl, + .lock = &ukbd_lock, + .clear_state = &ukbd_clear_state, + .get_state = &ukbd_get_state, + .set_state = &ukbd_set_state, + .get_fkeystr = &genkbd_get_fkeystr, + .poll = &ukbd_poll, + .diag = &genkbd_diag, +}; + +KEYBOARD_DRIVER(ukbd, ukbdsw, ukbd_configure); + +static int +ukbd_driver_load(module_t mod, int what, void *arg) +{ + switch (what) { + case MOD_LOAD: + kbd_add_driver(&ukbd_kbd_driver); + break; + case MOD_UNLOAD: + kbd_delete_driver(&ukbd_kbd_driver); + break; + } + return (0); +} + +static devclass_t ukbd_devclass; + +static device_method_t ukbd_methods[] = { + DEVMETHOD(device_probe, ukbd_probe), + DEVMETHOD(device_attach, ukbd_attach), + DEVMETHOD(device_detach, ukbd_detach), + DEVMETHOD(device_resume, ukbd_resume), + {0, 0} +}; + +static driver_t ukbd_driver = { + .name = "ukbd", + .methods = ukbd_methods, + .size = sizeof(struct ukbd_softc), +}; + +DRIVER_MODULE(ukbd, ushub, ukbd_driver, ukbd_devclass, ukbd_driver_load, 0); +MODULE_DEPEND(ukbd, usb2_input, 1, 1, 1); +MODULE_DEPEND(ukbd, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/input/ums2.c b/sys/dev/usb2/input/ums2.c new file mode 100644 index 000000000000..a04e7c849b0e --- /dev/null +++ b/sys/dev/usb2/input/ums2.c @@ -0,0 +1,911 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR ums_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#if USB_DEBUG +static int ums_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ums, CTLFLAG_RW, 0, "USB ums"); +SYSCTL_INT(_hw_usb2_ums, OID_AUTO, debug, CTLFLAG_RW, + &ums_debug, 0, "Debug level"); +#endif + +#define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE) +#define MOUSE_FLAGS (HIO_RELATIVE) + +#define UMS_BUF_SIZE 8 /* bytes */ +#define UMS_IFQ_MAXLEN 50 /* units */ +#define UMS_N_TRANSFER 2 /* units */ +#define UMS_BUTTON_MAX 31 /* exclusive, must be less than 32 */ +#define UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i)) + +struct ums_softc { + struct usb2_fifo_sc sc_fifo; + struct mtx sc_mtx; + struct usb2_callout sc_callout; + struct hid_location sc_loc_w; + struct hid_location sc_loc_x; + struct hid_location sc_loc_y; + struct hid_location sc_loc_z; + struct hid_location sc_loc_t; + struct hid_location sc_loc_btn[UMS_BUTTON_MAX]; + mousehw_t sc_hw; + mousemode_t sc_mode; + mousestatus_t sc_status; + + struct usb2_xfer *sc_xfer[UMS_N_TRANSFER]; + + uint32_t sc_flags; +#define UMS_FLAG_X_AXIS 0x0001 +#define UMS_FLAG_Y_AXIS 0x0002 +#define UMS_FLAG_Z_AXIS 0x0004 +#define UMS_FLAG_T_AXIS 0x0008 +#define UMS_FLAG_SBU 0x0010 /* spurious button up events */ +#define UMS_FLAG_INTR_STALL 0x0020 /* set if transfer error */ +#define UMS_FLAG_REVZ 0x0040 /* Z-axis is reversed */ +#define UMS_FLAG_W_AXIS 0x0080 + + uint8_t sc_buttons; + uint8_t sc_iid; + uint8_t sc_temp[64]; +}; + +static void ums_put_queue_timeout(void *__sc); + +static usb2_callback_t ums_clear_stall_callback; +static usb2_callback_t ums_intr_callback; + +static device_probe_t ums_probe; +static device_attach_t ums_attach; +static device_detach_t ums_detach; + +static usb2_fifo_cmd_t ums_start_read; +static usb2_fifo_cmd_t ums_stop_read; +static usb2_fifo_open_t ums_open; +static usb2_fifo_close_t ums_close; +static usb2_fifo_ioctl_t ums_ioctl; + +static void ums_put_queue(struct ums_softc *sc, int32_t dx, int32_t dy, int32_t dz, int32_t dt, int32_t buttons); + +static struct usb2_fifo_methods ums_fifo_methods = { + .f_open = &ums_open, + .f_close = &ums_close, + .f_ioctl = &ums_ioctl, + .f_start_read = &ums_start_read, + .f_stop_read = &ums_stop_read, + .basename[0] = "ums", +}; + +static void +ums_put_queue_timeout(void *__sc) +{ + struct ums_softc *sc = __sc; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + ums_put_queue(sc, 0, 0, 0, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +ums_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ums_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UMS_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ums_intr_callback(struct usb2_xfer *xfer) +{ + struct ums_softc *sc = xfer->priv_sc; + uint8_t *buf = sc->sc_temp; + uint16_t len = xfer->actlen; + int32_t buttons = 0; + int32_t dw; + int32_t dx; + int32_t dy; + int32_t dz; + int32_t dt; + uint8_t i; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(6, "sc=%p actlen=%d\n", sc, len); + + if (len > sizeof(sc->sc_temp)) { + DPRINTFN(6, "truncating large packet to %zu bytes\n", + sizeof(sc->sc_temp)); + len = sizeof(sc->sc_temp); + } + if (len == 0) { + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, len); + + DPRINTFN(6, "data = %02x %02x %02x %02x " + "%02x %02x %02x %02x\n", + (len > 0) ? buf[0] : 0, (len > 1) ? buf[1] : 0, + (len > 2) ? buf[2] : 0, (len > 3) ? buf[3] : 0, + (len > 4) ? buf[4] : 0, (len > 5) ? buf[5] : 0, + (len > 6) ? buf[6] : 0, (len > 7) ? buf[7] : 0); + + /* + * The M$ Wireless Intellimouse 2.0 sends 1 extra leading byte + * of data compared to most USB mice. This byte frequently + * switches from 0x01 (usual state) to 0x02. I assume it is to + * allow extra, non-standard, reporting (say battery-life). + * + * However at the same time it generates a left-click message + * on the button byte which causes spurious left-click's where + * there shouldn't be. This should sort that. Currently it's + * the only user of UMS_FLAG_T_AXIS so use it as an + * identifier. + * + * + * UPDATE: This problem affects the M$ Wireless Notebook Optical Mouse, + * too. However, the leading byte for this mouse is normally 0x11, + * and the phantom mouse click occurs when its 0x14. + * + * We probably should switch to some more official quirk. + */ + if (sc->sc_iid) { + if (sc->sc_flags & UMS_FLAG_T_AXIS) { + if (*buf == 0x02) { + goto tr_setup; + } + } else { + if (*buf != sc->sc_iid) { + goto tr_setup; + } + } + + len--; + buf++; + + } else { + if (sc->sc_flags & UMS_FLAG_SBU) { + if ((*buf == 0x14) || (*buf == 0x15)) { + goto tr_setup; + } + } + } + + dw = (sc->sc_flags & UMS_FLAG_W_AXIS) ? + hid_get_data(buf, len, &sc->sc_loc_w) : 0; + + dx = (sc->sc_flags & UMS_FLAG_X_AXIS) ? + hid_get_data(buf, len, &sc->sc_loc_x) : 0; + + dy = (sc->sc_flags & UMS_FLAG_Y_AXIS) ? + -hid_get_data(buf, len, &sc->sc_loc_y) : 0; + + dz = (sc->sc_flags & UMS_FLAG_Z_AXIS) ? + -hid_get_data(buf, len, &sc->sc_loc_z) : 0; + + if (sc->sc_flags & UMS_FLAG_REVZ) { + dz = -dz; + } + dt = (sc->sc_flags & UMS_FLAG_T_AXIS) ? + -hid_get_data(buf, len, &sc->sc_loc_t): 0; + + for (i = 0; i < sc->sc_buttons; i++) { + if (hid_get_data(buf, len, &sc->sc_loc_btn[i])) { + buttons |= (1 << UMS_BUT(i)); + } + } + + if (dx || dy || dz || dt || dw || + (buttons != sc->sc_status.button)) { + + DPRINTFN(6, "x:%d y:%d z:%d t:%d w:%d buttons:0x%08x\n", + dx, dy, dz, dt, dw, buttons); + + sc->sc_status.button = buttons; + sc->sc_status.dx += dx; + sc->sc_status.dy += dy; + sc->sc_status.dz += dz; + /* + * sc->sc_status.dt += dt; + * no way to export this yet + */ + + /* + * The Qtronix keyboard has a built in PS/2 port for a mouse. + * The firmware once in a while posts a spurious button up + * event. This event we ignore by doing a timeout for 50 msecs. + * If we receive dx=dy=dz=buttons=0 before we add the event to + * the queue. + * In any other case we delete the timeout event. + */ + if ((sc->sc_flags & UMS_FLAG_SBU) && + (dx == 0) && (dy == 0) && (dz == 0) && (dt == 0) && + (dw == 0) && (buttons == 0)) { + + usb2_callout_reset(&sc->sc_callout, hz / 20, + &ums_put_queue_timeout, sc); + } else { + + usb2_callout_stop(&sc->sc_callout); + + ums_put_queue(sc, dx, dy, dz, dt, buttons); + } + } + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flags & UMS_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[1]); + } else { + /* check if we can put more data into the FIFO */ + if (usb2_fifo_put_bytes_max( + sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= UMS_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[1]); + } + return; + } +} + +static const struct usb2_config ums_config[UMS_N_TRANSFER] = { + + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &ums_intr_callback, + }, + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ums_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static int +ums_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_interface_descriptor *id; + void *d_ptr; + int32_t error = 0; + uint16_t d_len; + + DPRINTFN(11, "\n"); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->iface == NULL) { + return (ENXIO); + } + id = usb2_get_interface_descriptor(uaa->iface); + + if ((id == NULL) || + (id->bInterfaceClass != UICLASS_HID)) { + return (ENXIO); + } + error = usb2_req_get_hid_desc + (uaa->device, &Giant, + &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); + + if (error) { + return (ENXIO); + } + if (hid_is_collection(d_ptr, d_len, + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))) { + error = 0; + } else if ((id->bInterfaceSubClass == UISUBCLASS_BOOT) && + (id->bInterfaceProtocol == UIPROTO_MOUSE)) { + error = 0; + } else { + error = ENXIO; + } + + free(d_ptr, M_TEMP); + return (error); +} + +static int +ums_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ums_softc *sc = device_get_softc(dev); + void *d_ptr = NULL; + int unit = device_get_unit(dev); + int32_t isize; + uint32_t flags; + int32_t err; + uint16_t d_len; + uint8_t i; + + DPRINTFN(11, "sc=%p\n", sc); + + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "ums lock", NULL, MTX_DEF | MTX_RECURSE); + + usb2_callout_init_mtx(&sc->sc_callout, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + /* + * Force the report (non-boot) protocol. + * + * Mice without boot protocol support may choose not to implement + * Set_Protocol at all; Ignore any error. + */ + err = usb2_req_set_protocol(uaa->device, NULL, uaa->info.bIfaceIndex, 1); + + err = usb2_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, ums_config, + UMS_N_TRANSFER, sc, &sc->sc_mtx); + + if (err) { + DPRINTF("error=%s\n", usb2_errstr(err)); + goto detach; + } + err = usb2_req_get_hid_desc + (uaa->device, &Giant, &d_ptr, + &d_len, M_TEMP, uaa->info.bIfaceIndex); + + if (err) { + device_printf(dev, "error reading report description\n"); + goto detach; + } + if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), + hid_input, &sc->sc_loc_x, &flags)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + sc->sc_flags |= UMS_FLAG_X_AXIS; + } + } + if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), + hid_input, &sc->sc_loc_y, &flags)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + sc->sc_flags |= UMS_FLAG_Y_AXIS; + } + } + /* Try the wheel first as the Z activator since it's tradition. */ + if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_WHEEL), hid_input, &sc->sc_loc_z, &flags) || + hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_TWHEEL), hid_input, &sc->sc_loc_z, &flags)) { + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + sc->sc_flags |= UMS_FLAG_Z_AXIS; + } + /* + * We might have both a wheel and Z direction, if so put + * put the Z on the W coordinate. + */ + if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_Z), hid_input, &sc->sc_loc_w, &flags)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + sc->sc_flags |= UMS_FLAG_W_AXIS; + } + } + } else if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_Z), hid_input, &sc->sc_loc_z, &flags)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + sc->sc_flags |= UMS_FLAG_Z_AXIS; + } + } + /* + * The Microsoft Wireless Intellimouse 2.0 reports it's wheel + * using 0x0048, which is HUG_TWHEEL, and seems to expect you + * to know that the byte after the wheel is the tilt axis. + * There are no other HID axis descriptors other than X,Y and + * TWHEEL + */ + if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL), + hid_input, &sc->sc_loc_t, &flags)) { + + sc->sc_loc_t.pos += 8; + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + sc->sc_flags |= UMS_FLAG_T_AXIS; + } + } + /* figure out the number of buttons */ + + for (i = 0; i < UMS_BUTTON_MAX; i++) { + if (!hid_locate(d_ptr, d_len, HID_USAGE2(HUP_BUTTON, (i + 1)), + hid_input, &sc->sc_loc_btn[i], NULL)) { + break; + } + } + + sc->sc_buttons = i; + + isize = hid_report_size(d_ptr, d_len, hid_input, &sc->sc_iid); + + /* + * The Microsoft Wireless Notebook Optical Mouse seems to be in worse + * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and + * all of its other button positions are all off. It also reports that + * it has two addional buttons and a tilt wheel. + */ + if (usb2_test_quirk(uaa, UQ_MS_BAD_CLASS)) { + sc->sc_flags = (UMS_FLAG_X_AXIS | + UMS_FLAG_Y_AXIS | + UMS_FLAG_Z_AXIS | + UMS_FLAG_SBU); + sc->sc_buttons = 3; + isize = 5; + sc->sc_iid = 0; + /* 1st byte of descriptor report contains garbage */ + sc->sc_loc_x.pos = 16; + sc->sc_loc_y.pos = 24; + sc->sc_loc_z.pos = 32; + sc->sc_loc_btn[0].pos = 8; + sc->sc_loc_btn[1].pos = 9; + sc->sc_loc_btn[2].pos = 10; + } + /* + * The Microsoft Wireless Notebook Optical Mouse 3000 Model 1049 has + * five Report IDs: 19 23 24 17 18 (in the order they appear in report + * descriptor), it seems that report id 17 contains the necessary + * mouse information(3-buttons,X,Y,wheel) so we specify it manually. + */ + if ((uaa->info.idVendor == USB_VENDOR_MICROSOFT) && + (uaa->info.idProduct == USB_PRODUCT_MICROSOFT_WLNOTEBOOK3)) { + sc->sc_flags = (UMS_FLAG_X_AXIS | + UMS_FLAG_Y_AXIS | + UMS_FLAG_Z_AXIS); + sc->sc_buttons = 3; + isize = 5; + sc->sc_iid = 17; + sc->sc_loc_x.pos = 8; + sc->sc_loc_y.pos = 16; + sc->sc_loc_z.pos = 24; + sc->sc_loc_btn[0].pos = 0; + sc->sc_loc_btn[1].pos = 1; + sc->sc_loc_btn[2].pos = 2; + } + if (usb2_test_quirk(uaa, UQ_MS_REVZ)) { + /* Some wheels need the Z axis reversed. */ + sc->sc_flags |= UMS_FLAG_REVZ; + } + if (isize > sc->sc_xfer[0]->max_frame_size) { + DPRINTF("WARNING: report size, %d bytes, is larger " + "than interrupt size, %d bytes!\n", + isize, sc->sc_xfer[0]->max_frame_size); + } + /* announce information about the mouse */ + + device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates\n", + (sc->sc_buttons), + (sc->sc_flags & UMS_FLAG_X_AXIS) ? "X" : "", + (sc->sc_flags & UMS_FLAG_Y_AXIS) ? "Y" : "", + (sc->sc_flags & UMS_FLAG_Z_AXIS) ? "Z" : "", + (sc->sc_flags & UMS_FLAG_T_AXIS) ? "T" : "", + (sc->sc_flags & UMS_FLAG_W_AXIS) ? "W" : ""); + + free(d_ptr, M_TEMP); + d_ptr = NULL; + +#if USB_DEBUG + DPRINTF("sc=%p\n", sc); + DPRINTF("X\t%d/%d\n", sc->sc_loc_x.pos, sc->sc_loc_x.size); + DPRINTF("Y\t%d/%d\n", sc->sc_loc_y.pos, sc->sc_loc_y.size); + DPRINTF("Z\t%d/%d\n", sc->sc_loc_z.pos, sc->sc_loc_z.size); + DPRINTF("T\t%d/%d\n", sc->sc_loc_t.pos, sc->sc_loc_t.size); + DPRINTF("W\t%d/%d\n", sc->sc_loc_w.pos, sc->sc_loc_w.size); + + for (i = 0; i < sc->sc_buttons; i++) { + DPRINTF("B%d\t%d/%d\n", + i + 1, sc->sc_loc_btn[i].pos, sc->sc_loc_btn[i].size); + } + DPRINTF("size=%d, id=%d\n", isize, sc->sc_iid); +#endif + + if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + + sc->sc_hw.iftype = MOUSE_IF_USB; + sc->sc_hw.type = MOUSE_MOUSE; + sc->sc_hw.model = MOUSE_MODEL_GENERIC; + sc->sc_hw.hwid = 0; + + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.rate = -1; + sc->sc_mode.resolution = MOUSE_RES_UNKNOWN; + sc->sc_mode.accelfactor = 0; + sc->sc_mode.level = 0; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + + sc->sc_status.flags = 0; + sc->sc_status.button = 0; + sc->sc_status.obutton = 0; + sc->sc_status.dx = 0; + sc->sc_status.dy = 0; + sc->sc_status.dz = 0; + + /* set interface permissions */ + usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + err = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &ums_fifo_methods, &sc->sc_fifo, + unit, 0 - 1, uaa->info.bIfaceIndex); + if (err) { + goto detach; + } + return (0); + +detach: + if (d_ptr) { + free(d_ptr, M_TEMP); + } + ums_detach(dev); + return (ENOMEM); +} + +static int +ums_detach(device_t self) +{ + struct ums_softc *sc = device_get_softc(self); + + DPRINTF("sc=%p\n", sc); + + usb2_fifo_detach(&sc->sc_fifo); + + usb2_transfer_unsetup(sc->sc_xfer, UMS_N_TRANSFER); + + usb2_callout_drain(&sc->sc_callout); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +ums_start_read(struct usb2_fifo *fifo) +{ + struct ums_softc *sc = fifo->priv_sc0; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +ums_stop_read(struct usb2_fifo *fifo) +{ + struct ums_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_callout_stop(&sc->sc_callout); + return; +} + + +#if ((MOUSE_SYS_PACKETSIZE != 8) || \ + (MOUSE_MSC_PACKETSIZE != 5)) +#error "Software assumptions are not met. Please update code." +#endif + +static void +ums_put_queue(struct ums_softc *sc, int32_t dx, int32_t dy, + int32_t dz, int32_t dt, int32_t buttons) +{ + uint8_t buf[8]; + + if (1) { + + if (dx > 254) + dx = 254; + if (dx < -256) + dx = -256; + if (dy > 254) + dy = 254; + if (dy < -256) + dy = -256; + if (dz > 126) + dz = 126; + if (dz < -128) + dz = -128; + if (dt > 126) + dt = 126; + if (dt < -128) + dt = -128; + + buf[0] = sc->sc_mode.syncmask[1]; + buf[0] |= (~buttons) & MOUSE_MSC_BUTTONS; + buf[1] = dx >> 1; + buf[2] = dy >> 1; + buf[3] = dx - (dx >> 1); + buf[4] = dy - (dy >> 1); + + if (sc->sc_mode.level == 1) { + buf[5] = dz >> 1; + buf[6] = dz - (dz >> 1); + buf[7] = (((~buttons) >> 3) & MOUSE_SYS_EXTBUTTONS); + } + usb2_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf, + sc->sc_mode.packetsize, 1); + + } else { + DPRINTF("Buffer full, discarded packet\n"); + } + + return; +} + +static void +ums_reset_buf(struct ums_softc *sc) +{ + /* reset read queue */ + usb2_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]); + return; +} + +static int +ums_open(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct ums_softc *sc = fifo->priv_sc0; + + DPRINTFN(2, "\n"); + + if (fflags & FREAD) { + + /* reset status */ + + sc->sc_status.flags = 0; + sc->sc_status.button = 0; + sc->sc_status.obutton = 0; + sc->sc_status.dx = 0; + sc->sc_status.dy = 0; + sc->sc_status.dz = 0; + /* sc->sc_status.dt = 0; */ + + if (usb2_fifo_alloc_buffer(fifo, + UMS_BUF_SIZE, UMS_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + return (0); +} + +static void +ums_close(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + if (fflags & FREAD) { + usb2_fifo_free_buffer(fifo); + } + return; +} + +static int +ums_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr, + int fflags, struct thread *td) +{ + struct ums_softc *sc = fifo->priv_sc0; + mousemode_t mode; + int error = 0; + + DPRINTFN(2, "\n"); + + mtx_lock(&sc->sc_mtx); + + switch (cmd) { + case MOUSE_GETHWINFO: + *(mousehw_t *)addr = sc->sc_hw; + break; + + case MOUSE_GETMODE: + *(mousemode_t *)addr = sc->sc_mode; + break; + + case MOUSE_SETMODE: + mode = *(mousemode_t *)addr; + + if (mode.level == -1) { + /* don't change the current setting */ + } else if ((mode.level < 0) || (mode.level > 1)) { + error = EINVAL; + goto done; + } else { + sc->sc_mode.level = mode.level; + } + + if (sc->sc_mode.level == 0) { + if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + } else if (sc->sc_mode.level == 1) { + if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; + sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; + } + ums_reset_buf(sc); + break; + + case MOUSE_GETLEVEL: + *(int *)addr = sc->sc_mode.level; + break; + + case MOUSE_SETLEVEL: + if (*(int *)addr < 0 || *(int *)addr > 1) { + error = EINVAL; + goto done; + } + sc->sc_mode.level = *(int *)addr; + + if (sc->sc_mode.level == 0) { + if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + } else if (sc->sc_mode.level == 1) { + if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; + sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; + } + ums_reset_buf(sc); + break; + + case MOUSE_GETSTATUS:{ + mousestatus_t *status = (mousestatus_t *)addr; + + *status = sc->sc_status; + sc->sc_status.obutton = sc->sc_status.button; + sc->sc_status.button = 0; + sc->sc_status.dx = 0; + sc->sc_status.dy = 0; + sc->sc_status.dz = 0; + /* sc->sc_status.dt = 0; */ + + if (status->dx || status->dy || status->dz /* || status->dt */ ) { + status->flags |= MOUSE_POSCHANGED; + } + if (status->button != status->obutton) { + status->flags |= MOUSE_BUTTONSCHANGED; + } + break; + } + default: + error = ENOTTY; + } + +done: + mtx_unlock(&sc->sc_mtx); + return (error); +} + +static devclass_t ums_devclass; + +static device_method_t ums_methods[] = { + DEVMETHOD(device_probe, ums_probe), + DEVMETHOD(device_attach, ums_attach), + DEVMETHOD(device_detach, ums_detach), + {0, 0} +}; + +static driver_t ums_driver = { + .name = "ums", + .methods = ums_methods, + .size = sizeof(struct ums_softc), +}; + +DRIVER_MODULE(ums, ushub, ums_driver, ums_devclass, NULL, 0); +MODULE_DEPEND(ums, usb2_input, 1, 1, 1); +MODULE_DEPEND(ums, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/input/usb2_input.c b/sys/dev/usb2/input/usb2_input.c new file mode 100644 index 000000000000..56f9ff229f15 --- /dev/null +++ b/sys/dev/usb2/input/usb2_input.c @@ -0,0 +1,31 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_input, 1); +MODULE_DEPEND(usb2_input, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/input/usb2_input.h b/sys/dev/usb2/input/usb2_input.h new file mode 100644 index 000000000000..0b5185374dc5 --- /dev/null +++ b/sys/dev/usb2/input/usb2_input.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_INPUT_H_ +#define _USB2_INPUT_H_ + +#endif /* _USB2_INPUT_H_ */ diff --git a/sys/dev/usb2/input/usb2_rdesc.h b/sys/dev/usb2/input/usb2_rdesc.h new file mode 100644 index 000000000000..9f4363dcfb68 --- /dev/null +++ b/sys/dev/usb2/input/usb2_rdesc.h @@ -0,0 +1,276 @@ +/*- + * Copyright (c) 2000 Nick Hibma + * All rights reserved. + * + * Copyright (c) 2005 Ed Schouten + * 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. + * + * $FreeBSD$ + * + * This file contains replacements for broken HID report descriptors. + */ + +#define UHID_GRAPHIRE_REPORT_DESCR(...) \ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x09, 0x33, /* USAGE (Touch) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */\ + 0x09, 0x3c, /* USAGE (Invert) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x38, /* USAGE (Transducer Index) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x32, /* USAGE (In Range) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x30, /* USAGE (Tip Pressure) */\ + 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x03, /* REPORT_ID (3) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + +#define UHID_GRAPHIRE3_4X5_REPORT_DESCR(...) \ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x02, /* USAGE (Mouse) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + 0x85, 0x01, /* REPORT_ID (1) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x05, 0x09, /* USAGE_PAGE (Button) */\ + 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */\ + 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x05, /* REPORT_SIZE (5) */\ + 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x09, 0x38, /* USAGE (Wheel) */\ + 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */\ + 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */\ + 0x75, 0x08, /* REPORT_SIZE (8) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */\ + 0xc0, /* END_COLLECTION */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x01, /* COLLECTION (Applicaption) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x09, 0x33, /* USAGE (Touch) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */\ + 0x09, 0x3c, /* USAGE (Invert) */\ + 0x09, 0x38, /* USAGE (Transducer Index) */\ + 0x09, 0x32, /* USAGE (In Range) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x30, /* USAGE (Tip Pressure) */\ + 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x03, /* REPORT_ID (3) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0xc0 /* END_COLLECTION */\ + +/* + * The descriptor has no output report format, thus preventing you from + * controlling the LEDs and the built-in rumblers. + */ +#define UHID_XB360GP_REPORT_DESCR(...) \ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x05, /* USAGE (Gamepad) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + /* Unused */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* Byte count */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x3b, /* USAGE (Byte Count) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* D-Pad */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x90, /* USAGE (D-Pad Up) */\ + 0x09, 0x91, /* USAGE (D-Pad Down) */\ + 0x09, 0x93, /* USAGE (D-Pad Left) */\ + 0x09, 0x92, /* USAGE (D-Pad Right) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + 0xc0, /* END COLLECTION */\ + /* Buttons 5-11 */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x07, /* REPORT COUNT (7) */\ + 0x05, 0x09, /* USAGE PAGE (Button) */\ + 0x09, 0x08, /* USAGE (Button 8) */\ + 0x09, 0x07, /* USAGE (Button 7) */\ + 0x09, 0x09, /* USAGE (Button 9) */\ + 0x09, 0x0a, /* USAGE (Button 10) */\ + 0x09, 0x05, /* USAGE (Button 5) */\ + 0x09, 0x06, /* USAGE (Button 6) */\ + 0x09, 0x0b, /* USAGE (Button 11) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Unused */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* Buttons 1-4 */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x09, /* USAGE PAGE (Button) */\ + 0x19, 0x01, /* USAGE MINIMUM (Button 1) */\ + 0x29, 0x04, /* USAGE MAXIMUM (Button 4) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Triggers */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x26, 0xff, 0x00, /* LOGICAL MAXIMUM (255) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x46, 0xff, 0x00, /* PHYSICAL MAXIMUM (255) */\ + 0x95, 0x02, /* REPORT SIZE (2) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x32, /* USAGE (Z) */\ + 0x09, 0x35, /* USAGE (Rz) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Sticks */\ + 0x75, 0x10, /* REPORT SIZE (16) */\ + 0x16, 0x00, 0x80, /* LOGICAL MINIMUM (-32768) */\ + 0x26, 0xff, 0x7f, /* LOGICAL MAXIMUM (32767) */\ + 0x36, 0x00, 0x80, /* PHYSICAL MINIMUM (-32768) */\ + 0x46, 0xff, 0x7f, /* PHYSICAL MAXIMUM (32767) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x09, 0x33, /* USAGE (Rx) */\ + 0x09, 0x34, /* USAGE (Ry) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Unused */\ + 0x75, 0x30, /* REPORT SIZE (48) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + 0xc0 /* END COLLECTION */\ + diff --git a/sys/dev/usb2/misc/udbp2.c b/sys/dev/usb2/misc/udbp2.c new file mode 100644 index 000000000000..d43386879734 --- /dev/null +++ b/sys/dev/usb2/misc/udbp2.c @@ -0,0 +1,861 @@ +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * 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 author 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 NICK HIBMA 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. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +/* Driver for arbitrary double bulk pipe devices. + * The driver assumes that there will be the same driver on the other side. + * + * XXX Some more information on what the framing of the IP packets looks like. + * + * To take full advantage of bulk transmission, packets should be chosen + * between 1k and 5k in size (1k to make sure the sending side starts + * streaming, and <5k to avoid overflowing the system with small TDs). + */ + + +/* probe/attach/detach: + * Connect the driver to the hardware and netgraph + * + * The reason we submit a bulk in transfer is that USB does not know about + * interrupts. The bulk transfer continuously polls the device for data. + * While the device has no data available, the device NAKs the TDs. As soon + * as there is data, the transfer happens and the data comes flowing in. + * + * In case you were wondering, interrupt transfers happen exactly that way. + * It therefore doesn't make sense to use the interrupt pipe to signal + * 'data ready' and then schedule a bulk transfer to fetch it. That would + * incur a 2ms delay at least, without reducing bandwidth requirements. + * + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR udbp_debug + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int udbp_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, udbp, CTLFLAG_RW, 0, "USB udbp"); +SYSCTL_INT(_hw_usb2_udbp, OID_AUTO, debug, CTLFLAG_RW, + &udbp_debug, 0, "udbp debug level"); +#endif + +#define UDBP_TIMEOUT 2000 /* timeout on outbound transfers, in + * msecs */ +#define UDBP_BUFFERSIZE MCLBYTES /* maximum number of bytes in one + * transfer */ +#define UDBP_T_WR 0 +#define UDBP_T_RD 1 +#define UDBP_T_WR_CS 2 +#define UDBP_T_RD_CS 3 +#define UDBP_T_MAX 4 +#define UDBP_Q_MAXLEN 50 + +struct udbp_softc { + + struct mtx sc_mtx; + struct ng_bt_mbufq sc_xmitq_hipri; /* hi-priority transmit queue */ + struct ng_bt_mbufq sc_xmitq; /* low-priority transmit queue */ + + struct usb2_xfer *sc_xfer[UDBP_T_MAX]; + node_p sc_node; /* back pointer to node */ + hook_p sc_hook; /* pointer to the hook */ + struct mbuf *sc_bulk_in_buffer; + + uint32_t sc_packets_in; /* packets in from downstream */ + uint32_t sc_packets_out; /* packets out towards downstream */ + + uint8_t sc_flags; +#define UDBP_FLAG_READ_STALL 0x01 /* read transfer stalled */ +#define UDBP_FLAG_WRITE_STALL 0x02 /* write transfer stalled */ + + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static int udbp_modload(module_t mod, int event, void *data); + +static device_probe_t udbp_probe; +static device_attach_t udbp_attach; +static device_detach_t udbp_detach; + +static usb2_callback_t udbp_bulk_read_callback; +static usb2_callback_t udbp_bulk_read_clear_stall_callback; +static usb2_callback_t udbp_bulk_write_callback; +static usb2_callback_t udbp_bulk_write_clear_stall_callback; + +static void udbp_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2); + +static ng_constructor_t ng_udbp_constructor; +static ng_rcvmsg_t ng_udbp_rcvmsg; +static ng_shutdown_t ng_udbp_rmnode; +static ng_newhook_t ng_udbp_newhook; +static ng_connect_t ng_udbp_connect; +static ng_rcvdata_t ng_udbp_rcvdata; +static ng_disconnect_t ng_udbp_disconnect; + +/* Parse type for struct ngudbpstat */ +static const struct ng_parse_struct_field + ng_udbp_stat_type_fields[] = NG_UDBP_STATS_TYPE_INFO; + +static const struct ng_parse_type ng_udbp_stat_type = { + &ng_parse_struct_type, + &ng_udbp_stat_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_udbp_cmdlist[] = { + { + NGM_UDBP_COOKIE, + NGM_UDBP_GET_STATUS, + "getstatus", + NULL, + &ng_udbp_stat_type, + }, + { + NGM_UDBP_COOKIE, + NGM_UDBP_SET_FLAG, + "setflag", + &ng_parse_int32_type, + NULL + }, + {0} +}; + +/* Netgraph node type descriptor */ +static struct ng_type ng_udbp_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_UDBP_NODE_TYPE, + .constructor = ng_udbp_constructor, + .rcvmsg = ng_udbp_rcvmsg, + .shutdown = ng_udbp_rmnode, + .newhook = ng_udbp_newhook, + .connect = ng_udbp_connect, + .rcvdata = ng_udbp_rcvdata, + .disconnect = ng_udbp_disconnect, + .cmdlist = ng_udbp_cmdlist, +}; + +/* USB config */ +static const struct usb2_config udbp_config[UDBP_T_MAX] = { + + [UDBP_T_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UDBP_BUFFERSIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &udbp_bulk_write_callback, + .mh.timeout = UDBP_TIMEOUT, + }, + + [UDBP_T_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UDBP_BUFFERSIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &udbp_bulk_read_callback, + }, + + [UDBP_T_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &udbp_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [UDBP_T_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &udbp_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static devclass_t udbp_devclass; + +static device_method_t udbp_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, udbp_probe), + DEVMETHOD(device_attach, udbp_attach), + DEVMETHOD(device_detach, udbp_detach), + {0, 0} +}; + +static driver_t udbp_driver = { + .name = "udbp", + .methods = udbp_methods, + .size = sizeof(struct udbp_softc), +}; + +DRIVER_MODULE(udbp, ushub, udbp_driver, udbp_devclass, udbp_modload, 0); +MODULE_DEPEND(udbp, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); +MODULE_DEPEND(udbp, usb2_misc, 1, 1, 1); +MODULE_DEPEND(udbp, usb2_core, 1, 1, 1); + +static int +udbp_modload(module_t mod, int event, void *data) +{ + int error; + + switch (event) { + case MOD_LOAD: + error = ng_newtype(&ng_udbp_typestruct); + if (error != 0) { + printf("%s: Could not register " + "Netgraph node type, error=%d\n", + NG_UDBP_NODE_TYPE, error); + } + break; + + case MOD_UNLOAD: + error = ng_rmtype(&ng_udbp_typestruct); + break; + + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +static int +udbp_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* + * XXX Julian, add the id of the device if you have one to test + * things with. run 'usbdevs -v' and note the 3 ID's that appear. + * The Vendor Id and Product Id are in hex and the Revision Id is in + * bcd. But as usual if the revision is 0x101 then you should + * compare the revision id in the device descriptor with 0x101 Or go + * search the file usbdevs.h. Maybe the device is already in there. + */ + if (((uaa->info.idVendor == USB_VENDOR_NETCHIP) && + (uaa->info.idProduct == USB_PRODUCT_NETCHIP_TURBOCONNECT))) + return (0); + + if (((uaa->info.idVendor == USB_VENDOR_PROLIFIC) && + ((uaa->info.idProduct == USB_PRODUCT_PROLIFIC_PL2301) || + (uaa->info.idProduct == USB_PRODUCT_PROLIFIC_PL2302)))) + return (0); + + if ((uaa->info.idVendor == USB_VENDOR_ANCHOR) && + (uaa->info.idProduct == USB_PRODUCT_ANCHOR_EZLINK)) + return (0); + + if ((uaa->info.idVendor == USB_VENDOR_GENESYS) && + (uaa->info.idProduct == USB_PRODUCT_GENESYS_GL620USB)) + return (0); + + return (ENXIO); +} + +static int +udbp_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct udbp_softc *sc = device_get_softc(dev); + int error; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "udbp lock", NULL, MTX_DEF | MTX_RECURSE); + + error = usb2_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, + sc->sc_xfer, udbp_config, UDBP_T_MAX, sc, &sc->sc_mtx); + if (error) { + DPRINTF("error=%s\n", usb2_errstr(error)); + goto detach; + } + NG_BT_MBUFQ_INIT(&sc->sc_xmitq, UDBP_Q_MAXLEN); + + NG_BT_MBUFQ_INIT(&sc->sc_xmitq_hipri, UDBP_Q_MAXLEN); + + /* create Netgraph node */ + + if (ng_make_node_common(&ng_udbp_typestruct, &sc->sc_node) != 0) { + printf("%s: Could not create Netgraph node\n", + sc->sc_name); + sc->sc_node = NULL; + goto detach; + } + /* name node */ + + if (ng_name_node(sc->sc_node, sc->sc_name) != 0) { + printf("%s: Could not name node\n", + sc->sc_name); + NG_NODE_UNREF(sc->sc_node); + sc->sc_node = NULL; + goto detach; + } + NG_NODE_SET_PRIVATE(sc->sc_node, sc); + + /* the device is now operational */ + + return (0); /* success */ + +detach: + udbp_detach(dev); + return (ENOMEM); /* failure */ +} + +static int +udbp_detach(device_t dev) +{ + struct udbp_softc *sc = device_get_softc(dev); + + /* destroy Netgraph node */ + + if (sc->sc_node != NULL) { + NG_NODE_SET_PRIVATE(sc->sc_node, NULL); + ng_rmnode_self(sc->sc_node); + sc->sc_node = NULL; + } + /* free USB transfers, if any */ + + usb2_transfer_unsetup(sc->sc_xfer, UDBP_T_MAX); + + mtx_destroy(&sc->sc_mtx); + + /* destroy queues */ + + NG_BT_MBUFQ_DESTROY(&sc->sc_xmitq); + NG_BT_MBUFQ_DESTROY(&sc->sc_xmitq_hipri); + + /* extra check */ + + if (sc->sc_bulk_in_buffer) { + m_freem(sc->sc_bulk_in_buffer); + sc->sc_bulk_in_buffer = NULL; + } + return (0); /* success */ +} + +static void +udbp_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct udbp_softc *sc = xfer->priv_sc; + struct mbuf *m; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* allocate new mbuf */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + + if (m == NULL) { + goto tr_setup; + } + MCLGET(m, M_DONTWAIT); + + if (!(m->m_flags & M_EXT)) { + m_freem(m); + goto tr_setup; + } + m->m_pkthdr.len = m->m_len = xfer->actlen; + + usb2_copy_out(xfer->frbuffers, 0, m->m_data, xfer->actlen); + + sc->sc_bulk_in_buffer = m; + + DPRINTF("received package %d " + "bytes\n", xfer->actlen); + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_bulk_in_buffer) { + ng_send_fn(sc->sc_node, NULL, &udbp_bulk_read_complete, NULL, 0); + return; + } + if (sc->sc_flags & UDBP_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[UDBP_T_RD_CS]); + return; + } + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UDBP_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[UDBP_T_RD_CS]); + } + return; + + } +} + +static void +udbp_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct udbp_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[UDBP_T_RD]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UDBP_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +udbp_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(node); + struct mbuf *m; + int error; + + if (sc == NULL) { + return; + } + mtx_lock(&sc->sc_mtx); + + m = sc->sc_bulk_in_buffer; + + if (m) { + + sc->sc_bulk_in_buffer = NULL; + + if ((sc->sc_hook == NULL) || + NG_HOOK_NOT_VALID(sc->sc_hook)) { + DPRINTF("No upstream hook\n"); + goto done; + } + sc->sc_packets_in++; + + NG_SEND_DATA_ONLY(error, sc->sc_hook, m); + + m = NULL; + } +done: + if (m) { + m_freem(m); + } + /* start USB bulk-in transfer, if not already started */ + + usb2_transfer_start(sc->sc_xfer[UDBP_T_RD]); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +udbp_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct udbp_softc *sc = xfer->priv_sc; + struct mbuf *m; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + sc->sc_packets_out++; + + case USB_ST_SETUP: + if (sc->sc_flags & UDBP_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[UDBP_T_WR_CS]); + return; + } + /* get next mbuf, if any */ + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_xmitq_hipri, m); + if (m == NULL) { + NG_BT_MBUFQ_DEQUEUE(&sc->sc_xmitq, m); + if (m == NULL) { + DPRINTF("Data queue is empty\n"); + return; + } + } + if (m->m_pkthdr.len > MCLBYTES) { + DPRINTF("truncating large packet " + "from %d to %d bytes\n", m->m_pkthdr.len, + MCLBYTES); + m->m_pkthdr.len = MCLBYTES; + } + usb2_m_copy_in(xfer->frbuffers, 0, m, 0, m->m_pkthdr.len); + + xfer->frlengths[0] = m->m_pkthdr.len; + + m_freem(m); + + DPRINTF("packet out: %d bytes\n", + xfer->frlengths[0]); + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UDBP_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[UDBP_T_WR_CS]); + } + return; + + } +} + +static void +udbp_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct udbp_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[UDBP_T_WR]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UDBP_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +/*********************************************************************** + * Start of Netgraph methods + **********************************************************************/ + +/* + * If this is a device node so this work is done in the attach() + * routine and the constructor will return EINVAL as you should not be able + * to create nodes that depend on hardware (unless you can add the hardware :) + */ +static int +ng_udbp_constructor(node_p node) +{ + return (EINVAL); +} + +/* + * Give our ok for a hook to be added... + * If we are not running this might kick a device into life. + * Possibly decode information out of the hook name. + * Add the hook's private info to the hook structure. + * (if we had some). In this example, we assume that there is a + * an array of structs, called 'channel' in the private info, + * one for each active channel. The private + * pointer of each hook points to the appropriate UDBP_hookinfo struct + * so that the source of an input packet is easily identified. + */ +static int +ng_udbp_newhook(node_p node, hook_p hook, const char *name) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(node); + int32_t error = 0; + + if (strcmp(name, NG_UDBP_HOOK_NAME)) { + return (EINVAL); + } + mtx_lock(&sc->sc_mtx); + + if (sc->sc_hook != NULL) { + error = EISCONN; + } else { + sc->sc_hook = hook; + NG_HOOK_SET_PRIVATE(hook, NULL); + } + + mtx_unlock(&sc->sc_mtx); + + return (error); +} + +/* + * Get a netgraph control message. + * Check it is one we understand. If needed, send a response. + * We could save the address for an async action later, but don't here. + * Always free the message. + * The response should be in a malloc'd region that the caller can 'free'. + * A response is not required. + * Theoretically you could respond defferently to old message types if + * the cookie in the header didn't match what we consider to be current + * (so that old userland programs could continue to work). + */ +static int +ng_udbp_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + /* Deal with message according to cookie and command */ + switch (msg->header.typecookie) { + case NGM_UDBP_COOKIE: + switch (msg->header.cmd) { + case NGM_UDBP_GET_STATUS: + { + struct ngudbpstat *stats; + + NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + stats = (struct ngudbpstat *)resp->data; + mtx_lock(&sc->sc_mtx); + stats->packets_in = sc->sc_packets_in; + stats->packets_out = sc->sc_packets_out; + mtx_unlock(&sc->sc_mtx); + break; + } + case NGM_UDBP_SET_FLAG: + if (msg->header.arglen != sizeof(uint32_t)) { + error = EINVAL; + break; + } + DPRINTF("flags = 0x%08x\n", + *((uint32_t *)msg->data)); + break; + default: + error = EINVAL; /* unknown command */ + break; + } + break; + default: + error = EINVAL; /* unknown cookie type */ + break; + } + + /* Take care of synchronous response, if any */ + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Accept data from the hook and queue it for output. + */ +static int +ng_udbp_rcvdata(hook_p hook, item_p item) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ng_bt_mbufq *queue_ptr; + struct mbuf *m; + struct ng_tag_prio *ptag; + int error; + + if (sc == NULL) { + NG_FREE_ITEM(item); + return (EHOSTDOWN); + } + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* + * Now queue the data for when it can be sent + */ + ptag = (void *)m_tag_locate(m, NGM_GENERIC_COOKIE, + NG_TAG_PRIO, NULL); + + if (ptag && (ptag->priority > NG_PRIO_CUTOFF)) + queue_ptr = &sc->sc_xmitq_hipri; + else + queue_ptr = &sc->sc_xmitq; + + mtx_lock(&sc->sc_mtx); + + if (NG_BT_MBUFQ_FULL(queue_ptr)) { + NG_BT_MBUFQ_DROP(queue_ptr); + NG_FREE_M(m); + error = ENOBUFS; + } else { + NG_BT_MBUFQ_ENQUEUE(queue_ptr, m); + /* + * start bulk-out transfer, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[UDBP_T_WR]); + error = 0; + } + + mtx_unlock(&sc->sc_mtx); + + return (error); +} + +/* + * Do local shutdown processing.. + * We are a persistant device, we refuse to go away, and + * only remove our links and reset ourself. + */ +static int +ng_udbp_rmnode(node_p node) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(node); + + /* Let old node go */ + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); /* forget it ever existed */ + + if (sc == NULL) { + goto done; + } + /* Create Netgraph node */ + if (ng_make_node_common(&ng_udbp_typestruct, &sc->sc_node) != 0) { + printf("%s: Could not create Netgraph node\n", + sc->sc_name); + sc->sc_node = NULL; + goto done; + } + /* Name node */ + if (ng_name_node(sc->sc_node, sc->sc_name) != 0) { + printf("%s: Could not name Netgraph node\n", + sc->sc_name); + NG_NODE_UNREF(sc->sc_node); + sc->sc_node = NULL; + goto done; + } + NG_NODE_SET_PRIVATE(sc->sc_node, sc); + +done: + if (sc) { + mtx_unlock(&sc->sc_mtx); + } + return (0); +} + +/* + * This is called once we've already connected a new hook to the other node. + * It gives us a chance to balk at the last minute. + */ +static int +ng_udbp_connect(hook_p hook) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + /* probably not at splnet, force outward queueing */ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + + mtx_lock(&sc->sc_mtx); + + sc->sc_flags |= (UDBP_FLAG_READ_STALL | + UDBP_FLAG_WRITE_STALL); + + /* start bulk-in transfer */ + usb2_transfer_start(sc->sc_xfer[UDBP_T_RD]); + + /* start bulk-out transfer */ + usb2_transfer_start(sc->sc_xfer[UDBP_T_WR]); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +/* + * Dook disconnection + * + * For this type, removal of the last link destroys the node + */ +static int +ng_udbp_disconnect(hook_p hook) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int error = 0; + + if (sc != NULL) { + + mtx_lock(&sc->sc_mtx); + + if (hook != sc->sc_hook) { + error = EINVAL; + } else { + + /* stop bulk-in transfer */ + usb2_transfer_stop(sc->sc_xfer[UDBP_T_RD_CS]); + usb2_transfer_stop(sc->sc_xfer[UDBP_T_RD]); + + /* stop bulk-out transfer */ + usb2_transfer_stop(sc->sc_xfer[UDBP_T_WR_CS]); + usb2_transfer_stop(sc->sc_xfer[UDBP_T_WR]); + + /* cleanup queues */ + NG_BT_MBUFQ_DRAIN(&sc->sc_xmitq); + NG_BT_MBUFQ_DRAIN(&sc->sc_xmitq_hipri); + + if (sc->sc_bulk_in_buffer) { + m_freem(sc->sc_bulk_in_buffer); + sc->sc_bulk_in_buffer = NULL; + } + sc->sc_hook = NULL; + } + + mtx_unlock(&sc->sc_mtx); + } + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + + return (error); +} diff --git a/sys/dev/usb2/misc/udbp2.h b/sys/dev/usb2/misc/udbp2.h new file mode 100644 index 000000000000..e6fd85326152 --- /dev/null +++ b/sys/dev/usb2/misc/udbp2.h @@ -0,0 +1,80 @@ +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file was derived from src/sys/netgraph/ng_sample.h, revision 1.1 + * written by Julian Elischer, Whistle Communications. + * + * $FreeBSD$ + */ + +#ifndef _NETGRAPH_UDBP_H_ +#define _NETGRAPH_UDBP_H_ + +/* Node type name. This should be unique among all netgraph node types */ +#define NG_UDBP_NODE_TYPE "udbp" + +/* Node type cookie. Should also be unique. This value MUST change whenever + an incompatible change is made to this header file, to insure consistency. + The de facto method for generating cookies is to take the output of the + date command: date -u +'%s' */ +#define NGM_UDBP_COOKIE 944609300 + + +#define NG_UDBP_HOOK_NAME "data" + +/* Netgraph commands understood by this node type */ +enum { + NGM_UDBP_SET_FLAG = 1, + NGM_UDBP_GET_STATUS, +}; + +/* This structure is returned by the NGM_UDBP_GET_STATUS command */ +struct ngudbpstat { + uint32_t packets_in; /* packets in from downstream */ + uint32_t packets_out; /* packets out towards downstream */ +}; + +/* + * This is used to define the 'parse type' for a struct ngudbpstat, which + * is bascially a description of how to convert a binary struct ngudbpstat + * to an ASCII string and back. See ng_parse.h for more info. + * + * This needs to be kept in sync with the above structure definition + */ +#define NG_UDBP_STATS_TYPE_INFO { \ + { "packets_in", &ng_parse_int32_type }, \ + { "packets_out", &ng_parse_int32_type }, \ + { NULL }, \ +} + +#endif /* _NETGRAPH_UDBP_H_ */ diff --git a/sys/dev/usb2/misc/ufm2.c b/sys/dev/usb2/misc/ufm2.c new file mode 100644 index 000000000000..ba24ca518186 --- /dev/null +++ b/sys/dev/usb2/misc/ufm2.c @@ -0,0 +1,336 @@ +/*- + * Copyright (c) 2001 M. Warner Losh + * 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. + * + * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson. + * This code includes software developed by the NetBSD Foundation, Inc. and + * its contributors. + */ + +#include +__FBSDID("$FreeBSD$"); + + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UFM_CMD0 0x00 +#define UFM_CMD_SET_FREQ 0x01 +#define UFM_CMD2 0x02 + +struct ufm_softc { + struct usb2_fifo_sc sc_fifo; + struct mtx sc_mtx; + + struct usb2_device *sc_udev; + + uint32_t sc_unit; + uint32_t sc_freq; + + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t ufm_probe; +static device_attach_t ufm_attach; +static device_detach_t ufm_detach; + +static usb2_fifo_ioctl_t ufm_ioctl; +static usb2_fifo_open_t ufm_open; + +static struct usb2_fifo_methods ufm_fifo_methods = { + .f_ioctl = &ufm_ioctl, + .f_open = &ufm_open, + .basename[0] = "ufm", +}; + +static int ufm_do_req(struct ufm_softc *sc, uint8_t request, uint16_t value, uint16_t index, uint8_t *retbuf); +static int ufm_set_freq(struct ufm_softc *sc, void *addr); +static int ufm_get_freq(struct ufm_softc *sc, void *addr); +static int ufm_start(struct ufm_softc *sc, void *addr); +static int ufm_stop(struct ufm_softc *sc, void *addr); +static int ufm_get_stat(struct ufm_softc *sc, void *addr); + +static devclass_t ufm_devclass; + +static device_method_t ufm_methods[] = { + DEVMETHOD(device_probe, ufm_probe), + DEVMETHOD(device_attach, ufm_attach), + DEVMETHOD(device_detach, ufm_detach), + {0, 0} +}; + +static driver_t ufm_driver = { + .name = "ufm", + .methods = ufm_methods, + .size = sizeof(struct ufm_softc), +}; + +MODULE_DEPEND(ufm, usb2_misc, 1, 1, 1); +DRIVER_MODULE(ufm, ushub, ufm_driver, ufm_devclass, NULL, 0); +MODULE_DEPEND(ufm, usb2_core, 1, 1, 1); + +static int +ufm_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if ((uaa->info.idVendor == USB_VENDOR_CYPRESS) && + (uaa->info.idProduct == USB_PRODUCT_CYPRESS_FMRADIO)) { + return (0); + } + return (ENXIO); +} + +static int +ufm_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ufm_softc *sc = device_get_softc(dev); + int error; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_unit = device_get_unit(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "ufm lock", NULL, MTX_DEF | MTX_RECURSE); + + device_set_usb2_desc(dev); + + /* set interface permissions */ + usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &ufm_fifo_methods, &sc->sc_fifo, + device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + ufm_detach(dev); + return (ENXIO); +} + +static int +ufm_detach(device_t dev) +{ + struct ufm_softc *sc = device_get_softc(dev); + + usb2_fifo_detach(&sc->sc_fifo); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static int +ufm_open(struct usb2_fifo *dev, int fflags, struct thread *td) +{ + if ((fflags & (FWRITE | FREAD)) != (FWRITE | FREAD)) { + return (EACCES); + } + return (0); +} + +static int +ufm_do_req(struct ufm_softc *sc, uint8_t request, + uint16_t value, uint16_t index, uint8_t *retbuf) +{ + int error; + + struct usb2_device_request req; + uint8_t buf[1]; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 1); + + error = usb2_do_request(sc->sc_udev, NULL, &req, buf); + + if (retbuf) { + *retbuf = buf[0]; + } + if (error) { + return (ENXIO); + } + return (0); +} + +static int +ufm_set_freq(struct ufm_softc *sc, void *addr) +{ + int freq = *(int *)addr; + + /* + * Freq now is in Hz. We need to convert it to the frequency + * that the radio wants. This frequency is 10.7MHz above + * the actual frequency. We then need to convert to + * units of 12.5kHz. We add one to the IFM to make rounding + * easier. + */ + mtx_lock(&sc->sc_mtx); + sc->sc_freq = freq; + mtx_unlock(&sc->sc_mtx); + + freq = (freq + 10700001) / 12500; + + /* This appears to set the frequency */ + if (ufm_do_req(sc, UFM_CMD_SET_FREQ, + freq >> 8, freq, NULL) != 0) { + return (EIO); + } + /* Not sure what this does */ + if (ufm_do_req(sc, UFM_CMD0, + 0x96, 0xb7, NULL) != 0) { + return (EIO); + } + return (0); +} + +static int +ufm_get_freq(struct ufm_softc *sc, void *addr) +{ + int *valp = (int *)addr; + + mtx_lock(&sc->sc_mtx); + *valp = sc->sc_freq; + mtx_unlock(&sc->sc_mtx); + return (0); +} + +static int +ufm_start(struct ufm_softc *sc, void *addr) +{ + uint8_t ret; + + if (ufm_do_req(sc, UFM_CMD0, + 0x00, 0xc7, &ret)) { + return (EIO); + } + if (ufm_do_req(sc, UFM_CMD2, + 0x01, 0x00, &ret)) { + return (EIO); + } + if (ret & 0x1) { + return (EIO); + } + return (0); +} + +static int +ufm_stop(struct ufm_softc *sc, void *addr) +{ + if (ufm_do_req(sc, UFM_CMD0, + 0x16, 0x1C, NULL)) { + return (EIO); + } + if (ufm_do_req(sc, UFM_CMD2, + 0x00, 0x00, NULL)) { + return (EIO); + } + return (0); +} + +static int +ufm_get_stat(struct ufm_softc *sc, void *addr) +{ + uint8_t ret; + + /* + * Note, there's a 240ms settle time before the status + * will be valid, so sleep that amount. + */ + + mtx_lock(&sc->sc_mtx); + usb2_pause_mtx(&sc->sc_mtx, USB_MS_HZ / 4); + mtx_unlock(&sc->sc_mtx); + + if (ufm_do_req(sc, UFM_CMD0, + 0x00, 0x24, &ret)) { + return (EIO); + } + *(int *)addr = ret; + + return (0); +} + +static int +ufm_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr, + int fflags, struct thread *td) +{ + struct ufm_softc *sc = fifo->priv_sc0; + int error = 0; + + switch (cmd) { + case FM_SET_FREQ: + error = ufm_set_freq(sc, addr); + break; + case FM_GET_FREQ: + error = ufm_get_freq(sc, addr); + break; + case FM_START: + error = ufm_start(sc, addr); + break; + case FM_STOP: + error = ufm_stop(sc, addr); + break; + case FM_GET_STAT: + error = ufm_get_stat(sc, addr); + break; + default: + error = ENOTTY; + break; + } + return (error); +} diff --git a/sys/dev/usb2/misc/usb2_misc.c b/sys/dev/usb2/misc/usb2_misc.c new file mode 100644 index 000000000000..74eb6c52435d --- /dev/null +++ b/sys/dev/usb2/misc/usb2_misc.c @@ -0,0 +1,31 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_misc, 1); +MODULE_DEPEND(usb2_misc, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/misc/usb2_misc.h b/sys/dev/usb2/misc/usb2_misc.h new file mode 100644 index 000000000000..dabf9e340748 --- /dev/null +++ b/sys/dev/usb2/misc/usb2_misc.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_MISC_H_ +#define _USB2_MISC_H_ + +#endif /* _USB2_MISC_H_ */ diff --git a/sys/dev/usb2/ndis/if_ndis_usb2.c b/sys/dev/usb2/ndis/if_ndis_usb2.c new file mode 100644 index 000000000000..3e2aaa2a6c63 --- /dev/null +++ b/sys/dev/usb2/ndis/if_ndis_usb2.c @@ -0,0 +1,144 @@ +/*- + * Copyright (c) 2005 + * Bill Paul . 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +MODULE_DEPEND(ndis, usb2_ndis, 1, 1, 1); +MODULE_DEPEND(ndis, usb2_core, 1, 1, 1); +MODULE_DEPEND(ndis, ndisapi, 1, 1, 1); +MODULE_DEPEND(ndis, if_ndis, 1, 1, 1); + +static device_probe_t ndisusb2_probe; +static device_attach_t ndisusb2_attach; +static struct resource_list *ndis_get_resource_list(device_t, device_t); + +extern device_attach_t ndis_attach; +extern device_shutdown_t ndis_shutdown; +extern device_detach_t ndis_detach; +extern device_suspend_t ndis_suspend; +extern device_resume_t ndis_resume; +extern int ndisdrv_modevent(module_t, int, void *); + +extern unsigned char drv_data[]; + +static device_method_t ndis_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ndisusb2_probe), + DEVMETHOD(device_attach, ndisusb2_attach), + DEVMETHOD(device_detach, ndis_detach), + DEVMETHOD(device_shutdown, ndis_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + DEVMETHOD(bus_get_resource_list, ndis_get_resource_list), + + {0, 0} +}; + +static driver_t ndis_driver = { + "ndis", + ndis_methods, + sizeof(struct ndis_softc) +}; + +static devclass_t ndis_devclass; + +DRIVER_MODULE(ndis, ushub, ndis_driver, ndis_devclass, ndisdrv_modevent, 0); + +static int +ndisusb2_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (windrv_lookup(0, "USB Bus") == NULL) { + return (ENXIO); + } + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + return (ENXIO); +} + +static int +ndisusb2_attach(device_t dev) +{ + struct ndis_softc *sc = device_get_softc(dev); + driver_object *drv; + + sc->ndis_dev = dev; + + /* Create PDO for this device instance */ + + drv = windrv_lookup(0, "USB Bus"); + windrv_create_pdo(drv, dev); + + if (ndis_attach(dev) != 0) { + return (ENXIO); + } + return (0); /* success */ +} + +static struct resource_list * +ndis_get_resource_list(device_t dev, device_t child) +{ + struct ndis_softc *sc = device_get_softc(dev); + + return (BUS_GET_RESOURCE_LIST(device_get_parent(sc->ndis_dev), dev)); +} diff --git a/sys/dev/usb2/ndis/usb2_ndis.c b/sys/dev/usb2/ndis/usb2_ndis.c new file mode 100644 index 000000000000..1776e0d1cfc6 --- /dev/null +++ b/sys/dev/usb2/ndis/usb2_ndis.c @@ -0,0 +1,31 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_ndis, 1); +MODULE_DEPEND(usb2_ndis, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/ndis/usb2_ndis.h b/sys/dev/usb2/ndis/usb2_ndis.h new file mode 100644 index 000000000000..7f187e1fc6e4 --- /dev/null +++ b/sys/dev/usb2/ndis/usb2_ndis.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_NDIS_H_ +#define _USB2_NDIS_H_ + +#endif /* _USB2_NDIS_H_ */ diff --git a/sys/dev/usb2/quirk/usb2_quirk.c b/sys/dev/usb2/quirk/usb2_quirk.c new file mode 100644 index 000000000000..dbaaa6a59234 --- /dev/null +++ b/sys/dev/usb2/quirk/usb2_quirk.c @@ -0,0 +1,372 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include + +#include + +MODULE_DEPEND(usb2_quirk, usb2_core, 1, 1, 1); +MODULE_VERSION(usb2_quirk, 1); + +/* + * The following macro adds one or more quirks for a USB device: + */ +#define USB_QUIRK_ENTRY(v,p,l,h,...) \ + .vid = (v), .pid = (p), .lo_rev = (l), .hi_rev = (h), .quirks = { __VA_ARGS__ } + +#define USB_DEV_QUIRKS_MAX 128 +#define USB_SUB_QUIRKS_MAX 8 + +struct usb2_quirk_entry { + uint16_t vid; + uint16_t pid; + uint16_t lo_rev; + uint16_t hi_rev; + uint16_t quirks[USB_SUB_QUIRKS_MAX]; +}; + +static struct mtx usb2_quirk_mtx; + +static struct usb2_quirk_entry usb2_quirks[USB_DEV_QUIRKS_MAX] = { + {USB_QUIRK_ENTRY(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_LCM, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_INSIDEOUT, USB_PRODUCT_INSIDEOUT_EDGEPORT4, 0x094, 0x094, UQ_SWAP_UNICODE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_DALLAS, USB_PRODUCT_DALLAS_J6502, 0x0a2, 0x0a2, UQ_BAD_ADC, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_DALLAS, USB_PRODUCT_DALLAS_J6502, 0x0a2, 0x0a2, UQ_AU_NO_XU, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_ALTEC, USB_PRODUCT_ALTEC_ADA70, 0x103, 0x103, UQ_BAD_ADC, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_ALTEC, USB_PRODUCT_ALTEC_ASC495, 0x000, 0x000, UQ_BAD_AUDIO, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_QTRONIX, USB_PRODUCT_QTRONIX_980N, 0x110, 0x110, UQ_SPUR_BUT_UP, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_ALCOR2, USB_PRODUCT_ALCOR2_KBD_HUB, 0x001, 0x001, UQ_SPUR_BUT_UP, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_MCT, USB_PRODUCT_MCT_HUB0100, 0x102, 0x102, UQ_BUS_POWERED, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_MCT, USB_PRODUCT_MCT_USB232, 0x102, 0x102, UQ_BUS_POWERED, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_TI, USB_PRODUCT_TI_UTUSB41, 0x110, 0x110, UQ_POWER_CLAIM, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_TELEX, USB_PRODUCT_TELEX_MIC1, 0x009, 0x009, UQ_AU_NO_FRAC, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_SILICONPORTALS, USB_PRODUCT_SILICONPORTALS_YAPPHONE, 0x100, 0x100, UQ_AU_INP_ASYNC, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_UN53B, 0x0000, 0xFFFF, UQ_NO_STRINGS, UQ_NONE)}, + + /* + * XXX The following quirks should have a more specific revision + * number: + */ + {USB_QUIRK_ENTRY(USB_VENDOR_HP, USB_PRODUCT_HP_895C, 0x0000, 0xFFFF, UQ_BROKEN_BIDIR, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_HP, USB_PRODUCT_HP_880C, 0x0000, 0xFFFF, UQ_BROKEN_BIDIR, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_HP, USB_PRODUCT_HP_815C, 0x0000, 0xFFFF, UQ_BROKEN_BIDIR, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_HP, USB_PRODUCT_HP_810C, 0x0000, 0xFFFF, UQ_BROKEN_BIDIR, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_HP, USB_PRODUCT_HP_830C, 0x0000, 0xFFFF, UQ_BROKEN_BIDIR, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_HP, USB_PRODUCT_HP_1220C, 0x0000, 0xFFFF, UQ_BROKEN_BIDIR, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_XEROX, USB_PRODUCT_XEROX_WCM15, 0x0000, 0xFFFF, UQ_BROKEN_BIDIR, UQ_NONE)}, + /* Devices which should be ignored by uhid */ + {USB_QUIRK_ENTRY(USB_VENDOR_APC, USB_PRODUCT_APC_UPS, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F6C550AVR, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_CYBERPOWER, USB_PRODUCT_CYBERPOWER_1500CAVRLCD, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_ITUNERNET, USB_PRODUCT_ITUNERNET_USBLCD2X20, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_MGE, USB_PRODUCT_MGE_UPS1, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_MGE, USB_PRODUCT_MGE_UPS2, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3G, 0x0000, 0xFFFF, UQ_HID_IGNORE, UQ_NONE)}, + /* Devices which should be ignored by both ukbd and uhid */ + {USB_QUIRK_ENTRY(USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_WISPY1A, 0x0000, 0xFFFF, UQ_KBD_IGNORE, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_METAGEEK, USB_PRODUCT_METAGEEK_WISPY1B, 0x0000, 0xFFFF, UQ_KBD_IGNORE, UQ_HID_IGNORE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_TENX, USB_PRODUCT_TENX_UAUDIO0, 0x0101, 0x0101, UQ_AUDIO_SWAP_LR, UQ_NONE)}, + /* MS keyboards do weird things */ + {USB_QUIRK_ENTRY(USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLNOTEBOOK, 0x0000, 0xFFFF, UQ_MS_BAD_CLASS, UQ_MS_LEADING_BYTE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLNOTEBOOK2, 0x0000, 0xFFFF, UQ_MS_BAD_CLASS, UQ_MS_LEADING_BYTE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLINTELLIMOUSE, 0x0000, 0xFFFF, UQ_MS_LEADING_BYTE, UQ_NONE)}, + {USB_QUIRK_ENTRY(USB_VENDOR_METAGEEK, USB_PRODUCT_METAGEEK_WISPY24X, 0x0000, 0xFFFF, UQ_KBD_IGNORE, UQ_HID_IGNORE, UQ_NONE)}, +}; + +USB_MAKE_DEBUG_TABLE(USB_QUIRK); + +/*------------------------------------------------------------------------* + * usb2_quirkstr + * + * This function converts an USB quirk code into a string. + *------------------------------------------------------------------------*/ +static const char * +usb2_quirkstr(uint16_t quirk) +{ + return ((quirk < USB_QUIRK_MAX) ? + USB_QUIRK[quirk] : "USB_QUIRK_UNKNOWN"); +} + +/*------------------------------------------------------------------------* + * usb2_test_quirk_by_info + * + * Returns: + * 0: Quirk not found + * Else: Quirk found + *------------------------------------------------------------------------*/ +static uint8_t +usb2_test_quirk_by_info(const struct usb2_lookup_info *info, uint16_t quirk) +{ + uint16_t x; + uint16_t y; + + if (quirk == UQ_NONE) { + return (0); + } + mtx_lock(&usb2_quirk_mtx); + + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb2_quirks[x].vid != info->idVendor) || + (usb2_quirks[x].pid != info->idProduct) || + (usb2_quirks[x].lo_rev > info->bcdDevice) || + (usb2_quirks[x].hi_rev < info->bcdDevice)) { + continue; + } + /* lookup quirk */ + for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) { + if (usb2_quirks[x].quirks[y] == quirk) { + mtx_unlock(&usb2_quirk_mtx); + DPRINTF("Found quirk '%s'.\n", usb2_quirkstr(quirk)); + return (1); + } + } + /* no quirk found */ + break; + } + mtx_unlock(&usb2_quirk_mtx); + return (0); +} + +static struct usb2_quirk_entry * +usb2_quirk_get_entry(uint16_t vid, uint16_t pid, + uint16_t lo_rev, uint16_t hi_rev, uint8_t do_alloc) +{ + uint16_t x; + + mtx_assert(&usb2_quirk_mtx, MA_OWNED); + + if ((vid | pid | lo_rev | hi_rev) == 0) { + /* all zero - special case */ + return (usb2_quirks + USB_DEV_QUIRKS_MAX - 1); + } + /* search for an existing entry */ + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb2_quirks[x].vid != vid) || + (usb2_quirks[x].pid != pid) || + (usb2_quirks[x].lo_rev != lo_rev) || + (usb2_quirks[x].hi_rev != hi_rev)) { + continue; + } + return (usb2_quirks + x); + } + + if (do_alloc == 0) { + /* no match */ + return (NULL); + } + /* search for a free entry */ + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb2_quirks[x].vid | + usb2_quirks[x].pid | + usb2_quirks[x].lo_rev | + usb2_quirks[x].hi_rev) != 0) { + continue; + } + usb2_quirks[x].vid = vid; + usb2_quirks[x].pid = pid; + usb2_quirks[x].lo_rev = lo_rev; + usb2_quirks[x].hi_rev = hi_rev; + + return (usb2_quirks + x); + } + + /* no entry found */ + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb2_quirk_ioctl - handle quirk IOCTLs + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb2_quirk_ioctl(unsigned long cmd, caddr_t data, + int fflag, struct thread *td) +{ + struct usb2_gen_quirk *pgq; + struct usb2_quirk_entry *pqe; + uint32_t x; + uint32_t y; + int err; + + switch (cmd) { + case USB_DEV_QUIRK_GET: + pgq = (void *)data; + x = pgq->index % USB_SUB_QUIRKS_MAX; + y = pgq->index / USB_SUB_QUIRKS_MAX; + if (y >= USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + mtx_lock(&usb2_quirk_mtx); + /* copy out data */ + pgq->vid = usb2_quirks[y].vid; + pgq->pid = usb2_quirks[y].pid; + pgq->bcdDeviceLow = usb2_quirks[y].lo_rev; + pgq->bcdDeviceHigh = usb2_quirks[y].hi_rev; + strlcpy(pgq->quirkname, + usb2_quirkstr(usb2_quirks[y].quirks[x]), + sizeof(pgq->quirkname)); + mtx_unlock(&usb2_quirk_mtx); + return (0); /* success */ + + case USB_QUIRK_NAME_GET: + pgq = (void *)data; + x = pgq->index; + if (x >= USB_QUIRK_MAX) { + return (EINVAL); + } + strlcpy(pgq->quirkname, + usb2_quirkstr(x), sizeof(pgq->quirkname)); + return (0); /* success */ + + case USB_DEV_QUIRK_ADD: + pgq = (void *)data; + + /* check privileges */ + err = priv_check(curthread, PRIV_DRIVER); + if (err) { + return (err); + } + /* convert quirk string into numerical */ + for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) { + if (strcmp(pgq->quirkname, usb2_quirkstr(y)) == 0) { + break; + } + } + if (y == USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + if (y == UQ_NONE) { + return (EINVAL); + } + mtx_lock(&usb2_quirk_mtx); + pqe = usb2_quirk_get_entry(pgq->vid, pgq->pid, + pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 1); + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] == UQ_NONE) { + pqe->quirks[x] = y; + break; + } + } + mtx_unlock(&usb2_quirk_mtx); + if (x == USB_SUB_QUIRKS_MAX) { + return (ENOMEM); + } + return (0); /* success */ + + case USB_DEV_QUIRK_REMOVE: + pgq = (void *)data; + /* check privileges */ + err = priv_check(curthread, PRIV_DRIVER); + if (err) { + return (err); + } + /* convert quirk string into numerical */ + for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) { + if (strcmp(pgq->quirkname, usb2_quirkstr(y)) == 0) { + break; + } + } + if (y == USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + if (y == UQ_NONE) { + return (EINVAL); + } + mtx_lock(&usb2_quirk_mtx); + pqe = usb2_quirk_get_entry(pgq->vid, pgq->pid, + pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 0); + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] == y) { + pqe->quirks[x] = UQ_NONE; + break; + } + } + if (x == USB_SUB_QUIRKS_MAX) { + mtx_unlock(&usb2_quirk_mtx); + return (ENOMEM); + } + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] != UQ_NONE) { + break; + } + } + if (x == USB_SUB_QUIRKS_MAX) { + /* all quirk entries are unused - release */ + memset(pqe, 0, sizeof(pqe)); + } + mtx_unlock(&usb2_quirk_mtx); + return (0); /* success */ + + default: + break; + } + return (ENOIOCTL); +} + +static void +usb2_quirk_init(void *arg) +{ + /* initialize mutex */ + mtx_init(&usb2_quirk_mtx, "USB quirk", NULL, MTX_DEF); + + /* register our function */ + usb2_test_quirk_p = &usb2_test_quirk_by_info; + usb2_quirk_ioctl_p = &usb2_quirk_ioctl; + return; +} + +static void +usb2_quirk_uninit(void *arg) +{ + usb2_quirk_unload(arg); + + /* destroy mutex */ + mtx_destroy(&usb2_quirk_mtx); + return; +} + +SYSINIT(usb2_quirk_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb2_quirk_init, NULL); +SYSUNINIT(usb2_quirk_uninit, SI_SUB_LOCK, SI_ORDER_ANY, usb2_quirk_uninit, NULL); diff --git a/sys/dev/usb2/quirk/usb2_quirk.h b/sys/dev/usb2/quirk/usb2_quirk.h new file mode 100644 index 000000000000..9b57a2e6c0c3 --- /dev/null +++ b/sys/dev/usb2/quirk/usb2_quirk.h @@ -0,0 +1,83 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_QUIRK_H_ +#define _USB2_QUIRK_H_ + +/* NOTE: UQ_NONE is not a valid quirk */ + +#define USB_QUIRK(m,n) \ + m(n, UQ_NONE) \ + /* left and right sound channels are swapped */ \ + m(n, UQ_AUDIO_SWAP_LR) \ + /* input is async despite claim of adaptive */ \ + m(n, UQ_AU_INP_ASYNC) \ + /* don't adjust for fractional samples */ \ + m(n, UQ_AU_NO_FRAC) \ + /* audio device has broken extension unit */ \ + m(n, UQ_AU_NO_XU) \ + /* bad audio spec version number */ \ + m(n, UQ_BAD_ADC) \ + /* device claims audio class, but isn't */ \ + m(n, UQ_BAD_AUDIO) \ + /* printer has broken bidir mode */ \ + m(n, UQ_BROKEN_BIDIR) \ + /* device is bus powered, despite claim */ \ + m(n, UQ_BUS_POWERED) \ + /* device should be ignored by hid class */ \ + m(n, UQ_HID_IGNORE) \ + /* device should be ignored by kbd class */ \ + m(n, UQ_KBD_IGNORE) \ + /* doesn't identify properly */ \ + m(n, UQ_MS_BAD_CLASS) \ + /* mouse sends an unknown leading byte */ \ + m(n, UQ_MS_LEADING_BYTE) \ + /* mouse has Z-axis reversed */ \ + m(n, UQ_MS_REVZ) \ + /* string descriptors are broken */ \ + m(n, UQ_NO_STRINGS) \ + /* device needs clear endpoint stall */ \ + m(n, UQ_OPEN_CLEARSTALL) \ + /* hub lies about power status */ \ + m(n, UQ_POWER_CLAIM) \ + /* spurious mouse button up events */ \ + m(n, UQ_SPUR_BUT_UP) \ + /* has some Unicode strings swapped */ \ + m(n, UQ_SWAP_UNICODE) \ + /* select configuration index 1 by default */ \ + m(n, UQ_CFG_INDEX_1) \ + /* select configuration index 2 by default */ \ + m(n, UQ_CFG_INDEX_2) \ + /* select configuration index 3 by default */ \ + m(n, UQ_CFG_INDEX_3) \ + /* select configuration index 4 by default */ \ + m(n, UQ_CFG_INDEX_4) \ + /* select configuration index 0 by default */ \ + m(n, UQ_CFG_INDEX_0) + +USB_MAKE_ENUM(USB_QUIRK); + +#endif /* _USB2_QUIRK_H_ */ diff --git a/sys/dev/usb2/serial/uark2.c b/sys/dev/usb2/serial/uark2.c new file mode 100644 index 000000000000..5503140c4379 --- /dev/null +++ b/sys/dev/usb2/serial/uark2.c @@ -0,0 +1,482 @@ +/* $OpenBSD: uark.c,v 1.1 2006/08/14 08:30:22 jsg Exp $ */ + +/* + * Copyright (c) 2006 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD$ + */ + +/* + * NOTE: all function names beginning like "uark_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define UARK_BUF_SIZE 1024 /* bytes */ + +#define UARK_N_TRANSFER 4 /* units */ + +#define UARK_SET_DATA_BITS(x) ((x) - 5) + +#define UARK_PARITY_NONE 0x00 +#define UARK_PARITY_ODD 0x08 +#define UARK_PARITY_EVEN 0x18 + +#define UARK_STOP_BITS_1 0x00 +#define UARK_STOP_BITS_2 0x04 + +#define UARK_BAUD_REF 3000000 + +#define UARK_WRITE 0x40 +#define UARK_READ 0xc0 + +#define UARK_REQUEST 0xfe + +#define UARK_CONFIG_INDEX 0 +#define UARK_IFACE_INDEX 0 + +struct uark_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer[UARK_N_TRANSFER]; + struct usb2_device *sc_udev; + + uint8_t sc_flags; +#define UARK_FLAG_BULK_READ_STALL 0x01 +#define UARK_FLAG_BULK_WRITE_STALL 0x02 + uint8_t sc_msr; + uint8_t sc_lsr; +}; + +/* prototypes */ + +static device_probe_t uark_probe; +static device_attach_t uark_attach; +static device_detach_t uark_detach; + +static usb2_callback_t uark_bulk_write_callback; +static usb2_callback_t uark_bulk_write_clear_stall_callback; +static usb2_callback_t uark_bulk_read_callback; +static usb2_callback_t uark_bulk_read_clear_stall_callback; + +static void uark_start_read(struct usb2_com_softc *ucom); +static void uark_stop_read(struct usb2_com_softc *ucom); +static void uark_start_write(struct usb2_com_softc *ucom); +static void uark_stop_write(struct usb2_com_softc *ucom); + +static int uark_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void uark_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static void uark_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static void uark_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static void uark_cfg_write(struct uark_softc *sc, uint16_t index, uint16_t value); + +static const struct usb2_config + uark_xfer_config[UARK_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UARK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &uark_bulk_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UARK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &uark_bulk_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &uark_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &uark_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback uark_callback = { + .usb2_com_cfg_get_status = &uark_cfg_get_status, + .usb2_com_cfg_set_break = &uark_cfg_set_break, + .usb2_com_cfg_param = &uark_cfg_param, + .usb2_com_pre_param = &uark_pre_param, + .usb2_com_start_read = &uark_start_read, + .usb2_com_stop_read = &uark_stop_read, + .usb2_com_start_write = &uark_start_write, + .usb2_com_stop_write = &uark_stop_write, +}; + +static device_method_t uark_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, uark_probe), + DEVMETHOD(device_attach, uark_attach), + DEVMETHOD(device_detach, uark_detach), + {0, 0} +}; + +static devclass_t uark_devclass; + +static driver_t uark_driver = { + .name = "uark", + .methods = uark_methods, + .size = sizeof(struct uark_softc), +}; + +DRIVER_MODULE(uark, ushub, uark_driver, uark_devclass, NULL, 0); +MODULE_DEPEND(uark, usb2_serial, 1, 1, 1); +MODULE_DEPEND(uark, usb2_core, 1, 1, 1); +MODULE_DEPEND(uark, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static const struct usb2_device_id uark_devs[] = { + {USB_VPI(USB_VENDOR_ARKMICRO, USB_PRODUCT_ARKMICRO_ARK3116, 0)}, +}; + +static int +uark_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UARK_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(uark_devs, sizeof(uark_devs), uaa)); +} + +static int +uark_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uark_softc *sc = device_get_softc(dev); + int32_t error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + sc->sc_udev = uaa->device; + + iface_index = UARK_IFACE_INDEX; + error = usb2_transfer_setup + (uaa->device, &iface_index, sc->sc_xfer, + uark_xfer_config, UARK_N_TRANSFER, sc, &Giant); + + if (error) { + device_printf(dev, "allocating control USB " + "transfers failed!\n"); + goto detach; + } + /* clear stall at first run */ + sc->sc_flags |= (UARK_FLAG_BULK_WRITE_STALL | + UARK_FLAG_BULK_READ_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uark_callback, &Giant); + if (error) { + DPRINTF("usb2_com_attach failed\n"); + goto detach; + } + return (0); /* success */ + +detach: + uark_detach(dev); + return (ENXIO); /* failure */ +} + +static int +uark_detach(device_t dev) +{ + struct uark_softc *sc = device_get_softc(dev); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UARK_N_TRANSFER); + + return (0); +} + +static void +uark_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct uark_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flags & UARK_FLAG_BULK_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UARK_BUF_SIZE, &actlen)) { + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UARK_FLAG_BULK_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} + +static void +uark_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uark_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UARK_FLAG_BULK_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uark_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct uark_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, + xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flags & UARK_FLAG_BULK_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UARK_FLAG_BULK_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +uark_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uark_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UARK_FLAG_BULK_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uark_start_read(struct usb2_com_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +uark_stop_read(struct usb2_com_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +uark_start_write(struct usb2_com_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +uark_stop_write(struct usb2_com_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static int +uark_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + if ((t->c_ospeed < 300) || (t->c_ospeed > 115200)) + return (EINVAL); + return (0); +} + +static void +uark_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uark_softc *sc = ucom->sc_parent; + uint32_t speed = t->c_ospeed; + uint16_t data; + + /* + * NOTE: When reverse computing the baud rate from the "data" all + * allowed baud rates are within 3% of the initial baud rate. + */ + data = (UARK_BAUD_REF + (speed / 2)) / speed; + + uark_cfg_write(sc, 3, 0x83); + uark_cfg_write(sc, 0, data & 0xFF); + uark_cfg_write(sc, 1, data >> 8); + uark_cfg_write(sc, 3, 0x03); + + if (t->c_cflag & CSTOPB) + data = UARK_STOP_BITS_2; + else + data = UARK_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) + data |= UARK_PARITY_ODD; + else + data |= UARK_PARITY_EVEN; + } else + data |= UARK_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + data |= UARK_SET_DATA_BITS(5); + break; + case CS6: + data |= UARK_SET_DATA_BITS(6); + break; + case CS7: + data |= UARK_SET_DATA_BITS(7); + break; + default: + case CS8: + data |= UARK_SET_DATA_BITS(8); + break; + } + uark_cfg_write(sc, 3, 0x00); + uark_cfg_write(sc, 3, data); + return; +} + +static void +uark_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uark_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; + return; +} + +static void +uark_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uark_softc *sc = ucom->sc_parent; + + DPRINTF("onoff=%d\n", onoff); + + uark_cfg_write(sc, 4, onoff ? 0x01 : 0x00); + return; +} + +static void +uark_cfg_write(struct uark_softc *sc, uint16_t index, uint16_t value) +{ + struct usb2_device_request req; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + return; + } + req.bmRequestType = UARK_WRITE; + req.bRequest = UARK_REQUEST; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 0); + + err = usb2_do_request_flags + (sc->sc_udev, &Giant, &req, NULL, 0, NULL, 1000); + + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + } + return; +} diff --git a/sys/dev/usb2/serial/ubsa2.c b/sys/dev/usb2/serial/ubsa2.c new file mode 100644 index 000000000000..140e0908fb64 --- /dev/null +++ b/sys/dev/usb2/serial/ubsa2.c @@ -0,0 +1,755 @@ +/*- + * Copyright (c) 2002, Alexander Kabaev . + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ichiro@ichiro.org). + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR ubsa_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int ubsa_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ubsa, CTLFLAG_RW, 0, "USB ubsa"); +SYSCTL_INT(_hw_usb2_ubsa, OID_AUTO, debug, CTLFLAG_RW, + &ubsa_debug, 0, "ubsa debug level"); +#endif + +#define UBSA_N_TRANSFER 6 /* units */ +#define UBSA_BSIZE 1024 /* bytes */ + +#define UBSA_CONFIG_INDEX 1 +#define UBSA_IFACE_INDEX 0 + +#define UBSA_REG_BAUDRATE 0x00 +#define UBSA_REG_STOP_BITS 0x01 +#define UBSA_REG_DATA_BITS 0x02 +#define UBSA_REG_PARITY 0x03 +#define UBSA_REG_DTR 0x0A +#define UBSA_REG_RTS 0x0B +#define UBSA_REG_BREAK 0x0C +#define UBSA_REG_FLOW_CTRL 0x10 + +#define UBSA_PARITY_NONE 0x00 +#define UBSA_PARITY_EVEN 0x01 +#define UBSA_PARITY_ODD 0x02 +#define UBSA_PARITY_MARK 0x03 +#define UBSA_PARITY_SPACE 0x04 + +#define UBSA_FLOW_NONE 0x0000 +#define UBSA_FLOW_OCTS 0x0001 +#define UBSA_FLOW_ODSR 0x0002 +#define UBSA_FLOW_IDSR 0x0004 +#define UBSA_FLOW_IDTR 0x0008 +#define UBSA_FLOW_IRTS 0x0010 +#define UBSA_FLOW_ORTS 0x0020 +#define UBSA_FLOW_UNKNOWN 0x0040 +#define UBSA_FLOW_OXON 0x0080 +#define UBSA_FLOW_IXON 0x0100 + +/* line status register */ +#define UBSA_LSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define UBSA_LSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define UBSA_LSR_BI 0x10 /* Break detected */ +#define UBSA_LSR_FE 0x08 /* Framing error: bad stop bit */ +#define UBSA_LSR_PE 0x04 /* Parity error */ +#define UBSA_LSR_OE 0x02 /* Overrun, lost incoming byte */ +#define UBSA_LSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define UBSA_LSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +/* modem status register */ +/* All deltas are from the last read of the MSR. */ +#define UBSA_MSR_DCD 0x80 /* Current Data Carrier Detect */ +#define UBSA_MSR_RI 0x40 /* Current Ring Indicator */ +#define UBSA_MSR_DSR 0x20 /* Current Data Set Ready */ +#define UBSA_MSR_CTS 0x10 /* Current Clear to Send */ +#define UBSA_MSR_DDCD 0x08 /* DCD has changed state */ +#define UBSA_MSR_TERI 0x04 /* RI has toggled low to high */ +#define UBSA_MSR_DDSR 0x02 /* DSR has changed state */ +#define UBSA_MSR_DCTS 0x01 /* CTS has changed state */ + +struct ubsa_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer[UBSA_N_TRANSFER]; + struct usb2_device *sc_udev; + + uint16_t sc_flag; +#define UBSA_FLAG_WRITE_STALL 0x0001 +#define UBSA_FLAG_READ_STALL 0x0002 +#define UBSA_FLAG_INTR_STALL 0x0004 + + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_iface_index; /* interface index */ + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* UBSA status register */ +}; + +static device_probe_t ubsa_probe; +static device_attach_t ubsa_attach; +static device_detach_t ubsa_detach; + +static usb2_callback_t ubsa_write_callback; +static usb2_callback_t ubsa_write_clear_stall_callback; +static usb2_callback_t ubsa_read_callback; +static usb2_callback_t ubsa_read_clear_stall_callback; +static usb2_callback_t ubsa_intr_callback; +static usb2_callback_t ubsa_intr_clear_stall_callback; + +static void ubsa_cfg_request(struct ubsa_softc *sc, uint8_t index, uint16_t value); +static void ubsa_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void ubsa_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static void ubsa_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static int ubsa_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void ubsa_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static void ubsa_start_read(struct usb2_com_softc *ucom); +static void ubsa_stop_read(struct usb2_com_softc *ucom); +static void ubsa_start_write(struct usb2_com_softc *ucom); +static void ubsa_stop_write(struct usb2_com_softc *ucom); +static void ubsa_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); + +static const struct usb2_config ubsa_config[UBSA_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UBSA_BSIZE, /* bytes */ + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &ubsa_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UBSA_BSIZE, /* bytes */ + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &ubsa_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ubsa_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ubsa_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &ubsa_intr_callback, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ubsa_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback ubsa_callback = { + .usb2_com_cfg_get_status = &ubsa_cfg_get_status, + .usb2_com_cfg_set_dtr = &ubsa_cfg_set_dtr, + .usb2_com_cfg_set_rts = &ubsa_cfg_set_rts, + .usb2_com_cfg_set_break = &ubsa_cfg_set_break, + .usb2_com_cfg_param = &ubsa_cfg_param, + .usb2_com_pre_param = &ubsa_pre_param, + .usb2_com_start_read = &ubsa_start_read, + .usb2_com_stop_read = &ubsa_stop_read, + .usb2_com_start_write = &ubsa_start_write, + .usb2_com_stop_write = &ubsa_stop_write, +}; + +static const struct usb2_device_id ubsa_devs[] = { + /* AnyData ADU-500A */ + {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_500A, 0)}, + /* AnyData ADU-E100A/H */ + {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_E100X, 0)}, + /* Axesstel MV100H */ + {USB_VPI(USB_VENDOR_AXESSTEL, USB_PRODUCT_AXESSTEL_DATAMODEM, 0)}, + /* BELKIN F5U103 */ + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U103, 0)}, + /* BELKIN F5U120 */ + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U120, 0)}, + /* GoHubs GO-COM232 */ + {USB_VPI(USB_VENDOR_ETEK, USB_PRODUCT_ETEK_1COM, 0)}, + /* GoHubs GO-COM232 */ + {USB_VPI(USB_VENDOR_GOHUBS, USB_PRODUCT_GOHUBS_GOCOM232, 0)}, + /* Peracom */ + {USB_VPI(USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_SERIAL1, 0)}, + /* Novatel Wireless Merlin cards */ + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U740, 0)}, + /* Dell version of the above */ + {USB_VPI(USB_VENDOR_DELL, USB_PRODUCT_DELL_U740, 0)}, + /* Novatel Wireless Merlin v740 */ + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V740, 0)}, + /* Option Vodafone MC3G */ + {USB_VPI(USB_VENDOR_OPTION, USB_PRODUCT_OPTION_VODAFONEMC3G, 0)}, + /* Option GlobeTrotter 3G */ + {USB_VPI(USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3G, 0)}, + /* Option GlobeTrotter 3G+ */ + {USB_VPI(USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3GPLUS, 0)}, + /* Option GlobeTrotter Max 3.6 */ + {USB_VPI(USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GTMAX36, 0)}, + /* Option GlobeTrotter 3G QUAD */ + {USB_VPI(USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3GQUAD, 0)}, + /* Sierra Wireless LENOVO UMTS card */ + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755_3, 0)}, + /* Qualcomm, Inc. ZTE CDMA */ + {USB_VPI(USB_VENDOR_QUALCOMMINC, USB_PRODUCT_QUALCOMMINC_CDMA_MSM, 0)}, +}; + +static device_method_t ubsa_methods[] = { + DEVMETHOD(device_probe, ubsa_probe), + DEVMETHOD(device_attach, ubsa_attach), + DEVMETHOD(device_detach, ubsa_detach), + {0, 0} +}; + +static devclass_t ubsa_devclass; + +static driver_t ubsa_driver = { + .name = "ubsa", + .methods = ubsa_methods, + .size = sizeof(struct ubsa_softc), +}; + +DRIVER_MODULE(ubsa, ushub, ubsa_driver, ubsa_devclass, NULL, 0); +MODULE_DEPEND(ubsa, usb2_serial, 1, 1, 1); +MODULE_DEPEND(ubsa, usb2_core, 1, 1, 1); +MODULE_DEPEND(ubsa, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static int +ubsa_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UBSA_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UBSA_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(ubsa_devs, sizeof(ubsa_devs), uaa)); +} + +static int +ubsa_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ubsa_softc *sc = device_get_softc(dev); + int error; + + DPRINTF("sc=%p\n", sc); + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + sc->sc_udev = uaa->device; + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UBSA_IFACE_INDEX; + + error = usb2_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, ubsa_config, UBSA_N_TRANSFER, sc, &Giant); + + if (error) { + DPRINTF("could not allocate all pipes\n"); + goto detach; + } + /* clear stall at first run */ + sc->sc_flag |= (UBSA_FLAG_WRITE_STALL | + UBSA_FLAG_READ_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ubsa_callback, &Giant); + if (error) { + DPRINTF("usb2_com_attach failed\n"); + goto detach; + } + return (0); + +detach: + ubsa_detach(dev); + return (ENXIO); +} + +static int +ubsa_detach(device_t dev) +{ + struct ubsa_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UBSA_N_TRANSFER); + + return (0); +} + +static void +ubsa_cfg_request(struct ubsa_softc *sc, uint8_t index, uint16_t value) +{ + struct usb2_device_request req; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + return; + } + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = index; + USETW(req.wValue, value); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + err = usb2_do_request_flags + (sc->sc_udev, &Giant, &req, NULL, 0, NULL, 1000); + + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + } + return; +} + +static void +ubsa_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_DTR, onoff ? 1 : 0); + return; +} + +static void +ubsa_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_RTS, onoff ? 1 : 0); + return; +} + +static void +ubsa_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_BREAK, onoff ? 1 : 0); + return; +} + +static int +ubsa_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("sc = %p\n", sc); + + switch (t->c_ospeed) { + case B0: + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + case B230400: + break; + default: + return (EINVAL); + } + return (0); +} + +static void +ubsa_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct ubsa_softc *sc = ucom->sc_parent; + uint16_t value = 0; + + DPRINTF("sc = %p\n", sc); + + switch (t->c_ospeed) { + case B0: + ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, 0); + ubsa_cfg_set_dtr(&sc->sc_ucom, 0); + ubsa_cfg_set_rts(&sc->sc_ucom, 0); + break; + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + case B230400: + value = B230400 / t->c_ospeed; + ubsa_cfg_request(sc, UBSA_REG_BAUDRATE, value); + break; + default: + return; + } + + if (t->c_cflag & PARENB) + value = (t->c_cflag & PARODD) ? UBSA_PARITY_ODD : UBSA_PARITY_EVEN; + else + value = UBSA_PARITY_NONE; + + ubsa_cfg_request(sc, UBSA_REG_PARITY, value); + + switch (t->c_cflag & CSIZE) { + case CS5: + value = 0; + break; + case CS6: + value = 1; + break; + case CS7: + value = 2; + break; + default: + case CS8: + value = 3; + break; + } + + ubsa_cfg_request(sc, UBSA_REG_DATA_BITS, value); + + value = (t->c_cflag & CSTOPB) ? 1 : 0; + + ubsa_cfg_request(sc, UBSA_REG_STOP_BITS, value); + + value = 0; + if (t->c_cflag & CRTSCTS) + value |= UBSA_FLOW_OCTS | UBSA_FLOW_IRTS; + + if (t->c_iflag & (IXON | IXOFF)) + value |= UBSA_FLOW_OXON | UBSA_FLOW_IXON; + + ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, value); + return; +} + +static void +ubsa_start_read(struct usb2_com_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usb2_transfer_start(sc->sc_xfer[4]); + + /* start read endpoint */ + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +ubsa_stop_read(struct usb2_com_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usb2_transfer_stop(sc->sc_xfer[5]); + usb2_transfer_stop(sc->sc_xfer[4]); + + /* stop read endpoint */ + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +ubsa_start_write(struct usb2_com_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +ubsa_stop_write(struct usb2_com_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static void +ubsa_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; + return; +} + +static void +ubsa_write_callback(struct usb2_xfer *xfer) +{ + struct ubsa_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flag & UBSA_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UBSA_BSIZE, &actlen)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UBSA_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} + +static void +ubsa_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubsa_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UBSA_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubsa_read_callback(struct usb2_xfer *xfer) +{ + struct ubsa_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flag & UBSA_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UBSA_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +ubsa_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubsa_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UBSA_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubsa_intr_callback(struct usb2_xfer *xfer) +{ + struct ubsa_softc *sc = xfer->priv_sc; + uint8_t buf[4]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen >= sizeof(buf)) { + + usb2_copy_out(xfer->frbuffers, 0, buf, sizeof(buf)); + + /* + * incidentally, Belkin adapter status bits match + * UART 16550 bits + */ + sc->sc_lsr = buf[2]; + sc->sc_msr = buf[3]; + + DPRINTF("lsr = 0x%02x, msr = 0x%02x\n", + sc->sc_lsr, sc->sc_msr); + + usb2_com_status_change(&sc->sc_ucom); + } else { + DPRINTF("ignoring short packet, %d bytes\n", + xfer->actlen); + } + + case USB_ST_SETUP: + if (sc->sc_flag & UBSA_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UBSA_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + return; + + } +} + +static void +ubsa_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubsa_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UBSA_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} diff --git a/sys/dev/usb2/serial/ubser2.c b/sys/dev/usb2/serial/ubser2.c new file mode 100644 index 000000000000..7b9c88dda304 --- /dev/null +++ b/sys/dev/usb2/serial/ubser2.c @@ -0,0 +1,604 @@ +/*- + * Copyright (c) 2004 Bernd Walter + * + * $URL: https://devel.bwct.de/svn/projects/ubser/ubser.c $ + * $Date: 2004-02-29 01:53:10 +0100 (Sun, 29 Feb 2004) $ + * $Author: ticso $ + * $Rev: 1127 $ + */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama . + * 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. + */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * BWCT serial adapter driver + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR ubser_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define UBSER_UNIT_MAX 32 + +/* Vendor Interface Requests */ +#define VENDOR_GET_NUMSER 0x01 +#define VENDOR_SET_BREAK 0x02 +#define VENDOR_CLEAR_BREAK 0x03 + +#if USB_DEBUG +static int ubser_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ubser, CTLFLAG_RW, 0, "USB ubser"); +SYSCTL_INT(_hw_usb2_ubser, OID_AUTO, debug, CTLFLAG_RW, + &ubser_debug, 0, "ubser debug level"); +#endif + +#define UBSER_TR_DT_WRITE 0 +#define UBSER_TR_DT_READ 1 +#define UBSER_TR_CS_WRITE 2 +#define UBSER_TR_CS_READ 3 +#define UBSER_TR_MAX 4 + +struct ubser_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom[UBSER_UNIT_MAX]; + + struct usb2_xfer *sc_xfer[UBSER_TR_MAX]; + struct usb2_device *sc_udev; + + uint16_t sc_tx_size; + + uint8_t sc_numser; + uint8_t sc_flags; +#define UBSER_FLAG_READ_STALL 0x01 +#define UBSER_FLAG_WRITE_STALL 0x02 + + uint8_t sc_iface_no; + uint8_t sc_iface_index; + uint8_t sc_curr_tx_unit; + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t ubser_probe; +static device_attach_t ubser_attach; +static device_detach_t ubser_detach; + +static usb2_callback_t ubser_write_clear_stall_callback; +static usb2_callback_t ubser_write_callback; +static usb2_callback_t ubser_read_clear_stall_callback; +static usb2_callback_t ubser_read_callback; + +static int ubser_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void ubser_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static void ubser_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static void ubser_start_read(struct usb2_com_softc *ucom); +static void ubser_stop_read(struct usb2_com_softc *ucom); +static void ubser_start_write(struct usb2_com_softc *ucom); +static void ubser_stop_write(struct usb2_com_softc *ucom); + +static const struct usb2_config ubser_config[UBSER_TR_MAX] = { + + [UBSER_TR_DT_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &ubser_write_callback, + }, + + [UBSER_TR_DT_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &ubser_read_callback, + }, + + [UBSER_TR_CS_WRITE] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ubser_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [UBSER_TR_CS_READ] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ubser_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback ubser_callback = { + .usb2_com_cfg_set_break = &ubser_cfg_set_break, + .usb2_com_cfg_get_status = &ubser_cfg_get_status, + .usb2_com_pre_param = &ubser_pre_param, + .usb2_com_start_read = &ubser_start_read, + .usb2_com_stop_read = &ubser_stop_read, + .usb2_com_start_write = &ubser_start_write, + .usb2_com_stop_write = &ubser_stop_write, +}; + +static device_method_t ubser_methods[] = { + DEVMETHOD(device_probe, ubser_probe), + DEVMETHOD(device_attach, ubser_attach), + DEVMETHOD(device_detach, ubser_detach), + {0, 0} +}; + +static devclass_t ubser_devclass; + +static driver_t ubser_driver = { + .name = "ubser", + .methods = ubser_methods, + .size = sizeof(struct ubser_softc), +}; + +DRIVER_MODULE(ubser, ushub, ubser_driver, ubser_devclass, NULL, 0); +MODULE_DEPEND(ubser, usb2_serial, 1, 1, 1); +MODULE_DEPEND(ubser, usb2_core, 1, 1, 1); +MODULE_DEPEND(ubser, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static int +ubser_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* check if this is a BWCT vendor specific ubser interface */ + if ((strcmp(uaa->device->manufacturer, "BWCT") == 0) && + (uaa->info.bInterfaceClass == 0xff) && + (uaa->info.bInterfaceSubClass == 0x00)) + return (0); + + return (ENXIO); +} + +static int +ubser_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ubser_softc *sc = device_get_softc(dev); + struct usb2_device_request req; + uint8_t n; + int error; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = uaa->info.bIfaceIndex; + sc->sc_udev = uaa->device; + + /* get number of serials */ + req.bmRequestType = UT_READ_VENDOR_INTERFACE; + req.bRequest = VENDOR_GET_NUMSER; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + error = usb2_do_request_flags + (uaa->device, &Giant, &req, &sc->sc_numser, + 0, NULL, USB_DEFAULT_TIMEOUT); + + if (error || (sc->sc_numser == 0)) { + device_printf(dev, "failed to get number " + "of serial ports: %s\n", + usb2_errstr(error)); + goto detach; + } + if (sc->sc_numser > UBSER_UNIT_MAX) + sc->sc_numser = UBSER_UNIT_MAX; + + device_printf(dev, "found %i serials\n", sc->sc_numser); + + error = usb2_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, ubser_config, UBSER_TR_MAX, sc, &Giant); + if (error) { + goto detach; + } + sc->sc_tx_size = sc->sc_xfer[UBSER_TR_DT_WRITE]->max_data_length; + + if (sc->sc_tx_size == 0) { + DPRINTFN(0, "invalid tx_size!\n"); + goto detach; + } + /* initialize port numbers */ + + for (n = 0; n < sc->sc_numser; n++) { + sc->sc_ucom[n].sc_portno = n; + } + + error = usb2_com_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_numser, sc, &ubser_callback, &Giant); + if (error) { + goto detach; + } + mtx_lock(&Giant); + + sc->sc_flags |= (UBSER_FLAG_READ_STALL | + UBSER_FLAG_WRITE_STALL); + + usb2_transfer_start(sc->sc_xfer[UBSER_TR_DT_READ]); + + mtx_unlock(&Giant); + + return (0); /* success */ + +detach: + ubser_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ubser_detach(device_t dev) +{ + struct ubser_softc *sc = device_get_softc(dev); + uint8_t n; + + DPRINTF("\n"); + + usb2_com_detach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numser); + + /* + * need to stop all transfers atomically, hence when clear stall + * completes, it might start other transfers ! + */ + mtx_lock(&Giant); + for (n = 0; n < UBSER_TR_MAX; n++) { + usb2_transfer_stop(sc->sc_xfer[n]); + } + mtx_unlock(&Giant); + + usb2_transfer_unsetup(sc->sc_xfer, UBSER_TR_MAX); + + return (0); +} + +static int +ubser_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + DPRINTF("\n"); + + /* + * The firmware on our devices can only do 8n1@9600bps + * without handshake. + * We refuse to accept other configurations. + */ + + /* ensure 9600bps */ + switch (t->c_ospeed) { + case 9600: + break; + default: + return (EINVAL); + } + + /* 2 stop bits not possible */ + if (t->c_cflag & CSTOPB) + return (EINVAL); + + /* XXX parity handling not possible with current firmware */ + if (t->c_cflag & PARENB) + return (EINVAL); + + /* we can only do 8 data bits */ + switch (t->c_cflag & CSIZE) { + case CS8: + break; + default: + return (EINVAL); + } + + /* we can't do any kind of hardware handshaking */ + if ((t->c_cflag & + (CRTS_IFLOW | CDTR_IFLOW | CDSR_OFLOW | CCAR_OFLOW)) != 0) + return (EINVAL); + + /* + * XXX xon/xoff not supported by the firmware! + * This is handled within FreeBSD only and may overflow buffers + * because of delayed reaction due to device buffering. + */ + + return (0); +} + +static __inline void +ubser_inc_tx_unit(struct ubser_softc *sc) +{ + sc->sc_curr_tx_unit++; + if (sc->sc_curr_tx_unit >= sc->sc_numser) { + sc->sc_curr_tx_unit = 0; + } + return; +} + +static void +ubser_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubser_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[UBSER_TR_DT_WRITE]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UBSER_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubser_write_callback(struct usb2_xfer *xfer) +{ + struct ubser_softc *sc = xfer->priv_sc; + uint8_t buf[1]; + uint8_t first_unit = sc->sc_curr_tx_unit; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flags & UBSER_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[UBSER_TR_CS_WRITE]); + return; + } + do { + if (usb2_com_get_data(sc->sc_ucom + sc->sc_curr_tx_unit, + xfer->frbuffers, 1, sc->sc_tx_size - 1, + &actlen)) { + + buf[0] = sc->sc_curr_tx_unit; + + usb2_copy_in(xfer->frbuffers, 0, buf, 1); + + xfer->frlengths[0] = actlen + 1; + usb2_start_hardware(xfer); + + ubser_inc_tx_unit(sc); /* round robin */ + + break; + } + ubser_inc_tx_unit(sc); + + } while (sc->sc_curr_tx_unit != first_unit); + + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UBSER_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[UBSER_TR_CS_WRITE]); + } + return; + + } +} + +static void +ubser_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ubser_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[UBSER_TR_DT_READ]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UBSER_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ubser_read_callback(struct usb2_xfer *xfer) +{ + struct ubser_softc *sc = xfer->priv_sc; + uint8_t buf[1]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen < 1) { + DPRINTF("invalid actlen=0!\n"); + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, 1); + + if (buf[0] >= sc->sc_numser) { + DPRINTF("invalid serial number!\n"); + goto tr_setup; + } + usb2_com_put_data(sc->sc_ucom + buf[0], + xfer->frbuffers, 1, xfer->actlen - 1); + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flags & UBSER_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[UBSER_TR_CS_READ]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UBSER_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[UBSER_TR_CS_READ]); + } + return; + + } +} + +static void +ubser_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct ubser_softc *sc = ucom->sc_parent; + uint8_t x = ucom->sc_portno; + struct usb2_device_request req; + usb2_error_t err; + + if (onoff) { + + req.bmRequestType = UT_READ_VENDOR_INTERFACE; + req.bRequest = VENDOR_SET_BREAK; + req.wValue[0] = x; + req.wValue[1] = 0; + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + err = usb2_do_request_flags + (sc->sc_udev, &Giant, &req, NULL, 0, NULL, 1000); + + if (err) { + DPRINTFN(0, "send break failed, error=%s\n", + usb2_errstr(err)); + } + } + return; +} + +static void +ubser_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + /* fake status bits */ + *lsr = 0; + *msr = SER_DCD; + return; +} + +static void +ubser_start_read(struct usb2_com_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[UBSER_TR_DT_READ]); + return; +} + +static void +ubser_stop_read(struct usb2_com_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[UBSER_TR_CS_READ]); + usb2_transfer_stop(sc->sc_xfer[UBSER_TR_DT_READ]); + return; +} + +static void +ubser_start_write(struct usb2_com_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[UBSER_TR_DT_WRITE]); + return; +} + +static void +ubser_stop_write(struct usb2_com_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[UBSER_TR_CS_WRITE]); + usb2_transfer_stop(sc->sc_xfer[UBSER_TR_DT_WRITE]); + return; +} diff --git a/sys/dev/usb2/serial/uchcom2.c b/sys/dev/usb2/serial/uchcom2.c new file mode 100644 index 000000000000..049c924dcf55 --- /dev/null +++ b/sys/dev/usb2/serial/uchcom2.c @@ -0,0 +1,1038 @@ +/* $NetBSD: uchcom.c,v 1.1 2007/09/03 17:57:37 tshiozak Exp $ */ + +/*- + * Copyright (c) 2007, Takanori Watanabe + * 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. + */ + +/* + * Copyright (c) 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Takuya SHIOZAKI (tshiozak@netbsd.org). + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * driver for WinChipHead CH341/340, the worst USB-serial chip in the world. + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR uchcom_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int uchcom_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uchcom, CTLFLAG_RW, 0, "USB uchcom"); +SYSCTL_INT(_hw_usb2_uchcom, OID_AUTO, debug, CTLFLAG_RW, + &uchcom_debug, 0, "uchcom debug level"); +#endif + +#define UCHCOM_IFACE_INDEX 0 +#define UCHCOM_CONFIG_INDEX 0 + +#define UCHCOM_REV_CH340 0x0250 +#define UCHCOM_INPUT_BUF_SIZE 8 + +#define UCHCOM_REQ_GET_VERSION 0x5F +#define UCHCOM_REQ_READ_REG 0x95 +#define UCHCOM_REQ_WRITE_REG 0x9A +#define UCHCOM_REQ_RESET 0xA1 +#define UCHCOM_REQ_SET_DTRRTS 0xA4 + +#define UCHCOM_REG_STAT1 0x06 +#define UCHCOM_REG_STAT2 0x07 +#define UCHCOM_REG_BPS_PRE 0x12 +#define UCHCOM_REG_BPS_DIV 0x13 +#define UCHCOM_REG_BPS_MOD 0x14 +#define UCHCOM_REG_BPS_PAD 0x0F +#define UCHCOM_REG_BREAK1 0x05 +#define UCHCOM_REG_BREAK2 0x18 +#define UCHCOM_REG_LCR1 0x18 +#define UCHCOM_REG_LCR2 0x25 + +#define UCHCOM_VER_20 0x20 + +#define UCHCOM_BASE_UNKNOWN 0 +#define UCHCOM_BPS_MOD_BASE 20000000 +#define UCHCOM_BPS_MOD_BASE_OFS 1100 + +#define UCHCOM_DTR_MASK 0x20 +#define UCHCOM_RTS_MASK 0x40 + +#define UCHCOM_BRK1_MASK 0x01 +#define UCHCOM_BRK2_MASK 0x40 + +#define UCHCOM_LCR1_MASK 0xAF +#define UCHCOM_LCR2_MASK 0x07 +#define UCHCOM_LCR1_PARENB 0x80 +#define UCHCOM_LCR2_PAREVEN 0x07 +#define UCHCOM_LCR2_PARODD 0x06 +#define UCHCOM_LCR2_PARMARK 0x05 +#define UCHCOM_LCR2_PARSPACE 0x04 + +#define UCHCOM_INTR_STAT1 0x02 +#define UCHCOM_INTR_STAT2 0x03 +#define UCHCOM_INTR_LEAST 4 + +#define UCHCOM_BULK_BUF_SIZE 1024 /* bytes */ +#define UCHCOM_N_TRANSFER 6 /* units */ + +struct uchcom_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer[UCHCOM_N_TRANSFER]; + struct usb2_device *sc_udev; + + uint8_t sc_dtr; /* local copy */ + uint8_t sc_rts; /* local copy */ + uint8_t sc_version; + uint8_t sc_msr; + uint8_t sc_lsr; /* local status register */ + uint8_t sc_flag; +#define UCHCOM_FLAG_INTR_STALL 0x01 +#define UCHCOM_FLAG_READ_STALL 0x02 +#define UCHCOM_FLAG_WRITE_STALL 0x04 +}; + +struct uchcom_divider { + uint8_t dv_prescaler; + uint8_t dv_div; + uint8_t dv_mod; +}; + +struct uchcom_divider_record { + uint32_t dvr_high; + uint32_t dvr_low; + uint32_t dvr_base_clock; + struct uchcom_divider dvr_divider; +}; + +static const struct uchcom_divider_record dividers[] = +{ + {307200, 307200, UCHCOM_BASE_UNKNOWN, {7, 0xD9, 0}}, + {921600, 921600, UCHCOM_BASE_UNKNOWN, {7, 0xF3, 0}}, + {2999999, 23530, 6000000, {3, 0, 0}}, + {23529, 2942, 750000, {2, 0, 0}}, + {2941, 368, 93750, {1, 0, 0}}, + {367, 1, 11719, {0, 0, 0}}, +}; + +#define NUM_DIVIDERS (sizeof (dividers) / sizeof (dividers[0])) + +static const struct usb2_device_id uchcom_devs[] = { + {USB_VPI(USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341SER, 0)}, +}; + +/* protypes */ + +static int uchcom_ioctl(struct usb2_com_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td); +static int uchcom_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void uchcom_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static void uchcom_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static void uchcom_cfg_set_break(struct usb2_com_softc *sc, uint8_t onoff); +static void uchcom_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void uchcom_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static void uchcom_start_read(struct usb2_com_softc *ucom); +static void uchcom_start_write(struct usb2_com_softc *ucom); +static void uchcom_stop_read(struct usb2_com_softc *ucom); +static void uchcom_stop_write(struct usb2_com_softc *ucom); + +static void uchcom_update_version(struct uchcom_softc *sc); +static void uchcom_convert_status(struct uchcom_softc *sc, uint8_t cur); +static void uchcom_update_status(struct uchcom_softc *sc); +static void uchcom_set_dtrrts(struct uchcom_softc *sc); +static int uchcom_calc_divider_settings(struct uchcom_divider *dp, uint32_t rate); +static void uchcom_set_dte_rate(struct uchcom_softc *sc, uint32_t rate); +static void uchcom_set_line_control(struct uchcom_softc *sc, tcflag_t cflag); +static void uchcom_clear_chip(struct uchcom_softc *sc); +static void uchcom_reset_chip(struct uchcom_softc *sc); + +static device_probe_t uchcom_probe; +static device_attach_t uchcom_attach; +static device_detach_t uchcom_detach; + +static usb2_callback_t uchcom_intr_callback; +static usb2_callback_t uchcom_intr_clear_stall_callback; +static usb2_callback_t uchcom_write_callback; +static usb2_callback_t uchcom_write_clear_stall_callback; +static usb2_callback_t uchcom_read_callback; +static usb2_callback_t uchcom_read_clear_stall_callback; + +static const struct usb2_config uchcom_config_data[UCHCOM_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UCHCOM_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &uchcom_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UCHCOM_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &uchcom_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uchcom_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uchcom_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &uchcom_intr_callback, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uchcom_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +struct usb2_com_callback uchcom_callback = { + .usb2_com_cfg_get_status = &uchcom_cfg_get_status, + .usb2_com_cfg_set_dtr = &uchcom_cfg_set_dtr, + .usb2_com_cfg_set_rts = &uchcom_cfg_set_rts, + .usb2_com_cfg_set_break = &uchcom_cfg_set_break, + .usb2_com_cfg_param = &uchcom_cfg_param, + .usb2_com_pre_param = &uchcom_pre_param, + .usb2_com_ioctl = &uchcom_ioctl, + .usb2_com_start_read = &uchcom_start_read, + .usb2_com_stop_read = &uchcom_stop_read, + .usb2_com_start_write = &uchcom_start_write, + .usb2_com_stop_write = &uchcom_stop_write, +}; + +/* ---------------------------------------------------------------------- + * driver entry points + */ + +static int +uchcom_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UCHCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UCHCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(uchcom_devs, sizeof(uchcom_devs), uaa)); +} + +static int +uchcom_attach(device_t dev) +{ + struct uchcom_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + int error; + uint8_t iface_index; + + DPRINTFN(11, "\n"); + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + sc->sc_udev = uaa->device; + + switch (uaa->info.bcdDevice) { + case UCHCOM_REV_CH340: + device_printf(dev, "CH340 detected\n"); + break; + default: + device_printf(dev, "CH341 detected\n"); + break; + } + + iface_index = UCHCOM_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, + &iface_index, sc->sc_xfer, uchcom_config_data, + UCHCOM_N_TRANSFER, sc, &Giant); + + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usb2_errstr(error)); + goto detach; + } + /* + * Do the initialization during attach so that the system does not + * sleep during open: + */ + uchcom_update_version(sc); + uchcom_clear_chip(sc); + uchcom_reset_chip(sc); + uchcom_update_status(sc); + + sc->sc_dtr = 1; + sc->sc_rts = 1; + + /* clear stall at first run */ + sc->sc_flag |= (UCHCOM_FLAG_READ_STALL | + UCHCOM_FLAG_WRITE_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uchcom_callback, &Giant); + if (error) { + goto detach; + } + return (0); + +detach: + uchcom_detach(dev); + return (ENXIO); +} + +static int +uchcom_detach(device_t dev) +{ + struct uchcom_softc *sc = device_get_softc(dev); + + DPRINTFN(11, "\n"); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UCHCOM_N_TRANSFER); + + return (0); +} + +/* ---------------------------------------------------------------------- + * low level i/o + */ + +static void +uchcom_do_request(struct uchcom_softc *sc, + struct usb2_device_request *req, void *data) +{ + uint16_t length; + uint16_t actlen; + usb2_error_t err; + + length = UGETW(req->wLength); + actlen = 0; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + goto done; + } + err = usb2_do_request_flags(sc->sc_udev, &Giant, req, + data, USB_SHORT_XFER_OK, &actlen, 1000); + + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + } +done: + if (length != actlen) { + if (req->bmRequestType & UT_READ) { + bzero(USB_ADD_BYTES(data, actlen), length - actlen); + } + } + return; +} + +static void +uchcom_ctrl_write(struct uchcom_softc *sc, uint8_t reqno, + uint16_t value, uint16_t index) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = reqno; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 0); + + uchcom_do_request(sc, &req, NULL); + return; +} + +static void +uchcom_ctrl_read(struct uchcom_softc *sc, uint8_t reqno, + uint16_t value, uint16_t index, void *buf, uint16_t buflen) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = reqno; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, buflen); + + uchcom_do_request(sc, &req, buf); + return; +} + +static void +uchcom_write_reg(struct uchcom_softc *sc, + uint8_t reg1, uint8_t val1, uint8_t reg2, uint8_t val2) +{ + DPRINTF("0x%02X<-0x%02X, 0x%02X<-0x%02X\n", + (unsigned)reg1, (unsigned)val1, + (unsigned)reg2, (unsigned)val2); + uchcom_ctrl_write( + sc, UCHCOM_REQ_WRITE_REG, + reg1 | ((uint16_t)reg2 << 8), val1 | ((uint16_t)val2 << 8)); + return; +} + +static void +uchcom_read_reg(struct uchcom_softc *sc, + uint8_t reg1, uint8_t *rval1, uint8_t reg2, uint8_t *rval2) +{ + uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; + + uchcom_ctrl_read( + sc, UCHCOM_REQ_READ_REG, + reg1 | ((uint16_t)reg2 << 8), 0, buf, sizeof(buf)); + + DPRINTF("0x%02X->0x%02X, 0x%02X->0x%02X\n", + (unsigned)reg1, (unsigned)buf[0], + (unsigned)reg2, (unsigned)buf[1]); + + if (rval1) + *rval1 = buf[0]; + if (rval2) + *rval2 = buf[1]; + + return; +} + +static void +uchcom_get_version(struct uchcom_softc *sc, uint8_t *rver) +{ + uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; + + uchcom_ctrl_read( + sc, UCHCOM_REQ_GET_VERSION, 0, 0, buf, sizeof(buf)); + + if (rver) + *rver = buf[0]; + + return; +} + +static void +uchcom_get_status(struct uchcom_softc *sc, uint8_t *rval) +{ + uchcom_read_reg(sc, UCHCOM_REG_STAT1, rval, UCHCOM_REG_STAT2, NULL); + return; +} + +static void +uchcom_set_dtrrts_10(struct uchcom_softc *sc, uint8_t val) +{ + uchcom_write_reg(sc, UCHCOM_REG_STAT1, val, UCHCOM_REG_STAT1, val); + return; +} + +static void +uchcom_set_dtrrts_20(struct uchcom_softc *sc, uint8_t val) +{ + uchcom_ctrl_write(sc, UCHCOM_REQ_SET_DTRRTS, val, 0); + return; +} + + +/* ---------------------------------------------------------------------- + * middle layer + */ + +static void +uchcom_update_version(struct uchcom_softc *sc) +{ + uchcom_get_version(sc, &sc->sc_version); + return; +} + +static void +uchcom_convert_status(struct uchcom_softc *sc, uint8_t cur) +{ + sc->sc_dtr = !(cur & UCHCOM_DTR_MASK); + sc->sc_rts = !(cur & UCHCOM_RTS_MASK); + + cur = ~cur & 0x0F; + sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur); +} + +static void +uchcom_update_status(struct uchcom_softc *sc) +{ + uint8_t cur; + + uchcom_get_status(sc, &cur); + uchcom_convert_status(sc, cur); + return; +} + + +static void +uchcom_set_dtrrts(struct uchcom_softc *sc) +{ + uint8_t val = 0; + + if (sc->sc_dtr) + val |= UCHCOM_DTR_MASK; + if (sc->sc_rts) + val |= UCHCOM_RTS_MASK; + + if (sc->sc_version < UCHCOM_VER_20) + uchcom_set_dtrrts_10(sc, ~val); + else + uchcom_set_dtrrts_20(sc, ~val); + + return; +} + +static void +uchcom_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + uint8_t brk1; + uint8_t brk2; + + uchcom_read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_BREAK2, &brk2); + if (onoff) { + /* on - clear bits */ + brk1 &= ~UCHCOM_BRK1_MASK; + brk2 &= ~UCHCOM_BRK2_MASK; + } else { + /* off - set bits */ + brk1 |= UCHCOM_BRK1_MASK; + brk2 |= UCHCOM_BRK2_MASK; + } + uchcom_write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_BREAK2, brk2); + + return; +} + +static int +uchcom_calc_divider_settings(struct uchcom_divider *dp, uint32_t rate) +{ + const struct uchcom_divider_record *rp; + uint32_t div; + uint32_t rem; + uint32_t mod; + uint8_t i; + + /* find record */ + for (i = 0; i != NUM_DIVIDERS; i++) { + if (dividers[i].dvr_high >= rate && + dividers[i].dvr_low <= rate) { + rp = ÷rs[i]; + goto found; + } + } + return (-1); + +found: + dp->dv_prescaler = rp->dvr_divider.dv_prescaler; + if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN) + dp->dv_div = rp->dvr_divider.dv_div; + else { + div = rp->dvr_base_clock / rate; + rem = rp->dvr_base_clock % rate; + if (div == 0 || div >= 0xFF) + return (-1); + if ((rem << 1) >= rate) + div += 1; + dp->dv_div = (uint8_t)-div; + } + + mod = UCHCOM_BPS_MOD_BASE / rate + UCHCOM_BPS_MOD_BASE_OFS; + mod = mod + mod / 2; + + dp->dv_mod = mod / 0x100; + + return (0); +} + +static void +uchcom_set_dte_rate(struct uchcom_softc *sc, uint32_t rate) +{ + struct uchcom_divider dv; + + if (uchcom_calc_divider_settings(&dv, rate)) + return; + + uchcom_write_reg(sc, + UCHCOM_REG_BPS_PRE, dv.dv_prescaler, + UCHCOM_REG_BPS_DIV, dv.dv_div); + uchcom_write_reg(sc, + UCHCOM_REG_BPS_MOD, dv.dv_mod, + UCHCOM_REG_BPS_PAD, 0); + return; +} + +static void +uchcom_set_line_control(struct uchcom_softc *sc, tcflag_t cflag) +{ + uint8_t lcr1 = 0; + uint8_t lcr2 = 0; + + uchcom_read_reg(sc, UCHCOM_REG_LCR1, &lcr1, UCHCOM_REG_LCR2, &lcr2); + + lcr1 &= ~UCHCOM_LCR1_MASK; + lcr2 &= ~UCHCOM_LCR2_MASK; + + /* + * XXX: it is difficult to handle the line control appropriately: + * - CS8, !CSTOPB and any parity mode seems ok, but + * - the chip doesn't have the function to calculate parity + * in !CS8 mode. + * - it is unclear that the chip supports CS5,6 mode. + * - it is unclear how to handle stop bits. + */ + + if (cflag & PARENB) { + lcr1 |= UCHCOM_LCR1_PARENB; + if (cflag & PARODD) + lcr2 |= UCHCOM_LCR2_PARODD; + else + lcr2 |= UCHCOM_LCR2_PAREVEN; + } + uchcom_write_reg(sc, UCHCOM_REG_LCR1, lcr1, UCHCOM_REG_LCR2, lcr2); + + return; +} + +static void +uchcom_clear_chip(struct uchcom_softc *sc) +{ + DPRINTF("\n"); + uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0, 0); + return; +} + +static void +uchcom_reset_chip(struct uchcom_softc *sc) +{ + uint16_t val; + uint16_t idx; + uint8_t lcr1; + uint8_t lcr2; + uint8_t pre; + uint8_t div; + uint8_t mod; + + uchcom_read_reg(sc, UCHCOM_REG_LCR1, &lcr1, UCHCOM_REG_LCR2, &lcr2); + uchcom_read_reg(sc, UCHCOM_REG_BPS_PRE, &pre, UCHCOM_REG_BPS_DIV, &div); + uchcom_read_reg(sc, UCHCOM_REG_BPS_MOD, &mod, UCHCOM_REG_BPS_PAD, NULL); + + val = 0; + idx = 0; + val |= (uint16_t)(lcr1 & 0xF0) << 8; + val |= 0x01; + val |= (uint16_t)(lcr2 & 0x0F) << 8; + val |= 0x02; + idx |= pre & 0x07; + val |= 0x04; + idx |= (uint16_t)div << 8; + val |= 0x08; + idx |= mod & 0xF8; + val |= 0x10; + + DPRINTF("reset v=0x%04X, i=0x%04X\n", val, idx); + + uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, val, idx); + + return; +} + +/* ---------------------------------------------------------------------- + * methods for ucom + */ +static void +uchcom_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; + return; +} + +static int +uchcom_ioctl(struct usb2_com_softc *ucom, uint32_t cmd, caddr_t data, int flag, + struct thread *td) +{ + return (ENOTTY); +} + +static void +uchcom_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + sc->sc_dtr = onoff; + uchcom_set_dtrrts(sc); + return; +} + +static void +uchcom_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + sc->sc_rts = onoff; + uchcom_set_dtrrts(sc); + return; +} + +static int +uchcom_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uchcom_divider dv; + + switch (t->c_cflag & CSIZE) { + case CS5: + case CS6: + case CS7: + return (EIO); + default: + break; + } + + if (uchcom_calc_divider_settings(&dv, t->c_ospeed)) { + return (EIO); + } + return (0); /* success */ +} + +static void +uchcom_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + uchcom_set_line_control(sc, t->c_cflag); + uchcom_set_dte_rate(sc, t->c_ospeed); + return; +} + +static void +uchcom_start_read(struct usb2_com_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usb2_transfer_start(sc->sc_xfer[4]); + + /* start read endpoint */ + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +uchcom_stop_read(struct usb2_com_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usb2_transfer_stop(sc->sc_xfer[4]); + + /* stop read endpoint */ + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +uchcom_start_write(struct usb2_com_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +uchcom_stop_write(struct usb2_com_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +/* ---------------------------------------------------------------------- + * callback when the modem status is changed. + */ +static void +uchcom_intr_callback(struct usb2_xfer *xfer) +{ + struct uchcom_softc *sc = xfer->priv_sc; + uint8_t buf[UCHCOM_INTR_LEAST]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen = %u\n", xfer->actlen); + + if (xfer->actlen >= UCHCOM_INTR_LEAST) { + usb2_copy_out(xfer->frbuffers, 0, buf, + UCHCOM_INTR_LEAST); + + DPRINTF("data = 0x%02X 0x%02X 0x%02X 0x%02X\n", + (unsigned)buf[0], (unsigned)buf[1], + (unsigned)buf[2], (unsigned)buf[3]); + + uchcom_convert_status(sc, buf[UCHCOM_INTR_STAT1]); + usb2_com_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: + if (sc->sc_flag & UCHCOM_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UCHCOM_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + break; + } + return; +} + +static void +uchcom_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uchcom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UCHCOM_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uchcom_write_callback(struct usb2_xfer *xfer) +{ + struct uchcom_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flag & UCHCOM_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UCHCOM_BULK_BUF_SIZE, &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UCHCOM_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} + +static void +uchcom_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uchcom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UCHCOM_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uchcom_read_callback(struct usb2_xfer *xfer) +{ + struct uchcom_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flag & UCHCOM_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UCHCOM_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +uchcom_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uchcom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UCHCOM_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static device_method_t uchcom_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uchcom_probe), + DEVMETHOD(device_attach, uchcom_attach), + DEVMETHOD(device_detach, uchcom_detach), + + {0, 0} +}; + +static driver_t uchcom_driver = { + "ucom", + uchcom_methods, + sizeof(struct uchcom_softc) +}; + +static devclass_t uchcom_devclass; + +DRIVER_MODULE(uchcom, ushub, uchcom_driver, uchcom_devclass, NULL, 0); +MODULE_DEPEND(uchcom, usb2_serial, 1, 1, 1); +MODULE_DEPEND(uchcom, usb2_core, 1, 1, 1); +MODULE_DEPEND(uchcom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); diff --git a/sys/dev/usb2/serial/ucycom2.c b/sys/dev/usb2/serial/ucycom2.c new file mode 100644 index 000000000000..2cbbe47240b6 --- /dev/null +++ b/sys/dev/usb2/serial/ucycom2.c @@ -0,0 +1,607 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2004 Dag-Erling Coïdan Smørgrav + * 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 + * in this position and unchanged. + * 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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. + */ + +/* + * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to + * RS232 bridges. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define UCYCOM_MAX_IOLEN (1024 + 2) /* bytes */ + +#define UCYCOM_ENDPT_MAX 3 /* units */ +#define UCYCOM_IFACE_INDEX 0 + +struct ucycom_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[UCYCOM_ENDPT_MAX]; + + uint32_t sc_model; +#define MODEL_CY7C63743 0x63743 +#define MODEL_CY7C64013 0x64013 + + uint16_t sc_flen; /* feature report length */ + uint16_t sc_ilen; /* input report length */ + uint16_t sc_olen; /* output report length */ + + uint8_t sc_fid; /* feature report id */ + uint8_t sc_iid; /* input report id */ + uint8_t sc_oid; /* output report id */ + uint8_t sc_cfg; +#define UCYCOM_CFG_RESET 0x80 +#define UCYCOM_CFG_PARODD 0x20 +#define UCYCOM_CFG_PAREN 0x10 +#define UCYCOM_CFG_STOPB 0x08 +#define UCYCOM_CFG_DATAB 0x03 + uint8_t sc_ist; /* status flags from last input */ + uint8_t sc_flags; +#define UCYCOM_FLAG_INTR_STALL 0x01 + uint8_t sc_name[16]; + uint8_t sc_iface_no; + uint8_t sc_temp_cfg[32]; +}; + +/* prototypes */ + +static device_probe_t ucycom_probe; +static device_attach_t ucycom_attach; +static device_detach_t ucycom_detach; + +static usb2_callback_t ucycom_ctrl_write_callback; +static usb2_callback_t ucycom_intr_read_clear_stall_callback; +static usb2_callback_t ucycom_intr_read_callback; + +static void ucycom_cfg_open(struct usb2_com_softc *ucom); +static void ucycom_start_read(struct usb2_com_softc *ucom); +static void ucycom_stop_read(struct usb2_com_softc *ucom); +static void ucycom_start_write(struct usb2_com_softc *ucom); +static void ucycom_stop_write(struct usb2_com_softc *ucom); +static void ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg); +static int ucycom_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void ucycom_cfg_param(struct usb2_com_softc *ucom, struct termios *t); + +static const struct usb2_config ucycom_config[UCYCOM_ENDPT_MAX] = { + + [0] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = (sizeof(struct usb2_device_request) + UCYCOM_MAX_IOLEN), + .mh.flags = {}, + .mh.callback = &ucycom_ctrl_write_callback, + .mh.timeout = 1000, /* 1 second */ + }, + + [1] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = UCYCOM_MAX_IOLEN, + .mh.callback = &ucycom_intr_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ucycom_intr_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback ucycom_callback = { + .usb2_com_cfg_param = &ucycom_cfg_param, + .usb2_com_cfg_open = &ucycom_cfg_open, + .usb2_com_pre_param = &ucycom_pre_param, + .usb2_com_start_read = &ucycom_start_read, + .usb2_com_stop_read = &ucycom_stop_read, + .usb2_com_start_write = &ucycom_start_write, + .usb2_com_stop_write = &ucycom_stop_write, +}; + +static device_method_t ucycom_methods[] = { + DEVMETHOD(device_probe, ucycom_probe), + DEVMETHOD(device_attach, ucycom_attach), + DEVMETHOD(device_detach, ucycom_detach), + {0, 0} +}; + +static devclass_t ucycom_devclass; + +static driver_t ucycom_driver = { + .name = "ucycom", + .methods = ucycom_methods, + .size = sizeof(struct ucycom_softc), +}; + +DRIVER_MODULE(ucycom, ushub, ucycom_driver, ucycom_devclass, NULL, 0); +MODULE_DEPEND(ucycom, usb2_serial, 1, 1, 1); +MODULE_DEPEND(ucycom, usb2_core, 1, 1, 1); +MODULE_DEPEND(ucycom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +/* + * Supported devices + */ +static const struct usb2_device_id ucycom_devs[] = { + {USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)}, +}; + +#define UCYCOM_DEFAULT_RATE 4800 +#define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */ + +static int +ucycom_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa)); +} + +static int +ucycom_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ucycom_softc *sc = device_get_softc(dev); + void *urd_ptr = NULL; + int32_t error; + uint16_t urd_len; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + DPRINTF("\n"); + + /* get chip model */ + sc->sc_model = USB_GET_DRIVER_INFO(uaa); + if (sc->sc_model == 0) { + device_printf(dev, "unsupported device\n"); + goto detach; + } + device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model); + + /* get report descriptor */ + + error = usb2_req_get_hid_desc + (uaa->device, &Giant, + &urd_ptr, &urd_len, M_USBDEV, + UCYCOM_IFACE_INDEX); + + if (error) { + device_printf(dev, "failed to get report " + "descriptor: %s\n", + usb2_errstr(error)); + goto detach; + } + /* get report sizes */ + + sc->sc_flen = hid_report_size(urd_ptr, urd_len, hid_feature, &sc->sc_fid); + sc->sc_ilen = hid_report_size(urd_ptr, urd_len, hid_input, &sc->sc_iid); + sc->sc_olen = hid_report_size(urd_ptr, urd_len, hid_output, &sc->sc_oid); + + if ((sc->sc_ilen > UCYCOM_MAX_IOLEN) || (sc->sc_ilen < 1) || + (sc->sc_olen > UCYCOM_MAX_IOLEN) || (sc->sc_olen < 2) || + (sc->sc_flen > UCYCOM_MAX_IOLEN) || (sc->sc_flen < 5)) { + device_printf(dev, "invalid report size i=%d, o=%d, f=%d, max=%d\n", + sc->sc_ilen, sc->sc_olen, sc->sc_flen, + UCYCOM_MAX_IOLEN); + goto detach; + } + sc->sc_iface_no = uaa->info.bIfaceNum; + + iface_index = UCYCOM_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, ucycom_config, UCYCOM_ENDPT_MAX, + sc, &Giant); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ucycom_callback, &Giant); + + if (error) { + goto detach; + } + if (urd_ptr) { + free(urd_ptr, M_USBDEV); + } + return (0); /* success */ + +detach: + if (urd_ptr) { + free(urd_ptr, M_USBDEV); + } + ucycom_detach(dev); + return (ENXIO); +} + +static int +ucycom_detach(device_t dev) +{ + struct ucycom_softc *sc = device_get_softc(dev); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UCYCOM_ENDPT_MAX); + + return (0); +} + +static void +ucycom_cfg_open(struct usb2_com_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + /* set default configuration */ + ucycom_cfg_write(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG); + return; +} + +static void +ucycom_start_read(struct usb2_com_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +ucycom_stop_read(struct usb2_com_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +ucycom_start_write(struct usb2_com_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +ucycom_stop_write(struct usb2_com_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static void +ucycom_ctrl_write_callback(struct usb2_xfer *xfer) +{ + struct ucycom_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + uint8_t data[2]; + uint8_t offset; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + case USB_ST_SETUP: + + switch (sc->sc_model) { + case MODEL_CY7C63743: + offset = 1; + break; + case MODEL_CY7C64013: + offset = 2; + break; + default: + offset = 0; + break; + } + + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers + 1, offset, + sc->sc_olen - offset, &actlen)) { + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, UHID_OUTPUT_REPORT, sc->sc_oid); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sc->sc_olen); + + switch (sc->sc_model) { + case MODEL_CY7C63743: + data[0] = actlen; + break; + case MODEL_CY7C64013: + data[0] = 0; + data[1] = actlen; + break; + default: + break; + } + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + usb2_copy_in(xfer->frbuffers + 1, 0, data, offset); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = sc->sc_olen; + xfer->nframes = xfer->frlengths[1] ? 2 : 1; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + return; + } + DPRINTF("error=%s\n", + usb2_errstr(xfer->error)); + goto tr_transferred; + } +} + +static void +ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg) +{ + struct usb2_device_request req; + uint16_t len; + usb2_error_t err; + + len = sc->sc_flen; + if (len > sizeof(sc->sc_temp_cfg)) { + len = sizeof(sc->sc_temp_cfg); + } + sc->sc_cfg = cfg; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, UHID_FEATURE_REPORT, sc->sc_fid); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, len); + + sc->sc_temp_cfg[0] = (baud & 0xff); + sc->sc_temp_cfg[1] = (baud >> 8) & 0xff; + sc->sc_temp_cfg[2] = (baud >> 16) & 0xff; + sc->sc_temp_cfg[3] = (baud >> 24) & 0xff; + sc->sc_temp_cfg[4] = cfg; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + return; + } + err = usb2_do_request_flags + (sc->sc_udev, &Giant, &req, sc->sc_temp_cfg, 0, NULL, 1000); + + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + } + return; +} + +static int +ucycom_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + switch (t->c_ospeed) { + case 600: + case 1200: + case 2400: + case 4800: + case 9600: + case 19200: + case 38400: + case 57600: +#if 0 + /* + * Stock chips only support standard baud rates in the 600 - 57600 + * range, but higher rates can be achieved using custom firmware. + */ + case 115200: + case 153600: + case 192000: +#endif + break; + default: + return (EINVAL); + } + return (0); +} + +static void +ucycom_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct ucycom_softc *sc = ucom->sc_parent; + uint8_t cfg; + + DPRINTF("\n"); + + if (t->c_cflag & CIGNORE) { + cfg = sc->sc_cfg; + } else { + cfg = 0; + switch (t->c_cflag & CSIZE) { + default: + case CS8: + ++cfg; + case CS7: + ++cfg; + case CS6: + ++cfg; + case CS5: + break; + } + + if (t->c_cflag & CSTOPB) + cfg |= UCYCOM_CFG_STOPB; + if (t->c_cflag & PARENB) + cfg |= UCYCOM_CFG_PAREN; + if (t->c_cflag & PARODD) + cfg |= UCYCOM_CFG_PARODD; + } + + ucycom_cfg_write(sc, t->c_ospeed, cfg); + return; +} + +static void +ucycom_intr_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ucycom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UCYCOM_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ucycom_intr_read_callback(struct usb2_xfer *xfer) +{ + struct ucycom_softc *sc = xfer->priv_sc; + uint8_t buf[2]; + uint32_t offset; + uint32_t len; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + switch (sc->sc_model) { + case MODEL_CY7C63743: + if (xfer->actlen < 1) { + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, 1); + + sc->sc_ist = buf[0] & ~0x07; + len = buf[0] & 0x07; + + (xfer->actlen)--; + + offset = 1; + + break; + + case MODEL_CY7C64013: + if (xfer->actlen < 2) { + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, 2); + + sc->sc_ist = buf[0] & ~0x07; + len = buf[1]; + + (xfer->actlen) -= 2; + + offset = 2; + + break; + + default: + DPRINTFN(0, "unsupported model number!\n"); + goto tr_setup; + } + + if (len > xfer->actlen) { + len = xfer->actlen; + } + if (len) { + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, + offset, len); + } + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flags & UCYCOM_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + } else { + xfer->frlengths[0] = sc->sc_ilen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UCYCOM_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} diff --git a/sys/dev/usb2/serial/ufoma2.c b/sys/dev/usb2/serial/ufoma2.c new file mode 100644 index 000000000000..5c04d0c9a26d --- /dev/null +++ b/sys/dev/usb2/serial/ufoma2.c @@ -0,0 +1,1198 @@ +/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2005, Takanori Watanabe + * Copyright (c) 2003, M. Warner Losh . + * 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. + */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +/* + * TODO: + * - Implement a Call Device for modems without multiplexed commands. + */ + +/* + * NOTE: all function names beginning like "ufoma_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef struct ufoma_mobile_acm_descriptor { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bType; + uint8_t bMode[1]; +} __packed usb2_mcpc_acm_descriptor; + +#define UISUBCLASS_MCPC 0x88 + +#define UDESC_VS_INTERFACE 0x44 +#define UDESCSUB_MCPC_ACM 0x11 + +#define UMCPC_ACM_TYPE_AB1 0x1 +#define UMCPC_ACM_TYPE_AB2 0x2 +#define UMCPC_ACM_TYPE_AB5 0x5 +#define UMCPC_ACM_TYPE_AB6 0x6 + +#define UMCPC_ACM_MODE_DEACTIVATED 0x0 +#define UMCPC_ACM_MODE_MODEM 0x1 +#define UMCPC_ACM_MODE_ATCOMMAND 0x2 +#define UMCPC_ACM_MODE_OBEX 0x60 +#define UMCPC_ACM_MODE_VENDOR1 0xc0 +#define UMCPC_ACM_MODE_VENDOR2 0xfe +#define UMCPC_ACM_MODE_UNLINKED 0xff + +#define UMCPC_CM_MOBILE_ACM 0x0 + +#define UMCPC_ACTIVATE_MODE 0x60 +#define UMCPC_GET_MODETABLE 0x61 +#define UMCPC_SET_LINK 0x62 +#define UMCPC_CLEAR_LINK 0x63 + +#define UMCPC_REQUEST_ACKNOWLEDGE 0x31 + +#define UFOMA_MAX_TIMEOUT 15 /* standard says 10 seconds */ +#define UFOMA_CMD_BUF_SIZE 64 /* bytes */ + +#define UFOMA_BULK_BUF_SIZE 1024 /* bytes */ + +#define UFOMA_CTRL_ENDPT_MAX 4 /* units */ +#define UFOMA_BULK_ENDPT_MAX 4 /* units */ + +struct ufoma_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + struct cv sc_cv; + + struct usb2_xfer *sc_ctrl_xfer[UFOMA_CTRL_ENDPT_MAX]; + struct usb2_xfer *sc_bulk_xfer[UFOMA_BULK_ENDPT_MAX]; + uint8_t *sc_modetable; + device_t sc_dev; + struct usb2_device *sc_udev; + + uint32_t sc_unit; + + uint16_t sc_line; + + uint8_t sc_num_msg; + uint8_t sc_is_pseudo; + uint8_t sc_ctrl_iface_no; + uint8_t sc_ctrl_iface_index; + uint8_t sc_data_iface_no; + uint8_t sc_data_iface_index; + uint8_t sc_cm_cap; + uint8_t sc_acm_cap; + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_modetoactivate; + uint8_t sc_currentmode; + uint8_t sc_flags; +#define UFOMA_FLAG_INTR_STALL 0x01 +#define UFOMA_FLAG_BULK_WRITE_STALL 0x02 +#define UFOMA_FLAG_BULK_READ_STALL 0x04 + + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t ufoma_probe; +static device_attach_t ufoma_attach; +static device_detach_t ufoma_detach; + +static usb2_callback_t ufoma_ctrl_read_callback; +static usb2_callback_t ufoma_ctrl_write_callback; +static usb2_callback_t ufoma_intr_clear_stall_callback; +static usb2_callback_t ufoma_intr_callback; +static usb2_callback_t ufoma_bulk_write_callback; +static usb2_callback_t ufoma_bulk_write_clear_stall_callback; +static usb2_callback_t ufoma_bulk_read_callback; +static usb2_callback_t ufoma_bulk_read_clear_stall_callback; + +static void ufoma_cfg_do_request(struct ufoma_softc *sc, struct usb2_device_request *req, void *data); +static void *ufoma_get_intconf(struct usb2_config_descriptor *cd, struct usb2_interface_descriptor *id, uint8_t type, uint8_t subtype); +static void ufoma_cfg_link_state(struct ufoma_softc *sc); +static void ufoma_cfg_activate_state(struct ufoma_softc *sc, uint16_t state); +static void ufoma_cfg_open(struct usb2_com_softc *ucom); +static void ufoma_cfg_close(struct usb2_com_softc *ucom); +static void ufoma_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static void ufoma_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static void ufoma_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void ufoma_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static int ufoma_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void ufoma_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static int ufoma_modem_setup(device_t dev, struct ufoma_softc *sc, struct usb2_attach_arg *uaa); +static void ufoma_start_read(struct usb2_com_softc *ucom); +static void ufoma_stop_read(struct usb2_com_softc *ucom); +static void ufoma_start_write(struct usb2_com_softc *ucom); +static void ufoma_stop_write(struct usb2_com_softc *ucom); + +static const struct usb2_config + ufoma_ctrl_config[UFOMA_CTRL_ENDPT_MAX] = { + + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = sizeof(struct usb2_cdc_notification), + .mh.callback = &ufoma_intr_callback, + }, + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ufoma_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = (sizeof(struct usb2_device_request) + UFOMA_CMD_BUF_SIZE), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &ufoma_ctrl_read_callback, + .mh.timeout = 1000, /* 1 second */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = (sizeof(struct usb2_device_request) + 1), + .mh.flags = {}, + .mh.callback = &ufoma_ctrl_write_callback, + .mh.timeout = 1000, /* 1 second */ + }, +}; + +static const struct usb2_config + ufoma_bulk_config[UFOMA_BULK_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UFOMA_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &ufoma_bulk_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UFOMA_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &ufoma_bulk_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ufoma_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ufoma_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback ufoma_callback = { + .usb2_com_cfg_get_status = &ufoma_cfg_get_status, + .usb2_com_cfg_set_dtr = &ufoma_cfg_set_dtr, + .usb2_com_cfg_set_rts = &ufoma_cfg_set_rts, + .usb2_com_cfg_set_break = &ufoma_cfg_set_break, + .usb2_com_cfg_param = &ufoma_cfg_param, + .usb2_com_cfg_open = &ufoma_cfg_open, + .usb2_com_cfg_close = &ufoma_cfg_close, + .usb2_com_pre_param = &ufoma_pre_param, + .usb2_com_start_read = &ufoma_start_read, + .usb2_com_stop_read = &ufoma_stop_read, + .usb2_com_start_write = &ufoma_start_write, + .usb2_com_stop_write = &ufoma_stop_write, +}; + +static device_method_t ufoma_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, ufoma_probe), + DEVMETHOD(device_attach, ufoma_attach), + DEVMETHOD(device_detach, ufoma_detach), + {0, 0} +}; + +static devclass_t ufoma_devclass; + +static driver_t ufoma_driver = { + .name = "ufoma", + .methods = ufoma_methods, + .size = sizeof(struct ufoma_softc), +}; + +DRIVER_MODULE(ufoma, ushub, ufoma_driver, ufoma_devclass, NULL, 0); +MODULE_DEPEND(ufoma, usb2_serial, 1, 1, 1); +MODULE_DEPEND(ufoma, usb2_core, 1, 1, 1); +MODULE_DEPEND(ufoma, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static int +ufoma_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_interface_descriptor *id; + struct usb2_config_descriptor *cd; + usb2_mcpc_acm_descriptor *mad; + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + id = usb2_get_interface_descriptor(uaa->iface); + cd = usb2_get_config_descriptor(uaa->device); + + if ((id == NULL) || + (cd == NULL) || + (id->bInterfaceClass != UICLASS_CDC) || + (id->bInterfaceSubClass != UISUBCLASS_MCPC)) { + return (ENXIO); + } + mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); + if (mad == NULL) { + return (ENXIO); + } +#ifndef UFOMA_HANDSFREE + if ((mad->bType == UMCPC_ACM_TYPE_AB5) || + (mad->bType == UMCPC_ACM_TYPE_AB6)) { + return (ENXIO); + } +#endif + return (0); +} + +static int +ufoma_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ufoma_softc *sc = device_get_softc(dev); + struct usb2_config_descriptor *cd; + struct usb2_interface_descriptor *id; + usb2_mcpc_acm_descriptor *mad; + uint8_t elements; + int32_t error; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + usb2_cv_init(&sc->sc_cv, "CWAIT"); + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + DPRINTF("\n"); + + /* setup control transfers */ + + cd = usb2_get_config_descriptor(uaa->device); + id = usb2_get_interface_descriptor(uaa->iface); + sc->sc_ctrl_iface_no = id->bInterfaceNumber; + sc->sc_ctrl_iface_index = uaa->info.bIfaceIndex; + + error = usb2_transfer_setup(uaa->device, + &sc->sc_ctrl_iface_index, sc->sc_ctrl_xfer, + ufoma_ctrl_config, UFOMA_CTRL_ENDPT_MAX, sc, &Giant); + + if (error) { + device_printf(dev, "allocating control USB " + "transfers failed!\n"); + goto detach; + } + mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); + if (mad == NULL) { + goto detach; + } + if (mad->bFunctionLength < sizeof(*mad)) { + device_printf(dev, "invalid MAD descriptor\n"); + goto detach; + } + if ((mad->bType == UMCPC_ACM_TYPE_AB5) || + (mad->bType == UMCPC_ACM_TYPE_AB6)) { + sc->sc_is_pseudo = 1; + } else { + sc->sc_is_pseudo = 0; + if (ufoma_modem_setup(dev, sc, uaa)) { + goto detach; + } + } + + elements = (mad->bFunctionLength - sizeof(*mad) + 1); + + /* initialize mode variables */ + + sc->sc_modetable = malloc(elements + 1, M_USBDEV, M_WAITOK); + + if (sc->sc_modetable == NULL) { + goto detach; + } + sc->sc_modetable[0] = (elements + 1); + bcopy(mad->bMode, &sc->sc_modetable[1], elements); + + sc->sc_currentmode = UMCPC_ACM_MODE_UNLINKED; + sc->sc_modetoactivate = mad->bMode[0]; + + /* clear stall at first run */ + sc->sc_flags |= (UFOMA_FLAG_BULK_WRITE_STALL | + UFOMA_FLAG_BULK_READ_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ufoma_callback, &Giant); + if (error) { + DPRINTF("usb2_com_attach failed\n"); + goto detach; + } + return (0); /* success */ + +detach: + ufoma_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ufoma_detach(device_t dev) +{ + struct ufoma_softc *sc = device_get_softc(dev); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX); + + usb2_transfer_unsetup(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX); + + if (sc->sc_modetable) { + free(sc->sc_modetable, M_USBDEV); + } + usb2_cv_destroy(&sc->sc_cv); + + return (0); +} + +static void +ufoma_cfg_do_request(struct ufoma_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &Giant, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +static void * +ufoma_get_intconf(struct usb2_config_descriptor *cd, struct usb2_interface_descriptor *id, + uint8_t type, uint8_t subtype) +{ + struct usb2_descriptor *desc = (void *)id; + + while ((desc = usb2_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + return (NULL); + } + if ((desc->bDescriptorType == type) && + (desc->bDescriptorSubtype == subtype)) { + break; + } + } + return (desc); +} + +static void +ufoma_cfg_link_state(struct ufoma_softc *sc) +{ + struct usb2_device_request req; + int32_t error; + + req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; + req.bRequest = UMCPC_SET_LINK; + USETW(req.wValue, UMCPC_CM_MOBILE_ACM); + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wLength, sc->sc_modetable[0]); + + ufoma_cfg_do_request(sc, &req, sc->sc_modetable); + + error = usb2_cv_timedwait(&sc->sc_cv, &Giant, hz); + + if (error) { + DPRINTF("NO response\n"); + } + return; +} + +static void +ufoma_cfg_activate_state(struct ufoma_softc *sc, uint16_t state) +{ + struct usb2_device_request req; + int32_t error; + + req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; + req.bRequest = UMCPC_ACTIVATE_MODE; + USETW(req.wValue, state); + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wLength, 0); + + ufoma_cfg_do_request(sc, &req, NULL); + + error = usb2_cv_timedwait(&sc->sc_cv, &Giant, + (UFOMA_MAX_TIMEOUT * hz)); + if (error) { + DPRINTF("No response\n"); + } + return; +} + +static void +ufoma_ctrl_read_callback(struct usb2_xfer *xfer) +{ + struct ufoma_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (xfer->aframes != xfer->nframes) { + goto tr_setup; + } + if (xfer->frlengths[1] > 0) { + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers + 1, + 0, xfer->frlengths[1]); + } + case USB_ST_SETUP: +tr_setup: + if (sc->sc_num_msg) { + sc->sc_num_msg--; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wValue, 0); + USETW(req.wLength, UFOMA_CMD_BUF_SIZE); + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = UFOMA_CMD_BUF_SIZE; + xfer->nframes = 2; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + DPRINTF("error = %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error == USB_ERR_CANCELLED) { + return; + } else { + goto tr_setup; + } + + goto tr_transferred; + } +} + +static void +ufoma_ctrl_write_callback(struct usb2_xfer *xfer) +{ + struct ufoma_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + case USB_ST_SETUP: +tr_setup: + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers + 1, + 0, 1, &actlen)) { + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wValue, 0); + USETW(req.wLength, 1); + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = 1; + xfer->nframes = 2; + + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + DPRINTF("error = %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error == USB_ERR_CANCELLED) { + return; + } else { + goto tr_setup; + } + + goto tr_transferred; + } +} + +static void +ufoma_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ufoma_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_ctrl_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UFOMA_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ufoma_intr_callback(struct usb2_xfer *xfer) +{ + struct ufoma_softc *sc = xfer->priv_sc; + struct usb2_cdc_notification pkt; + uint16_t wLen; + uint16_t temp; + uint8_t mstatus; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen < 8) { + DPRINTF("too short message\n"); + goto tr_setup; + } + if (xfer->actlen > sizeof(pkt)) { + DPRINTF("truncating message\n"); + xfer->actlen = sizeof(pkt); + } + usb2_copy_out(xfer->frbuffers, 0, &pkt, xfer->actlen); + + xfer->actlen -= 8; + + wLen = UGETW(pkt.wLength); + if (xfer->actlen > wLen) { + xfer->actlen = wLen; + } + if ((pkt.bmRequestType == UT_READ_VENDOR_INTERFACE) && + (pkt.bNotification == UMCPC_REQUEST_ACKNOWLEDGE)) { + temp = UGETW(pkt.wValue); + sc->sc_currentmode = (temp >> 8); + if (!(temp & 0xff)) { + DPRINTF("Mode change failed!\n"); + } + usb2_cv_signal(&sc->sc_cv); + } + if (pkt.bmRequestType != UCDC_NOTIFICATION) { + goto tr_setup; + } + switch (pkt.bNotification) { + case UCDC_N_RESPONSE_AVAILABLE: + if (!(sc->sc_is_pseudo)) { + DPRINTF("Wrong serial state!\n"); + break; + } + if (sc->sc_num_msg != 0xFF) { + sc->sc_num_msg++; + } + usb2_transfer_start(sc->sc_ctrl_xfer[3]); + break; + + case UCDC_N_SERIAL_STATE: + if (sc->sc_is_pseudo) { + DPRINTF("Wrong serial state!\n"); + break; + } + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + if (xfer->actlen < 2) { + DPRINTF("invalid notification " + "length, %d bytes!\n", xfer->actlen); + break; + } + DPRINTF("notify bytes = 0x%02x, 0x%02x\n", + pkt.data[0], pkt.data[1]); + + /* currently, lsr is always zero. */ + sc->sc_lsr = 0; + sc->sc_msr = 0; + + mstatus = pkt.data[0]; + + if (mstatus & UCDC_N_SERIAL_RI) { + sc->sc_msr |= SER_RI; + } + if (mstatus & UCDC_N_SERIAL_DSR) { + sc->sc_msr |= SER_DSR; + } + if (mstatus & UCDC_N_SERIAL_DCD) { + sc->sc_msr |= SER_DCD; + } + usb2_com_status_change(&sc->sc_ucom); + break; + + default: + break; + } + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flags & UFOMA_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_ctrl_xfer[1]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= UFOMA_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_ctrl_xfer[1]); + } + return; + + } +} + +static void +ufoma_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct ufoma_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flags & UFOMA_FLAG_BULK_WRITE_STALL) { + usb2_transfer_start(sc->sc_bulk_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UFOMA_BULK_BUF_SIZE, &actlen)) { + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UFOMA_FLAG_BULK_WRITE_STALL; + usb2_transfer_start(sc->sc_bulk_xfer[2]); + } + return; + + } +} + +static void +ufoma_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ufoma_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_bulk_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UFOMA_FLAG_BULK_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ufoma_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct ufoma_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, + xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flags & UFOMA_FLAG_BULK_READ_STALL) { + usb2_transfer_start(sc->sc_bulk_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UFOMA_FLAG_BULK_READ_STALL; + usb2_transfer_start(sc->sc_bulk_xfer[3]); + } + return; + + } +} + +static void +ufoma_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ufoma_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_bulk_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UFOMA_FLAG_BULK_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ufoma_cfg_open(struct usb2_com_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* empty input queue */ + + if (sc->sc_num_msg != 0xFF) { + sc->sc_num_msg++; + } + if (sc->sc_currentmode == UMCPC_ACM_MODE_UNLINKED) { + ufoma_cfg_link_state(sc); + } + if (sc->sc_currentmode == UMCPC_ACM_MODE_DEACTIVATED) { + ufoma_cfg_activate_state(sc, sc->sc_modetoactivate); + } + return; +} + +static void +ufoma_cfg_close(struct usb2_com_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + ufoma_cfg_activate_state(sc, UMCPC_ACM_MODE_DEACTIVATED); + return; +} + +static void +ufoma_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + uint16_t wValue; + + if (sc->sc_is_pseudo) { + return; + } + if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK)) { + return; + } + wValue = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, wValue); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ufoma_cfg_do_request(sc, &req, 0); + return; +} + +static void +ufoma_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; + return; +} + +static void +ufoma_cfg_set_line_state(struct ufoma_softc *sc) +{ + struct usb2_device_request req; + + /* Don't send line state emulation request for OBEX port */ + if (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX) { + return; + } + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ufoma_cfg_do_request(sc, &req, 0); + return; +} + +static void +ufoma_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_is_pseudo) { + return; + } + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + ufoma_cfg_set_line_state(sc); + return; +} + +static void +ufoma_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_is_pseudo) { + return; + } + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + ufoma_cfg_set_line_state(sc); + return; +} + +static int +ufoma_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +ufoma_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct ufoma_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + struct usb2_cdc_line_state ls; + + if (sc->sc_is_pseudo || + (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) { + return; + } + DPRINTF("\n"); + + bzero(&ls, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + if (t->c_cflag & CSTOPB) { + ls.bCharFormat = UCDC_STOP_BIT_2; + } else { + ls.bCharFormat = UCDC_STOP_BIT_1; + } + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + ls.bParityType = UCDC_PARITY_ODD; + } else { + ls.bParityType = UCDC_PARITY_EVEN; + } + } else { + ls.bParityType = UCDC_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + ufoma_cfg_do_request(sc, &req, &ls); + return; +} + +static int +ufoma_modem_setup(device_t dev, struct ufoma_softc *sc, + struct usb2_attach_arg *uaa) +{ + struct usb2_config_descriptor *cd; + struct usb2_cdc_acm_descriptor *acm; + struct usb2_cdc_cm_descriptor *cmd; + struct usb2_interface_descriptor *id; + struct usb2_interface *iface; + uint8_t i; + int32_t error; + + cd = usb2_get_config_descriptor(uaa->device); + id = usb2_get_interface_descriptor(uaa->iface); + + cmd = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + + if ((cmd == NULL) || + (cmd->bLength < sizeof(*cmd))) { + return (EINVAL); + } + sc->sc_cm_cap = cmd->bmCapabilities; + sc->sc_data_iface_no = cmd->bDataInterface; + + acm = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); + + if ((acm == NULL) || + (acm->bLength < sizeof(*acm))) { + return (EINVAL); + } + sc->sc_acm_cap = acm->bmCapabilities; + + device_printf(dev, "data interface %d, has %sCM over data, " + "has %sbreak\n", + sc->sc_data_iface_no, + sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", + sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); + + /* get the data interface too */ + + for (i = 0;; i++) { + + iface = usb2_get_iface(uaa->device, i); + + if (iface) { + + id = usb2_get_interface_descriptor(iface); + + if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { + sc->sc_data_iface_index = i; + usb2_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + break; + } + } else { + device_printf(dev, "no data interface!\n"); + return (EINVAL); + } + } + + error = usb2_transfer_setup(uaa->device, + &sc->sc_data_iface_index, sc->sc_bulk_xfer, + ufoma_bulk_config, UFOMA_BULK_ENDPT_MAX, sc, &Giant); + + if (error) { + device_printf(dev, "allocating BULK USB " + "transfers failed!\n"); + return (EINVAL); + } + return (0); +} + +static void +ufoma_start_read(struct usb2_com_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* start interrupt transfer */ + usb2_transfer_start(sc->sc_ctrl_xfer[0]); + + /* start data transfer */ + if (sc->sc_is_pseudo) { + usb2_transfer_start(sc->sc_ctrl_xfer[2]); + } else { + usb2_transfer_start(sc->sc_bulk_xfer[1]); + } + return; +} + +static void +ufoma_stop_read(struct usb2_com_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* stop interrupt transfer */ + usb2_transfer_stop(sc->sc_ctrl_xfer[1]); + usb2_transfer_stop(sc->sc_ctrl_xfer[0]); + + /* stop data transfer */ + if (sc->sc_is_pseudo) { + usb2_transfer_stop(sc->sc_ctrl_xfer[2]); + } else { + usb2_transfer_stop(sc->sc_bulk_xfer[3]); + usb2_transfer_stop(sc->sc_bulk_xfer[1]); + } + return; +} + +static void +ufoma_start_write(struct usb2_com_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_is_pseudo) { + usb2_transfer_start(sc->sc_ctrl_xfer[3]); + } else { + usb2_transfer_start(sc->sc_bulk_xfer[0]); + } + return; +} + +static void +ufoma_stop_write(struct usb2_com_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_is_pseudo) { + usb2_transfer_stop(sc->sc_ctrl_xfer[3]); + } else { + usb2_transfer_stop(sc->sc_bulk_xfer[2]); + usb2_transfer_stop(sc->sc_bulk_xfer[0]); + } + return; +} diff --git a/sys/dev/usb2/serial/uftdi2.c b/sys/dev/usb2/serial/uftdi2.c new file mode 100644 index 000000000000..d620441c7e02 --- /dev/null +++ b/sys/dev/usb2/serial/uftdi2.c @@ -0,0 +1,868 @@ +/* $NetBSD: uftdi.c,v 1.13 2002/09/23 05:51:23 simonb Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * NOTE: all function names beginning like "uftdi_cfg_" can only + * be called from within the config thread function ! + */ + +/* + * FTDI FT8U100AX serial adapter driver + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR uftdi_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if USB_DEBUG +static int uftdi_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uftdi, CTLFLAG_RW, 0, "USB uftdi"); +SYSCTL_INT(_hw_usb2_uftdi, OID_AUTO, debug, CTLFLAG_RW, + &uftdi_debug, 0, "Debug level"); +#endif + +#define UFTDI_CONFIG_INDEX 0 +#define UFTDI_IFACE_INDEX 0 +#define UFTDI_ENDPT_MAX 4 + +#define UFTDI_IBUFSIZE 64 /* bytes, maximum number of bytes per + * frame */ +#define UFTDI_OBUFSIZE 64 /* bytes, cannot be increased due to + * do size encoding */ + +struct uftdi_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[UFTDI_ENDPT_MAX]; + device_t sc_dev; + + uint32_t sc_unit; + enum uftdi_type sc_type; + + uint16_t sc_last_lcr; + + uint8_t sc_iface_index; + uint8_t sc_hdrlen; + + uint8_t sc_msr; + uint8_t sc_lsr; + + uint8_t sc_flag; +#define UFTDI_FLAG_WRITE_STALL 0x01 +#define UFTDI_FLAG_READ_STALL 0x02 + + uint8_t sc_name[16]; +}; + +struct uftdi_param_config { + uint16_t rate; + uint16_t lcr; + uint8_t v_start; + uint8_t v_stop; + uint8_t v_flow; +}; + +/* prototypes */ + +static device_probe_t uftdi_probe; +static device_attach_t uftdi_attach; +static device_detach_t uftdi_detach; + +static usb2_callback_t uftdi_write_callback; +static usb2_callback_t uftdi_write_clear_stall_callback; +static usb2_callback_t uftdi_read_callback; +static usb2_callback_t uftdi_read_clear_stall_callback; + +static void uftdi_cfg_do_request(struct uftdi_softc *sc, struct usb2_device_request *req, void *data); +static void uftdi_cfg_open(struct usb2_com_softc *ucom); +static void uftdi_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void uftdi_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static void uftdi_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static int uftdi_set_parm_soft(struct termios *t, struct uftdi_param_config *cfg, uint8_t type); +static int uftdi_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void uftdi_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static void uftdi_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static void uftdi_start_read(struct usb2_com_softc *ucom); +static void uftdi_stop_read(struct usb2_com_softc *ucom); +static void uftdi_start_write(struct usb2_com_softc *ucom); +static void uftdi_stop_write(struct usb2_com_softc *ucom); +static uint8_t uftdi_8u232am_getrate(uint32_t speed, uint16_t *rate); + +static const struct usb2_config uftdi_config[UFTDI_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UFTDI_OBUFSIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &uftdi_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UFTDI_IBUFSIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &uftdi_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &uftdi_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &uftdi_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback uftdi_callback = { + .usb2_com_cfg_get_status = &uftdi_cfg_get_status, + .usb2_com_cfg_set_dtr = &uftdi_cfg_set_dtr, + .usb2_com_cfg_set_rts = &uftdi_cfg_set_rts, + .usb2_com_cfg_set_break = &uftdi_cfg_set_break, + .usb2_com_cfg_param = &uftdi_cfg_param, + .usb2_com_cfg_open = &uftdi_cfg_open, + .usb2_com_pre_param = &uftdi_pre_param, + .usb2_com_start_read = &uftdi_start_read, + .usb2_com_stop_read = &uftdi_stop_read, + .usb2_com_start_write = &uftdi_start_write, + .usb2_com_stop_write = &uftdi_stop_write, +}; + +static device_method_t uftdi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uftdi_probe), + DEVMETHOD(device_attach, uftdi_attach), + DEVMETHOD(device_detach, uftdi_detach), + + {0, 0} +}; + +static devclass_t uftdi_devclass; + +static driver_t uftdi_driver = { + .name = "uftdi", + .methods = uftdi_methods, + .size = sizeof(struct uftdi_softc), +}; + +DRIVER_MODULE(uftdi, ushub, uftdi_driver, uftdi_devclass, NULL, 0); +MODULE_DEPEND(uftdi, usb2_serial, 1, 1, 1); +MODULE_DEPEND(uftdi, usb2_core, 1, 1, 1); +MODULE_DEPEND(uftdi, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static struct usb2_device_id uftdi_devs[] = { + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_8U100AX, UFTDI_TYPE_SIO)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_2232C, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_8U232AM, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SEMC_DSS20, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_631, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_632, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_633, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_634, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_635, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_USBSERIAL, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_MX2_3, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_MX4_5, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_LK202, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_LK204, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13M, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13S, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13U, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EISCOU, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_UOPTBR, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EMCU2D, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_PCMSFU, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EMCU2H, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_SIIG2, USB_PRODUCT_SIIG2_US2308, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_INTREPIDCS, USB_PRODUCT_INTREPIDCS_VALUECAN, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_INTREPIDCS, USB_PRODUCT_INTREPIDCS_NEOVI, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_BBELECTRONICS, USB_PRODUCT_BBELECTRONICS_USOTL4, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_PCOPRS1, UFTDI_TYPE_8U232AM)}, +}; + +static int +uftdi_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UFTDI_CONFIG_INDEX) { + return (ENXIO); + } + /* attach to all present interfaces */ + + return (usb2_lookup_id_by_uaa(uftdi_devs, sizeof(uftdi_devs), uaa)); +} + +static int +uftdi_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uftdi_softc *sc = device_get_softc(dev); + int error; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + DPRINTF("\n"); + + sc->sc_iface_index = uaa->info.bIfaceIndex; + sc->sc_type = USB_GET_DRIVER_INFO(uaa); + + switch (sc->sc_type) { + case UFTDI_TYPE_SIO: + sc->sc_hdrlen = 1; + break; + case UFTDI_TYPE_8U232AM: + default: + sc->sc_hdrlen = 0; + break; + } + + error = usb2_transfer_setup(uaa->device, + &sc->sc_iface_index, sc->sc_xfer, uftdi_config, + UFTDI_ENDPT_MAX, sc, &Giant); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; + + /* clear stall at first run */ + + sc->sc_flag |= (UFTDI_FLAG_WRITE_STALL | + UFTDI_FLAG_READ_STALL); + + /* set a valid "lcr" value */ + + sc->sc_last_lcr = + (FTDI_SIO_SET_DATA_STOP_BITS_2 | + FTDI_SIO_SET_DATA_PARITY_NONE | + FTDI_SIO_SET_DATA_BITS(8)); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uftdi_callback, &Giant); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + uftdi_detach(dev); + return (ENXIO); +} + +static int +uftdi_detach(device_t dev) +{ + struct uftdi_softc *sc = device_get_softc(dev); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UFTDI_ENDPT_MAX); + + return (0); +} + +static void +uftdi_cfg_do_request(struct uftdi_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &Giant, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +static void +uftdi_cfg_open(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + struct usb2_device_request req; + + DPRINTF(""); + + /* perform a full reset on the device */ + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_RESET; + USETW(req.wValue, FTDI_SIO_RESET_SIO); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + uftdi_cfg_do_request(sc, &req, NULL); + + /* turn on RTS/CTS flow control */ + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_FLOW_CTRL; + USETW(req.wValue, 0); + USETW2(req.wIndex, FTDI_SIO_RTS_CTS_HS, wIndex); + USETW(req.wLength, 0); + uftdi_cfg_do_request(sc, &req, NULL); + + /* + * NOTE: with the new UCOM layer there will always be a + * "uftdi_cfg_param()" call after "open()", so there is no need for + * "open()" to configure anything + */ + return; +} + +static void +uftdi_write_callback(struct usb2_xfer *xfer) +{ + struct uftdi_softc *sc = xfer->priv_sc; + uint32_t actlen; + uint8_t buf[1]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flag & UFTDI_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, + sc->sc_hdrlen, UFTDI_OBUFSIZE - sc->sc_hdrlen, + &actlen)) { + + if (sc->sc_hdrlen > 0) { + buf[0] = + FTDI_OUT_TAG(actlen, sc->sc_ucom.sc_portno); + usb2_copy_in(xfer->frbuffers, 0, buf, 1); + } + xfer->frlengths[0] = actlen + sc->sc_hdrlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UFTDI_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} + +static void +uftdi_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uftdi_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UFTDI_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uftdi_read_callback(struct usb2_xfer *xfer) +{ + struct uftdi_softc *sc = xfer->priv_sc; + uint8_t buf[2]; + uint8_t msr; + uint8_t lsr; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen < 2) { + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, 2); + + msr = FTDI_GET_MSR(buf); + lsr = FTDI_GET_LSR(buf); + + if ((sc->sc_msr != msr) || + ((sc->sc_lsr & FTDI_LSR_MASK) != (lsr & FTDI_LSR_MASK))) { + DPRINTF("status change msr=0x%02x (0x%02x) " + "lsr=0x%02x (0x%02x)\n", msr, sc->sc_msr, + lsr, sc->sc_lsr); + + sc->sc_msr = msr; + sc->sc_lsr = lsr; + + usb2_com_status_change(&sc->sc_ucom); + } + xfer->actlen -= 2; + + if (xfer->actlen > 0) { + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 2, + xfer->actlen); + } + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flag & UFTDI_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UFTDI_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +uftdi_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uftdi_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UFTDI_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uftdi_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb2_device_request req; + + wValue = onoff ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_MODEM_CTRL; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + uftdi_cfg_do_request(sc, &req, NULL); + return; +} + +static void +uftdi_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb2_device_request req; + + wValue = onoff ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_MODEM_CTRL; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + uftdi_cfg_do_request(sc, &req, NULL); + return; +} + +static void +uftdi_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb2_device_request req; + + if (onoff) { + sc->sc_last_lcr |= FTDI_SIO_SET_BREAK; + } else { + sc->sc_last_lcr &= ~FTDI_SIO_SET_BREAK; + } + + wValue = sc->sc_last_lcr; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_DATA; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + uftdi_cfg_do_request(sc, &req, NULL); + return; +} + +static int +uftdi_set_parm_soft(struct termios *t, + struct uftdi_param_config *cfg, uint8_t type) +{ + bzero(cfg, sizeof(*cfg)); + + switch (type) { + case UFTDI_TYPE_SIO: + switch (t->c_ospeed) { + case 300: + cfg->rate = ftdi_sio_b300; + break; + case 600: + cfg->rate = ftdi_sio_b600; + break; + case 1200: + cfg->rate = ftdi_sio_b1200; + break; + case 2400: + cfg->rate = ftdi_sio_b2400; + break; + case 4800: + cfg->rate = ftdi_sio_b4800; + break; + case 9600: + cfg->rate = ftdi_sio_b9600; + break; + case 19200: + cfg->rate = ftdi_sio_b19200; + break; + case 38400: + cfg->rate = ftdi_sio_b38400; + break; + case 57600: + cfg->rate = ftdi_sio_b57600; + break; + case 115200: + cfg->rate = ftdi_sio_b115200; + break; + default: + return (EINVAL); + } + break; + + case UFTDI_TYPE_8U232AM: + if (uftdi_8u232am_getrate(t->c_ospeed, &cfg->rate)) { + return (EINVAL); + } + break; + } + + if (t->c_cflag & CSTOPB) + cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_2; + else + cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_ODD; + } else { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_EVEN; + } + } else { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(5); + break; + + case CS6: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(6); + break; + + case CS7: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(7); + break; + + case CS8: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(8); + break; + } + + if (t->c_cflag & CRTSCTS) { + cfg->v_flow = FTDI_SIO_RTS_CTS_HS; + } else if (t->c_iflag & (IXON | IXOFF)) { + cfg->v_flow = FTDI_SIO_XON_XOFF_HS; + cfg->v_start = t->c_cc[VSTART]; + cfg->v_stop = t->c_cc[VSTOP]; + } else { + cfg->v_flow = FTDI_SIO_DISABLE_FLOW_CTRL; + } + + return (0); +} + +static int +uftdi_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uftdi_softc *sc = ucom->sc_parent; + struct uftdi_param_config cfg; + + DPRINTF("\n"); + + return (uftdi_set_parm_soft(t, &cfg, sc->sc_type)); +} + +static void +uftdi_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + struct uftdi_param_config cfg; + struct usb2_device_request req; + + if (uftdi_set_parm_soft(t, &cfg, sc->sc_type)) { + /* should not happen */ + return; + } + sc->sc_last_lcr = cfg.lcr; + + DPRINTF("\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_BAUD_RATE; + USETW(req.wValue, cfg.rate); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + uftdi_cfg_do_request(sc, &req, NULL); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_DATA; + USETW(req.wValue, cfg.lcr); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + uftdi_cfg_do_request(sc, &req, NULL); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_FLOW_CTRL; + USETW2(req.wValue, cfg.v_stop, cfg.v_start); + USETW2(req.wIndex, cfg.v_flow, wIndex); + USETW(req.wLength, 0); + uftdi_cfg_do_request(sc, &req, NULL); + + return; +} + +static void +uftdi_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + DPRINTF("msr=0x%02x lsr=0x%02x\n", + sc->sc_msr, sc->sc_lsr); + + *msr = sc->sc_msr; + *lsr = sc->sc_lsr; + return; +} + +static void +uftdi_start_read(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +uftdi_stop_read(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +uftdi_start_write(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +uftdi_stop_write(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +/*------------------------------------------------------------------------* + * uftdi_8u232am_getrate + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +uftdi_8u232am_getrate(uint32_t speed, uint16_t *rate) +{ + /* Table of the nearest even powers-of-2 for values 0..15. */ + static const uint8_t roundoff[16] = { + 0, 2, 2, 4, 4, 4, 8, 8, + 8, 8, 8, 8, 16, 16, 16, 16, + }; + uint32_t d; + uint32_t freq; + uint16_t result; + + if ((speed < 178) || (speed > ((3000000 * 100) / 97))) + return (1); /* prevent numerical overflow */ + + /* Special cases for 2M and 3M. */ + if ((speed >= ((3000000 * 100) / 103)) && + (speed <= ((3000000 * 100) / 97))) { + result = 0; + goto done; + } + if ((speed >= ((2000000 * 100) / 103)) && + (speed <= ((2000000 * 100) / 97))) { + result = 1; + goto done; + } + d = (FTDI_8U232AM_FREQ << 4) / speed; + d = (d & ~15) + roundoff[d & 15]; + + if (d < FTDI_8U232AM_MIN_DIV) + d = FTDI_8U232AM_MIN_DIV; + else if (d > FTDI_8U232AM_MAX_DIV) + d = FTDI_8U232AM_MAX_DIV; + + /* + * Calculate the frequency needed for "d" to exactly divide down to + * our target "speed", and check that the actual frequency is within + * 3% of this. + */ + freq = (speed * d); + if ((freq < ((FTDI_8U232AM_FREQ * 1600ULL) / 103)) || + (freq > ((FTDI_8U232AM_FREQ * 1600ULL) / 97))) + return (1); + + /* + * Pack the divisor into the resultant value. The lower 14-bits + * hold the integral part, while the upper 2 bits encode the + * fractional component: either 0, 0.5, 0.25, or 0.125. + */ + result = (d >> 4); + if (d & 8) + result |= 0x4000; + else if (d & 4) + result |= 0x8000; + else if (d & 2) + result |= 0xc000; + +done: + *rate = result; + return (0); +} diff --git a/sys/dev/usb2/serial/uftdi2_reg.h b/sys/dev/usb2/serial/uftdi2_reg.h new file mode 100644 index 000000000000..0074bc5d701b --- /dev/null +++ b/sys/dev/usb2/serial/uftdi2_reg.h @@ -0,0 +1,340 @@ +/* $NetBSD: uftdireg.h,v 1.6 2002/07/11 21:14:28 augustss Exp $ */ +/* $FreeBSD$ */ + +/* + * Definitions for the FTDI USB Single Port Serial Converter - + * known as FTDI_SIO (Serial Input/Output application of the chipset) + * + * The device is based on the FTDI FT8U100AX chip. It has a DB25 on one side, + * USB on the other. + * + * Thanx to FTDI (http://www.ftdi.co.uk) for so kindly providing details + * of the protocol required to talk to the device and ongoing assistence + * during development. + * + * Bill Ryder - bryder@sgi.com of Silicon Graphics, Inc. is the original + * author of this file. + */ +/* Modified by Lennart Augustsson */ + +/* Vendor Request Interface */ +#define FTDI_SIO_RESET 0 /* Reset the port */ +#define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ +#define FTDI_SIO_SET_FLOW_CTRL 2 /* Set flow control register */ +#define FTDI_SIO_SET_BAUD_RATE 3 /* Set baud rate */ +#define FTDI_SIO_SET_DATA 4 /* Set the data characteristics of the + * port */ +#define FTDI_SIO_GET_STATUS 5 /* Retrieve current value of status + * reg */ +#define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */ +#define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ + +/* Port Identifier Table */ +#define FTDI_PIT_DEFAULT 0 /* SIOA */ +#define FTDI_PIT_SIOA 1 /* SIOA */ +#define FTDI_PIT_SIOB 2 /* SIOB */ +#define FTDI_PIT_PARALLEL 3 /* Parallel */ + +enum uftdi_type { + UFTDI_TYPE_SIO, + UFTDI_TYPE_8U232AM +}; + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_RESET + * wValue: Control Value + * 0 = Reset SIO + * 1 = Purge RX buffer + * 2 = Purge TX buffer + * wIndex: Port + * wLength: 0 + * Data: None + * + * The Reset SIO command has this effect: + * + * Sets flow control set to 'none' + * Event char = 0x0d + * Event trigger = disabled + * Purge RX buffer + * Purge TX buffer + * Clear DTR + * Clear RTS + * baud and data format not reset + * + * The Purge RX and TX buffer commands affect nothing except the buffers + * + */ +/* FTDI_SIO_RESET */ +#define FTDI_SIO_RESET_SIO 0 +#define FTDI_SIO_RESET_PURGE_RX 1 +#define FTDI_SIO_RESET_PURGE_TX 2 + + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_BAUDRATE + * wValue: BaudRate value - see below + * wIndex: Port + * wLength: 0 + * Data: None + */ +/* FTDI_SIO_SET_BAUDRATE */ +enum { + ftdi_sio_b300 = 0, + ftdi_sio_b600 = 1, + ftdi_sio_b1200 = 2, + ftdi_sio_b2400 = 3, + ftdi_sio_b4800 = 4, + ftdi_sio_b9600 = 5, + ftdi_sio_b19200 = 6, + ftdi_sio_b38400 = 7, + ftdi_sio_b57600 = 8, + ftdi_sio_b115200 = 9 +}; + +#define FTDI_8U232AM_FREQ 3000000 + +/* Bounds for normal divisors as 4-bit fixed precision ints. */ +#define FTDI_8U232AM_MIN_DIV 0x20 +#define FTDI_8U232AM_MAX_DIV 0x3fff8 + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_DATA + * wValue: Data characteristics (see below) + * wIndex: Port + * wLength: 0 + * Data: No + * + * Data characteristics + * + * B0..7 Number of data bits + * B8..10 Parity + * 0 = None + * 1 = Odd + * 2 = Even + * 3 = Mark + * 4 = Space + * B11..13 Stop Bits + * 0 = 1 + * 1 = 1.5 + * 2 = 2 + * B14..15 Reserved + * + */ +/* FTDI_SIO_SET_DATA */ +#define FTDI_SIO_SET_DATA_BITS(n) (n) +#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) +#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) +#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) +#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) +#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) +#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) +#define FTDI_SIO_SET_BREAK (0x1 << 14) + + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_MODEM_CTRL + * wValue: ControlValue (see below) + * wIndex: Port + * wLength: 0 + * Data: None + * + * NOTE: If the device is in RTS/CTS flow control, the RTS set by this + * command will be IGNORED without an error being returned + * Also - you can not set DTR and RTS with one control message + * + * ControlValue + * B0 DTR state + * 0 = reset + * 1 = set + * B1 RTS state + * 0 = reset + * 1 = set + * B2..7 Reserved + * B8 DTR state enable + * 0 = ignore + * 1 = use DTR state + * B9 RTS state enable + * 0 = ignore + * 1 = use RTS state + * B10..15 Reserved + */ +/* FTDI_SIO_MODEM_CTRL */ +#define FTDI_SIO_SET_DTR_MASK 0x1 +#define FTDI_SIO_SET_DTR_HIGH (1 | ( FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_DTR_LOW (0 | ( FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_RTS_MASK 0x2 +#define FTDI_SIO_SET_RTS_HIGH (2 | ( FTDI_SIO_SET_RTS_MASK << 8)) +#define FTDI_SIO_SET_RTS_LOW (0 | ( FTDI_SIO_SET_RTS_MASK << 8)) + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_FLOW_CTRL + * wValue: Xoff/Xon + * wIndex: Protocol/Port - hIndex is protocl / lIndex is port + * wLength: 0 + * Data: None + * + * hIndex protocol is: + * B0 Output handshaking using RTS/CTS + * 0 = disabled + * 1 = enabled + * B1 Output handshaking using DTR/DSR + * 0 = disabled + * 1 = enabled + * B2 Xon/Xoff handshaking + * 0 = disabled + * 1 = enabled + * + * A value of zero in the hIndex field disables handshaking + * + * If Xon/Xoff handshaking is specified, the hValue field should contain the + * XOFF character and the lValue field contains the XON character. + */ +/* FTDI_SIO_SET_FLOW_CTRL */ +#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 +#define FTDI_SIO_RTS_CTS_HS 0x1 +#define FTDI_SIO_DTR_DSR_HS 0x2 +#define FTDI_SIO_XON_XOFF_HS 0x4 + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_EVENT_CHAR + * wValue: Event Char + * wIndex: Port + * wLength: 0 + * Data: None + * + * wValue: + * B0..7 Event Character + * B8 Event Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + * FTDI_SIO_SET_EVENT_CHAR + * + * Set the special event character for the specified communications port. + * If the device sees this character it will immediately return the + * data read so far - rather than wait 40ms or until 62 bytes are read + * which is what normally happens. + */ + + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_ERROR_CHAR + * wValue: Error Char + * wIndex: Port + * wLength: 0 + * Data: None + * + * Error Char + * B0..7 Error Character + * B8 Error Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + * + * FTDI_SIO_SET_ERROR_CHAR + * Set the parity error replacement character for the specified communications + * port. + */ + + +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_SIO_GET_MODEM_STATUS + * wValue: zero + * wIndex: Port + * wLength: 1 + * Data: Status + * + * One byte of data is returned + * B0..3 0 + * B4 CTS + * 0 = inactive + * 1 = active + * B5 DSR + * 0 = inactive + * 1 = active + * B6 Ring Indicator (RI) + * 0 = inactive + * 1 = active + * B7 Receive Line Signal Detect (RLSD) + * 0 = inactive + * 1 = active + * + * FTDI_SIO_GET_MODEM_STATUS + * Retrieve the current value of the modem status register. + */ +#define FTDI_SIO_CTS_MASK 0x10 +#define FTDI_SIO_DSR_MASK 0x20 +#define FTDI_SIO_RI_MASK 0x40 +#define FTDI_SIO_RLSD_MASK 0x80 + + + +/* + * + * DATA FORMAT + * + * IN Endpoint + * + * The device reserves the first two bytes of data on this endpoint to contain + * the current values of the modem and line status registers. In the absence of + * data, the device generates a message consisting of these two status bytes + * every 40 ms. + * + * Byte 0: Modem Status + * NOTE: 4 upper bits have same layout as the MSR register in a 16550 + * + * Offset Description + * B0..3 Port + * B4 Clear to Send (CTS) + * B5 Data Set Ready (DSR) + * B6 Ring Indicator (RI) + * B7 Receive Line Signal Detect (RLSD) + * + * Byte 1: Line Status + * NOTE: same layout as the LSR register in a 16550 + * + * Offset Description + * B0 Data Ready (DR) + * B1 Overrun Error (OE) + * B2 Parity Error (PE) + * B3 Framing Error (FE) + * B4 Break Interrupt (BI) + * B5 Transmitter Holding Register (THRE) + * B6 Transmitter Empty (TEMT) + * B7 Error in RCVR FIFO + * + * + * OUT Endpoint + * + * This device reserves the first bytes of data on this endpoint contain the + * length and port identifier of the message. For the FTDI USB Serial converter + * the port identifier is always 1. + * + * Byte 0: Port & length + * + * Offset Description + * B0..1 Port + * B2..7 Length of message - (not including Byte 0) + * + */ +#define FTDI_PORT_MASK 0x0f +#define FTDI_MSR_MASK 0xf0 +#define FTDI_GET_MSR(p) (((p)[0]) & FTDI_MSR_MASK) +#define FTDI_GET_LSR(p) ((p)[1]) +#define FTDI_LSR_MASK (~0x60) /* interesting bits */ +#define FTDI_OUT_TAG(len, port) (((len) << 2) | (port)) diff --git a/sys/dev/usb2/serial/ugensa2.c b/sys/dev/usb2/serial/ugensa2.c new file mode 100644 index 000000000000..e3ae41906f9a --- /dev/null +++ b/sys/dev/usb2/serial/ugensa2.c @@ -0,0 +1,462 @@ +/* $FreeBSD$ */ +/* $NetBSD: ugensa.c,v 1.9.2.1 2007/03/24 14:55:50 yamt Exp $ */ + +/* + * Copyright (c) 2004, 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Roland C. Dowdeswell . + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * NOTE: all function names beginning like "ugensa_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define UGENSA_BUF_SIZE 2048 /* bytes */ +#define UGENSA_N_TRANSFER 4 /* units */ +#define UGENSA_CONFIG_INDEX 0 +#define UGENSA_IFACE_INDEX 0 +#define UGENSA_IFACE_MAX 8 /* exclusivly */ + +struct ugensa_sub_softc { + struct usb2_com_softc *sc_usb2_com_ptr; + struct usb2_xfer *sc_xfer[UGENSA_N_TRANSFER]; + + uint8_t sc_flags; +#define UGENSA_FLAG_BULK_READ_STALL 0x01 +#define UGENSA_FLAG_BULK_WRITE_STALL 0x02 +}; + +struct ugensa_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom[UGENSA_IFACE_MAX]; + struct ugensa_sub_softc sc_sub[UGENSA_IFACE_MAX]; + + struct mtx sc_mtx; + uint8_t sc_niface; +}; + +/* prototypes */ + +static device_probe_t ugensa_probe; +static device_attach_t ugensa_attach; +static device_detach_t ugensa_detach; + +static usb2_callback_t ugensa_bulk_write_callback; +static usb2_callback_t ugensa_bulk_write_clear_stall_callback; +static usb2_callback_t ugensa_bulk_read_callback; +static usb2_callback_t ugensa_bulk_read_clear_stall_callback; + +static void ugensa_start_read(struct usb2_com_softc *ucom); +static void ugensa_stop_read(struct usb2_com_softc *ucom); +static void ugensa_start_write(struct usb2_com_softc *ucom); +static void ugensa_stop_write(struct usb2_com_softc *ucom); + +static const struct usb2_config + ugensa_xfer_config[UGENSA_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UGENSA_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &ugensa_bulk_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UGENSA_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &ugensa_bulk_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ugensa_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &ugensa_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback ugensa_callback = { + .usb2_com_start_read = &ugensa_start_read, + .usb2_com_stop_read = &ugensa_stop_read, + .usb2_com_start_write = &ugensa_start_write, + .usb2_com_stop_write = &ugensa_stop_write, +}; + +static device_method_t ugensa_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, ugensa_probe), + DEVMETHOD(device_attach, ugensa_attach), + DEVMETHOD(device_detach, ugensa_detach), + {0, 0} +}; + +static devclass_t ugensa_devclass; + +static driver_t ugensa_driver = { + .name = "ugensa", + .methods = ugensa_methods, + .size = sizeof(struct ugensa_softc), +}; + +DRIVER_MODULE(ugensa, ushub, ugensa_driver, ugensa_devclass, NULL, 0); +MODULE_DEPEND(ugensa, usb2_serial, 1, 1, 1); +MODULE_DEPEND(ugensa, usb2_core, 1, 1, 1); +MODULE_DEPEND(ugensa, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static const struct usb2_device_id ugensa_devs[] = { + {USB_VPI(USB_VENDOR_AIRPRIME, USB_PRODUCT_AIRPRIME_PC5220, 0)}, + {USB_VPI(USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_CDMA_MODEM1, 0)}, + {USB_VPI(USB_VENDOR_KYOCERA2, USB_PRODUCT_KYOCERA2_CDMA_MSM_K, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_49GPLUS, 0)}, + {USB_VPI(USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E270, 0)}, + {USB_VPI(USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_MOBILE, 0)}, + {USB_VPI(USB_VENDOR_MERLIN, USB_PRODUCT_MERLIN_V620, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_CDMA_MODEM, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_ES620, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U720, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U727, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U740, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U740_2, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U950D, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V620, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V640, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V720, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V740, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_X950D, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U870, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_XU870, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL2, USB_PRODUCT_NOVATEL2_FLEXPACKGPS, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD580, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD595, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC595U, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC597E, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_C597, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880E, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880U, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881E, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881U, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_EM5625, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720_2, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5725, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MINI5725, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD875, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755_2, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755_3, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8765, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC875U, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8775_2, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8780, 0)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8781, 0)}, +}; + +static int +ugensa_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UGENSA_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != 0) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(ugensa_devs, sizeof(ugensa_devs), uaa)); +} + +static int +ugensa_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ugensa_softc *sc = device_get_softc(dev); + struct ugensa_sub_softc *ssc; + struct usb2_interface *iface; + int32_t error; + uint8_t iface_index; + int x, cnt; + + if (sc == NULL) + return (ENOMEM); + + device_set_usb2_desc(dev); + mtx_init(&sc->sc_mtx, "ugensa", NULL, MTX_DEF); + + /* Figure out how many interfaces this device has got */ + for (cnt = 0; cnt < UGENSA_IFACE_MAX; cnt++) { + if ((usb2_get_pipe(uaa->device, cnt, ugensa_xfer_config + 0) == NULL) || + (usb2_get_pipe(uaa->device, cnt, ugensa_xfer_config + 1) == NULL)) { + /* we have reached the end */ + break; + } + } + + if (cnt == 0) { + device_printf(dev, "No interfaces!\n"); + goto detach; + } + for (x = 0; x < cnt; x++) { + iface = usb2_get_iface(uaa->device, x); + if (iface->idesc->bInterfaceClass != UICLASS_VENDOR) + /* Not a serial port, most likely a SD reader */ + continue; + + ssc = sc->sc_sub + sc->sc_niface; + ssc->sc_usb2_com_ptr = sc->sc_ucom + sc->sc_niface; + + iface_index = (UGENSA_IFACE_INDEX + x); + error = usb2_transfer_setup(uaa->device, + &iface_index, ssc->sc_xfer, ugensa_xfer_config, + UGENSA_N_TRANSFER, ssc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + /* clear stall at first run */ + ssc->sc_flags |= (UGENSA_FLAG_BULK_WRITE_STALL | + UGENSA_FLAG_BULK_READ_STALL); + + /* initialize port number */ + ssc->sc_usb2_com_ptr->sc_portno = sc->sc_niface; + sc->sc_niface++; + if (x != uaa->info.bIfaceIndex) + usb2_set_parent_iface(uaa->device, x, + uaa->info.bIfaceIndex); + } + device_printf(dev, "Found %d interfaces.\n", sc->sc_niface); + + error = usb2_com_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_niface, sc, + &ugensa_callback, &sc->sc_mtx); + if (error) { + DPRINTF("attach failed\n"); + goto detach; + } + return (0); /* success */ + +detach: + ugensa_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ugensa_detach(device_t dev) +{ + struct ugensa_softc *sc = device_get_softc(dev); + uint8_t x; + + usb2_com_detach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_niface); + + for (x = 0; x < sc->sc_niface; x++) { + usb2_transfer_unsetup(sc->sc_sub[x].sc_xfer, UGENSA_N_TRANSFER); + } + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +ugensa_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct ugensa_sub_softc *ssc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (ssc->sc_flags & UGENSA_FLAG_BULK_WRITE_STALL) { + usb2_transfer_start(ssc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(ssc->sc_usb2_com_ptr, xfer->frbuffers, 0, + UGENSA_BUF_SIZE, &actlen)) { + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + ssc->sc_flags |= UGENSA_FLAG_BULK_WRITE_STALL; + usb2_transfer_start(ssc->sc_xfer[2]); + } + return; + + } +} + +static void +ugensa_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ugensa_sub_softc *ssc = xfer->priv_sc; + struct usb2_xfer *xfer_other = ssc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + ssc->sc_flags &= ~UGENSA_FLAG_BULK_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ugensa_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct ugensa_sub_softc *ssc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(ssc->sc_usb2_com_ptr, xfer->frbuffers, 0, + xfer->actlen); + + case USB_ST_SETUP: + if (ssc->sc_flags & UGENSA_FLAG_BULK_READ_STALL) { + usb2_transfer_start(ssc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + ssc->sc_flags |= UGENSA_FLAG_BULK_READ_STALL; + usb2_transfer_start(ssc->sc_xfer[3]); + } + return; + + } +} + +static void +ugensa_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ugensa_sub_softc *ssc = xfer->priv_sc; + struct usb2_xfer *xfer_other = ssc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + ssc->sc_flags &= ~UGENSA_FLAG_BULK_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ugensa_start_read(struct usb2_com_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usb2_transfer_start(ssc->sc_xfer[1]); + return; +} + +static void +ugensa_stop_read(struct usb2_com_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usb2_transfer_stop(ssc->sc_xfer[3]); + usb2_transfer_stop(ssc->sc_xfer[1]); + return; +} + +static void +ugensa_start_write(struct usb2_com_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usb2_transfer_start(ssc->sc_xfer[0]); + return; +} + +static void +ugensa_stop_write(struct usb2_com_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usb2_transfer_stop(ssc->sc_xfer[2]); + usb2_transfer_stop(ssc->sc_xfer[0]); + return; +} diff --git a/sys/dev/usb2/serial/uipaq2.c b/sys/dev/usb2/serial/uipaq2.c new file mode 100644 index 000000000000..91503d88665d --- /dev/null +++ b/sys/dev/usb2/serial/uipaq2.c @@ -0,0 +1,1408 @@ +/* $NetBSD: uipaq.c,v 1.4 2006/11/16 01:33:27 christos Exp $ */ +/* $OpenBSD: uipaq.c,v 1.1 2005/06/17 23:50:33 deraadt Exp $ */ + +/* + * Copyright (c) 2000-2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * iPAQ driver + * + * 19 July 2003: Incorporated changes suggested by Sam Lawrance from + * the uppc module + * + * + * Contact isis@cs.umd.edu if you have any questions/comments about this driver + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define UIPAQ_CONFIG_INDEX 0 /* config number 1 */ +#define UIPAQ_IFACE_INDEX 0 + +#define UIPAQ_BUF_SIZE 1024 +#define UIPAQ_N_DATA_TRANSFER 4 + +struct uipaq_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer_data[UIPAQ_N_DATA_TRANSFER]; + struct usb2_device *sc_udev; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* modem status register */ + uint8_t sc_flag; +#define UIPAQ_FLAG_READ_STALL 0x01 +#define UIPAQ_FLAG_WRITE_STALL 0x02 +#define UIPAQ_FLAG_INTR_STALL 0x04 +}; + +static device_probe_t uipaq_probe; +static device_attach_t uipaq_attach; +static device_detach_t uipaq_detach; + +static usb2_callback_t uipaq_write_callback; +static usb2_callback_t uipaq_read_callback; +static usb2_callback_t uipaq_write_clear_stall_callback; +static usb2_callback_t uipaq_read_clear_stall_callback; + +static void uipaq_start_read(struct usb2_com_softc *ucom); +static void uipaq_stop_read(struct usb2_com_softc *ucom); +static void uipaq_start_write(struct usb2_com_softc *ucom); +static void uipaq_stop_write(struct usb2_com_softc *ucom); +static void uipaq_cfg_do_request(struct uipaq_softc *sc, struct usb2_device_request *req, void *data); +static void uipaq_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void uipaq_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static void uipaq_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); + +static const struct usb2_config uipaq_config_data[UIPAQ_N_DATA_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UIPAQ_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &uipaq_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UIPAQ_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &uipaq_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uipaq_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uipaq_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback uipaq_callback = { + .usb2_com_cfg_set_dtr = &uipaq_cfg_set_dtr, + .usb2_com_cfg_set_rts = &uipaq_cfg_set_rts, + .usb2_com_cfg_set_break = &uipaq_cfg_set_break, + .usb2_com_start_read = &uipaq_start_read, + .usb2_com_stop_read = &uipaq_stop_read, + .usb2_com_start_write = &uipaq_start_write, + .usb2_com_stop_write = &uipaq_stop_write, +}; + +/* + * Much of this list is generated from lists of other drivers that + * support the same hardware. Numeric values are used where no usbdevs + * entries exist. + */ +static const struct usb2_device_id uipaq_devs[] = { + /* Socket USB Sync */ + {USB_VPI(0x0104, 0x00be, 0)}, + /* USB Sync 0301 */ + {USB_VPI(0x04ad, 0x0301, 0)}, + /* USB Sync 0302 */ + {USB_VPI(0x04ad, 0x0302, 0)}, + /* USB Sync 0303 */ + {USB_VPI(0x04ad, 0x0303, 0)}, + /* GPS Pocket PC USB Sync */ + {USB_VPI(0x04ad, 0x0306, 0)}, + /* HHP PDT */ + {USB_VPI(0x0536, 0x01a0, 0)}, + /* Intermec Mobile Computer */ + {USB_VPI(0x067e, 0x1001, 0)}, + /* Linkup Systems USB Sync */ + {USB_VPI(0x094b, 0x0001, 0)}, + /* BCOM USB Sync 0065 */ + {USB_VPI(0x0960, 0x0065, 0)}, + /* BCOM USB Sync 0066 */ + {USB_VPI(0x0960, 0x0066, 0)}, + /* BCOM USB Sync 0067 */ + {USB_VPI(0x0960, 0x0067, 0)}, + /* Portatec USB Sync */ + {USB_VPI(0x0961, 0x0010, 0)}, + /* Trimble GeoExplorer */ + {USB_VPI(0x099e, 0x0052, 0)}, + /* TDS Data Collector */ + {USB_VPI(0x099e, 0x4000, 0)}, + /* Motorola iDEN Smartphone */ + {USB_VPI(0x0c44, 0x03a2, 0)}, + /* Cesscom Luxian Series */ + {USB_VPI(0x0c8e, 0x6000, 0)}, + /* Motorola PowerPad Pocket PCDevice */ + {USB_VPI(0x0cad, 0x9001, 0)}, + /* Freedom Scientific USB Sync */ + {USB_VPI(0x0f4e, 0x0200, 0)}, + /* Cyberbank USB Sync */ + {USB_VPI(0x0f98, 0x0201, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3001, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3002, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3003, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x4001, 0)}, + /* E-TEN USB Sync */ + {USB_VPI(0x1066, 0x00ce, 0)}, + /* E-TEN P3XX Pocket PC */ + {USB_VPI(0x1066, 0x0300, 0)}, + /* E-TEN P5XX Pocket PC */ + {USB_VPI(0x1066, 0x0500, 0)}, + /* E-TEN P6XX Pocket PC */ + {USB_VPI(0x1066, 0x0600, 0)}, + /* E-TEN P7XX Pocket PC */ + {USB_VPI(0x1066, 0x0700, 0)}, + /* Psion Teklogix Sync 753x */ + {USB_VPI(0x1114, 0x0001, 0)}, + /* Psion Teklogix Sync netBookPro */ + {USB_VPI(0x1114, 0x0004, 0)}, + /* Psion Teklogix Sync 7525 */ + {USB_VPI(0x1114, 0x0006, 0)}, + /* VES USB Sync */ + {USB_VPI(0x1182, 0x1388, 0)}, + /* Rugged Pocket PC 2003 */ + {USB_VPI(0x11d9, 0x1002, 0)}, + /* Rugged Pocket PC 2003 */ + {USB_VPI(0x11d9, 0x1003, 0)}, + /* USB Sync 03 */ + {USB_VPI(0x1231, 0xce01, 0)}, + /* USB Sync 03 */ + {USB_VPI(0x1231, 0xce02, 0)}, + /* Mio DigiWalker PPC StrongARM */ + {USB_VPI(0x3340, 0x011c, 0)}, + /* Mio DigiWalker 338 */ + {USB_VPI(0x3340, 0x0326, 0)}, + /* Mio DigiWalker 338 */ + {USB_VPI(0x3340, 0x0426, 0)}, + /* Mio DigiWalker USB Sync */ + {USB_VPI(0x3340, 0x043a, 0)}, + /* MiTAC USB Sync 528 */ + {USB_VPI(0x3340, 0x051c, 0)}, + /* Mio DigiWalker SmartPhone USB Sync */ + {USB_VPI(0x3340, 0x053a, 0)}, + /* MiTAC USB Sync */ + {USB_VPI(0x3340, 0x071c, 0)}, + /* Generic PPC StrongARM */ + {USB_VPI(0x3340, 0x0b1c, 0)}, + /* Generic PPC USB Sync */ + {USB_VPI(0x3340, 0x0e3a, 0)}, + /* Itautec USB Sync */ + {USB_VPI(0x3340, 0x0f1c, 0)}, + /* Generic SmartPhone USB Sync */ + {USB_VPI(0x3340, 0x0f3a, 0)}, + /* Itautec USB Sync */ + {USB_VPI(0x3340, 0x1326, 0)}, + /* YAKUMO USB Sync */ + {USB_VPI(0x3340, 0x191c, 0)}, + /* Vobis USB Sync */ + {USB_VPI(0x3340, 0x2326, 0)}, + /* MEDION Winodws Moble USB Sync */ + {USB_VPI(0x3340, 0x3326, 0)}, + /* Legend USB Sync */ + {USB_VPI(0x3708, 0x20ce, 0)}, + /* Lenovo USB Sync */ + {USB_VPI(0x3708, 0x21ce, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0210, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0211, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0400, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0410, 0)}, + /* Smartphone */ + {USB_VPI(0x4505, 0x0010, 0)}, + /* SAGEM Wireless Assistant */ + {USB_VPI(0x5e04, 0xce00, 0)}, + /* c10 Series */ + {USB_VPI(USB_VENDOR_ACER, 0x1631, 0)}, + /* c20 Series */ + {USB_VPI(USB_VENDOR_ACER, 0x1632, 0)}, + /* Acer n10 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e1, 0)}, + /* Acer n20 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e2, 0)}, + /* Acer n30 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e3, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4200, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4201, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4202, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x9200, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x9202, 0)}, + /**/ + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_P535, 0)}, + /* CASIO USB Sync 2001 */ + {USB_VPI(USB_VENDOR_CASIO, 0x2001, 0)}, + /* CASIO USB Sync 2003 */ + {USB_VPI(USB_VENDOR_CASIO, 0x2003, 0)}, + /**/ + {USB_VPI(USB_VENDOR_CASIO, USB_PRODUCT_CASIO_BE300, 0)}, + /* MyGuide 7000 XL USB Sync */ + {USB_VPI(USB_VENDOR_COMPAL, 0x0531, 0)}, + /* Compaq iPAQ USB Sync */ + {USB_VPI(USB_VENDOR_COMPAQ, 0x0032, 0)}, + /**/ + {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQPOCKETPC, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4001, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4002, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4003, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4004, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4005, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4006, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4007, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4008, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4009, 0)}, + /* Fujitsu Siemens Computers USB Sync */ + {USB_VPI(USB_VENDOR_FSC, 0x1001, 0)}, + /* FUJITSU USB Sync */ + {USB_VPI(USB_VENDOR_FUJITSU, 0x1058, 0)}, + /* FUJITSU USB Sync */ + {USB_VPI(USB_VENDOR_FUJITSU, 0x1079, 0)}, + /* Askey USB Sync */ + {USB_VPI(USB_VENDOR_GIGASET, 0x0601, 0)}, + /* Hitachi USB Sync */ + {USB_VPI(USB_VENDOR_HITACHI, 0x0014, 0)}, + /* HP USB Sync 1612 */ + {USB_VPI(USB_VENDOR_HP, 0x1216, 0)}, + /* HP USB Sync 1620 */ + {USB_VPI(USB_VENDOR_HP, 0x2016, 0)}, + /* HP USB Sync 1621 */ + {USB_VPI(USB_VENDOR_HP, 0x2116, 0)}, + /* HP USB Sync 1622 */ + {USB_VPI(USB_VENDOR_HP, 0x2216, 0)}, + /* HP USB Sync 1630 */ + {USB_VPI(USB_VENDOR_HP, 0x3016, 0)}, + /* HP USB Sync 1631 */ + {USB_VPI(USB_VENDOR_HP, 0x3116, 0)}, + /* HP USB Sync 1632 */ + {USB_VPI(USB_VENDOR_HP, 0x3216, 0)}, + /* HP USB Sync 1640 */ + {USB_VPI(USB_VENDOR_HP, 0x4016, 0)}, + /* HP USB Sync 1641 */ + {USB_VPI(USB_VENDOR_HP, 0x4116, 0)}, + /* HP USB Sync 1642 */ + {USB_VPI(USB_VENDOR_HP, 0x4216, 0)}, + /* HP USB Sync 1650 */ + {USB_VPI(USB_VENDOR_HP, 0x5016, 0)}, + /* HP USB Sync 1651 */ + {USB_VPI(USB_VENDOR_HP, 0x5116, 0)}, + /* HP USB Sync 1652 */ + {USB_VPI(USB_VENDOR_HP, 0x5216, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_2215, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_568J, 0)}, + /* HTC USB Modem */ + {USB_VPI(USB_VENDOR_HTC, 0x00cf, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a01, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a02, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a03, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a04, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a05, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a06, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a07, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a08, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a09, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a10, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a11, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a12, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a13, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a14, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a15, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a16, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a17, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a18, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a19, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a20, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a21, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a22, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a23, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a24, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a25, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a26, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a27, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a28, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a29, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a30, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a31, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a32, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a33, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a34, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a35, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a36, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a37, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a38, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a39, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a40, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a41, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a42, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a43, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a44, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a45, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a46, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a47, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a48, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a49, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4f, 0)}, + /* HTC SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a50, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a52, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a53, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a54, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a55, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a56, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a57, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a58, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a59, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a60, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a61, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a62, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a63, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a64, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a65, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a66, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a67, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a68, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a69, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a70, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a71, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a72, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a73, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a74, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a75, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a76, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a77, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a78, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a79, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a80, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a81, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a82, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a83, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a84, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a85, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a86, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a87, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a88, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a89, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a90, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a91, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a92, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a93, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a94, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a95, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a96, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a97, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a98, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a99, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9f, 0)}, + /* "High Tech Computer Corp" */ + {USB_VPI(USB_VENDOR_HTC, 0x0bce, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_PPC6700MODEM, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_SMARTPHONE, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_WINMOBILE, 0)}, + /* JVC USB Sync */ + {USB_VPI(USB_VENDOR_JVC, 0x3011, 0)}, + /* JVC USB Sync */ + {USB_VPI(USB_VENDOR_JVC, 0x3012, 0)}, + /* LGE USB Sync */ + {USB_VPI(USB_VENDOR_LG, 0x9c01, 0)}, + /* Microsoft USB Sync */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x00ce, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0400, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0401, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0402, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0403, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0404, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0405, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0406, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0407, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0408, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0409, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040a, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040b, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040c, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040d, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040e, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040f, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0410, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0411, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0412, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0413, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0414, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0415, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0416, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0417, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0432, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0433, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0434, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0435, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0436, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0437, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0438, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0439, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0440, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0441, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0442, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0443, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0444, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0445, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0446, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0447, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0448, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0449, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0450, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0451, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0452, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0453, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0454, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0455, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0456, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0457, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0458, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0459, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0460, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0461, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0462, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0463, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0464, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0465, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0466, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0467, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0468, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0469, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0470, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0471, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0472, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0473, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0474, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0475, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0476, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0477, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0478, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0479, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x047a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x047b, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04c8, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04c9, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ca, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cb, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cc, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cd, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ce, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d7, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d8, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d9, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04da, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04db, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04dc, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04dd, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04de, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04df, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e0, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e1, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e2, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e3, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e4, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e5, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e6, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e7, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e8, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e9, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ea, 0)}, + /* Motorola MPx200 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4204, 0)}, + /* Motorola MPc GSM */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4214, 0)}, + /* Motorola MPx220 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4224, 0)}, + /* Motorola MPc CDMA */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4234, 0)}, + /* Motorola MPx100 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4244, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d5, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d6, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d7, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x8024, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x8025, 0)}, + /* Panasonic USB Sync */ + {USB_VPI(USB_VENDOR_PANASONIC, 0x2500, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f00, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f01, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f02, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f03, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f04, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6611, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6613, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6615, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6617, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6619, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x661b, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x662e, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6630, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6632, 0)}, + /* SHARP WS003SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9102, 0)}, + /* SHARP WS004SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9121, 0)}, + /* SHARP S01SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9151, 0)}, +/**/ + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ES, 0)}, + /* Symbol USB Sync */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2000, 0)}, + /* Symbol USB Sync 0x2001 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2001, 0)}, + /* Symbol USB Sync 0x2002 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2002, 0)}, + /* Symbol USB Sync 0x2003 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2003, 0)}, + /* Symbol USB Sync 0x2004 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2004, 0)}, + /* Symbol USB Sync 0x2005 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2005, 0)}, + /* Symbol USB Sync 0x2006 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2006, 0)}, + /* Symbol USB Sync 0x2007 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2007, 0)}, + /* Symbol USB Sync 0x2008 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2008, 0)}, + /* Symbol USB Sync 0x2009 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2009, 0)}, + /* Symbol USB Sync 0x200a */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x200a, 0)}, + /* TOSHIBA USB Sync 0700 */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0700, 0)}, + /* TOSHIBA Pocket PC e310 */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0705, 0)}, + /* TOSHIBA Pocket PC e330 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0707, 0)}, + /* TOSHIBA Pocket PC e350Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0708, 0)}, + /* TOSHIBA Pocket PC e750 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0709, 0)}, + /* TOSHIBA Pocket PC e400 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x070a, 0)}, + /* TOSHIBA Pocket PC e800 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x070b, 0)}, + /* TOSHIBA Pocket PC e740 */ + {USB_VPI(USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_POCKETPC_E740, 0)}, + /* ViewSonic Color Pocket PC V35 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x0ed9, 0)}, + /* ViewSonic Color Pocket PC V36 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1527, 0)}, + /* ViewSonic Color Pocket PC V37 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1529, 0)}, + /* ViewSonic Color Pocket PC V38 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x152b, 0)}, + /* ViewSonic Pocket PC */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x152e, 0)}, + /* ViewSonic Communicator Pocket PC */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1921, 0)}, + /* ViewSonic Smartphone */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1922, 0)}, + /* ViewSonic Pocket PC V30 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1923, 0)}, +}; + +static device_method_t uipaq_methods[] = { + DEVMETHOD(device_probe, uipaq_probe), + DEVMETHOD(device_attach, uipaq_attach), + DEVMETHOD(device_detach, uipaq_detach), + {0, 0} +}; + +static devclass_t uipaq_devclass; + +static driver_t uipaq_driver = { + .name = "uipaq", + .methods = uipaq_methods, + .size = sizeof(struct uipaq_softc), +}; + +DRIVER_MODULE(uipaq, ushub, uipaq_driver, uipaq_devclass, NULL, 0); +MODULE_DEPEND(uipaq, usb2_serial, 1, 1, 1); +MODULE_DEPEND(uipaq, usb2_core, 1, 1, 1); +MODULE_DEPEND(uipaq, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static int +uipaq_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UIPAQ_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UIPAQ_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(uipaq_devs, sizeof(uipaq_devs), uaa)); +} + +static int +uipaq_attach(device_t dev) +{ + struct usb2_device_request req; + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uipaq_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + uint8_t i; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + + device_set_usb2_desc(dev); + + /* + * Send magic bytes, cribbed from Linux ipaq driver that + * claims to have sniffed them from Win98. Wait for driver to + * become ready on device side? + */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, UCDC_LINE_DTR); + USETW(req.wIndex, 0x0); + USETW(req.wLength, 0); + for (i = 0; i != 64; i++) { + error = + usb2_do_request_flags(uaa->device, NULL, &req, + NULL, 0, NULL, 100); + if (error == 0) + break; + usb2_pause_mtx(NULL, 100); + } + + iface_index = UIPAQ_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer_data, uipaq_config_data, + UIPAQ_N_DATA_TRANSFER, sc, &Giant); + + if (error) { + goto detach; + } + /* clear stall at first run */ + sc->sc_flag |= (UIPAQ_FLAG_READ_STALL | + UIPAQ_FLAG_WRITE_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uipaq_callback, &Giant); + if (error) { + goto detach; + } + return (0); + +detach: + uipaq_detach(dev); + return (ENXIO); +} + +int +uipaq_detach(device_t dev) +{ + struct uipaq_softc *sc = device_get_softc(dev); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer_data, UIPAQ_N_DATA_TRANSFER); + + return (0); +} + +static void +uipaq_start_read(struct usb2_com_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + /* start read endpoint */ + usb2_transfer_start(sc->sc_xfer_data[1]); + return; +} + +static void +uipaq_stop_read(struct usb2_com_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + /* stop read endpoint */ + usb2_transfer_stop(sc->sc_xfer_data[3]); + usb2_transfer_stop(sc->sc_xfer_data[1]); + return; +} + +static void +uipaq_start_write(struct usb2_com_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer_data[0]); + return; +} + +static void +uipaq_stop_write(struct usb2_com_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer_data[2]); + usb2_transfer_stop(sc->sc_xfer_data[0]); + return; +} + +static void +uipaq_cfg_do_request(struct uipaq_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + goto error; + } + err = usb2_do_request(sc->sc_udev, &Giant, req, data); + + if (err) { + + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +static void +uipaq_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + uipaq_cfg_do_request(sc, &req, NULL); + return; +} + +static void +uipaq_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + uipaq_cfg_do_request(sc, &req, NULL); + return; +} + +static void +uipaq_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + uint16_t temp; + + temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + uipaq_cfg_do_request(sc, &req, NULL); + return; +} + +static void +uipaq_write_callback(struct usb2_xfer *xfer) +{ + struct uipaq_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flag & UIPAQ_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer_data[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UIPAQ_BUF_SIZE, &actlen)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UIPAQ_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer_data[2]); + } + return; + + } +} + +static void +uipaq_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uipaq_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_data[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UIPAQ_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uipaq_read_callback(struct usb2_xfer *xfer) +{ + struct uipaq_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, + xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flag & UIPAQ_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer_data[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UIPAQ_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer_data[3]); + } + return; + } +} + +static void +uipaq_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uipaq_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_data[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UIPAQ_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} diff --git a/sys/dev/usb2/serial/ulpt2.c b/sys/dev/usb2/serial/ulpt2.c new file mode 100644 index 000000000000..98910e38bfcc --- /dev/null +++ b/sys/dev/usb2/serial/ulpt2.c @@ -0,0 +1,798 @@ +#include +__FBSDID("$FreeBSD$"); + +/* $NetBSD: ulpt.c,v 1.60 2003/10/04 21:19:50 augustss Exp $ */ + +/*- + * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * Printer Class spec: http://www.usb.org/developers/data/devclass/usbprint109.PDF + * Printer Class spec: http://www.usb.org/developers/devclass_docs/usbprint11.pdf + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR ulpt_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int ulpt_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ulpt, CTLFLAG_RW, 0, "USB ulpt"); +SYSCTL_INT(_hw_usb2_ulpt, OID_AUTO, debug, CTLFLAG_RW, + &ulpt_debug, 0, "Debug level"); +#endif + +#define ULPT_BSIZE (1<<15) /* bytes */ +#define ULPT_IFQ_MAXLEN 2 /* units */ +#define ULPT_N_TRANSFER 5 /* units */ + +#define UR_GET_DEVICE_ID 0x00 +#define UR_GET_PORT_STATUS 0x01 +#define UR_SOFT_RESET 0x02 + +#define LPS_NERR 0x08 /* printer no error */ +#define LPS_SELECT 0x10 /* printer selected */ +#define LPS_NOPAPER 0x20 /* printer out of paper */ +#define LPS_INVERT (LPS_SELECT|LPS_NERR) +#define LPS_MASK (LPS_SELECT|LPS_NERR|LPS_NOPAPER) + +struct ulpt_softc { + struct usb2_fifo_sc sc_fifo; + struct usb2_fifo_sc sc_fifo_noreset; + struct mtx sc_mtx; + struct usb2_callout sc_watchdog; + + device_t sc_dev; + struct usb2_device *sc_udev; + struct usb2_fifo *sc_fifo_open[2]; + struct usb2_xfer *sc_xfer[ULPT_N_TRANSFER]; + + int sc_fflags; /* current open flags, FREAD and + * FWRITE */ + + uint8_t sc_flags; +#define ULPT_FLAG_READ_STALL 0x04 /* read transfer stalled */ +#define ULPT_FLAG_WRITE_STALL 0x08 /* write transfer stalled */ + + uint8_t sc_iface_no; + uint8_t sc_last_status; + uint8_t sc_zlps; /* number of consequtive zero length + * packets received */ +}; + +/* prototypes */ + +static device_probe_t ulpt_probe; +static device_attach_t ulpt_attach; +static device_detach_t ulpt_detach; + +static usb2_callback_t ulpt_write_callback; +static usb2_callback_t ulpt_write_clear_stall_callback; +static usb2_callback_t ulpt_read_callback; +static usb2_callback_t ulpt_read_clear_stall_callback; +static usb2_callback_t ulpt_status_callback; + +static void ulpt_reset(struct ulpt_softc *sc); +static void ulpt_watchdog(void *arg); + +static usb2_fifo_close_t ulpt_close; +static usb2_fifo_cmd_t ulpt_start_read; +static usb2_fifo_cmd_t ulpt_start_write; +static usb2_fifo_cmd_t ulpt_stop_read; +static usb2_fifo_cmd_t ulpt_stop_write; +static usb2_fifo_ioctl_t ulpt_ioctl; +static usb2_fifo_open_t ulpt_open; +static usb2_fifo_open_t unlpt_open; + +static struct usb2_fifo_methods ulpt_fifo_methods = { + .f_close = &ulpt_close, + .f_ioctl = &ulpt_ioctl, + .f_open = &ulpt_open, + .f_start_read = &ulpt_start_read, + .f_start_write = &ulpt_start_write, + .f_stop_read = &ulpt_stop_read, + .f_stop_write = &ulpt_stop_write, + .basename[0] = "ulpt", +}; + +static struct usb2_fifo_methods unlpt_fifo_methods = { + .f_close = &ulpt_close, + .f_ioctl = &ulpt_ioctl, + .f_open = &unlpt_open, + .f_start_read = &ulpt_start_read, + .f_start_write = &ulpt_start_write, + .f_stop_read = &ulpt_stop_read, + .f_stop_write = &ulpt_stop_write, + .basename[0] = "unlpt", +}; + +static void +ulpt_reset(struct ulpt_softc *sc) +{ + struct usb2_device_request req; + + DPRINTFN(2, "\n"); + + req.bRequest = UR_SOFT_RESET; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + /* + * There was a mistake in the USB printer 1.0 spec that gave the + * request type as UT_WRITE_CLASS_OTHER; it should have been + * UT_WRITE_CLASS_INTERFACE. Many printers use the old one, + * so we try both. + */ + + mtx_lock(&sc->sc_mtx); + req.bmRequestType = UT_WRITE_CLASS_OTHER; + if (usb2_do_request_flags(sc->sc_udev, &sc->sc_mtx, + &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.0 */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + if (usb2_do_request_flags(sc->sc_udev, &sc->sc_mtx, + &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.1 */ + /* ignore error */ + } + } + mtx_unlock(&sc->sc_mtx); + return; +} + +static void +ulpt_write_callback(struct usb2_xfer *xfer) +{ + struct ulpt_softc *sc = xfer->priv_sc; + struct usb2_fifo *f = sc->sc_fifo_open[USB_FIFO_TX]; + uint32_t actlen; + + if (f == NULL) { + /* should not happen */ + DPRINTF("no FIFO\n"); + return; + } + DPRINTF("state=0x%x\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: + if (sc->sc_flags & ULPT_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + break; + } + if (usb2_fifo_get_data(f, xfer->frbuffers, + 0, xfer->max_data_length, &actlen, 0)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= ULPT_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + break; + } + return; +} + +static void +ulpt_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ulpt_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~ULPT_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ulpt_read_callback(struct usb2_xfer *xfer) +{ + struct ulpt_softc *sc = xfer->priv_sc; + struct usb2_fifo *f = sc->sc_fifo_open[USB_FIFO_RX]; + + if (f == NULL) { + /* should not happen */ + DPRINTF("no FIFO\n"); + return; + } + DPRINTF("state=0x%x\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen == 0) { + + if (sc->sc_zlps == 4) { + /* enable BULK throttle */ + xfer->interval = 500; /* ms */ + } else { + sc->sc_zlps++; + } + } else { + /* disable BULK throttle */ + + xfer->interval = 0; + sc->sc_zlps = 0; + } + + usb2_fifo_put_data(f, xfer->frbuffers, + 0, xfer->actlen, 1); + + case USB_ST_SETUP: + if (sc->sc_flags & ULPT_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[4]); + break; + } + if (usb2_fifo_put_bytes_max(f) != 0) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + /* disable BULK throttle */ + xfer->interval = 0; + sc->sc_zlps = 0; + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= ULPT_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[4]); + } + break; + } + return; +} + +static void +ulpt_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ulpt_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~ULPT_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ulpt_status_callback(struct usb2_xfer *xfer) +{ + struct ulpt_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + uint8_t cur_status; + uint8_t new_status; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + usb2_copy_out(xfer->frbuffers + 1, 0, &cur_status, 1); + + cur_status = (cur_status ^ LPS_INVERT) & LPS_MASK; + new_status = cur_status & ~sc->sc_last_status; + sc->sc_last_status = cur_status; + + if (new_status & LPS_SELECT) + log(LOG_NOTICE, "%s: offline\n", + device_get_nameunit(sc->sc_dev)); + else if (new_status & LPS_NOPAPER) + log(LOG_NOTICE, "%s: out of paper\n", + device_get_nameunit(sc->sc_dev)); + else if (new_status & LPS_NERR) + log(LOG_NOTICE, "%s: output error\n", + device_get_nameunit(sc->sc_dev)); + break; + + case USB_ST_SETUP: + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_PORT_STATUS; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = 1; + xfer->nframes = 2; + usb2_start_hardware(xfer); + break; + + default: /* Error */ + DPRINTF("error=%s\n", usb2_errstr(xfer->error)); + if (xfer->error != USB_ERR_CANCELLED) { + /* wait for next watchdog timeout */ + } + break; + } + return; +} + +static const struct usb2_config ulpt_config[ULPT_N_TRANSFER] = { + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = ULPT_BSIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,.proxy_buffer = 1}, + .mh.callback = &ulpt_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = ULPT_BSIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1}, + .mh.callback = &ulpt_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request) + 1, + .mh.callback = &ulpt_status_callback, + .mh.timeout = 1000, /* 1 second */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ulpt_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ulpt_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static void +ulpt_start_read(struct usb2_fifo *fifo) +{ + struct ulpt_softc *sc = fifo->priv_sc0; + + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +ulpt_stop_read(struct usb2_fifo *fifo) +{ + struct ulpt_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[4]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +ulpt_start_write(struct usb2_fifo *fifo) +{ + struct ulpt_softc *sc = fifo->priv_sc0; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +ulpt_stop_write(struct usb2_fifo *fifo) +{ + struct ulpt_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static int +ulpt_open(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct ulpt_softc *sc = fifo->priv_sc0; + + /* we assume that open is a serial process */ + + if (sc->sc_fflags == 0) { + ulpt_reset(sc); + } + return (unlpt_open(fifo, fflags, td)); +} + +static int +unlpt_open(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct ulpt_softc *sc = fifo->priv_sc0; + + if (sc->sc_fflags & fflags) { + return (EBUSY); + } + if (fflags & FREAD) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + sc->sc_flags |= ULPT_FLAG_READ_STALL; + mtx_unlock(&sc->sc_mtx); + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_xfer[1]->max_data_length, + ULPT_IFQ_MAXLEN)) { + return (ENOMEM); + } + /* set which FIFO is opened */ + sc->sc_fifo_open[USB_FIFO_RX] = fifo; + } + if (fflags & FWRITE) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + sc->sc_flags |= ULPT_FLAG_WRITE_STALL; + mtx_unlock(&sc->sc_mtx); + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_xfer[0]->max_data_length, + ULPT_IFQ_MAXLEN)) { + return (ENOMEM); + } + /* set which FIFO is opened */ + sc->sc_fifo_open[USB_FIFO_TX] = fifo; + } + sc->sc_fflags |= fflags & (FREAD | FWRITE); + return (0); +} + +static void +ulpt_close(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct ulpt_softc *sc = fifo->priv_sc0; + + sc->sc_fflags &= ~(fflags & (FREAD | FWRITE)); + + if (fflags & (FREAD | FWRITE)) { + usb2_fifo_free_buffer(fifo); + } + return; +} + +static int +ulpt_ioctl(struct usb2_fifo *fifo, u_long cmd, void *data, + int fflags, struct thread *td) +{ + return (ENODEV); +} + +static int +ulpt_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if ((uaa->info.bInterfaceClass == UICLASS_PRINTER) && + (uaa->info.bInterfaceSubClass == UISUBCLASS_PRINTER) && + ((uaa->info.bInterfaceProtocol == UIPROTO_PRINTER_UNI) || + (uaa->info.bInterfaceProtocol == UIPROTO_PRINTER_BI) || + (uaa->info.bInterfaceProtocol == UIPROTO_PRINTER_1284))) { + return (0); + } + return (ENXIO); +} + +static int +ulpt_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ulpt_softc *sc = device_get_softc(dev); + struct usb2_interface_descriptor *id; + int unit = device_get_unit(dev); + int error; + uint8_t iface_index = uaa->info.bIfaceIndex; + uint8_t alt_index; + + DPRINTFN(11, "sc=%p\n", sc); + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "ulpt lock", NULL, MTX_DEF | MTX_RECURSE); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + /* search through all the descriptors looking for bidir mode */ + + id = usb2_get_interface_descriptor(uaa->iface); + alt_index = 0 - 1; + while (1) { + if (id == NULL) { + break; + } + if ((id->bDescriptorType == UDESC_INTERFACE) && + (id->bLength >= sizeof(*id))) { + if (id->bInterfaceNumber != uaa->info.bIfaceNum) { + break; + } else { + alt_index++; + if ((id->bInterfaceClass == UICLASS_PRINTER) && + (id->bInterfaceSubClass == UISUBCLASS_PRINTER) && + (id->bInterfaceProtocol == UIPROTO_PRINTER_BI)) { + goto found; + } + } + } + id = (void *)usb2_desc_foreach( + usb2_get_config_descriptor(uaa->device), (void *)id); + } + goto detach; + +found: + + DPRINTF("setting alternate " + "config number: %d\n", alt_index); + + if (alt_index) { + + error = usb2_set_alt_interface_index + (uaa->device, iface_index, alt_index); + + if (error) { + DPRINTF("could not set alternate " + "config, error=%s\n", usb2_errstr(error)); + goto detach; + } + } + sc->sc_iface_no = id->bInterfaceNumber; + + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, ulpt_config, ULPT_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + DPRINTF("error=%s\n", usb2_errstr(error)); + goto detach; + } + device_printf(sc->sc_dev, "using bi-directional mode\n"); + +#if 0 +/* + * This code is disabled because for some mysterious reason it causes + * printing not to work. But only sometimes, and mostly with + * UHCI and less often with OHCI. *sigh* + */ + { + struct usb2_config_descriptor *cd = usb2_get_config_descriptor(dev); + struct usb2_device_request req; + int len, alen; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_DEVICE_ID; + USETW(req.wValue, cd->bConfigurationValue); + USETW2(req.wIndex, id->bInterfaceNumber, id->bAlternateSetting); + USETW(req.wLength, sizeof devinfo - 1); + error = usb2_do_request_flags(dev, &req, devinfo, USB_SHORT_XFER_OK, + &alen, USB_DEFAULT_TIMEOUT); + if (error) { + device_printf(sc->sc_dev, "cannot get device id\n"); + } else if (alen <= 2) { + device_printf(sc->sc_dev, "empty device id, no " + "printer connected?\n"); + } else { + /* devinfo now contains an IEEE-1284 device ID */ + len = ((devinfo[0] & 0xff) << 8) | (devinfo[1] & 0xff); + if (len > sizeof devinfo - 3) + len = sizeof devinfo - 3; + devinfo[len] = 0; + printf("%s: device id <", device_get_nameunit(sc->sc_dev)); + ieee1284_print_id(devinfo + 2); + printf(">\n"); + } + } +#endif + + /* set interface permissions */ + usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &ulpt_fifo_methods, &sc->sc_fifo, + unit, 0 - 1, uaa->info.bIfaceIndex); + if (error) { + goto detach; + } + error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &unlpt_fifo_methods, &sc->sc_fifo_noreset, + unit, 0 - 1, uaa->info.bIfaceIndex); + if (error) { + goto detach; + } + /* start reading of status */ + + mtx_lock(&sc->sc_mtx); + + ulpt_watchdog(sc); /* will unlock mutex */ + + return (0); + +detach: + ulpt_detach(dev); + return (ENOMEM); +} + +static int +ulpt_detach(device_t dev) +{ + struct ulpt_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + usb2_fifo_detach(&sc->sc_fifo); + usb2_fifo_detach(&sc->sc_fifo_noreset); + + mtx_lock(&sc->sc_mtx); + usb2_callout_stop(&sc->sc_watchdog); + mtx_unlock(&sc->sc_mtx); + + usb2_transfer_unsetup(sc->sc_xfer, ULPT_N_TRANSFER); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +#if 0 +/* XXX This does not belong here. */ + +/* + * Compare two strings until the second ends. + */ + +static uint8_t +ieee1284_compare(const char *a, const char *b) +{ + while (1) { + + if (*b == 0) { + break; + } + if (*a != *b) { + return 1; + } + b++; + a++; + } + return 0; +} + +/* + * Print select parts of an IEEE 1284 device ID. + */ +void +ieee1284_print_id(char *str) +{ + char *p, *q; + + for (p = str - 1; p; p = strchr(p, ';')) { + p++; /* skip ';' */ + if (ieee1284_compare(p, "MFG:") == 0 || + ieee1284_compare(p, "MANUFACTURER:") == 0 || + ieee1284_compare(p, "MDL:") == 0 || + ieee1284_compare(p, "MODEL:") == 0) { + q = strchr(p, ';'); + if (q) + printf("%.*s", (int)(q - p + 1), p); + } + } +} + +#endif + +static void +ulpt_watchdog(void *arg) +{ + struct ulpt_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + usb2_transfer_start(sc->sc_xfer[2]); + + usb2_callout_reset(&sc->sc_watchdog, + hz, &ulpt_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + return; +} + +static devclass_t ulpt_devclass; + +static device_method_t ulpt_methods[] = { + DEVMETHOD(device_probe, ulpt_probe), + DEVMETHOD(device_attach, ulpt_attach), + DEVMETHOD(device_detach, ulpt_detach), + {0, 0} +}; + +static driver_t ulpt_driver = { + .name = "ulpt", + .methods = ulpt_methods, + .size = sizeof(struct ulpt_softc), +}; + +DRIVER_MODULE(ulpt, ushub, ulpt_driver, ulpt_devclass, NULL, 0); +MODULE_DEPEND(ulpt, usb2_core, 1, 1, 1); +MODULE_DEPEND(ulpt, usb2_serial, 1, 1, 1); diff --git a/sys/dev/usb2/serial/umct2.c b/sys/dev/usb2/serial/umct2.c new file mode 100644 index 000000000000..3d9c3164eb82 --- /dev/null +++ b/sys/dev/usb2/serial/umct2.c @@ -0,0 +1,689 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2003 Scott Long + * 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. + * + */ + +/* + * Driver for the MCT (Magic Control Technology) USB-RS232 Converter. + * Based on the superb documentation from the linux mct_u232 driver by + * Wolfgang Grandeggar . + * This device smells a lot like the Belkin F5U103, except that it has + * suffered some mild brain-damage. This driver is based off of the ubsa.c + * driver from Alexander Kabaev . Merging the two together + * might be useful, though the subtle differences might lead to lots of + * #ifdef's. + */ + +/* + * NOTE: all function names beginning like "umct_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* The UMCT advertises the standard 8250 UART registers */ +#define UMCT_GET_MSR 2 /* Get Modem Status Register */ +#define UMCT_GET_MSR_SIZE 1 +#define UMCT_GET_LCR 6 /* Get Line Control Register */ +#define UMCT_GET_LCR_SIZE 1 +#define UMCT_SET_BAUD 5 /* Set the Baud Rate Divisor */ +#define UMCT_SET_BAUD_SIZE 4 +#define UMCT_SET_LCR 7 /* Set Line Control Register */ +#define UMCT_SET_LCR_SIZE 1 +#define UMCT_SET_MCR 10 /* Set Modem Control Register */ +#define UMCT_SET_MCR_SIZE 1 + +#define UMCT_INTR_INTERVAL 100 +#define UMCT_IFACE_INDEX 0 +#define UMCT_CONFIG_INDEX 1 + +#define UMCT_ENDPT_MAX 6 /* units */ + +struct umct_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[UMCT_ENDPT_MAX]; + + uint32_t sc_unit; + + uint16_t sc_obufsize; + + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_lcr; + uint8_t sc_mcr; + + uint8_t sc_name[16]; + uint8_t sc_flags; +#define UMCT_FLAG_READ_STALL 0x01 +#define UMCT_FLAG_WRITE_STALL 0x02 +#define UMCT_FLAG_INTR_STALL 0x04 + uint8_t sc_iface_no; +}; + +/* prototypes */ + +static device_probe_t umct_probe; +static device_attach_t umct_attach; +static device_detach_t umct_detach; + +static usb2_callback_t umct_intr_clear_stall_callback; +static usb2_callback_t umct_intr_callback; +static usb2_callback_t umct_write_callback; +static usb2_callback_t umct_write_clear_stall_callback; +static usb2_callback_t umct_read_callback; +static usb2_callback_t umct_read_clear_stall_callback; + +static void umct_cfg_do_request(struct umct_softc *sc, uint8_t request, uint16_t len, uint32_t value); +static void umct_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static void umct_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static void umct_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void umct_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static uint8_t umct_calc_baud(uint32_t baud); +static int umct_pre_param(struct usb2_com_softc *ucom, struct termios *ti); +static void umct_cfg_param(struct usb2_com_softc *ucom, struct termios *ti); +static void umct_start_read(struct usb2_com_softc *ucom); +static void umct_stop_read(struct usb2_com_softc *ucom); +static void umct_start_write(struct usb2_com_softc *ucom); +static void umct_stop_write(struct usb2_com_softc *ucom); + +static const struct usb2_config umct_config[UMCT_ENDPT_MAX] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &umct_write_callback, + }, + + [1] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &umct_read_callback, + .ep_index = 0, /* first interrupt endpoint */ + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umct_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umct_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &umct_intr_callback, + .ep_index = 1, /* second interrupt endpoint */ + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umct_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback umct_callback = { + .usb2_com_cfg_get_status = &umct_cfg_get_status, + .usb2_com_cfg_set_dtr = &umct_cfg_set_dtr, + .usb2_com_cfg_set_rts = &umct_cfg_set_rts, + .usb2_com_cfg_set_break = &umct_cfg_set_break, + .usb2_com_cfg_param = &umct_cfg_param, + .usb2_com_pre_param = &umct_pre_param, + .usb2_com_start_read = &umct_start_read, + .usb2_com_stop_read = &umct_stop_read, + .usb2_com_start_write = &umct_start_write, + .usb2_com_stop_write = &umct_stop_write, +}; + +static const struct usb2_device_id umct_devs[] = { + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_USB232, 0)}, + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_SITECOM_USB232, 0)}, + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_DU_H3SP_USB232, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U109, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U409, 0)}, +}; + +static device_method_t umct_methods[] = { + DEVMETHOD(device_probe, umct_probe), + DEVMETHOD(device_attach, umct_attach), + DEVMETHOD(device_detach, umct_detach), + {0, 0} +}; + +static devclass_t umct_devclass; + +static driver_t umct_driver = { + .name = "umct", + .methods = umct_methods, + .size = sizeof(struct umct_softc), +}; + +DRIVER_MODULE(umct, ushub, umct_driver, umct_devclass, NULL, 0); +MODULE_DEPEND(umct, usb2_serial, 1, 1, 1); +MODULE_DEPEND(umct, usb2_core, 1, 1, 1); +MODULE_DEPEND(umct, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static int +umct_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UMCT_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UMCT_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(umct_devs, sizeof(umct_devs), uaa)); +} + +static int +umct_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct umct_softc *sc = device_get_softc(dev); + int32_t error; + uint16_t maxp; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_unit = device_get_unit(dev); + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + sc->sc_iface_no = uaa->info.bIfaceNum; + + iface_index = UMCT_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, umct_config, UMCT_ENDPT_MAX, sc, &Giant); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + /* + * The real bulk-in endpoint is also marked as an interrupt. + * The only way to differentiate it from the real interrupt + * endpoint is to look at the wMaxPacketSize field. + */ + maxp = UGETW(sc->sc_xfer[1]->pipe->edesc->wMaxPacketSize); + if (maxp == 0x2) { + + /* guessed wrong - switch around endpoints */ + + struct usb2_xfer *temp = sc->sc_xfer[4]; + + sc->sc_xfer[4] = sc->sc_xfer[1]; + sc->sc_xfer[1] = temp; + + sc->sc_xfer[1]->callback = &umct_read_callback; + sc->sc_xfer[4]->callback = &umct_intr_callback; + } + sc->sc_obufsize = sc->sc_xfer[0]->max_data_length; + + if (uaa->info.idProduct == USB_PRODUCT_MCT_SITECOM_USB232) { + if (sc->sc_obufsize > 16) { + sc->sc_obufsize = 16; + } + } + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umct_callback, &Giant); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + umct_detach(dev); + return (ENXIO); /* failure */ +} + +static int +umct_detach(device_t dev) +{ + struct umct_softc *sc = device_get_softc(dev); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UMCT_ENDPT_MAX); + + return (0); +} + +static void +umct_cfg_do_request(struct umct_softc *sc, uint8_t request, + uint16_t len, uint32_t value) +{ + struct usb2_device_request req; + usb2_error_t err; + uint8_t temp[4]; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + goto done; + } + if (len > 4) { + len = 4; + } + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = request; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, len); + USETDW(temp, value); + + err = usb2_do_request_flags(sc->sc_udev, &Giant, &req, + temp, 0, NULL, 1000); + + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + } +done: + return; +} + +static void +umct_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umct_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UMCT_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +umct_intr_callback(struct usb2_xfer *xfer) +{ + struct umct_softc *sc = xfer->priv_sc; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen < 2) { + DPRINTF("too short message\n"); + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, sizeof(buf)); + + sc->sc_msr = buf[0]; + sc->sc_lsr = buf[1]; + + usb2_com_status_change(&sc->sc_ucom); + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flags & UMCT_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* start clear stall */ + sc->sc_flags |= UMCT_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + return; + + } +} + +static void +umct_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct umct_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; + return; +} + +static void +umct_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_lcr |= 0x40; + else + sc->sc_lcr &= ~0x40; + + umct_cfg_do_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, sc->sc_lcr); + return; +} + +static void +umct_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= 0x01; + else + sc->sc_mcr &= ~0x01; + + umct_cfg_do_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr); + return; +} + +static void +umct_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= 0x02; + else + sc->sc_mcr &= ~0x02; + + umct_cfg_do_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr); + return; +} + +static uint8_t +umct_calc_baud(uint32_t baud) +{ + switch (baud) { + case B300:return (0x1); + case B600: + return (0x2); + case B1200: + return (0x3); + case B2400: + return (0x4); + case B4800: + return (0x6); + case B9600: + return (0x8); + case B19200: + return (0x9); + case B38400: + return (0xa); + case B57600: + return (0xb); + case 115200: + return (0xc); + case B0: + default: + break; + } + return (0x0); +} + +static int +umct_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +umct_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct umct_softc *sc = ucom->sc_parent; + uint32_t value; + + value = umct_calc_baud(t->c_ospeed); + umct_cfg_do_request(sc, UMCT_SET_BAUD, UMCT_SET_BAUD_SIZE, value); + + value = (sc->sc_lcr & 0x40); + + switch (t->c_cflag & CSIZE) { + case CS5: + value |= 0x0; + break; + case CS6: + value |= 0x1; + break; + case CS7: + value |= 0x2; + break; + default: + case CS8: + value |= 0x3; + break; + } + + value |= (t->c_cflag & CSTOPB) ? 0x4 : 0; + if (t->c_cflag & PARENB) { + value |= 0x8; + value |= (t->c_cflag & PARODD) ? 0x0 : 0x10; + } + /* + * XXX There doesn't seem to be a way to tell the device + * to use flow control. + */ + + sc->sc_lcr = value; + umct_cfg_do_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, value); + return; +} + +static void +umct_start_read(struct usb2_com_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usb2_transfer_start(sc->sc_xfer[4]); + + /* start read endpoint */ + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +umct_stop_read(struct usb2_com_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usb2_transfer_stop(sc->sc_xfer[5]); + usb2_transfer_stop(sc->sc_xfer[4]); + + /* stop read endpoint */ + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +umct_start_write(struct usb2_com_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +umct_stop_write(struct usb2_com_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static void +umct_write_callback(struct usb2_xfer *xfer) +{ + struct umct_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flags & UMCT_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + sc->sc_obufsize, &actlen)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UMCT_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} + +static void +umct_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umct_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UMCT_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +umct_read_callback(struct usb2_xfer *xfer) +{ + struct umct_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, + 0, xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flags & UMCT_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flags |= UMCT_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +umct_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umct_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UMCT_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} diff --git a/sys/dev/usb2/serial/umodem2.c b/sys/dev/usb2/serial/umodem2.c new file mode 100644 index 000000000000..01d719d7f3ba --- /dev/null +++ b/sys/dev/usb2/serial/umodem2.c @@ -0,0 +1,924 @@ +/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2003, M. Warner Losh . + * 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. + */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +/* + * TODO: + * - Add error recovery in various places; the big problem is what + * to do in a callback if there is an error. + * - Implement a Call Device for modems without multiplexed commands. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR umodem_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int umodem_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, umodem, CTLFLAG_RW, 0, "USB umodem"); +SYSCTL_INT(_hw_usb2_umodem, OID_AUTO, debug, CTLFLAG_RW, + &umodem_debug, 0, "Debug level"); +#endif + +static const struct usb2_device_id umodem_devs[] = { + /* Generic Modem class match */ + {USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), + USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)}, + /* Kyocera AH-K3001V */ + {USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)}, + {USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)}, +}; + +/* + * As speeds for umodem deivces increase, these numbers will need to + * be increased. They should be good for G3 speeds and below. + * + * TODO: The TTY buffers should be increased! + */ +#define UMODEM_BUF_SIZE 1024 +#define UMODEM_N_DATA_TRANSFER 4 +#define UMODEM_N_INTR_TRANSFER 2 + +#define UMODEM_MODVER 1 /* module version */ + +struct umodem_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer_data[UMODEM_N_DATA_TRANSFER]; + struct usb2_xfer *sc_xfer_intr[UMODEM_N_INTR_TRANSFER]; + struct usb2_device *sc_udev; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* modem status register */ + uint8_t sc_ctrl_iface_no; + uint8_t sc_ctrl_iface_index; + uint8_t sc_data_iface_no; + uint8_t sc_data_iface_index; + uint8_t sc_cm_over_data; + uint8_t sc_cm_cap; /* CM capabilities */ + uint8_t sc_acm_cap; /* ACM capabilities */ + uint8_t sc_flag; +#define UMODEM_FLAG_READ_STALL 0x01 +#define UMODEM_FLAG_WRITE_STALL 0x02 +#define UMODEM_FLAG_INTR_STALL 0x04 +}; + +static device_probe_t umodem_probe; +static device_attach_t umodem_attach; +static device_detach_t umodem_detach; + +static usb2_callback_t umodem_intr_callback; +static usb2_callback_t umodem_intr_clear_stall_callback; +static usb2_callback_t umodem_write_callback; +static usb2_callback_t umodem_read_callback; +static usb2_callback_t umodem_write_clear_stall_callback; +static usb2_callback_t umodem_read_clear_stall_callback; + +static void umodem_start_read(struct usb2_com_softc *ucom); +static void umodem_stop_read(struct usb2_com_softc *ucom); +static void umodem_start_write(struct usb2_com_softc *ucom); +static void umodem_stop_write(struct usb2_com_softc *ucom); +static void umodem_get_caps(struct usb2_attach_arg *uaa, uint8_t *cm, uint8_t *acm); +static void umodem_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static int umodem_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void umodem_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static int umodem_ioctl(struct usb2_com_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td); +static void umodem_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void umodem_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static void umodem_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static void *umodem_get_desc(struct usb2_attach_arg *uaa, uint8_t type, uint8_t subtype); +static usb2_error_t umodem_set_comm_feature(struct usb2_device *udev, uint8_t iface_no, uint16_t feature, uint16_t state); +static void umodem_cfg_do_request(struct umodem_softc *sc, struct usb2_device_request *req, void *data); + +static const struct usb2_config umodem_config_data[UMODEM_N_DATA_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UMODEM_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &umodem_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UMODEM_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &umodem_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &umodem_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &umodem_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_config umodem_config_intr[UMODEM_N_INTR_TRANSFER] = { + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &umodem_intr_callback, + }, + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &umodem_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback umodem_callback = { + .usb2_com_cfg_get_status = &umodem_cfg_get_status, + .usb2_com_cfg_set_dtr = &umodem_cfg_set_dtr, + .usb2_com_cfg_set_rts = &umodem_cfg_set_rts, + .usb2_com_cfg_set_break = &umodem_cfg_set_break, + .usb2_com_cfg_param = &umodem_cfg_param, + .usb2_com_pre_param = &umodem_pre_param, + .usb2_com_ioctl = &umodem_ioctl, + .usb2_com_start_read = &umodem_start_read, + .usb2_com_stop_read = &umodem_stop_read, + .usb2_com_start_write = &umodem_start_write, + .usb2_com_stop_write = &umodem_stop_write, +}; + +static device_method_t umodem_methods[] = { + DEVMETHOD(device_probe, umodem_probe), + DEVMETHOD(device_attach, umodem_attach), + DEVMETHOD(device_detach, umodem_detach), + {0, 0} +}; + +static devclass_t umodem_devclass; + +static driver_t umodem_driver = { + .name = "umodem", + .methods = umodem_methods, + .size = sizeof(struct umodem_softc), +}; + +DRIVER_MODULE(umodem, ushub, umodem_driver, umodem_devclass, NULL, 0); +MODULE_DEPEND(umodem, usb2_serial, 1, 1, 1); +MODULE_DEPEND(umodem, usb2_core, 1, 1, 1); +MODULE_DEPEND(umodem, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(umodem, UMODEM_MODVER); + +static int +umodem_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + uint8_t cm; + uint8_t acm; + int error; + + DPRINTFN(11, "\n"); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + error = usb2_lookup_id_by_uaa(umodem_devs, sizeof(umodem_devs), uaa); + if (error) { + return (error); + } + if (uaa->driver_info == NULL) { + /* some modems do not have any capabilities */ + return (error); + } + umodem_get_caps(uaa, &cm, &acm); + if (!(cm & USB_CDC_CM_DOES_CM) || + !(cm & USB_CDC_CM_OVER_DATA) || + !(acm & USB_CDC_ACM_HAS_LINE)) { + error = ENXIO; + } + return (error); +} + +static int +umodem_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct umodem_softc *sc = device_get_softc(dev); + struct usb2_cdc_cm_descriptor *cmd; + uint8_t i; + int error; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; + sc->sc_ctrl_iface_index = uaa->info.bIfaceIndex; + sc->sc_udev = uaa->device; + + umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap); + + /* get the data interface number */ + + cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + + if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { + device_printf(dev, "no CM descriptor!\n"); + goto detach; + } + sc->sc_data_iface_no = cmd->bDataInterface; + + device_printf(dev, "data interface %d, has %sCM over " + "data, has %sbreak\n", + sc->sc_data_iface_no, + sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", + sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); + + /* get the data interface too */ + + for (i = 0;; i++) { + struct usb2_interface *iface; + struct usb2_interface_descriptor *id; + + iface = usb2_get_iface(uaa->device, i); + + if (iface) { + + id = usb2_get_interface_descriptor(iface); + + if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { + sc->sc_data_iface_index = i; + usb2_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + break; + } + } else { + device_printf(dev, "no data interface!\n"); + goto detach; + } + } + + if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) { + if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) { + + error = umodem_set_comm_feature + (uaa->device, sc->sc_ctrl_iface_no, + UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED); + + /* ignore any errors */ + } + sc->sc_cm_over_data = 1; + } + error = usb2_transfer_setup(uaa->device, + &sc->sc_data_iface_index, sc->sc_xfer_data, + umodem_config_data, UMODEM_N_DATA_TRANSFER, + sc, &Giant); + if (error) { + goto detach; + } + error = usb2_transfer_setup(uaa->device, + &sc->sc_ctrl_iface_index, sc->sc_xfer_intr, + umodem_config_intr, UMODEM_N_INTR_TRANSFER, + sc, &Giant); + + if (error == 0) { + device_printf(dev, "status change " + "notification available\n"); + } + /* clear stall at first run */ + sc->sc_flag |= (UMODEM_FLAG_READ_STALL | + UMODEM_FLAG_WRITE_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umodem_callback, &Giant); + if (error) { + goto detach; + } + return (0); + +detach: + umodem_detach(dev); + return (ENXIO); +} + +static void +umodem_start_read(struct usb2_com_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + if (sc->sc_xfer_intr[0]) { + /* start interrupt endpoint */ + usb2_transfer_start(sc->sc_xfer_intr[0]); + } + /* start read endpoint */ + usb2_transfer_start(sc->sc_xfer_data[1]); + return; +} + +static void +umodem_stop_read(struct usb2_com_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + if (sc->sc_xfer_intr[0]) { + /* stop interrupt endpoint */ + usb2_transfer_stop(sc->sc_xfer_intr[0]); + } + /* stop read endpoint */ + usb2_transfer_stop(sc->sc_xfer_data[3]); + usb2_transfer_stop(sc->sc_xfer_data[1]); + return; +} + +static void +umodem_start_write(struct usb2_com_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer_data[0]); + return; +} + +static void +umodem_stop_write(struct usb2_com_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer_data[2]); + usb2_transfer_stop(sc->sc_xfer_data[0]); + return; +} + +static void +umodem_get_caps(struct usb2_attach_arg *uaa, uint8_t *cm, uint8_t *acm) +{ + struct usb2_cdc_cm_descriptor *cmd; + struct usb2_cdc_acm_descriptor *cad; + + *cm = *acm = 0; + + cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { + DPRINTF("no CM desc\n"); + return; + } + *cm = cmd->bmCapabilities; + + cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); + if ((cad == NULL) || (cad->bLength < sizeof(*cad))) { + DPRINTF("no ACM desc\n"); + return; + } + *acm = cad->bmCapabilities; + + return; +} + +static void +umodem_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct umodem_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; + return; +} + +static int +umodem_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +umodem_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb2_cdc_line_state ls; + struct usb2_device_request req; + + DPRINTF("sc=%p\n", sc); + + bzero(&ls, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + ls.bCharFormat = (t->c_cflag & CSTOPB) ? + UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1; + + ls.bParityType = (t->c_cflag & PARENB) ? + ((t->c_cflag & PARODD) ? + UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(ls.dwDTERate), ls.bCharFormat, + ls.bParityType, ls.bDataBits); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(ls)); + + umodem_cfg_do_request(sc, &req, &ls); + return; +} + +static int +umodem_ioctl(struct usb2_com_softc *ucom, uint32_t cmd, caddr_t data, + int flag, struct thread *td) +{ + struct umodem_softc *sc = ucom->sc_parent; + int error = 0; + + DPRINTF("cmd=0x%08x\n", cmd); + + switch (cmd) { + case USB_GET_CM_OVER_DATA: + *(int *)data = sc->sc_cm_over_data; + break; + + case USB_SET_CM_OVER_DATA: + if (*(int *)data != sc->sc_cm_over_data) { + /* XXX change it */ + } + break; + + default: + DPRINTF("unknown\n"); + error = ENOIOCTL; + break; + } + + return (error); +} + +static void +umodem_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + umodem_cfg_do_request(sc, &req, NULL); + return; +} + +static void +umodem_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + umodem_cfg_do_request(sc, &req, NULL); + return; +} + +static void +umodem_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + uint16_t temp; + + DPRINTF("onoff=%d\n", onoff); + + if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) { + + temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + umodem_cfg_do_request(sc, &req, NULL); + } + return; +} + +static void +umodem_intr_callback(struct usb2_xfer *xfer) +{ + struct usb2_cdc_notification pkt; + struct umodem_softc *sc = xfer->priv_sc; + uint16_t wLen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen < 8) { + DPRINTF("received short packet, " + "%d bytes\n", xfer->actlen); + goto tr_setup; + } + if (xfer->actlen > sizeof(pkt)) { + DPRINTF("truncating message\n"); + xfer->actlen = sizeof(pkt); + } + usb2_copy_out(xfer->frbuffers, 0, &pkt, xfer->actlen); + + xfer->actlen -= 8; + + wLen = UGETW(pkt.wLength); + if (xfer->actlen > wLen) { + xfer->actlen = wLen; + } + if (pkt.bmRequestType != UCDC_NOTIFICATION) { + DPRINTF("unknown message type, " + "0x%02x, on notify pipe!\n", + pkt.bmRequestType); + goto tr_setup; + } + switch (pkt.bNotification) { + case UCDC_N_SERIAL_STATE: + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + if (xfer->actlen < 2) { + DPRINTF("invalid notification " + "length, %d bytes!\n", xfer->actlen); + break; + } + DPRINTF("notify bytes = %02x%02x\n", + pkt.data[0], + pkt.data[1]); + + /* Currently, lsr is always zero. */ + sc->sc_lsr = 0; + sc->sc_msr = 0; + + if (pkt.data[0] & UCDC_N_SERIAL_RI) { + sc->sc_msr |= SER_RI; + } + if (pkt.data[0] & UCDC_N_SERIAL_DSR) { + sc->sc_msr |= SER_DSR; + } + if (pkt.data[0] & UCDC_N_SERIAL_DCD) { + sc->sc_msr |= SER_DCD; + } + usb2_com_status_change(&sc->sc_ucom); + break; + + default: + DPRINTF("unknown notify message: 0x%02x\n", + pkt.bNotification); + break; + } + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flag & UMODEM_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer_intr[1]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UMODEM_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer_intr[1]); + } + return; + + } +} + +static void +umodem_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umodem_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_intr[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UMODEM_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +umodem_write_callback(struct usb2_xfer *xfer) +{ + struct umodem_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flag & UMODEM_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer_data[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UMODEM_BUF_SIZE, &actlen)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UMODEM_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer_data[2]); + } + return; + + } +} + +static void +umodem_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umodem_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_data[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UMODEM_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +umodem_read_callback(struct usb2_xfer *xfer) +{ + struct umodem_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen=%d\n", xfer->actlen); + + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, + xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flag & UMODEM_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer_data[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UMODEM_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer_data[3]); + } + return; + + } +} + +static void +umodem_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umodem_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_data[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UMODEM_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void * +umodem_get_desc(struct usb2_attach_arg *uaa, uint8_t type, uint8_t subtype) +{ + return (usb2_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, + type, 0 - 1, subtype, 0 - 1)); +} + +static usb2_error_t +umodem_set_comm_feature(struct usb2_device *udev, uint8_t iface_no, + uint16_t feature, uint16_t state) +{ + struct usb2_device_request req; + struct usb2_cdc_abstract_state ast; + + DPRINTF("feature=%d state=%d\n", + feature, state); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_COMM_FEATURE; + USETW(req.wValue, feature); + req.wIndex[0] = iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH); + USETW(ast.wState, state); + + return (usb2_do_request(udev, &Giant, &req, &ast)); +} + +static int +umodem_detach(device_t dev) +{ + struct umodem_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer_intr, UMODEM_N_INTR_TRANSFER); + + usb2_transfer_unsetup(sc->sc_xfer_data, UMODEM_N_DATA_TRANSFER); + + return (0); +} + +static void +umodem_cfg_do_request(struct umodem_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + goto error; + } + err = usb2_do_request_flags(sc->sc_udev, &Giant, req, + data, 0, NULL, 1000); + + if (err) { + + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} diff --git a/sys/dev/usb2/serial/umoscom2.c b/sys/dev/usb2/serial/umoscom2.c new file mode 100644 index 000000000000..3c142a5b1688 --- /dev/null +++ b/sys/dev/usb2/serial/umoscom2.c @@ -0,0 +1,799 @@ +/* $FreeBSD$ */ +/* $OpenBSD: umoscom.c,v 1.2 2006/10/26 06:02:43 jsg Exp $ */ + +/* + * Copyright (c) 2006 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR umoscom_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int umoscom_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, umoscom, CTLFLAG_RW, 0, "USB umoscom"); +SYSCTL_INT(_hw_usb2_umoscom, OID_AUTO, debug, CTLFLAG_RW, + &umoscom_debug, 0, "Debug level"); +#endif + +#define UMOSCOM_BUFSIZE 1024 /* bytes */ +#define UMOSCOM_N_DATA_TRANSFER 6 /* units */ + +#define UMOSCOM_CONFIG_INDEX 0 +#define UMOSCOM_IFACE_INDEX 0 + +/* interrupt packet */ +#define UMOSCOM_IIR_RLS 0x06 +#define UMOSCOM_IIR_RDA 0x04 +#define UMOSCOM_IIR_CTI 0x0c +#define UMOSCOM_IIR_THR 0x02 +#define UMOSCOM_IIR_MS 0x00 + +/* registers */ +#define UMOSCOM_READ 0x0d +#define UMOSCOM_WRITE 0x0e +#define UMOSCOM_UART_REG 0x0300 +#define UMOSCOM_VEND_REG 0x0000 + +#define UMOSCOM_TXBUF 0x00 /* Write */ +#define UMOSCOM_RXBUF 0x00 /* Read */ +#define UMOSCOM_INT 0x01 +#define UMOSCOM_FIFO 0x02 /* Write */ +#define UMOSCOM_ISR 0x02 /* Read */ +#define UMOSCOM_LCR 0x03 +#define UMOSCOM_MCR 0x04 +#define UMOSCOM_LSR 0x05 +#define UMOSCOM_MSR 0x06 +#define UMOSCOM_SCRATCH 0x07 +#define UMOSCOM_DIV_LO 0x08 +#define UMOSCOM_DIV_HI 0x09 +#define UMOSCOM_EFR 0x0a +#define UMOSCOM_XON1 0x0b +#define UMOSCOM_XON2 0x0c +#define UMOSCOM_XOFF1 0x0d +#define UMOSCOM_XOFF2 0x0e + +#define UMOSCOM_BAUDLO 0x00 +#define UMOSCOM_BAUDHI 0x01 + +#define UMOSCOM_INT_RXEN 0x01 +#define UMOSCOM_INT_TXEN 0x02 +#define UMOSCOM_INT_RSEN 0x04 +#define UMOSCOM_INT_MDMEM 0x08 +#define UMOSCOM_INT_SLEEP 0x10 +#define UMOSCOM_INT_XOFF 0x20 +#define UMOSCOM_INT_RTS 0x40 + +#define UMOSCOM_FIFO_EN 0x01 +#define UMOSCOM_FIFO_RXCLR 0x02 +#define UMOSCOM_FIFO_TXCLR 0x04 +#define UMOSCOM_FIFO_DMA_BLK 0x08 +#define UMOSCOM_FIFO_TXLVL_MASK 0x30 +#define UMOSCOM_FIFO_TXLVL_8 0x00 +#define UMOSCOM_FIFO_TXLVL_16 0x10 +#define UMOSCOM_FIFO_TXLVL_32 0x20 +#define UMOSCOM_FIFO_TXLVL_56 0x30 +#define UMOSCOM_FIFO_RXLVL_MASK 0xc0 +#define UMOSCOM_FIFO_RXLVL_8 0x00 +#define UMOSCOM_FIFO_RXLVL_16 0x40 +#define UMOSCOM_FIFO_RXLVL_56 0x80 +#define UMOSCOM_FIFO_RXLVL_80 0xc0 + +#define UMOSCOM_ISR_MDM 0x00 +#define UMOSCOM_ISR_NONE 0x01 +#define UMOSCOM_ISR_TX 0x02 +#define UMOSCOM_ISR_RX 0x04 +#define UMOSCOM_ISR_LINE 0x06 +#define UMOSCOM_ISR_RXTIMEOUT 0x0c +#define UMOSCOM_ISR_RX_XOFF 0x10 +#define UMOSCOM_ISR_RTSCTS 0x20 +#define UMOSCOM_ISR_FIFOEN 0xc0 + +#define UMOSCOM_LCR_DBITS(x) ((x) - 5) +#define UMOSCOM_LCR_STOP_BITS_1 0x00 +#define UMOSCOM_LCR_STOP_BITS_2 0x04 /* 2 if 6-8 bits/char or 1.5 if 5 */ +#define UMOSCOM_LCR_PARITY_NONE 0x00 +#define UMOSCOM_LCR_PARITY_ODD 0x08 +#define UMOSCOM_LCR_PARITY_EVEN 0x18 +#define UMOSCOM_LCR_BREAK 0x40 +#define UMOSCOM_LCR_DIVLATCH_EN 0x80 + +#define UMOSCOM_MCR_DTR 0x01 +#define UMOSCOM_MCR_RTS 0x02 +#define UMOSCOM_MCR_LOOP 0x04 +#define UMOSCOM_MCR_INTEN 0x08 +#define UMOSCOM_MCR_LOOPBACK 0x10 +#define UMOSCOM_MCR_XONANY 0x20 +#define UMOSCOM_MCR_IRDA_EN 0x40 +#define UMOSCOM_MCR_BAUD_DIV4 0x80 + +#define UMOSCOM_LSR_RXDATA 0x01 +#define UMOSCOM_LSR_RXOVER 0x02 +#define UMOSCOM_LSR_RXPAR_ERR 0x04 +#define UMOSCOM_LSR_RXFRM_ERR 0x08 +#define UMOSCOM_LSR_RXBREAK 0x10 +#define UMOSCOM_LSR_TXEMPTY 0x20 +#define UMOSCOM_LSR_TXALLEMPTY 0x40 +#define UMOSCOM_LSR_TXFIFO_ERR 0x80 + +#define UMOSCOM_MSR_CTS_CHG 0x01 +#define UMOSCOM_MSR_DSR_CHG 0x02 +#define UMOSCOM_MSR_RI_CHG 0x04 +#define UMOSCOM_MSR_CD_CHG 0x08 +#define UMOSCOM_MSR_CTS 0x10 +#define UMOSCOM_MSR_RTS 0x20 +#define UMOSCOM_MSR_RI 0x40 +#define UMOSCOM_MSR_CD 0x80 + +#define UMOSCOM_BAUD_REF 115200 + +struct umoscom_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer_data[UMOSCOM_N_DATA_TRANSFER]; + struct usb2_device *sc_udev; + + uint8_t sc_mcr; + uint8_t sc_lcr; + uint8_t sc_flags; +#define UMOSCOM_FLAG_READ_STALL 0x01 +#define UMOSCOM_FLAG_WRITE_STALL 0x02 +#define UMOSCOM_FLAG_INTR_STALL 0x04 +}; + +/* prototypes */ + +static device_probe_t umoscom_probe; +static device_attach_t umoscom_attach; +static device_detach_t umoscom_detach; + +static usb2_callback_t umoscom_write_callback; +static usb2_callback_t umoscom_write_clear_stall_callback; +static usb2_callback_t umoscom_read_callback; +static usb2_callback_t umoscom_read_clear_stall_callback; +static usb2_callback_t umoscom_intr_callback; +static usb2_callback_t umoscom_intr_clear_stall_callback; + +static void umoscom_cfg_open(struct usb2_com_softc *ucom); +static void umoscom_cfg_close(struct usb2_com_softc *ucom); +static void umoscom_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static void umoscom_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void umoscom_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static int umoscom_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void umoscom_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static void umoscom_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static void umoscom_cfg_write(struct umoscom_softc *sc, uint16_t reg, uint16_t val); +static uint8_t umoscom_cfg_read(struct umoscom_softc *sc, uint16_t reg); +static void umoscom_cfg_do_request(struct umoscom_softc *sc, struct usb2_device_request *req, void *data); + +static void umoscom_start_read(struct usb2_com_softc *ucom); +static void umoscom_stop_read(struct usb2_com_softc *ucom); +static void umoscom_start_write(struct usb2_com_softc *ucom); +static void umoscom_stop_write(struct usb2_com_softc *ucom); + +static const struct usb2_config umoscom_config_data[UMOSCOM_N_DATA_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UMOSCOM_BUFSIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &umoscom_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UMOSCOM_BUFSIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &umoscom_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &umoscom_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &umoscom_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &umoscom_intr_callback, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &umoscom_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback umoscom_callback = { + /* configuration callbacks */ + .usb2_com_cfg_get_status = &umoscom_cfg_get_status, + .usb2_com_cfg_set_dtr = &umoscom_cfg_set_dtr, + .usb2_com_cfg_set_rts = &umoscom_cfg_set_rts, + .usb2_com_cfg_set_break = &umoscom_cfg_set_break, + .usb2_com_cfg_param = &umoscom_cfg_param, + .usb2_com_cfg_open = &umoscom_cfg_open, + .usb2_com_cfg_close = &umoscom_cfg_close, + + /* other callbacks */ + .usb2_com_pre_param = &umoscom_pre_param, + .usb2_com_start_read = &umoscom_start_read, + .usb2_com_stop_read = &umoscom_stop_read, + .usb2_com_start_write = &umoscom_start_write, + .usb2_com_stop_write = &umoscom_stop_write, +}; + +static device_method_t umoscom_methods[] = { + DEVMETHOD(device_probe, umoscom_probe), + DEVMETHOD(device_attach, umoscom_attach), + DEVMETHOD(device_detach, umoscom_detach), + {0, 0} +}; + +static devclass_t umoscom_devclass; + +static driver_t umoscom_driver = { + .name = "umoscom", + .methods = umoscom_methods, + .size = sizeof(struct umoscom_softc), +}; + +DRIVER_MODULE(umoscom, ushub, umoscom_driver, umoscom_devclass, NULL, 0); +MODULE_DEPEND(umoscom, usb2_serial, 1, 1, 1); +MODULE_DEPEND(umoscom, usb2_core, 1, 1, 1); +MODULE_DEPEND(umoscom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static const struct usb2_device_id umoscom_devs[] = { + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7703, 0)} +}; + +static int +umoscom_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UMOSCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UMOSCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(umoscom_devs, sizeof(umoscom_devs), uaa)); +} + +static int +umoscom_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct umoscom_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_udev = uaa->device; + sc->sc_mcr = 0x08; /* enable interrupts */ + + /* XXX the device doesn't provide any ID string, so set a static one */ + device_set_desc(dev, "MOSCHIP USB Serial Port Adapter"); + device_printf(dev, "\n"); + + iface_index = UMOSCOM_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer_data, umoscom_config_data, + UMOSCOM_N_DATA_TRANSFER, sc, &Giant); + + if (error) { + goto detach; + } + /* clear stall at first run */ + sc->sc_flags |= (UMOSCOM_FLAG_READ_STALL | + UMOSCOM_FLAG_WRITE_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umoscom_callback, &Giant); + if (error) { + goto detach; + } + return (0); + +detach: + device_printf(dev, "attach error: %s\n", usb2_errstr(error)); + umoscom_detach(dev); + return (ENXIO); +} + +static int +umoscom_detach(device_t dev) +{ + struct umoscom_softc *sc = device_get_softc(dev); + + mtx_lock(&Giant); + + mtx_unlock(&Giant); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer_data, UMOSCOM_N_DATA_TRANSFER); + + return (0); +} + +static void +umoscom_cfg_open(struct usb2_com_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + /* Purge FIFOs or odd things happen */ + umoscom_cfg_write(sc, UMOSCOM_FIFO, 0x00 | UMOSCOM_UART_REG); + + /* Enable FIFO */ + umoscom_cfg_write(sc, UMOSCOM_FIFO, UMOSCOM_FIFO_EN | + UMOSCOM_FIFO_RXCLR | UMOSCOM_FIFO_TXCLR | + UMOSCOM_FIFO_DMA_BLK | UMOSCOM_FIFO_RXLVL_MASK | + UMOSCOM_UART_REG); + + /* Enable Interrupt Registers */ + umoscom_cfg_write(sc, UMOSCOM_INT, 0x0C | UMOSCOM_UART_REG); + + /* Magic */ + umoscom_cfg_write(sc, 0x01, 0x08); + + /* Magic */ + umoscom_cfg_write(sc, 0x00, 0x02); + + return; +} + +static void +umoscom_cfg_close(struct usb2_com_softc *ucom) +{ + return; +} + +static void +umoscom_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint16_t val; + + val = sc->sc_lcr; + if (onoff) + val |= UMOSCOM_LCR_BREAK; + + umoscom_cfg_write(sc, UMOSCOM_LCR, val | UMOSCOM_UART_REG); + return; +} + +static void +umoscom_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= UMOSCOM_MCR_DTR; + else + sc->sc_mcr &= ~UMOSCOM_MCR_DTR; + + umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG); + return; +} + +static void +umoscom_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= UMOSCOM_MCR_RTS; + else + sc->sc_mcr &= ~UMOSCOM_MCR_RTS; + + umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG); + return; +} + +static int +umoscom_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + if ((t->c_ospeed <= 1) || (t->c_ospeed > 115200)) + return (EINVAL); + + return (0); +} + +static void +umoscom_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint16_t data; + + DPRINTF("speed=%d\n", t->c_ospeed); + + data = ((uint32_t)UMOSCOM_BAUD_REF) / ((uint32_t)t->c_ospeed); + + if (data == 0) { + DPRINTF("invalid baud rate!\n"); + return; + } + umoscom_cfg_write(sc, UMOSCOM_LCR, + UMOSCOM_LCR_DIVLATCH_EN | UMOSCOM_UART_REG); + + umoscom_cfg_write(sc, UMOSCOM_BAUDLO, + (data & 0xFF) | UMOSCOM_UART_REG); + + umoscom_cfg_write(sc, UMOSCOM_BAUDHI, + ((data >> 8) & 0xFF) | UMOSCOM_UART_REG); + + if (t->c_cflag & CSTOPB) + data = UMOSCOM_LCR_STOP_BITS_2; + else + data = UMOSCOM_LCR_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) + data |= UMOSCOM_LCR_PARITY_ODD; + else + data |= UMOSCOM_LCR_PARITY_EVEN; + } else + data |= UMOSCOM_LCR_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + data |= UMOSCOM_LCR_DBITS(5); + break; + case CS6: + data |= UMOSCOM_LCR_DBITS(6); + break; + case CS7: + data |= UMOSCOM_LCR_DBITS(7); + break; + case CS8: + data |= UMOSCOM_LCR_DBITS(8); + break; + } + + sc->sc_lcr = data; + umoscom_cfg_write(sc, UMOSCOM_LCR, data | UMOSCOM_UART_REG); + + return; +} + +static void +umoscom_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *p_lsr, uint8_t *p_msr) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint8_t lsr; + uint8_t msr; + + DPRINTFN(5, "\n"); + + /* read status registers */ + + lsr = umoscom_cfg_read(sc, UMOSCOM_LSR); + msr = umoscom_cfg_read(sc, UMOSCOM_MSR); + + /* translate bits */ + + if (msr & UMOSCOM_MSR_CTS) + *p_msr |= SER_CTS; + + if (msr & UMOSCOM_MSR_CD) + *p_msr |= SER_DCD; + + if (msr & UMOSCOM_MSR_RI) + *p_msr |= SER_RI; + + if (msr & UMOSCOM_MSR_RTS) + *p_msr |= SER_DSR; + + return; +} + +static void +umoscom_cfg_write(struct umoscom_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UMOSCOM_WRITE; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + umoscom_cfg_do_request(sc, &req, NULL); +} + +static uint8_t +umoscom_cfg_read(struct umoscom_softc *sc, uint16_t reg) +{ + struct usb2_device_request req; + uint8_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UMOSCOM_READ; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + umoscom_cfg_do_request(sc, &req, &val); + + DPRINTF("reg=0x%04x, val=0x%02x\n", reg, val); + + return (val); +} + +static void +umoscom_cfg_do_request(struct umoscom_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) + goto error; + + err = usb2_do_request_flags + (sc->sc_udev, &Giant, req, data, 0, NULL, 1000); + + if (err) { + DPRINTFN(0, "control request failed: %s\n", + usb2_errstr(err)); +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +static void +umoscom_start_read(struct usb2_com_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + +#if 0 + /* start interrupt endpoint */ + usb2_transfer_start(sc->sc_xfer_data[4]); +#endif + /* start read endpoint */ + usb2_transfer_start(sc->sc_xfer_data[1]); + return; +} + +static void +umoscom_stop_read(struct usb2_com_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + /* stop interrupt transfer */ + usb2_transfer_stop(sc->sc_xfer_data[5]); + usb2_transfer_stop(sc->sc_xfer_data[4]); + + /* stop read endpoint */ + usb2_transfer_stop(sc->sc_xfer_data[3]); + usb2_transfer_stop(sc->sc_xfer_data[1]); + return; +} + +static void +umoscom_start_write(struct usb2_com_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer_data[0]); + return; +} + +static void +umoscom_stop_write(struct usb2_com_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer_data[2]); + usb2_transfer_stop(sc->sc_xfer_data[0]); + return; +} + +static void +umoscom_write_callback(struct usb2_xfer *xfer) +{ + struct umoscom_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + DPRINTF("\n"); + + if (sc->sc_flags & UMOSCOM_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer_data[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UMOSCOM_BUFSIZE, &actlen)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + sc->sc_flags |= UMOSCOM_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer_data[2]); + } + return; + } +} + +static void +umoscom_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umoscom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_data[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UMOSCOM_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +umoscom_read_callback(struct usb2_xfer *xfer) +{ + struct umoscom_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("got %d bytes\n", xfer->actlen); + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, xfer->actlen); + + case USB_ST_SETUP: + DPRINTF("\n"); + + if (sc->sc_flags & UMOSCOM_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer_data[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + sc->sc_flags |= UMOSCOM_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer_data[3]); + } + return; + + } +} + +static void +umoscom_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umoscom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_data[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UMOSCOM_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +umoscom_intr_callback(struct usb2_xfer *xfer) +{ + struct umoscom_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen < 2) { + DPRINTF("too short message\n"); + goto tr_setup; + } + usb2_com_status_change(&sc->sc_ucom); + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flags & UMOSCOM_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer_data[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + sc->sc_flags |= UMOSCOM_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer_data[5]); + } + return; + } +} + +static void +umoscom_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umoscom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer_data[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UMOSCOM_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} diff --git a/sys/dev/usb2/serial/uplcom2.c b/sys/dev/usb2/serial/uplcom2.c new file mode 100644 index 000000000000..ebd8cb4d8832 --- /dev/null +++ b/sys/dev/usb2/serial/uplcom2.c @@ -0,0 +1,964 @@ +/* $NetBSD: uplcom.c,v 1.21 2001/11/13 06:24:56 lukem Exp $ */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama . + * 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. + */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ichiro@ichiro.org). + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * This driver supports several USB-to-RS232 serial adapters driven by + * Prolific PL-2303, PL-2303X and probably PL-2303HX USB-to-RS232 + * bridge chip. The adapters are sold under many different brand + * names. + * + * Datasheets are available at Prolific www site at + * http://www.prolific.com.tw. The datasheets don't contain full + * programming information for the chip. + * + * PL-2303HX is probably programmed the same as PL-2303X. + * + * There are several differences between PL-2303 and PL-2303(H)X. + * PL-2303(H)X can do higher bitrate in bulk mode, has _probably_ + * different command for controlling CRTSCTS and needs special + * sequence of commands for initialization which aren't also + * documented in the datasheet. + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR uplcom_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int uplcom_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uplcom, CTLFLAG_RW, 0, "USB uplcom"); +SYSCTL_INT(_hw_usb2_uplcom, OID_AUTO, debug, CTLFLAG_RW, + &uplcom_debug, 0, "Debug level"); +#endif + +#define UPLCOM_MODVER 1 /* module version */ + +#define UPLCOM_CONFIG_INDEX 0 +#define UPLCOM_IFACE_INDEX 0 +#define UPLCOM_SECOND_IFACE_INDEX 1 + +#ifndef UPLCOM_INTR_INTERVAL +#define UPLCOM_INTR_INTERVAL 0 /* default */ +#endif + +#define UPLCOM_BULK_BUF_SIZE 1024 /* bytes */ +#define UPLCOM_N_TRANSFER 6 + +#define UPLCOM_SET_REQUEST 0x01 +#define UPLCOM_SET_CRTSCTS 0x41 +#define UPLCOM_SET_CRTSCTS_PL2303X 0x61 +#define RSAQ_STATUS_CTS 0x80 +#define RSAQ_STATUS_DSR 0x02 +#define RSAQ_STATUS_DCD 0x01 + +#define TYPE_PL2303 0 +#define TYPE_PL2303X 1 + +struct uplcom_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer[UPLCOM_N_TRANSFER]; + struct usb2_device *sc_udev; + + uint16_t sc_line; + + uint8_t sc_flag; +#define UPLCOM_FLAG_INTR_STALL 0x01 +#define UPLCOM_FLAG_READ_STALL 0x02 +#define UPLCOM_FLAG_WRITE_STALL 0x04 + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* uplcom status register */ + uint8_t sc_chiptype; /* type of chip */ + uint8_t sc_ctrl_iface_no; + uint8_t sc_data_iface_no; + uint8_t sc_iface_index[2]; +}; + +/* prototypes */ + +static usb2_error_t uplcom_reset(struct uplcom_softc *sc, struct usb2_device *udev); +static int uplcom_pl2303x_init(struct usb2_device *udev); +static void uplcom_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void uplcom_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static void uplcom_cfg_set_break(struct usb2_com_softc *sc, uint8_t onoff); +static int uplcom_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void uplcom_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static void uplcom_start_read(struct usb2_com_softc *ucom); +static void uplcom_stop_read(struct usb2_com_softc *ucom); +static void uplcom_start_write(struct usb2_com_softc *ucom); +static void uplcom_stop_write(struct usb2_com_softc *ucom); +static void uplcom_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static int uplcom_ioctl(struct usb2_com_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td); +static void uplcom_cfg_do_request(struct uplcom_softc *sc, struct usb2_device_request *req, void *data); + +static device_probe_t uplcom_probe; +static device_attach_t uplcom_attach; +static device_detach_t uplcom_detach; + +static usb2_callback_t uplcom_intr_callback; +static usb2_callback_t uplcom_intr_clear_stall_callback; +static usb2_callback_t uplcom_write_callback; +static usb2_callback_t uplcom_write_clear_stall_callback; +static usb2_callback_t uplcom_read_callback; +static usb2_callback_t uplcom_read_clear_stall_callback; + +static const struct usb2_config uplcom_config_data[UPLCOM_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UPLCOM_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &uplcom_write_callback, + .if_index = 0, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UPLCOM_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &uplcom_read_callback, + .if_index = 0, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uplcom_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + .if_index = 0, + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uplcom_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + .if_index = 0, + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &uplcom_intr_callback, + .if_index = 1, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uplcom_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + .if_index = 1, + }, +}; + +struct usb2_com_callback uplcom_callback = { + .usb2_com_cfg_get_status = &uplcom_cfg_get_status, + .usb2_com_cfg_set_dtr = &uplcom_cfg_set_dtr, + .usb2_com_cfg_set_rts = &uplcom_cfg_set_rts, + .usb2_com_cfg_set_break = &uplcom_cfg_set_break, + .usb2_com_cfg_param = &uplcom_cfg_param, + .usb2_com_pre_param = &uplcom_pre_param, + .usb2_com_ioctl = &uplcom_ioctl, + .usb2_com_start_read = &uplcom_start_read, + .usb2_com_stop_read = &uplcom_stop_read, + .usb2_com_start_write = &uplcom_start_write, + .usb2_com_stop_write = &uplcom_stop_write, +}; + +#define USB_UPL(v,p,rl,rh,t) \ + USB_VENDOR(v), USB_PRODUCT(p), USB_DEV_BCD_GTEQ(rl), \ + USB_DEV_BCD_LTEQ(rh), USB_DRIVER_INFO(t) + +static const struct usb2_device_id uplcom_devs[] = { + /* Belkin F5U257 */ + {USB_UPL(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U257, 0, 0xFFFF, TYPE_PL2303X)}, + /* I/O DATA USB-RSAQ */ + {USB_UPL(USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBRSAQ, 0, 0xFFFF, TYPE_PL2303)}, + /* I/O DATA USB-RSAQ2 */ + {USB_UPL(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_RSAQ2, 0, 0xFFFF, TYPE_PL2303)}, + /* I/O DATA USB-RSAQ3 */ + {USB_UPL(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_RSAQ3, 0, 0xFFFF, TYPE_PL2303X)}, + /* PLANEX USB-RS232 URS-03 */ + {USB_UPL(USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC232A, 0, 0xFFFF, TYPE_PL2303)}, + /* TrendNet TU-S9 */ + {USB_UPL(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2303, 0x0400, 0xFFFF, TYPE_PL2303X)}, + /* ST Lab USB-SERIAL-4 */ + {USB_UPL(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2303, 0x0300, 0x03FF, TYPE_PL2303X)}, + /* IOGEAR/ATEN UC-232A (also ST Lab USB-SERIAL-1) */ + {USB_UPL(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2303, 0, 0x02FF, TYPE_PL2303)}, + /* TDK USB-PHS Adapter UHA6400 */ + {USB_UPL(USB_VENDOR_TDK, USB_PRODUCT_TDK_UHA6400, 0, 0xFFFF, TYPE_PL2303)}, + /* RATOC REX-USB60 */ + {USB_UPL(USB_VENDOR_RATOC, USB_PRODUCT_RATOC_REXUSB60, 0, 0xFFFF, TYPE_PL2303)}, + /* ELECOM UC-SGT */ + {USB_UPL(USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_UCSGT, 0, 0xFFFF, TYPE_PL2303)}, + {USB_UPL(USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_UCSGT0, 0, 0xFFFF, TYPE_PL2303)}, + /* Sagem USB-Serial Controller */ + {USB_UPL(USB_VENDOR_SAGEM, USB_PRODUCT_SAGEM_USBSERIAL, 0, 0xFFFF, TYPE_PL2303X)}, + /* Sony Ericsson USB Cable */ + {USB_UPL(USB_VENDOR_SONYERICSSON, USB_PRODUCT_SONYERICSSON_DCU10, 0, 0xFFFF, TYPE_PL2303)}, + /* SOURCENEXT KeikaiDenwa 8 */ + {USB_UPL(USB_VENDOR_SOURCENEXT, USB_PRODUCT_SOURCENEXT_KEIKAI8, 0, 0xFFFF, TYPE_PL2303)}, + /* SOURCENEXT KeikaiDenwa 8 with charger */ + {USB_UPL(USB_VENDOR_SOURCENEXT, USB_PRODUCT_SOURCENEXT_KEIKAI8_CHG, 0, 0, TYPE_PL2303)}, + /* HAL Corporation Crossam2+USB */ + {USB_UPL(USB_VENDOR_HAL, USB_PRODUCT_HAL_IMR001, 0, 0xFFFF, TYPE_PL2303)}, + /* Sitecom USB to Serial */ + {USB_UPL(USB_VENDOR_SITECOM, USB_PRODUCT_SITECOM_SERIAL, 0, 0xFFFF, TYPE_PL2303)}, + /* Tripp-Lite U209-000-R */ + {USB_UPL(USB_VENDOR_TRIPPLITE, USB_PRODUCT_TRIPPLITE_U209, 0, 0xFFFF, TYPE_PL2303X)}, + {USB_UPL(USB_VENDOR_RADIOSHACK, USB_PRODUCT_RADIOSHACK_USBCABLE, 0, 0xFFFF, TYPE_PL2303)}, + /* Prolific Pharos */ + {USB_UPL(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PHAROS, 0, 0xFFFF, TYPE_PL2303)}, + /* Willcom W-SIM */ + {USB_UPL(USB_VENDOR_PROLIFIC2, USB_PRODUCT_PROLIFIC2_WSIM, 0, 0xFFFF, TYPE_PL2303X)}, +}; + +static device_method_t uplcom_methods[] = { + DEVMETHOD(device_probe, uplcom_probe), + DEVMETHOD(device_attach, uplcom_attach), + DEVMETHOD(device_detach, uplcom_detach), + {0, 0} +}; + +static devclass_t uplcom_devclass; + +static driver_t uplcom_driver = { + .name = "uplcom", + .methods = uplcom_methods, + .size = sizeof(struct uplcom_softc), +}; + +DRIVER_MODULE(uplcom, ushub, uplcom_driver, uplcom_devclass, NULL, 0); +MODULE_DEPEND(uplcom, usb2_serial, 1, 1, 1); +MODULE_DEPEND(uplcom, usb2_core, 1, 1, 1); +MODULE_DEPEND(uplcom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(uplcom, UPLCOM_MODVER); + +static int +uplcom_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UPLCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UPLCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(uplcom_devs, sizeof(uplcom_devs), uaa)); +} + +static int +uplcom_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uplcom_softc *sc = device_get_softc(dev); + struct usb2_interface *iface; + struct usb2_interface_descriptor *id; + int error; + + DPRINTFN(11, "\n"); + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + DPRINTF("sc = %p\n", sc); + + sc->sc_chiptype = USB_GET_DRIVER_INFO(uaa); + sc->sc_udev = uaa->device; + + DPRINTF("chiptype: %s\n", + (sc->sc_chiptype == TYPE_PL2303X) ? + "2303X" : "2303"); + + /* + * USB-RSAQ1 has two interface + * + * USB-RSAQ1 | USB-RSAQ2 + * -----------------+----------------- + * Interface 0 |Interface 0 + * Interrupt(0x81) | Interrupt(0x81) + * -----------------+ BulkIN(0x02) + * Interface 1 | BulkOUT(0x83) + * BulkIN(0x02) | + * BulkOUT(0x83) | + */ + + sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index[1] = UPLCOM_IFACE_INDEX; + + iface = usb2_get_iface(uaa->device, UPLCOM_SECOND_IFACE_INDEX); + if (iface) { + id = usb2_get_interface_descriptor(iface); + if (id == NULL) { + device_printf(dev, "no interface descriptor (2)!\n"); + goto detach; + } + sc->sc_data_iface_no = id->bInterfaceNumber; + sc->sc_iface_index[0] = UPLCOM_SECOND_IFACE_INDEX; + usb2_set_parent_iface(uaa->device, + UPLCOM_SECOND_IFACE_INDEX, uaa->info.bIfaceIndex); + } else { + sc->sc_data_iface_no = sc->sc_ctrl_iface_no; + sc->sc_iface_index[0] = UPLCOM_IFACE_INDEX; + } + + error = usb2_transfer_setup(uaa->device, + sc->sc_iface_index, sc->sc_xfer, uplcom_config_data, + UPLCOM_N_TRANSFER, sc, &Giant); + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usb2_errstr(error)); + goto detach; + } + error = uplcom_reset(sc, uaa->device); + if (error) { + device_printf(dev, "reset failed, error=%s\n", + usb2_errstr(error)); + goto detach; + } + /* clear stall at first run */ + sc->sc_flag |= (UPLCOM_FLAG_READ_STALL | + UPLCOM_FLAG_WRITE_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uplcom_callback, &Giant); + if (error) { + goto detach; + } + /* + * do the initialization during attach so that the system does not + * sleep during open: + */ + if (sc->sc_chiptype == TYPE_PL2303X) { + if (uplcom_pl2303x_init(uaa->device)) { + device_printf(dev, "init failed!\n"); + goto detach; + } + } + return (0); + +detach: + uplcom_detach(dev); + return (ENXIO); +} + +static int +uplcom_detach(device_t dev) +{ + struct uplcom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UPLCOM_N_TRANSFER); + + return (0); +} + +static usb2_error_t +uplcom_reset(struct uplcom_softc *sc, struct usb2_device *udev) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + return (usb2_do_request(udev, &Giant, &req, NULL)); +} + +struct pl2303x_init { + uint8_t req_type; + uint8_t request; + uint16_t value; + uint16_t index; + uint16_t length; +}; + +static const struct pl2303x_init pl2303x[] = { + {UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1}, + {UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 0, 0}, + {UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1}, + {UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1}, + {UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1}, + {UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 1, 0}, + {UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1}, + {UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1}, + {UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0, 1, 0}, + {UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 1, 0, 0}, + {UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x44, 0}, + {UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 8, 0, 0}, + {UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 9, 0, 0}, +}; + +#define N_PL2302X_INIT (sizeof(pl2303x)/sizeof(pl2303x[0])) + +static int +uplcom_pl2303x_init(struct usb2_device *udev) +{ + struct usb2_device_request req; + usb2_error_t err; + uint8_t buf[4]; + uint8_t i; + + for (i = 0; i != N_PL2302X_INIT; i++) { + req.bmRequestType = pl2303x[i].req_type; + req.bRequest = pl2303x[i].request; + USETW(req.wValue, pl2303x[i].value); + USETW(req.wIndex, pl2303x[i].index); + USETW(req.wLength, pl2303x[i].length); + + err = usb2_do_request(udev, &Giant, &req, buf); + if (err) { + DPRINTF("error=%s\n", usb2_errstr(err)); + return (EIO); + } + } + return (0); +} + +static void +uplcom_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + uplcom_cfg_do_request(sc, &req, NULL); + return; +} + +static void +uplcom_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + uplcom_cfg_do_request(sc, &req, NULL); + return; +} + +static void +uplcom_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb2_device_request req; + uint16_t temp; + + DPRINTF("onoff = %d\n", onoff); + + temp = (onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + uplcom_cfg_do_request(sc, &req, NULL); + return; +} + +static const int32_t uplcom_rates[] = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, + 19200, 28800, 38400, 57600, 115200, + /* + * Higher speeds are probably possible. PL2303X supports up to + * 6Mb and can set any rate + */ + 230400, 460800, 614400, 921600, 1228800 +}; + +#define N_UPLCOM_RATES (sizeof(uplcom_rates)/sizeof(uplcom_rates[0])) + +static int +uplcom_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + uint8_t i; + + DPRINTF("\n"); + + /* check requested baud rate */ + + for (i = 0;; i++) { + + if (i != N_UPLCOM_RATES) { + if (uplcom_rates[i] == t->c_ospeed) { + break; + } + } else { + DPRINTF("invalid baud rate (%d)\n", t->c_ospeed); + return (EIO); + } + } + + return (0); +} + +static void +uplcom_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb2_cdc_line_state ls; + struct usb2_device_request req; + + DPRINTF("sc = %p\n", sc); + + bzero(&ls, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + if (t->c_cflag & CSTOPB) { + ls.bCharFormat = UCDC_STOP_BIT_2; + } else { + ls.bCharFormat = UCDC_STOP_BIT_1; + } + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + ls.bParityType = UCDC_PARITY_ODD; + } else { + ls.bParityType = UCDC_PARITY_EVEN; + } + } else { + ls.bParityType = UCDC_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(ls.dwDTERate), ls.bCharFormat, + ls.bParityType, ls.bDataBits); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + uplcom_cfg_do_request(sc, &req, &ls); + + if (t->c_cflag & CRTSCTS) { + + DPRINTF("crtscts = on\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + if (sc->sc_chiptype == TYPE_PL2303X) + USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303X); + else + USETW(req.wIndex, UPLCOM_SET_CRTSCTS); + USETW(req.wLength, 0); + + uplcom_cfg_do_request(sc, &req, NULL); + } else { + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + uplcom_cfg_do_request(sc, &req, NULL); + } + return; +} + +static void +uplcom_start_read(struct usb2_com_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usb2_transfer_start(sc->sc_xfer[4]); + + /* start read endpoint */ + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +uplcom_stop_read(struct usb2_com_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usb2_transfer_stop(sc->sc_xfer[4]); + + /* stop read endpoint */ + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +uplcom_start_write(struct usb2_com_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +uplcom_stop_write(struct usb2_com_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static void +uplcom_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; + return; +} + +static int +uplcom_ioctl(struct usb2_com_softc *ucom, uint32_t cmd, caddr_t data, int flag, + struct thread *td) +{ + return (ENOTTY); +} + +static void +uplcom_intr_callback(struct usb2_xfer *xfer) +{ + struct uplcom_softc *sc = xfer->priv_sc; + uint8_t buf[9]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen = %u\n", xfer->actlen); + + if (xfer->actlen >= 9) { + + usb2_copy_out(xfer->frbuffers, 0, buf, sizeof(buf)); + + DPRINTF("status = 0x%02x\n", buf[8]); + + sc->sc_lsr = 0; + sc->sc_msr = 0; + + if (buf[8] & RSAQ_STATUS_CTS) { + sc->sc_msr |= SER_CTS; + } + if (buf[8] & RSAQ_STATUS_DSR) { + sc->sc_msr |= SER_DSR; + } + if (buf[8] & RSAQ_STATUS_DCD) { + sc->sc_msr |= SER_DCD; + } + usb2_com_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: + if (sc->sc_flag & UPLCOM_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UPLCOM_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + return; + + } +} + +static void +uplcom_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uplcom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UPLCOM_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uplcom_write_callback(struct usb2_xfer *xfer) +{ + struct uplcom_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flag & UPLCOM_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UPLCOM_BULK_BUF_SIZE, &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UPLCOM_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} + +static void +uplcom_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uplcom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UPLCOM_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uplcom_read_callback(struct usb2_xfer *xfer) +{ + struct uplcom_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flag & UPLCOM_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UPLCOM_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +uplcom_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uplcom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UPLCOM_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uplcom_cfg_do_request(struct uplcom_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + goto error; + } + err = usb2_do_request_flags(sc->sc_udev, &Giant, req, + data, 0, NULL, 1000); + + if (err) { + + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} diff --git a/sys/dev/usb2/serial/usb2_serial.c b/sys/dev/usb2/serial/usb2_serial.c new file mode 100644 index 000000000000..e9cf9e52058f --- /dev/null +++ b/sys/dev/usb2/serial/usb2_serial.c @@ -0,0 +1,1112 @@ +/* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ + +/*- + * Copyright (c) 2001-2003, 2005, 2008 + * Shunsuke Akiyama . + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * NOTE: all function names beginning like "usb2_com_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_com_debug +#define usb2_config_td_cc usb2_com_config_copy +#define usb2_config_td_softc usb2_com_softc + +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int usb2_com_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); +SYSCTL_INT(_hw_usb2_ucom, OID_AUTO, debug, CTLFLAG_RW, + &usb2_com_debug, 0, "ucom debug level"); +#endif + +struct usb2_com_config_copy { + struct usb2_com_softc *cc_softc; + uint8_t cc_flag0; + uint8_t cc_flag1; + uint8_t cc_flag2; + uint8_t cc_flag3; +}; + +static usb2_config_td_command_t usb2_com_config_copy; +static usb2_config_td_command_t usb2_com_cfg_start_transfers; +static usb2_config_td_command_t usb2_com_cfg_open; +static usb2_config_td_command_t usb2_com_cfg_close; +static usb2_config_td_command_t usb2_com_cfg_break; +static usb2_config_td_command_t usb2_com_cfg_dtr; +static usb2_config_td_command_t usb2_com_cfg_rts; +static usb2_config_td_command_t usb2_com_cfg_status_change; +static usb2_config_td_command_t usb2_com_cfg_param; + +static uint8_t usb2_com_units_alloc(uint32_t sub_units, uint32_t *p_root_unit); +static void usb2_com_units_free(uint32_t root_unit, uint32_t sub_units); +static int usb2_com_attach_sub(struct usb2_com_softc *sc); +static void usb2_com_detach_sub(struct usb2_com_softc *sc); +static void usb2_com_queue_command(struct usb2_com_softc *sc, usb2_config_td_command_t *cmd, int flag); +static void usb2_com_shutdown(struct usb2_com_softc *sc); +static void usb2_com_start_transfers(struct usb2_com_softc *sc); +static void usb2_com_break(struct usb2_com_softc *sc, uint8_t onoff); +static void usb2_com_dtr(struct usb2_com_softc *sc, uint8_t onoff); +static void usb2_com_rts(struct usb2_com_softc *sc, uint8_t onoff); + +static tsw_open_t usb2_com_open; +static tsw_close_t usb2_com_close; +static tsw_ioctl_t usb2_com_ioctl; +static tsw_modem_t usb2_com_modem; +static tsw_param_t usb2_com_param; +static tsw_outwakeup_t usb2_com_start_write; +static tsw_free_t usb2_com_free; + +static struct ttydevsw usb2_com_class = { + .tsw_flags = TF_INITLOCK | TF_CALLOUT, + .tsw_open = usb2_com_open, + .tsw_close = usb2_com_close, + .tsw_outwakeup = usb2_com_start_write, + .tsw_ioctl = usb2_com_ioctl, + .tsw_param = usb2_com_param, + .tsw_modem = usb2_com_modem, + .tsw_free = usb2_com_free, +}; + +MODULE_DEPEND(ucom, usb2_core, 1, 1, 1); +MODULE_VERSION(ucom, UCOM_MODVER); +MODULE_VERSION(usb2_serial, 1); + +#define UCOM_UNIT_MAX 0x1000 /* exclusive */ +#define UCOM_SUB_UNIT_MAX 0x100 /* exclusive */ + +static uint8_t usb2_com_bitmap[(UCOM_UNIT_MAX + 7) / 8]; + +static uint8_t +usb2_com_units_alloc(uint32_t sub_units, uint32_t *p_root_unit) +{ + uint32_t n; + uint32_t o; + uint32_t x; + uint32_t max = UCOM_UNIT_MAX - (UCOM_UNIT_MAX % sub_units); + uint8_t error = 1; + + mtx_lock(&Giant); + + for (n = 0; n < max; n += sub_units) { + + /* check for free consecutive bits */ + + for (o = 0; o < sub_units; o++) { + + x = n + o; + + if (usb2_com_bitmap[x / 8] & (1 << (x % 8))) { + goto skip; + } + } + + /* allocate */ + + for (o = 0; o < sub_units; o++) { + + x = n + o; + + usb2_com_bitmap[x / 8] |= (1 << (x % 8)); + } + + error = 0; + + break; + +skip: ; + } + + mtx_unlock(&Giant); + + /* + * Always set the variable pointed to by "p_root_unit" so that + * the compiler does not think that it is used uninitialised: + */ + *p_root_unit = n; + + return (error); +} + +static void +usb2_com_units_free(uint32_t root_unit, uint32_t sub_units) +{ + uint32_t x; + + mtx_lock(&Giant); + + while (sub_units--) { + x = root_unit + sub_units; + usb2_com_bitmap[x / 8] &= ~(1 << (x % 8)); + } + + mtx_unlock(&Giant); + + return; +} + +/* + * "N" sub_units are setup at a time. All sub-units will + * be given sequential unit numbers. The number of + * sub-units can be used to differentiate among + * different types of devices. + * + * The mutex pointed to by "p_mtx" is applied before all + * callbacks are called back. Also "p_mtx" must be applied + * before calling into the ucom-layer! Currently only Giant + * is supported. + */ +int +usb2_com_attach(struct usb2_com_super_softc *ssc, struct usb2_com_softc *sc, + uint32_t sub_units, void *parent, + const struct usb2_com_callback *callback, struct mtx *p_mtx) +{ + uint32_t n; + uint32_t root_unit; + int error = 0; + + if ((sc == NULL) || + (sub_units == 0) || + (sub_units > UCOM_SUB_UNIT_MAX) || + (callback == NULL)) { + return (EINVAL); + } + if (usb2_com_units_alloc(sub_units, &root_unit)) { + return (ENOMEM); + } + if (usb2_config_td_setup + (&ssc->sc_config_td, sc, p_mtx, NULL, + sizeof(struct usb2_com_config_copy), 24 * sub_units)) { + usb2_com_units_free(root_unit, sub_units); + return (ENOMEM); + } + for (n = 0; n < sub_units; n++, sc++) { + sc->sc_unit = root_unit + n; + sc->sc_local_unit = n; + sc->sc_super = ssc; + sc->sc_parent_mtx = p_mtx; + sc->sc_parent = parent; + sc->sc_callback = callback; + + error = usb2_com_attach_sub(sc); + if (error) { + usb2_com_detach(ssc, sc - n, n); + usb2_com_units_free(root_unit + n, sub_units - n); + break; + } + sc->sc_flag |= UCOM_FLAG_ATTACHED; + } + return (error); +} + +/* NOTE: the following function will do nothing if + * the structure pointed to by "ssc" and "sc" is zero. + */ +void +usb2_com_detach(struct usb2_com_super_softc *ssc, struct usb2_com_softc *sc, + uint32_t sub_units) +{ + uint32_t n; + + usb2_config_td_drain(&ssc->sc_config_td); + + for (n = 0; n < sub_units; n++, sc++) { + if (sc->sc_flag & UCOM_FLAG_ATTACHED) { + + usb2_com_detach_sub(sc); + + usb2_com_units_free(sc->sc_unit, 1); + + /* avoid duplicate detach: */ + sc->sc_flag &= ~UCOM_FLAG_ATTACHED; + } + } + + usb2_config_td_unsetup(&ssc->sc_config_td); + + return; +} + +static int +usb2_com_attach_sub(struct usb2_com_softc *sc) +{ + struct tty *tp; + int error = 0; + char buf[32]; /* temporary TTY device name buffer */ + + tp = tty_alloc(&usb2_com_class, sc, sc->sc_parent_mtx); + if (tp == NULL) { + error = ENOMEM; + goto done; + } + DPRINTF("tp = %p, unit = %d\n", tp, sc->sc_unit); + + buf[0] = 0; /* set some default value */ + + /* Check if the client has a custom TTY name */ + if (sc->sc_callback->usb2_com_tty_name) { + sc->sc_callback->usb2_com_tty_name(sc, buf, + sizeof(buf), sc->sc_local_unit); + } + if (buf[0] == 0) { + /* Use default TTY name */ + if (snprintf(buf, sizeof(buf), "U%u", sc->sc_unit)) { + /* ignore */ + } + } + tty_makedev(tp, NULL, "%s", buf); + + sc->sc_tty = tp; + + DPRINTF("ttycreate: %s\n", buf); + usb2_cv_init(&sc->sc_cv, "usb2_com"); + +done: + return (error); +} + +static void +usb2_com_detach_sub(struct usb2_com_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty); + + /* the config thread has been stopped when we get here */ + + mtx_lock(sc->sc_parent_mtx); + sc->sc_flag |= UCOM_FLAG_GONE; + sc->sc_flag &= ~(UCOM_FLAG_HL_READY | + UCOM_FLAG_LL_READY); + mtx_unlock(sc->sc_parent_mtx); + if (tp) { + tty_lock(tp); + + usb2_com_close(tp); /* close, if any */ + + tty_rel_gone(tp); + + mtx_lock(sc->sc_parent_mtx); + /* Wait for the callback after the TTY is torn down */ + while (sc->sc_ttyfreed == 0) + usb2_cv_wait(&sc->sc_cv, sc->sc_parent_mtx); + /* + * make sure that read and write transfers are stopped + */ + if (sc->sc_callback->usb2_com_stop_read) { + (sc->sc_callback->usb2_com_stop_read) (sc); + } + if (sc->sc_callback->usb2_com_stop_write) { + (sc->sc_callback->usb2_com_stop_write) (sc); + } + mtx_unlock(sc->sc_parent_mtx); + } + usb2_cv_destroy(&sc->sc_cv); + return; +} + +static void +usb2_com_config_copy(struct usb2_com_softc *sc, struct usb2_com_config_copy *cc, + uint16_t refcount) +{ + cc->cc_softc = sc + (refcount % UCOM_SUB_UNIT_MAX); + cc->cc_flag0 = (refcount / (1 * UCOM_SUB_UNIT_MAX)) % 2; + cc->cc_flag1 = (refcount / (2 * UCOM_SUB_UNIT_MAX)) % 2; + cc->cc_flag2 = (refcount / (4 * UCOM_SUB_UNIT_MAX)) % 2; + cc->cc_flag3 = (refcount / (8 * UCOM_SUB_UNIT_MAX)) % 2; + return; +} + +static void +usb2_com_queue_command(struct usb2_com_softc *sc, usb2_config_td_command_t *cmd, int flag) +{ + struct usb2_com_super_softc *ssc = sc->sc_super; + + usb2_config_td_queue_command + (&ssc->sc_config_td, &usb2_com_config_copy, + cmd, (cmd == &usb2_com_cfg_status_change) ? 1 : 0, + ((sc->sc_local_unit % UCOM_SUB_UNIT_MAX) + + (flag ? UCOM_SUB_UNIT_MAX : 0))); + return; +} + +static void +usb2_com_shutdown(struct usb2_com_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + DPRINTF("\n"); + + /* + * Hang up if necessary: + */ + if (tp->t_termios.c_cflag & HUPCL) { + usb2_com_modem(tp, 0, SER_DTR); + } + return; +} + +/* + * Return values: + * 0: normal delay + * else: config thread is gone + */ +uint8_t +usb2_com_cfg_sleep(struct usb2_com_softc *sc, uint32_t timeout) +{ + struct usb2_com_super_softc *ssc = sc->sc_super; + + return (usb2_config_td_sleep(&ssc->sc_config_td, timeout)); +} + +/* + * Return values: + * 0: normal + * else: config thread is gone + */ +uint8_t +usb2_com_cfg_is_gone(struct usb2_com_softc *sc) +{ + struct usb2_com_super_softc *ssc = sc->sc_super; + + return (usb2_config_td_is_gone(&ssc->sc_config_td)); +} + +static void +usb2_com_cfg_start_transfers(struct usb2_com_softc *sc, struct usb2_com_config_copy *cc, + uint16_t refcount) +{ + sc = cc->cc_softc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* TTY device closed */ + return; + } + sc->sc_flag |= UCOM_FLAG_GP_DATA; + + if (sc->sc_callback->usb2_com_start_read) { + (sc->sc_callback->usb2_com_start_read) (sc); + } + if (sc->sc_callback->usb2_com_start_write) { + (sc->sc_callback->usb2_com_start_write) (sc); + } + return; +} + +static void +usb2_com_start_transfers(struct usb2_com_softc *sc) +{ + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + /* + * do a direct call first, to get hardware buffers flushed + */ + + if (sc->sc_callback->usb2_com_start_read) { + (sc->sc_callback->usb2_com_start_read) (sc); + } + if (sc->sc_callback->usb2_com_start_write) { + (sc->sc_callback->usb2_com_start_write) (sc); + } + if (!(sc->sc_flag & UCOM_FLAG_GP_DATA)) { + usb2_com_queue_command(sc, &usb2_com_cfg_start_transfers, 0); + } + return; +} + +static void +usb2_com_cfg_open(struct usb2_com_softc *sc, struct usb2_com_config_copy *cc, + uint16_t refcount) +{ + sc = cc->cc_softc; + + DPRINTF("\n"); + + if (sc->sc_flag & UCOM_FLAG_LL_READY) { + + /* already opened */ + + } else { + + sc->sc_flag |= UCOM_FLAG_LL_READY; + + if (sc->sc_callback->usb2_com_cfg_open) { + (sc->sc_callback->usb2_com_cfg_open) (sc); + + /* wait a little */ + usb2_com_cfg_sleep(sc, hz / 10); + } + } + return; +} + +static int +usb2_com_open(struct tty *tp) +{ + struct usb2_com_softc *sc = tty_softc(tp); + int error; + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_GONE) { + return (ENXIO); + } + if (sc->sc_flag & UCOM_FLAG_HL_READY) { + /* already opened */ + return (0); + } + DPRINTF("tp = %p\n", tp); + + if (sc->sc_callback->usb2_com_pre_open) { + /* + * give the lower layer a chance to disallow TTY open, for + * example if the device is not present: + */ + error = (sc->sc_callback->usb2_com_pre_open) (sc); + if (error) { + return (error); + } + } + sc->sc_flag |= UCOM_FLAG_HL_READY; + + /* Disable transfers */ + sc->sc_flag &= ~UCOM_FLAG_GP_DATA; + + sc->sc_lsr = 0; + sc->sc_msr = 0; + sc->sc_mcr = 0; + + usb2_com_queue_command(sc, &usb2_com_cfg_open, 0); + + usb2_com_start_transfers(sc); + + usb2_com_modem(tp, SER_DTR | SER_RTS, 0); + + usb2_com_break(sc, 0); + + usb2_com_status_change(sc); + + return (0); +} + +static void +usb2_com_cfg_close(struct usb2_com_softc *sc, struct usb2_com_config_copy *cc, + uint16_t refcount) +{ + sc = cc->cc_softc; + + DPRINTF("\n"); + + if (sc->sc_flag & UCOM_FLAG_LL_READY) { + + sc->sc_flag &= ~(UCOM_FLAG_LL_READY | + UCOM_FLAG_GP_DATA); + + if (sc->sc_callback->usb2_com_cfg_close) { + (sc->sc_callback->usb2_com_cfg_close) (sc); + } + } else { + /* already closed */ + } + return; +} + +static void +usb2_com_close(struct tty *tp) +{ + struct usb2_com_softc *sc = tty_softc(tp); + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + DPRINTF("tp=%p\n", tp); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + DPRINTF("tp=%p already closed\n", tp); + return; + } + usb2_com_shutdown(sc); + + usb2_com_queue_command(sc, &usb2_com_cfg_close, 0); + + sc->sc_flag &= ~(UCOM_FLAG_HL_READY | + UCOM_FLAG_WR_START | + UCOM_FLAG_RTS_IFLOW); + + if (sc->sc_callback->usb2_com_stop_read) { + (sc->sc_callback->usb2_com_stop_read) (sc); + } + if (sc->sc_callback->usb2_com_stop_write) { + (sc->sc_callback->usb2_com_stop_write) (sc); + } + return; +} + +static int +usb2_com_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) +{ + struct usb2_com_softc *sc = tty_softc(tp); + int error; + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return (EIO); + } + DPRINTF("cmd = 0x%08lx\n", cmd); + + switch (cmd) { + case TIOCSBRK: + usb2_com_break(sc, 1); + error = 0; + break; + case TIOCCBRK: + usb2_com_break(sc, 0); + error = 0; + break; + default: + if (sc->sc_callback->usb2_com_ioctl) { + error = (sc->sc_callback->usb2_com_ioctl) + (sc, cmd, data, 0, td); + } else { + error = ENOIOCTL; + } + break; + } + return (error); +} + +static int +usb2_com_modem(struct tty *tp, int sigon, int sigoff) +{ + struct usb2_com_softc *sc = tty_softc(tp); + uint8_t onoff; + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return (0); + } + if ((sigon == 0) && (sigoff == 0)) { + + if (sc->sc_mcr & SER_DTR) { + sigon |= SER_DTR; + } + if (sc->sc_mcr & SER_RTS) { + sigon |= SER_RTS; + } + if (sc->sc_msr & SER_CTS) { + sigon |= SER_CTS; + } + if (sc->sc_msr & SER_DCD) { + sigon |= SER_DCD; + } + if (sc->sc_msr & SER_DSR) { + sigon |= SER_DSR; + } + if (sc->sc_msr & SER_RI) { + sigon |= SER_RI; + } + return (sigon); + } + if (sigon & SER_DTR) { + sc->sc_mcr |= SER_DTR; + } + if (sigoff & SER_DTR) { + sc->sc_mcr &= ~SER_DTR; + } + if (sigon & SER_RTS) { + sc->sc_mcr |= SER_RTS; + } + if (sigoff & SER_RTS) { + sc->sc_mcr &= ~SER_RTS; + } + onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0; + usb2_com_dtr(sc, onoff); + + onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0; + usb2_com_rts(sc, onoff); + + return (0); +} + +static void +usb2_com_cfg_break(struct usb2_com_softc *sc, struct usb2_com_config_copy *cc, + uint16_t refcount) +{ + sc = cc->cc_softc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + DPRINTF("onoff=%d\n", cc->cc_flag0); + + if (sc->sc_callback->usb2_com_cfg_set_break) { + (sc->sc_callback->usb2_com_cfg_set_break) (sc, cc->cc_flag0); + } + return; +} + +static void +usb2_com_break(struct usb2_com_softc *sc, uint8_t onoff) +{ + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + DPRINTF("onoff = %d\n", onoff); + + usb2_com_queue_command(sc, &usb2_com_cfg_break, onoff); + return; +} + +static void +usb2_com_cfg_dtr(struct usb2_com_softc *sc, struct usb2_com_config_copy *cc, + uint16_t refcount) +{ + sc = cc->cc_softc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + DPRINTF("onoff=%d\n", cc->cc_flag0); + + if (sc->sc_callback->usb2_com_cfg_set_dtr) { + (sc->sc_callback->usb2_com_cfg_set_dtr) (sc, cc->cc_flag0); + } + return; +} + +static void +usb2_com_dtr(struct usb2_com_softc *sc, uint8_t onoff) +{ + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + DPRINTF("onoff = %d\n", onoff); + + usb2_com_queue_command(sc, &usb2_com_cfg_dtr, onoff); + return; +} + +static void +usb2_com_cfg_rts(struct usb2_com_softc *sc, struct usb2_com_config_copy *cc, + uint16_t refcount) +{ + sc = cc->cc_softc; + + DPRINTF("onoff=%d\n", cc->cc_flag0); + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->usb2_com_cfg_set_rts) { + (sc->sc_callback->usb2_com_cfg_set_rts) (sc, cc->cc_flag0); + } + return; +} + +static void +usb2_com_rts(struct usb2_com_softc *sc, uint8_t onoff) +{ + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + DPRINTF("onoff = %d\n", onoff); + + usb2_com_queue_command(sc, &usb2_com_cfg_rts, onoff); + + return; +} + +static void +usb2_com_cfg_status_change(struct usb2_com_softc *sc, + struct usb2_com_config_copy *cc, uint16_t refcount) +{ + struct tty *tp; + + uint8_t new_msr; + uint8_t new_lsr; + uint8_t onoff; + + sc = cc->cc_softc; + tp = sc->sc_tty; + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->usb2_com_cfg_get_status == NULL) { + return; + } + /* get status */ + + new_msr = 0; + new_lsr = 0; + + (sc->sc_callback->usb2_com_cfg_get_status) (sc, &new_lsr, &new_msr); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* TTY device closed */ + return; + } + onoff = ((sc->sc_msr ^ new_msr) & SER_DCD); + + sc->sc_msr = new_msr; + sc->sc_lsr = new_lsr; + + if (onoff) { + + onoff = (sc->sc_msr & SER_DCD) ? 1 : 0; + + DPRINTF("DCD changed to %d\n", onoff); + + ttydisc_modem(tp, onoff); + } + return; +} + +void +usb2_com_status_change(struct usb2_com_softc *sc) +{ + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + DPRINTF("\n"); + + usb2_com_queue_command(sc, &usb2_com_cfg_status_change, 0); + return; +} + +static void +usb2_com_cfg_param(struct usb2_com_softc *sc, struct usb2_com_config_copy *cc, + uint16_t refcount) +{ + struct termios t_copy; + + sc = cc->cc_softc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->usb2_com_cfg_param == NULL) { + return; + } + t_copy = sc->sc_termios_copy; + + (sc->sc_callback->usb2_com_cfg_param) (sc, &t_copy); + + /* wait a little */ + usb2_com_cfg_sleep(sc, hz / 10); + + return; +} + +static int +usb2_com_param(struct tty *tp, struct termios *t) +{ + struct usb2_com_softc *sc = tty_softc(tp); + uint8_t opened; + int error; + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + opened = 0; + error = 0; + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + + /* XXX the TTY layer should call "open()" first! */ + + error = usb2_com_open(tp); + if (error) { + goto done; + } + opened = 1; + } + DPRINTF("sc = %p\n", sc); + + /* Check requested parameters. */ + if (t->c_ospeed < 0) { + DPRINTF("negative ospeed\n"); + error = EINVAL; + goto done; + } + if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) { + DPRINTF("mismatch ispeed and ospeed\n"); + error = EINVAL; + goto done; + } + t->c_ispeed = t->c_ospeed; + + if (sc->sc_callback->usb2_com_pre_param) { + /* Let the lower layer verify the parameters */ + error = (sc->sc_callback->usb2_com_pre_param) (sc, t); + if (error) { + DPRINTF("callback error = %d\n", error); + goto done; + } + } + /* Make a copy of the termios parameters */ + sc->sc_termios_copy = *t; + + /* Disable transfers */ + sc->sc_flag &= ~UCOM_FLAG_GP_DATA; + + /* Queue baud rate programming command first */ + usb2_com_queue_command(sc, &usb2_com_cfg_param, 0); + + /* Queue transfer enable command last */ + usb2_com_start_transfers(sc); + + if (t->c_cflag & CRTS_IFLOW) { + sc->sc_flag |= UCOM_FLAG_RTS_IFLOW; + } else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) { + sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW; + usb2_com_modem(tp, SER_RTS, 0); + } +done: + if (error) { + if (opened) { + usb2_com_close(tp); + } + } + return (error); +} + +static void +usb2_com_start_write(struct tty *tp) +{ + struct usb2_com_softc *sc = tty_softc(tp); + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + DPRINTF("sc = %p\n", sc); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* The higher layer is not ready */ + return; + } + sc->sc_flag |= UCOM_FLAG_WR_START; + + usb2_com_start_transfers(sc); + + return; +} + +/*------------------------------------------------------------------------* + * usb2_com_get_data + * + * Return values: + * 0: No data is available. + * Else: Data is available. + *------------------------------------------------------------------------*/ +uint8_t +usb2_com_get_data(struct usb2_com_softc *sc, struct usb2_page_cache *pc, + uint32_t offset, uint32_t len, uint32_t *actlen) +{ + struct usb2_page_search res; + struct tty *tp = sc->sc_tty; + uint32_t cnt; + uint32_t offset_orig; + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if ((!(sc->sc_flag & UCOM_FLAG_HL_READY)) || + (!(sc->sc_flag & UCOM_FLAG_GP_DATA)) || + (!(sc->sc_flag & UCOM_FLAG_WR_START))) { + actlen[0] = 0; + return (0); /* multiport device polling */ + } + offset_orig = offset; + + while (len != 0) { + + usb2_get_page(pc, offset, &res); + + if (res.length > len) { + res.length = len; + } + /* copy data directly into USB buffer */ + cnt = ttydisc_getc(tp, res.buffer, res.length); + + offset += cnt; + len -= cnt; + + if (cnt < res.length) { + /* end of buffer */ + break; + } + } + + actlen[0] = offset - offset_orig; + + DPRINTF("cnt=%d\n", actlen[0]); + + if (actlen[0] == 0) { + return (0); + } + return (1); +} + +void +usb2_com_put_data(struct usb2_com_softc *sc, struct usb2_page_cache *pc, + uint32_t offset, uint32_t len) +{ + struct usb2_page_search res; + struct tty *tp = sc->sc_tty; + char *buf; + uint32_t cnt; + + mtx_assert(sc->sc_parent_mtx, MA_OWNED); + + if ((!(sc->sc_flag & UCOM_FLAG_HL_READY)) || + (!(sc->sc_flag & UCOM_FLAG_GP_DATA))) { + return; /* multiport device polling */ + } + if (len == 0) + return; /* no data */ + + /* set a flag to prevent recursation ? */ + + while (len > 0) { + + usb2_get_page(pc, offset, &res); + + if (res.length > len) { + res.length = len; + } + len -= res.length; + offset += res.length; + + /* pass characters to tty layer */ + + buf = res.buffer; + cnt = res.length; + + /* first check if we can pass the buffer directly */ + + if (ttydisc_can_bypass(tp)) { + if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) { + DPRINTF("tp=%p, data lost\n", tp); + } + continue; + } + /* need to loop */ + + for (cnt = 0; cnt != res.length; cnt++) { + if (ttydisc_rint(tp, buf[cnt], 0) == -1) { + /* XXX what should we do? */ + + DPRINTF("tp=%p, lost %d " + "chars\n", tp, res.length - cnt); + break; + } + } + } + ttydisc_rint_done(tp); + return; +} + +static void +usb2_com_free(void *xsc) +{ + struct usb2_com_softc *sc = xsc; + + mtx_lock(sc->sc_parent_mtx); + sc->sc_ttyfreed = 1; + usb2_cv_signal(&sc->sc_cv); + mtx_unlock(sc->sc_parent_mtx); +} diff --git a/sys/dev/usb2/serial/usb2_serial.h b/sys/dev/usb2/serial/usb2_serial.h new file mode 100644 index 000000000000..aa4c7d04da68 --- /dev/null +++ b/sys/dev/usb2/serial/usb2_serial.h @@ -0,0 +1,159 @@ +/* $NetBSD: ucomvar.h,v 1.9 2001/01/23 21:56:17 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama . + * 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. + */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#ifndef _USB2_SERIAL_H_ +#define _USB2_SERIAL_H_ + +#include +#include +#include +#include + +/* Module interface related macros */ +#define UCOM_MODVER 1 + +#define UCOM_MINVER 1 +#define UCOM_PREFVER UCOM_MODVER +#define UCOM_MAXVER 1 + +struct usb2_com_softc; +struct thread; + +/* NOTE: Only callbacks with "_cfg_" in its name are called + * from a config thread, and are allowed to sleep! The other + * callbacks are _not_ allowed to sleep! + * + * NOTE: There is no guarantee that "usb2_com_cfg_close()" will + * be called after "usb2_com_cfg_open()" if the device is detached + * while it is open! + */ +struct usb2_com_callback { + void (*usb2_com_cfg_get_status) (struct usb2_com_softc *, uint8_t *plsr, uint8_t *pmsr); + void (*usb2_com_cfg_set_dtr) (struct usb2_com_softc *, uint8_t); + void (*usb2_com_cfg_set_rts) (struct usb2_com_softc *, uint8_t); + void (*usb2_com_cfg_set_break) (struct usb2_com_softc *, uint8_t); + void (*usb2_com_cfg_param) (struct usb2_com_softc *, struct termios *); + void (*usb2_com_cfg_open) (struct usb2_com_softc *); + void (*usb2_com_cfg_close) (struct usb2_com_softc *); + int (*usb2_com_pre_open) (struct usb2_com_softc *); + int (*usb2_com_pre_param) (struct usb2_com_softc *, struct termios *); + int (*usb2_com_ioctl) (struct usb2_com_softc *, uint32_t, caddr_t, int, struct thread *); + void (*usb2_com_start_read) (struct usb2_com_softc *); + void (*usb2_com_stop_read) (struct usb2_com_softc *); + void (*usb2_com_start_write) (struct usb2_com_softc *); + void (*usb2_com_stop_write) (struct usb2_com_softc *); + void (*usb2_com_tty_name) (struct usb2_com_softc *, char *pbuf, uint16_t buflen, uint16_t local_subunit); +}; + +/* Line status register */ +#define ULSR_RCV_FIFO 0x80 +#define ULSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define ULSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define ULSR_BI 0x10 /* Break detected */ +#define ULSR_FE 0x08 /* Framing error: bad stop bit */ +#define ULSR_PE 0x04 /* Parity error */ +#define ULSR_OE 0x02 /* Overrun, lost incoming byte */ +#define ULSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define ULSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +struct usb2_com_super_softc { + struct usb2_config_td sc_config_td; +}; + +struct usb2_com_softc { + struct termios sc_termios_copy; + struct cv sc_cv; + const struct usb2_com_callback *sc_callback; + struct usb2_com_super_softc *sc_super; + struct tty *sc_tty; + struct mtx *sc_parent_mtx; + void *sc_parent; + uint32_t sc_unit; + uint32_t sc_local_unit; + uint16_t sc_portno; + uint8_t sc_flag; +#define UCOM_FLAG_RTS_IFLOW 0x01 /* use RTS input flow control */ +#define UCOM_FLAG_GONE 0x02 /* the device is gone */ +#define UCOM_FLAG_ATTACHED 0x04 /* set if attached */ +#define UCOM_FLAG_GP_DATA 0x08 /* set if get and put data is possible */ +#define UCOM_FLAG_WR_START 0x10 /* set if write start was issued */ +#define UCOM_FLAG_LL_READY 0x20 /* set if low layer is ready */ +#define UCOM_FLAG_HL_READY 0x40 /* set if high layer is ready */ + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_mcr; + uint8_t sc_ttyfreed; /* set when TTY has been freed */ +}; + +int usb2_com_attach(struct usb2_com_super_softc *ssc, struct usb2_com_softc *sc, uint32_t sub_units, void *parent, const struct usb2_com_callback *callback, struct mtx *p_mtx); +void usb2_com_detach(struct usb2_com_super_softc *ssc, struct usb2_com_softc *sc, uint32_t sub_units); +void usb2_com_status_change(struct usb2_com_softc *); +uint8_t usb2_com_get_data(struct usb2_com_softc *sc, struct usb2_page_cache *pc, uint32_t offset, uint32_t len, uint32_t *actlen); +void usb2_com_put_data(struct usb2_com_softc *sc, struct usb2_page_cache *pc, uint32_t offset, uint32_t len); +uint8_t usb2_com_cfg_sleep(struct usb2_com_softc *sc, uint32_t timeout); +uint8_t usb2_com_cfg_is_gone(struct usb2_com_softc *sc); + +#endif /* _USB2_SERIAL_H_ */ diff --git a/sys/dev/usb2/serial/uvisor2.c b/sys/dev/usb2/serial/uvisor2.c new file mode 100644 index 000000000000..7280401c68af --- /dev/null +++ b/sys/dev/usb2/serial/uvisor2.c @@ -0,0 +1,675 @@ +/* $NetBSD: uvisor.c,v 1.9 2001/01/23 14:04:14 augustss Exp $ */ +/* $FreeBSD$ */ + +/* Also already merged from NetBSD: + * $NetBSD: uvisor.c,v 1.12 2001/11/13 06:24:57 lukem Exp $ + * $NetBSD: uvisor.c,v 1.13 2002/02/11 15:11:49 augustss Exp $ + * $NetBSD: uvisor.c,v 1.14 2002/02/27 23:00:03 augustss Exp $ + * $NetBSD: uvisor.c,v 1.15 2002/06/16 15:01:31 augustss Exp $ + * $NetBSD: uvisor.c,v 1.16 2002/07/11 21:14:36 augustss Exp $ + * $NetBSD: uvisor.c,v 1.17 2002/08/13 11:38:15 augustss Exp $ + * $NetBSD: uvisor.c,v 1.18 2003/02/05 00:50:14 augustss Exp $ + * $NetBSD: uvisor.c,v 1.19 2003/02/07 18:12:37 augustss Exp $ + * $NetBSD: uvisor.c,v 1.20 2003/04/11 01:30:10 simonb Exp $ + */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * Handspring Visor (Palmpilot compatible PDA) driver + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR uvisor_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int uvisor_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uvisor, CTLFLAG_RW, 0, "USB uvisor"); +SYSCTL_INT(_hw_usb2_uvisor, OID_AUTO, debug, CTLFLAG_RW, + &uvisor_debug, 0, "Debug level"); +#endif + +#define UVISOR_CONFIG_INDEX 0 +#define UVISOR_IFACE_INDEX 0 +#define UVISOR_N_TRANSFER 4 /* units */ +#define UVISOR_BUFSIZE 1024 /* bytes */ + +/* From the Linux driver */ +/* + * UVISOR_REQUEST_BYTES_AVAILABLE asks the visor for the number of bytes that + * are available to be transfered to the host for the specified endpoint. + * Currently this is not used, and always returns 0x0001 + */ +#define UVISOR_REQUEST_BYTES_AVAILABLE 0x01 + +/* + * UVISOR_CLOSE_NOTIFICATION is set to the device to notify it that the host + * is now closing the pipe. An empty packet is sent in response. + */ +#define UVISOR_CLOSE_NOTIFICATION 0x02 + +/* + * UVISOR_GET_CONNECTION_INFORMATION is sent by the host during enumeration to + * get the endpoints used by the connection. + */ +#define UVISOR_GET_CONNECTION_INFORMATION 0x03 + +/* + * UVISOR_GET_CONNECTION_INFORMATION returns data in the following format + */ +#define UVISOR_MAX_CONN 8 +struct uvisor_connection_info { + uWord num_ports; + struct { + uByte port_function_id; + uByte port; + } __packed connections[UVISOR_MAX_CONN]; +} __packed; + +#define UVISOR_CONNECTION_INFO_SIZE 18 + +/* struct uvisor_connection_info.connection[x].port defines: */ +#define UVISOR_ENDPOINT_1 0x01 +#define UVISOR_ENDPOINT_2 0x02 + +/* struct uvisor_connection_info.connection[x].port_function_id defines: */ +#define UVISOR_FUNCTION_GENERIC 0x00 +#define UVISOR_FUNCTION_DEBUGGER 0x01 +#define UVISOR_FUNCTION_HOTSYNC 0x02 +#define UVISOR_FUNCTION_CONSOLE 0x03 +#define UVISOR_FUNCTION_REMOTE_FILE_SYS 0x04 + +/* + * Unknown PalmOS stuff. + */ +#define UVISOR_GET_PALM_INFORMATION 0x04 +#define UVISOR_GET_PALM_INFORMATION_LEN 0x44 + +struct uvisor_palm_connection_info { + uByte num_ports; + uByte endpoint_numbers_different; + uWord reserved1; + struct { + uDWord port_function_id; + uByte port; + uByte end_point_info; + uWord reserved; + } __packed connections[UVISOR_MAX_CONN]; +} __packed; + +struct uvisor_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer[UVISOR_N_TRANSFER]; + struct usb2_device *sc_udev; + + uint16_t sc_flag; +#define UVISOR_FLAG_PALM4 0x0001 +#define UVISOR_FLAG_VISOR 0x0002 +#define UVISOR_FLAG_PALM35 0x0004 +#define UVISOR_FLAG_SEND_NOTIFY 0x0008 +#define UVISOR_FLAG_WRITE_STALL 0x0010 +#define UVISOR_FLAG_READ_STALL 0x0020 + + uint8_t sc_iface_no; + uint8_t sc_iface_index; +}; + +/* prototypes */ + +static device_probe_t uvisor_probe; +static device_attach_t uvisor_attach; +static device_detach_t uvisor_detach; + +static usb2_callback_t uvisor_write_callback; +static usb2_callback_t uvisor_write_clear_stall_callback; +static usb2_callback_t uvisor_read_callback; +static usb2_callback_t uvisor_read_clear_stall_callback; + +static usb2_error_t uvisor_init(struct uvisor_softc *sc, struct usb2_device *udev, struct usb2_config *config); +static void uvisor_cfg_open(struct usb2_com_softc *ucom); +static void uvisor_cfg_close(struct usb2_com_softc *ucom); +static void uvisor_start_read(struct usb2_com_softc *ucom); +static void uvisor_stop_read(struct usb2_com_softc *ucom); +static void uvisor_start_write(struct usb2_com_softc *ucom); +static void uvisor_stop_write(struct usb2_com_softc *ucom); + +static const struct usb2_config uvisor_config[UVISOR_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UVISOR_BUFSIZE, /* bytes */ + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &uvisor_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UVISOR_BUFSIZE, /* bytes */ + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &uvisor_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uvisor_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uvisor_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback uvisor_callback = { + .usb2_com_cfg_open = &uvisor_cfg_open, + .usb2_com_cfg_close = &uvisor_cfg_close, + .usb2_com_start_read = &uvisor_start_read, + .usb2_com_stop_read = &uvisor_stop_read, + .usb2_com_start_write = &uvisor_start_write, + .usb2_com_stop_write = &uvisor_stop_write, +}; + +static device_method_t uvisor_methods[] = { + DEVMETHOD(device_probe, uvisor_probe), + DEVMETHOD(device_attach, uvisor_attach), + DEVMETHOD(device_detach, uvisor_detach), + {0, 0} +}; + +static devclass_t uvisor_devclass; + +static driver_t uvisor_driver = { + .name = "uvisor", + .methods = uvisor_methods, + .size = sizeof(struct uvisor_softc), +}; + +DRIVER_MODULE(uvisor, ushub, uvisor_driver, uvisor_devclass, NULL, 0); +MODULE_DEPEND(uvisor, usb2_serial, 1, 1, 1); +MODULE_DEPEND(uvisor, usb2_core, 1, 1, 1); +MODULE_DEPEND(uvisor, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static const struct usb2_device_id uvisor_devs[] = { + {USB_VPI(USB_VENDOR_ACEECA, USB_PRODUCT_ACEECA_MEZ1000, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_GARMIN, USB_PRODUCT_GARMIN_IQUE_3600, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_FOSSIL, USB_PRODUCT_FOSSIL_WRISTPDA, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_VISOR, UVISOR_FLAG_VISOR)}, + {USB_VPI(USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_TREO, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_TREO600, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_M500, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_M505, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_M515, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_I705, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_M125, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_M130, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_TUNGSTEN_Z, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_TUNGSTEN_T, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_ZIRE, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_PALM, USB_PRODUCT_PALM_ZIRE31, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_SAMSUNG, USB_PRODUCT_SAMSUNG_I500, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_40, 0)}, + {USB_VPI(USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_41, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_S360, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_NX60, UVISOR_FLAG_PALM4)}, + {USB_VPI(USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_35, UVISOR_FLAG_PALM35)}, +/* {USB_VPI(USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_25, UVISOR_FLAG_PALM4 )}, */ + {USB_VPI(USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_TJ37, UVISOR_FLAG_PALM4)}, +/* {USB_VPI(USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_TH55, UVISOR_FLAG_PALM4 )}, See PR 80935 */ + {USB_VPI(USB_VENDOR_TAPWAVE, USB_PRODUCT_TAPWAVE_ZODIAC, UVISOR_FLAG_PALM4)}, +}; + +static int +uvisor_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UVISOR_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UVISOR_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(uvisor_devs, sizeof(uvisor_devs), uaa)); +} + +static int +uvisor_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uvisor_softc *sc = device_get_softc(dev); + struct usb2_config uvisor_config_copy[UVISOR_N_TRANSFER]; + int error; + + DPRINTF("sc=%p\n", sc); + bcopy(uvisor_config, uvisor_config_copy, + sizeof(uvisor_config_copy)); + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + sc->sc_udev = uaa->device; + + /* configure the device */ + + sc->sc_flag = USB_GET_DRIVER_INFO(uaa); + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UVISOR_IFACE_INDEX; + + error = uvisor_init(sc, uaa->device, uvisor_config_copy); + + if (error) { + DPRINTF("init failed, error=%s\n", + usb2_errstr(error)); + goto detach; + } + error = usb2_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, uvisor_config_copy, UVISOR_N_TRANSFER, + sc, &Giant); + if (error) { + DPRINTF("could not allocate all pipes\n"); + goto detach; + } + /* clear stall at first run */ + sc->sc_flag |= (UVISOR_FLAG_WRITE_STALL | + UVISOR_FLAG_READ_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uvisor_callback, &Giant); + if (error) { + DPRINTF("usb2_com_attach failed\n"); + goto detach; + } + return (0); + +detach: + uvisor_detach(dev); + return (ENXIO); +} + +static int +uvisor_detach(device_t dev) +{ + struct uvisor_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UVISOR_N_TRANSFER); + + return (0); +} + +static usb2_error_t +uvisor_init(struct uvisor_softc *sc, struct usb2_device *udev, struct usb2_config *config) +{ + usb2_error_t err = 0; + struct usb2_device_request req; + struct uvisor_connection_info coninfo; + struct uvisor_palm_connection_info pconinfo; + uint16_t actlen; + uWord wAvail; + uint8_t buffer[256]; + + if (sc->sc_flag & UVISOR_FLAG_VISOR) { + DPRINTF("getting connection info\n"); + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_CONNECTION_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); + err = usb2_do_request_flags + (udev, &Giant, &req, &coninfo, USB_SHORT_XFER_OK, + &actlen, USB_DEFAULT_TIMEOUT); + + if (err) { + goto done; + } + } +#if USB_DEBUG + if (sc->sc_flag & UVISOR_FLAG_VISOR) { + uint16_t i, np; + const char *desc; + + np = UGETW(coninfo.num_ports); + if (np > UVISOR_MAX_CONN) { + np = UVISOR_MAX_CONN; + } + DPRINTF("Number of ports: %d\n", np); + + for (i = 0; i < np; ++i) { + switch (coninfo.connections[i].port_function_id) { + case UVISOR_FUNCTION_GENERIC: + desc = "Generic"; + break; + case UVISOR_FUNCTION_DEBUGGER: + desc = "Debugger"; + break; + case UVISOR_FUNCTION_HOTSYNC: + desc = "HotSync"; + break; + case UVISOR_FUNCTION_REMOTE_FILE_SYS: + desc = "Remote File System"; + break; + default: + desc = "unknown"; + break; + } + DPRINTF("Port %d is for %s\n", + coninfo.connections[i].port, desc); + } + } +#endif + + if (sc->sc_flag & UVISOR_FLAG_PALM4) { + uint8_t port; + + /* Palm OS 4.0 Hack */ + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_PALM_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); + + err = usb2_do_request_flags + (udev, &Giant, &req, &pconinfo, USB_SHORT_XFER_OK, + &actlen, USB_DEFAULT_TIMEOUT); + + if (err) { + goto done; + } + if (actlen < 12) { + DPRINTF("too little data\n"); + err = USB_ERR_INVAL; + goto done; + } + if (pconinfo.endpoint_numbers_different) { + port = pconinfo.connections[0].end_point_info; + config[0].endpoint = (port & 0xF); /* output */ + config[1].endpoint = (port >> 4); /* input */ + } else { + port = pconinfo.connections[0].port; + config[0].endpoint = (port & 0xF); /* output */ + config[1].endpoint = (port & 0xF); /* input */ + } +#if 0 + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_PALM_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); + err = usb2_do_request(udev, &req, buffer); + if (err) { + goto done; + } +#endif + } + if (sc->sc_flag & UVISOR_FLAG_PALM35) { + /* get the config number */ + DPRINTF("getting config info\n"); + req.bmRequestType = UT_READ; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + + err = usb2_do_request(udev, &Giant, &req, buffer); + if (err) { + goto done; + } + /* get the interface number */ + DPRINTF("get the interface number\n"); + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + err = usb2_do_request(udev, &Giant, &req, buffer); + if (err) { + goto done; + } + } + DPRINTF("getting available bytes\n"); + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_REQUEST_BYTES_AVAILABLE; + USETW(req.wValue, 0); + USETW(req.wIndex, 5); + USETW(req.wLength, sizeof(wAvail)); + err = usb2_do_request(udev, &Giant, &req, &wAvail); + if (err) { + goto done; + } + DPRINTF("avail=%d\n", UGETW(wAvail)); + + DPRINTF("done\n"); +done: + return (err); +} + +static void +uvisor_cfg_open(struct usb2_com_softc *ucom) +{ + return; +} + +static void +uvisor_cfg_close(struct usb2_com_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + uint8_t buffer[UVISOR_CONNECTION_INFO_SIZE]; + struct usb2_device_request req; + usb2_error_t err; + + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; /* XXX read? */ + req.bRequest = UVISOR_CLOSE_NOTIFICATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); + + err = usb2_do_request_flags + (sc->sc_udev, &Giant, &req, &buffer, 0, NULL, 1000); + + if (err) { + DPRINTFN(0, "close notification failed, error=%s\n", + usb2_errstr(err)); + } + return; +} + +static void +uvisor_start_read(struct usb2_com_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +uvisor_stop_read(struct usb2_com_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +uvisor_start_write(struct usb2_com_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +uvisor_stop_write(struct usb2_com_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static void +uvisor_write_callback(struct usb2_xfer *xfer) +{ + struct uvisor_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flag & UVISOR_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UVISOR_BUFSIZE, &actlen)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UVISOR_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} + +static void +uvisor_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uvisor_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UVISOR_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uvisor_read_callback(struct usb2_xfer *xfer) +{ + struct uvisor_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flag & UVISOR_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UVISOR_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +uvisor_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uvisor_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UVISOR_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} diff --git a/sys/dev/usb2/serial/uvscom2.c b/sys/dev/usb2/serial/uvscom2.c new file mode 100644 index 000000000000..189b26b97357 --- /dev/null +++ b/sys/dev/usb2/serial/uvscom2.c @@ -0,0 +1,827 @@ +/* $NetBSD: usb/uvscom.c,v 1.1 2002/03/19 15:08:42 augustss Exp $ */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama . + * 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. + * + */ + +/* + * uvscom: SUNTAC Slipper U VS-10U driver. + * Slipper U is a PC Card to USB converter for data communication card + * adapter. It supports DDI Pocket's Air H" C@rd, C@rd H" 64, NTT's P-in, + * P-in m@ater and various data communication card adapters. + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR uvscom_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USB_DEBUG +static int uvscom_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uvscom, CTLFLAG_RW, 0, "USB uvscom"); +SYSCTL_INT(_hw_usb2_uvscom, OID_AUTO, debug, CTLFLAG_RW, + &uvscom_debug, 0, "Debug level"); +#endif + +#define UVSCOM_MODVER 1 /* module version */ + +#define UVSCOM_CONFIG_INDEX 0 +#define UVSCOM_IFACE_INDEX 0 + +/* Request */ +#define UVSCOM_SET_SPEED 0x10 +#define UVSCOM_LINE_CTL 0x11 +#define UVSCOM_SET_PARAM 0x12 +#define UVSCOM_READ_STATUS 0xd0 +#define UVSCOM_SHUTDOWN 0xe0 + +/* UVSCOM_SET_SPEED parameters */ +#define UVSCOM_SPEED_150BPS 0x00 +#define UVSCOM_SPEED_300BPS 0x01 +#define UVSCOM_SPEED_600BPS 0x02 +#define UVSCOM_SPEED_1200BPS 0x03 +#define UVSCOM_SPEED_2400BPS 0x04 +#define UVSCOM_SPEED_4800BPS 0x05 +#define UVSCOM_SPEED_9600BPS 0x06 +#define UVSCOM_SPEED_19200BPS 0x07 +#define UVSCOM_SPEED_38400BPS 0x08 +#define UVSCOM_SPEED_57600BPS 0x09 +#define UVSCOM_SPEED_115200BPS 0x0a + +/* UVSCOM_LINE_CTL parameters */ +#define UVSCOM_BREAK 0x40 +#define UVSCOM_RTS 0x02 +#define UVSCOM_DTR 0x01 +#define UVSCOM_LINE_INIT 0x08 + +/* UVSCOM_SET_PARAM parameters */ +#define UVSCOM_DATA_MASK 0x03 +#define UVSCOM_DATA_BIT_8 0x03 +#define UVSCOM_DATA_BIT_7 0x02 +#define UVSCOM_DATA_BIT_6 0x01 +#define UVSCOM_DATA_BIT_5 0x00 + +#define UVSCOM_STOP_MASK 0x04 +#define UVSCOM_STOP_BIT_2 0x04 +#define UVSCOM_STOP_BIT_1 0x00 + +#define UVSCOM_PARITY_MASK 0x18 +#define UVSCOM_PARITY_EVEN 0x18 +#define UVSCOM_PARITY_ODD 0x08 +#define UVSCOM_PARITY_NONE 0x00 + +/* Status bits */ +#define UVSCOM_TXRDY 0x04 +#define UVSCOM_RXRDY 0x01 + +#define UVSCOM_DCD 0x08 +#define UVSCOM_NOCARD 0x04 +#define UVSCOM_DSR 0x02 +#define UVSCOM_CTS 0x01 +#define UVSCOM_USTAT_MASK (UVSCOM_NOCARD | UVSCOM_DSR | UVSCOM_CTS) + +#define UVSCOM_BULK_BUF_SIZE 1024 /* bytes */ + +#define UVSCOM_N_TRANSFER 6 /* units */ + +struct uvscom_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_xfer *sc_xfer[UVSCOM_N_TRANSFER]; + struct usb2_device *sc_udev; + + uint16_t sc_line; /* line control register */ + + uint8_t sc_flag; +#define UVSCOM_FLAG_WRITE_STALL 0x0001 +#define UVSCOM_FLAG_READ_STALL 0x0002 +#define UVSCOM_FLAG_INTR_STALL 0x0004 + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_iface_index; /* interface index */ + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* uvscom status register */ + uint8_t sc_unit_status; /* unit status */ +}; + +/* prototypes */ + +static device_probe_t uvscom_probe; +static device_attach_t uvscom_attach; +static device_detach_t uvscom_detach; + +static usb2_callback_t uvscom_write_callback; +static usb2_callback_t uvscom_write_clear_stall_callback; +static usb2_callback_t uvscom_read_callback; +static usb2_callback_t uvscom_read_clear_stall_callback; +static usb2_callback_t uvscom_intr_callback; +static usb2_callback_t uvscom_intr_clear_stall_callback; + +static void uvscom_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff); +static void uvscom_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff); +static void uvscom_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff); +static int uvscom_pre_param(struct usb2_com_softc *ucom, struct termios *t); +static void uvscom_cfg_param(struct usb2_com_softc *ucom, struct termios *t); +static int uvscom_pre_open(struct usb2_com_softc *ucom); +static void uvscom_cfg_open(struct usb2_com_softc *ucom); +static void uvscom_cfg_close(struct usb2_com_softc *ucom); +static void uvscom_start_read(struct usb2_com_softc *ucom); +static void uvscom_stop_read(struct usb2_com_softc *ucom); +static void uvscom_start_write(struct usb2_com_softc *ucom); +static void uvscom_stop_write(struct usb2_com_softc *ucom); +static void uvscom_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr); +static int uvscom_ioctl(struct usb2_com_softc *ucom, uint32_t cmd, caddr_t data, int fflag, struct thread *td); +static void uvscom_cfg_write(struct uvscom_softc *sc, uint8_t index, uint16_t value); +static uint16_t uvscom_cfg_read_status(struct uvscom_softc *sc); + +static const struct usb2_config uvscom_config[UVSCOM_N_TRANSFER] = { + + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UVSCOM_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &uvscom_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UVSCOM_BULK_BUF_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &uvscom_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uvscom_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uvscom_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [4] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.bufsize = 0, /* use wMaxPacketSize */ + .mh.callback = &uvscom_intr_callback, + }, + + [5] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &uvscom_intr_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static const struct usb2_com_callback uvscom_callback = { + .usb2_com_cfg_get_status = &uvscom_cfg_get_status, + .usb2_com_cfg_set_dtr = &uvscom_cfg_set_dtr, + .usb2_com_cfg_set_rts = &uvscom_cfg_set_rts, + .usb2_com_cfg_set_break = &uvscom_cfg_set_break, + .usb2_com_cfg_param = &uvscom_cfg_param, + .usb2_com_cfg_open = &uvscom_cfg_open, + .usb2_com_cfg_close = &uvscom_cfg_close, + .usb2_com_pre_open = &uvscom_pre_open, + .usb2_com_pre_param = &uvscom_pre_param, + .usb2_com_ioctl = &uvscom_ioctl, + .usb2_com_start_read = &uvscom_start_read, + .usb2_com_stop_read = &uvscom_stop_read, + .usb2_com_start_write = &uvscom_start_write, + .usb2_com_stop_write = &uvscom_stop_write, +}; + +static const struct usb2_device_id uvscom_devs[] = { + /* SUNTAC U-Cable type A4 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_AS144L4, 0)}, + /* SUNTAC U-Cable type D2 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_DS96L, 0)}, + /* SUNTAC Ir-Trinity */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_IS96U, 0)}, + /* SUNTAC U-Cable type P1 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_PS64P1, 0)}, + /* SUNTAC Slipper U */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_VS10U, 0)}, +}; + +static device_method_t uvscom_methods[] = { + DEVMETHOD(device_probe, uvscom_probe), + DEVMETHOD(device_attach, uvscom_attach), + DEVMETHOD(device_detach, uvscom_detach), + {0, 0} +}; + +static devclass_t uvscom_devclass; + +static driver_t uvscom_driver = { + .name = "uvscom", + .methods = uvscom_methods, + .size = sizeof(struct uvscom_softc), +}; + +DRIVER_MODULE(uvscom, ushub, uvscom_driver, uvscom_devclass, NULL, 0); +MODULE_DEPEND(uvscom, usb2_serial, 1, 1, 1); +MODULE_DEPEND(uvscom, usb2_core, 1, 1, 1); +MODULE_DEPEND(uvscom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(uvscom, UVSCOM_MODVER); + +static int +uvscom_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UVSCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UVSCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(uvscom_devs, sizeof(uvscom_devs), uaa)); +} + +static int +uvscom_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uvscom_softc *sc = device_get_softc(dev); + int error; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + sc->sc_udev = uaa->device; + + DPRINTF("sc=%p\n", sc); + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UVSCOM_IFACE_INDEX; + + error = usb2_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, uvscom_config, UVSCOM_N_TRANSFER, sc, &Giant); + + if (error) { + DPRINTF("could not allocate all USB transfers!\n"); + goto detach; + } + sc->sc_line = UVSCOM_LINE_INIT; + + /* clear stall at first run */ + sc->sc_flag |= (UVSCOM_FLAG_WRITE_STALL | + UVSCOM_FLAG_READ_STALL); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uvscom_callback, &Giant); + if (error) { + goto detach; + } + /* start interrupt pipe */ + mtx_lock(sc->sc_xfer[4]->priv_mtx); + usb2_transfer_start(sc->sc_xfer[4]); + mtx_unlock(sc->sc_xfer[4]->priv_mtx); + + return (0); + +detach: + uvscom_detach(dev); + return (ENXIO); +} + +static int +uvscom_detach(device_t dev) +{ + struct uvscom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + /* stop interrupt pipe */ + + if (sc->sc_xfer[4]) { + usb2_transfer_stop(sc->sc_xfer[4]); + } + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UVSCOM_N_TRANSFER); + + return (0); +} + +static void +uvscom_write_callback(struct usb2_xfer *xfer) +{ + struct uvscom_softc *sc = xfer->priv_sc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + if (sc->sc_flag & UVSCOM_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + return; + } + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, 0, + UVSCOM_BULK_BUF_SIZE, &actlen)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UVSCOM_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + return; + + } +} + +static void +uvscom_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uvscom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UVSCOM_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uvscom_read_callback(struct usb2_xfer *xfer) +{ + struct uvscom_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 0, xfer->actlen); + + case USB_ST_SETUP: + if (sc->sc_flag & UVSCOM_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UVSCOM_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +uvscom_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uvscom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UVSCOM_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uvscom_intr_callback(struct usb2_xfer *xfer) +{ + struct uvscom_softc *sc = xfer->priv_sc; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen >= 2) { + + usb2_copy_out(xfer->frbuffers, 0, buf, sizeof(buf)); + + sc->sc_lsr = 0; + sc->sc_msr = 0; + sc->sc_unit_status = buf[1]; + + if (buf[0] & UVSCOM_TXRDY) { + sc->sc_lsr |= ULSR_TXRDY; + } + if (buf[0] & UVSCOM_RXRDY) { + sc->sc_lsr |= ULSR_RXRDY; + } + if (buf[1] & UVSCOM_CTS) { + sc->sc_msr |= SER_CTS; + } + if (buf[1] & UVSCOM_DSR) { + sc->sc_msr |= SER_DSR; + } + if (buf[1] & UVSCOM_DCD) { + sc->sc_msr |= SER_DCD; + } + /* + * the UCOM layer will ignore this call if the TTY + * device is closed! + */ + usb2_com_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: + if (sc->sc_flag & UVSCOM_FLAG_INTR_STALL) { + usb2_transfer_start(sc->sc_xfer[5]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + sc->sc_flag |= UVSCOM_FLAG_INTR_STALL; + usb2_transfer_start(sc->sc_xfer[5]); + } + return; + + } +} + +static void +uvscom_intr_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct uvscom_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[4]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flag &= ~UVSCOM_FLAG_INTR_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +uvscom_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_DTR; + else + sc->sc_line &= ~UVSCOM_DTR; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); + return; +} + +static void +uvscom_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_RTS; + else + sc->sc_line &= ~UVSCOM_RTS; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); + return; +} + +static void +uvscom_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_BREAK; + else + sc->sc_line &= ~UVSCOM_BREAK; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); + return; +} + +static int +uvscom_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + switch (t->c_ospeed) { + case B150: + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + default: + return (EINVAL); + } + return (0); +} + +static void +uvscom_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uvscom_softc *sc = ucom->sc_parent; + uint16_t value; + + DPRINTF("\n"); + + switch (t->c_ospeed) { + case B150: + value = UVSCOM_SPEED_150BPS; + break; + case B300: + value = UVSCOM_SPEED_300BPS; + break; + case B600: + value = UVSCOM_SPEED_600BPS; + break; + case B1200: + value = UVSCOM_SPEED_1200BPS; + break; + case B2400: + value = UVSCOM_SPEED_2400BPS; + break; + case B4800: + value = UVSCOM_SPEED_4800BPS; + break; + case B9600: + value = UVSCOM_SPEED_9600BPS; + break; + case B19200: + value = UVSCOM_SPEED_19200BPS; + break; + case B38400: + value = UVSCOM_SPEED_38400BPS; + break; + case B57600: + value = UVSCOM_SPEED_57600BPS; + break; + case B115200: + value = UVSCOM_SPEED_115200BPS; + break; + default: + return; + } + + uvscom_cfg_write(sc, UVSCOM_SET_SPEED, value); + + value = 0; + + if (t->c_cflag & CSTOPB) { + value |= UVSCOM_STOP_BIT_2; + } + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + value |= UVSCOM_PARITY_ODD; + } else { + value |= UVSCOM_PARITY_EVEN; + } + } else { + value |= UVSCOM_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + value |= UVSCOM_DATA_BIT_5; + break; + case CS6: + value |= UVSCOM_DATA_BIT_6; + break; + case CS7: + value |= UVSCOM_DATA_BIT_7; + break; + default: + case CS8: + value |= UVSCOM_DATA_BIT_8; + break; + } + + uvscom_cfg_write(sc, UVSCOM_SET_PARAM, value); + return; +} + +static int +uvscom_pre_open(struct usb2_com_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc = %p\n", sc); + + /* check if PC card was inserted */ + + if (sc->sc_unit_status & UVSCOM_NOCARD) { + DPRINTF("no PC card!\n"); + return (ENXIO); + } + return (0); +} + +static void +uvscom_cfg_open(struct usb2_com_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc = %p\n", sc); + + uvscom_cfg_read_status(sc); + + return; +} + +static void +uvscom_cfg_close(struct usb2_com_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc=%p\n", sc); + + uvscom_cfg_write(sc, UVSCOM_SHUTDOWN, 0); + + return; +} + +static void +uvscom_start_read(struct usb2_com_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[1]); + return; +} + +static void +uvscom_stop_read(struct usb2_com_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[3]); + usb2_transfer_stop(sc->sc_xfer[1]); + return; +} + +static void +uvscom_start_write(struct usb2_com_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +uvscom_stop_write(struct usb2_com_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[0]); + return; +} + +static void +uvscom_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; + return; +} + +static int +uvscom_ioctl(struct usb2_com_softc *ucom, uint32_t cmd, caddr_t data, int fflag, + struct thread *td) +{ + return (ENOTTY); +} + +static void +uvscom_cfg_write(struct uvscom_softc *sc, uint8_t index, uint16_t value) +{ + struct usb2_device_request req; + usb2_error_t err; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + return; + } + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = index; + USETW(req.wValue, value); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = usb2_do_request_flags(sc->sc_udev, &Giant, &req, + NULL, 0, NULL, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + } + return; +} + +static uint16_t +uvscom_cfg_read_status(struct uvscom_softc *sc) +{ + struct usb2_device_request req; + usb2_error_t err; + uint8_t data[2]; + + if (usb2_com_cfg_is_gone(&sc->sc_ucom)) { + return (0); + } + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UVSCOM_READ_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 2); + + err = usb2_do_request_flags(sc->sc_udev, &Giant, &req, + data, 0, NULL, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + data[0] = 0; + data[1] = 0; + } + return (data[0] | (data[1] << 8)); +} diff --git a/sys/dev/usb2/sound/uaudio2.c b/sys/dev/usb2/sound/uaudio2.c new file mode 100644 index 000000000000..d6e3878f8de4 --- /dev/null +++ b/sys/dev/usb2/sound/uaudio2.c @@ -0,0 +1,3786 @@ +/* $NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +/* + * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf + * http://www.usb.org/developers/devclass_docs/frmts10.pdf + * http://www.usb.org/developers/devclass_docs/termt10.pdf + */ + +/* + * Also merged: + * $NetBSD: uaudio.c,v 1.94 2005/01/15 15:19:53 kent Exp $ + * $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $ + * $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $ + * $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $ + */ + +#include +#include +#include +#include + +#define USB_DEBUG_VAR uaudio_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include /* for bootverbose */ + +#include +#include +#include "feeder_if.h" + +#if USB_DEBUG +static int uaudio_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uaudio, CTLFLAG_RW, 0, "USB uaudio"); +SYSCTL_INT(_hw_usb2_uaudio, OID_AUTO, debug, CTLFLAG_RW, + &uaudio_debug, 0, "uaudio debug level"); +#endif + +static uint32_t uaudio_default_rate = 96000; +static uint8_t uaudio_default_bits = 32; +static uint8_t uaudio_default_channels = 2; + +#define UAUDIO_NCHANBUFS 2 /* number of outstanding request */ +#define UAUDIO_NFRAMES 25 /* ms of sound in each request */ +#define UAUDIO_RECURSE_LIMIT 24 /* rounds */ +#define UAUDIO_DEFAULT_BUFSZ ((2 * 96000 * 4 * 2) / (1000 / UAUDIO_NCHANBUFS)) /* bytes */ + +#define MAKE_WORD(h,l) (((h) << 8) | (l)) +#define BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1) + +struct uaudio_mixer_node { + int32_t minval; + int32_t maxval; +#define MIX_MAX_CHAN 8 + int32_t wValue[MIX_MAX_CHAN]; /* using nchan */ + uint32_t delta; + uint32_t mul; + uint32_t ctl; + + uint16_t wData[MIX_MAX_CHAN]; /* using nchan */ + uint16_t wIndex; + + uint8_t update[(MIX_MAX_CHAN + 7) / 8]; + uint8_t nchan; + uint8_t type; +#define MIX_ON_OFF 1 +#define MIX_SIGNED_16 2 +#define MIX_UNSIGNED_16 3 +#define MIX_SIGNED_8 4 +#define MIX_SELECTOR 5 +#define MIX_UNKNOWN 6 +#define MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \ + ((n) == MIX_UNSIGNED_16)) ? 2 : 1) +#define MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16) + +#define MAX_SELECTOR_INPUT_PIN 256 + uint8_t slctrtype[MAX_SELECTOR_INPUT_PIN]; + uint8_t class; + + struct uaudio_mixer_node *next; +}; + +struct uaudio_chan { + struct pcmchan_caps pcm_cap; /* capabilities */ + + struct snd_dbuf *pcm_buf; + const struct usb2_config *usb2_cfg; + struct mtx *pcm_mtx; /* lock protecting this structure */ + struct uaudio_softc *priv_sc; + struct pcm_channel *pcm_ch; + struct usb2_xfer *xfer[UAUDIO_NCHANBUFS]; + const struct usb2_audio_streaming_interface_descriptor *p_asid; + const struct usb2_audio_streaming_type1_descriptor *p_asf1d; + const struct usb2_audio_streaming_endpoint_descriptor *p_sed; + const usb2_endpoint_descriptor_audio_t *p_ed1; + const usb2_endpoint_descriptor_audio_t *p_ed2; + const struct uaudio_format *p_fmt; + + uint8_t *buf; /* pointer to buffer */ + uint8_t *start; /* upper layer buffer start */ + uint8_t *end; /* upper layer buffer end */ + uint8_t *cur; /* current position in upper layer + * buffer */ + + uint32_t block_size; + uint32_t sample_rate; + uint32_t format; + uint32_t pcm_format[2]; + + uint16_t bytes_per_frame; + + uint8_t valid; + uint8_t iface_index; + uint8_t iface_alt_index; +}; + +#define UMIDI_N_TRANSFER 4 /* units */ +#define UMIDI_CABLES_MAX 16 /* units */ +#define UMIDI_BULK_SIZE 1024 /* bytes */ + +struct umidi_sub_chan { + struct usb2_fifo_sc fifo; + uint8_t *temp_cmd; + uint8_t temp_0[4]; + uint8_t temp_1[4]; + uint8_t state; +#define UMIDI_ST_UNKNOWN 0 /* scan for command */ +#define UMIDI_ST_1PARAM 1 +#define UMIDI_ST_2PARAM_1 2 +#define UMIDI_ST_2PARAM_2 3 +#define UMIDI_ST_SYSEX_0 4 +#define UMIDI_ST_SYSEX_1 5 +#define UMIDI_ST_SYSEX_2 6 + + uint8_t read_open:1; + uint8_t write_open:1; + uint8_t unused:6; +}; + +struct umidi_chan { + + struct umidi_sub_chan sub[UMIDI_CABLES_MAX]; + struct mtx mtx; + + struct usb2_xfer *xfer[UMIDI_N_TRANSFER]; + + uint8_t iface_index; + uint8_t iface_alt_index; + + uint8_t flags; +#define UMIDI_FLAG_READ_STALL 0x01 +#define UMIDI_FLAG_WRITE_STALL 0x02 + + uint8_t read_open_refcount; + uint8_t write_open_refcount; + + uint8_t curr_cable; + uint8_t max_cable; + uint8_t valid; +}; + +struct uaudio_softc { + struct sbuf sc_sndstat; + struct sndcard_func sc_sndcard_func; + struct uaudio_chan sc_rec_chan; + struct uaudio_chan sc_play_chan; + struct umidi_chan sc_midi_chan; + + struct usb2_device *sc_udev; + struct usb2_xfer *sc_mixer_xfer[1]; + struct uaudio_mixer_node *sc_mixer_root; + struct uaudio_mixer_node *sc_mixer_curr; + + uint32_t sc_mix_info; + uint32_t sc_recsrc_info; + + uint16_t sc_audio_rev; + uint16_t sc_mixer_count; + + uint8_t sc_sndstat_valid; + uint8_t sc_mixer_iface_index; + uint8_t sc_mixer_iface_no; + uint8_t sc_mixer_chan; + uint8_t sc_pcm_registered:1; + uint8_t sc_mixer_init:1; + uint8_t sc_uq_audio_swap_lr:1; + uint8_t sc_uq_au_inp_async:1; + uint8_t sc_uq_au_no_xu:1; + uint8_t sc_uq_bad_adc:1; +}; + +struct uaudio_search_result { + uint8_t bit_input[(256 + 7) / 8]; + uint8_t bit_output[(256 + 7) / 8]; + uint8_t bit_visited[(256 + 7) / 8]; + uint8_t recurse_level; + uint8_t id_max; +}; + +struct uaudio_terminal_node { + union { + const struct usb2_descriptor *desc; + const struct usb2_audio_input_terminal *it; + const struct usb2_audio_output_terminal *ot; + const struct usb2_audio_mixer_unit_0 *mu; + const struct usb2_audio_selector_unit *su; + const struct usb2_audio_feature_unit *fu; + const struct usb2_audio_processing_unit_0 *pu; + const struct usb2_audio_extension_unit_0 *eu; + } u; + struct uaudio_search_result usr; + struct uaudio_terminal_node *root; +}; + +struct uaudio_format { + uint16_t wFormat; + uint8_t bPrecision; + uint32_t freebsd_fmt; + const char *description; +}; + +static const struct uaudio_format uaudio_formats[] = { + + {UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, + {UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, + {UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, + {UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, + + {UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, + {UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, + {UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, + {UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, + + {UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, + {UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, + + {0, 0, 0, NULL} +}; + +#define UAC_OUTPUT 0 +#define UAC_INPUT 1 +#define UAC_EQUAL 2 +#define UAC_RECORD 3 +#define UAC_NCLASSES 4 + +#if USB_DEBUG +static const char *uac_names[] = { + "outputs", "inputs", "equalization", "record" +}; + +#endif + +/* prototypes */ + +static device_probe_t uaudio_probe; +static device_attach_t uaudio_attach; +static device_detach_t uaudio_detach; + +static usb2_callback_t uaudio_chan_play_callback; +static usb2_callback_t uaudio_chan_record_callback; +static usb2_callback_t uaudio_mixer_write_cfg_callback; +static usb2_callback_t umidi_read_clear_stall_callback; +static usb2_callback_t umidi_bulk_read_callback; +static usb2_callback_t umidi_write_clear_stall_callback; +static usb2_callback_t umidi_bulk_write_callback; + +#if USB_DEBUG +static void uaudio_chan_dump_ep_desc(const usb2_endpoint_descriptor_audio_t *ed); + +#endif + +static void uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb2_device *udev, uint32_t rate, uint16_t fps, uint8_t channels, uint8_t bit_resolution); +static void uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb2_device *udev); +static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc); +static void uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc); +static void uaudio_mixer_add_input(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id); +static void uaudio_mixer_add_output(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id); +static void uaudio_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id); +static void uaudio_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id); +static uint32_t uaudio_mixer_feature_get_bmaControls(const struct usb2_audio_feature_unit *d, uint8_t index); +static void uaudio_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id); +static void uaudio_mixer_add_processing_updown(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id); +static void uaudio_mixer_add_processing(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id); +static void uaudio_mixer_add_extension(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id); +static const void *uaudio_mixer_verify_desc(const void *arg, uint32_t len); + +#if USB_DEBUG +static void uaudio_mixer_dump_cluster(uint8_t id, const struct uaudio_terminal_node *iot); + +#endif + +static struct usb2_audio_cluster uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot); + +#if USB_DEBUG +static const char *uaudio_mixer_get_terminal_name(uint16_t terminal_type); + +#endif + +static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix); +static uint16_t uaudio_mixer_feature_name(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix); +static const struct uaudio_terminal_node *uaudio_mixer_get_input(const struct uaudio_terminal_node *iot, uint8_t index); +static const struct uaudio_terminal_node *uaudio_mixer_get_output(const struct uaudio_terminal_node *iot, uint8_t index); +static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info); +static void uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *root, uint8_t id, uint8_t n_id, struct uaudio_search_result *info); +static void uaudio_mixer_fill_info(struct uaudio_softc *sc, struct usb2_device *udev, void *desc); +static uint16_t uaudio_mixer_get(struct usb2_device *udev, uint8_t what, struct uaudio_mixer_node *mc); +static usb2_error_t uaudio_set_speed(struct usb2_device *udev, uint8_t endpt, uint32_t speed); +static int uaudio_mixer_signext(uint8_t type, int val); +static int uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int32_t val); + +static void uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc, uint8_t chan, int32_t val); +static void uaudio_mixer_init(struct uaudio_softc *sc); +static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b); +static struct umidi_sub_chan *umidi_sub_by_fifo(struct usb2_fifo *fifo); +static void umidi_start_read(struct usb2_fifo *fifo); +static void umidi_stop_read(struct usb2_fifo *fifo); +static void umidi_start_write(struct usb2_fifo *fifo); +static void umidi_stop_write(struct usb2_fifo *fifo); +static int umidi_open(struct usb2_fifo *fifo, int fflags, struct thread *td); +static int umidi_ioctl(struct usb2_fifo *fifo, u_long cmd, void *data, int fflags, struct thread *td); +static void umidi_close(struct usb2_fifo *fifo, int fflags, struct thread *td); +static void umidi_init(device_t dev); +static int32_t umidi_probe(device_t dev); +static int32_t umidi_detach(device_t dev); + +static const struct usb2_config + uaudio_cfg_record_full_speed[UAUDIO_NCHANBUFS] = { + [0] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UAUDIO_NFRAMES, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &uaudio_chan_record_callback, + }, + + [1] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UAUDIO_NFRAMES, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &uaudio_chan_record_callback, + }, +}; + +static const struct usb2_config + uaudio_cfg_record_high_speed[UAUDIO_NCHANBUFS] = { + [0] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = (UAUDIO_NFRAMES * 8), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &uaudio_chan_record_callback, + }, + + [1] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = (UAUDIO_NFRAMES * 8), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &uaudio_chan_record_callback, + }, +}; + +static const struct usb2_config + uaudio_cfg_play_full_speed[UAUDIO_NCHANBUFS] = { + [0] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UAUDIO_NFRAMES, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &uaudio_chan_play_callback, + }, + + [1] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = UAUDIO_NFRAMES, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &uaudio_chan_play_callback, + }, +}; + +static const struct usb2_config + uaudio_cfg_play_high_speed[UAUDIO_NCHANBUFS] = { + [0] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = (UAUDIO_NFRAMES * 8), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &uaudio_chan_play_callback, + }, + + [1] = { + .type = UE_ISOCHRONOUS, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = 0, /* use "wMaxPacketSize * frames" */ + .mh.frames = (UAUDIO_NFRAMES * 8), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &uaudio_chan_play_callback, + }, +}; + +static const struct usb2_config + uaudio_mixer_config[1] = { + [0] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = (sizeof(struct usb2_device_request) + 4), + .mh.callback = &uaudio_mixer_write_cfg_callback, + .mh.timeout = 1000, /* 1 second */ + }, +}; + +static const +uint8_t umidi_cmd_to_len[16] = { + [0x0] = 0, /* reserved */ + [0x1] = 0, /* reserved */ + [0x2] = 2, /* bytes */ + [0x3] = 3, /* bytes */ + [0x4] = 3, /* bytes */ + [0x5] = 1, /* bytes */ + [0x6] = 2, /* bytes */ + [0x7] = 3, /* bytes */ + [0x8] = 3, /* bytes */ + [0x9] = 3, /* bytes */ + [0xA] = 3, /* bytes */ + [0xB] = 3, /* bytes */ + [0xC] = 2, /* bytes */ + [0xD] = 2, /* bytes */ + [0xE] = 3, /* bytes */ + [0xF] = 1, /* bytes */ +}; + +static const struct usb2_config + umidi_config[UMIDI_N_TRANSFER] = { + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UMIDI_BULK_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &umidi_bulk_write_callback, + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UMIDI_BULK_SIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &umidi_bulk_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umidi_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umidi_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static devclass_t uaudio_devclass; + +static device_method_t uaudio_methods[] = { + DEVMETHOD(device_probe, uaudio_probe), + DEVMETHOD(device_attach, uaudio_attach), + DEVMETHOD(device_detach, uaudio_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(bus_print_child, bus_generic_print_child), + {0, 0} +}; + +static driver_t uaudio_driver = { + .name = "uaudio", + .methods = uaudio_methods, + .size = sizeof(struct uaudio_softc), +}; + +static int +uaudio_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* trigger on the control interface */ + + if ((uaa->info.bInterfaceClass == UICLASS_AUDIO) && + (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL)) { + if (usb2_test_quirk(uaa, UQ_BAD_AUDIO)) + return (ENXIO); + else + return (0); + } + return (ENXIO); +} + +static int +uaudio_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uaudio_softc *sc = device_get_softc(dev); + struct usb2_interface_descriptor *id; + device_t child; + + if (sc == NULL) { + return (ENOMEM); + } + sc->sc_play_chan.priv_sc = sc; + sc->sc_rec_chan.priv_sc = sc; + sc->sc_udev = uaa->device; + + if (usb2_test_quirk(uaa, UQ_AUDIO_SWAP_LR)) + sc->sc_uq_audio_swap_lr = 1; + + if (usb2_test_quirk(uaa, UQ_AU_INP_ASYNC)) + sc->sc_uq_au_inp_async = 1; + + if (usb2_test_quirk(uaa, UQ_AU_NO_XU)) + sc->sc_uq_au_no_xu = 1; + + if (usb2_test_quirk(uaa, UQ_BAD_ADC)) + sc->sc_uq_bad_adc = 1; + + umidi_init(dev); + + device_set_usb2_desc(dev); + + id = usb2_get_interface_descriptor(uaa->iface); + + uaudio_chan_fill_info(sc, uaa->device); + + uaudio_mixer_fill_info(sc, uaa->device, id); + + sc->sc_mixer_iface_index = uaa->info.bIfaceIndex; + sc->sc_mixer_iface_no = uaa->info.bIfaceNum; + + DPRINTF("audio rev %d.%02x\n", + sc->sc_audio_rev >> 8, + sc->sc_audio_rev & 0xff); + + DPRINTF("%d mixer controls\n", + sc->sc_mixer_count); + + if (sc->sc_play_chan.valid) { + device_printf(dev, "Play: %d Hz, %d ch, %s format\n", + sc->sc_play_chan.sample_rate, + sc->sc_play_chan.p_asf1d->bNrChannels, + sc->sc_play_chan.p_fmt->description); + } else { + device_printf(dev, "No playback!\n"); + } + + if (sc->sc_rec_chan.valid) { + device_printf(dev, "Record: %d Hz, %d ch, %s format\n", + sc->sc_rec_chan.sample_rate, + sc->sc_rec_chan.p_asf1d->bNrChannels, + sc->sc_rec_chan.p_fmt->description); + } else { + device_printf(dev, "No recording!\n"); + } + + if (sc->sc_midi_chan.valid) { + + if (umidi_probe(dev)) { + goto detach; + } + device_printf(dev, "MIDI sequencer\n"); + } else { + device_printf(dev, "No midi sequencer\n"); + } + + DPRINTF("doing child attach\n"); + + /* attach the children */ + + sc->sc_sndcard_func.func = SCF_PCM; + + child = device_add_child(dev, "pcm", -1); + + if (child == NULL) { + DPRINTF("out of memory\n"); + goto detach; + } + device_set_ivars(child, &sc->sc_sndcard_func); + + if (bus_generic_attach(dev)) { + DPRINTF("child attach failed\n"); + goto detach; + } + return (0); /* success */ + +detach: + uaudio_detach(dev); + return (ENXIO); +} + +static void +uaudio_pcm_setflags(device_t dev, uint32_t flags) +{ + pcm_setflags(dev, pcm_getflags(dev) | flags); +} + +int +uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class) +{ + struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); + char status[SND_STATUSLEN]; + + if (bootverbose) { + device_printf(dev, "using a default buffer " + "size of %u bytes\n", UAUDIO_DEFAULT_BUFSZ); + } + uaudio_mixer_init(sc); + + if (sc->sc_uq_audio_swap_lr) { + DPRINTF("hardware has swapped left and right\n"); + uaudio_pcm_setflags(dev, SD_F_PSWAPLR); + } + if (!(sc->sc_mix_info & SOUND_MASK_PCM)) { + + DPRINTF("emulating master volume\n"); + + /* + * Emulate missing pcm mixer controller + * through FEEDER_VOLUME + */ + uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL); + } + if (mixer_init(dev, mixer_class, sc)) { + goto detach; + } + sc->sc_mixer_init = 1; + + snprintf(status, sizeof(status), "at ? %s", PCM_KLDSTRING(snd_uaudio)); + + if (pcm_register(dev, sc, + sc->sc_play_chan.valid ? 1 : 0, + sc->sc_rec_chan.valid ? 1 : 0)) { + goto detach; + } + sc->sc_pcm_registered = 1; + + if (sc->sc_play_chan.valid) { + pcm_addchan(dev, PCMDIR_PLAY, chan_class, sc); + } + if (sc->sc_rec_chan.valid) { + pcm_addchan(dev, PCMDIR_REC, chan_class, sc); + } + pcm_setstatus(dev, status); + + return (0); /* success */ + +detach: + uaudio_detach_sub(dev); + return (ENXIO); +} + +int +uaudio_detach_sub(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); + int error = 0; + +repeat: + if (sc->sc_pcm_registered) { + error = pcm_unregister(dev); + } else { + if (sc->sc_mixer_init) { + error = mixer_uninit(dev); + } + } + + if (error) { + device_printf(dev, "Waiting for sound application to exit!\n"); + mtx_lock(&Giant); + usb2_pause_mtx(&Giant, 2000); + mtx_unlock(&Giant); + goto repeat; /* try again */ + } + return (0); /* success */ +} + +static int +uaudio_detach(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (bus_generic_detach(dev)) { + DPRINTF("detach failed!\n"); + } + sbuf_delete(&sc->sc_sndstat); + sc->sc_sndstat_valid = 0; + + umidi_detach(dev); + + return (0); +} + +/*========================================================================* + * AS - Audio Stream - routines + *========================================================================*/ + +#if USB_DEBUG +static void +uaudio_chan_dump_ep_desc(const usb2_endpoint_descriptor_audio_t *ed) +{ + if (ed) { + DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n" + "bEndpointAddress=%d bmAttributes=0x%x \n" + "wMaxPacketSize=%d bInterval=%d \n" + "bRefresh=%d bSynchAddress=%d\n", + ed, ed->bLength, ed->bDescriptorType, + ed->bEndpointAddress, ed->bmAttributes, + UGETW(ed->wMaxPacketSize), ed->bInterval, + ed->bRefresh, ed->bSynchAddress); + } + return; +} + +#endif + +static void +uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb2_device *udev, + uint32_t rate, uint16_t fps, uint8_t channels, + uint8_t bit_resolution) +{ + struct usb2_descriptor *desc = NULL; + const struct usb2_audio_streaming_interface_descriptor *asid = NULL; + const struct usb2_audio_streaming_type1_descriptor *asf1d = NULL; + const struct usb2_audio_streaming_endpoint_descriptor *sed = NULL; + const usb2_endpoint_descriptor_audio_t *ed1 = NULL; + const usb2_endpoint_descriptor_audio_t *ed2 = NULL; + struct usb2_config_descriptor *cd = usb2_get_config_descriptor(udev); + struct usb2_interface_descriptor *id; + const struct uaudio_format *p_fmt; + struct uaudio_chan *chan; + uint16_t curidx = 0xFFFF; + uint16_t lastidx = 0xFFFF; + uint16_t alt_index = 0; + uint16_t wFormat; + uint8_t ep_dir; + uint8_t ep_type; + uint8_t ep_sync; + uint8_t bChannels; + uint8_t bBitResolution; + uint8_t x; + uint8_t audio_if = 0; + uint8_t sample_size; + + while ((desc = usb2_desc_foreach(cd, desc))) { + + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + + id = (void *)desc; + + if (id->bInterfaceNumber != lastidx) { + lastidx = id->bInterfaceNumber; + curidx++; + alt_index = 0; + + } else { + alt_index++; + } + + if ((id->bInterfaceClass == UICLASS_AUDIO) && + (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) { + audio_if = 1; + } else { + audio_if = 0; + } + + if ((id->bInterfaceClass == UICLASS_AUDIO) && + (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) { + + /* + * XXX could allow multiple MIDI interfaces + * XXX + */ + + if ((sc->sc_midi_chan.valid == 0) && + usb2_get_iface(udev, curidx)) { + sc->sc_midi_chan.iface_index = curidx; + sc->sc_midi_chan.iface_alt_index = alt_index; + sc->sc_midi_chan.valid = 1; + } + } + asid = NULL; + asf1d = NULL; + ed1 = NULL; + ed2 = NULL; + sed = NULL; + } + if ((desc->bDescriptorType == UDESC_CS_INTERFACE) && + (desc->bDescriptorSubtype == AS_GENERAL) && + (desc->bLength >= sizeof(*asid))) { + if (asid == NULL) { + asid = (void *)desc; + } + } + if ((desc->bDescriptorType == UDESC_CS_INTERFACE) && + (desc->bDescriptorSubtype == FORMAT_TYPE) && + (desc->bLength >= sizeof(*asf1d))) { + if (asf1d == NULL) { + asf1d = (void *)desc; + if (asf1d->bFormatType != FORMAT_TYPE_I) { + DPRINTFN(11, "ignored bFormatType = %d\n", + asf1d->bFormatType); + asf1d = NULL; + continue; + } + if (asf1d->bLength < (sizeof(*asf1d) + + (asf1d->bSamFreqType == 0) ? 6 : + (asf1d->bSamFreqType * 3))) { + DPRINTFN(11, "'asf1d' descriptor is too short\n"); + asf1d = NULL; + continue; + } + } + } + if ((desc->bDescriptorType == UDESC_ENDPOINT) && + (desc->bLength >= sizeof(*ed1))) { + if (ed1 == NULL) { + ed1 = (void *)desc; + if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) { + ed1 = NULL; + } + } else { + if (ed2 == NULL) { + ed2 = (void *)desc; + if (UE_GET_XFERTYPE(ed2->bmAttributes) != UE_ISOCHRONOUS) { + ed2 = NULL; + continue; + } + if (ed2->bSynchAddress != 0) { + DPRINTFN(11, "invalid endpoint: bSynchAddress != 0\n"); + ed2 = NULL; + continue; + } + if (ed2->bEndpointAddress != ed1->bSynchAddress) { + DPRINTFN(11, "invalid endpoint addresses: " + "ep[0]->bSynchAddress=0x%x " + "ep[1]->bEndpointAddress=0x%x\n", + ed1->bSynchAddress, + ed2->bEndpointAddress); + ed2 = NULL; + continue; + } + } + } + } + if ((desc->bDescriptorType == UDESC_CS_ENDPOINT) && + (desc->bDescriptorSubtype == AS_GENERAL) && + (desc->bLength >= sizeof(*sed))) { + if (sed == NULL) { + sed = (void *)desc; + } + } + if (audio_if && asid && asf1d && ed1 && sed) { + + ep_dir = UE_GET_DIR(ed1->bEndpointAddress); + ep_type = UE_GET_ISO_TYPE(ed1->bmAttributes); + ep_sync = 0; + + if ((sc->sc_uq_au_inp_async) && + (ep_dir == UE_DIR_IN) && (ep_type == UE_ISO_ADAPT)) { + ep_type = UE_ISO_ASYNC; + } + if ((ep_dir == UE_DIR_IN) && (ep_type == UE_ISO_ADAPT)) { + ep_sync = 1; + } + if ((ep_dir != UE_DIR_IN) && (ep_type == UE_ISO_ASYNC)) { + ep_sync = 1; + } + /* Ignore sync endpoint information until further. */ +#if 0 + if (ep_sync && (!ed2)) { + continue; + } + /* + * we can't handle endpoints that need a sync pipe + * yet + */ + + if (ep_sync) { + DPRINTF("skipped sync interface\n"); + audio_if = 0; + continue; + } +#endif + + wFormat = UGETW(asid->wFormatTag); + bChannels = asf1d->bNrChannels; + bBitResolution = asf1d->bBitResolution; + + if (asf1d->bSamFreqType == 0) { + DPRINTFN(16, "Sample rate: %d-%dHz\n", + UA_SAMP_LO(asf1d), UA_SAMP_HI(asf1d)); + + if ((rate >= UA_SAMP_LO(asf1d)) && + (rate <= UA_SAMP_HI(asf1d))) { + goto found_rate; + } + } else { + + for (x = 0; x < asf1d->bSamFreqType; x++) { + DPRINTFN(16, "Sample rate = %dHz\n", + UA_GETSAMP(asf1d, x)); + + if (rate == UA_GETSAMP(asf1d, x)) { + goto found_rate; + } + } + } + + audio_if = 0; + continue; + + found_rate: + + for (p_fmt = uaudio_formats; + p_fmt->wFormat; + p_fmt++) { + if ((p_fmt->wFormat == wFormat) && + (p_fmt->bPrecision == bBitResolution)) { + goto found_format; + } + } + + audio_if = 0; + continue; + + found_format: + + if ((bChannels == channels) && + (bBitResolution == bit_resolution)) { + + chan = (ep_dir == UE_DIR_IN) ? + &sc->sc_rec_chan : + &sc->sc_play_chan; + + if ((chan->valid == 0) && usb2_get_iface(udev, curidx)) { + + chan->valid = 1; +#if USB_DEBUG + uaudio_chan_dump_ep_desc(ed1); + uaudio_chan_dump_ep_desc(ed2); + + if (sed->bmAttributes & UA_SED_FREQ_CONTROL) { + DPRINTFN(2, "FREQ_CONTROL\n"); + } + if (sed->bmAttributes & UA_SED_PITCH_CONTROL) { + DPRINTFN(2, "PITCH_CONTROL\n"); + } +#endif + DPRINTF("Sample rate = %dHz, channels = %d, " + "bits = %d, format = %s\n", rate, channels, + bit_resolution, p_fmt->description); + + chan->sample_rate = rate; + chan->p_asid = asid; + chan->p_asf1d = asf1d; + chan->p_ed1 = ed1; + chan->p_ed2 = ed2; + chan->p_fmt = p_fmt; + chan->p_sed = sed; + chan->iface_index = curidx; + chan->iface_alt_index = alt_index; + + chan->usb2_cfg = + (ep_dir == UE_DIR_IN) ? + ((fps == 1000) ? + uaudio_cfg_record_full_speed : + uaudio_cfg_record_high_speed) : + ((fps == 1000) ? + uaudio_cfg_play_full_speed : + uaudio_cfg_play_high_speed); + + + sample_size = ((chan->p_asf1d->bNrChannels * + chan->p_asf1d->bBitResolution) / 8); + + chan->bytes_per_frame = ((rate / fps) * sample_size); + + if (sc->sc_sndstat_valid) { + sbuf_printf(&sc->sc_sndstat, "\n\t" + "mode %d.%d:(%s) %dch, %d/%dbit, %s, %dHz", + curidx, alt_index, + (ep_dir == UE_DIR_IN) ? "input" : "output", + asf1d->bNrChannels, asf1d->bBitResolution, + asf1d->bSubFrameSize * 8, + p_fmt->description, rate); + } + } + } + audio_if = 0; + continue; + } + } + return; +} + +static void +uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb2_device *udev) +{ + uint32_t rate = uaudio_default_rate; + uint32_t z; + uint16_t fps = (usb2_get_speed(udev) == USB_SPEED_HIGH) ? 8000 : 1000; + uint8_t bits = uaudio_default_bits; + uint8_t y; + uint8_t channels = uaudio_default_channels; + uint8_t x; + + bits -= (bits % 8); + rate -= (rate % fps); + + if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND)) { + sc->sc_sndstat_valid = 1; + } + /* try to search for a valid config */ + + for (x = channels; x; x--) { + for (y = bits; y; y -= 8) { + for (z = rate; z; z -= fps) { + uaudio_chan_fill_info_sub(sc, udev, z, fps, x, y); + + if (sc->sc_rec_chan.valid && + sc->sc_play_chan.valid) { + goto done; + } + } + } + } + +done: + if (sc->sc_sndstat_valid) { + sbuf_finish(&sc->sc_sndstat); + } + return; +} + +static void +uaudio_chan_play_callback(struct usb2_xfer *xfer) +{ + struct uaudio_chan *ch = xfer->priv_sc; + uint32_t *p_len = xfer->frlengths; + uint32_t total = (sndbuf_getblkcnt(ch->pcm_buf) * + sndbuf_getblksz(ch->pcm_buf)) / 2; + uint32_t blockcount; + uint32_t n; + uint32_t offset; + + /* allow dynamic sizing of play buffer */ + blockcount = total / ch->bytes_per_frame; + + /* align to 8 units */ + blockcount &= ~7; + + /* range check - min */ + if (blockcount == 0) { + blockcount = 8; + } + /* range check - max */ + if (blockcount > xfer->max_frame_count) { + blockcount = xfer->max_frame_count; + } + /* compute the total length */ + total = blockcount * ch->bytes_per_frame; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (xfer->actlen < xfer->sumlen) { + DPRINTF("short transfer, " + "%d of %d bytes\n", xfer->actlen, total); + } + chn_intr(ch->pcm_ch); + + case USB_ST_SETUP: + if (ch->bytes_per_frame > xfer->max_frame_size) { + DPRINTF("bytes per transfer, %d, " + "exceeds maximum, %d!\n", + ch->bytes_per_frame, + xfer->max_frame_size); + break; + } + /* setup frame length */ + xfer->nframes = blockcount; + for (n = 0; n != blockcount; n++) { + p_len[n] = ch->bytes_per_frame; + } + + if (ch->end == ch->start) { + DPRINTF("no buffer!\n"); + break; + } + DPRINTFN(6, "transfer %d bytes\n", total); + + offset = 0; + + while (total > 0) { + + n = (ch->end - ch->cur); + if (n > total) { + n = total; + } + usb2_copy_in(xfer->frbuffers, offset, ch->cur, n); + + total -= n; + ch->cur += n; + offset += n; + + if (ch->cur >= ch->end) { + ch->cur = ch->start; + } + } + + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_transferred; + } + return; +} + +static void +uaudio_chan_record_callback(struct usb2_xfer *xfer) +{ + struct uaudio_chan *ch = xfer->priv_sc; + uint32_t *p_len = xfer->frlengths; + uint32_t n; + uint32_t m; + uint32_t total = (sndbuf_getblkcnt(ch->pcm_buf) * + sndbuf_getblksz(ch->pcm_buf)) / 2; + uint32_t blockcount; + uint32_t offset0; + uint32_t offset1; + + /* allow dynamic sizing of play buffer */ + blockcount = total / ch->bytes_per_frame; + + /* align to 8 units */ + blockcount &= ~7; + + /* range check - min */ + if (blockcount == 0) { + blockcount = 8; + } + /* range check - max */ + if (blockcount > xfer->max_frame_count) { + blockcount = xfer->max_frame_count; + } + /* compute the total length */ + total = blockcount * ch->bytes_per_frame; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (xfer->actlen < total) { + DPRINTF("short transfer, " + "%d of %d bytes\n", xfer->actlen, total); + } else { + DPRINTFN(6, "transferred %d bytes\n", xfer->actlen); + } + + offset0 = 0; + + for (n = 0; n != xfer->nframes; n++) { + + offset1 = offset0; + + while (p_len[n] > 0) { + + m = (ch->end - ch->cur); + + if (m > p_len[n]) { + m = p_len[n]; + } + usb2_copy_out(xfer->frbuffers, offset1, ch->cur, m); + + p_len[n] -= m; + offset1 += m; + ch->cur += m; + + if (ch->cur >= ch->end) { + ch->cur = ch->start; + } + } + + offset0 += ch->bytes_per_frame; + } + + chn_intr(ch->pcm_ch); + + case USB_ST_SETUP: + if (ch->bytes_per_frame > xfer->max_frame_size) { + DPRINTF("bytes per transfer, %d, " + "exceeds maximum, %d!\n", + ch->bytes_per_frame, + xfer->max_frame_size); + return; + } + xfer->nframes = blockcount; + for (n = 0; n != xfer->nframes; n++) { + p_len[n] = ch->bytes_per_frame; + } + + if (ch->end == ch->start) { + DPRINTF("no buffer!\n"); + return; + } + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + return; + } + goto tr_transferred; + } +} + +void * +uaudio_chan_init(struct uaudio_softc *sc, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct uaudio_chan *ch = ((dir == PCMDIR_PLAY) ? + &sc->sc_play_chan : &sc->sc_rec_chan); + uint8_t endpoint; + uint8_t iface_index; + uint8_t alt_index; + usb2_error_t err; + + ch->buf = malloc(UAUDIO_DEFAULT_BUFSZ, M_DEVBUF, M_WAITOK | M_ZERO); + + if (ch->buf == NULL) { + goto error; + } + if (sndbuf_setup(b, ch->buf, UAUDIO_DEFAULT_BUFSZ) != 0) { + goto error; + } + ch->start = ch->buf; + ch->end = ch->buf + UAUDIO_DEFAULT_BUFSZ; + ch->cur = ch->buf; + ch->pcm_ch = c; + ch->pcm_mtx = c->lock; + ch->pcm_buf = b; + + if (ch->pcm_mtx == NULL) { + DPRINTF("ERROR: PCM channels does not have a mutex!\n"); + goto error; + } + /* setup play/record format */ + + ch->pcm_cap.fmtlist = ch->pcm_format; + + ch->pcm_format[0] = 0; + ch->pcm_format[1] = 0; + + ch->pcm_cap.minspeed = ch->sample_rate; + ch->pcm_cap.maxspeed = ch->sample_rate; + + ch->pcm_cap.fmtlist[0] = ch->p_fmt->freebsd_fmt; + + if (ch->p_asf1d->bNrChannels == 2) { + ch->pcm_cap.fmtlist[0] |= AFMT_STEREO; + } + ch->pcm_cap.fmtlist[1] = 0; + + + /* set alternate interface corresponding to the mode */ + + endpoint = ch->p_ed1->bEndpointAddress; + iface_index = ch->iface_index; + alt_index = ch->iface_alt_index; + + DPRINTF("endpoint=0x%02x, speed=%d, iface=%d alt=%d\n", + endpoint, ch->sample_rate, iface_index, alt_index); + + err = usb2_set_alt_interface_index(sc->sc_udev, iface_index, alt_index); + if (err) { + DPRINTF("setting of alternate index failed: %s!\n", + usb2_errstr(err)); + goto error; + } + usb2_set_parent_iface(sc->sc_udev, iface_index, sc->sc_mixer_iface_index); + + /* + * If just one sampling rate is supported, + * no need to call "uaudio_set_speed()". + * Roland SD-90 freezes by a SAMPLING_FREQ_CONTROL request. + */ + if (ch->p_asf1d->bSamFreqType != 1) { + if (uaudio_set_speed(sc->sc_udev, endpoint, ch->sample_rate)) { + /* + * If the endpoint is adaptive setting the speed may + * fail. + */ + DPRINTF("setting of sample rate failed! (continuing anyway)\n"); + } + } + if (usb2_transfer_setup(sc->sc_udev, &iface_index, ch->xfer, + ch->usb2_cfg, UAUDIO_NCHANBUFS, ch, ch->pcm_mtx)) { + DPRINTF("could not allocate USB transfers!\n"); + goto error; + } + return (ch); + +error: + uaudio_chan_free(ch); + return (NULL); +} + +int +uaudio_chan_free(struct uaudio_chan *ch) +{ + if (ch->buf != NULL) { + free(ch->buf, M_DEVBUF); + ch->buf = NULL; + } + usb2_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS); + + ch->valid = 0; + + return (0); +} + +int +uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize) +{ + uaudio_chan_set_param_fragments(ch, blocksize, 0 - 1); + + return (ch->block_size); +} + +int +uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize, + uint32_t blockcount) +{ + uint32_t max = sndbuf_getmaxsize(ch->pcm_buf); + + RANGE(blocksize, 128, max / 2); + + blockcount = max / blocksize; + RANGE(blockcount, 2, 512); + + if ((sndbuf_getblksz(ch->pcm_buf) != blocksize) || + (sndbuf_getblkcnt(ch->pcm_buf) != blockcount)) { + + if (sndbuf_resize(ch->pcm_buf, blockcount, blocksize)) { + DPRINTFN(0, "failed to resize sound buffer, count=%u, " + "size=%u\n", blockcount, blocksize); + } + } + ch->block_size = sndbuf_getblksz(ch->pcm_buf); + + return (1); +} + +int +uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed) +{ + if (speed != ch->sample_rate) { + DPRINTF("rate conversion required\n"); + } + return (ch->sample_rate); +} + +int +uaudio_chan_getptr(struct uaudio_chan *ch) +{ + return (ch->cur - ch->start); +} + +struct pcmchan_caps * +uaudio_chan_getcaps(struct uaudio_chan *ch) +{ + return (&ch->pcm_cap); +} + +int +uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format) +{ + ch->format = format; + return (0); +} + +int +uaudio_chan_start(struct uaudio_chan *ch) +{ + ch->cur = ch->start; + +#if (UAUDIO_NCHANBUFS != 2) +#error "please update code" +#endif + if (ch->xfer[0]) { + usb2_transfer_start(ch->xfer[0]); + } + if (ch->xfer[1]) { + usb2_transfer_start(ch->xfer[1]); + } + return (0); +} + +int +uaudio_chan_stop(struct uaudio_chan *ch) +{ +#if (UAUDIO_NCHANBUFS != 2) +#error "please update code" +#endif + usb2_transfer_stop(ch->xfer[0]); + usb2_transfer_stop(ch->xfer[1]); + return (0); +} + +/*========================================================================* + * AC - Audio Controller - routines + *========================================================================*/ + +static void +uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) +{ + struct uaudio_mixer_node *p_mc_new = + malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK); + + if (p_mc_new) { + bcopy(mc, p_mc_new, sizeof(*p_mc_new)); + p_mc_new->next = sc->sc_mixer_root; + sc->sc_mixer_root = p_mc_new; + sc->sc_mixer_count++; + } else { + DPRINTF("out of memory\n"); + } + return; +} + +static void +uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) +{ + int32_t res; + + if (mc->class < UAC_NCLASSES) { + DPRINTF("adding %s.%d\n", + uac_names[mc->class], mc->ctl); + } else { + DPRINTF("adding %d\n", mc->ctl); + } + + mc->delta = 0; + if (mc->type == MIX_ON_OFF) { + mc->minval = 0; + mc->maxval = 1; + } else if (mc->type == MIX_SELECTOR) { + + } else { + + /* determine min and max values */ + + mc->minval = uaudio_mixer_get(sc->sc_udev, GET_MIN, mc); + + mc->minval = uaudio_mixer_signext(mc->type, mc->minval); + + mc->maxval = uaudio_mixer_get(sc->sc_udev, GET_MAX, mc); + + mc->maxval = 1 + uaudio_mixer_signext(mc->type, mc->maxval); + + mc->mul = mc->maxval - mc->minval; + if (mc->mul == 0) { + mc->mul = 1; + } + res = uaudio_mixer_get(sc->sc_udev, GET_RES, mc); + if (res > 0) { + mc->delta = ((res * 255) + (mc->mul / 2)) / mc->mul; + } + } + + if (mc->maxval < mc->minval) { + mc->maxval = mc->minval; + } + uaudio_mixer_add_ctl_sub(sc, mc); + +#if USB_DEBUG + if (uaudio_debug > 2) { + uint8_t i; + + for (i = 0; i < mc->nchan; i++) { + DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]); + } + DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' " + "min=%d max=%d\n", + mc->wIndex, mc->type, mc->ctl, + mc->minval, mc->maxval); + } +#endif + return; +} + +static void +uaudio_mixer_add_input(struct uaudio_softc *sc, + const struct uaudio_terminal_node *iot, int id) +{ +#if USB_DEBUG + const struct usb2_audio_input_terminal *d = iot[id].u.it; + + DPRINTFN(3, "bTerminalId=%d wTerminalType=0x%04x " + "bAssocTerminal=%d bNrChannels=%d wChannelConfig=%d " + "iChannelNames=%d\n", + d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, + d->bNrChannels, UGETW(d->wChannelConfig), + d->iChannelNames); +#endif + return; +} + +static void +uaudio_mixer_add_output(struct uaudio_softc *sc, + const struct uaudio_terminal_node *iot, int id) +{ +#if USB_DEBUG + const struct usb2_audio_output_terminal *d = iot[id].u.ot; + + DPRINTFN(3, "bTerminalId=%d wTerminalType=0x%04x " + "bAssocTerminal=%d bSourceId=%d iTerminal=%d\n", + d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, + d->bSourceId, d->iTerminal); +#endif + return; +} + +static void +uaudio_mixer_add_mixer(struct uaudio_softc *sc, + const struct uaudio_terminal_node *iot, int id) +{ + struct uaudio_mixer_node mix; + + const struct usb2_audio_mixer_unit_0 *d0 = iot[id].u.mu; + const struct usb2_audio_mixer_unit_1 *d1; + + uint32_t bno; /* bit number */ + uint32_t p; /* bit number accumulator */ + uint32_t mo; /* matching outputs */ + uint32_t mc; /* matching channels */ + uint32_t ichs; /* input channels */ + uint32_t ochs; /* output channels */ + uint32_t c; + uint32_t chs; /* channels */ + uint32_t i; + uint32_t o; + + DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", + d0->bUnitId, d0->bNrInPins); + + /* compute the number of input channels */ + + ichs = 0; + for (i = 0; i < d0->bNrInPins; i++) { + ichs += (uaudio_mixer_get_cluster(d0->baSourceId[i], iot) + .bNrChannels); + } + + d1 = (const void *)(d0->baSourceId + d0->bNrInPins); + + /* and the number of output channels */ + + ochs = d1->bNrChannels; + + DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); + + bzero(&mix, sizeof(mix)); + + mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); + uaudio_mixer_determine_class(&iot[id], &mix); + mix.type = MIX_SIGNED_16; + + if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) { + return; + } + for (p = i = 0; i < d0->bNrInPins; i++) { + chs = uaudio_mixer_get_cluster(d0->baSourceId[i], iot).bNrChannels; + mc = 0; + for (c = 0; c < chs; c++) { + mo = 0; + for (o = 0; o < ochs; o++) { + bno = ((p + c) * ochs) + o; + if (BIT_TEST(d1->bmControls, bno)) { + mo++; + } + } + if (mo == 1) { + mc++; + } + } + if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { + + /* repeat bit-scan */ + + mc = 0; + for (c = 0; c < chs; c++) { + for (o = 0; o < ochs; o++) { + bno = ((p + c) * ochs) + o; + if (BIT_TEST(d1->bmControls, bno)) { + mix.wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); + } + } + } + mix.nchan = chs; + uaudio_mixer_add_ctl(sc, &mix); + } else { + /* XXX */ + } + p += chs; + } + return; +} + +static void +uaudio_mixer_add_selector(struct uaudio_softc *sc, + const struct uaudio_terminal_node *iot, int id) +{ + const struct usb2_audio_selector_unit *d = iot[id].u.su; + struct uaudio_mixer_node mix; + uint16_t i; + + DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", + d->bUnitId, d->bNrInPins); + + if (d->bNrInPins == 0) { + return; + } + bzero(&mix, sizeof(mix)); + + mix.wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); + mix.wValue[0] = MAKE_WORD(0, 0); + uaudio_mixer_determine_class(&iot[id], &mix); + mix.nchan = 1; + mix.type = MIX_SELECTOR; + + mix.ctl = SOUND_MIXER_NRDEVICES; + mix.minval = 1; + mix.maxval = d->bNrInPins; + + if (mix.maxval > MAX_SELECTOR_INPUT_PIN) { + mix.maxval = MAX_SELECTOR_INPUT_PIN; + } + mix.mul = (mix.maxval - mix.minval); + for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) { + mix.slctrtype[i] = SOUND_MIXER_NRDEVICES; + } + + for (i = 0; i < mix.maxval; i++) { + mix.slctrtype[i] = uaudio_mixer_feature_name + (&iot[d->baSourceId[i]], &mix); + } + + mix.class = 0; /* not used */ + + uaudio_mixer_add_ctl(sc, &mix); + return; +} + +static uint32_t +uaudio_mixer_feature_get_bmaControls(const struct usb2_audio_feature_unit *d, + uint8_t index) +{ + uint32_t temp = 0; + uint32_t offset = (index * d->bControlSize); + + if (d->bControlSize > 0) { + temp |= d->bmaControls[offset]; + if (d->bControlSize > 1) { + temp |= d->bmaControls[offset + 1] << 8; + if (d->bControlSize > 2) { + temp |= d->bmaControls[offset + 2] << 16; + if (d->bControlSize > 3) { + temp |= d->bmaControls[offset + 3] << 24; + } + } + } + } + return (temp); +} + +static void +uaudio_mixer_add_feature(struct uaudio_softc *sc, + const struct uaudio_terminal_node *iot, int id) +{ + const struct usb2_audio_feature_unit *d = iot[id].u.fu; + struct uaudio_mixer_node mix; + uint32_t fumask; + uint32_t mmask; + uint32_t cmask; + uint16_t mixernumber; + uint8_t nchan; + uint8_t chan; + uint8_t ctl; + uint8_t i; + + if (d->bControlSize == 0) { + return; + } + bzero(&mix, sizeof(mix)); + + nchan = (d->bLength - 7) / d->bControlSize; + mmask = uaudio_mixer_feature_get_bmaControls(d, 0); + cmask = 0; + + if (nchan == 0) { + return; + } + /* figure out what we can control */ + + for (chan = 1; chan < nchan; chan++) { + DPRINTFN(10, "chan=%d mask=%x\n", + chan, uaudio_mixer_feature_get_bmaControls(d, chan)); + + cmask |= uaudio_mixer_feature_get_bmaControls(d, chan); + } + + if (nchan > MIX_MAX_CHAN) { + nchan = MIX_MAX_CHAN; + } + mix.wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); + + for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) { + + fumask = FU_MASK(ctl); + + DPRINTFN(5, "ctl=%d fumask=0x%04x\n", + ctl, fumask); + + if (mmask & fumask) { + mix.nchan = 1; + mix.wValue[0] = MAKE_WORD(ctl, 0); + } else if (cmask & fumask) { + mix.nchan = nchan - 1; + for (i = 1; i < nchan; i++) { + if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask) + mix.wValue[i - 1] = MAKE_WORD(ctl, i); + else + mix.wValue[i - 1] = -1; + } + } else { + continue; + } + + mixernumber = uaudio_mixer_feature_name(&iot[id], &mix); + + switch (ctl) { + case MUTE_CONTROL: + mix.type = MIX_ON_OFF; + mix.ctl = SOUND_MIXER_NRDEVICES; + break; + + case VOLUME_CONTROL: + mix.type = MIX_SIGNED_16; + mix.ctl = mixernumber; + break; + + case BASS_CONTROL: + mix.type = MIX_SIGNED_8; + mix.ctl = SOUND_MIXER_BASS; + break; + + case MID_CONTROL: + mix.type = MIX_SIGNED_8; + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ + break; + + case TREBLE_CONTROL: + mix.type = MIX_SIGNED_8; + mix.ctl = SOUND_MIXER_TREBLE; + break; + + case GRAPHIC_EQUALIZER_CONTROL: + continue; /* XXX don't add anything */ + break; + + case AGC_CONTROL: + mix.type = MIX_ON_OFF; + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ + break; + + case DELAY_CONTROL: + mix.type = MIX_UNSIGNED_16; + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ + break; + + case BASS_BOOST_CONTROL: + mix.type = MIX_ON_OFF; + mix.ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ + break; + + case LOUDNESS_CONTROL: + mix.type = MIX_ON_OFF; + mix.ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ + break; + + default: + mix.type = MIX_UNKNOWN; + break; + } + + if (mix.type != MIX_UNKNOWN) { + uaudio_mixer_add_ctl(sc, &mix); + } + } + return; +} + +static void +uaudio_mixer_add_processing_updown(struct uaudio_softc *sc, + const struct uaudio_terminal_node *iot, int id) +{ + const struct usb2_audio_processing_unit_0 *d0 = iot[id].u.pu; + const struct usb2_audio_processing_unit_1 *d1 = + (const void *)(d0->baSourceId + d0->bNrInPins); + const struct usb2_audio_processing_unit_updown *ud = + (const void *)(d1->bmControls + d1->bControlSize); + struct uaudio_mixer_node mix; + uint8_t i; + + if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) { + return; + } + if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes)) + == NULL) { + return; + } + DPRINTFN(3, "bUnitId=%d bNrModes=%d\n", + d0->bUnitId, ud->bNrModes); + + if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) { + DPRINTF("no mode select\n"); + return; + } + bzero(&mix, sizeof(mix)); + + mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); + mix.nchan = 1; + mix.wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0); + uaudio_mixer_determine_class(&iot[id], &mix); + mix.type = MIX_ON_OFF; /* XXX */ + + for (i = 0; i < ud->bNrModes; i++) { + DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i])); + /* XXX */ + } + + uaudio_mixer_add_ctl(sc, &mix); + return; +} + +static void +uaudio_mixer_add_processing(struct uaudio_softc *sc, + const struct uaudio_terminal_node *iot, int id) +{ + const struct usb2_audio_processing_unit_0 *d0 = iot[id].u.pu; + const struct usb2_audio_processing_unit_1 *d1 = + (const void *)(d0->baSourceId + d0->bNrInPins); + struct uaudio_mixer_node mix; + uint16_t ptype; + + bzero(&mix, sizeof(mix)); + + ptype = UGETW(d0->wProcessType); + + DPRINTFN(3, "wProcessType=%d bUnitId=%d " + "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins); + + if (d1->bControlSize == 0) { + return; + } + if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) { + mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); + mix.nchan = 1; + mix.wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0); + uaudio_mixer_determine_class(&iot[id], &mix); + mix.type = MIX_ON_OFF; + uaudio_mixer_add_ctl(sc, &mix); + } + switch (ptype) { + case UPDOWNMIX_PROCESS: + uaudio_mixer_add_processing_updown(sc, iot, id); + break; + + case DOLBY_PROLOGIC_PROCESS: + case P3D_STEREO_EXTENDER_PROCESS: + case REVERBATION_PROCESS: + case CHORUS_PROCESS: + case DYN_RANGE_COMP_PROCESS: + default: + DPRINTF("unit %d, type=%d is not implemented\n", + d0->bUnitId, ptype); + break; + } + return; +} + +static void +uaudio_mixer_add_extension(struct uaudio_softc *sc, + const struct uaudio_terminal_node *iot, int id) +{ + const struct usb2_audio_extension_unit_0 *d0 = iot[id].u.eu; + const struct usb2_audio_extension_unit_1 *d1 = + (const void *)(d0->baSourceId + d0->bNrInPins); + struct uaudio_mixer_node mix; + + DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", + d0->bUnitId, d0->bNrInPins); + + if (sc->sc_uq_au_no_xu) { + return; + } + if (d1->bControlSize == 0) { + return; + } + if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) { + + bzero(&mix, sizeof(mix)); + + mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); + mix.nchan = 1; + mix.wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0); + uaudio_mixer_determine_class(&iot[id], &mix); + mix.type = MIX_ON_OFF; + + uaudio_mixer_add_ctl(sc, &mix); + } + return; +} + +static const void * +uaudio_mixer_verify_desc(const void *arg, uint32_t len) +{ + const struct usb2_audio_mixer_unit_1 *d1; + const struct usb2_audio_extension_unit_1 *e1; + const struct usb2_audio_processing_unit_1 *u1; + + union { + const struct usb2_descriptor *desc; + const struct usb2_audio_input_terminal *it; + const struct usb2_audio_output_terminal *ot; + const struct usb2_audio_mixer_unit_0 *mu; + const struct usb2_audio_selector_unit *su; + const struct usb2_audio_feature_unit *fu; + const struct usb2_audio_processing_unit_0 *pu; + const struct usb2_audio_extension_unit_0 *eu; + } u; + + u.desc = arg; + + if (u.desc == NULL) { + goto error; + } + if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) { + goto error; + } + switch (u.desc->bDescriptorSubtype) { + case UDESCSUB_AC_INPUT: + len += sizeof(*u.it); + break; + + case UDESCSUB_AC_OUTPUT: + len += sizeof(*u.ot); + break; + + case UDESCSUB_AC_MIXER: + len += sizeof(*u.mu); + + if (u.desc->bLength < len) { + goto error; + } + len += u.mu->bNrInPins; + + if (u.desc->bLength < len) { + goto error; + } + d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); + + len += sizeof(*d1); + break; + + case UDESCSUB_AC_SELECTOR: + len += sizeof(*u.su); + + if (u.desc->bLength < len) { + goto error; + } + len += u.su->bNrInPins; + break; + + case UDESCSUB_AC_FEATURE: + len += (sizeof(*u.fu) + 1); + break; + + case UDESCSUB_AC_PROCESSING: + len += sizeof(*u.pu); + + if (u.desc->bLength < len) { + goto error; + } + len += u.pu->bNrInPins; + + if (u.desc->bLength < len) { + goto error; + } + u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); + + len += sizeof(*u1); + + if (u.desc->bLength < len) { + goto error; + } + len += u1->bControlSize; + + break; + + case UDESCSUB_AC_EXTENSION: + len += sizeof(*u.eu); + + if (u.desc->bLength < len) { + goto error; + } + len += u.eu->bNrInPins; + + if (u.desc->bLength < len) { + goto error; + } + e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); + + len += sizeof(*e1); + + if (u.desc->bLength < len) { + goto error; + } + len += e1->bControlSize; + break; + + default: + goto error; + } + + if (u.desc->bLength < len) { + goto error; + } + return (u.desc); + +error: + if (u.desc) { + DPRINTF("invalid descriptor, type=%d, " + "sub_type=%d, len=%d of %d bytes\n", + u.desc->bDescriptorType, + u.desc->bDescriptorSubtype, + u.desc->bLength, len); + } + return (NULL); +} + +#if USB_DEBUG +static void +uaudio_mixer_dump_cluster(uint8_t id, const struct uaudio_terminal_node *iot) +{ + static const char *channel_names[16] = { + "LEFT", "RIGHT", "CENTER", "LFE", + "LEFT_SURROUND", "RIGHT_SURROUND", "LEFT_CENTER", "RIGHT_CENTER", + "SURROUND", "LEFT_SIDE", "RIGHT_SIDE", "TOP", + "RESERVED12", "RESERVED13", "RESERVED14", "RESERVED15", + }; + uint16_t cc; + uint8_t i; + const struct usb2_audio_cluster cl = uaudio_mixer_get_cluster(id, iot); + + cc = UGETW(cl.wChannelConfig); + + DPRINTF("cluster: bNrChannels=%u iChannelNames=%u wChannelConfig=" + "0x%04x:\n", cl.iChannelNames, cl.bNrChannels, cc); + + for (i = 0; cc; i++) { + if (cc & 1) { + DPRINTF(" - %s\n", channel_names[i]); + } + cc >>= 1; + } + return; +} + +#endif + +static struct usb2_audio_cluster +uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) +{ + struct usb2_audio_cluster r; + const struct usb2_descriptor *dp; + uint8_t i; + + for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ + dp = iot[id].u.desc; + if (dp == NULL) { + goto error; + } + switch (dp->bDescriptorSubtype) { + case UDESCSUB_AC_INPUT: + r.bNrChannels = iot[id].u.it->bNrChannels; + r.wChannelConfig[0] = iot[id].u.it->wChannelConfig[0]; + r.wChannelConfig[1] = iot[id].u.it->wChannelConfig[1]; + r.iChannelNames = iot[id].u.it->iChannelNames; + goto done; + + case UDESCSUB_AC_OUTPUT: + id = iot[id].u.ot->bSourceId; + break; + + case UDESCSUB_AC_MIXER: + r = *(const struct usb2_audio_cluster *) + &iot[id].u.mu->baSourceId[iot[id].u.mu-> + bNrInPins]; + goto done; + + case UDESCSUB_AC_SELECTOR: + if (iot[id].u.su->bNrInPins > 0) { + /* XXX This is not really right */ + id = iot[id].u.su->baSourceId[0]; + } + break; + + case UDESCSUB_AC_FEATURE: + id = iot[id].u.fu->bSourceId; + break; + + case UDESCSUB_AC_PROCESSING: + r = *((const struct usb2_audio_cluster *) + &iot[id].u.pu->baSourceId[iot[id].u.pu-> + bNrInPins]); + goto done; + + case UDESCSUB_AC_EXTENSION: + r = *((const struct usb2_audio_cluster *) + &iot[id].u.eu->baSourceId[iot[id].u.eu-> + bNrInPins]); + goto done; + + default: + goto error; + } + } +error: + DPRINTF("bad data\n"); + bzero(&r, sizeof(r)); +done: + return (r); +} + +#if USB_DEBUG + +struct uaudio_tt_to_string { + uint16_t terminal_type; + const char *desc; +}; + +static const struct uaudio_tt_to_string uaudio_tt_to_string[] = { + + /* USB terminal types */ + {UAT_UNDEFINED, "UAT_UNDEFINED"}, + {UAT_STREAM, "UAT_STREAM"}, + {UAT_VENDOR, "UAT_VENDOR"}, + + /* input terminal types */ + {UATI_UNDEFINED, "UATI_UNDEFINED"}, + {UATI_MICROPHONE, "UATI_MICROPHONE"}, + {UATI_DESKMICROPHONE, "UATI_DESKMICROPHONE"}, + {UATI_PERSONALMICROPHONE, "UATI_PERSONALMICROPHONE"}, + {UATI_OMNIMICROPHONE, "UATI_OMNIMICROPHONE"}, + {UATI_MICROPHONEARRAY, "UATI_MICROPHONEARRAY"}, + {UATI_PROCMICROPHONEARR, "UATI_PROCMICROPHONEARR"}, + + /* output terminal types */ + {UATO_UNDEFINED, "UATO_UNDEFINED"}, + {UATO_SPEAKER, "UATO_SPEAKER"}, + {UATO_HEADPHONES, "UATO_HEADPHONES"}, + {UATO_DISPLAYAUDIO, "UATO_DISPLAYAUDIO"}, + {UATO_DESKTOPSPEAKER, "UATO_DESKTOPSPEAKER"}, + {UATO_ROOMSPEAKER, "UATO_ROOMSPEAKER"}, + {UATO_COMMSPEAKER, "UATO_COMMSPEAKER"}, + {UATO_SUBWOOFER, "UATO_SUBWOOFER"}, + + /* bidir terminal types */ + {UATB_UNDEFINED, "UATB_UNDEFINED"}, + {UATB_HANDSET, "UATB_HANDSET"}, + {UATB_HEADSET, "UATB_HEADSET"}, + {UATB_SPEAKERPHONE, "UATB_SPEAKERPHONE"}, + {UATB_SPEAKERPHONEESUP, "UATB_SPEAKERPHONEESUP"}, + {UATB_SPEAKERPHONEECANC, "UATB_SPEAKERPHONEECANC"}, + + /* telephony terminal types */ + {UATT_UNDEFINED, "UATT_UNDEFINED"}, + {UATT_PHONELINE, "UATT_PHONELINE"}, + {UATT_TELEPHONE, "UATT_TELEPHONE"}, + {UATT_DOWNLINEPHONE, "UATT_DOWNLINEPHONE"}, + + /* external terminal types */ + {UATE_UNDEFINED, "UATE_UNDEFINED"}, + {UATE_ANALOGCONN, "UATE_ANALOGCONN"}, + {UATE_LINECONN, "UATE_LINECONN"}, + {UATE_LEGACYCONN, "UATE_LEGACYCONN"}, + {UATE_DIGITALAUIFC, "UATE_DIGITALAUIFC"}, + {UATE_SPDIF, "UATE_SPDIF"}, + {UATE_1394DA, "UATE_1394DA"}, + {UATE_1394DV, "UATE_1394DV"}, + + /* embedded function terminal types */ + {UATF_UNDEFINED, "UATF_UNDEFINED"}, + {UATF_CALIBNOISE, "UATF_CALIBNOISE"}, + {UATF_EQUNOISE, "UATF_EQUNOISE"}, + {UATF_CDPLAYER, "UATF_CDPLAYER"}, + {UATF_DAT, "UATF_DAT"}, + {UATF_DCC, "UATF_DCC"}, + {UATF_MINIDISK, "UATF_MINIDISK"}, + {UATF_ANALOGTAPE, "UATF_ANALOGTAPE"}, + {UATF_PHONOGRAPH, "UATF_PHONOGRAPH"}, + {UATF_VCRAUDIO, "UATF_VCRAUDIO"}, + {UATF_VIDEODISCAUDIO, "UATF_VIDEODISCAUDIO"}, + {UATF_DVDAUDIO, "UATF_DVDAUDIO"}, + {UATF_TVTUNERAUDIO, "UATF_TVTUNERAUDIO"}, + {UATF_SATELLITE, "UATF_SATELLITE"}, + {UATF_CABLETUNER, "UATF_CABLETUNER"}, + {UATF_DSS, "UATF_DSS"}, + {UATF_RADIORECV, "UATF_RADIORECV"}, + {UATF_RADIOXMIT, "UATF_RADIOXMIT"}, + {UATF_MULTITRACK, "UATF_MULTITRACK"}, + {UATF_SYNTHESIZER, "UATF_SYNTHESIZER"}, + + /* unknown */ + {0x0000, "UNKNOWN"}, +}; + +static const char * +uaudio_mixer_get_terminal_name(uint16_t terminal_type) +{ + const struct uaudio_tt_to_string *uat = uaudio_tt_to_string; + + while (uat->terminal_type) { + if (uat->terminal_type == terminal_type) { + break; + } + uat++; + } + if (uat->terminal_type == 0) { + DPRINTF("unknown terminal type (0x%04x)", terminal_type); + } + return (uat->desc); +} + +#endif + +static uint16_t +uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot, + struct uaudio_mixer_node *mix) +{ + uint16_t terminal_type = 0x0000; + const struct uaudio_terminal_node *input[2]; + const struct uaudio_terminal_node *output[2]; + + input[0] = uaudio_mixer_get_input(iot, 0); + input[1] = uaudio_mixer_get_input(iot, 1); + + output[0] = uaudio_mixer_get_output(iot, 0); + output[1] = uaudio_mixer_get_output(iot, 1); + + /* + * check if there is only + * one output terminal: + */ + if (output[0] && (!output[1])) { + terminal_type = UGETW(output[0]->u.ot->wTerminalType); + } + /* + * If the only output terminal is USB, + * the class is UAC_RECORD. + */ + if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) { + + mix->class = UAC_RECORD; + if (input[0] && (!input[1])) { + terminal_type = UGETW(input[0]->u.it->wTerminalType); + } else { + terminal_type = 0; + } + goto done; + } + /* + * if the unit is connected to just + * one input terminal, the + * class is UAC_INPUT: + */ + if (input[0] && (!input[1])) { + mix->class = UAC_INPUT; + terminal_type = UGETW(input[0]->u.it->wTerminalType); + goto done; + } + /* + * Otherwise, the class is UAC_OUTPUT. + */ + mix->class = UAC_OUTPUT; +done: + return (terminal_type); +} + +struct uaudio_tt_to_feature { + uint16_t terminal_type; + uint16_t feature; +}; + +static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = { + + {UAT_STREAM, SOUND_MIXER_PCM}, + + {UATI_MICROPHONE, SOUND_MIXER_MIC}, + {UATI_DESKMICROPHONE, SOUND_MIXER_MIC}, + {UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC}, + {UATI_OMNIMICROPHONE, SOUND_MIXER_MIC}, + {UATI_MICROPHONEARRAY, SOUND_MIXER_MIC}, + {UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC}, + + {UATO_SPEAKER, SOUND_MIXER_SPEAKER}, + {UATO_DESKTOPSPEAKER, SOUND_MIXER_SPEAKER}, + {UATO_ROOMSPEAKER, SOUND_MIXER_SPEAKER}, + {UATO_COMMSPEAKER, SOUND_MIXER_SPEAKER}, + + {UATE_ANALOGCONN, SOUND_MIXER_LINE}, + {UATE_LINECONN, SOUND_MIXER_LINE}, + {UATE_LEGACYCONN, SOUND_MIXER_LINE}, + + {UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM}, + {UATE_SPDIF, SOUND_MIXER_ALTPCM}, + {UATE_1394DA, SOUND_MIXER_ALTPCM}, + {UATE_1394DV, SOUND_MIXER_ALTPCM}, + + {UATF_CDPLAYER, SOUND_MIXER_CD}, + + {UATF_SYNTHESIZER, SOUND_MIXER_SYNTH}, + + {UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO}, + {UATF_DVDAUDIO, SOUND_MIXER_VIDEO}, + {UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO}, + + /* telephony terminal types */ + {UATT_UNDEFINED, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ + {UATT_PHONELINE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ + {UATT_TELEPHONE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ + {UATT_DOWNLINEPHONE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ + + {UATF_RADIORECV, SOUND_MIXER_RADIO}, + {UATF_RADIOXMIT, SOUND_MIXER_RADIO}, + + {UAT_UNDEFINED, SOUND_MIXER_VOLUME}, + {UAT_VENDOR, SOUND_MIXER_VOLUME}, + {UATI_UNDEFINED, SOUND_MIXER_VOLUME}, + + /* output terminal types */ + {UATO_UNDEFINED, SOUND_MIXER_VOLUME}, + {UATO_DISPLAYAUDIO, SOUND_MIXER_VOLUME}, + {UATO_SUBWOOFER, SOUND_MIXER_VOLUME}, + {UATO_HEADPHONES, SOUND_MIXER_VOLUME}, + + /* bidir terminal types */ + {UATB_UNDEFINED, SOUND_MIXER_VOLUME}, + {UATB_HANDSET, SOUND_MIXER_VOLUME}, + {UATB_HEADSET, SOUND_MIXER_VOLUME}, + {UATB_SPEAKERPHONE, SOUND_MIXER_VOLUME}, + {UATB_SPEAKERPHONEESUP, SOUND_MIXER_VOLUME}, + {UATB_SPEAKERPHONEECANC, SOUND_MIXER_VOLUME}, + + /* external terminal types */ + {UATE_UNDEFINED, SOUND_MIXER_VOLUME}, + + /* embedded function terminal types */ + {UATF_UNDEFINED, SOUND_MIXER_VOLUME}, + {UATF_CALIBNOISE, SOUND_MIXER_VOLUME}, + {UATF_EQUNOISE, SOUND_MIXER_VOLUME}, + {UATF_DAT, SOUND_MIXER_VOLUME}, + {UATF_DCC, SOUND_MIXER_VOLUME}, + {UATF_MINIDISK, SOUND_MIXER_VOLUME}, + {UATF_ANALOGTAPE, SOUND_MIXER_VOLUME}, + {UATF_PHONOGRAPH, SOUND_MIXER_VOLUME}, + {UATF_VCRAUDIO, SOUND_MIXER_VOLUME}, + {UATF_SATELLITE, SOUND_MIXER_VOLUME}, + {UATF_CABLETUNER, SOUND_MIXER_VOLUME}, + {UATF_DSS, SOUND_MIXER_VOLUME}, + {UATF_MULTITRACK, SOUND_MIXER_VOLUME}, + {0xffff, SOUND_MIXER_VOLUME}, + + /* default */ + {0x0000, SOUND_MIXER_VOLUME}, +}; + +static uint16_t +uaudio_mixer_feature_name(const struct uaudio_terminal_node *iot, + struct uaudio_mixer_node *mix) +{ + const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature; + uint16_t terminal_type = uaudio_mixer_determine_class(iot, mix); + + if ((mix->class == UAC_RECORD) && (terminal_type == 0)) { + return (SOUND_MIXER_IMIX); + } + while (uat->terminal_type) { + if (uat->terminal_type == terminal_type) { + break; + } + uat++; + } + + DPRINTF("terminal_type=%s (0x%04x) -> %d\n", + uaudio_mixer_get_terminal_name(terminal_type), + terminal_type, uat->feature); + + return (uat->feature); +} + +const static struct uaudio_terminal_node * +uaudio_mixer_get_input(const struct uaudio_terminal_node *iot, uint8_t index) +{ + struct uaudio_terminal_node *root = iot->root; + uint8_t n; + + n = iot->usr.id_max; + do { + if (iot->usr.bit_input[n / 8] & (1 << (n % 8))) { + if (!index--) { + return (root + n); + } + } + } while (n--); + + return (NULL); +} + +const static struct uaudio_terminal_node * +uaudio_mixer_get_output(const struct uaudio_terminal_node *iot, uint8_t index) +{ + struct uaudio_terminal_node *root = iot->root; + uint8_t n; + + n = iot->usr.id_max; + do { + if (iot->usr.bit_output[n / 8] & (1 << (n % 8))) { + if (!index--) { + return (root + n); + } + } + } while (n--); + + return (NULL); +} + +static void +uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root, + const uint8_t *p_id, uint8_t n_id, + struct uaudio_search_result *info) +{ + struct uaudio_terminal_node *iot; + uint8_t n; + uint8_t i; + + if (info->recurse_level >= UAUDIO_RECURSE_LIMIT) { + return; + } + info->recurse_level++; + + for (n = 0; n < n_id; n++) { + + i = p_id[n]; + + if (info->bit_visited[i / 8] & (1 << (i % 8))) { + /* don't go into a circle */ + DPRINTF("avoided going into a circle at id=%d!\n", i); + continue; + } else { + info->bit_visited[i / 8] |= (1 << (i % 8)); + } + + iot = (root + i); + + if (iot->u.desc == NULL) { + continue; + } + switch (iot->u.desc->bDescriptorSubtype) { + case UDESCSUB_AC_INPUT: + info->bit_input[i / 8] |= (1 << (i % 8)); + break; + + case UDESCSUB_AC_FEATURE: + uaudio_mixer_find_inputs_sub + (root, &iot->u.fu->bSourceId, 1, info); + break; + + case UDESCSUB_AC_OUTPUT: + uaudio_mixer_find_inputs_sub + (root, &iot->u.ot->bSourceId, 1, info); + break; + + case UDESCSUB_AC_MIXER: + uaudio_mixer_find_inputs_sub + (root, iot->u.mu->baSourceId, + iot->u.mu->bNrInPins, info); + break; + + case UDESCSUB_AC_SELECTOR: + uaudio_mixer_find_inputs_sub + (root, iot->u.su->baSourceId, + iot->u.su->bNrInPins, info); + break; + + case UDESCSUB_AC_PROCESSING: + uaudio_mixer_find_inputs_sub + (root, iot->u.pu->baSourceId, + iot->u.pu->bNrInPins, info); + break; + + case UDESCSUB_AC_EXTENSION: + uaudio_mixer_find_inputs_sub + (root, iot->u.eu->baSourceId, + iot->u.eu->bNrInPins, info); + break; + + case UDESCSUB_AC_HEADER: + default: + break; + } + } + info->recurse_level--; + return; +} + +static void +uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *root, uint8_t id, + uint8_t n_id, struct uaudio_search_result *info) +{ + struct uaudio_terminal_node *iot = (root + id); + uint8_t j; + + j = n_id; + do { + if ((j != id) && ((root + j)->u.desc) && + ((root + j)->u.desc->bDescriptorSubtype == UDESCSUB_AC_OUTPUT)) { + + /* + * "j" (output) <--- virtual wire <--- "id" (input) + * + * if "j" has "id" on the input, then "id" have "j" on + * the output, because they are connected: + */ + if ((root + j)->usr.bit_input[id / 8] & (1 << (id % 8))) { + iot->usr.bit_output[j / 8] |= (1 << (j % 8)); + } + } + } while (j--); + + return; +} + +static void +uaudio_mixer_fill_info(struct uaudio_softc *sc, struct usb2_device *udev, + void *desc) +{ + const struct usb2_audio_control_descriptor *acdp; + struct usb2_config_descriptor *cd = usb2_get_config_descriptor(udev); + const struct usb2_descriptor *dp; + const struct usb2_audio_unit *au; + struct uaudio_terminal_node *iot = NULL; + uint16_t wTotalLen; + uint8_t ID_max = 0; /* inclusive */ + uint8_t i; + + desc = usb2_desc_foreach(cd, desc); + + if (desc == NULL) { + DPRINTF("no Audio Control header\n"); + goto done; + } + acdp = desc; + + if ((acdp->bLength < sizeof(*acdp)) || + (acdp->bDescriptorType != UDESC_CS_INTERFACE) || + (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) { + DPRINTF("invalid Audio Control header\n"); + goto done; + } + wTotalLen = UGETW(cd->wTotalLength); + sc->sc_audio_rev = UGETW(acdp->bcdADC); + + DPRINTFN(3, "found AC header, vers=%03x, len=%d\n", + sc->sc_audio_rev, wTotalLen); + + if (sc->sc_audio_rev != UAUDIO_VERSION) { + + if (sc->sc_uq_bad_adc) { + + } else { + DPRINTF("invalid audio version\n"); + goto done; + } + } + iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP, + M_WAITOK | M_ZERO); + + if (iot == NULL) { + DPRINTF("no memory!\n"); + goto done; + } + while ((desc = usb2_desc_foreach(cd, desc))) { + + dp = desc; + + if (dp->bLength > wTotalLen) { + break; + } else { + wTotalLen -= dp->bLength; + } + + au = uaudio_mixer_verify_desc(dp, 0); + + if (au) { + iot[au->bUnitId].u.desc = (const void *)au; + if (au->bUnitId > ID_max) { + ID_max = au->bUnitId; + } + } + } + + DPRINTF("Maximum ID=%d\n", ID_max); + + /* + * determine sourcing inputs for + * all nodes in the tree: + */ + i = ID_max; + do { + uaudio_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); + } while (i--); + + /* + * determine outputs for + * all nodes in the tree: + */ + i = ID_max; + do { + uaudio_mixer_find_outputs_sub(iot, i, ID_max, &((iot + i)->usr)); + } while (i--); + + /* set "id_max" and "root" */ + + i = ID_max; + do { + (iot + i)->usr.id_max = ID_max; + (iot + i)->root = iot; + } while (i--); + +#if USB_DEBUG + i = ID_max; + do { + uint8_t j; + + if (iot[i].u.desc == NULL) { + continue; + } + DPRINTF("id %d:\n", i); + + switch (iot[i].u.desc->bDescriptorSubtype) { + case UDESCSUB_AC_INPUT: + DPRINTF(" - AC_INPUT type=%s\n", + uaudio_mixer_get_terminal_name + (UGETW(iot[i].u.it->wTerminalType))); + uaudio_mixer_dump_cluster(i, iot); + break; + + case UDESCSUB_AC_OUTPUT: + DPRINTF(" - AC_OUTPUT type=%s " + "src=%d\n", uaudio_mixer_get_terminal_name + (UGETW(iot[i].u.ot->wTerminalType)), + iot[i].u.ot->bSourceId); + break; + + case UDESCSUB_AC_MIXER: + DPRINTF(" - AC_MIXER src:\n"); + for (j = 0; j < iot[i].u.mu->bNrInPins; j++) { + DPRINTF(" - %d\n", iot[i].u.mu->baSourceId[j]); + } + uaudio_mixer_dump_cluster(i, iot); + break; + + case UDESCSUB_AC_SELECTOR: + DPRINTF(" - AC_SELECTOR src:\n"); + for (j = 0; j < iot[i].u.su->bNrInPins; j++) { + DPRINTF(" - %d\n", iot[i].u.su->baSourceId[j]); + } + break; + + case UDESCSUB_AC_FEATURE: + DPRINTF(" - AC_FEATURE src=%d\n", iot[i].u.fu->bSourceId); + break; + + case UDESCSUB_AC_PROCESSING: + DPRINTF(" - AC_PROCESSING src:\n"); + for (j = 0; j < iot[i].u.pu->bNrInPins; j++) { + DPRINTF(" - %d\n", iot[i].u.pu->baSourceId[j]); + } + uaudio_mixer_dump_cluster(i, iot); + break; + + case UDESCSUB_AC_EXTENSION: + DPRINTF(" - AC_EXTENSION src:\n"); + for (j = 0; j < iot[i].u.eu->bNrInPins; j++) { + DPRINTF("%d ", iot[i].u.eu->baSourceId[j]); + } + uaudio_mixer_dump_cluster(i, iot); + break; + + default: + DPRINTF("unknown audio control (subtype=%d)\n", + iot[i].u.desc->bDescriptorSubtype); + } + + DPRINTF("Inputs to this ID are:\n"); + + j = ID_max; + do { + if (iot[i].usr.bit_input[j / 8] & (1 << (j % 8))) { + DPRINTF(" -- ID=%d\n", j); + } + } while (j--); + + DPRINTF("Outputs from this ID are:\n"); + + j = ID_max; + do { + if (iot[i].usr.bit_output[j / 8] & (1 << (j % 8))) { + DPRINTF(" -- ID=%d\n", j); + } + } while (j--); + + } while (i--); +#endif + + /* + * scan the config to create a linked + * list of "mixer" nodes: + */ + + i = ID_max; + do { + dp = iot[i].u.desc; + + if (dp == NULL) { + continue; + } + DPRINTFN(11, "id=%d subtype=%d\n", + i, dp->bDescriptorSubtype); + + switch (dp->bDescriptorSubtype) { + case UDESCSUB_AC_HEADER: + DPRINTF("unexpected AC header\n"); + break; + + case UDESCSUB_AC_INPUT: + uaudio_mixer_add_input(sc, iot, i); + break; + + case UDESCSUB_AC_OUTPUT: + uaudio_mixer_add_output(sc, iot, i); + break; + + case UDESCSUB_AC_MIXER: + uaudio_mixer_add_mixer(sc, iot, i); + break; + + case UDESCSUB_AC_SELECTOR: + uaudio_mixer_add_selector(sc, iot, i); + break; + + case UDESCSUB_AC_FEATURE: + uaudio_mixer_add_feature(sc, iot, i); + break; + + case UDESCSUB_AC_PROCESSING: + uaudio_mixer_add_processing(sc, iot, i); + break; + + case UDESCSUB_AC_EXTENSION: + uaudio_mixer_add_extension(sc, iot, i); + break; + + default: + DPRINTF("bad AC desc subtype=0x%02x\n", + dp->bDescriptorSubtype); + break; + } + + } while (i--); + +done: + if (iot) { + free(iot, M_TEMP); + } + return; +} + +static uint16_t +uaudio_mixer_get(struct usb2_device *udev, uint8_t what, + struct uaudio_mixer_node *mc) +{ + struct usb2_device_request req; + uint16_t val; + uint16_t len = MIX_SIZE(mc->type); + uint8_t data[4]; + usb2_error_t err; + + if (mc->wValue[0] == -1) { + return (0); + } + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = what; + USETW(req.wValue, mc->wValue[0]); + USETW(req.wIndex, mc->wIndex); + USETW(req.wLength, len); + + err = usb2_do_request(udev, &Giant, &req, data); + if (err) { + DPRINTF("err=%s\n", usb2_errstr(err)); + return (0); + } + if (len < 1) { + data[0] = 0; + } + if (len < 2) { + data[1] = 0; + } + val = (data[0] | (data[1] << 8)); + + DPRINTFN(3, "val=%d\n", val); + + return (val); +} + +static void +uaudio_mixer_write_cfg_callback(struct usb2_xfer *xfer) +{ + struct usb2_device_request req; + struct uaudio_softc *sc = xfer->priv_sc; + struct uaudio_mixer_node *mc = sc->sc_mixer_curr; + uint16_t len; + uint8_t repeat = 1; + uint8_t update; + uint8_t chan; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + case USB_ST_SETUP: +tr_setup: + + if (mc == NULL) { + mc = sc->sc_mixer_root; + sc->sc_mixer_curr = mc; + sc->sc_mixer_chan = 0; + repeat = 0; + } + while (mc) { + while (sc->sc_mixer_chan < mc->nchan) { + + len = MIX_SIZE(mc->type); + + chan = sc->sc_mixer_chan; + + sc->sc_mixer_chan++; + + update = ((mc->update[chan / 8] & (1 << (chan % 8))) && + (mc->wValue[chan] != -1)); + + mc->update[chan / 8] &= ~(1 << (chan % 8)); + + if (update) { + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = SET_CUR; + USETW(req.wValue, mc->wValue[chan]); + USETW(req.wIndex, mc->wIndex); + USETW(req.wLength, len); + + if (len > 0) { + buf[0] = (mc->wData[chan] & 0xFF); + } + if (len > 1) { + buf[1] = (mc->wData[chan] >> 8) & 0xFF; + } + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + usb2_copy_in(xfer->frbuffers + 1, 0, buf, len); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = len; + xfer->nframes = xfer->frlengths[1] ? 2 : 1; + usb2_start_hardware(xfer); + return; + } + } + + mc = mc->next; + sc->sc_mixer_curr = mc; + sc->sc_mixer_chan = 0; + } + + if (repeat) { + goto tr_setup; + } + return; + + default: /* Error */ + DPRINTF("error=%s\n", usb2_errstr(xfer->error)); + + goto tr_transferred; + } +} + +static usb2_error_t +uaudio_set_speed(struct usb2_device *udev, uint8_t endpt, uint32_t speed) +{ + struct usb2_device_request req; + uint8_t data[3]; + + DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed); + + req.bmRequestType = UT_WRITE_CLASS_ENDPOINT; + req.bRequest = SET_CUR; + USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0); + USETW(req.wIndex, endpt); + USETW(req.wLength, 3); + data[0] = speed; + data[1] = speed >> 8; + data[2] = speed >> 16; + + return (usb2_do_request(udev, &Giant, &req, data)); +} + +static int +uaudio_mixer_signext(uint8_t type, int val) +{ + if (!MIX_UNSIGNED(type)) { + if (MIX_SIZE(type) == 2) { + val = (int16_t)val; + } else { + val = (int8_t)val; + } + } + return (val); +} + +static int +uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int32_t val) +{ + DPRINTFN(6, "type=%03x val=%d min=%d max=%d ", + mc->type, val, mc->minval, mc->maxval); + + if (mc->type == MIX_ON_OFF) { + val = (val != 0); + } else if (mc->type == MIX_SELECTOR) { + if ((val < mc->minval) || + (val > mc->maxval)) { + val = mc->minval; + } + } else { + val = (((val + (mc->delta / 2)) * mc->mul) / 255) + mc->minval; + } + + DPRINTFN(6, "val=%d\n", val); + return (val); +} + +static void +uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc, + uint8_t chan, int32_t val) +{ + val = uaudio_mixer_bsd2value(mc, val); + + mc->update[chan / 8] |= (1 << (chan % 8)); + mc->wData[chan] = val; + + /* start the transfer, if not already started */ + + usb2_transfer_start(sc->sc_mixer_xfer[0]); + + return; +} + +static void +uaudio_mixer_init(struct uaudio_softc *sc) +{ + struct uaudio_mixer_node *mc; + int32_t i; + + for (mc = sc->sc_mixer_root; mc; + mc = mc->next) { + + if (mc->ctl != SOUND_MIXER_NRDEVICES) { + /* + * Set device mask bits. See + * /usr/include/machine/soundcard.h + */ + sc->sc_mix_info |= (1 << mc->ctl); + } + if ((mc->ctl == SOUND_MIXER_NRDEVICES) && + (mc->type == MIX_SELECTOR)) { + + for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { + if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) { + continue; + } + sc->sc_recsrc_info |= 1 << mc->slctrtype[i - 1]; + } + } + } + return; +} + +int +uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m) +{ + DPRINTF("\n"); + + if (usb2_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index, + sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc, + mixer_get_lock(m))) { + DPRINTFN(0, "could not allocate USB " + "transfer for audio mixer!\n"); + return (ENOMEM); + } + if (!(sc->sc_mix_info & SOUND_MASK_VOLUME)) { + mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); + mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); + } + mix_setdevs(m, sc->sc_mix_info); + mix_setrecdevs(m, sc->sc_recsrc_info); + return (0); +} + +int +uaudio_mixer_uninit_sub(struct uaudio_softc *sc) +{ + DPRINTF("\n"); + + usb2_transfer_unsetup(sc->sc_mixer_xfer, 1); + + return (0); +} + +void +uaudio_mixer_set(struct uaudio_softc *sc, unsigned type, + unsigned left, unsigned right) +{ + struct uaudio_mixer_node *mc; + + for (mc = sc->sc_mixer_root; mc; + mc = mc->next) { + + if (mc->ctl == type) { + if (mc->nchan == 2) { + /* set Right */ + uaudio_mixer_ctl_set(sc, mc, 1, (int)(right * 255) / 100); + } + /* set Left or Mono */ + uaudio_mixer_ctl_set(sc, mc, 0, (int)(left * 255) / 100); + } + } + return; +} + +uint32_t +uaudio_mixer_setrecsrc(struct uaudio_softc *sc, uint32_t src) +{ + struct uaudio_mixer_node *mc; + uint32_t mask; + uint32_t temp; + int32_t i; + + for (mc = sc->sc_mixer_root; mc; + mc = mc->next) { + + if ((mc->ctl == SOUND_MIXER_NRDEVICES) && + (mc->type == MIX_SELECTOR)) { + + /* compute selector mask */ + + mask = 0; + for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { + mask |= (1 << mc->slctrtype[i - 1]); + } + + temp = mask & src; + if (temp == 0) { + continue; + } + /* find the first set bit */ + temp = (-temp) & temp; + + /* update "src" */ + src &= ~mask; + src |= temp; + + for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { + if (temp != (1 << mc->slctrtype[i - 1])) { + continue; + } + uaudio_mixer_ctl_set(sc, mc, 0, i); + break; + } + } + } + return (src); +} + +/*========================================================================* + * MIDI support routines + *========================================================================*/ + +static void +umidi_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umidi_chan *chan = xfer->priv_sc; + struct usb2_xfer *xfer_other = chan->xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + chan->flags &= ~UMIDI_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +umidi_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct umidi_chan *chan = xfer->priv_sc; + struct umidi_sub_chan *sub; + uint8_t buf[1]; + uint8_t cmd_len; + uint8_t cn; + uint16_t pos; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen=%d bytes\n", xfer->actlen); + + if (xfer->actlen == 0) { + /* should not happen */ + goto tr_error; + } + pos = 0; + + while (xfer->actlen >= 4) { + + usb2_copy_out(xfer->frbuffers, pos, buf, 1); + + cmd_len = umidi_cmd_to_len[buf[0] & 0xF]; /* command length */ + cn = buf[0] >> 4; /* cable number */ + sub = &chan->sub[cn]; + + if (cmd_len && (cn < chan->max_cable) && sub->read_open) { + usb2_fifo_put_data(sub->fifo.fp[USB_FIFO_RX], xfer->frbuffers, + pos + 1, cmd_len, 1); + } else { + /* ignore the command */ + } + + xfer->actlen -= 4; + pos += 4; + } + + case USB_ST_SETUP: + DPRINTF("start\n"); + + if (chan->flags & UMIDI_FLAG_READ_STALL) { + usb2_transfer_start(chan->xfer[3]); + return; + } + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + return; + + default: +tr_error: + + DPRINTF("error=%s\n", usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + chan->flags |= UMIDI_FLAG_READ_STALL; + usb2_transfer_start(chan->xfer[3]); + } + return; + + } +} + +static void +umidi_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct umidi_chan *chan = xfer->priv_sc; + struct usb2_xfer *xfer_other = chan->xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + chan->flags &= ~UMIDI_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +/* + * The following statemachine, that converts MIDI commands to + * USB MIDI packets, derives from Linux's usbmidi.c, which + * was written by "Clemens Ladisch": + * + * Returns: + * 0: No command + * Else: Command is complete + */ +static uint8_t +umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b) +{ + uint8_t p0 = (cn << 4); + + if (b >= 0xf8) { + sub->temp_0[0] = p0 | 0x0f; + sub->temp_0[1] = b; + sub->temp_0[2] = 0; + sub->temp_0[3] = 0; + sub->temp_cmd = sub->temp_0; + return (1); + + } else if (b >= 0xf0) { + switch (b) { + case 0xf0: /* system exclusive begin */ + sub->temp_1[1] = b; + sub->state = UMIDI_ST_SYSEX_1; + break; + case 0xf1: /* MIDI time code */ + case 0xf3: /* song select */ + sub->temp_1[1] = b; + sub->state = UMIDI_ST_1PARAM; + break; + case 0xf2: /* song position pointer */ + sub->temp_1[1] = b; + sub->state = UMIDI_ST_2PARAM_1; + break; + case 0xf4: /* unknown */ + case 0xf5: /* unknown */ + sub->state = UMIDI_ST_UNKNOWN; + break; + case 0xf6: /* tune request */ + sub->temp_1[0] = p0 | 0x05; + sub->temp_1[1] = 0xf6; + sub->temp_1[2] = 0; + sub->temp_1[3] = 0; + sub->temp_cmd = sub->temp_1; + sub->state = UMIDI_ST_UNKNOWN; + return (1); + + case 0xf7: /* system exclusive end */ + switch (sub->state) { + case UMIDI_ST_SYSEX_0: + sub->temp_1[0] = p0 | 0x05; + sub->temp_1[1] = 0xf7; + sub->temp_1[2] = 0; + sub->temp_1[3] = 0; + sub->temp_cmd = sub->temp_1; + sub->state = UMIDI_ST_UNKNOWN; + return (1); + case UMIDI_ST_SYSEX_1: + sub->temp_1[0] = p0 | 0x06; + sub->temp_1[2] = 0xf7; + sub->temp_1[3] = 0; + sub->temp_cmd = sub->temp_1; + sub->state = UMIDI_ST_UNKNOWN; + return (1); + case UMIDI_ST_SYSEX_2: + sub->temp_1[0] = p0 | 0x07; + sub->temp_1[3] = 0xf7; + sub->temp_cmd = sub->temp_1; + sub->state = UMIDI_ST_UNKNOWN; + return (1); + } + sub->state = UMIDI_ST_UNKNOWN; + break; + } + } else if (b >= 0x80) { + sub->temp_1[1] = b; + if ((b >= 0xc0) && (b <= 0xdf)) { + sub->state = UMIDI_ST_1PARAM; + } else { + sub->state = UMIDI_ST_2PARAM_1; + } + } else { /* b < 0x80 */ + switch (sub->state) { + case UMIDI_ST_1PARAM: + if (sub->temp_1[1] < 0xf0) { + p0 |= sub->temp_1[1] >> 4; + } else { + p0 |= 0x02; + sub->state = UMIDI_ST_UNKNOWN; + } + sub->temp_1[0] = p0; + sub->temp_1[2] = b; + sub->temp_1[3] = 0; + sub->temp_cmd = sub->temp_1; + return (1); + case UMIDI_ST_2PARAM_1: + sub->temp_1[2] = b; + sub->state = UMIDI_ST_2PARAM_2; + break; + case UMIDI_ST_2PARAM_2: + if (sub->temp_1[1] < 0xf0) { + p0 |= sub->temp_1[1] >> 4; + sub->state = UMIDI_ST_2PARAM_1; + } else { + p0 |= 0x03; + sub->state = UMIDI_ST_UNKNOWN; + } + sub->temp_1[0] = p0; + sub->temp_1[3] = b; + sub->temp_cmd = sub->temp_1; + return (1); + case UMIDI_ST_SYSEX_0: + sub->temp_1[1] = b; + sub->state = UMIDI_ST_SYSEX_1; + break; + case UMIDI_ST_SYSEX_1: + sub->temp_1[2] = b; + sub->state = UMIDI_ST_SYSEX_2; + break; + case UMIDI_ST_SYSEX_2: + sub->temp_1[0] = p0 | 0x04; + sub->temp_1[3] = b; + sub->temp_cmd = sub->temp_1; + sub->state = UMIDI_ST_SYSEX_0; + return (1); + } + } + return (0); +} + +static void +umidi_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct umidi_chan *chan = xfer->priv_sc; + struct umidi_sub_chan *sub; + uint32_t actlen; + uint16_t total_length; + uint8_t buf; + uint8_t start_cable; + uint8_t tr_any; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("actlen=%d bytes\n", xfer->actlen); + + case USB_ST_SETUP: + + DPRINTF("start\n"); + + if (chan->flags & UMIDI_FLAG_WRITE_STALL) { + usb2_transfer_start(chan->xfer[2]); + return; + } + total_length = 0; /* reset */ + + start_cable = chan->curr_cable; + + tr_any = 0; + + while (1) { + + /* round robin de-queueing */ + + sub = &chan->sub[chan->curr_cable]; + + if (sub->write_open) { + usb2_fifo_get_data(sub->fifo.fp[USB_FIFO_TX], + xfer->frbuffers, total_length, + 1, &actlen, 0); + } else { + actlen = 0; + } + + if (actlen) { + usb2_copy_out(xfer->frbuffers, total_length, &buf, 1); + + tr_any = 1; + + DPRINTF("byte=0x%02x\n", buf); + + if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) { + + DPRINTF("sub= %02x %02x %02x %02x\n", + sub->temp_cmd[0], sub->temp_cmd[1], + sub->temp_cmd[2], sub->temp_cmd[3]); + + usb2_copy_in(xfer->frbuffers, total_length, + sub->temp_cmd, 4); + + total_length += 4; + + if (total_length >= UMIDI_BULK_SIZE) { + break; + } + } else { + continue; + } + } + chan->curr_cable++; + if (chan->curr_cable >= chan->max_cable) { + chan->curr_cable = 0; + } + if (chan->curr_cable == start_cable) { + if (tr_any == 0) { + break; + } + tr_any = 0; + } + } + + if (total_length) { + xfer->frlengths[0] = total_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + + DPRINTF("error=%s\n", usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + chan->flags |= UMIDI_FLAG_WRITE_STALL; + usb2_transfer_start(chan->xfer[2]); + } + return; + + } +} + +static struct umidi_sub_chan * +umidi_sub_by_fifo(struct usb2_fifo *fifo) +{ + struct umidi_chan *chan = fifo->priv_sc0; + struct umidi_sub_chan *sub; + uint32_t n; + + for (n = 0; n < UMIDI_CABLES_MAX; n++) { + sub = &chan->sub[n]; + if ((sub->fifo.fp[USB_FIFO_RX] == fifo) || + (sub->fifo.fp[USB_FIFO_TX] == fifo)) { + return (sub); + } + } + + panic("%s:%d cannot find usb2_fifo!\n", + __FILE__, __LINE__); + + return (NULL); +} + +static void +umidi_start_read(struct usb2_fifo *fifo) +{ + struct umidi_chan *chan = fifo->priv_sc0; + + usb2_transfer_start(chan->xfer[1]); + return; +} + +static void +umidi_stop_read(struct usb2_fifo *fifo) +{ + struct umidi_chan *chan = fifo->priv_sc0; + struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); + + DPRINTF("\n"); + + sub->read_open = 0; + + if (--(chan->read_open_refcount) == 0) { + /* + * XXX don't stop the read transfer here, hence that causes + * problems with some MIDI adapters + */ + DPRINTF("(stopping read transfer)\n"); + } + return; +} + +static void +umidi_start_write(struct usb2_fifo *fifo) +{ + struct umidi_chan *chan = fifo->priv_sc0; + + usb2_transfer_start(chan->xfer[0]); + return; +} + +static void +umidi_stop_write(struct usb2_fifo *fifo) +{ + struct umidi_chan *chan = fifo->priv_sc0; + struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); + + DPRINTF("\n"); + + sub->write_open = 0; + + if (--(chan->write_open_refcount) == 0) { + DPRINTF("(stopping write transfer)\n"); + usb2_transfer_stop(chan->xfer[2]); + usb2_transfer_stop(chan->xfer[0]); + } + return; +} + +static int +umidi_open(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct umidi_chan *chan = fifo->priv_sc0; + struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); + + if (fflags & FREAD) { + if (usb2_fifo_alloc_buffer(fifo, 4, (1024 / 4))) { + return (ENOMEM); + } + mtx_lock(fifo->priv_mtx); + chan->read_open_refcount++; + sub->read_open = 1; + mtx_unlock(fifo->priv_mtx); + } + if (fflags & FWRITE) { + if (usb2_fifo_alloc_buffer(fifo, 32, (1024 / 32))) { + return (ENOMEM); + } + /* clear stall first */ + mtx_lock(fifo->priv_mtx); + chan->flags |= UMIDI_FLAG_WRITE_STALL; + chan->write_open_refcount++; + sub->write_open = 1; + + /* reset */ + sub->state = UMIDI_ST_UNKNOWN; + mtx_unlock(fifo->priv_mtx); + } + return (0); /* success */ +} + +static void +umidi_close(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + if (fflags & FREAD) { + usb2_fifo_free_buffer(fifo); + } + if (fflags & FWRITE) { + usb2_fifo_free_buffer(fifo); + } + return; +} + + +static int +umidi_ioctl(struct usb2_fifo *fifo, u_long cmd, void *data, + int fflags, struct thread *td) +{ + return (ENODEV); +} + +static void +umidi_init(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + struct umidi_chan *chan = &sc->sc_midi_chan; + + mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE); + return; +} + +static struct usb2_fifo_methods umidi_fifo_methods = { + .f_start_read = &umidi_start_read, + .f_start_write = &umidi_start_write, + .f_stop_read = &umidi_stop_read, + .f_stop_write = &umidi_stop_write, + .f_open = &umidi_open, + .f_close = &umidi_close, + .f_ioctl = &umidi_ioctl, + .basename[0] = "umidi", +}; + +static int32_t +umidi_probe(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct umidi_chan *chan = &sc->sc_midi_chan; + struct umidi_sub_chan *sub; + int unit = device_get_unit(dev); + int error; + uint32_t n; + + if (usb2_set_alt_interface_index(sc->sc_udev, chan->iface_index, + chan->iface_alt_index)) { + DPRINTF("setting of alternate index failed!\n"); + goto detach; + } + usb2_set_parent_iface(sc->sc_udev, chan->iface_index, sc->sc_mixer_iface_index); + + error = usb2_transfer_setup(uaa->device, &chan->iface_index, + chan->xfer, umidi_config, UMIDI_N_TRANSFER, + chan, &chan->mtx); + if (error) { + DPRINTF("error=%s\n", usb2_errstr(error)); + goto detach; + } + if ((chan->max_cable > UMIDI_CABLES_MAX) || + (chan->max_cable == 0)) { + chan->max_cable = UMIDI_CABLES_MAX; + } + /* set interface permissions */ + usb2_set_iface_perm(sc->sc_udev, chan->iface_index, + UID_ROOT, GID_OPERATOR, 0644); + + for (n = 0; n < chan->max_cable; n++) { + + sub = &chan->sub[n]; + + error = usb2_fifo_attach(sc->sc_udev, chan, &chan->mtx, + &umidi_fifo_methods, &sub->fifo, unit, n, + chan->iface_index); + if (error) { + goto detach; + } + } + + mtx_lock(&chan->mtx); + + /* clear stall first */ + chan->flags |= UMIDI_FLAG_READ_STALL; + + /* + * NOTE: at least one device will not work properly unless + * the BULK pipe is open all the time. + */ + usb2_transfer_start(chan->xfer[1]); + + mtx_unlock(&chan->mtx); + + return (0); /* success */ + +detach: + return (ENXIO); /* failure */ +} + +static int32_t +umidi_detach(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + struct umidi_chan *chan = &sc->sc_midi_chan; + uint32_t n; + + for (n = 0; n < UMIDI_CABLES_MAX; n++) { + usb2_fifo_detach(&chan->sub[n].fifo); + } + + mtx_lock(&chan->mtx); + + usb2_transfer_stop(chan->xfer[3]); + usb2_transfer_stop(chan->xfer[1]); + + mtx_unlock(&chan->mtx); + + usb2_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER); + + mtx_destroy(&chan->mtx); + + return (0); +} + +DRIVER_MODULE(uaudio, ushub, uaudio_driver, uaudio_devclass, NULL, 0); +MODULE_DEPEND(uaudio, usb2_sound, 1, 1, 1); +MODULE_DEPEND(uaudio, usb2_core, 1, 1, 1); +MODULE_DEPEND(uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(uaudio, 1); diff --git a/sys/dev/usb2/sound/uaudio2.h b/sys/dev/usb2/sound/uaudio2.h new file mode 100644 index 000000000000..f01d1c70937f --- /dev/null +++ b/sys/dev/usb2/sound/uaudio2.h @@ -0,0 +1,55 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2000-2002 Hiroyuki Aizu + * + * 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. + */ + +/* prototypes from "uaudio.c" used by "uaudio_pcm.c" */ + +struct uaudio_chan; +struct uaudio_softc; +struct snd_dbuf; +struct snd_mixer; +struct pcm_channel; + +extern int uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class); +extern int uaudio_detach_sub(device_t dev); +extern void *uaudio_chan_init(struct uaudio_softc *sc, struct snd_dbuf *b, struct pcm_channel *c, int dir); +extern int uaudio_chan_free(struct uaudio_chan *ch); +extern int uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize); +extern int uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize, uint32_t blockcount); +extern int uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed); +extern int uaudio_chan_getptr(struct uaudio_chan *ch); +extern struct pcmchan_caps *uaudio_chan_getcaps(struct uaudio_chan *ch); +extern int uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format); +extern int uaudio_chan_start(struct uaudio_chan *ch); +extern int uaudio_chan_stop(struct uaudio_chan *ch); +extern int uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m); +extern int uaudio_mixer_uninit_sub(struct uaudio_softc *sc); +extern void uaudio_mixer_set(struct uaudio_softc *sc, unsigned type, unsigned left, unsigned right); +extern uint32_t uaudio_mixer_setrecsrc(struct uaudio_softc *sc, uint32_t src); + +int uaudio_get_vendor(device_t dev); +int uaudio_get_product(device_t dev); +int uaudio_get_release(device_t dev); diff --git a/sys/dev/usb2/sound/uaudio2_pcm.c b/sys/dev/usb2/sound/uaudio2_pcm.c new file mode 100644 index 000000000000..c073c19d3644 --- /dev/null +++ b/sys/dev/usb2/sound/uaudio2_pcm.c @@ -0,0 +1,234 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2000-2002 Hiroyuki Aizu + * Copyright (c) 2006 Hans Petter Selasky + * + * 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. + */ + + +#include +#include +#include + +#include + +#include "mixer_if.h" + +/************************************************************/ +static void * +ua_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + return (uaudio_chan_init(devinfo, b, c, dir)); +} + +static int +ua_chan_free(kobj_t obj, void *data) +{ + return (uaudio_chan_free(data)); +} + +static int +ua_chan_setformat(kobj_t obj, void *data, uint32_t format) +{ + /* + * At this point, no need to query as we + * shouldn't select an unsorted format + */ + return (uaudio_chan_set_param_format(data, format)); +} + +static int +ua_chan_setspeed(kobj_t obj, void *data, uint32_t speed) +{ + return (uaudio_chan_set_param_speed(data, speed)); +} + +static int +ua_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) +{ + return (uaudio_chan_set_param_blocksize(data, blocksize)); +} + +static int +ua_chan_setfragments(kobj_t obj, void *data, uint32_t blocksize, uint32_t blockcount) +{ + return (uaudio_chan_set_param_fragments(data, blocksize, blockcount)); +} + +static int +ua_chan_trigger(kobj_t obj, void *data, int go) +{ + if (!PCMTRIG_COMMON(go)) { + return (0); + } + if (go == PCMTRIG_START) { + return (uaudio_chan_start(data)); + } else { + return (uaudio_chan_stop(data)); + } +} + +static int +ua_chan_getptr(kobj_t obj, void *data) +{ + return (uaudio_chan_getptr(data)); +} + +static struct pcmchan_caps * +ua_chan_getcaps(kobj_t obj, void *data) +{ + return (uaudio_chan_getcaps(data)); +} + +static kobj_method_t ua_chan_methods[] = { + KOBJMETHOD(channel_init, ua_chan_init), + KOBJMETHOD(channel_free, ua_chan_free), + KOBJMETHOD(channel_setformat, ua_chan_setformat), + KOBJMETHOD(channel_setspeed, ua_chan_setspeed), + KOBJMETHOD(channel_setblocksize, ua_chan_setblocksize), + KOBJMETHOD(channel_setfragments, ua_chan_setfragments), + KOBJMETHOD(channel_trigger, ua_chan_trigger), + KOBJMETHOD(channel_getptr, ua_chan_getptr), + KOBJMETHOD(channel_getcaps, ua_chan_getcaps), + {0, 0} +}; + +CHANNEL_DECLARE(ua_chan); + +/************************************************************/ +static int +ua_mixer_init(struct snd_mixer *m) +{ + return (uaudio_mixer_init_sub(mix_getdevinfo(m), m)); +} + +static int +ua_mixer_set(struct snd_mixer *m, unsigned type, unsigned left, unsigned right) +{ + struct mtx *mtx = mixer_get_lock(m); + uint8_t do_unlock; + + if (mtx_owned(mtx)) { + do_unlock = 0; + } else { + do_unlock = 1; + mtx_lock(mtx); + } + uaudio_mixer_set(mix_getdevinfo(m), type, left, right); + if (do_unlock) { + mtx_unlock(mtx); + } + return (left | (right << 8)); +} + +static int +ua_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) +{ + struct mtx *mtx = mixer_get_lock(m); + int retval; + uint8_t do_unlock; + + if (mtx_owned(mtx)) { + do_unlock = 0; + } else { + do_unlock = 1; + mtx_lock(mtx); + } + retval = uaudio_mixer_setrecsrc(mix_getdevinfo(m), src); + if (do_unlock) { + mtx_unlock(mtx); + } + return (retval); +} + +static int +ua_mixer_uninit(struct snd_mixer *m) +{ + return (uaudio_mixer_uninit_sub(mix_getdevinfo(m))); +} + +static kobj_method_t ua_mixer_methods[] = { + KOBJMETHOD(mixer_init, ua_mixer_init), + KOBJMETHOD(mixer_uninit, ua_mixer_uninit), + KOBJMETHOD(mixer_set, ua_mixer_set), + KOBJMETHOD(mixer_setrecsrc, ua_mixer_setrecsrc), + + {0, 0} +}; + +MIXER_DECLARE(ua_mixer); +/************************************************************/ + + +static int +ua_probe(device_t dev) +{ + struct sndcard_func *func; + + /* the parent device has already been probed */ + + func = device_get_ivars(dev); + + if ((func == NULL) || + (func->func != SCF_PCM)) { + return (ENXIO); + } + device_set_desc(dev, "USB audio"); + + return (BUS_PROBE_DEFAULT); +} + +static int +ua_attach(device_t dev) +{ + return (uaudio_attach_sub(dev, &ua_mixer_class, &ua_chan_class)); +} + +static int +ua_detach(device_t dev) +{ + return (uaudio_detach_sub(dev)); +} + +/************************************************************/ + +static device_method_t ua_pcm_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ua_probe), + DEVMETHOD(device_attach, ua_attach), + DEVMETHOD(device_detach, ua_detach), + + {0, 0} +}; + +static driver_t ua_pcm_driver = { + "pcm", + ua_pcm_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(ua_pcm, uaudio, ua_pcm_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(ua_pcm, uaudio, 1, 1, 1); +MODULE_DEPEND(ua_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(ua_pcm, 1); diff --git a/sys/dev/usb2/sound/uaudio2_reg.h b/sys/dev/usb2/sound/uaudio2_reg.h new file mode 100644 index 000000000000..2bf68a17c609 --- /dev/null +++ b/sys/dev/usb2/sound/uaudio2_reg.h @@ -0,0 +1,406 @@ +/* $NetBSD: uaudioreg.h,v 1.12 2004/11/05 19:08:29 kent Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 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. + */ + +#define UAUDIO_VERSION 0x100 + +#define UDESC_CS_CONFIG 0x22 +#define UDESC_CS_STRING 0x23 +#define UDESC_CS_INTERFACE 0x24 +#define UDESC_CS_ENDPOINT 0x25 + +#define UDESCSUB_AC_HEADER 1 +#define UDESCSUB_AC_INPUT 2 +#define UDESCSUB_AC_OUTPUT 3 +#define UDESCSUB_AC_MIXER 4 +#define UDESCSUB_AC_SELECTOR 5 +#define UDESCSUB_AC_FEATURE 6 +#define UDESCSUB_AC_PROCESSING 7 +#define UDESCSUB_AC_EXTENSION 8 + +/* The first fields are identical to struct usb2_endpoint_descriptor */ +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bEndpointAddress; + uByte bmAttributes; + uWord wMaxPacketSize; + uByte bInterval; + /* + * The following two entries are only used by the Audio Class. + * And according to the specs the Audio Class is the only one + * allowed to extend the endpoint descriptor. + * Who knows what goes on in the minds of the people in the USB + * standardization? :-( + */ + uByte bRefresh; + uByte bSynchAddress; +} __packed usb2_endpoint_descriptor_audio_t; + +struct usb2_audio_control_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uWord bcdADC; + uWord wTotalLength; + uByte bInCollection; + uByte baInterfaceNr[1]; +} __packed; + +struct usb2_audio_streaming_interface_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bTerminalLink; + uByte bDelay; + uWord wFormatTag; +} __packed; + +struct usb2_audio_streaming_endpoint_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmAttributes; +#define UA_SED_FREQ_CONTROL 0x01 +#define UA_SED_PITCH_CONTROL 0x02 +#define UA_SED_MAXPACKETSONLY 0x80 + uByte bLockDelayUnits; + uWord wLockDelay; +} __packed; + +struct usb2_audio_streaming_type1_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bFormatType; + uByte bNrChannels; + uByte bSubFrameSize; + uByte bBitResolution; + uByte bSamFreqType; +#define UA_SAMP_CONTNUOUS 0 + uByte tSamFreq[0]; +#define UA_GETSAMP(p, n) (((p)->tSamFreq[((n)*3)+0]) | \ + ((p)->tSamFreq[((n)*3)+1] << 8) | \ + ((p)->tSamFreq[((n)*3)+2] << 16)) +#define UA_SAMP_LO(p) UA_GETSAMP(p, 0) +#define UA_SAMP_HI(p) UA_GETSAMP(p, 1) +} __packed; + +struct usb2_audio_cluster { + uByte bNrChannels; + uWord wChannelConfig; +#define UA_CHANNEL_LEFT 0x0001 +#define UA_CHANNEL_RIGHT 0x0002 +#define UA_CHANNEL_CENTER 0x0004 +#define UA_CHANNEL_LFE 0x0008 +#define UA_CHANNEL_L_SURROUND 0x0010 +#define UA_CHANNEL_R_SURROUND 0x0020 +#define UA_CHANNEL_L_CENTER 0x0040 +#define UA_CHANNEL_R_CENTER 0x0080 +#define UA_CHANNEL_SURROUND 0x0100 +#define UA_CHANNEL_L_SIDE 0x0200 +#define UA_CHANNEL_R_SIDE 0x0400 +#define UA_CHANNEL_TOP 0x0800 + uByte iChannelNames; +} __packed; + +/* Shared by all units and terminals */ +struct usb2_audio_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; +}; + +/* UDESCSUB_AC_INPUT */ +struct usb2_audio_input_terminal { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bTerminalId; + uWord wTerminalType; + uByte bAssocTerminal; + uByte bNrChannels; + uWord wChannelConfig; + uByte iChannelNames; +/* uByte iTerminal; */ +} __packed; + +/* UDESCSUB_AC_OUTPUT */ +struct usb2_audio_output_terminal { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bTerminalId; + uWord wTerminalType; + uByte bAssocTerminal; + uByte bSourceId; + uByte iTerminal; +} __packed; + +/* UDESCSUB_AC_MIXER */ +struct usb2_audio_mixer_unit_0 { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uByte bNrInPins; + uByte baSourceId[0]; /* [bNrInPins] */ + /* struct usb2_audio_mixer_unit_1 */ +} __packed; +struct usb2_audio_mixer_unit_1 { + uByte bNrChannels; + uWord wChannelConfig; + uByte iChannelNames; + uByte bmControls[0]; /* [see source code] */ + /* uByte iMixer; */ +} __packed; + +/* UDESCSUB_AC_SELECTOR */ +struct usb2_audio_selector_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uByte bNrInPins; + uByte baSourceId[0]; /* [bNrInPins] */ + /* uByte iSelector; */ +} __packed; + +/* UDESCSUB_AC_FEATURE */ +struct usb2_audio_feature_unit { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uByte bSourceId; + uByte bControlSize; + uByte bmaControls[0]; /* [bControlSize * x] */ + /* uByte iFeature; */ +} __packed; + +/* UDESCSUB_AC_PROCESSING */ +struct usb2_audio_processing_unit_0 { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uWord wProcessType; + uByte bNrInPins; + uByte baSourceId[0]; /* [bNrInPins] */ + /* struct usb2_audio_processing_unit_1 */ +} __packed; +struct usb2_audio_processing_unit_1 { + uByte bNrChannels; + uWord wChannelConfig; + uByte iChannelNames; + uByte bControlSize; + uByte bmControls[0]; /* [bControlSize] */ +#define UA_PROC_ENABLE_MASK 1 +} __packed; + +struct usb2_audio_processing_unit_updown { + uByte iProcessing; + uByte bNrModes; + uWord waModes[0]; /* [bNrModes] */ +} __packed; + +/* UDESCSUB_AC_EXTENSION */ +struct usb2_audio_extension_unit_0 { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bUnitId; + uWord wExtensionCode; + uByte bNrInPins; + uByte baSourceId[0]; /* [bNrInPins] */ + /* struct usb2_audio_extension_unit_1 */ +} __packed; +struct usb2_audio_extension_unit_1 { + uByte bNrChannels; + uWord wChannelConfig; + uByte iChannelNames; + uByte bControlSize; + uByte bmControls[0]; /* [bControlSize] */ +#define UA_EXT_ENABLE_MASK 1 +#define UA_EXT_ENABLE 1 + /* uByte iExtension; */ +} __packed; + +/* USB terminal types */ +#define UAT_UNDEFINED 0x0100 +#define UAT_STREAM 0x0101 +#define UAT_VENDOR 0x01ff +/* input terminal types */ +#define UATI_UNDEFINED 0x0200 +#define UATI_MICROPHONE 0x0201 +#define UATI_DESKMICROPHONE 0x0202 +#define UATI_PERSONALMICROPHONE 0x0203 +#define UATI_OMNIMICROPHONE 0x0204 +#define UATI_MICROPHONEARRAY 0x0205 +#define UATI_PROCMICROPHONEARR 0x0206 +/* output terminal types */ +#define UATO_UNDEFINED 0x0300 +#define UATO_SPEAKER 0x0301 +#define UATO_HEADPHONES 0x0302 +#define UATO_DISPLAYAUDIO 0x0303 +#define UATO_DESKTOPSPEAKER 0x0304 +#define UATO_ROOMSPEAKER 0x0305 +#define UATO_COMMSPEAKER 0x0306 +#define UATO_SUBWOOFER 0x0307 +/* bidir terminal types */ +#define UATB_UNDEFINED 0x0400 +#define UATB_HANDSET 0x0401 +#define UATB_HEADSET 0x0402 +#define UATB_SPEAKERPHONE 0x0403 +#define UATB_SPEAKERPHONEESUP 0x0404 +#define UATB_SPEAKERPHONEECANC 0x0405 +/* telephony terminal types */ +#define UATT_UNDEFINED 0x0500 +#define UATT_PHONELINE 0x0501 +#define UATT_TELEPHONE 0x0502 +#define UATT_DOWNLINEPHONE 0x0503 +/* external terminal types */ +#define UATE_UNDEFINED 0x0600 +#define UATE_ANALOGCONN 0x0601 +#define UATE_DIGITALAUIFC 0x0602 +#define UATE_LINECONN 0x0603 +#define UATE_LEGACYCONN 0x0604 +#define UATE_SPDIF 0x0605 +#define UATE_1394DA 0x0606 +#define UATE_1394DV 0x0607 +/* embedded function terminal types */ +#define UATF_UNDEFINED 0x0700 +#define UATF_CALIBNOISE 0x0701 +#define UATF_EQUNOISE 0x0702 +#define UATF_CDPLAYER 0x0703 +#define UATF_DAT 0x0704 +#define UATF_DCC 0x0705 +#define UATF_MINIDISK 0x0706 +#define UATF_ANALOGTAPE 0x0707 +#define UATF_PHONOGRAPH 0x0708 +#define UATF_VCRAUDIO 0x0709 +#define UATF_VIDEODISCAUDIO 0x070a +#define UATF_DVDAUDIO 0x070b +#define UATF_TVTUNERAUDIO 0x070c +#define UATF_SATELLITE 0x070d +#define UATF_CABLETUNER 0x070e +#define UATF_DSS 0x070f +#define UATF_RADIORECV 0x0710 +#define UATF_RADIOXMIT 0x0711 +#define UATF_MULTITRACK 0x0712 +#define UATF_SYNTHESIZER 0x0713 + + +#define SET_CUR 0x01 +#define GET_CUR 0x81 +#define SET_MIN 0x02 +#define GET_MIN 0x82 +#define SET_MAX 0x03 +#define GET_MAX 0x83 +#define SET_RES 0x04 +#define GET_RES 0x84 +#define SET_MEM 0x05 +#define GET_MEM 0x85 +#define GET_STAT 0xff + +#define MUTE_CONTROL 0x01 +#define VOLUME_CONTROL 0x02 +#define BASS_CONTROL 0x03 +#define MID_CONTROL 0x04 +#define TREBLE_CONTROL 0x05 +#define GRAPHIC_EQUALIZER_CONTROL 0x06 +#define AGC_CONTROL 0x07 +#define DELAY_CONTROL 0x08 +#define BASS_BOOST_CONTROL 0x09 +#define LOUDNESS_CONTROL 0x0a + +#define FU_MASK(u) (1 << ((u)-1)) + +#define MASTER_CHAN 0 + +#define AS_GENERAL 1 +#define FORMAT_TYPE 2 +#define FORMAT_SPECIFIC 3 + +#define UA_FMT_PCM 1 +#define UA_FMT_PCM8 2 +#define UA_FMT_IEEE_FLOAT 3 +#define UA_FMT_ALAW 4 +#define UA_FMT_MULAW 5 +#define UA_FMT_MPEG 0x1001 +#define UA_FMT_AC3 0x1002 + +#define SAMPLING_FREQ_CONTROL 0x01 +#define PITCH_CONTROL 0x02 + +#define FORMAT_TYPE_UNDEFINED 0 +#define FORMAT_TYPE_I 1 +#define FORMAT_TYPE_II 2 +#define FORMAT_TYPE_III 3 + +#define UA_PROC_MASK(n) (1<< ((n)-1)) +#define PROCESS_UNDEFINED 0 +#define XX_ENABLE_CONTROL 1 +#define UPDOWNMIX_PROCESS 1 +#define UD_ENABLE_CONTROL 1 +#define UD_MODE_SELECT_CONTROL 2 +#define DOLBY_PROLOGIC_PROCESS 2 +#define DP_ENABLE_CONTROL 1 +#define DP_MODE_SELECT_CONTROL 2 +#define P3D_STEREO_EXTENDER_PROCESS 3 +#define P3D_ENABLE_CONTROL 1 +#define P3D_SPACIOUSNESS_CONTROL 2 +#define REVERBATION_PROCESS 4 +#define RV_ENABLE_CONTROL 1 +#define RV_LEVEL_CONTROL 2 +#define RV_TIME_CONTROL 3 +#define RV_FEEDBACK_CONTROL 4 +#define CHORUS_PROCESS 5 +#define CH_ENABLE_CONTROL 1 +#define CH_LEVEL_CONTROL 2 +#define CH_RATE_CONTROL 3 +#define CH_DEPTH_CONTROL 4 +#define DYN_RANGE_COMP_PROCESS 6 +#define DR_ENABLE_CONTROL 1 +#define DR_COMPRESSION_RATE_CONTROL 2 +#define DR_MAXAMPL_CONTROL 3 +#define DR_THRESHOLD_CONTROL 4 +#define DR_ATTACK_TIME_CONTROL 5 +#define DR_RELEASE_TIME_CONTROL 6 diff --git a/sys/dev/usb2/sound/usb2_sound.c b/sys/dev/usb2/sound/usb2_sound.c new file mode 100644 index 000000000000..0e6994baaa21 --- /dev/null +++ b/sys/dev/usb2/sound/usb2_sound.c @@ -0,0 +1,31 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_sound, 1); +MODULE_DEPEND(usb2_sound, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/sound/usb2_sound.h b/sys/dev/usb2/sound/usb2_sound.h new file mode 100644 index 000000000000..5b6ae152c4b8 --- /dev/null +++ b/sys/dev/usb2/sound/usb2_sound.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_SOUND_H_ +#define _USB2_SOUND_H_ + +#endif /* _USB2_SOUND_H_ */ diff --git a/sys/dev/usb2/storage/ata-usb2.c b/sys/dev/usb2/storage/ata-usb2.c new file mode 100644 index 000000000000..17a63c62135f --- /dev/null +++ b/sys/dev/usb2/storage/ata-usb2.c @@ -0,0 +1,1114 @@ +/*- + * Copyright (c) 2006 - 2008 Søren Schmidt + * All rights reserved. + * + * Copyright (c) 2006 Hans Petter Selasky + * 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, + * without modification, immediately at the beginning of the file. + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define ATAUSB_BULK_SIZE (1<<17) + +/* Command Block Wrapper */ +struct bbb_cbw { + uint8_t signature[4]; +#define CBWSIGNATURE 0x43425355 + + uint8_t tag[4]; + uint8_t transfer_length[4]; + uint8_t flags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + + uint8_t lun; + uint8_t length; +#define CBWCDBLENGTH 16 + + uint8_t cdb[CBWCDBLENGTH]; +} __packed; + +/* Command Status Wrapper */ +struct bbb_csw { + uint8_t signature[4]; +#define CSWSIGNATURE 0x53425355 + + uint8_t tag[4]; + uint8_t residue[4]; + uint8_t status; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed; + +/* USB-ATA 'controller' softc */ +struct atausb2_softc { + struct bbb_cbw cbw; + struct bbb_csw csw; + struct mtx locked_mtx; + + struct ata_channel *locked_ch; + struct ata_channel *restart_ch; + struct ata_request *ata_request; + +#define ATAUSB_T_BBB_RESET1 0 +#define ATAUSB_T_BBB_RESET2 1 +#define ATAUSB_T_BBB_RESET3 2 +#define ATAUSB_T_BBB_COMMAND 3 +#define ATAUSB_T_BBB_DATA_READ 4 +#define ATAUSB_T_BBB_DATA_RD_CS 5 +#define ATAUSB_T_BBB_DATA_WRITE 6 +#define ATAUSB_T_BBB_DATA_WR_CS 7 +#define ATAUSB_T_BBB_STATUS 8 +#define ATAUSB_T_BBB_MAX 9 + +#define ATAUSB_T_MAX ATAUSB_T_BBB_MAX + + struct usb2_xfer *xfer[ATAUSB_T_MAX]; + caddr_t ata_data; + device_t dev; + + uint32_t timeout; + uint32_t ata_donecount; + uint32_t ata_bytecount; + + uint8_t last_xfer_no; + uint8_t usb2_speed; + uint8_t intr_stalled; + uint8_t maxlun; + uint8_t iface_no; + uint8_t status_try; +}; + +static const int atausbdebug = 0; + +/* prototypes */ + +static device_probe_t atausb2_probe; +static device_attach_t atausb2_attach; +static device_detach_t atausb2_detach; + +static usb2_callback_t atausb2_t_bbb_reset1_callback; +static usb2_callback_t atausb2_t_bbb_reset2_callback; +static usb2_callback_t atausb2_t_bbb_reset3_callback; +static usb2_callback_t atausb2_t_bbb_command_callback; +static usb2_callback_t atausb2_t_bbb_data_read_callback; +static usb2_callback_t atausb2_t_bbb_data_rd_cs_callback; +static usb2_callback_t atausb2_t_bbb_data_write_callback; +static usb2_callback_t atausb2_t_bbb_data_wr_cs_callback; +static usb2_callback_t atausb2_t_bbb_status_callback; +static usb2_callback_t atausb2_tr_error; + +static void atausb2_cancel_request(struct atausb2_softc *sc); +static void atausb2_transfer_start(struct atausb2_softc *sc, uint8_t xfer_no); +static void atausb2_t_bbb_data_clear_stall_callback(struct usb2_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer); +static int ata_usbchannel_begin_transaction(struct ata_request *request); +static int ata_usbchannel_end_transaction(struct ata_request *request); + +static device_probe_t ata_usbchannel_probe; +static device_attach_t ata_usbchannel_attach; +static device_detach_t ata_usbchannel_detach; + +static ata_setmode_t ata_usbchannel_setmode; +static ata_locking_t ata_usbchannel_locking; + +/* + * USB frontend part + */ + +struct usb2_config atausb2_config[ATAUSB_T_BBB_MAX] = { + + [ATAUSB_T_BBB_RESET1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &atausb2_t_bbb_reset1_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 500, /* 500 milliseconds */ + }, + + [ATAUSB_T_BBB_RESET2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &atausb2_t_bbb_reset2_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 50, /* 50 milliseconds */ + }, + + [ATAUSB_T_BBB_RESET3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &atausb2_t_bbb_reset3_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 50, /* 50 milliseconds */ + }, + + [ATAUSB_T_BBB_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = sizeof(struct bbb_cbw), + .mh.flags = {}, + .mh.callback = &atausb2_t_bbb_command_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [ATAUSB_T_BBB_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = ATAUSB_BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &atausb2_t_bbb_data_read_callback, + .mh.timeout = 0, /* overwritten later */ + }, + + [ATAUSB_T_BBB_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &atausb2_t_bbb_data_rd_cs_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [ATAUSB_T_BBB_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = ATAUSB_BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .mh.callback = &atausb2_t_bbb_data_write_callback, + .mh.timeout = 0, /* overwritten later */ + }, + + [ATAUSB_T_BBB_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &atausb2_t_bbb_data_wr_cs_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [ATAUSB_T_BBB_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = sizeof(struct bbb_csw), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &atausb2_t_bbb_status_callback, + .mh.timeout = 5000, /* ms */ + }, +}; + +static devclass_t atausb2_devclass; + +static device_method_t atausb2_methods[] = { + DEVMETHOD(device_probe, atausb2_probe), + DEVMETHOD(device_attach, atausb2_attach), + DEVMETHOD(device_detach, atausb2_detach), + {0, 0} +}; + +static driver_t atausb2_driver = { + .name = "atausb", + .methods = atausb2_methods, + .size = sizeof(struct atausb2_softc), +}; + +DRIVER_MODULE(atausb, ushub, atausb2_driver, atausb2_devclass, 0, 0); +MODULE_DEPEND(atausb, usb2_storage, 1, 1, 1); +MODULE_DEPEND(atausb, usb2_core, 1, 1, 1); +MODULE_VERSION(atausb, 1); + +static int +atausb2_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_interface_descriptor *id; + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->use_generic == 0) { + /* give other drivers a try first */ + return (ENXIO); + } + id = usb2_get_interface_descriptor(uaa->iface); + if ((!id) || (id->bInterfaceClass != UICLASS_MASS)) { + return (ENXIO); + } + switch (id->bInterfaceSubClass) { + case UISUBCLASS_QIC157: + case UISUBCLASS_RBC: + case UISUBCLASS_SCSI: + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + case UISUBCLASS_UFI: + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_CBI: + case UIPROTO_MASS_CBI_I: + case UIPROTO_MASS_BBB: + case UIPROTO_MASS_BBB_OLD: + return (0); + default: + return (0); + } + break; + default: + return (0); + } +} + +static int +atausb2_attach(device_t dev) +{ + struct atausb2_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_interface_descriptor *id; + const char *proto, *subclass; + struct usb2_device_request request; + uint16_t i; + uint8_t maxlun; + uint8_t has_intr; + int err; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + sc->dev = dev; + sc->maxlun = 0; + sc->locked_ch = NULL; + sc->restart_ch = NULL; + sc->usb2_speed = usb2_get_speed(uaa->device); + mtx_init(&sc->locked_mtx, "ATAUSB lock", NULL, (MTX_DEF | MTX_RECURSE)); + + id = usb2_get_interface_descriptor(uaa->iface); + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_BBB: + case UIPROTO_MASS_BBB_OLD: + proto = "Bulk-Only"; + break; + case UIPROTO_MASS_CBI: + proto = "CBI"; + break; + case UIPROTO_MASS_CBI_I: + proto = "CBI with CCI"; + break; + default: + proto = "Unknown"; + } + + switch (id->bInterfaceSubClass) { + case UISUBCLASS_RBC: + subclass = "RBC"; + break; + case UISUBCLASS_QIC157: + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + subclass = "ATAPI"; + break; + case UISUBCLASS_SCSI: + subclass = "SCSI"; + break; + case UISUBCLASS_UFI: + subclass = "UFI"; + break; + default: + subclass = "Unknown"; + } + + has_intr = (id->bInterfaceProtocol == UIPROTO_MASS_CBI_I); + sc->iface_no = id->bInterfaceNumber; + + device_printf(dev, "using %s over %s\n", subclass, proto); + if (strcmp(proto, "Bulk-Only") || + (strcmp(subclass, "ATAPI") && strcmp(subclass, "SCSI"))) { + goto detach; + } + err = usb2_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, + sc->xfer, atausb2_config, ATAUSB_T_BBB_MAX, sc, + &sc->locked_mtx); + + /* skip reset first time */ + sc->last_xfer_no = ATAUSB_T_BBB_COMMAND; + + if (err) { + device_printf(sc->dev, "could not setup required " + "transfers, %s\n", usb2_errstr(err)); + goto detach; + } + /* get number of devices so we can add matching channels */ + request.bmRequestType = UT_READ_CLASS_INTERFACE; + request.bRequest = 0xfe; /* GET_MAX_LUN; */ + USETW(request.wValue, 0); + USETW(request.wIndex, sc->iface_no); + USETW(request.wLength, sizeof(maxlun)); + err = usb2_do_request(uaa->device, &Giant, &request, &maxlun); + + if (err) { + if (bootverbose) { + device_printf(sc->dev, "get maxlun not supported %s\n", + usb2_errstr(err)); + } + } else { + sc->maxlun = maxlun; + if (bootverbose) { + device_printf(sc->dev, "maxlun=%d\n", sc->maxlun); + } + } + + /* ata channels are children to this USB control device */ + for (i = 0; i <= sc->maxlun; i++) { + if (!device_add_child(sc->dev, "ata", + devclass_find_free_unit(ata_devclass, 2))) { + device_printf(sc->dev, "failed to attach ata child device\n"); + goto detach; + } + } + bus_generic_attach(sc->dev); + + return (0); + +detach: + atausb2_detach(dev); + return (ENXIO); +} + +static int +atausb2_detach(device_t dev) +{ + struct atausb2_softc *sc = device_get_softc(dev); + device_t *children; + int nchildren, i; + + /* teardown our statemachine */ + + usb2_transfer_unsetup(sc->xfer, ATAUSB_T_MAX); + + /* detach & delete all children, if any */ + + if (!device_get_children(dev, &children, &nchildren)) { + for (i = 0; i < nchildren; i++) { + device_delete_child(dev, children[i]); + } + free(children, M_TEMP); + } + mtx_destroy(&sc->locked_mtx); + return (0); +} + +static void +atausb2_transfer_start(struct atausb2_softc *sc, uint8_t xfer_no) +{ + if (atausbdebug) { + device_printf(sc->dev, "BBB transfer %d\n", xfer_no); + } + if (sc->xfer[xfer_no]) { + sc->last_xfer_no = xfer_no; + usb2_transfer_start(sc->xfer[xfer_no]); + } else { + atausb2_cancel_request(sc); + } + return; +} + +static void +atausb2_t_bbb_reset1_callback(struct usb2_xfer *xfer) +{ + struct atausb2_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + atausb2_transfer_start(sc, ATAUSB_T_BBB_RESET2); + return; + + case USB_ST_SETUP: + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = 0xff; /* bulk-only reset */ + USETW(req.wValue, 0); + req.wIndex[0] = sc->iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + xfer->frlengths[0] = sizeof(req); + xfer->nframes = 1; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + atausb2_tr_error(xfer); + return; + + } +} + +static void +atausb2_t_bbb_reset2_callback(struct usb2_xfer *xfer) +{ + atausb2_t_bbb_data_clear_stall_callback(xfer, ATAUSB_T_BBB_RESET3, + ATAUSB_T_BBB_DATA_READ); + return; +} + +static void +atausb2_t_bbb_reset3_callback(struct usb2_xfer *xfer) +{ + atausb2_t_bbb_data_clear_stall_callback(xfer, ATAUSB_T_BBB_COMMAND, + ATAUSB_T_BBB_DATA_WRITE); + return; +} + +static void +atausb2_t_bbb_data_clear_stall_callback(struct usb2_xfer *xfer, + uint8_t next_xfer, + uint8_t stall_xfer) +{ + struct atausb2_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + atausb2_transfer_start(sc, next_xfer); + return; + + case USB_ST_SETUP: + if (usb2_clear_stall_callback(xfer, sc->xfer[stall_xfer])) { + goto tr_transferred; + } + return; + + default: /* Error */ + atausb2_tr_error(xfer); + return; + + } +} + +static void +atausb2_t_bbb_command_callback(struct usb2_xfer *xfer) +{ + struct atausb2_softc *sc = xfer->priv_sc; + struct ata_request *request = sc->ata_request; + struct ata_channel *ch; + uint32_t tag; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + atausb2_transfer_start + (sc, ((request->flags & ATA_R_READ) ? ATAUSB_T_BBB_DATA_READ : + (request->flags & ATA_R_WRITE) ? ATAUSB_T_BBB_DATA_WRITE : + ATAUSB_T_BBB_STATUS)); + return; + + case USB_ST_SETUP: + + sc->status_try = 0; + + if (request) { + ch = device_get_softc(request->parent); + + sc->timeout = (request->timeout * 1000) + 5000; + + tag = UGETDW(sc->cbw.tag) + 1; + + USETDW(sc->cbw.signature, CBWSIGNATURE); + USETDW(sc->cbw.tag, tag); + USETDW(sc->cbw.transfer_length, request->bytecount); + sc->cbw.flags = (request->flags & ATA_R_READ) ? CBWFLAGS_IN : CBWFLAGS_OUT; + sc->cbw.lun = ch->unit; + sc->cbw.length = 16; + bzero(sc->cbw.cdb, 16); + bcopy(request->u.atapi.ccb, sc->cbw.cdb, 12); /* XXX SOS */ + + usb2_copy_in(xfer->frbuffers, 0, &sc->cbw, sizeof(sc->cbw)); + + xfer->frlengths[0] = sizeof(sc->cbw); + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + atausb2_tr_error(xfer); + return; + + } +} + +static void +atausb2_t_bbb_data_read_callback(struct usb2_xfer *xfer) +{ + struct atausb2_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + usb2_copy_out(xfer->frbuffers, 0, + sc->ata_data, xfer->actlen); + + sc->ata_bytecount -= xfer->actlen; + sc->ata_data += xfer->actlen; + sc->ata_donecount += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->ata_bytecount = 0; + } + case USB_ST_SETUP: + + if (atausbdebug > 1) { + device_printf(sc->dev, "%s: max_bulk=%d, ata_bytecount=%d\n", + __FUNCTION__, max_bulk, sc->ata_bytecount); + } + if (sc->ata_bytecount == 0) { + atausb2_transfer_start(sc, ATAUSB_T_BBB_STATUS); + return; + } + if (max_bulk > sc->ata_bytecount) { + max_bulk = sc->ata_bytecount; + } + xfer->timeout = sc->timeout; + xfer->frlengths[0] = max_bulk; + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + atausb2_tr_error(xfer); + } else { + atausb2_transfer_start(sc, ATAUSB_T_BBB_DATA_RD_CS); + } + return; + + } +} + +static void +atausb2_t_bbb_data_rd_cs_callback(struct usb2_xfer *xfer) +{ + atausb2_t_bbb_data_clear_stall_callback(xfer, ATAUSB_T_BBB_STATUS, + ATAUSB_T_BBB_DATA_READ); + return; +} + +static void +atausb2_t_bbb_data_write_callback(struct usb2_xfer *xfer) +{ + struct atausb2_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + sc->ata_bytecount -= xfer->actlen; + sc->ata_data += xfer->actlen; + sc->ata_donecount += xfer->actlen; + + case USB_ST_SETUP: + + if (atausbdebug > 1) { + device_printf(sc->dev, "%s: max_bulk=%d, ata_bytecount=%d\n", + __FUNCTION__, max_bulk, sc->ata_bytecount); + } + if (sc->ata_bytecount == 0) { + atausb2_transfer_start(sc, ATAUSB_T_BBB_STATUS); + return; + } + if (max_bulk > sc->ata_bytecount) { + max_bulk = sc->ata_bytecount; + } + xfer->timeout = sc->timeout; + xfer->frlengths[0] = max_bulk; + + usb2_copy_in(xfer->frbuffers, 0, + sc->ata_data, max_bulk); + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + atausb2_tr_error(xfer); + } else { + atausb2_transfer_start(sc, ATAUSB_T_BBB_DATA_WR_CS); + } + return; + + } +} + +static void +atausb2_t_bbb_data_wr_cs_callback(struct usb2_xfer *xfer) +{ + atausb2_t_bbb_data_clear_stall_callback(xfer, ATAUSB_T_BBB_STATUS, + ATAUSB_T_BBB_DATA_WRITE); + return; +} + +static void +atausb2_t_bbb_status_callback(struct usb2_xfer *xfer) +{ + struct atausb2_softc *sc = xfer->priv_sc; + struct ata_request *request = sc->ata_request; + uint32_t residue; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen < sizeof(sc->csw)) { + bzero(&sc->csw, sizeof(sc->csw)); + } + usb2_copy_out(xfer->frbuffers, 0, &sc->csw, xfer->actlen); + + if (request->flags & (ATA_R_READ | ATA_R_WRITE)) { + request->donecount = sc->ata_donecount; + } + residue = UGETDW(sc->csw.residue); + + if (!residue) { + residue = (request->bytecount - request->donecount); + } + if (residue > request->bytecount) { + if (atausbdebug) { + device_printf(sc->dev, "truncating residue from %d " + "to %d bytes\n", residue, + request->bytecount); + } + residue = request->bytecount; + } + /* check CSW and handle eventual error */ + if (UGETDW(sc->csw.signature) != CSWSIGNATURE) { + if (atausbdebug) { + device_printf(sc->dev, "bad CSW signature 0x%08x != 0x%08x\n", + UGETDW(sc->csw.signature), CSWSIGNATURE); + } + goto tr_error; + } else if (UGETDW(sc->csw.tag) != UGETDW(sc->cbw.tag)) { + if (atausbdebug) { + device_printf(sc->dev, "bad CSW tag %d != %d\n", + UGETDW(sc->csw.tag), UGETDW(sc->cbw.tag)); + } + goto tr_error; + } else if (sc->csw.status > CSWSTATUS_PHASE) { + if (atausbdebug) { + device_printf(sc->dev, "bad CSW status %d > %d\n", + sc->csw.status, CSWSTATUS_PHASE); + } + goto tr_error; + } else if (sc->csw.status == CSWSTATUS_PHASE) { + if (atausbdebug) { + device_printf(sc->dev, "phase error residue = %d\n", residue); + } + goto tr_error; + } else if (request->donecount > request->bytecount) { + if (atausbdebug) { + device_printf(sc->dev, "buffer overrun %d > %d\n", + request->donecount, request->bytecount); + } + goto tr_error; + } else if (sc->csw.status == CSWSTATUS_FAILED) { + if (atausbdebug) { + device_printf(sc->dev, "CSWSTATUS_FAILED\n"); + } + request->error = ATA_E_ATAPI_SENSE_MASK; + } + sc->last_xfer_no = ATAUSB_T_BBB_COMMAND; + + sc->ata_request = NULL; + + mtx_unlock(xfer->priv_mtx); + + ata_interrupt(device_get_softc(request->parent)); + + mtx_lock(xfer->priv_mtx); + return; + + case USB_ST_SETUP: + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + return; + + default: +tr_error: + if ((xfer->error == USB_ERR_CANCELLED) || + (sc->status_try)) { + atausb2_tr_error(xfer); + } else { + sc->status_try = 1; + atausb2_transfer_start(sc, ATAUSB_T_BBB_DATA_RD_CS); + } + return; + + } +} + +static void +atausb2_cancel_request(struct atausb2_softc *sc) +{ + struct ata_request *request; + + mtx_assert(&sc->locked_mtx, MA_OWNED); + + request = sc->ata_request; + sc->ata_request = NULL; + sc->last_xfer_no = ATAUSB_T_BBB_RESET1; + + if (request) { + request->error = ATA_E_ATAPI_SENSE_MASK; + + mtx_unlock(&sc->locked_mtx); + + ata_interrupt(device_get_softc(request->parent)); + + mtx_lock(&sc->locked_mtx); + } + return; +} + +static void +atausb2_tr_error(struct usb2_xfer *xfer) +{ + struct atausb2_softc *sc = xfer->priv_sc; + + if (xfer->error != USB_ERR_CANCELLED) { + + if (atausbdebug) { + device_printf(sc->dev, "transfer failed, %s, in state %d " + "-> BULK reset\n", usb2_errstr(xfer->error), + sc->last_xfer_no); + } + } + atausb2_cancel_request(sc); + + return; +} + +/* + * ATA backend part + */ +struct atapi_inquiry { + uint8_t device_type; + uint8_t device_modifier; + uint8_t version; + uint8_t response_format; + uint8_t length; + uint8_t reserved[2]; + uint8_t flags; + uint8_t vendor[8]; + uint8_t product[16]; + uint8_t revision[4]; + /* uint8_t crap[60]; */ +} __packed; + +static int +ata_usbchannel_begin_transaction(struct ata_request *request) +{ + struct atausb2_softc *sc = + device_get_softc(device_get_parent(request->parent)); + int error; + + if (atausbdebug > 1) { + device_printf(request->dev, "begin_transaction %s\n", + ata_cmd2str(request)); + } + mtx_lock(&sc->locked_mtx); + + /* sanity, just in case */ + if (sc->ata_request) { + device_printf(request->dev, "begin is busy, " + "state = %d\n", sc->last_xfer_no); + request->result = EBUSY; + error = ATA_OP_FINISHED; + goto done; + } + /* + * XXX SOS convert the request into the format used, only BBB for + * now + */ + + /* ATA/ATAPI IDENTIFY needs special treatment */ + if (!(request->flags & ATA_R_ATAPI)) { + if (request->u.ata.command != ATA_ATAPI_IDENTIFY) { + device_printf(request->dev, "%s unsupported\n", + ata_cmd2str(request)); + request->result = EIO; + error = ATA_OP_FINISHED; + goto done; + } + request->flags |= ATA_R_ATAPI; + bzero(request->u.atapi.ccb, 16); + request->u.atapi.ccb[0] = ATAPI_INQUIRY; + request->u.atapi.ccb[4] = 255; /* sizeof(struct + * atapi_inquiry); */ + request->data += 256; /* arbitrary offset into ata_param */ + request->bytecount = 255; /* sizeof(struct + * atapi_inquiry); */ + } + if (sc->xfer[sc->last_xfer_no]) { + + sc->ata_request = request; + sc->ata_bytecount = request->bytecount; + sc->ata_data = request->data; + sc->ata_donecount = 0; + + usb2_transfer_start(sc->xfer[sc->last_xfer_no]); + error = ATA_OP_CONTINUES; + } else { + request->result = EIO; + error = ATA_OP_FINISHED; + } + +done: + mtx_unlock(&sc->locked_mtx); + return (error); +} + +static int +ata_usbchannel_end_transaction(struct ata_request *request) +{ + if (atausbdebug > 1) { + device_printf(request->dev, "end_transaction %s\n", + ata_cmd2str(request)); + } + /* + * XXX SOS convert the request from the format used, only BBB for + * now + */ + + /* ATA/ATAPI IDENTIFY needs special treatment */ + if ((request->flags & ATA_R_ATAPI) && + (request->u.atapi.ccb[0] == ATAPI_INQUIRY)) { + struct ata_device *atadev = device_get_softc(request->dev); + struct atapi_inquiry *inquiry = (struct atapi_inquiry *)request->data; + uint16_t *ptr; + + /* convert inquiry data into simple ata_param like format */ + atadev->param.config = ATA_PROTO_ATAPI | ATA_PROTO_ATAPI_12; + atadev->param.config |= (inquiry->device_type & 0x1f) << 8; + bzero(atadev->param.model, sizeof(atadev->param.model)); + strncpy(atadev->param.model, inquiry->vendor, 8); + strcpy(atadev->param.model, " "); + strncpy(atadev->param.model, inquiry->product, 16); + ptr = (uint16_t *)(atadev->param.model + sizeof(atadev->param.model)); + while (--ptr >= (uint16_t *)atadev->param.model) { + *ptr = ntohs(*ptr); + } + strncpy(atadev->param.revision, inquiry->revision, 4); + ptr = (uint16_t *)(atadev->param.revision + sizeof(atadev->param.revision)); + while (--ptr >= (uint16_t *)atadev->param.revision) { + *ptr = ntohs(*ptr); + } + request->result = 0; + } + return (ATA_OP_FINISHED); +} + +static int +ata_usbchannel_probe(device_t dev) +{ + struct ata_channel *ch = device_get_softc(dev); + device_t *children; + int count, i; + char buffer[32]; + + /* take care of green memory */ + bzero(ch, sizeof(struct ata_channel)); + + /* find channel number on this controller */ + if (!device_get_children(device_get_parent(dev), &children, &count)) { + for (i = 0; i < count; i++) { + if (children[i] == dev) + ch->unit = i; + } + free(children, M_TEMP); + } + snprintf(buffer, sizeof(buffer), "USB lun %d", ch->unit); + device_set_desc_copy(dev, buffer); + + return (0); +} + +static int +ata_usbchannel_attach(device_t dev) +{ + struct ata_channel *ch = device_get_softc(dev); + + /* initialize the softc basics */ + ch->dev = dev; + ch->state = ATA_IDLE; + ch->hw.begin_transaction = ata_usbchannel_begin_transaction; + ch->hw.end_transaction = ata_usbchannel_end_transaction; + ch->hw.status = NULL; + ch->hw.command = NULL; + bzero(&ch->state_mtx, sizeof(struct mtx)); + mtx_init(&ch->state_mtx, "ATA state lock", NULL, MTX_DEF); + bzero(&ch->queue_mtx, sizeof(struct mtx)); + mtx_init(&ch->queue_mtx, "ATA queue lock", NULL, MTX_DEF); + TAILQ_INIT(&ch->ata_queue); + + /* XXX SOS reset the controller HW, the channel and device(s) */ + /* ATA_RESET(dev); */ + + /* probe and attach device on this channel */ + ch->devices = ATA_ATAPI_MASTER; + if (!ata_delayed_attach) { + ata_identify(dev); + } + return (0); +} + +static int +ata_usbchannel_detach(device_t dev) +{ + struct ata_channel *ch = device_get_softc(dev); + device_t *children; + int nchildren, i; + + /* detach & delete all children */ + if (!device_get_children(dev, &children, &nchildren)) { + for (i = 0; i < nchildren; i++) + if (children[i]) + device_delete_child(dev, children[i]); + free(children, M_TEMP); + } + mtx_destroy(&ch->state_mtx); + mtx_destroy(&ch->queue_mtx); + return (0); +} + +static void +ata_usbchannel_setmode(device_t parent, device_t dev) +{ + struct atausb2_softc *sc = device_get_softc(GRANDPARENT(dev)); + struct ata_device *atadev = device_get_softc(dev); + + if (sc->usb2_speed == USB_SPEED_HIGH) + atadev->mode = ATA_USB2; + else + atadev->mode = ATA_USB1; + return; +} + +static int +ata_usbchannel_locking(device_t dev, int flags) +{ + struct atausb2_softc *sc = device_get_softc(device_get_parent(dev)); + struct ata_channel *ch = device_get_softc(dev); + int res = -1; + + mtx_lock(&sc->locked_mtx); + switch (flags) { + case ATA_LF_LOCK: + if (sc->locked_ch == NULL) + sc->locked_ch = ch; + if (sc->locked_ch != ch) + sc->restart_ch = ch; + break; + + case ATA_LF_UNLOCK: + if (sc->locked_ch == ch) { + sc->locked_ch = NULL; + if (sc->restart_ch) { + ch = sc->restart_ch; + sc->restart_ch = NULL; + mtx_unlock(&sc->locked_mtx); + ata_start(ch->dev); + return (res); + } + } + break; + + case ATA_LF_WHICH: + break; + } + if (sc->locked_ch) { + res = sc->locked_ch->unit; + } + mtx_unlock(&sc->locked_mtx); + return (res); +} + +static device_method_t ata_usbchannel_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, ata_usbchannel_probe), + DEVMETHOD(device_attach, ata_usbchannel_attach), + DEVMETHOD(device_detach, ata_usbchannel_detach), + + /* ATA methods */ + DEVMETHOD(ata_setmode, ata_usbchannel_setmode), + DEVMETHOD(ata_locking, ata_usbchannel_locking), + /* DEVMETHOD(ata_reset, ata_usbchannel_reset), */ + + {0, 0} +}; + +static driver_t ata_usbchannel_driver = { + "ata", + ata_usbchannel_methods, + sizeof(struct ata_channel), +}; + +DRIVER_MODULE(ata, atausb, ata_usbchannel_driver, ata_devclass, 0, 0); +MODULE_DEPEND(atausb, ata, 1, 1, 1); diff --git a/sys/dev/usb2/storage/umass2.c b/sys/dev/usb2/storage/umass2.c new file mode 100644 index 000000000000..f998cf1cb2cd --- /dev/null +++ b/sys/dev/usb2/storage/umass2.c @@ -0,0 +1,3670 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1999 MAEKAWA Masahide , + * Nick Hibma + * 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. + * + * $FreeBSD$ + * $NetBSD: umass.c,v 1.28 2000/04/02 23:46:53 augustss Exp $ + */ + +/* Also already merged from NetBSD: + * $NetBSD: umass.c,v 1.67 2001/11/25 19:05:22 augustss Exp $ + * $NetBSD: umass.c,v 1.90 2002/11/04 19:17:33 pooka Exp $ + * $NetBSD: umass.c,v 1.108 2003/11/07 17:03:25 wiz Exp $ + * $NetBSD: umass.c,v 1.109 2003/12/04 13:57:31 keihan Exp $ + */ + +/* + * Universal Serial Bus Mass Storage Class specs: + * http://www.usb.org/developers/devclass_docs/usb_msc_overview_1.2.pdf + * http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf + * http://www.usb.org/developers/devclass_docs/usb_msc_cbi_1.1.pdf + * http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf + */ + +/* + * Ported to NetBSD by Lennart Augustsson . + * Parts of the code written by Jason R. Thorpe . + */ + +/* + * The driver handles 3 Wire Protocols + * - Command/Bulk/Interrupt (CBI) + * - Command/Bulk/Interrupt with Command Completion Interrupt (CBI with CCI) + * - Mass Storage Bulk-Only (BBB) + * (BBB refers Bulk/Bulk/Bulk for Command/Data/Status phases) + * + * Over these wire protocols it handles the following command protocols + * - SCSI + * - UFI (floppy command set) + * - 8070i (ATAPI) + * + * UFI and 8070i (ATAPI) are transformed versions of the SCSI command set. The + * sc->sc_transform method is used to convert the commands into the appropriate + * format (if at all necessary). For example, UFI requires all commands to be + * 12 bytes in length amongst other things. + * + * The source code below is marked and can be split into a number of pieces + * (in this order): + * + * - probe/attach/detach + * - generic transfer routines + * - BBB + * - CBI + * - CBI_I (in addition to functions from CBI) + * - CAM (Common Access Method) + * - SCSI + * - UFI + * - 8070i (ATAPI) + * + * The protocols are implemented using a state machine, for the transfers as + * well as for the resets. The state machine is contained in umass_t_*_callback. + * The state machine is started through either umass_command_start() or + * umass_reset(). + * + * The reason for doing this is a) CAM performs a lot better this way and b) it + * avoids using tsleep from interrupt context (for example after a failed + * transfer). + */ + +/* + * The SCSI related part of this driver has been derived from the + * dev/ppbus/vpo.c driver, by Nicolas Souchu (nsouch@freebsd.org). + * + * The CAM layer uses so called actions which are messages sent to the host + * adapter for completion. The actions come in through umass_cam_action. The + * appropriate block of routines is called depending on the transport protocol + * in use. When the transfer has finished, these routines call + * umass_cam_cb again to complete the CAM command. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#if 1 +/* this enables loading of virtual buffers into DMA */ +#define UMASS_USB_FLAGS .ext_buffer=1, +#else +#define UMASS_USB_FLAGS +#endif + +#if USB_DEBUG +#define DIF(m, x) \ + do { \ + if (umass_debug & (m)) { x ; } \ + } while (0) + +#define DPRINTF(sc, m, fmt, ...) \ + do { \ + if (umass_debug & (m)) { \ + printf("%s:%s: " fmt, \ + (sc) ? (const char *)(sc)->sc_name : \ + (const char *)"umassX", \ + __FUNCTION__ ,## __VA_ARGS__); \ + } \ + } while (0) + +#define UDMASS_GEN 0x00010000 /* general */ +#define UDMASS_SCSI 0x00020000 /* scsi */ +#define UDMASS_UFI 0x00040000 /* ufi command set */ +#define UDMASS_ATAPI 0x00080000 /* 8070i command set */ +#define UDMASS_CMD (UDMASS_SCSI|UDMASS_UFI|UDMASS_ATAPI) +#define UDMASS_USB 0x00100000 /* USB general */ +#define UDMASS_BBB 0x00200000 /* Bulk-Only transfers */ +#define UDMASS_CBI 0x00400000 /* CBI transfers */ +#define UDMASS_WIRE (UDMASS_BBB|UDMASS_CBI) +#define UDMASS_ALL 0xffff0000 /* all of the above */ +static int umass_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, umass, CTLFLAG_RW, 0, "USB umass"); +SYSCTL_INT(_hw_usb2_umass, OID_AUTO, debug, CTLFLAG_RW, + &umass_debug, 0, "umass debug level"); +#else +#define DIF(...) do { } while (0) +#define DPRINTF(...) do { } while (0) +#endif + +#define UMASS_GONE ((struct umass_softc *)1) +#define UMASS_MAXUNIT 64 /* XXX temporary */ + +#define UMASS_BULK_SIZE (1 << 17) +#define UMASS_CBI_DIAGNOSTIC_CMDLEN 12 /* bytes */ +#define UMASS_MAX_CMDLEN MAX(12, CAM_MAX_CDBLEN) /* bytes */ + +/* USB transfer definitions */ + +#define UMASS_T_BBB_RESET1 0 /* Bulk-Only */ +#define UMASS_T_BBB_RESET2 1 +#define UMASS_T_BBB_RESET3 2 +#define UMASS_T_BBB_COMMAND 3 +#define UMASS_T_BBB_DATA_READ 4 +#define UMASS_T_BBB_DATA_RD_CS 5 +#define UMASS_T_BBB_DATA_WRITE 6 +#define UMASS_T_BBB_DATA_WR_CS 7 +#define UMASS_T_BBB_STATUS 8 +#define UMASS_T_BBB_MAX 9 + +#define UMASS_T_CBI_RESET1 0 /* CBI */ +#define UMASS_T_CBI_RESET2 1 +#define UMASS_T_CBI_RESET3 2 +#define UMASS_T_CBI_COMMAND 3 +#define UMASS_T_CBI_DATA_READ 4 +#define UMASS_T_CBI_DATA_RD_CS 5 +#define UMASS_T_CBI_DATA_WRITE 6 +#define UMASS_T_CBI_DATA_WR_CS 7 +#define UMASS_T_CBI_STATUS 8 +#define UMASS_T_CBI_RESET4 9 +#define UMASS_T_CBI_MAX 10 + +#define UMASS_T_MAX MAX(UMASS_T_CBI_MAX, UMASS_T_BBB_MAX) + +/* Generic definitions */ + +/* Direction for transfer */ +#define DIR_NONE 0 +#define DIR_IN 1 +#define DIR_OUT 2 + +/* device name */ +#define DEVNAME "umass" +#define DEVNAME_SIM "umass-sim" + +/* Approximate maximum transfer speeds (assumes 33% overhead). */ +#define UMASS_FULL_TRANSFER_SPEED 1000 +#define UMASS_HIGH_TRANSFER_SPEED 40000 +#define UMASS_FLOPPY_TRANSFER_SPEED 20 + +#define UMASS_TIMEOUT 5000 /* ms */ + +/* CAM specific definitions */ + +#define UMASS_SCSIID_MAX 1 /* maximum number of drives expected */ +#define UMASS_SCSIID_HOST UMASS_SCSIID_MAX + +/* Bulk-Only features */ + +#define UR_BBB_RESET 0xff /* Bulk-Only reset */ +#define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */ + +/* Command Block Wrapper */ +typedef struct { + uDWord dCBWSignature; +#define CBWSIGNATURE 0x43425355 + uDWord dCBWTag; + uDWord dCBWDataTransferLength; + uByte bCBWFlags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + uByte bCBWLUN; + uByte bCDBLength; +#define CBWCDBLENGTH 16 + uByte CBWCDB[CBWCDBLENGTH]; +} __packed umass_bbb_cbw_t; + +#define UMASS_BBB_CBW_SIZE 31 + +/* Command Status Wrapper */ +typedef struct { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 +#define CSWSIGNATURE_IMAGINATION_DBX1 0x43425355 +#define CSWSIGNATURE_OLYMPUS_C1 0x55425355 + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed umass_bbb_csw_t; + +#define UMASS_BBB_CSW_SIZE 13 + +/* CBI features */ + +#define UR_CBI_ADSC 0x00 + +typedef union { + struct { + uint8_t type; +#define IDB_TYPE_CCI 0x00 + uint8_t value; +#define IDB_VALUE_PASS 0x00 +#define IDB_VALUE_FAIL 0x01 +#define IDB_VALUE_PHASE 0x02 +#define IDB_VALUE_PERSISTENT 0x03 +#define IDB_VALUE_STATUS_MASK 0x03 + } __packed common; + + struct { + uint8_t asc; + uint8_t ascq; + } __packed ufi; +} __packed umass_cbi_sbl_t; + +struct umass_softc; /* see below */ + +typedef void (umass_callback_t)(struct umass_softc *sc, union ccb *ccb, + uint32_t residue, uint8_t status); + +#define STATUS_CMD_OK 0 /* everything ok */ +#define STATUS_CMD_UNKNOWN 1 /* will have to fetch sense */ +#define STATUS_CMD_FAILED 2 /* transfer was ok, command failed */ +#define STATUS_WIRE_FAILED 3 /* couldn't even get command across */ + +typedef uint8_t (umass_transform_t)(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len); + +struct umass_devdescr { + uint32_t vid; +#define VID_WILDCARD 0xffffffff +#define VID_EOT 0xfffffffe + uint32_t pid; +#define PID_WILDCARD 0xffffffff +#define PID_EOT 0xfffffffe + uint32_t rid; +#define RID_WILDCARD 0xffffffff +#define RID_EOT 0xfffffffe + + /* wire and command protocol */ + uint16_t proto; +#define UMASS_PROTO_BBB 0x0001 /* USB wire protocol */ +#define UMASS_PROTO_CBI 0x0002 +#define UMASS_PROTO_CBI_I 0x0004 +#define UMASS_PROTO_WIRE 0x00ff /* USB wire protocol mask */ +#define UMASS_PROTO_SCSI 0x0100 /* command protocol */ +#define UMASS_PROTO_ATAPI 0x0200 +#define UMASS_PROTO_UFI 0x0400 +#define UMASS_PROTO_RBC 0x0800 +#define UMASS_PROTO_COMMAND 0xff00 /* command protocol mask */ + + /* Device specific quirks */ + uint16_t quirks; +#define NO_QUIRKS 0x0000 + /* + * The drive does not support Test Unit Ready. Convert to Start Unit + */ +#define NO_TEST_UNIT_READY 0x0001 + /* + * The drive does not reset the Unit Attention state after REQUEST + * SENSE has been sent. The INQUIRY command does not reset the UA + * either, and so CAM runs in circles trying to retrieve the initial + * INQUIRY data. + */ +#define RS_NO_CLEAR_UA 0x0002 + /* The drive does not support START STOP. */ +#define NO_START_STOP 0x0004 + /* Don't ask for full inquiry data (255b). */ +#define FORCE_SHORT_INQUIRY 0x0008 + /* Needs to be initialised the Shuttle way */ +#define SHUTTLE_INIT 0x0010 + /* Drive needs to be switched to alternate iface 1 */ +#define ALT_IFACE_1 0x0020 + /* Drive does not do 1Mb/s, but just floppy speeds (20kb/s) */ +#define FLOPPY_SPEED 0x0040 + /* The device can't count and gets the residue of transfers wrong */ +#define IGNORE_RESIDUE 0x0080 + /* No GetMaxLun call */ +#define NO_GETMAXLUN 0x0100 + /* The device uses a weird CSWSIGNATURE. */ +#define WRONG_CSWSIG 0x0200 + /* Device cannot handle INQUIRY so fake a generic response */ +#define NO_INQUIRY 0x0400 + /* Device cannot handle INQUIRY EVPD, return CHECK CONDITION */ +#define NO_INQUIRY_EVPD 0x0800 + /* Pad all RBC requests to 12 bytes. */ +#define RBC_PAD_TO_12 0x1000 + /* + * Device reports number of sectors from READ_CAPACITY, not max + * sector number. + */ +#define READ_CAPACITY_OFFBY1 0x2000 + /* + * Device cannot handle a SCSI synchronize cache command. Normally + * this quirk would be handled in the cam layer, but for IDE bridges + * we need to associate the quirk with the bridge and not the + * underlying disk device. This is handled by faking a success + * result. + */ +#define NO_SYNCHRONIZE_CACHE 0x4000 +}; + +static const struct umass_devdescr umass_devdescr[] = { + {USB_VENDOR_ASAHIOPTICAL, PID_WILDCARD, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + RS_NO_CLEAR_UA + }, + {USB_VENDOR_ADDON, USB_PRODUCT_ADDON_ATTACHE, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_ADDON, USB_PRODUCT_ADDON_A256MB, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_ADDON, USB_PRODUCT_ADDON_DISKPRO512, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_ADDONICS2, USB_PRODUCT_ADDONICS2_CABLE_205, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_AIPTEK, USB_PRODUCT_AIPTEK_POCKETCAM3M, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_ALCOR, USB_PRODUCT_ALCOR_UMCR_9361, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + {USB_VENDOR_ASAHIOPTICAL, USB_PRODUCT_ASAHIOPTICAL_OPTIO230, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_ASAHIOPTICAL, USB_PRODUCT_ASAHIOPTICAL_OPTIO330, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_USB2SCSI, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_CASIO, USB_PRODUCT_CASIO_QV_DIGICAM, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_INQUIRY + }, + {USB_VENDOR_CCYU, USB_PRODUCT_CCYU_ED1064, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_CENTURY, USB_PRODUCT_CENTURY_EX35QUAT, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + {USB_VENDOR_DESKNOTE, USB_PRODUCT_DESKNOTE_UCR_61S2B, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_DMI, USB_PRODUCT_DMI_CFSM_RW, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_GETMAXLUN + }, + {USB_VENDOR_EPSON, USB_PRODUCT_EPSON_STYLUS_875DC, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_INQUIRY + }, + {USB_VENDOR_EPSON, USB_PRODUCT_EPSON_STYLUS_895, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + {USB_VENDOR_FEIYA, USB_PRODUCT_FEIYA_5IN1, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_FREECOM, USB_PRODUCT_FREECOM_DVD, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_FUJIPHOTO, USB_PRODUCT_FUJIPHOTO_MASS0100, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + RS_NO_CLEAR_UA + }, + {USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB2IDE, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + {USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB2IDE_2, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + {USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + {USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB_2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + WRONG_CSWSIG + }, + {USB_VENDOR_HAGIWARA, USB_PRODUCT_HAGIWARA_FG, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_HAGIWARA, USB_PRODUCT_HAGIWARA_FGSM, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_HITACHI, USB_PRODUCT_HITACHI_DVDCAM_DZ_MV100A, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + {USB_VENDOR_HITACHI, USB_PRODUCT_HITACHI_DVDCAM_USB, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + NO_INQUIRY + }, + {USB_VENDOR_HP, USB_PRODUCT_HP_CDW4E, RID_WILDCARD, + UMASS_PROTO_ATAPI, + NO_QUIRKS + }, + {USB_VENDOR_HP, USB_PRODUCT_HP_CDW8200, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + NO_TEST_UNIT_READY | NO_START_STOP + }, + {USB_VENDOR_IMAGINATION, USB_PRODUCT_IMAGINATION_DBX1, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + WRONG_CSWSIG + }, + {USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_USBCABLE, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_TEST_UNIT_READY | NO_START_STOP | ALT_IFACE_1 + }, + {USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_ATAPI, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_STORAGE_V2, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_IODATA, USB_PRODUCT_IODATA_IU_CD2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_IODATA, USB_PRODUCT_IODATA_DVR_UEH8, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_IOMEGA, USB_PRODUCT_IOMEGA_ZIP100, RID_WILDCARD, + /* + * XXX This is not correct as there are Zip drives that use + * ATAPI. + */ + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_TEST_UNIT_READY + }, + {USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_L3, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S3X, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_INQUIRY + }, + {USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S4, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_INQUIRY + }, + {USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S5, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_LACIE, USB_PRODUCT_LACIE_HD, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_LEXAR, USB_PRODUCT_LEXAR_CF_READER, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_LEXAR, USB_PRODUCT_LEXAR_JUMPSHOT, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_LOGITEC, USB_PRODUCT_LOGITEC_LDR_H443SU2, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_LOGITEC, USB_PRODUCT_LOGITEC_LDR_H443U2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_MELCO, USB_PRODUCT_MELCO_DUBPXXG, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + {USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_DPCM, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_TEST_UNIT_READY | NO_START_STOP + }, + {USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_SCSIDB25, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_SCSIHD50, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_E223, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_F300, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_MITSUMI, USB_PRODUCT_MITSUMI_CDRRW, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_MITSUMI, USB_PRODUCT_MITSUMI_FDD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + {USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_E398, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_INQUIRY_EVPD | NO_GETMAXLUN + }, + {USB_VENDOR_MSYSTEMS, USB_PRODUCT_MSYSTEMS_DISKONKEY, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE | NO_GETMAXLUN | RS_NO_CLEAR_UA + }, + {USB_VENDOR_MSYSTEMS, USB_PRODUCT_MSYSTEMS_DISKONKEY2, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_MYSON, USB_PRODUCT_MYSON_HEDEN, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY | IGNORE_RESIDUE + }, + {USB_VENDOR_NEODIO, USB_PRODUCT_NEODIO_ND3260, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY + }, + {USB_VENDOR_NETAC, USB_PRODUCT_NETAC_CF_CARD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_NETAC, USB_PRODUCT_NETAC_ONLYDISK, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_CLIK_40, RID_WILDCARD, + UMASS_PROTO_ATAPI, + NO_INQUIRY + }, + {USB_VENDOR_NIKON, USB_PRODUCT_NIKON_D300, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_OLYMPUS, USB_PRODUCT_OLYMPUS_C1, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + WRONG_CSWSIG + }, + {USB_VENDOR_OLYMPUS, USB_PRODUCT_OLYMPUS_C700, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_GETMAXLUN + }, + {USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFMS_RW, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_COMBO, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_READER, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_READER2, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_MDCFE_B_CF_READER, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_MDSM_B_READER, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_INQUIRY + }, + {USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_READER, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_UCF100, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + NO_INQUIRY | NO_GETMAXLUN + }, + {USB_VENDOR_ONSPEC2, USB_PRODUCT_ONSPEC2_IMAGEMATE_SDDR55, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_GETMAXLUN + }, + {USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXL840AN, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + {USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXLCB20AN, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXLCB35AN, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_LS120CAM, RID_WILDCARD, + UMASS_PROTO_UFI, + NO_QUIRKS + }, + {USB_VENDOR_PLEXTOR, USB_PRODUCT_PLEXTOR_40_12_40U, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_TEST_UNIT_READY + }, + {USB_VENDOR_PNY, USB_PRODUCT_PNY_ATTACHE2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE | NO_START_STOP + }, + {USB_VENDOR_SAMSUNG, USB_PRODUCT_SAMSUNG_YP_U2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + SHUTTLE_INIT | NO_GETMAXLUN + }, + {USB_VENDOR_SAMSUNG_TECHWIN, USB_PRODUCT_SAMSUNG_TECHWIN_DIGIMAX_410, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR05A, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + READ_CAPACITY_OFFBY1 | NO_GETMAXLUN + }, + {USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR09, RID_WILDCARD, + UMASS_PROTO_SCSI, + READ_CAPACITY_OFFBY1 | NO_GETMAXLUN + }, + {USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR12, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + READ_CAPACITY_OFFBY1 | NO_GETMAXLUN + }, + {USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ2_256, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ4_128, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ4_256, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR31, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + READ_CAPACITY_OFFBY1 + }, + {USB_VENDOR_SCANLOGIC, USB_PRODUCT_SCANLOGIC_SL11R, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSB, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + NO_TEST_UNIT_READY | NO_START_STOP | SHUTTLE_INIT + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_CDRW, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_CF, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSBATAPI, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSBCFSM, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSCSI, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_HIFD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_SDDR09, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_GETMAXLUN + }, + {USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_ZIOMMC, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + {USB_VENDOR_SIGMATEL, USB_PRODUCT_SIGMATEL_I_BEAD100, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + SHUTTLE_INIT + }, + {USB_VENDOR_SIIG, USB_PRODUCT_SIIG_WINTERREADER, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_SKANHEX, USB_PRODUCT_SKANHEX_MD_7425, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_SKANHEX, USB_PRODUCT_SKANHEX_SX_520Z, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_HANDYCAM, 0x0500, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + RBC_PAD_TO_12 + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_40_MS, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_DSC, 0x0500, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + RBC_PAD_TO_12 + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_DSC, 0x0600, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + RBC_PAD_TO_12 + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_DSC, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_HANDYCAM, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_MSC, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_MSC_U03, RID_WILDCARD, + UMASS_PROTO_UFI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_NW_MS7, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_PEG_N760C, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_MSACUS1, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + {USB_VENDOR_SONY, USB_PRODUCT_SONY_PORTABLE_HDD_V2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_TAUGA, USB_PRODUCT_TAUGA_CAMERAMATE, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_TEAC, USB_PRODUCT_TEAC_FD05PUB, RID_WILDCARD, + UMASS_PROTO_UFI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_TREK, USB_PRODUCT_TREK_MEMKEY, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_TREK, USB_PRODUCT_TREK_THUMBDRIVE_8MB, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + {USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_C3310, RID_WILDCARD, + UMASS_PROTO_UFI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_MP3, RID_WILDCARD, + UMASS_PROTO_RBC, + NO_QUIRKS + }, + {USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_T33520, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + {USB_VENDOR_TWINMOS, USB_PRODUCT_TWINMOS_MDIV, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + {USB_VENDOR_VIA, USB_PRODUCT_VIA_USB2IDEBRIDGE, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_SYNCHRONIZE_CACHE + }, + {USB_VENDOR_VIVITAR, USB_PRODUCT_VIVITAR_35XX, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_COMBO, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + {USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_EXTHDD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + {USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_MYBOOK, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY_EVPD + }, + {USB_VENDOR_WINMAXGROUP, USB_PRODUCT_WINMAXGROUP_FLASH64MC, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + {USB_VENDOR_YANO, USB_PRODUCT_YANO_FW800HD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + {USB_VENDOR_YANO, USB_PRODUCT_YANO_U640MO, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + FORCE_SHORT_INQUIRY + }, + {USB_VENDOR_YEDATA, USB_PRODUCT_YEDATA_FLASHBUSTERU, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + {USB_VENDOR_ZORAN, USB_PRODUCT_ZORAN_EX20DSC, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + {VID_EOT, PID_EOT, RID_EOT, 0, 0} +}; + +struct umass_softc { + + struct scsi_sense cam_scsi_sense; + struct scsi_test_unit_ready cam_scsi_test_unit_ready; + struct mtx sc_mtx; + struct { + uint8_t *data_ptr; + union ccb *ccb; + umass_callback_t *callback; + + uint32_t data_len; /* bytes */ + uint32_t data_rem; /* bytes */ + uint32_t data_timeout; /* ms */ + uint32_t actlen; /* bytes */ + + uint8_t cmd_data[UMASS_MAX_CMDLEN]; + uint8_t cmd_len; /* bytes */ + uint8_t dir; + uint8_t lun; + } sc_transfer; + + /* Bulk specific variables for transfers in progress */ + umass_bbb_cbw_t cbw; /* command block wrapper */ + umass_bbb_csw_t csw; /* command status wrapper */ + + /* CBI specific variables for transfers in progress */ + umass_cbi_sbl_t sbl; /* status block */ + + device_t sc_dev; + struct usb2_device *sc_udev; + struct cam_sim *sc_sim; /* SCSI Interface Module */ + struct usb2_xfer *sc_xfer[UMASS_T_MAX]; + + /* + * The command transform function is used to convert the SCSI + * commands into their derivatives, like UFI, ATAPI, and friends. + */ + umass_transform_t *sc_transform; + + uint32_t sc_unit; + + uint16_t sc_proto; /* wire and cmd protocol */ + uint16_t sc_quirks; /* they got it almost right */ + + uint8_t sc_name[16]; + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_maxlun; /* maximum LUN number, inclusive */ + uint8_t sc_last_xfer_index; + uint8_t sc_status_try; +}; + +struct umass_probe_proto { + uint16_t quirks; + uint16_t proto; + + int32_t error; +}; + +/* prototypes */ + +static device_probe_t umass_probe; +static device_attach_t umass_attach; +static device_detach_t umass_detach; + +static usb2_callback_t umass_tr_error; +static usb2_callback_t umass_t_bbb_reset1_callback; +static usb2_callback_t umass_t_bbb_reset2_callback; +static usb2_callback_t umass_t_bbb_reset3_callback; +static usb2_callback_t umass_t_bbb_command_callback; +static usb2_callback_t umass_t_bbb_data_read_callback; +static usb2_callback_t umass_t_bbb_data_rd_cs_callback; +static usb2_callback_t umass_t_bbb_data_write_callback; +static usb2_callback_t umass_t_bbb_data_wr_cs_callback; +static usb2_callback_t umass_t_bbb_status_callback; +static usb2_callback_t umass_t_cbi_reset1_callback; +static usb2_callback_t umass_t_cbi_reset2_callback; +static usb2_callback_t umass_t_cbi_reset3_callback; +static usb2_callback_t umass_t_cbi_reset4_callback; +static usb2_callback_t umass_t_cbi_command_callback; +static usb2_callback_t umass_t_cbi_data_read_callback; +static usb2_callback_t umass_t_cbi_data_rd_cs_callback; +static usb2_callback_t umass_t_cbi_data_write_callback; +static usb2_callback_t umass_t_cbi_data_wr_cs_callback; +static usb2_callback_t umass_t_cbi_status_callback; + +static void umass_cancel_ccb(struct umass_softc *sc); +static void umass_init_shuttle(struct umass_softc *sc); +static void umass_reset(struct umass_softc *sc); +static void umass_t_bbb_data_clear_stall_callback(struct usb2_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer); +static void umass_command_start(struct umass_softc *sc, uint8_t dir, void *data_ptr, uint32_t data_len, uint32_t data_timeout, umass_callback_t *callback, union ccb *ccb); +static uint8_t umass_bbb_get_max_lun(struct umass_softc *sc); +static void umass_cbi_start_status(struct umass_softc *sc); +static void umass_t_cbi_data_clear_stall_callback(struct usb2_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer); +static int umass_cam_attach_sim(struct umass_softc *sc); +static void umass_cam_rescan_callback(struct cam_periph *periph, union ccb *ccb); +static void umass_cam_rescan(struct umass_softc *sc); +static void umass_cam_attach(struct umass_softc *sc); +static void umass_cam_detach_sim(struct umass_softc *sc); +static void umass_cam_action(struct cam_sim *sim, union ccb *ccb); +static void umass_cam_poll(struct cam_sim *sim); +static void umass_cam_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status); +static void umass_cam_sense_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status); +static void umass_cam_quirk_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, uint8_t status); +static uint8_t umass_scsi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len); +static uint8_t umass_rbc_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len); +static uint8_t umass_ufi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len); +static uint8_t umass_atapi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len); +static uint8_t umass_no_transform(struct umass_softc *sc, uint8_t *cmd, uint8_t cmdlen); +static uint8_t umass_std_transform(struct umass_softc *sc, union ccb *ccb, uint8_t *cmd, uint8_t cmdlen); +static int umass_driver_loaded(struct module *mod, int what, void *arg); + +#if USB_DEBUG +static void umass_bbb_dump_cbw(struct umass_softc *sc, umass_bbb_cbw_t *cbw); +static void umass_bbb_dump_csw(struct umass_softc *sc, umass_bbb_csw_t *csw); +static void umass_cbi_dump_cmd(struct umass_softc *sc, void *cmd, uint8_t cmdlen); +static void umass_dump_buffer(struct umass_softc *sc, uint8_t *buffer, uint32_t buflen, uint32_t printlen); + +#endif + +struct usb2_config umass_bbb_config[UMASS_T_BBB_MAX] = { + + [UMASS_T_BBB_RESET1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_bbb_reset1_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 500, /* 500 milliseconds */ + }, + + [UMASS_T_BBB_RESET2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_bbb_reset2_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_BBB_RESET3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_bbb_reset3_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_BBB_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = sizeof(umass_bbb_cbw_t), + .mh.flags = {}, + .mh.callback = &umass_t_bbb_command_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UMASS_BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .mh.callback = &umass_t_bbb_data_read_callback, + .mh.timeout = 0, /* overwritten later */ + }, + + [UMASS_T_BBB_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_bbb_data_rd_cs_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UMASS_BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .mh.callback = &umass_t_bbb_data_write_callback, + .mh.timeout = 0, /* overwritten later */ + }, + + [UMASS_T_BBB_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_bbb_data_wr_cs_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = sizeof(umass_bbb_csw_t), + .mh.flags = {.short_xfer_ok = 1,}, + .mh.callback = &umass_t_bbb_status_callback, + .mh.timeout = 5000, /* ms */ + }, +}; + +struct usb2_config umass_cbi_config[UMASS_T_CBI_MAX] = { + + [UMASS_T_CBI_RESET1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = (sizeof(struct usb2_device_request) + + UMASS_CBI_DIAGNOSTIC_CMDLEN), + .mh.flags = {}, + .mh.callback = &umass_t_cbi_reset1_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 500, /* 500 milliseconds */ + }, + + [UMASS_T_CBI_RESET2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_cbi_reset2_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_CBI_RESET3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_cbi_reset3_callback, + .mh.timeout = 5000, /* 5 seconds */ + .mh.interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_CBI_COMMAND] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = (sizeof(struct usb2_device_request) + + UMASS_MAX_CMDLEN), + .mh.flags = {}, + .mh.callback = &umass_t_cbi_command_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UMASS_BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .mh.callback = &umass_t_cbi_data_read_callback, + .mh.timeout = 0, /* overwritten later */ + }, + + [UMASS_T_CBI_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_cbi_data_rd_cs_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UMASS_BULK_SIZE, + .mh.flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .mh.callback = &umass_t_cbi_data_write_callback, + .mh.timeout = 0, /* overwritten later */ + }, + + [UMASS_T_CBI_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_cbi_data_wr_cs_callback, + .mh.timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_STATUS] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.flags = {.short_xfer_ok = 1,}, + .mh.bufsize = sizeof(umass_cbi_sbl_t), + .mh.callback = &umass_t_cbi_status_callback, + .mh.timeout = 5000, /* ms */ + }, + + [UMASS_T_CBI_RESET4] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &umass_t_cbi_reset4_callback, + .mh.timeout = 5000, /* ms */ + }, +}; + +/* If device cannot return valid inquiry data, fake it */ +static const uint8_t fake_inq_data[SHORT_INQUIRY_LENGTH] = { + 0, /* removable */ 0x80, SCSI_REV_2, SCSI_REV_2, + /* additional_length */ 31, 0, 0, 0 +}; + +#define UFI_COMMAND_LENGTH 12 /* UFI commands are always 12 bytes */ +#define ATAPI_COMMAND_LENGTH 12 /* ATAPI commands are always 12 bytes */ + +static struct cam_sim *umass_sim[UMASS_MAXUNIT]; +static struct mtx umass_mtx; + +static devclass_t umass_devclass; + +static device_method_t umass_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, umass_probe), + DEVMETHOD(device_attach, umass_attach), + DEVMETHOD(device_detach, umass_detach), + {0, 0} +}; + +static driver_t umass_driver = { + .name = "umass", + .methods = umass_methods, + .size = sizeof(struct umass_softc), +}; + +DRIVER_MODULE(umass, ushub, umass_driver, umass_devclass, umass_driver_loaded, 0); +MODULE_DEPEND(umass, usb2_storage, 1, 1, 1); +MODULE_DEPEND(umass, usb2_core, 1, 1, 1); +MODULE_DEPEND(umass, cam, 1, 1, 1); + +/* + * USB device probe/attach/detach + */ + +/* + * Match the device we are seeing with the + * devices supported. + */ +static struct umass_probe_proto +umass_probe_proto(device_t dev, struct usb2_attach_arg *uaa) +{ + const struct umass_devdescr *udd = umass_devdescr; + struct usb2_interface_descriptor *id; + struct umass_probe_proto ret; + + bzero(&ret, sizeof(ret)); + + /* + * An entry specifically for Y-E Data devices as they don't fit in + * the device description table. + */ + if ((uaa->info.idVendor == USB_VENDOR_YEDATA) && + (uaa->info.idProduct == USB_PRODUCT_YEDATA_FLASHBUSTERU)) { + + /* + * Revisions < 1.28 do not handle the interrupt endpoint + * very well. + */ + if (uaa->info.bcdDevice < 0x128) { + ret.proto = UMASS_PROTO_UFI | UMASS_PROTO_CBI; + } else { + ret.proto = UMASS_PROTO_UFI | UMASS_PROTO_CBI_I; + } + + /* + * Revisions < 1.28 do not have the TEST UNIT READY command + * Revisions == 1.28 have a broken TEST UNIT READY + */ + if (uaa->info.bcdDevice <= 0x128) { + ret.quirks |= NO_TEST_UNIT_READY; + } + ret.quirks |= RS_NO_CLEAR_UA | FLOPPY_SPEED; + ret.error = 0; + goto done; + } + /* + * Check the list of supported devices for a match. While looking, + * check for wildcarded and fully matched. First match wins. + */ + for (; udd->vid != VID_EOT; udd++) { + if ((udd->vid == VID_WILDCARD) && + (udd->pid == PID_WILDCARD) && + (udd->rid == RID_WILDCARD)) { + device_printf(dev, "ignoring invalid " + "wildcard quirk\n"); + continue; + } + if (((udd->vid == uaa->info.idVendor) || + (udd->vid == VID_WILDCARD)) && + ((udd->pid == uaa->info.idProduct) || + (udd->pid == PID_WILDCARD))) { + if (udd->rid == RID_WILDCARD) { + ret.proto = udd->proto; + ret.quirks = udd->quirks; + ret.error = 0; + goto done; + } else if (udd->rid == uaa->info.bcdDevice) { + ret.proto = udd->proto; + ret.quirks = udd->quirks; + ret.error = 0; + goto done; + } /* else RID does not match */ + } + } + + /* Check for a standards compliant device */ + id = usb2_get_interface_descriptor(uaa->iface); + if ((id == NULL) || + (id->bInterfaceClass != UICLASS_MASS)) { + ret.error = ENXIO; + goto done; + } + switch (id->bInterfaceSubClass) { + case UISUBCLASS_SCSI: + ret.proto |= UMASS_PROTO_SCSI; + break; + case UISUBCLASS_UFI: + ret.proto |= UMASS_PROTO_UFI; + break; + case UISUBCLASS_RBC: + ret.proto |= UMASS_PROTO_RBC; + break; + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + ret.proto |= UMASS_PROTO_ATAPI; + break; + default: + device_printf(dev, "unsupported command " + "protocol %d\n", id->bInterfaceSubClass); + ret.error = ENXIO; + goto done; + } + + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_CBI: + ret.proto |= UMASS_PROTO_CBI; + break; + case UIPROTO_MASS_CBI_I: + ret.proto |= UMASS_PROTO_CBI_I; + break; + case UIPROTO_MASS_BBB_OLD: + case UIPROTO_MASS_BBB: + ret.proto |= UMASS_PROTO_BBB; + break; + default: + device_printf(dev, "unsupported wire " + "protocol %d\n", id->bInterfaceProtocol); + ret.error = ENXIO; + goto done; + } + + ret.error = 0; +done: + return (ret); +} + +static int +umass_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct umass_probe_proto temp; + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->use_generic == 0) { + /* give other drivers a try first */ + return (ENXIO); + } + temp = umass_probe_proto(dev, uaa); + + return (temp.error); +} + +static int +umass_attach(device_t dev) +{ + struct umass_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct umass_probe_proto temp = umass_probe_proto(dev, uaa); + struct usb2_interface_descriptor *id; + int32_t err; + + if (sc == NULL) { + return (ENOMEM); + } + if (device_get_unit(dev) >= UMASS_MAXUNIT) { + device_printf(dev, "Maxunit(%u) limit reached!\n", + UMASS_MAXUNIT); + return (ENOMEM); + } + /* + * NOTE: the softc struct is bzero-ed in device_set_driver. + * We can safely call umass_detach without specifically + * initializing the struct. + */ + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + sc->sc_proto = temp.proto; + sc->sc_quirks = temp.quirks; + sc->sc_unit = device_get_unit(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + device_set_usb2_desc(dev); + + /* get interface index */ + + id = usb2_get_interface_descriptor(uaa->iface); + if (id == NULL) { + device_printf(dev, "failed to get " + "interface number\n"); + goto detach; + } + sc->sc_iface_no = id->bInterfaceNumber; + +#if USB_DEBUG + device_printf(dev, " "); + + switch (sc->sc_proto & UMASS_PROTO_COMMAND) { + case UMASS_PROTO_SCSI: + printf("SCSI"); + break; + case UMASS_PROTO_ATAPI: + printf("8070i (ATAPI)"); + break; + case UMASS_PROTO_UFI: + printf("UFI"); + break; + case UMASS_PROTO_RBC: + printf("RBC"); + break; + default: + printf("(unknown 0x%02x)", + sc->sc_proto & UMASS_PROTO_COMMAND); + break; + } + + printf(" over "); + + switch (sc->sc_proto & UMASS_PROTO_WIRE) { + case UMASS_PROTO_BBB: + printf("Bulk-Only"); + break; + case UMASS_PROTO_CBI: /* uses Comand/Bulk pipes */ + printf("CBI"); + break; + case UMASS_PROTO_CBI_I: /* uses Comand/Bulk/Interrupt pipes */ + printf("CBI with CCI"); + break; + default: + printf("(unknown 0x%02x)", + sc->sc_proto & UMASS_PROTO_WIRE); + } + + printf("; quirks = 0x%04x\n", sc->sc_quirks); +#endif + + if (sc->sc_quirks & ALT_IFACE_1) { + err = usb2_set_alt_interface_index + (uaa->device, uaa->info.bIfaceIndex, 1); + + if (err) { + DPRINTF(sc, UDMASS_USB, "could not switch to " + "Alt Interface 1\n"); + goto detach; + } + } + /* allocate all required USB transfers */ + + if (sc->sc_proto & UMASS_PROTO_BBB) { + + err = usb2_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, umass_bbb_config, + UMASS_T_BBB_MAX, sc, &umass_mtx); + + /* skip reset first time */ + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + } else if (sc->sc_proto & (UMASS_PROTO_CBI | UMASS_PROTO_CBI_I)) { + + err = usb2_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, umass_cbi_config, + (sc->sc_proto & UMASS_PROTO_CBI_I) ? + UMASS_T_CBI_MAX : (UMASS_T_CBI_MAX - 2), sc, + &umass_mtx); + + /* skip reset first time */ + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + } else { + err = USB_ERR_INVAL; + } + + if (err) { + device_printf(dev, "could not setup required " + "transfers, %s\n", usb2_errstr(err)); + goto detach; + } + sc->sc_transform = + (sc->sc_proto & UMASS_PROTO_SCSI) ? &umass_scsi_transform : + (sc->sc_proto & UMASS_PROTO_UFI) ? &umass_ufi_transform : + (sc->sc_proto & UMASS_PROTO_ATAPI) ? &umass_atapi_transform : + (sc->sc_proto & UMASS_PROTO_RBC) ? &umass_rbc_transform : + &umass_no_transform; + + /* from here onwards the device can be used. */ + + if (sc->sc_quirks & SHUTTLE_INIT) { + umass_init_shuttle(sc); + } + /* get the maximum LUN supported by the device */ + + if (((sc->sc_proto & UMASS_PROTO_WIRE) == UMASS_PROTO_BBB) && + !(sc->sc_quirks & NO_GETMAXLUN)) + sc->sc_maxlun = umass_bbb_get_max_lun(sc); + else + sc->sc_maxlun = 0; + + /* Prepare the SCSI command block */ + sc->cam_scsi_sense.opcode = REQUEST_SENSE; + sc->cam_scsi_test_unit_ready.opcode = TEST_UNIT_READY; + + /* + * some devices need a delay after that the configuration value is + * set to function properly: + */ + usb2_pause_mtx(&Giant, USB_MS_HZ); + + /* register the SIM */ + err = umass_cam_attach_sim(sc); + if (err) { + goto detach; + } + /* scan the SIM */ + umass_cam_attach(sc); + + DPRINTF(sc, UDMASS_GEN, "Attach finished\n"); + + return (0); /* success */ + +detach: + umass_detach(dev); + return (ENXIO); /* failure */ +} + +static int +umass_detach(device_t dev) +{ + struct umass_softc *sc = device_get_softc(dev); + + DPRINTF(sc, UDMASS_USB, "\n"); + + /* teardown our statemachine */ + + usb2_transfer_unsetup(sc->sc_xfer, UMASS_T_MAX); + +#if (__FreeBSD_version >= 700037) + mtx_lock(&umass_mtx); +#endif + umass_cam_detach_sim(sc); + +#if (__FreeBSD_version >= 700037) + mtx_unlock(&umass_mtx); +#endif + + return (0); /* success */ +} + +static void +umass_init_shuttle(struct umass_softc *sc) +{ + struct usb2_device_request req; + usb2_error_t err; + uint8_t status[2] = {0, 0}; + + /* + * The Linux driver does this, but no one can tell us what the + * command does. + */ + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = 1; /* XXX unknown command */ + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(status)); + err = usb2_do_request(sc->sc_udev, &Giant, &req, &status); + + DPRINTF(sc, UDMASS_GEN, "Shuttle init returned 0x%02x%02x\n", + status[0], status[1]); + return; +} + +/* + * Generic functions to handle transfers + */ + +static void +umass_transfer_start(struct umass_softc *sc, uint8_t xfer_index) +{ + DPRINTF(sc, UDMASS_GEN, "transfer index = " + "%d\n", xfer_index); + + if (sc->sc_xfer[xfer_index]) { + sc->sc_last_xfer_index = xfer_index; + usb2_transfer_start(sc->sc_xfer[xfer_index]); + } else { + umass_cancel_ccb(sc); + } + return; +} + +static void +umass_reset(struct umass_softc *sc) +{ + DPRINTF(sc, UDMASS_GEN, "resetting device\n"); + + /* + * stop the last transfer, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]); + umass_transfer_start(sc, 0); + return; +} + +static void +umass_cancel_ccb(struct umass_softc *sc) +{ + union ccb *ccb; + + mtx_assert(&umass_mtx, MA_OWNED); + + ccb = sc->sc_transfer.ccb; + sc->sc_transfer.ccb = NULL; + sc->sc_last_xfer_index = 0; + + if (ccb) { + (sc->sc_transfer.callback) + (sc, ccb, (sc->sc_transfer.data_len - + sc->sc_transfer.actlen), STATUS_WIRE_FAILED); + } + return; +} + +static void +umass_tr_error(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + + if (xfer->error != USB_ERR_CANCELLED) { + + DPRINTF(sc, UDMASS_GEN, "transfer error, %s -> " + "reset\n", usb2_errstr(xfer->error)); + } + umass_cancel_ccb(sc); + return; +} + +/* + * BBB protocol specific functions + */ + +static void +umass_t_bbb_reset1_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start(sc, UMASS_T_BBB_RESET2); + return; + + case USB_ST_SETUP: + /* + * Reset recovery (5.3.4 in Universal Serial Bus Mass Storage Class) + * + * For Reset Recovery the host shall issue in the following order: + * a) a Bulk-Only Mass Storage Reset + * b) a Clear Feature HALT to the Bulk-In endpoint + * c) a Clear Feature HALT to the Bulk-Out endpoint + * + * This is done in 3 steps, using 3 transfers: + * UMASS_T_BBB_RESET1 + * UMASS_T_BBB_RESET2 + * UMASS_T_BBB_RESET3 + */ + + DPRINTF(sc, UDMASS_BBB, "BBB reset!\n"); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_BBB_RESET; /* bulk only reset */ + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + xfer->frlengths[0] = sizeof(req); + xfer->nframes = 1; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + umass_tr_error(xfer); + return; + + } +} + +static void +umass_t_bbb_reset2_callback(struct usb2_xfer *xfer) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_RESET3, + UMASS_T_BBB_DATA_READ); + return; +} + +static void +umass_t_bbb_reset3_callback(struct usb2_xfer *xfer) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_COMMAND, + UMASS_T_BBB_DATA_WRITE); + return; +} + +static void +umass_t_bbb_data_clear_stall_callback(struct usb2_xfer *xfer, + uint8_t next_xfer, + uint8_t stall_xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + umass_transfer_start(sc, next_xfer); + return; + + case USB_ST_SETUP: + if (usb2_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { + goto tr_transferred; + } + return; + + default: /* Error */ + umass_tr_error(xfer); + return; + + } +} + +static void +umass_t_bbb_command_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + union ccb *ccb = sc->sc_transfer.ccb; + uint32_t tag; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start + (sc, ((sc->sc_transfer.dir == DIR_IN) ? UMASS_T_BBB_DATA_READ : + (sc->sc_transfer.dir == DIR_OUT) ? UMASS_T_BBB_DATA_WRITE : + UMASS_T_BBB_STATUS)); + return; + + case USB_ST_SETUP: + + sc->sc_status_try = 0; + + if (ccb) { + + /* + * the initial value is not important, + * as long as the values are unique: + */ + tag = UGETDW(sc->cbw.dCBWTag) + 1; + + USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); + USETDW(sc->cbw.dCBWTag, tag); + + /* + * dCBWDataTransferLength: + * This field indicates the number of bytes of data that the host + * intends to transfer on the IN or OUT Bulk endpoint(as indicated by + * the Direction bit) during the execution of this command. If this + * field is set to 0, the device will expect that no data will be + * transferred IN or OUT during this command, regardless of the value + * of the Direction bit defined in dCBWFlags. + */ + USETDW(sc->cbw.dCBWDataTransferLength, sc->sc_transfer.data_len); + + /* + * dCBWFlags: + * The bits of the Flags field are defined as follows: + * Bits 0-6 reserved + * Bit 7 Direction - this bit shall be ignored if the + * dCBWDataTransferLength field is zero. + * 0 = data Out from host to device + * 1 = data In from device to host + */ + sc->cbw.bCBWFlags = ((sc->sc_transfer.dir == DIR_IN) ? + CBWFLAGS_IN : CBWFLAGS_OUT); + sc->cbw.bCBWLUN = sc->sc_transfer.lun; + + if (sc->sc_transfer.cmd_len > sizeof(sc->cbw.CBWCDB)) { + sc->sc_transfer.cmd_len = sizeof(sc->cbw.CBWCDB); + DPRINTF(sc, UDMASS_BBB, "Truncating long command!\n"); + } + sc->cbw.bCDBLength = sc->sc_transfer.cmd_len; + + bcopy(sc->sc_transfer.cmd_data, sc->cbw.CBWCDB, + sc->sc_transfer.cmd_len); + + bzero(sc->sc_transfer.cmd_data + sc->sc_transfer.cmd_len, + sizeof(sc->cbw.CBWCDB) - sc->sc_transfer.cmd_len); + + DIF(UDMASS_BBB, umass_bbb_dump_cbw(sc, &sc->cbw)); + + usb2_copy_in(xfer->frbuffers, 0, &sc->cbw, sizeof(sc->cbw)); + + xfer->frlengths[0] = sizeof(sc->cbw); + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + umass_tr_error(xfer); + return; + + } +} + +static void +umass_t_bbb_data_read_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (!xfer->flags.ext_buffer) { + usb2_copy_out(xfer->frbuffers, 0, + sc->sc_transfer.data_ptr, xfer->actlen); + } + sc->sc_transfer.data_rem -= xfer->actlen; + sc->sc_transfer.data_ptr += xfer->actlen; + sc->sc_transfer.actlen += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_transfer_start(sc, UMASS_T_BBB_STATUS); + return; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + xfer->timeout = sc->sc_transfer.data_timeout; + xfer->frlengths[0] = max_bulk; + + if (xfer->flags.ext_buffer) { + usb2_set_frame_data(xfer, sc->sc_transfer.data_ptr, 0); + } + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + umass_tr_error(xfer); + } else { + umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); + } + return; + + } +} + +static void +umass_t_bbb_data_rd_cs_callback(struct usb2_xfer *xfer) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, + UMASS_T_BBB_DATA_READ); + return; +} + +static void +umass_t_bbb_data_write_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= xfer->actlen; + sc->sc_transfer.data_ptr += xfer->actlen; + sc->sc_transfer.actlen += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_transfer_start(sc, UMASS_T_BBB_STATUS); + return; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + xfer->timeout = sc->sc_transfer.data_timeout; + xfer->frlengths[0] = max_bulk; + + if (xfer->flags.ext_buffer) { + usb2_set_frame_data(xfer, sc->sc_transfer.data_ptr, 0); + } else { + usb2_copy_in(xfer->frbuffers, 0, + sc->sc_transfer.data_ptr, max_bulk); + } + + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + umass_tr_error(xfer); + } else { + umass_transfer_start(sc, UMASS_T_BBB_DATA_WR_CS); + } + return; + + } +} + +static void +umass_t_bbb_data_wr_cs_callback(struct usb2_xfer *xfer) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, + UMASS_T_BBB_DATA_WRITE); + return; +} + +static void +umass_t_bbb_status_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + union ccb *ccb = sc->sc_transfer.ccb; + uint32_t residue; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* + * Do a full reset if there is something wrong with the CSW: + */ + sc->sc_status_try = 1; + + /* Zero missing parts of the CSW: */ + + if (xfer->actlen < sizeof(sc->csw)) { + bzero(&sc->csw, sizeof(sc->csw)); + } + usb2_copy_out(xfer->frbuffers, 0, &sc->csw, xfer->actlen); + + DIF(UDMASS_BBB, umass_bbb_dump_csw(sc, &sc->csw)); + + residue = UGETDW(sc->csw.dCSWDataResidue); + + if (!residue) { + residue = (sc->sc_transfer.data_len - + sc->sc_transfer.actlen); + } + if (residue > sc->sc_transfer.data_len) { + DPRINTF(sc, UDMASS_BBB, "truncating residue from %d " + "to %d bytes\n", residue, sc->sc_transfer.data_len); + residue = sc->sc_transfer.data_len; + } + /* translate weird command-status signatures: */ + if (sc->sc_quirks & WRONG_CSWSIG) { + + uint32_t temp = UGETDW(sc->csw.dCSWSignature); + + if ((temp == CSWSIGNATURE_OLYMPUS_C1) || + (temp == CSWSIGNATURE_IMAGINATION_DBX1)) { + USETDW(sc->csw.dCSWSignature, CSWSIGNATURE); + } + } + /* check CSW and handle eventual error */ + if (UGETDW(sc->csw.dCSWSignature) != CSWSIGNATURE) { + DPRINTF(sc, UDMASS_BBB, "bad CSW signature 0x%08x != 0x%08x\n", + UGETDW(sc->csw.dCSWSignature), CSWSIGNATURE); + /* + * Invalid CSW: Wrong signature or wrong tag might + * indicate that we lost synchronization. Reset the + * device. + */ + goto tr_error; + } else if (UGETDW(sc->csw.dCSWTag) != UGETDW(sc->cbw.dCBWTag)) { + DPRINTF(sc, UDMASS_BBB, "Invalid CSW: tag 0x%08x should be " + "0x%08x\n", UGETDW(sc->csw.dCSWTag), + UGETDW(sc->cbw.dCBWTag)); + goto tr_error; + } else if (sc->csw.bCSWStatus > CSWSTATUS_PHASE) { + DPRINTF(sc, UDMASS_BBB, "Invalid CSW: status %d > %d\n", + sc->csw.bCSWStatus, CSWSTATUS_PHASE); + goto tr_error; + } else if (sc->csw.bCSWStatus == CSWSTATUS_PHASE) { + DPRINTF(sc, UDMASS_BBB, "Phase error, residue = " + "%d\n", residue); + goto tr_error; + } else if (sc->sc_transfer.actlen > sc->sc_transfer.data_len) { + DPRINTF(sc, UDMASS_BBB, "Buffer overrun %d > %d\n", + sc->sc_transfer.actlen, sc->sc_transfer.data_len); + goto tr_error; + } else if (sc->csw.bCSWStatus == CSWSTATUS_FAILED) { + DPRINTF(sc, UDMASS_BBB, "Command failed, residue = " + "%d\n", residue); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, STATUS_CMD_FAILED); + } else { + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, STATUS_CMD_OK); + } + return; + + case USB_ST_SETUP: + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + return; + + default: +tr_error: + DPRINTF(sc, UDMASS_BBB, "Failed to read CSW: %s, try %d\n", + usb2_errstr(xfer->error), sc->sc_status_try); + + if ((xfer->error == USB_ERR_CANCELLED) || + (sc->sc_status_try)) { + umass_tr_error(xfer); + } else { + sc->sc_status_try = 1; + umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); + } + return; + + } +} + +static void +umass_command_start(struct umass_softc *sc, uint8_t dir, + void *data_ptr, uint32_t data_len, + uint32_t data_timeout, umass_callback_t *callback, + union ccb *ccb) +{ + sc->sc_transfer.lun = ccb->ccb_h.target_lun; + + /* + * NOTE: assumes that "sc->sc_transfer.cmd_data" and + * "sc->sc_transfer.cmd_len" has been properly + * initialized. + */ + + sc->sc_transfer.dir = data_len ? dir : DIR_NONE; + sc->sc_transfer.data_ptr = data_ptr; + sc->sc_transfer.data_len = data_len; + sc->sc_transfer.data_rem = data_len; + sc->sc_transfer.data_timeout = (data_timeout + UMASS_TIMEOUT); + + sc->sc_transfer.actlen = 0; + sc->sc_transfer.callback = callback; + sc->sc_transfer.ccb = ccb; + + if (sc->sc_xfer[sc->sc_last_xfer_index]) { + usb2_transfer_start(sc->sc_xfer[sc->sc_last_xfer_index]); + } else { + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + } + return; +} + +static uint8_t +umass_bbb_get_max_lun(struct umass_softc *sc) +{ + struct usb2_device_request req; + usb2_error_t err; + uint8_t buf = 0; + + /* The Get Max Lun command is a class-specific request. */ + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_BBB_GET_MAX_LUN; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + + err = usb2_do_request(sc->sc_udev, &Giant, &req, &buf); + if (err) { + buf = 0; + + /* Device doesn't support Get Max Lun request. */ + printf("%s: Get Max Lun not supported (%s)\n", + sc->sc_name, usb2_errstr(err)); + } + return (buf); +} + +/* + * Command/Bulk/Interrupt (CBI) specific functions + */ + +static void +umass_cbi_start_status(struct umass_softc *sc) +{ + if (sc->sc_xfer[UMASS_T_CBI_STATUS]) { + umass_transfer_start(sc, UMASS_T_CBI_STATUS); + } else { + union ccb *ccb = sc->sc_transfer.ccb; + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, (sc->sc_transfer.data_len - + sc->sc_transfer.actlen), STATUS_CMD_UNKNOWN); + } + return; +} + +static void +umass_t_cbi_reset1_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + struct usb2_device_request req; + uint8_t buf[UMASS_CBI_DIAGNOSTIC_CMDLEN]; + + uint8_t i; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start(sc, UMASS_T_CBI_RESET2); + return; + + case USB_ST_SETUP: + /* + * Command Block Reset Protocol + * + * First send a reset request to the device. Then clear + * any possibly stalled bulk endpoints. + * + * This is done in 3 steps, using 3 transfers: + * UMASS_T_CBI_RESET1 + * UMASS_T_CBI_RESET2 + * UMASS_T_CBI_RESET3 + * UMASS_T_CBI_RESET4 (only if there is an interrupt endpoint) + */ + + DPRINTF(sc, UDMASS_CBI, "CBI reset!\n"); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_CBI_ADSC; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UMASS_CBI_DIAGNOSTIC_CMDLEN); + + /* + * The 0x1d code is the SEND DIAGNOSTIC command. To + * distinguish between the two, the last 10 bytes of the CBL + * is filled with 0xff (section 2.2 of the CBI + * specification) + */ + buf[0] = 0x1d; /* Command Block Reset */ + buf[1] = 0x04; + + for (i = 2; i < UMASS_CBI_DIAGNOSTIC_CMDLEN; i++) { + buf[i] = 0xff; + } + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + usb2_copy_in(xfer->frbuffers + 1, 0, buf, sizeof(buf)); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = sizeof(buf); + xfer->nframes = 2; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + umass_tr_error(xfer); + return; + + } +} + +static void +umass_t_cbi_reset2_callback(struct usb2_xfer *xfer) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_RESET3, + UMASS_T_CBI_DATA_READ); + return; +} + +static void +umass_t_cbi_reset3_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + + umass_t_cbi_data_clear_stall_callback + (xfer, (sc->sc_xfer[UMASS_T_CBI_RESET4] && + sc->sc_xfer[UMASS_T_CBI_STATUS]) ? + UMASS_T_CBI_RESET4 : UMASS_T_CBI_COMMAND, + UMASS_T_CBI_DATA_WRITE); + + return; +} + +static void +umass_t_cbi_reset4_callback(struct usb2_xfer *xfer) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_COMMAND, + UMASS_T_CBI_STATUS); + return; +} + +static void +umass_t_cbi_data_clear_stall_callback(struct usb2_xfer *xfer, + uint8_t next_xfer, + uint8_t stall_xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (next_xfer == UMASS_T_CBI_STATUS) { + umass_cbi_start_status(sc); + } else { + umass_transfer_start(sc, next_xfer); + } + return; + + case USB_ST_SETUP: + if (usb2_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { + goto tr_transferred; /* should not happen */ + } + return; + + default: /* Error */ + umass_tr_error(xfer); + return; + + } +} + +static void +umass_t_cbi_command_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + union ccb *ccb = sc->sc_transfer.ccb; + struct usb2_device_request req; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (sc->sc_transfer.dir == DIR_NONE) { + umass_cbi_start_status(sc); + } else { + umass_transfer_start + (sc, (sc->sc_transfer.dir == DIR_IN) ? + UMASS_T_CBI_DATA_READ : UMASS_T_CBI_DATA_WRITE); + } + return; + + case USB_ST_SETUP: + + if (ccb) { + + /* + * do a CBI transfer with cmd_len bytes from + * cmd_data, possibly a data phase of data_len + * bytes from/to the device and finally a status + * read phase. + */ + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_CBI_ADSC; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + req.wLength[0] = sc->sc_transfer.cmd_len; + req.wLength[1] = 0; + + usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + usb2_copy_in(xfer->frbuffers + 1, 0, sc->sc_transfer.cmd_data, + sc->sc_transfer.cmd_len); + + xfer->frlengths[0] = sizeof(req); + xfer->frlengths[1] = sc->sc_transfer.cmd_len; + xfer->nframes = xfer->frlengths[1] ? 2 : 1; + + DIF(UDMASS_CBI, + umass_cbi_dump_cmd(sc, + sc->sc_transfer.cmd_data, + sc->sc_transfer.cmd_len)); + + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + umass_tr_error(xfer); + return; + + } +} + +static void +umass_t_cbi_data_read_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (!xfer->flags.ext_buffer) { + usb2_copy_out(xfer->frbuffers, 0, + sc->sc_transfer.data_ptr, xfer->actlen); + } + sc->sc_transfer.data_rem -= xfer->actlen; + sc->sc_transfer.data_ptr += xfer->actlen; + sc->sc_transfer.actlen += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_cbi_start_status(sc); + return; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + xfer->timeout = sc->sc_transfer.data_timeout; + + xfer->frlengths[0] = max_bulk; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if ((xfer->error == USB_ERR_CANCELLED) || + (sc->sc_transfer.callback != &umass_cam_cb)) { + umass_tr_error(xfer); + } else { + umass_transfer_start(sc, UMASS_T_CBI_DATA_RD_CS); + } + return; + + } +} + +static void +umass_t_cbi_data_rd_cs_callback(struct usb2_xfer *xfer) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, + UMASS_T_CBI_DATA_READ); + return; +} + +static void +umass_t_cbi_data_write_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= xfer->actlen; + sc->sc_transfer.data_ptr += xfer->actlen; + sc->sc_transfer.actlen += xfer->actlen; + + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_cbi_start_status(sc); + return; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + xfer->timeout = sc->sc_transfer.data_timeout; + + if (xfer->flags.ext_buffer) { + usb2_set_frame_data(xfer, sc->sc_transfer.data_ptr, 0); + } else { + usb2_copy_in(xfer->frbuffers, 0, + sc->sc_transfer.data_ptr, max_bulk); + } + + xfer->frlengths[0] = max_bulk; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if ((xfer->error == USB_ERR_CANCELLED) || + (sc->sc_transfer.callback != &umass_cam_cb)) { + umass_tr_error(xfer); + } else { + umass_transfer_start(sc, UMASS_T_CBI_DATA_WR_CS); + } + return; + + } +} + +static void +umass_t_cbi_data_wr_cs_callback(struct usb2_xfer *xfer) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, + UMASS_T_CBI_DATA_WRITE); + return; +} + +static void +umass_t_cbi_status_callback(struct usb2_xfer *xfer) +{ + struct umass_softc *sc = xfer->priv_sc; + union ccb *ccb = sc->sc_transfer.ccb; + uint32_t residue; + uint8_t status; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen < sizeof(sc->sbl)) { + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, &sc->sbl, sizeof(sc->sbl)); + + residue = (sc->sc_transfer.data_len - + sc->sc_transfer.actlen); + + /* dissect the information in the buffer */ + + if (sc->sc_proto & UMASS_PROTO_UFI) { + + /* + * Section 3.4.3.1.3 specifies that the UFI command + * protocol returns an ASC and ASCQ in the interrupt + * data block. + */ + + DPRINTF(sc, UDMASS_CBI, "UFI CCI, ASC = 0x%02x, " + "ASCQ = 0x%02x\n", sc->sbl.ufi.asc, + sc->sbl.ufi.ascq); + + status = (((sc->sbl.ufi.asc == 0) && + (sc->sbl.ufi.ascq == 0)) ? + STATUS_CMD_OK : STATUS_CMD_FAILED); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, status); + + return; + + } else { + + /* Command Interrupt Data Block */ + + DPRINTF(sc, UDMASS_CBI, "type=0x%02x, value=0x%02x\n", + sc->sbl.common.type, sc->sbl.common.value); + + if (sc->sbl.common.type == IDB_TYPE_CCI) { + + status = (sc->sbl.common.value & IDB_VALUE_STATUS_MASK); + + status = ((status == IDB_VALUE_PASS) ? STATUS_CMD_OK : + (status == IDB_VALUE_FAIL) ? STATUS_CMD_FAILED : + (status == IDB_VALUE_PERSISTENT) ? STATUS_CMD_FAILED : + STATUS_WIRE_FAILED); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, status); + + return; + } + } + + /* fallthrough */ + + case USB_ST_SETUP: +tr_setup: + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + DPRINTF(sc, UDMASS_CBI, "Failed to read CSW: %s\n", + usb2_errstr(xfer->error)); + umass_tr_error(xfer); + return; + + } +} + +/* + * CAM specific functions (used by SCSI, UFI, 8070i (ATAPI)) + */ + +static int +umass_cam_attach_sim(struct umass_softc *sc) +{ + struct cam_devq *devq; /* Per device Queue */ + + if (umass_sim[sc->sc_unit] != NULL) { + sc->sc_sim = umass_sim[sc->sc_unit]; + goto register_only; + } + /* + * A HBA is attached to the CAM layer. + * + * The CAM layer will then after a while start probing for devices on + * the bus. The number of SIMs is limited to one. + */ + + devq = cam_simq_alloc(1 /* maximum openings */ ); + if (devq == NULL) { + return (ENOMEM); + } + sc->sc_sim = cam_sim_alloc + (&umass_cam_action, &umass_cam_poll, + DEVNAME_SIM, + sc /* priv */ , + sc->sc_unit /* unit number */ , +#if (__FreeBSD_version >= 700037) + &umass_mtx /* mutex */ , +#endif + 1 /* maximum device openings */ , + 0 /* maximum tagged device openings */ , + devq); + + if (sc->sc_sim == NULL) { + cam_simq_free(devq); + return (ENOMEM); + } + umass_sim[sc->sc_unit] = sc->sc_sim; + +register_only: + + /* update the softc pointer */ + sc->sc_sim->softc = sc; + +#if (__FreeBSD_version >= 700037) + mtx_lock(&umass_mtx); +#endif + +#if (__FreeBSD_version >= 700048) + if (xpt_bus_register(sc->sc_sim, sc->sc_dev, sc->sc_unit) != CAM_SUCCESS) { + mtx_unlock(&umass_mtx); + return (ENOMEM); + } +#else + if (xpt_bus_register(sc->sc_sim, sc->sc_unit) != CAM_SUCCESS) { +#if (__FreeBSD_version >= 700037) + mtx_unlock(&umass_mtx); +#endif + return (ENOMEM); + } +#endif + +#if (__FreeBSD_version >= 700037) + mtx_unlock(&umass_mtx); +#endif + return (0); +} + +static void +umass_cam_rescan_callback(struct cam_periph *periph, union ccb *ccb) +{ +#if USB_DEBUG + struct umass_softc *sc = NULL; + + if (ccb->ccb_h.status != CAM_REQ_CMP) { + DPRINTF(sc, UDMASS_SCSI, "%s:%d Rescan failed, 0x%04x\n", + periph->periph_name, periph->unit_number, + ccb->ccb_h.status); + } else { + DPRINTF(sc, UDMASS_SCSI, "%s%d: Rescan succeeded\n", + periph->periph_name, periph->unit_number); + } +#endif + + xpt_free_path(ccb->ccb_h.path); + free(ccb, M_USBDEV); + return; +} + +static void +umass_cam_rescan(struct umass_softc *sc) +{ + struct cam_path *path; + union ccb *ccb; + + DPRINTF(sc, UDMASS_SCSI, "scbus%d: scanning for %d:%d:%d\n", + cam_sim_path(sc->sc_sim), + cam_sim_path(sc->sc_sim), + sc->sc_unit, CAM_LUN_WILDCARD); + + ccb = malloc(sizeof(*ccb), M_USBDEV, M_WAITOK | M_ZERO); + + if (ccb == NULL) { + return; + } +#if (__FreeBSD_version >= 700037) + mtx_lock(&umass_mtx); +#endif + + if (xpt_create_path(&path, xpt_periph, cam_sim_path(sc->sc_sim), + CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) + != CAM_REQ_CMP) { +#if (__FreeBSD_version >= 700037) + mtx_unlock(&umass_mtx); +#endif + free(ccb, M_USBDEV); + return; + } + xpt_setup_ccb(&ccb->ccb_h, path, 5 /* priority (low) */ ); + ccb->ccb_h.func_code = XPT_SCAN_BUS; + ccb->ccb_h.cbfcnp = &umass_cam_rescan_callback; + ccb->crcn.flags = CAM_FLAG_NONE; + xpt_action(ccb); + +#if (__FreeBSD_version >= 700037) + mtx_unlock(&umass_mtx); +#endif + + /* The scan is in progress now. */ + + return; +} + +static void +umass_cam_attach(struct umass_softc *sc) +{ +#ifndef USB_DEBUG + if (bootverbose) +#endif + printf("%s:%d:%d:%d: Attached to scbus%d\n", + sc->sc_name, cam_sim_path(sc->sc_sim), + sc->sc_unit, CAM_LUN_WILDCARD, + cam_sim_path(sc->sc_sim)); + + if (!cold) { + /* + * Notify CAM of the new device after a short delay. Any + * failure is benign, as the user can still do it by hand + * (camcontrol rescan ). Only do this if we are not + * booting, because CAM does a scan after booting has + * completed, when interrupts have been enabled. + */ + + /* scan the new sim */ + umass_cam_rescan(sc); + } + return; +} + +/* umass_cam_detach + * detach from the CAM layer + */ + +static void +umass_cam_detach_sim(struct umass_softc *sc) +{ + if (sc->sc_sim) { + if (xpt_bus_deregister(cam_sim_path(sc->sc_sim))) { +#if 0 /* NOTYET */ + cam_sim_free(sc->sc_sim, /* free_devq */ TRUE); +#else + /* accessing the softc is not possible after this */ + sc->sc_sim->softc = UMASS_GONE; +#endif + } else { + panic("%s: CAM layer is busy!\n", + sc->sc_name); + } + sc->sc_sim = NULL; + } + return; +} + +/* umass_cam_action + * CAM requests for action come through here + */ + +static void +umass_cam_action(struct cam_sim *sim, union ccb *ccb) +{ + struct umass_softc *sc = (struct umass_softc *)sim->softc; + + if (sc == UMASS_GONE) { + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + return; + } + if (sc) { +#if (__FreeBSD_version < 700037) + mtx_lock(&umass_mtx); +#endif + } + /* + * Verify, depending on the operation to perform, that we either got + * a valid sc, because an existing target was referenced, or + * otherwise the SIM is addressed. + * + * This avoids bombing out at a printf and does give the CAM layer some + * sensible feedback on errors. + */ + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + case XPT_RESET_DEV: + case XPT_GET_TRAN_SETTINGS: + case XPT_SET_TRAN_SETTINGS: + case XPT_CALC_GEOMETRY: + /* the opcodes requiring a target. These should never occur. */ + if (sc == NULL) { + DPRINTF(sc, UDMASS_GEN, "%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (target needed)\n", + DEVNAME_SIM, cam_sim_path(sc->sc_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun, + ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + goto done; + } + break; + case XPT_PATH_INQ: + case XPT_NOOP: + /* + * The opcodes sometimes aimed at a target (sc is valid), + * sometimes aimed at the SIM (sc is invalid and target is + * CAM_TARGET_WILDCARD) + */ + if ((sc == NULL) && + (ccb->ccb_h.target_id != CAM_TARGET_WILDCARD)) { + DPRINTF(sc, UDMASS_SCSI, "%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (no wildcard)\n", + DEVNAME_SIM, cam_sim_path(sc->sc_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun, + ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + goto done; + } + break; + default: + /* XXX Hm, we should check the input parameters */ + break; + } + + /* Perform the requested action */ + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + { + uint8_t *cmd; + uint8_t dir; + + if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); + } else { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); + } + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SCSI_IO: " + "cmd: 0x%02x, flags: 0x%02x, " + "%db cmd/%db data/%db sense\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun, cmd[0], + ccb->ccb_h.flags & CAM_DIR_MASK, ccb->csio.cdb_len, + ccb->csio.dxfer_len, ccb->csio.sense_len); + + if (sc->sc_transfer.ccb) { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SCSI_IO: " + "I/O in progress, deferring\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + ccb->ccb_h.status = CAM_SCSI_BUSY; + xpt_done(ccb); + goto done; + } + switch (ccb->ccb_h.flags & CAM_DIR_MASK) { + case CAM_DIR_IN: + dir = DIR_IN; + break; + case CAM_DIR_OUT: + dir = DIR_OUT; + DIF(UDMASS_SCSI, + umass_dump_buffer(sc, ccb->csio.data_ptr, + ccb->csio.dxfer_len, 48)); + break; + default: + dir = DIR_NONE; + } + + ccb->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED; + + /* + * sc->sc_transform will convert the command to the + * command format needed by the specific command set + * and return the converted command in + * "sc->sc_transfer.cmd_data" + */ + if (umass_std_transform(sc, ccb, cmd, ccb->csio.cdb_len)) { + + if (sc->sc_transfer.cmd_data[0] == INQUIRY) { + + /* + * Handle EVPD inquiry for broken devices first + * NO_INQUIRY also implies NO_INQUIRY_EVPD + */ + if ((sc->sc_quirks & (NO_INQUIRY_EVPD | NO_INQUIRY)) && + (sc->sc_transfer.cmd_data[1] & SI_EVPD)) { + struct scsi_sense_data *sense; + + sense = &ccb->csio.sense_data; + bzero(sense, sizeof(*sense)); + sense->error_code = SSD_CURRENT_ERROR; + sense->flags = SSD_KEY_ILLEGAL_REQUEST; + sense->add_sense_code = 0x24; + sense->extra_len = 10; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | + CAM_AUTOSNS_VALID; + xpt_done(ccb); + goto done; + } + /* + * Return fake inquiry data for + * broken devices + */ + if (sc->sc_quirks & NO_INQUIRY) { + memcpy(ccb->csio.data_ptr, &fake_inq_data, + sizeof(fake_inq_data)); + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + ccb->csio.dxfer_len = SHORT_INQUIRY_LENGTH; + } + } else if (sc->sc_transfer.cmd_data[0] == SYNCHRONIZE_CACHE) { + if (sc->sc_quirks & NO_SYNCHRONIZE_CACHE) { + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } + } + umass_command_start(sc, dir, ccb->csio.data_ptr, + ccb->csio.dxfer_len, + ccb->ccb_h.timeout, + &umass_cam_cb, ccb); + } + break; + } + case XPT_PATH_INQ: + { + struct ccb_pathinq *cpi = &ccb->cpi; + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_PATH_INQ:.\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + /* host specific information */ + cpi->version_num = 1; + cpi->hba_inquiry = 0; + cpi->target_sprt = 0; + cpi->hba_misc = PIM_NO_6_BYTE; + cpi->hba_eng_cnt = 0; + cpi->max_target = UMASS_SCSIID_MAX; /* one target */ + cpi->initiator_id = UMASS_SCSIID_HOST; + strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strlcpy(cpi->hba_vid, "USB SCSI", HBA_IDLEN); + strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->bus_id = sc->sc_unit; +#if (__FreeBSD_version >= 700025) + cpi->protocol = PROTO_SCSI; + cpi->protocol_version = SCSI_REV_2; + cpi->transport = XPORT_USB; + cpi->transport_version = 0; +#endif + if (sc == NULL) { + cpi->base_transfer_speed = 0; + cpi->max_lun = 0; + } else { + if (sc->sc_quirks & FLOPPY_SPEED) { + cpi->base_transfer_speed = + UMASS_FLOPPY_TRANSFER_SPEED; + } else if (usb2_get_speed(sc->sc_udev) == + USB_SPEED_HIGH) { + cpi->base_transfer_speed = + UMASS_HIGH_TRANSFER_SPEED; + } else { + cpi->base_transfer_speed = + UMASS_FULL_TRANSFER_SPEED; + } + cpi->max_lun = sc->sc_maxlun; + } + + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_RESET_DEV: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_RESET_DEV:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + umass_reset(sc); + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + { + struct ccb_trans_settings *cts = &ccb->cts; + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_GET_TRAN_SETTINGS:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + +#if (__FreeBSD_version >= 700025) + cts->protocol = PROTO_SCSI; + cts->protocol_version = SCSI_REV_2; + cts->transport = XPORT_USB; + cts->transport_version = 0; + cts->xport_specific.valid = 0; +#else + cts->valid = 0; + cts->flags = 0; /* no disconnection, tagging */ +#endif + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_SET_TRAN_SETTINGS: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SET_TRAN_SETTINGS:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } + case XPT_CALC_GEOMETRY: + { + cam_calc_geometry(&ccb->ccg, /* extended */ 1); + xpt_done(ccb); + break; + } + case XPT_NOOP: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_NOOP:.\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + default: + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:func_code 0x%04x: " + "Not implemented\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun, ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } + +done: +#if (__FreeBSD_version < 700037) + if (sc) { + mtx_unlock(&umass_mtx); + } +#endif + return; +} + +static void +umass_cam_poll(struct cam_sim *sim) +{ + struct umass_softc *sc = (struct umass_softc *)sim->softc; + + if (sc == UMASS_GONE) + return; + + DPRINTF(sc, UDMASS_SCSI, "CAM poll\n"); + + usb2_do_poll(sc->sc_xfer, UMASS_T_MAX); + return; +} + + +/* umass_cam_cb + * finalise a completed CAM command + */ + +static void +umass_cam_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + ccb->csio.resid = residue; + + switch (status) { + case STATUS_CMD_OK: + ccb->ccb_h.status = CAM_REQ_CMP; + if ((sc->sc_quirks & READ_CAPACITY_OFFBY1) && + (ccb->ccb_h.func_code == XPT_SCSI_IO) && + (ccb->csio.cdb_io.cdb_bytes[0] == READ_CAPACITY)) { + struct scsi_read_capacity_data *rcap; + uint32_t maxsector; + + rcap = (void *)(ccb->csio.data_ptr); + maxsector = scsi_4btoul(rcap->addr) - 1; + scsi_ulto4b(maxsector, rcap->addr); + } + xpt_done(ccb); + break; + + case STATUS_CMD_UNKNOWN: + case STATUS_CMD_FAILED: + + /* fetch sense data */ + + /* the rest of the command was filled in at attach */ + sc->cam_scsi_sense.length = ccb->csio.sense_len; + + DPRINTF(sc, UDMASS_SCSI, "Fetching %d bytes of " + "sense data\n", ccb->csio.sense_len); + + if (umass_std_transform(sc, ccb, &sc->cam_scsi_sense.opcode, + sizeof(sc->cam_scsi_sense))) { + + if ((sc->sc_quirks & FORCE_SHORT_INQUIRY) && + (sc->sc_transfer.cmd_data[0] == INQUIRY)) { + ccb->csio.sense_len = SHORT_INQUIRY_LENGTH; + } + umass_command_start(sc, DIR_IN, &ccb->csio.sense_data.error_code, + ccb->csio.sense_len, ccb->ccb_h.timeout, + &umass_cam_sense_cb, ccb); + } + break; + + default: + /* + * the wire protocol failed and will have recovered + * (hopefully). We return an error to CAM and let CAM retry + * the command if necessary. + */ + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + xpt_done(ccb); + break; + } +} + +/* + * Finalise a completed autosense operation + */ +static void +umass_cam_sense_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + uint8_t *cmd; + uint8_t key; + + switch (status) { + case STATUS_CMD_OK: + case STATUS_CMD_UNKNOWN: + case STATUS_CMD_FAILED: + + if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); + } else { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); + } + + key = (ccb->csio.sense_data.flags & SSD_KEY); + + /* + * Getting sense data always succeeds (apart from wire + * failures): + */ + if ((sc->sc_quirks & RS_NO_CLEAR_UA) && + (cmd[0] == INQUIRY) && + (key == SSD_KEY_UNIT_ATTENTION)) { + /* + * Ignore unit attention errors in the case where + * the Unit Attention state is not cleared on + * REQUEST SENSE. They will appear again at the next + * command. + */ + ccb->ccb_h.status = CAM_REQ_CMP; + } else if (key == SSD_KEY_NO_SENSE) { + /* + * No problem after all (in the case of CBI without + * CCI) + */ + ccb->ccb_h.status = CAM_REQ_CMP; + } else if ((sc->sc_quirks & RS_NO_CLEAR_UA) && + (cmd[0] == READ_CAPACITY) && + (key == SSD_KEY_UNIT_ATTENTION)) { + /* + * Some devices do not clear the unit attention error + * on request sense. We insert a test unit ready + * command to make sure we clear the unit attention + * condition, then allow the retry to proceed as + * usual. + */ + + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + +#if 0 + DELAY(300000); +#endif + DPRINTF(sc, UDMASS_SCSI, "Doing a sneaky" + "TEST_UNIT_READY\n"); + + /* the rest of the command was filled in at attach */ + + if (umass_std_transform(sc, ccb, + &sc->cam_scsi_test_unit_ready.opcode, + sizeof(sc->cam_scsi_test_unit_ready))) { + umass_command_start(sc, DIR_NONE, NULL, 0, + ccb->ccb_h.timeout, + &umass_cam_quirk_cb, ccb); + } + break; + } else { + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + } + xpt_done(ccb); + break; + + default: + DPRINTF(sc, UDMASS_SCSI, "Autosense failed, " + "status %d\n", status); + ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; + xpt_done(ccb); + } + return; +} + +/* + * This completion code just handles the fact that we sent a test-unit-ready + * after having previously failed a READ CAPACITY with CHECK_COND. Even + * though this command succeeded, we have to tell CAM to retry. + */ +static void +umass_cam_quirk_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + DPRINTF(sc, UDMASS_SCSI, "Test unit ready " + "returned status %d\n", status); + + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + xpt_done(ccb); + return; +} + +/* + * SCSI specific functions + */ + +static uint8_t +umass_scsi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + sc->sc_transfer.cmd_len = cmd_len; + + switch (cmd_ptr[0]) { + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + bzero(sc->sc_transfer.cmd_data, cmd_len); + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case INQUIRY: + /* + * some drives wedge when asked for full inquiry + * information. + */ + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; + return (1); + } + break; + } + + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + return (1); +} + +static uint8_t +umass_rbc_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + switch (cmd_ptr[0]) { + /* these commands are defined in RBC: */ + case READ_10: + case READ_CAPACITY: + case START_STOP_UNIT: + case SYNCHRONIZE_CACHE: + case WRITE_10: + case 0x2f: /* VERIFY_10 is absent from + * scsi_all.h??? */ + case INQUIRY: + case MODE_SELECT_10: + case MODE_SENSE_10: + case TEST_UNIT_READY: + case WRITE_BUFFER: + /* + * The following commands are not listed in my copy of the + * RBC specs. CAM however seems to want those, and at least + * the Sony DSC device appears to support those as well + */ + case REQUEST_SENSE: + case PREVENT_ALLOW: + + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + + if ((sc->sc_quirks & RBC_PAD_TO_12) && (cmd_len < 12)) { + bzero(sc->sc_transfer.cmd_data + cmd_len, 12 - cmd_len); + cmd_len = 12; + } + sc->sc_transfer.cmd_len = cmd_len; + return (1); /* sucess */ + + /* All other commands are not legal in RBC */ + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported RBC " + "command 0x%02x\n", cmd_ptr[0]); + return (0); /* failure */ + } +} + +static uint8_t +umass_ufi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + /* An UFI command is always 12 bytes in length */ + sc->sc_transfer.cmd_len = UFI_COMMAND_LENGTH; + + /* Zero the command data */ + bzero(sc->sc_transfer.cmd_data, UFI_COMMAND_LENGTH); + + switch (cmd_ptr[0]) { + /* + * Commands of which the format has been verified. They + * should work. Copy the command into the (zeroed out) + * destination buffer. + */ + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + /* + * Some devices do not support this command. Start + * Stop Unit should give the same results + */ + DPRINTF(sc, UDMASS_UFI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case REZERO_UNIT: + case REQUEST_SENSE: + case FORMAT_UNIT: + case INQUIRY: + case START_STOP_UNIT: + case SEND_DIAGNOSTIC: + case PREVENT_ALLOW: + case READ_CAPACITY: + case READ_10: + case WRITE_10: + case POSITION_TO_ELEMENT: /* SEEK_10 */ + case WRITE_AND_VERIFY: + case VERIFY: + case MODE_SELECT_10: + case MODE_SENSE_10: + case READ_12: + case WRITE_12: + case READ_FORMAT_CAPACITIES: + break; + + /* + * SYNCHRONIZE_CACHE isn't supported by UFI, nor should it be + * required for UFI devices, so it is appropriate to fake + * success. + */ + case SYNCHRONIZE_CACHE: + return (2); + + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported UFI " + "command 0x%02x\n", cmd_ptr[0]); + return (0); /* failure */ + } + + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + return (1); /* success */ +} + +/* + * 8070i (ATAPI) specific functions + */ +static uint8_t +umass_atapi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + /* An ATAPI command is always 12 bytes in length. */ + sc->sc_transfer.cmd_len = ATAPI_COMMAND_LENGTH; + + /* Zero the command data */ + bzero(sc->sc_transfer.cmd_data, ATAPI_COMMAND_LENGTH); + + switch (cmd_ptr[0]) { + /* + * Commands of which the format has been verified. They + * should work. Copy the command into the destination + * buffer. + */ + case INQUIRY: + /* + * some drives wedge when asked for full inquiry + * information. + */ + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + + sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; + return (1); + } + break; + + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case REZERO_UNIT: + case REQUEST_SENSE: + case START_STOP_UNIT: + case SEND_DIAGNOSTIC: + case PREVENT_ALLOW: + case READ_CAPACITY: + case READ_10: + case WRITE_10: + case POSITION_TO_ELEMENT: /* SEEK_10 */ + case SYNCHRONIZE_CACHE: + case MODE_SELECT_10: + case MODE_SENSE_10: + case READ_BUFFER: + case 0x42: /* READ_SUBCHANNEL */ + case 0x43: /* READ_TOC */ + case 0x44: /* READ_HEADER */ + case 0x47: /* PLAY_MSF (Play Minute/Second/Frame) */ + case 0x48: /* PLAY_TRACK */ + case 0x49: /* PLAY_TRACK_REL */ + case 0x4b: /* PAUSE */ + case 0x51: /* READ_DISK_INFO */ + case 0x52: /* READ_TRACK_INFO */ + case 0x54: /* SEND_OPC */ + case 0x59: /* READ_MASTER_CUE */ + case 0x5b: /* CLOSE_TR_SESSION */ + case 0x5c: /* READ_BUFFER_CAP */ + case 0x5d: /* SEND_CUE_SHEET */ + case 0xa1: /* BLANK */ + case 0xa5: /* PLAY_12 */ + case 0xa6: /* EXCHANGE_MEDIUM */ + case 0xad: /* READ_DVD_STRUCTURE */ + case 0xbb: /* SET_CD_SPEED */ + case 0xe5: /* READ_TRACK_INFO_PHILIPS */ + break;; + + case READ_12: + case WRITE_12: + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported ATAPI " + "command 0x%02x - trying anyway\n", + cmd_ptr[0]); + break;; + } + + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + return (1); /* success */ +} + +static uint8_t +umass_no_transform(struct umass_softc *sc, uint8_t *cmd, + uint8_t cmdlen) +{ + return (0); /* failure */ +} + +static uint8_t +umass_std_transform(struct umass_softc *sc, union ccb *ccb, + uint8_t *cmd, uint8_t cmdlen) +{ + uint8_t retval; + + retval = (sc->sc_transform) (sc, cmd, cmdlen); + + if (retval == 2) { + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return (0); + } else if (retval == 0) { + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + return (0); + } + /* Command should be executed */ + return (1); +} + +#if USB_DEBUG +static void +umass_bbb_dump_cbw(struct umass_softc *sc, umass_bbb_cbw_t *cbw) +{ + uint8_t *c = cbw->CBWCDB; + + uint32_t dlen = UGETDW(cbw->dCBWDataTransferLength); + uint32_t tag = UGETDW(cbw->dCBWTag); + + uint8_t clen = cbw->bCDBLength; + uint8_t flags = cbw->bCBWFlags; + uint8_t lun = cbw->bCBWLUN; + + DPRINTF(sc, UDMASS_BBB, "CBW %d: cmd = %db " + "(0x%02x%02x%02x%02x%02x%02x%s), " + "data = %db, lun = %d, dir = %s\n", + tag, clen, + c[0], c[1], c[2], c[3], c[4], c[5], (clen > 6 ? "..." : ""), + dlen, lun, (flags == CBWFLAGS_IN ? "in" : + (flags == CBWFLAGS_OUT ? "out" : ""))); + return; +} + +static void +umass_bbb_dump_csw(struct umass_softc *sc, umass_bbb_csw_t *csw) +{ + uint32_t sig = UGETDW(csw->dCSWSignature); + uint32_t tag = UGETDW(csw->dCSWTag); + uint32_t res = UGETDW(csw->dCSWDataResidue); + uint8_t status = csw->bCSWStatus; + + DPRINTF(sc, UDMASS_BBB, "CSW %d: sig = 0x%08x (%s), tag = 0x%08x, " + "res = %d, status = 0x%02x (%s)\n", + tag, sig, (sig == CSWSIGNATURE ? "valid" : "invalid"), + tag, res, + status, (status == CSWSTATUS_GOOD ? "good" : + (status == CSWSTATUS_FAILED ? "failed" : + (status == CSWSTATUS_PHASE ? "phase" : "")))); + return; +} + +static void +umass_cbi_dump_cmd(struct umass_softc *sc, void *cmd, uint8_t cmdlen) +{ + uint8_t *c = cmd; + uint8_t dir = sc->sc_transfer.dir; + + DPRINTF(sc, UDMASS_BBB, "cmd = %db " + "(0x%02x%02x%02x%02x%02x%02x%s), " + "data = %db, dir = %s\n", + cmdlen, + c[0], c[1], c[2], c[3], c[4], c[5], (cmdlen > 6 ? "..." : ""), + sc->sc_transfer.data_len, + (dir == DIR_IN ? "in" : + (dir == DIR_OUT ? "out" : + (dir == DIR_NONE ? "no data phase" : "")))); + return; +} + +static void +umass_dump_buffer(struct umass_softc *sc, uint8_t *buffer, uint32_t buflen, + uint32_t printlen) +{ + uint32_t i, j; + char s1[40]; + char s2[40]; + char s3[5]; + + s1[0] = '\0'; + s3[0] = '\0'; + + sprintf(s2, " buffer=%p, buflen=%d", buffer, buflen); + for (i = 0; (i < buflen) && (i < printlen); i++) { + j = i % 16; + if (j == 0 && i != 0) { + DPRINTF(sc, UDMASS_GEN, "0x %s%s\n", + s1, s2); + s2[0] = '\0'; + } + sprintf(&s1[j * 2], "%02x", buffer[i] & 0xff); + } + if (buflen > printlen) + sprintf(s3, " ..."); + DPRINTF(sc, UDMASS_GEN, "0x %s%s%s\n", + s1, s2, s3); + return; +} + +#endif + +static int +umass_driver_loaded(struct module *mod, int what, void *arg) +{ + uint16_t x; + + switch (what) { + case MOD_LOAD: + mtx_init(&umass_mtx, "UMASS lock", NULL, (MTX_DEF | MTX_RECURSE)); + break; + + case MOD_UNLOAD: + for (x = 0; x != UMASS_MAXUNIT; x++) { + /* cleanup */ + if (umass_sim[x]) + cam_sim_free(umass_sim[x], /* free_devq */ TRUE); + } + mtx_destroy(&umass_mtx); + break; + default: + return (EOPNOTSUPP); + } + + return (0); +} diff --git a/sys/dev/usb2/storage/urio2.c b/sys/dev/usb2/storage/urio2.c new file mode 100644 index 000000000000..775c3cb4d8b3 --- /dev/null +++ b/sys/dev/usb2/storage/urio2.c @@ -0,0 +1,491 @@ +/*- + * Copyright (c) 2000 Iwasa Kazmi + * 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. + * + * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson. + * This code includes software developed by the NetBSD Foundation, Inc. and + * its contributors. + */ + +#include +__FBSDID("$FreeBSD$"); + + +/* + * 2000/3/24 added NetBSD/OpenBSD support (from Alex Nemirovsky) + * 2000/3/07 use two bulk-pipe handles for read and write (Dirk) + * 2000/3/06 change major number(143), and copyright header + * some fix for 4.0 (Dirk) + * 2000/3/05 codes for FreeBSD 4.x - CURRENT (Thanks to Dirk-Willem van Gulik) + * 2000/3/01 remove retry code from urioioctl() + * change method of bulk transfer (no interrupt) + * 2000/2/28 small fixes for new rio_usb.h + * 2000/2/24 first version. + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR urio_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USB_DEBUG +static int urio_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, urio, CTLFLAG_RW, 0, "USB urio"); +SYSCTL_INT(_hw_usb2_urio, OID_AUTO, debug, CTLFLAG_RW, + &urio_debug, 0, "urio debug level"); +#endif + +#define URIO_T_WR 0 +#define URIO_T_RD 1 +#define URIO_T_WR_CS 2 +#define URIO_T_RD_CS 3 +#define URIO_T_MAX 4 + +#define URIO_BSIZE (1<<12) /* bytes */ +#define URIO_IFQ_MAXLEN 2 /* units */ + +struct urio_softc { + struct usb2_fifo_sc sc_fifo; + struct mtx sc_mtx; + + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[URIO_T_MAX]; + + uint8_t sc_flags; +#define URIO_FLAG_READ_STALL 0x01 /* read transfer stalled */ +#define URIO_FLAG_WRITE_STALL 0x02 /* write transfer stalled */ + + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t urio_probe; +static device_attach_t urio_attach; +static device_detach_t urio_detach; + +static usb2_callback_t urio_write_callback; +static usb2_callback_t urio_write_clear_stall_callback; +static usb2_callback_t urio_read_callback; +static usb2_callback_t urio_read_clear_stall_callback; + +static usb2_fifo_close_t urio_close; +static usb2_fifo_cmd_t urio_start_read; +static usb2_fifo_cmd_t urio_start_write; +static usb2_fifo_cmd_t urio_stop_read; +static usb2_fifo_cmd_t urio_stop_write; +static usb2_fifo_ioctl_t urio_ioctl; +static usb2_fifo_open_t urio_open; + +static struct usb2_fifo_methods urio_fifo_methods = { + .f_close = &urio_close, + .f_ioctl = &urio_ioctl, + .f_open = &urio_open, + .f_start_read = &urio_start_read, + .f_start_write = &urio_start_write, + .f_stop_read = &urio_stop_read, + .f_stop_write = &urio_stop_write, + .basename[0] = "urio", +}; + +static const struct usb2_config urio_config[URIO_T_MAX] = { + [URIO_T_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = URIO_BSIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,.proxy_buffer = 1,}, + .mh.callback = &urio_write_callback, + }, + + [URIO_T_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = URIO_BSIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,}, + .mh.callback = &urio_read_callback, + }, + + [URIO_T_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &urio_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [URIO_T_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &urio_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static devclass_t urio_devclass; + +static device_method_t urio_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, urio_probe), + DEVMETHOD(device_attach, urio_attach), + DEVMETHOD(device_detach, urio_detach), + {0, 0} +}; + +static driver_t urio_driver = { + .name = "urio", + .methods = urio_methods, + .size = sizeof(struct urio_softc), +}; + +DRIVER_MODULE(urio, ushub, urio_driver, urio_devclass, NULL, 0); +MODULE_DEPEND(urio, usb2_storage, 1, 1, 1); +MODULE_DEPEND(urio, usb2_core, 1, 1, 1); + +static int +urio_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if ((((uaa->info.idVendor == USB_VENDOR_DIAMOND) && + (uaa->info.idProduct == USB_PRODUCT_DIAMOND_RIO500USB)) || + ((uaa->info.idVendor == USB_VENDOR_DIAMOND2) && + ((uaa->info.idProduct == USB_PRODUCT_DIAMOND2_RIO600USB) || + (uaa->info.idProduct == USB_PRODUCT_DIAMOND2_RIO800USB))))) + return (0); + else + return (ENXIO); +} + +static int +urio_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct urio_softc *sc = device_get_softc(dev); + int error; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + sc->sc_udev = uaa->device; + + mtx_init(&sc->sc_mtx, "urio lock", NULL, MTX_DEF | MTX_RECURSE); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + error = usb2_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, + urio_config, URIO_T_MAX, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("error=%s\n", usb2_errstr(error)); + goto detach; + } + /* set interface permissions */ + usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &urio_fifo_methods, &sc->sc_fifo, + device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + urio_detach(dev); + return (ENOMEM); /* failure */ +} + +static void +urio_write_callback(struct usb2_xfer *xfer) +{ + struct urio_softc *sc = xfer->priv_sc; + struct usb2_fifo *f = sc->sc_fifo.fp[USB_FIFO_TX]; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: + if (sc->sc_flags & URIO_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[URIO_T_WR_CS]); + return; + } + if (usb2_fifo_get_data(f, xfer->frbuffers, 0, + xfer->max_data_length, &actlen, 0)) { + + xfer->frlengths[0] = actlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= URIO_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[URIO_T_WR_CS]); + } + return; + } +} + +static void +urio_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct urio_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[URIO_T_WR]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~URIO_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +urio_read_callback(struct usb2_xfer *xfer) +{ + struct urio_softc *sc = xfer->priv_sc; + struct usb2_fifo *f = sc->sc_fifo.fp[USB_FIFO_RX]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb2_fifo_put_data(f, xfer->frbuffers, 0, + xfer->actlen, 1); + + case USB_ST_SETUP: + if (sc->sc_flags & URIO_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[URIO_T_RD_CS]); + return; + } + if (usb2_fifo_put_bytes_max(f) != 0) { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= URIO_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[URIO_T_RD_CS]); + } + return; + } +} + +static void +urio_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct urio_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[URIO_T_RD]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~URIO_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +urio_start_read(struct usb2_fifo *fifo) +{ + struct urio_softc *sc = fifo->priv_sc0; + + usb2_transfer_start(sc->sc_xfer[URIO_T_RD]); + return; +} + +static void +urio_stop_read(struct usb2_fifo *fifo) +{ + struct urio_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[URIO_T_RD_CS]); + usb2_transfer_stop(sc->sc_xfer[URIO_T_RD]); + return; +} + +static void +urio_start_write(struct usb2_fifo *fifo) +{ + struct urio_softc *sc = fifo->priv_sc0; + + usb2_transfer_start(sc->sc_xfer[URIO_T_WR]); + return; +} + +static void +urio_stop_write(struct usb2_fifo *fifo) +{ + struct urio_softc *sc = fifo->priv_sc0; + + usb2_transfer_stop(sc->sc_xfer[URIO_T_WR_CS]); + usb2_transfer_stop(sc->sc_xfer[URIO_T_WR]); + return; +} + +static int +urio_open(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + struct urio_softc *sc = fifo->priv_sc0; + + if ((fflags & (FWRITE | FREAD)) != (FWRITE | FREAD)) { + return (EACCES); + } + if (fflags & FREAD) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + sc->sc_flags |= URIO_FLAG_READ_STALL; + mtx_unlock(&sc->sc_mtx); + + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_xfer[URIO_T_RD]->max_data_length, + URIO_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + if (fflags & FWRITE) { + /* clear stall first */ + sc->sc_flags |= URIO_FLAG_WRITE_STALL; + + if (usb2_fifo_alloc_buffer(fifo, + sc->sc_xfer[URIO_T_WR]->max_data_length, + URIO_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + return (0); /* success */ +} + +static void +urio_close(struct usb2_fifo *fifo, int fflags, struct thread *td) +{ + if (fflags & (FREAD | FWRITE)) { + usb2_fifo_free_buffer(fifo); + } + return; +} + +static int +urio_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr, + int fflags, struct thread *td) +{ + struct usb2_ctl_request ur; + struct RioCommand *rio_cmd; + int error; + + switch (cmd) { + case RIO_RECV_COMMAND: + if (!(fflags & FWRITE)) { + error = EPERM; + goto done; + } + bzero(&ur, sizeof(ur)); + rio_cmd = addr; + ur.ucr_request.bmRequestType = + rio_cmd->requesttype | UT_READ_VENDOR_DEVICE; + break; + + case RIO_SEND_COMMAND: + if (!(fflags & FWRITE)) { + error = EPERM; + goto done; + } + bzero(&ur, sizeof(ur)); + rio_cmd = addr; + ur.ucr_request.bmRequestType = + rio_cmd->requesttype | UT_WRITE_VENDOR_DEVICE; + break; + + default: + error = EINVAL; + goto done; + } + + DPRINTFN(2, "Sending command\n"); + + /* Send rio control message */ + ur.ucr_request.bRequest = rio_cmd->request; + USETW(ur.ucr_request.wValue, rio_cmd->value); + USETW(ur.ucr_request.wIndex, rio_cmd->index); + USETW(ur.ucr_request.wLength, rio_cmd->length); + ur.ucr_data = rio_cmd->buffer; + + /* reuse generic USB code */ + error = ugen_do_request(fifo, &ur); + +done: + return (error); +} + +static int +urio_detach(device_t dev) +{ + struct urio_softc *sc = device_get_softc(dev); + + DPRINTF("\n"); + + usb2_fifo_detach(&sc->sc_fifo); + + usb2_transfer_unsetup(sc->sc_xfer, URIO_T_MAX); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} diff --git a/sys/dev/usb2/storage/usb2_storage.c b/sys/dev/usb2/storage/usb2_storage.c new file mode 100644 index 000000000000..c8233efb4581 --- /dev/null +++ b/sys/dev/usb2/storage/usb2_storage.c @@ -0,0 +1,31 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_storage, 1); +MODULE_DEPEND(usb2_storage, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/storage/usb2_storage.h b/sys/dev/usb2/storage/usb2_storage.h new file mode 100644 index 000000000000..f40828a9b3b2 --- /dev/null +++ b/sys/dev/usb2/storage/usb2_storage.h @@ -0,0 +1,30 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_STORAGE_H_ +#define _USB2_STORAGE_H_ + +#endif /* _USB2_STORAGE_H_ */ diff --git a/sys/dev/usb2/storage/ustorage2_fs.c b/sys/dev/usb2/storage/ustorage2_fs.c new file mode 100644 index 000000000000..b7977de228bb --- /dev/null +++ b/sys/dev/usb2/storage/ustorage2_fs.c @@ -0,0 +1,1906 @@ +/* $FreeBSD$ */ +/*- + * Copyright (C) 2003-2005 Alan Stern + * Copyright (C) 2008 Hans Petter Selasky + * 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, + * without modification. + * 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. The names of the above-listed copyright holders may not 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 OWNER 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. + */ + +/* + * NOTE: Much of the SCSI statemachine handling code derives from the + * Linux USB gadget stack. + */ +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR ustorage_fs_debug + +#include +#include +#include +#include +#include +#include + +#if USB_DEBUG +static int ustorage_fs_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ustorage_fs, CTLFLAG_RW, 0, "USB ustorage_fs"); +SYSCTL_INT(_hw_usb2_ustorage_fs, OID_AUTO, debug, CTLFLAG_RW, + &ustorage_fs_debug, 0, "ustorage_fs debug level"); +#endif + +/* Define some limits */ + +#define USTORAGE_FS_BULK_SIZE (1 << 17) +#define USTORAGE_FS_MAX_LUN 8 +#define USTORAGE_FS_RELEASE 0x0101 +#define USTORAGE_FS_RAM_SECT (1 << 13) + +static uint8_t *ustorage_fs_ramdisk; + +/* USB transfer definitions */ + +#define USTORAGE_FS_T_BBB_COMMAND 0 +#define USTORAGE_FS_T_BBB_DATA_DUMP 1 +#define USTORAGE_FS_T_BBB_DATA_READ 2 +#define USTORAGE_FS_T_BBB_DATA_WRITE 3 +#define USTORAGE_FS_T_BBB_STATUS 4 +#define USTORAGE_FS_T_BBB_MAX 5 + +/* USB data stage direction */ + +#define DIR_NONE 0 +#define DIR_READ 1 +#define DIR_WRITE 2 + +/* USB interface specific control request */ + +#define UR_BBB_RESET 0xff /* Bulk-Only reset */ +#define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */ + +/* Command Block Wrapper */ +typedef struct { + uDWord dCBWSignature; +#define CBWSIGNATURE 0x43425355 + uDWord dCBWTag; + uDWord dCBWDataTransferLength; + uByte bCBWFlags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + uByte bCBWLUN; + uByte bCDBLength; +#define CBWCDBLENGTH 16 + uByte CBWCDB[CBWCDBLENGTH]; +} __packed ustorage_fs_bbb_cbw_t; + +#define USTORAGE_FS_BBB_CBW_SIZE 31 + +/* Command Status Wrapper */ +typedef struct { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed ustorage_fs_bbb_csw_t; + +#define USTORAGE_FS_BBB_CSW_SIZE 13 + +struct ustorage_fs_lun { + + void *memory_image; + + uint32_t num_sectors; + uint32_t sense_data; + uint32_t sense_data_info; + uint32_t unit_attention_data; + + uint8_t read_only:1; + uint8_t prevent_medium_removal:1; + uint8_t info_valid:1; + uint8_t removable:1; +}; + +struct ustorage_fs_softc { + + ustorage_fs_bbb_cbw_t sc_cbw; /* Command Wrapper Block */ + ustorage_fs_bbb_csw_t sc_csw; /* Command Status Block */ + + struct mtx sc_mtx; + + struct ustorage_fs_lun sc_lun[USTORAGE_FS_MAX_LUN]; + + struct { + uint8_t *data_ptr; + struct ustorage_fs_lun *currlun; + + uint32_t data_rem; /* bytes, as reported by the command + * block wrapper */ + uint32_t offset; /* bytes */ + + uint8_t cbw_dir; + uint8_t cmd_dir; + uint8_t lun; + uint8_t cmd_data[CBWCDBLENGTH]; + uint8_t cmd_len; + uint8_t data_short:1; + uint8_t data_error:1; + } sc_transfer; + + device_t sc_dev; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[USTORAGE_FS_T_BBB_MAX]; + + uint32_t sc_unit; + + uint8_t sc_name[16]; + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_last_lun; + uint8_t sc_last_xfer_index; + uint8_t sc_qdata[1024]; +}; + +/* prototypes */ + +static device_probe_t ustorage_fs_probe; +static device_attach_t ustorage_fs_attach; +static device_detach_t ustorage_fs_detach; +static device_suspend_t ustorage_fs_suspend; +static device_resume_t ustorage_fs_resume; +static device_shutdown_t ustorage_fs_shutdown; +static usb2_handle_request_t ustorage_fs_handle_request; + +static usb2_callback_t ustorage_fs_t_bbb_command_callback; +static usb2_callback_t ustorage_fs_t_bbb_data_dump_callback; +static usb2_callback_t ustorage_fs_t_bbb_data_read_callback; +static usb2_callback_t ustorage_fs_t_bbb_data_write_callback; +static usb2_callback_t ustorage_fs_t_bbb_status_callback; + +static void ustorage_fs_transfer_start(struct ustorage_fs_softc *sc, uint8_t xfer_index); +static void ustorage_fs_transfer_stop(struct ustorage_fs_softc *sc); + +static uint8_t ustorage_fs_verify(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_inquiry(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_request_sense(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_read_capacity(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_mode_sense(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_start_stop(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_prevent_allow(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_read_format_capacities(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_mode_select(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_min_len(struct ustorage_fs_softc *sc, uint32_t len, uint32_t mask); +static uint8_t ustorage_fs_read(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_write(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_check_cmd(struct ustorage_fs_softc *sc, uint8_t cmd_size, uint16_t mask, uint8_t needs_medium); +static uint8_t ustorage_fs_do_cmd(struct ustorage_fs_softc *sc); + +static device_method_t ustorage_fs_methods[] = { + /* USB interface */ + DEVMETHOD(usb2_handle_request, ustorage_fs_handle_request), + + /* Device interface */ + DEVMETHOD(device_probe, ustorage_fs_probe), + DEVMETHOD(device_attach, ustorage_fs_attach), + DEVMETHOD(device_detach, ustorage_fs_detach), + DEVMETHOD(device_suspend, ustorage_fs_suspend), + DEVMETHOD(device_resume, ustorage_fs_resume), + DEVMETHOD(device_shutdown, ustorage_fs_shutdown), + + {0, 0} +}; + +static driver_t ustorage_fs_driver = { + .name = "ustorage_fs", + .methods = ustorage_fs_methods, + .size = sizeof(struct ustorage_fs_softc), +}; + +static devclass_t ustorage_fs_devclass; + +DRIVER_MODULE(ustorage_fs, ushub, ustorage_fs_driver, ustorage_fs_devclass, NULL, 0); +MODULE_VERSION(ustorage_fs, 0); +MODULE_DEPEND(ustorage_fs, usb2_storage, 1, 1, 1); +MODULE_DEPEND(ustorage_fs, usb2_core, 1, 1, 1); + +struct usb2_config ustorage_fs_bbb_config[USTORAGE_FS_T_BBB_MAX] = { + + [USTORAGE_FS_T_BBB_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .md.bufsize = sizeof(ustorage_fs_bbb_cbw_t), + .md.flags = {.ext_buffer = 1,}, + .md.callback = &ustorage_fs_t_bbb_command_callback, + }, + + [USTORAGE_FS_T_BBB_DATA_DUMP] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .md.bufsize = 0, /* use wMaxPacketSize */ + .md.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .md.callback = &ustorage_fs_t_bbb_data_dump_callback, + }, + + [USTORAGE_FS_T_BBB_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .md.bufsize = USTORAGE_FS_BULK_SIZE, + .md.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer = 1}, + .md.callback = &ustorage_fs_t_bbb_data_read_callback, + }, + + [USTORAGE_FS_T_BBB_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .md.bufsize = USTORAGE_FS_BULK_SIZE, + .md.flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer = 1}, + .md.callback = &ustorage_fs_t_bbb_data_write_callback, + }, + + [USTORAGE_FS_T_BBB_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .md.bufsize = sizeof(ustorage_fs_bbb_csw_t), + .md.flags = {.short_xfer_ok = 1,.ext_buffer = 1,}, + .md.callback = &ustorage_fs_t_bbb_status_callback, + }, +}; + +/* + * USB device probe/attach/detach + */ + +static int +ustorage_fs_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_interface_descriptor *id; + + if (uaa->usb2_mode != USB_MODE_DEVICE) { + return (ENXIO); + } + if (uaa->use_generic == 0) { + /* give other drivers a try first */ + return (ENXIO); + } + /* Check for a standards compliant device */ + id = usb2_get_interface_descriptor(uaa->iface); + if ((id == NULL) || + (id->bInterfaceClass != UICLASS_MASS) || + (id->bInterfaceSubClass != UISUBCLASS_SCSI) || + (id->bInterfaceProtocol != UIPROTO_MASS_BBB)) { + return (ENXIO); + } + return (0); +} + +static int +ustorage_fs_attach(device_t dev) +{ + struct ustorage_fs_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct usb2_interface_descriptor *id; + int err; + + if (sc == NULL) { + return (ENOMEM); + } + /* + * NOTE: the softc struct is bzero-ed in device_set_driver. + * We can safely call ustorage_fs_detach without specifically + * initializing the struct. + */ + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + sc->sc_unit = device_get_unit(dev); + + if (sc->sc_unit == 0) { + if (ustorage_fs_ramdisk == NULL) { + /* + * allocate a memory image for our ramdisk until + * further + */ + ustorage_fs_ramdisk = + malloc(USTORAGE_FS_RAM_SECT << 9, M_USB, M_ZERO | M_WAITOK); + if (ustorage_fs_ramdisk == NULL) { + return (ENOMEM); + } + } + sc->sc_lun[0].memory_image = ustorage_fs_ramdisk; + sc->sc_lun[0].num_sectors = USTORAGE_FS_RAM_SECT; + sc->sc_lun[0].removable = 1; + } + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "USTORAGE_FS lock", + NULL, (MTX_DEF | MTX_RECURSE)); + + /* get interface index */ + + id = usb2_get_interface_descriptor(uaa->iface); + if (id == NULL) { + device_printf(dev, "failed to get " + "interface number\n"); + goto detach; + } + sc->sc_iface_no = id->bInterfaceNumber; + + err = usb2_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, ustorage_fs_bbb_config, + USTORAGE_FS_T_BBB_MAX, sc, &sc->sc_mtx); + if (err) { + device_printf(dev, "could not setup required " + "transfers, %s\n", usb2_errstr(err)); + goto detach; + } + /* start Mass Storage State Machine */ + + mtx_lock(&sc->sc_mtx); + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND); + mtx_unlock(&sc->sc_mtx); + + return (0); /* success */ + +detach: + ustorage_fs_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ustorage_fs_detach(device_t dev) +{ + struct ustorage_fs_softc *sc = device_get_softc(dev); + + /* teardown our statemachine */ + + usb2_transfer_unsetup(sc->sc_xfer, USTORAGE_FS_T_BBB_MAX); + + mtx_destroy(&sc->sc_mtx); + + return (0); /* success */ +} + +static int +ustorage_fs_suspend(device_t dev) +{ + device_printf(dev, "suspending\n"); + return (0); /* success */ +} + +static int +ustorage_fs_resume(device_t dev) +{ + device_printf(dev, "resuming\n"); + return (0); /* success */ +} + +static int +ustorage_fs_shutdown(device_t dev) +{ + return (0); /* success */ +} + +/* + * Generic functions to handle transfers + */ + +static void +ustorage_fs_transfer_start(struct ustorage_fs_softc *sc, uint8_t xfer_index) +{ + if (sc->sc_xfer[xfer_index]) { + sc->sc_last_xfer_index = xfer_index; + usb2_transfer_start(sc->sc_xfer[xfer_index]); + } + return; +} + +static void +ustorage_fs_transfer_stop(struct ustorage_fs_softc *sc) +{ + usb2_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]); + mtx_unlock(&sc->sc_mtx); + usb2_transfer_drain(sc->sc_xfer[sc->sc_last_xfer_index]); + mtx_lock(&sc->sc_mtx); + return; +} + +static int +ustorage_fs_handle_request(device_t dev, + const void *preq, void **pptr, uint16_t *plen, + uint16_t offset, uint8_t is_complete) +{ + struct ustorage_fs_softc *sc = device_get_softc(dev); + const struct usb2_device_request *req = preq; + + if (!is_complete) { + if (req->bRequest == UR_BBB_RESET) { + *plen = 0; + mtx_lock(&sc->sc_mtx); + ustorage_fs_transfer_stop(sc); + sc->sc_transfer.data_error = 1; + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_COMMAND); + mtx_unlock(&sc->sc_mtx); + return (0); + } else if (req->bRequest == UR_BBB_GET_MAX_LUN) { + if (offset == 0) { + *plen = 1; + *pptr = &sc->sc_last_lun; + } else { + *plen = 0; + } + return (0); + } + } + return (ENXIO); /* use builtin handler */ +} + +static void +ustorage_fs_t_bbb_command_callback(struct usb2_xfer *xfer) +{ + struct ustorage_fs_softc *sc = xfer->priv_sc; + uint32_t tag; + uint8_t error = 0; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + tag = UGETDW(sc->sc_cbw.dCBWSignature); + + if (tag != CBWSIGNATURE) { + /* do nothing */ + DPRINTF("invalid signature 0x%08x\n", tag); + break; + } + tag = UGETDW(sc->sc_cbw.dCBWTag); + + /* echo back tag */ + USETDW(sc->sc_csw.dCSWTag, tag); + + /* reset status */ + sc->sc_csw.bCSWStatus = 0; + + /* reset data offset, data length and data remainder */ + sc->sc_transfer.offset = 0; + sc->sc_transfer.data_rem = + UGETDW(sc->sc_cbw.dCBWDataTransferLength); + + /* reset data flags */ + sc->sc_transfer.data_short = 0; + + /* extract LUN */ + sc->sc_transfer.lun = sc->sc_cbw.bCBWLUN; + + if (sc->sc_transfer.data_rem == 0) { + sc->sc_transfer.cbw_dir = DIR_NONE; + } else { + if (sc->sc_cbw.bCBWFlags & CBWFLAGS_IN) { + sc->sc_transfer.cbw_dir = DIR_WRITE; + } else { + sc->sc_transfer.cbw_dir = DIR_READ; + } + } + + sc->sc_transfer.cmd_len = sc->sc_cbw.bCDBLength; + if ((sc->sc_transfer.cmd_len > sizeof(sc->sc_cbw.CBWCDB)) || + (sc->sc_transfer.cmd_len == 0)) { + /* just halt - this is invalid */ + DPRINTF("invalid command length %d bytes\n", + sc->sc_transfer.cmd_len); + break; + } + bcopy(sc->sc_cbw.CBWCDB, sc->sc_transfer.cmd_data, + sc->sc_transfer.cmd_len); + + bzero(sc->sc_cbw.CBWCDB + sc->sc_transfer.cmd_len, + sizeof(sc->sc_cbw.CBWCDB) - sc->sc_transfer.cmd_len); + + error = ustorage_fs_do_cmd(sc); + if (error) { + /* got an error */ + DPRINTF("command failed\n"); + break; + } + if ((sc->sc_transfer.data_rem > 0) && + (sc->sc_transfer.cbw_dir != sc->sc_transfer.cmd_dir)) { + /* contradicting data transfer direction */ + error = 1; + DPRINTF("data direction mismatch\n"); + break; + } + switch (sc->sc_transfer.cbw_dir) { + case DIR_READ: + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_READ); + break; + case DIR_WRITE: + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_WRITE); + break; + default: + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_STATUS); + break; + } + break; + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + xfer->flags.stall_pipe = 1; + DPRINTF("stall pipe\n"); + } else { + xfer->flags.stall_pipe = 0; + } + + xfer->frlengths[0] = sizeof(sc->sc_cbw); + usb2_set_frame_data(xfer, &sc->sc_cbw, 0); + usb2_start_hardware(xfer); + break; + + default: /* Error */ + DPRINTF("error\n"); + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + /* If the pipe is already stalled, don't do another stall */ + if (!xfer->pipe->is_stalled) { + sc->sc_transfer.data_error = 1; + } + /* try again */ + goto tr_setup; + } + if (error) { + if (sc->sc_csw.bCSWStatus == 0) { + /* set some default error code */ + sc->sc_csw.bCSWStatus = CSWSTATUS_FAILED; + } + if (sc->sc_transfer.cbw_dir == DIR_READ) { + /* dump all data */ + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_DATA_DUMP); + return; + } + if (sc->sc_transfer.cbw_dir == DIR_WRITE) { + /* need to stall before status */ + sc->sc_transfer.data_error = 1; + } + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_STATUS); + } + return; +} + +static void +ustorage_fs_t_bbb_data_dump_callback(struct usb2_xfer *xfer) +{ + struct ustorage_fs_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= xfer->actlen; + sc->sc_transfer.offset += xfer->actlen; + + if ((xfer->actlen != xfer->sumlen) || + (sc->sc_transfer.data_rem == 0)) { + /* short transfer or end of data */ + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_STATUS); + break; + } + /* Fallthrough */ + + case USB_ST_SETUP: +tr_setup: + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + xfer->flags.stall_pipe = 1; + } else { + xfer->flags.stall_pipe = 0; + } + xfer->frlengths[0] = max_bulk; + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + /* + * If the pipe is already stalled, don't do another stall: + */ + if (!xfer->pipe->is_stalled) { + sc->sc_transfer.data_error = 1; + } + /* try again */ + goto tr_setup; + } + return; +} + +static void +ustorage_fs_t_bbb_data_read_callback(struct usb2_xfer *xfer) +{ + struct ustorage_fs_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= xfer->actlen; + sc->sc_transfer.data_ptr += xfer->actlen; + sc->sc_transfer.offset += xfer->actlen; + + if ((xfer->actlen != xfer->sumlen) || + (sc->sc_transfer.data_rem == 0)) { + /* short transfer or end of data */ + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_STATUS); + break; + } + /* Fallthrough */ + + case USB_ST_SETUP: +tr_setup: + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + xfer->flags.stall_pipe = 1; + } else { + xfer->flags.stall_pipe = 0; + } + + xfer->frlengths[0] = max_bulk; + usb2_set_frame_data(xfer, sc->sc_transfer.data_ptr, 0); + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + /* If the pipe is already stalled, don't do another stall */ + if (!xfer->pipe->is_stalled) { + sc->sc_transfer.data_error = 1; + } + /* try again */ + goto tr_setup; + } + return; +} + +static void +ustorage_fs_t_bbb_data_write_callback(struct usb2_xfer *xfer) +{ + struct ustorage_fs_softc *sc = xfer->priv_sc; + uint32_t max_bulk = xfer->max_data_length; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= xfer->actlen; + sc->sc_transfer.data_ptr += xfer->actlen; + sc->sc_transfer.offset += xfer->actlen; + + if ((xfer->actlen != xfer->sumlen) || + (sc->sc_transfer.data_rem == 0)) { + /* short transfer or end of data */ + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_STATUS); + break; + } + case USB_ST_SETUP: +tr_setup: + if (max_bulk >= sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + if (sc->sc_transfer.data_short) { + xfer->flags.force_short_xfer = 1; + } else { + xfer->flags.force_short_xfer = 0; + } + } else { + xfer->flags.force_short_xfer = 0; + } + + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + xfer->flags.stall_pipe = 1; + } else { + xfer->flags.stall_pipe = 0; + } + + xfer->frlengths[0] = max_bulk; + usb2_set_frame_data(xfer, sc->sc_transfer.data_ptr, 0); + usb2_start_hardware(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + /* + * If the pipe is already stalled, don't do another + * stall + */ + if (!xfer->pipe->is_stalled) { + sc->sc_transfer.data_error = 1; + } + /* try again */ + goto tr_setup; + } + return; +} + +static void +ustorage_fs_t_bbb_status_callback(struct usb2_xfer *xfer) +{ + struct ustorage_fs_softc *sc = xfer->priv_sc; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND); + break; + + case USB_ST_SETUP: +tr_setup: + USETDW(sc->sc_csw.dCSWSignature, CSWSIGNATURE); + USETDW(sc->sc_csw.dCSWDataResidue, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + xfer->flags.stall_pipe = 1; + } else { + xfer->flags.stall_pipe = 0; + } + + xfer->frlengths[0] = sizeof(sc->sc_csw); + usb2_set_frame_data(xfer, &sc->sc_csw, 0); + usb2_start_hardware(xfer); + break; + + default: + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + /* If the pipe is already stalled, don't do another stall */ + if (!xfer->pipe->is_stalled) { + sc->sc_transfer.data_error = 1; + } + /* try again */ + goto tr_setup; + } + return; +} + +/* SCSI commands that we recognize */ +#define SC_FORMAT_UNIT 0x04 +#define SC_INQUIRY 0x12 +#define SC_MODE_SELECT_6 0x15 +#define SC_MODE_SELECT_10 0x55 +#define SC_MODE_SENSE_6 0x1a +#define SC_MODE_SENSE_10 0x5a +#define SC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e +#define SC_READ_6 0x08 +#define SC_READ_10 0x28 +#define SC_READ_12 0xa8 +#define SC_READ_CAPACITY 0x25 +#define SC_READ_FORMAT_CAPACITIES 0x23 +#define SC_RELEASE 0x17 +#define SC_REQUEST_SENSE 0x03 +#define SC_RESERVE 0x16 +#define SC_SEND_DIAGNOSTIC 0x1d +#define SC_START_STOP_UNIT 0x1b +#define SC_SYNCHRONIZE_CACHE 0x35 +#define SC_TEST_UNIT_READY 0x00 +#define SC_VERIFY 0x2f +#define SC_WRITE_6 0x0a +#define SC_WRITE_10 0x2a +#define SC_WRITE_12 0xaa + +/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ +#define SS_NO_SENSE 0 +#define SS_COMMUNICATION_FAILURE 0x040800 +#define SS_INVALID_COMMAND 0x052000 +#define SS_INVALID_FIELD_IN_CDB 0x052400 +#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 +#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 +#define SS_MEDIUM_NOT_PRESENT 0x023a00 +#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 +#define SS_NOT_READY_TO_READY_TRANSITION 0x062800 +#define SS_RESET_OCCURRED 0x062900 +#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 +#define SS_UNRECOVERED_READ_ERROR 0x031100 +#define SS_WRITE_ERROR 0x030c02 +#define SS_WRITE_PROTECTED 0x072700 + +#define SK(x) ((uint8_t) ((x) >> 16)) /* Sense Key byte, etc. */ +#define ASC(x) ((uint8_t) ((x) >> 8)) +#define ASCQ(x) ((uint8_t) (x)) + +/* Routines for unaligned data access */ + +static uint16_t +get_be16(uint8_t *buf) +{ + return ((uint16_t)buf[0] << 8) | ((uint16_t)buf[1]); +} + +static uint32_t +get_be32(uint8_t *buf) +{ + return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | + ((uint32_t)buf[2] << 8) | ((uint32_t)buf[3]); +} + +static void +put_be16(uint8_t *buf, uint16_t val) +{ + buf[0] = val >> 8; + buf[1] = val; +} + +static void +put_be32(uint8_t *buf, uint32_t val) +{ + buf[0] = val >> 24; + buf[1] = val >> 16; + buf[2] = val >> 8; + buf[3] = val & 0xff; +} + +/*------------------------------------------------------------------------* + * ustorage_fs_verify + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_verify(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint32_t lba; + uint32_t vlen; + uint64_t file_offset; + uint64_t amount_left; + + /* + * Get the starting Logical Block Address + */ + lba = get_be32(&sc->sc_transfer.cmd_data[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the cache) + * but we don't implement it. + */ + if ((sc->sc_transfer.cmd_data[1] & ~0x10) != 0) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + vlen = get_be16(&sc->sc_transfer.cmd_data[7]); + if (vlen == 0) { + goto done; + } + /* No default reply */ + + /* Prepare to carry out the file verify */ + amount_left = vlen; + amount_left <<= 9; + file_offset = lba; + file_offset <<= 9; + + /* Range check */ + vlen += lba; + + if ((vlen < lba) || + (vlen > currlun->num_sectors) || + (lba >= currlun->num_sectors)) { + currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return (1); + } + /* XXX TODO: verify that data is readable */ +done: + return (ustorage_fs_min_len(sc, 0, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_inquiry + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_inquiry(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + static const char vendor_id[] = "FreeBSD "; + static const char product_id[] = "File-Stor Gadget"; + + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + + if (!sc->sc_transfer.currlun) { + /* Unsupported LUNs are okay */ + memset(buf, 0, 36); + buf[0] = 0x7f; + /* Unsupported, no device - type */ + return (ustorage_fs_min_len(sc, 36, 0 - 1)); + } + memset(buf, 0, 8); + /* Non - removable, direct - access device */ + if (currlun->removable) + buf[1] = 0x80; + buf[2] = 2; + /* ANSI SCSI level 2 */ + buf[3] = 2; + /* SCSI - 2 INQUIRY data format */ + buf[4] = 31; + /* Additional length */ + /* No special options */ + /* + * NOTE: We are writing an extra zero here, that is not + * transferred to the peer: + */ + snprintf(buf + 8, 28 + 1, "%-8s%-16s%04x", vendor_id, product_id, + USTORAGE_FS_RELEASE); + return (ustorage_fs_min_len(sc, 36, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_request_sense + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_request_sense(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint32_t sd; + uint32_t sdinfo; + uint8_t valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (currlun && currlun->unit_attention_data != SS_NO_SENSE) { + currlun->sense_data = currlun->unit_attention_data; + currlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!currlun) { + /* Unsupported LUNs are okay */ + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + sdinfo = 0; + valid = 0; + } else { + sd = currlun->sense_data; + sdinfo = currlun->sense_data_info; + valid = currlun->info_valid << 7; + currlun->sense_data = SS_NO_SENSE; + currlun->sense_data_info = 0; + currlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; + /* Valid, current error */ + buf[2] = SK(sd); + put_be32(&buf[3], sdinfo); + /* Sense information */ + buf[7] = 18 - 8; + /* Additional sense length */ + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + return (ustorage_fs_min_len(sc, 18, 0 - 1)); +} + + +/*------------------------------------------------------------------------* + * ustorage_fs_read_capacity + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_read_capacity(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint32_t lba = get_be32(&sc->sc_transfer.cmd_data[2]); + uint8_t pmi = sc->sc_transfer.cmd_data[8]; + + /* Check the PMI and LBA fields */ + if ((pmi > 1) || ((pmi == 0) && (lba != 0))) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + put_be32(&buf[0], currlun->num_sectors - 1); + /* Max logical block */ + put_be32(&buf[4], 512); + /* Block length */ + return (ustorage_fs_min_len(sc, 8, 0 - 1)); +} + + +/*------------------------------------------------------------------------* + * ustorage_fs_mode_sense + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_mode_sense(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint8_t *buf0; + uint16_t len; + uint16_t limit; + uint8_t mscmnd = sc->sc_transfer.cmd_data[0]; + uint8_t pc; + uint8_t page_code; + uint8_t changeable_values; + uint8_t all_pages; + + buf0 = buf; + + if ((sc->sc_transfer.cmd_data[1] & ~0x08) != 0) { + /* Mask away DBD */ + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + pc = sc->sc_transfer.cmd_data[2] >> 6; + page_code = sc->sc_transfer.cmd_data[2] & 0x3f; + if (pc == 3) { + currlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return (1); + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* + * Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. + */ + memset(buf, 0, 8); + if (mscmnd == SC_MODE_SENSE_6) { + buf[2] = (currlun->read_only ? 0x80 : 0x00); + /* WP, DPOFUA */ + buf += 4; + limit = 255; + } else { + /* SC_MODE_SENSE_10 */ + buf[3] = (currlun->read_only ? 0x80 : 0x00); + /* WP, DPOFUA */ + buf += 8; + limit = 65535; + /* Should really be mod_data.buflen */ + } + + /* No block descriptors */ + + /* + * The mode pages, in numerical order. + */ + if ((page_code == 0x08) || all_pages) { + buf[0] = 0x08; + /* Page code */ + buf[1] = 10; + /* Page length */ + memset(buf + 2, 0, 10); + /* None of the fields are changeable */ + + if (!changeable_values) { + buf[2] = 0x04; + /* Write cache enable, */ + /* Read cache not disabled */ + /* No cache retention priorities */ + put_be16(&buf[4], 0xffff); + /* Don 't disable prefetch */ + /* Minimum prefetch = 0 */ + put_be16(&buf[8], 0xffff); + /* Maximum prefetch */ + put_be16(&buf[10], 0xffff); + /* Maximum prefetch ceiling */ + } + buf += 12; + } + /* + * Check that a valid page was requested and the mode data length + * isn't too long. + */ + len = buf - buf0; + if (len > limit) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + /* Store the mode data length */ + if (mscmnd == SC_MODE_SENSE_6) + buf0[0] = len - 1; + else + put_be16(buf0, len - 2); + return (ustorage_fs_min_len(sc, len, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_start_stop + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_start_stop(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint8_t loej; + uint8_t start; + uint8_t immed; + + if (!currlun->removable) { + currlun->sense_data = SS_INVALID_COMMAND; + return (1); + } + immed = sc->sc_transfer.cmd_data[1] & 0x01; + loej = sc->sc_transfer.cmd_data[4] & 0x02; + start = sc->sc_transfer.cmd_data[4] & 0x01; + + if (immed || loej || start) { + /* compile fix */ + } + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_prevent_allow + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_prevent_allow(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint8_t prevent; + + if (!currlun->removable) { + currlun->sense_data = SS_INVALID_COMMAND; + return (1); + } + prevent = sc->sc_transfer.cmd_data[4] & 0x01; + if ((sc->sc_transfer.cmd_data[4] & ~0x01) != 0) { + /* Mask away Prevent */ + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + if (currlun->prevent_medium_removal && !prevent) { + //fsync_sub(currlun); + } + currlun->prevent_medium_removal = prevent; + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_read_format_capacities + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_read_format_capacities(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; + /* Only the Current / Maximum Capacity Descriptor */ + buf += 4; + + put_be32(&buf[0], currlun->num_sectors); + /* Number of blocks */ + put_be32(&buf[4], 512); + /* Block length */ + buf[4] = 0x02; + /* Current capacity */ + return (ustorage_fs_min_len(sc, 12, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_mode_select + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_mode_select(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + + /* We don't support MODE SELECT */ + currlun->sense_data = SS_INVALID_COMMAND; + return (1); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_synchronize_cache + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_synchronize_cache(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint8_t rc; + + /* + * We ignore the requested LBA and write out all dirty data buffers. + */ + rc = 0; + if (rc) { + currlun->sense_data = SS_WRITE_ERROR; + } + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_read - read data from disk + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_read(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint64_t file_offset; + uint32_t lba; + uint32_t len; + + /* + * Get the starting Logical Block Address and check that it's not + * too big + */ + if (sc->sc_transfer.cmd_data[0] == SC_READ_6) { + lba = (sc->sc_transfer.cmd_data[1] << 16) | + get_be16(&sc->sc_transfer.cmd_data[2]); + } else { + lba = get_be32(&sc->sc_transfer.cmd_data[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. + */ + if ((sc->sc_transfer.cmd_data[1] & ~0x18) != 0) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + } + len = sc->sc_transfer.data_rem >> 9; + len += lba; + + if ((len < lba) || + (len > currlun->num_sectors) || + (lba >= currlun->num_sectors)) { + currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return (1); + } + file_offset = lba; + file_offset <<= 9; + + sc->sc_transfer.data_ptr = + USB_ADD_BYTES(currlun->memory_image, (uint32_t)file_offset); + + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_write - write data to disk + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_write(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint64_t file_offset; + uint32_t lba; + uint32_t len; + + if (currlun->read_only) { + currlun->sense_data = SS_WRITE_PROTECTED; + return (1); + } + /* XXX clear SYNC */ + + /* + * Get the starting Logical Block Address and check that it's not + * too big. + */ + if (sc->sc_transfer.cmd_data[0] == SC_WRITE_6) + lba = (sc->sc_transfer.cmd_data[1] << 16) | + get_be16(&sc->sc_transfer.cmd_data[2]); + else { + lba = get_be32(&sc->sc_transfer.cmd_data[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. + */ + if ((sc->sc_transfer.cmd_data[1] & ~0x18) != 0) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + if (sc->sc_transfer.cmd_data[1] & 0x08) { + /* FUA */ + /* XXX set SYNC flag here */ + } + } + + len = sc->sc_transfer.data_rem >> 9; + len += lba; + + if ((len < lba) || + (len > currlun->num_sectors) || + (lba >= currlun->num_sectors)) { + currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return (1); + } + file_offset = lba; + file_offset <<= 9; + + sc->sc_transfer.data_ptr = + USB_ADD_BYTES(currlun->memory_image, (uint32_t)file_offset); + + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_min_len + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_min_len(struct ustorage_fs_softc *sc, uint32_t len, uint32_t mask) +{ + if (len != sc->sc_transfer.data_rem) { + + if (sc->sc_transfer.cbw_dir == DIR_READ) { + /* + * there must be something wrong about this SCSI + * command + */ + sc->sc_csw.bCSWStatus = CSWSTATUS_PHASE; + return (1); + } + /* compute the minimum length */ + + if (sc->sc_transfer.data_rem > len) { + /* data ends prematurely */ + sc->sc_transfer.data_rem = len; + sc->sc_transfer.data_short = 1; + } + /* check length alignment */ + + if (sc->sc_transfer.data_rem & ~mask) { + /* data ends prematurely */ + sc->sc_transfer.data_rem &= mask; + sc->sc_transfer.data_short = 1; + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_check_cmd - check command routine + * + * Check whether the command is properly formed and whether its data + * size and direction agree with the values we already have. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_check_cmd(struct ustorage_fs_softc *sc, uint8_t min_cmd_size, + uint16_t mask, uint8_t needs_medium) +{ + struct ustorage_fs_lun *currlun; + uint8_t lun = (sc->sc_transfer.cmd_data[1] >> 5); + uint8_t i; + + /* Verify the length of the command itself */ + if (min_cmd_size > sc->sc_transfer.cmd_len) { + DPRINTF("%u > %u\n", + min_cmd_size, sc->sc_transfer.cmd_len); + sc->sc_csw.bCSWStatus = CSWSTATUS_PHASE; + return (1); + } + /* Mask away the LUN */ + sc->sc_transfer.cmd_data[1] &= 0x1f; + + /* Check if LUN is correct */ + if (lun != sc->sc_transfer.lun) { + + } + /* Check the LUN */ + if (sc->sc_transfer.lun <= sc->sc_last_lun) { + sc->sc_transfer.currlun = currlun = + sc->sc_lun + sc->sc_transfer.lun; + if (sc->sc_transfer.cmd_data[0] != SC_REQUEST_SENSE) { + currlun->sense_data = SS_NO_SENSE; + currlun->sense_data_info = 0; + currlun->info_valid = 0; + } + /* + * If a unit attention condition exists, only INQUIRY + * and REQUEST SENSE commands are allowed. Anything + * else must fail! + */ + if ((currlun->unit_attention_data != SS_NO_SENSE) && + (sc->sc_transfer.cmd_data[0] != SC_INQUIRY) && + (sc->sc_transfer.cmd_data[0] != SC_REQUEST_SENSE)) { + currlun->sense_data = currlun->unit_attention_data; + currlun->unit_attention_data = SS_NO_SENSE; + return (1); + } + } else { + sc->sc_transfer.currlun = currlun = NULL; + + /* + * INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. + */ + if ((sc->sc_transfer.cmd_data[0] != SC_INQUIRY) && + (sc->sc_transfer.cmd_data[0] != SC_REQUEST_SENSE)) { + return (1); + } + } + + /* + * Check that only command bytes listed in the mask are + * non-zero. + */ + for (i = 0; i != min_cmd_size; i++) { + if (sc->sc_transfer.cmd_data[i] && !(mask & (1 << i))) { + if (currlun) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + } + return (1); + } + } + + /* + * If the medium isn't mounted and the command needs to access + * it, return an error. + */ + if (currlun && (!currlun->memory_image) && needs_medium) { + currlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return (1); + } + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_do_cmd - do command + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_do_cmd(struct ustorage_fs_softc *sc) +{ + uint8_t error = 1; + uint8_t i; + + /* set default data transfer pointer */ + sc->sc_transfer.data_ptr = sc->sc_qdata; + + DPRINTF("cmd_data[0]=0x%02x, data_rem=0x%08x\n", + sc->sc_transfer.cmd_data[0], sc->sc_transfer.data_rem); + + switch (sc->sc_transfer.cmd_data[0]) { + case SC_INQUIRY: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, sc->sc_transfer.cmd_data[4], 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1 << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_inquiry(sc); + + break; + + case SC_MODE_SELECT_6: + sc->sc_transfer.cmd_dir = DIR_READ; + error = ustorage_fs_min_len(sc, sc->sc_transfer.cmd_data[4], 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1 << 1) | (1 << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_mode_select(sc); + + break; + + case SC_MODE_SELECT_10: + sc->sc_transfer.cmd_dir = DIR_READ; + error = ustorage_fs_min_len(sc, + get_be16(&sc->sc_transfer.cmd_data[7]), 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1 << 1) | (3 << 7) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_mode_select(sc); + + break; + + case SC_MODE_SENSE_6: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, sc->sc_transfer.cmd_data[4], 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1 << 1) | (1 << 2) | (1 << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_mode_sense(sc); + + break; + + case SC_MODE_SENSE_10: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, + get_be16(&sc->sc_transfer.cmd_data[7]), 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1 << 1) | (1 << 2) | (3 << 7) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_mode_sense(sc); + + break; + + case SC_PREVENT_ALLOW_MEDIUM_REMOVAL: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1 << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_prevent_allow(sc); + + break; + + case SC_READ_6: + i = sc->sc_transfer.cmd_data[4]; + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, + ((i == 0) ? 256 : i) << 9, 0 - (1 << 9)); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (7 << 1) | (1 << 4) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read(sc); + + break; + + case SC_READ_10: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, + get_be16(&sc->sc_transfer.cmd_data[7]) << 9, 0 - (1 << 9)); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1 << 1) | (0xf << 2) | (3 << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read(sc); + + break; + + case SC_READ_12: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, + get_be32(&sc->sc_transfer.cmd_data[6]) << 9, 0 - (1 << 9)); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 12, + (1 << 1) | (0xf << 2) | (0xf << 6) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read(sc); + + break; + + case SC_READ_CAPACITY: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_check_cmd(sc, 10, + (0xf << 2) | (1 << 8) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read_capacity(sc); + + break; + + case SC_READ_FORMAT_CAPACITIES: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, + get_be16(&sc->sc_transfer.cmd_data[7]), 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (3 << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read_format_capacities(sc); + + break; + + case SC_REQUEST_SENSE: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, sc->sc_transfer.cmd_data[4], 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1 << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_request_sense(sc); + + break; + + case SC_START_STOP_UNIT: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1 << 1) | (1 << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_start_stop(sc); + + break; + + case SC_SYNCHRONIZE_CACHE: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (0xf << 2) | (3 << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_synchronize_cache(sc); + + break; + + case SC_TEST_UNIT_READY: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + 0 | 1, 1); + break; + + /* + * Although optional, this command is used by MS-Windows. + * We support a minimal version: BytChk must be 0. + */ + case SC_VERIFY: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1 << 1) | (0xf << 2) | (3 << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_verify(sc); + + break; + + case SC_WRITE_6: + i = sc->sc_transfer.cmd_data[4]; + sc->sc_transfer.cmd_dir = DIR_READ; + error = ustorage_fs_min_len(sc, + ((i == 0) ? 256 : i) << 9, 0 - (1 << 9)); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (7 << 1) | (1 << 4) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_write(sc); + + break; + + case SC_WRITE_10: + sc->sc_transfer.cmd_dir = DIR_READ; + error = ustorage_fs_min_len(sc, + get_be16(&sc->sc_transfer.cmd_data[7]) << 9, 0 - (1 << 9)); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1 << 1) | (0xf << 2) | (3 << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_write(sc); + + break; + + case SC_WRITE_12: + sc->sc_transfer.cmd_dir = DIR_READ; + error = ustorage_fs_min_len(sc, + get_be32(&sc->sc_transfer.cmd_data[6]) << 9, 0 - (1 << 9)); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 12, + (1 << 1) | (0xf << 2) | (0xf << 6) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_write(sc); + + break; + + /* + * Some mandatory commands that we recognize but don't + * implement. They don't mean much in this setting. + * It's left as an exercise for anyone interested to + * implement RESERVE and RELEASE in terms of Posix + * locks. + */ + case SC_FORMAT_UNIT: + case SC_RELEASE: + case SC_RESERVE: + case SC_SEND_DIAGNOSTIC: + /* Fallthrough */ + + default: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, sc->sc_transfer.cmd_len, + 0xff, 0); + if (error) { + break; + } + sc->sc_transfer.currlun->sense_data = + SS_INVALID_COMMAND; + error = 1; + + break; + } + return (error); +} diff --git a/sys/dev/usb2/template/usb2_template.c b/sys/dev/usb2/template/usb2_template.c new file mode 100644 index 000000000000..20c866277db9 --- /dev/null +++ b/sys/dev/usb2/template/usb2_template.c @@ -0,0 +1,1306 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Hans Petter Selasky. 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. + */ + +/* + * This file contains sub-routines to build up USB descriptors from + * USB templates. + */ + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb2_debug + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +MODULE_DEPEND(usb2_template, usb2_core, 1, 1, 1); +MODULE_VERSION(usb2_template, 1); + +/* function prototypes */ + +static void usb2_make_raw_desc(struct usb2_temp_setup *temp, const uint8_t *raw); +static void usb2_make_endpoint_desc(struct usb2_temp_setup *temp, const struct usb2_temp_endpoint_desc *ted); +static void usb2_make_interface_desc(struct usb2_temp_setup *temp, const struct usb2_temp_interface_desc *tid); +static void usb2_make_config_desc(struct usb2_temp_setup *temp, const struct usb2_temp_config_desc *tcd); +static void usb2_make_device_desc(struct usb2_temp_setup *temp, const struct usb2_temp_device_desc *tdd); +static uint8_t usb2_hw_ep_match(const struct usb2_hw_ep_profile *pf, uint8_t ep_type, uint8_t ep_dir_in); +static uint8_t usb2_hw_ep_find_match(struct usb2_hw_ep_scratch *ues, struct usb2_hw_ep_scratch_sub *ep, uint8_t is_simplex); +static uint8_t usb2_hw_ep_get_needs(struct usb2_hw_ep_scratch *ues, uint8_t ep_type, uint8_t is_complete); +static usb2_error_t usb2_hw_ep_resolve(struct usb2_device *udev, struct usb2_descriptor *desc); +static const struct usb2_temp_device_desc *usb2_temp_get_tdd(struct usb2_device *udev); +static void *usb2_temp_get_device_desc(struct usb2_device *udev); +static void *usb2_temp_get_qualifier_desc(struct usb2_device *udev); +static void *usb2_temp_get_config_desc(struct usb2_device *udev, uint16_t *pLength, uint8_t index); +static const void *usb2_temp_get_string_desc(struct usb2_device *udev, uint16_t lang_id, uint8_t string_index); +static const void *usb2_temp_get_vendor_desc(struct usb2_device *udev, const struct usb2_device_request *req); +static const void *usb2_temp_get_hub_desc(struct usb2_device *udev); +static void usb2_temp_get_desc(struct usb2_device *udev, struct usb2_device_request *req, const void **pPtr, uint16_t *pLength); +static usb2_error_t usb2_temp_setup(struct usb2_device *udev, const struct usb2_temp_device_desc *tdd); +static void usb2_temp_unsetup(struct usb2_device *udev); +static usb2_error_t usb2_temp_setup_by_index(struct usb2_device *udev, uint16_t index); +static void usb2_temp_init(void *arg); + +/*------------------------------------------------------------------------* + * usb2_make_raw_desc + * + * This function will insert a raw USB descriptor into the generated + * USB configuration. + *------------------------------------------------------------------------*/ +static void +usb2_make_raw_desc(struct usb2_temp_setup *temp, + const uint8_t *raw) +{ + void *dst; + uint8_t len; + + /* + * The first byte of any USB descriptor gives the length. + */ + if (raw) { + len = raw[0]; + if (temp->buf) { + dst = USB_ADD_BYTES(temp->buf, temp->size); + bcopy(raw, dst, len); + + /* check if we have got a CDC union descriptor */ + + if ((raw[0] >= sizeof(struct usb2_cdc_union_descriptor)) && + (raw[1] == UDESC_CS_INTERFACE) && + (raw[2] == UDESCSUB_CDC_UNION)) { + struct usb2_cdc_union_descriptor *ud = (void *)dst; + + /* update the interface numbers */ + + ud->bMasterInterface += + temp->bInterfaceNumber; + ud->bSlaveInterface[0] += + temp->bInterfaceNumber; + } + } + temp->size += len; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_make_endpoint_desc + * + * This function will generate an USB endpoint descriptor from the + * given USB template endpoint descriptor, which will be inserted into + * the USB configuration. + *------------------------------------------------------------------------*/ +static void +usb2_make_endpoint_desc(struct usb2_temp_setup *temp, + const struct usb2_temp_endpoint_desc *ted) +{ + struct usb2_endpoint_descriptor *ed; + const void **rd; + uint16_t old_size; + uint16_t mps; + uint8_t ea = 0; /* Endpoint Address */ + uint8_t et = 0; /* Endpiont Type */ + + /* Reserve memory */ + old_size = temp->size; + temp->size += sizeof(*ed); + + /* Scan all Raw Descriptors first */ + + rd = ted->ppRawDesc; + if (rd) { + while (*rd) { + usb2_make_raw_desc(temp, *rd); + rd++; + } + } + if (ted->pPacketSize == NULL) { + /* not initialized */ + temp->err = USB_ERR_INVAL; + return; + } + mps = ted->pPacketSize->mps[temp->usb2_speed]; + if (mps == 0) { + /* not initialized */ + temp->err = USB_ERR_INVAL; + return; + } else if (mps == UE_ZERO_MPS) { + /* escape for Zero Max Packet Size */ + mps = 0; + } + ea = (ted->bEndpointAddress & (UE_ADDR | UE_DIR_IN | UE_DIR_OUT)); + et = (ted->bmAttributes & UE_XFERTYPE); + + /* + * Fill out the real USB endpoint descriptor + * in case there is a buffer present: + */ + if (temp->buf) { + ed = USB_ADD_BYTES(temp->buf, old_size); + ed->bLength = sizeof(*ed); + ed->bDescriptorType = UDESC_ENDPOINT; + ed->bEndpointAddress = ea; + ed->bmAttributes = ted->bmAttributes; + USETW(ed->wMaxPacketSize, mps); + + /* setup bInterval parameter */ + + if (ted->pIntervals && + ted->pIntervals->bInterval[temp->usb2_speed]) { + ed->bInterval = + ted->pIntervals->bInterval[temp->usb2_speed]; + } else { + switch (et) { + case UE_BULK: + case UE_CONTROL: + ed->bInterval = 0; /* not used */ + break; + case UE_INTERRUPT: + switch (temp->usb2_speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + ed->bInterval = 1; /* 1 ms */ + break; + default: + ed->bInterval = 8; /* 8*125 us */ + break; + } + break; + default: /* UE_ISOCHRONOUS */ + switch (temp->usb2_speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + ed->bInterval = 1; /* 1 ms */ + break; + default: + ed->bInterval = 1; /* 125 us */ + break; + } + break; + } + } + } + temp->bNumEndpoints++; + return; +} + +/*------------------------------------------------------------------------* + * usb2_make_interface_desc + * + * This function will generate an USB interface descriptor from the + * given USB template interface descriptor, which will be inserted + * into the USB configuration. + *------------------------------------------------------------------------*/ +static void +usb2_make_interface_desc(struct usb2_temp_setup *temp, + const struct usb2_temp_interface_desc *tid) +{ + struct usb2_interface_descriptor *id; + const struct usb2_temp_endpoint_desc **ted; + const void **rd; + uint16_t old_size; + + /* Reserve memory */ + + old_size = temp->size; + temp->size += sizeof(*id); + + /* Update interface and alternate interface numbers */ + + if (tid->isAltInterface == 0) { + temp->bAlternateSetting = 0; + temp->bInterfaceNumber++; + } else { + temp->bAlternateSetting++; + } + + /* Scan all Raw Descriptors first */ + + rd = tid->ppRawDesc; + + if (rd) { + while (*rd) { + usb2_make_raw_desc(temp, *rd); + rd++; + } + } + /* Reset some counters */ + + temp->bNumEndpoints = 0; + + /* Scan all Endpoint Descriptors second */ + + ted = tid->ppEndpoints; + if (ted) { + while (*ted) { + usb2_make_endpoint_desc(temp, *ted); + ted++; + } + } + /* + * Fill out the real USB interface descriptor + * in case there is a buffer present: + */ + if (temp->buf) { + id = USB_ADD_BYTES(temp->buf, old_size); + id->bLength = sizeof(*id); + id->bDescriptorType = UDESC_INTERFACE; + id->bInterfaceNumber = temp->bInterfaceNumber; + id->bAlternateSetting = temp->bAlternateSetting; + id->bNumEndpoints = temp->bNumEndpoints; + id->bInterfaceClass = tid->bInterfaceClass; + id->bInterfaceSubClass = tid->bInterfaceSubClass; + id->bInterfaceProtocol = tid->bInterfaceProtocol; + id->iInterface = tid->iInterface; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_make_config_desc + * + * This function will generate an USB config descriptor from the given + * USB template config descriptor, which will be inserted into the USB + * configuration. + *------------------------------------------------------------------------*/ +static void +usb2_make_config_desc(struct usb2_temp_setup *temp, + const struct usb2_temp_config_desc *tcd) +{ + struct usb2_config_descriptor *cd; + const struct usb2_temp_interface_desc **tid; + uint16_t old_size; + + /* Reserve memory */ + + old_size = temp->size; + temp->size += sizeof(*cd); + + /* Reset some counters */ + + temp->bInterfaceNumber = 0 - 1; + temp->bAlternateSetting = 0; + + /* Scan all the USB interfaces */ + + tid = tcd->ppIfaceDesc; + if (tid) { + while (*tid) { + usb2_make_interface_desc(temp, *tid); + tid++; + } + } + /* + * Fill out the real USB config descriptor + * in case there is a buffer present: + */ + if (temp->buf) { + cd = USB_ADD_BYTES(temp->buf, old_size); + + /* compute total size */ + old_size = temp->size - old_size; + + cd->bLength = sizeof(*cd); + cd->bDescriptorType = UDESC_CONFIG; + USETW(cd->wTotalLength, old_size); + cd->bNumInterface = temp->bInterfaceNumber + 1; + cd->bConfigurationValue = temp->bConfigurationValue; + cd->iConfiguration = tcd->iConfiguration; + cd->bmAttributes = tcd->bmAttributes; + cd->bMaxPower = tcd->bMaxPower; + cd->bmAttributes |= (UC_REMOTE_WAKEUP | UC_BUS_POWERED); + + if (temp->self_powered) { + cd->bmAttributes |= UC_SELF_POWERED; + } else { + cd->bmAttributes &= ~UC_SELF_POWERED; + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_make_device_desc + * + * This function will generate an USB device descriptor from the + * given USB template device descriptor. + *------------------------------------------------------------------------*/ +static void +usb2_make_device_desc(struct usb2_temp_setup *temp, + const struct usb2_temp_device_desc *tdd) +{ + struct usb2_temp_data *utd; + const struct usb2_temp_config_desc **tcd; + uint16_t old_size; + + /* Reserve memory */ + + old_size = temp->size; + temp->size += sizeof(*utd); + + /* Scan all the USB configs */ + + temp->bConfigurationValue = 1; + tcd = tdd->ppConfigDesc; + if (tcd) { + while (*tcd) { + usb2_make_config_desc(temp, *tcd); + temp->bConfigurationValue++; + tcd++; + } + } + /* + * Fill out the real USB device descriptor + * in case there is a buffer present: + */ + + if (temp->buf) { + utd = USB_ADD_BYTES(temp->buf, old_size); + + /* Store a pointer to our template device descriptor */ + utd->tdd = tdd; + + /* Fill out USB device descriptor */ + utd->udd.bLength = sizeof(utd->udd); + utd->udd.bDescriptorType = UDESC_DEVICE; + utd->udd.bDeviceClass = tdd->bDeviceClass; + utd->udd.bDeviceSubClass = tdd->bDeviceSubClass; + utd->udd.bDeviceProtocol = tdd->bDeviceProtocol; + USETW(utd->udd.idVendor, tdd->idVendor); + USETW(utd->udd.idProduct, tdd->idProduct); + USETW(utd->udd.bcdDevice, tdd->bcdDevice); + utd->udd.iManufacturer = tdd->iManufacturer; + utd->udd.iProduct = tdd->iProduct; + utd->udd.iSerialNumber = tdd->iSerialNumber; + utd->udd.bNumConfigurations = temp->bConfigurationValue - 1; + + /* + * Fill out the USB device qualifier. Pretend that we + * don't support any other speeds by setting + * "bNumConfigurations" equal to zero. That saves us + * generating an extra set of configuration + * descriptors. + */ + utd->udq.bLength = sizeof(utd->udq); + utd->udq.bDescriptorType = UDESC_DEVICE_QUALIFIER; + utd->udq.bDeviceClass = tdd->bDeviceClass; + utd->udq.bDeviceSubClass = tdd->bDeviceSubClass; + utd->udq.bDeviceProtocol = tdd->bDeviceProtocol; + utd->udq.bNumConfigurations = 0; + USETW(utd->udq.bcdUSB, 0x0200); + utd->udq.bMaxPacketSize0 = 0; + + switch (temp->usb2_speed) { + case USB_SPEED_LOW: + USETW(utd->udd.bcdUSB, 0x0110); + utd->udd.bMaxPacketSize = 8; + break; + case USB_SPEED_FULL: + USETW(utd->udd.bcdUSB, 0x0110); + utd->udd.bMaxPacketSize = 32; + break; + case USB_SPEED_HIGH: + USETW(utd->udd.bcdUSB, 0x0200); + utd->udd.bMaxPacketSize = 64; + break; + case USB_SPEED_VARIABLE: + USETW(utd->udd.bcdUSB, 0x0250); + utd->udd.bMaxPacketSize = 255; /* 512 bytes */ + break; + default: + temp->err = USB_ERR_INVAL; + break; + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_hw_ep_match + * + * Return values: + * 0: The endpoint profile does not match the criterias + * Else: The endpoint profile matches the criterias + *------------------------------------------------------------------------*/ +static uint8_t +usb2_hw_ep_match(const struct usb2_hw_ep_profile *pf, + uint8_t ep_type, uint8_t ep_dir_in) +{ + if (ep_type == UE_CONTROL) { + /* special */ + return (pf->support_control); + } + if ((pf->support_in && ep_dir_in) || + (pf->support_out && !ep_dir_in)) { + if ((pf->support_interrupt && (ep_type == UE_INTERRUPT)) || + (pf->support_isochronous && (ep_type == UE_ISOCHRONOUS)) || + (pf->support_bulk && (ep_type == UE_BULK))) { + return (1); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb2_hw_ep_find_match + * + * This function is used to find the best matching endpoint profile + * for and endpoint belonging to an USB descriptor. + * + * Return values: + * 0: Success. Got a match. + * Else: Failure. No match. + *------------------------------------------------------------------------*/ +static uint8_t +usb2_hw_ep_find_match(struct usb2_hw_ep_scratch *ues, + struct usb2_hw_ep_scratch_sub *ep, uint8_t is_simplex) +{ + const struct usb2_hw_ep_profile *pf; + uint16_t distance; + uint16_t temp; + uint16_t max_frame_size; + uint8_t n; + uint8_t best_n; + uint8_t dir_in; + uint8_t dir_out; + + distance = 0xFFFF; + best_n = 0; + + if ((!ep->needs_in) && (!ep->needs_out)) { + return (0); /* we are done */ + } + if (ep->needs_ep_type == UE_CONTROL) { + dir_in = 1; + dir_out = 1; + } else { + if (ep->needs_in) { + dir_in = 1; + dir_out = 0; + } else { + dir_in = 0; + dir_out = 1; + } + } + + for (n = 1; n != (USB_EP_MAX / 2); n++) { + + /* get HW endpoint profile */ + (ues->methods->get_hw_ep_profile) (ues->udev, &pf, n); + if (pf == NULL) { + /* end of profiles */ + break; + } + /* check if IN-endpoint is reserved */ + if (dir_in || pf->is_simplex) { + if (ues->bmInAlloc[n / 8] & (1 << (n % 8))) { + /* mismatch */ + continue; + } + } + /* check if OUT-endpoint is reserved */ + if (dir_out || pf->is_simplex) { + if (ues->bmOutAlloc[n / 8] & (1 << (n % 8))) { + /* mismatch */ + continue; + } + } + /* check simplex */ + if (pf->is_simplex == is_simplex) { + /* mismatch */ + continue; + } + /* check if HW endpoint matches */ + if (!usb2_hw_ep_match(pf, ep->needs_ep_type, dir_in)) { + /* mismatch */ + continue; + } + /* get maximum frame size */ + if (dir_in) + max_frame_size = pf->max_in_frame_size; + else + max_frame_size = pf->max_out_frame_size; + + /* check if we have a matching profile */ + if (max_frame_size >= ep->max_frame_size) { + temp = (max_frame_size - ep->max_frame_size); + if (distance > temp) { + distance = temp; + best_n = n; + ep->pf = pf; + } + } + } + + /* see if we got a match */ + if (best_n != 0) { + /* get the correct profile */ + pf = ep->pf; + + /* reserve IN-endpoint */ + if (dir_in) { + ues->bmInAlloc[best_n / 8] |= + (1 << (best_n % 8)); + ep->hw_endpoint_in = best_n | UE_DIR_IN; + ep->needs_in = 0; + } + /* reserve OUT-endpoint */ + if (dir_out) { + ues->bmOutAlloc[best_n / 8] |= + (1 << (best_n % 8)); + ep->hw_endpoint_out = best_n | UE_DIR_OUT; + ep->needs_out = 0; + } + return (0); /* got a match */ + } + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usb2_hw_ep_get_needs + * + * This function will figure out the type and number of endpoints + * which are needed for an USB configuration. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +static uint8_t +usb2_hw_ep_get_needs(struct usb2_hw_ep_scratch *ues, + uint8_t ep_type, uint8_t is_complete) +{ + const struct usb2_hw_ep_profile *pf; + struct usb2_hw_ep_scratch_sub *ep_iface; + struct usb2_hw_ep_scratch_sub *ep_curr; + struct usb2_hw_ep_scratch_sub *ep_max; + struct usb2_hw_ep_scratch_sub *ep_end; + struct usb2_descriptor *desc; + struct usb2_interface_descriptor *id; + struct usb2_endpoint_descriptor *ed; + uint16_t wMaxPacketSize; + uint16_t temp; + uint8_t speed; + uint8_t ep_no; + + ep_iface = ues->ep_max; + ep_curr = ues->ep_max; + ep_end = ues->ep + USB_EP_MAX; + ep_max = ues->ep_max; + desc = NULL; + speed = usb2_get_speed(ues->udev); + +repeat: + + while ((desc = usb2_desc_foreach(ues->cd, desc))) { + + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + + id = (void *)desc; + + if (id->bAlternateSetting == 0) { + /* going forward */ + ep_iface = ep_max; + } else { + /* reset */ + ep_curr = ep_iface; + } + } + if ((desc->bDescriptorType == UDESC_ENDPOINT) && + (desc->bLength >= sizeof(*ed))) { + + ed = (void *)desc; + + goto handle_endpoint_desc; + } + } + ues->ep_max = ep_max; + return (0); + +handle_endpoint_desc: + temp = (ed->bmAttributes & UE_XFERTYPE); + + if (temp == ep_type) { + + if (ep_curr == ep_end) { + /* too many endpoints */ + return (1); /* failure */ + } + wMaxPacketSize = UGETW(ed->wMaxPacketSize); + if ((wMaxPacketSize & 0xF800) && + (speed == USB_SPEED_HIGH)) { + /* handle packet multiplier */ + temp = (wMaxPacketSize >> 11) & 3; + wMaxPacketSize &= 0x7FF; + if (temp == 1) { + wMaxPacketSize *= 2; + } else { + wMaxPacketSize *= 3; + } + } + /* + * Check if we have a fixed endpoint number, else the + * endpoint number is allocated dynamically: + */ + ep_no = (ed->bEndpointAddress & UE_ADDR); + if (ep_no != 0) { + + /* get HW endpoint profile */ + (ues->methods->get_hw_ep_profile) + (ues->udev, &pf, ep_no); + if (pf == NULL) { + /* HW profile does not exist - failure */ + DPRINTFN(0, "Endpoint profile %u " + "does not exist\n", ep_no); + return (1); + } + /* reserve fixed endpoint number */ + if (ep_type == UE_CONTROL) { + ues->bmInAlloc[ep_no / 8] |= + (1 << (ep_no % 8)); + ues->bmOutAlloc[ep_no / 8] |= + (1 << (ep_no % 8)); + if ((pf->max_in_frame_size < wMaxPacketSize) || + (pf->max_out_frame_size < wMaxPacketSize)) { + DPRINTFN(0, "Endpoint profile %u " + "has too small buffer!\n", ep_no); + return (1); + } + } else if (ed->bEndpointAddress & UE_DIR_IN) { + ues->bmInAlloc[ep_no / 8] |= + (1 << (ep_no % 8)); + if (pf->max_in_frame_size < wMaxPacketSize) { + DPRINTFN(0, "Endpoint profile %u " + "has too small buffer!\n", ep_no); + return (1); + } + } else { + ues->bmOutAlloc[ep_no / 8] |= + (1 << (ep_no % 8)); + if (pf->max_out_frame_size < wMaxPacketSize) { + DPRINTFN(0, "Endpoint profile %u " + "has too small buffer!\n", ep_no); + return (1); + } + } + } else if (is_complete) { + + /* check if we have enough buffer space */ + if (wMaxPacketSize > + ep_curr->max_frame_size) { + return (1); /* failure */ + } + if (ed->bEndpointAddress & UE_DIR_IN) { + ed->bEndpointAddress = + ep_curr->hw_endpoint_in; + } else { + ed->bEndpointAddress = + ep_curr->hw_endpoint_out; + } + + } else { + + /* compute the maximum frame size */ + if (ep_curr->max_frame_size < wMaxPacketSize) { + ep_curr->max_frame_size = wMaxPacketSize; + } + if (temp == UE_CONTROL) { + ep_curr->needs_in = 1; + ep_curr->needs_out = 1; + } else { + if (ed->bEndpointAddress & UE_DIR_IN) { + ep_curr->needs_in = 1; + } else { + ep_curr->needs_out = 1; + } + } + ep_curr->needs_ep_type = ep_type; + } + + ep_curr++; + if (ep_max < ep_curr) { + ep_max = ep_curr; + } + } + goto repeat; +} + +/*------------------------------------------------------------------------* + * usb2_hw_ep_resolve + * + * This function will try to resolve endpoint requirements by the + * given endpoint profiles that the USB hardware reports. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_hw_ep_resolve(struct usb2_device *udev, + struct usb2_descriptor *desc) +{ + struct usb2_hw_ep_scratch *ues; + struct usb2_hw_ep_scratch_sub *ep; + const struct usb2_hw_ep_profile *pf; + struct usb2_bus_methods *methods; + struct usb2_device_descriptor *dd; + uint16_t mps; + + if (desc == NULL) { + return (USB_ERR_INVAL); + } + /* get bus methods */ + methods = udev->bus->methods; + + if (methods->get_hw_ep_profile == NULL) { + return (USB_ERR_INVAL); + } + if (desc->bDescriptorType == UDESC_DEVICE) { + + if (desc->bLength < sizeof(*dd)) { + return (USB_ERR_INVAL); + } + dd = (void *)desc; + + /* get HW control endpoint 0 profile */ + (methods->get_hw_ep_profile) (udev, &pf, 0); + if (pf == NULL) { + return (USB_ERR_INVAL); + } + if (!usb2_hw_ep_match(pf, UE_CONTROL, 0)) { + DPRINTFN(0, "Endpoint 0 does not " + "support control\n"); + return (USB_ERR_INVAL); + } + mps = dd->bMaxPacketSize; + + if (udev->speed == USB_SPEED_FULL) { + /* + * We can optionally choose another packet size ! + */ + while (1) { + /* check if "mps" is ok */ + if (pf->max_in_frame_size >= mps) { + break; + } + /* reduce maximum packet size */ + mps /= 2; + + /* check if "mps" is too small */ + if (mps < 8) { + return (USB_ERR_INVAL); + } + } + + dd->bMaxPacketSize = mps; + + } else { + /* We only have one choice */ + if (mps == 255) { + mps = 512; + } + /* Check if we support the specified wMaxPacketSize */ + if (pf->max_in_frame_size < mps) { + return (USB_ERR_INVAL); + } + } + return (0); /* success */ + } + if (desc->bDescriptorType != UDESC_CONFIG) { + return (USB_ERR_INVAL); + } + if (desc->bLength < sizeof(*(ues->cd))) { + return (USB_ERR_INVAL); + } + ues = udev->bus->scratch[0].hw_ep_scratch; + + bzero(ues, sizeof(*ues)); + + ues->ep_max = ues->ep; + ues->cd = (void *)desc; + ues->methods = methods; + ues->udev = udev; + + /* Get all the endpoints we need */ + + if (usb2_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 0) || + usb2_hw_ep_get_needs(ues, UE_INTERRUPT, 0) || + usb2_hw_ep_get_needs(ues, UE_CONTROL, 0) || + usb2_hw_ep_get_needs(ues, UE_BULK, 0)) { + DPRINTFN(0, "Could not get needs\n"); + return (USB_ERR_INVAL); + } + for (ep = ues->ep; ep != ues->ep_max; ep++) { + + while (ep->needs_in || ep->needs_out) { + + /* + * First try to use a simplex endpoint. + * Then try to use a duplex endpoint. + */ + if (usb2_hw_ep_find_match(ues, ep, 1) && + usb2_hw_ep_find_match(ues, ep, 0)) { + DPRINTFN(0, "Could not find match\n"); + return (USB_ERR_INVAL); + } + } + } + + ues->ep_max = ues->ep; + + /* Update all endpoint addresses */ + + if (usb2_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 1) || + usb2_hw_ep_get_needs(ues, UE_INTERRUPT, 1) || + usb2_hw_ep_get_needs(ues, UE_CONTROL, 1) || + usb2_hw_ep_get_needs(ues, UE_BULK, 1)) { + DPRINTFN(0, "Could not update endpoint address\n"); + return (USB_ERR_INVAL); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb2_temp_get_tdd + * + * Returns: + * NULL: No USB template device descriptor found. + * Else: Pointer to the USB template device descriptor. + *------------------------------------------------------------------------*/ +static const struct usb2_temp_device_desc * +usb2_temp_get_tdd(struct usb2_device *udev) +{ + if (udev->usb2_template_ptr == NULL) { + return (NULL); + } + return (udev->usb2_template_ptr->tdd); +} + +/*------------------------------------------------------------------------* + * usb2_temp_get_device_desc + * + * Returns: + * NULL: No USB device descriptor found. + * Else: Pointer to USB device descriptor. + *------------------------------------------------------------------------*/ +static void * +usb2_temp_get_device_desc(struct usb2_device *udev) +{ + struct usb2_device_descriptor *dd; + + if (udev->usb2_template_ptr == NULL) { + return (NULL); + } + dd = &udev->usb2_template_ptr->udd; + if (dd->bDescriptorType != UDESC_DEVICE) { + /* sanity check failed */ + return (NULL); + } + return (dd); +} + +/*------------------------------------------------------------------------* + * usb2_temp_get_qualifier_desc + * + * Returns: + * NULL: No USB device_qualifier descriptor found. + * Else: Pointer to USB device_qualifier descriptor. + *------------------------------------------------------------------------*/ +static void * +usb2_temp_get_qualifier_desc(struct usb2_device *udev) +{ + struct usb2_device_qualifier *dq; + + if (udev->usb2_template_ptr == NULL) { + return (NULL); + } + dq = &udev->usb2_template_ptr->udq; + if (dq->bDescriptorType != UDESC_DEVICE_QUALIFIER) { + /* sanity check failed */ + return (NULL); + } + return (dq); +} + +/*------------------------------------------------------------------------* + * usb2_temp_get_config_desc + * + * Returns: + * NULL: No USB config descriptor found. + * Else: Pointer to USB config descriptor having index "index". + *------------------------------------------------------------------------*/ +static void * +usb2_temp_get_config_desc(struct usb2_device *udev, + uint16_t *pLength, uint8_t index) +{ + struct usb2_device_descriptor *dd; + struct usb2_config_descriptor *cd; + uint16_t temp; + + if (udev->usb2_template_ptr == NULL) { + return (NULL); + } + dd = &udev->usb2_template_ptr->udd; + cd = (void *)(udev->usb2_template_ptr + 1); + + if (index >= dd->bNumConfigurations) { + /* out of range */ + return (NULL); + } + while (index--) { + if (cd->bDescriptorType != UDESC_CONFIG) { + /* sanity check failed */ + return (NULL); + } + temp = UGETW(cd->wTotalLength); + cd = USB_ADD_BYTES(cd, temp); + } + + if (pLength) { + *pLength = UGETW(cd->wTotalLength); + } + return (cd); +} + +/*------------------------------------------------------------------------* + * usb2_temp_get_vendor_desc + * + * Returns: + * NULL: No vendor descriptor found. + * Else: Pointer to a vendor descriptor. + *------------------------------------------------------------------------*/ +static const void * +usb2_temp_get_vendor_desc(struct usb2_device *udev, + const struct usb2_device_request *req) +{ + const struct usb2_temp_device_desc *tdd; + + tdd = usb2_temp_get_tdd(udev); + if (tdd == NULL) { + return (NULL); + } + if (tdd->getVendorDesc == NULL) { + return (NULL); + } + return ((tdd->getVendorDesc) (req)); +} + +/*------------------------------------------------------------------------* + * usb2_temp_get_string_desc + * + * Returns: + * NULL: No string descriptor found. + * Else: Pointer to a string descriptor. + *------------------------------------------------------------------------*/ +static const void * +usb2_temp_get_string_desc(struct usb2_device *udev, + uint16_t lang_id, uint8_t string_index) +{ + const struct usb2_temp_device_desc *tdd; + + tdd = usb2_temp_get_tdd(udev); + if (tdd == NULL) { + return (NULL); + } + if (tdd->getStringDesc == NULL) { + return (NULL); + } + return ((tdd->getStringDesc) (lang_id, string_index)); +} + +/*------------------------------------------------------------------------* + * usb2_temp_get_hub_desc + * + * Returns: + * NULL: No USB HUB descriptor found. + * Else: Pointer to a USB HUB descriptor. + *------------------------------------------------------------------------*/ +static const void * +usb2_temp_get_hub_desc(struct usb2_device *udev) +{ + return (NULL); /* needs to be implemented */ +} + +/*------------------------------------------------------------------------* + * usb2_temp_get_desc + * + * This function is a demultiplexer for local USB device side control + * endpoint requests. + *------------------------------------------------------------------------*/ +static void +usb2_temp_get_desc(struct usb2_device *udev, struct usb2_device_request *req, + const void **pPtr, uint16_t *pLength) +{ + const uint8_t *buf; + uint16_t len; + + buf = NULL; + len = 0; + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + default: + goto tr_stalled; + } + break; + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + default: + goto tr_stalled; + } + break; + case UT_READ_VENDOR_DEVICE: + case UT_READ_VENDOR_OTHER: + buf = usb2_temp_get_vendor_desc(udev, req); + goto tr_valid; + default: + goto tr_stalled; + } + +tr_handle_get_descriptor: + switch (req->wValue[1]) { + case UDESC_DEVICE: + if (req->wValue[0]) { + goto tr_stalled; + } + buf = usb2_temp_get_device_desc(udev); + goto tr_valid; + case UDESC_DEVICE_QUALIFIER: + if (udev->speed != USB_SPEED_HIGH) { + goto tr_stalled; + } + if (req->wValue[0]) { + goto tr_stalled; + } + buf = usb2_temp_get_qualifier_desc(udev); + goto tr_valid; + case UDESC_OTHER_SPEED_CONFIGURATION: + if (udev->speed != USB_SPEED_HIGH) { + goto tr_stalled; + } + case UDESC_CONFIG: + buf = usb2_temp_get_config_desc(udev, + &len, req->wValue[0]); + goto tr_valid; + case UDESC_STRING: + buf = usb2_temp_get_string_desc(udev, + UGETW(req->wIndex), req->wValue[0]); + goto tr_valid; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_class_descriptor: + if (req->wValue[0]) { + goto tr_stalled; + } + buf = usb2_temp_get_hub_desc(udev); + goto tr_valid; + +tr_valid: + if (buf == NULL) { + goto tr_stalled; + } + if (len == 0) { + len = buf[0]; + } + *pPtr = buf; + *pLength = len; + return; + +tr_stalled: + *pPtr = NULL; + *pLength = 0; + return; +} + +/*------------------------------------------------------------------------* + * usb2_temp_setup + * + * This function generates USB descriptors according to the given USB + * template device descriptor. It will also try to figure out the best + * matching endpoint addresses using the hardware endpoint profiles. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb2_error_t +usb2_temp_setup(struct usb2_device *udev, + const struct usb2_temp_device_desc *tdd) +{ + struct usb2_temp_setup *uts; + void *buf; + uint8_t n; + + if (tdd == NULL) { + /* be NULL safe */ + return (0); + } + uts = udev->bus->scratch[0].temp_setup; + + bzero(uts, sizeof(*uts)); + + uts->usb2_speed = udev->speed; + uts->self_powered = udev->flags.self_powered; + + /* first pass */ + + usb2_make_device_desc(uts, tdd); + + if (uts->err) { + /* some error happened */ + return (uts->err); + } + /* sanity check */ + if (uts->size == 0) { + return (USB_ERR_INVAL); + } + /* allocate zeroed memory */ + uts->buf = malloc(uts->size, M_USB, M_WAITOK | M_ZERO); + if (uts->buf == NULL) { + /* could not allocate memory */ + return (USB_ERR_NOMEM); + } + /* second pass */ + + uts->size = 0; + + usb2_make_device_desc(uts, tdd); + + /* + * Store a pointer to our descriptors: + */ + udev->usb2_template_ptr = uts->buf; + + if (uts->err) { + /* some error happened during second pass */ + goto error; + } + /* + * Resolve all endpoint addresses ! + */ + buf = usb2_temp_get_device_desc(udev); + uts->err = usb2_hw_ep_resolve(udev, buf); + if (uts->err) { + DPRINTFN(0, "Could not resolve endpoints for " + "Device Descriptor, error = %s\n", + usb2_errstr(uts->err)); + goto error; + } + for (n = 0;; n++) { + + buf = usb2_temp_get_config_desc(udev, NULL, n); + if (buf == NULL) { + break; + } + uts->err = usb2_hw_ep_resolve(udev, buf); + if (uts->err) { + DPRINTFN(0, "Could not resolve endpoints for " + "Config Descriptor %u, error = %s\n", n, + usb2_errstr(uts->err)); + goto error; + } + } + return (uts->err); + +error: + usb2_temp_unsetup(udev); + return (uts->err); +} + +/*------------------------------------------------------------------------* + * usb2_temp_unsetup + * + * This function frees any memory associated with the currently + * setup template, if any. + *------------------------------------------------------------------------*/ +static void +usb2_temp_unsetup(struct usb2_device *udev) +{ + if (udev->usb2_template_ptr) { + + free(udev->usb2_template_ptr, M_USB); + + udev->usb2_template_ptr = NULL; + } + return; +} + +static usb2_error_t +usb2_temp_setup_by_index(struct usb2_device *udev, uint16_t index) +{ + usb2_error_t err; + + switch (index) { + case 0: + err = usb2_temp_setup(udev, &usb2_template_msc); + break; + case 1: + err = usb2_temp_setup(udev, &usb2_template_cdce); + break; + case 2: + err = usb2_temp_setup(udev, &usb2_template_mtp); + break; + default: + return (USB_ERR_INVAL); + } + + return (err); +} + +static void +usb2_temp_init(void *arg) +{ + /* register our functions */ + usb2_temp_get_desc_p = &usb2_temp_get_desc; + usb2_temp_setup_by_index_p = &usb2_temp_setup_by_index; + usb2_temp_unsetup_p = &usb2_temp_unsetup; + return; +} + +SYSINIT(usb2_temp_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb2_temp_init, NULL); +SYSUNINIT(usb2_temp_unload, SI_SUB_LOCK, SI_ORDER_ANY, usb2_temp_unload, NULL); diff --git a/sys/dev/usb2/template/usb2_template.h b/sys/dev/usb2/template/usb2_template.h new file mode 100644 index 000000000000..361de3abf428 --- /dev/null +++ b/sys/dev/usb2/template/usb2_template.h @@ -0,0 +1,102 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Hans Petter Selasky + * 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. + */ + +/* USB templates are used to build up real USB descriptors */ + +#ifndef _USB_TEMPLATE_H_ +#define _USB_TEMPLATE_H_ + +typedef const void *(usb2_temp_get_string_desc_t)(uint16_t lang_id, uint8_t string_index); +typedef const void *(usb2_temp_get_vendor_desc_t)(const struct usb2_device_request *req); + +struct usb2_temp_packet_size { + uint16_t mps[USB_SPEED_MAX]; +}; + +struct usb2_temp_interval { + uint8_t bInterval[USB_SPEED_MAX]; +}; + +struct usb2_temp_endpoint_desc { + const void **ppRawDesc; + const struct usb2_temp_packet_size *pPacketSize; + const struct usb2_temp_interval *pIntervals; + /* + * If (bEndpointAddress & UE_ADDR) is non-zero the endpoint number + * is pre-selected for this endpoint descriptor. Else an endpoint + * number is automatically chosen. + */ + uint8_t bEndpointAddress; /* UE_DIR_IN or UE_DIR_OUT */ + uint8_t bmAttributes; +}; + +struct usb2_temp_interface_desc { + const void **ppRawDesc; + const struct usb2_temp_endpoint_desc **ppEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + uint8_t isAltInterface; +}; + +struct usb2_temp_config_desc { + const struct usb2_temp_interface_desc **ppIfaceDesc; + uint8_t bmAttributes; + uint8_t bMaxPower; + uint8_t iConfiguration; +}; + +struct usb2_temp_device_desc { + usb2_temp_get_string_desc_t *getStringDesc; + usb2_temp_get_vendor_desc_t *getVendorDesc; + const struct usb2_temp_config_desc **ppConfigDesc; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; +}; + +struct usb2_temp_data { + const struct usb2_temp_device_desc *tdd; + struct usb2_device_descriptor udd; /* device descriptor */ + struct usb2_device_qualifier udq; /* device qualifier */ +}; + +/* prototypes */ + +extern const struct usb2_temp_device_desc usb2_template_cdce; +extern const struct usb2_temp_device_desc usb2_template_msc; /* Mass Storage Class */ +extern const struct usb2_temp_device_desc usb2_template_mtp; /* Message Transfer + * Protocol */ + +#endif /* _USB_TEMPLATE_H_ */ diff --git a/sys/dev/usb2/template/usb2_template_cdce.c b/sys/dev/usb2/template/usb2_template_cdce.c new file mode 100644 index 000000000000..119c6bd628a0 --- /dev/null +++ b/sys/dev/usb2/template/usb2_template_cdce.c @@ -0,0 +1,325 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2007 Hans Petter Selasky + * 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. + */ + +/* + * This file contains the USB templates for a CDC USB ethernet device. + */ + +#include +#include +#include + +#include + +#include + +enum { + STRING_LANG_INDEX, + STRING_MAC_INDEX, + STRING_ETH_CONTROL_INDEX, + STRING_ETH_CONTROL_512X4_INDEX, + STRING_ETH_DATA_INDEX, + STRING_ETH_CONFIG_INDEX, + STRING_ETH_VENDOR_INDEX, + STRING_ETH_PRODUCT_INDEX, + STRING_ETH_SERIAL_INDEX, + STRING_ETH_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_MAC \ + '2', 0, 'A', 0, '2', 0, '3', 0, \ + '4', 0, '5', 0, '6', 0, '7', 0, \ + '8', 0, '9', 0, 'A', 0, 'B', 0, + +#define STRING_ETH_CONTROL \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'E', 0, 't', 0, 'h', 0, 'e', 0, \ + 'r', 0, 'n', 0, 'e', 0, 't', 0, \ + ' ', 0, 'C', 0, 'o', 0, 'm', 0, \ + 'm', 0, ' ', 0, 'i', 0, 'n', 0, \ + 't', 0, 'e', 0, 'r', 0, 'f', 0, \ + 'a', 0, 'c', 0, 'e', 0, + +/* + * Hardware Accelerated Zero Copy CDC ethernet. Buffer capacity: 512 + * frames by 4 fragments per USB transfer. + */ +#define STRING_ETH_512X4_CONTROL \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'E', 0, 't', 0, 'h', 0, 'e', 0, \ + 'r', 0, 'n', 0, 'e', 0, 't', 0, \ + ' ', 0, 'C', 0, 'o', 0, 'm', 0, \ + 'm', 0, ' ', 0, 'i', 0, 'n', 0, \ + 't', 0, 'e', 0, 'r', 0, 'f', 0, \ + 'a', 0, 'c', 0, 'e', 0, ' ', 0, \ + '5', 0, '1', 0, '2', 0, 'x', 0, \ + '4', 0, ' ', 0, 'H', 0, 'W', 0, \ + ' ', 0, 'A', 0, 'c', 0, 'c', 0, \ + 'e', 0, 'l', 0, 'e', 0, 'r', 0, \ + 'a', 0, 't', 0, 'e', 0, 'd', 0, + +#define STRING_ETH_DATA \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'E', 0, 't', 0, 'h', 0, 'e', 0, \ + 'r', 0, 'n', 0, 'e', 0, 't', 0, \ + ' ', 0, 'D', 0, 'a', 0, 't', 0, \ + 'a', 0, ' ', 0, 'i', 0, 'n', 0, \ + 't', 0, 'e', 0, 'r', 0, 'f', 0, \ + 'a', 0, 'c', 0, 'e', 0, + +#define STRING_ETH_CONFIG \ + 'D', 0, 'e', 0, 'f', 0, 'a', 0, \ + 'u', 0, 'l', 0, 't', 0, ' ', 0, \ + 'c', 0, 'o', 0, 'n', 0, 'f', 0, \ + 'i', 0, 'g', 0, + +#define STRING_ETH_VENDOR \ + 'F', 0, 'r', 0, 'e', 0, 'e', 0, \ + 'B', 0, 'S', 0, 'D', 0, ' ', 0, \ + 'f', 0, 'o', 0, 'u', 0, 'n', 0, \ + 'd', 0, 'a', 0, 't', 0, 'i', 0, \ + 'o', 0, 'n', 0, + +#define STRING_ETH_PRODUCT \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'E', 0, 't', 0, 'h', 0, 'e', 0, \ + 'r', 0, 'n', 0, 'e', 0, 't', 0, \ + ' ', 0, 'A', 0, 'd', 0, 'a', 0, \ + 'p', 0, 't', 0, 'e', 0, 'r', 0, + +#define STRING_ETH_SERIAL \ + 'D', 0, 'e', 0, 'c', 0, 'e', 0, \ + 'm', 0, 'b', 0, 'e', 0, 'r', 0, \ + ' ', 0, '2', 0, '0', 0, '0', 0, \ + '7', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_MAC, string_mac); +USB_MAKE_STRING_DESC(STRING_ETH_CONTROL, string_eth_control); +USB_MAKE_STRING_DESC(STRING_ETH_512X4_CONTROL, string_eth_512x4_control); +USB_MAKE_STRING_DESC(STRING_ETH_DATA, string_eth_data); +USB_MAKE_STRING_DESC(STRING_ETH_CONFIG, string_eth_config); +USB_MAKE_STRING_DESC(STRING_ETH_VENDOR, string_eth_vendor); +USB_MAKE_STRING_DESC(STRING_ETH_PRODUCT, string_eth_product); +USB_MAKE_STRING_DESC(STRING_ETH_SERIAL, string_eth_serial); + +/* prototypes */ + +static usb2_temp_get_string_desc_t eth_get_string_desc; + +static const struct usb2_cdc_union_descriptor eth_union_desc = { + .bLength = sizeof(eth_union_desc), + .bDescriptorType = UDESC_CS_INTERFACE, + .bDescriptorSubtype = UDESCSUB_CDC_UNION, + .bMasterInterface = 0, /* this is automatically updated */ + .bSlaveInterface[0] = 1, /* this is automatically updated */ +}; + +static const struct usb2_cdc_header_descriptor eth_header_desc = { + .bLength = sizeof(eth_header_desc), + .bDescriptorType = UDESC_CS_INTERFACE, + .bDescriptorSubtype = UDESCSUB_CDC_HEADER, + .bcdCDC[0] = 0x10, + .bcdCDC[1] = 0x01, +}; + +static const struct usb2_cdc_ethernet_descriptor eth_enf_desc = { + .bLength = sizeof(eth_enf_desc), + .bDescriptorType = UDESC_CS_INTERFACE, + .bDescriptorSubtype = UDESCSUB_CDC_ENF, + .iMacAddress = STRING_MAC_INDEX, + .bmEthernetStatistics = {0, 0, 0, 0}, + .wMaxSegmentSize = {0xEA, 0x05},/* 1514 bytes */ + .wNumberMCFilters = {0, 0}, + .bNumberPowerFilters = 0, +}; + +static const void *eth_control_if_desc[] = { + ð_union_desc, + ð_header_desc, + ð_enf_desc, + NULL, +}; + +static const struct usb2_temp_packet_size bulk_mps = { + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 512, +}; + +static const struct usb2_temp_packet_size intr_mps = { + .mps[USB_SPEED_FULL] = 8, + .mps[USB_SPEED_HIGH] = 8, +}; + +static const struct usb2_temp_endpoint_desc bulk_in_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_IN_EP_0 + .bEndpointAddress = USB_HIP_IN_EP_0, +#else + .bEndpointAddress = UE_DIR_IN, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb2_temp_endpoint_desc bulk_out_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_OUT_EP_0 + .bEndpointAddress = USB_HIP_OUT_EP_0, +#else + .bEndpointAddress = UE_DIR_OUT, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb2_temp_endpoint_desc intr_in_ep = { + .pPacketSize = &intr_mps, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_INTERRUPT, +}; + +static const struct usb2_temp_endpoint_desc *eth_intr_endpoints[] = { + &intr_in_ep, + NULL, +}; + +static const struct usb2_temp_interface_desc eth_control_interface = { + .ppEndpoints = eth_intr_endpoints, + .ppRawDesc = eth_control_if_desc, + .bInterfaceClass = UICLASS_CDC, + .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, + .bInterfaceProtocol = 0, + .iInterface = STRING_ETH_CONTROL_INDEX, +}; + +static const struct usb2_temp_interface_desc eth_control_if_512x4 = { + .ppEndpoints = eth_intr_endpoints, + .ppRawDesc = eth_control_if_desc, + .bInterfaceClass = UICLASS_CDC, + .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, + .bInterfaceProtocol = UIPROTO_CDC_ETH_512X4, + .iInterface = STRING_ETH_CONTROL_512X4_INDEX, + .isAltInterface = 1, /* this is an alternate setting */ +}; + + +static const struct usb2_temp_endpoint_desc *eth_data_endpoints[] = { + &bulk_in_ep, + &bulk_out_ep, + NULL, +}; + +static const struct usb2_temp_interface_desc eth_data_null_interface = { + .ppEndpoints = NULL, /* no endpoints */ + .bInterfaceClass = UICLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = STRING_ETH_DATA_INDEX, +}; + +static const struct usb2_temp_interface_desc eth_data_interface = { + .ppEndpoints = eth_data_endpoints, + .bInterfaceClass = UICLASS_CDC_DATA, + .bInterfaceSubClass = UISUBCLASS_DATA, + .bInterfaceProtocol = 0, + .iInterface = STRING_ETH_DATA_INDEX, + .isAltInterface = 1, /* this is an alternate setting */ +}; + +static const struct usb2_temp_interface_desc *eth_interfaces[] = { + ð_control_interface, + ð_control_if_512x4, + ð_data_null_interface, + ð_data_interface, + NULL, +}; + +static const struct usb2_temp_config_desc eth_config_desc = { + .ppIfaceDesc = eth_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = STRING_ETH_CONFIG_INDEX, +}; + +static const struct usb2_temp_config_desc *eth_configs[] = { + ð_config_desc, + NULL, +}; + +const struct usb2_temp_device_desc usb2_template_cdce = { + .getStringDesc = ð_get_string_desc, + .ppConfigDesc = eth_configs, + .idVendor = 0x0001, + .idProduct = 0x0001, + .bcdDevice = 0x0100, + .bDeviceClass = UDCLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = STRING_ETH_VENDOR_INDEX, + .iProduct = STRING_ETH_PRODUCT_INDEX, + .iSerialNumber = STRING_ETH_SERIAL_INDEX, +}; + +/*------------------------------------------------------------------------* + * eth_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +eth_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[STRING_ETH_MAX] = { + [STRING_LANG_INDEX] = &string_lang, + [STRING_MAC_INDEX] = &string_mac, + [STRING_ETH_CONTROL_INDEX] = &string_eth_control, + [STRING_ETH_CONTROL_512X4_INDEX] = &string_eth_512x4_control, + [STRING_ETH_DATA_INDEX] = &string_eth_data, + [STRING_ETH_CONFIG_INDEX] = &string_eth_config, + [STRING_ETH_VENDOR_INDEX] = &string_eth_vendor, + [STRING_ETH_PRODUCT_INDEX] = &string_eth_product, + [STRING_ETH_SERIAL_INDEX] = &string_eth_serial, + }; + + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < STRING_ETH_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/dev/usb2/template/usb2_template_msc.c b/sys/dev/usb2/template/usb2_template_msc.c new file mode 100644 index 000000000000..b8bc7ad2dfd8 --- /dev/null +++ b/sys/dev/usb2/template/usb2_template_msc.c @@ -0,0 +1,199 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2008 Hans Petter Selasky + * 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. + */ + +/* + * This file contains the USB templates for an USB Mass Storage Device. + */ + +#include +#include + +#include + +#include + +enum { + STRING_LANG_INDEX, + STRING_MSC_DATA_INDEX, + STRING_MSC_CONFIG_INDEX, + STRING_MSC_VENDOR_INDEX, + STRING_MSC_PRODUCT_INDEX, + STRING_MSC_SERIAL_INDEX, + STRING_MSC_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_MSC_DATA \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'M', 0, 'a', 0, 's', 0, 's', 0, \ + ' ', 0, 'S', 0, 't', 0, 'o', 0, \ + 'r', 0, 'a', 0, 'g', 0, 'e', 0, \ + ' ', 0, 'I', 0, 'n', 0, 't', 0, \ + 'e', 0, 'r', 0, 'f', 0, 'a', 0, \ + 'c', 0, 'e', 0, + +#define STRING_MSC_CONFIG \ + 'D', 0, 'e', 0, 'f', 0, 'a', 0, \ + 'u', 0, 'l', 0, 't', 0, ' ', 0, \ + 'c', 0, 'o', 0, 'n', 0, 'f', 0, \ + 'i', 0, 'g', 0, + +#define STRING_MSC_VENDOR \ + 'F', 0, 'r', 0, 'e', 0, 'e', 0, \ + 'B', 0, 'S', 0, 'D', 0, ' ', 0, \ + 'f', 0, 'o', 0, 'u', 0, 'n', 0, \ + 'd', 0, 'a', 0, 't', 0, 'i', 0, \ + 'o', 0, 'n', 0, + +#define STRING_MSC_PRODUCT \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'M', 0, 'e', 0, 'm', 0, 'o', 0, \ + 'r', 0, 'y', 0, ' ', 0, 'S', 0, \ + 't', 0, 'i', 0, 'c', 0, 'k', 0 + +#define STRING_MSC_SERIAL \ + 'M', 0, 'a', 0, 'r', 0, 'c', 0, \ + 'h', 0, ' ', 0, '2', 0, '0', 0, \ + '0', 0, '8', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_MSC_DATA, string_msc_data); +USB_MAKE_STRING_DESC(STRING_MSC_CONFIG, string_msc_config); +USB_MAKE_STRING_DESC(STRING_MSC_VENDOR, string_msc_vendor); +USB_MAKE_STRING_DESC(STRING_MSC_PRODUCT, string_msc_product); +USB_MAKE_STRING_DESC(STRING_MSC_SERIAL, string_msc_serial); + +/* prototypes */ + +static usb2_temp_get_string_desc_t msc_get_string_desc; + +static const struct usb2_temp_packet_size bulk_mps = { + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 512, +}; + +static const struct usb2_temp_endpoint_desc bulk_in_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_IN_EP_0 + .bEndpointAddress = USB_HIP_IN_EP_0, +#else + .bEndpointAddress = UE_DIR_IN, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb2_temp_endpoint_desc bulk_out_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_OUT_EP_0 + .bEndpointAddress = USB_HIP_OUT_EP_0, +#else + .bEndpointAddress = UE_DIR_OUT, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb2_temp_endpoint_desc *msc_data_endpoints[] = { + &bulk_in_ep, + &bulk_out_ep, + NULL, +}; + +static const struct usb2_temp_interface_desc msc_data_interface = { + .ppEndpoints = msc_data_endpoints, + .bInterfaceClass = UICLASS_MASS, + .bInterfaceSubClass = UISUBCLASS_SCSI, + .bInterfaceProtocol = UIPROTO_MASS_BBB, + .iInterface = STRING_MSC_DATA_INDEX, +}; + +static const struct usb2_temp_interface_desc *msc_interfaces[] = { + &msc_data_interface, + NULL, +}; + +static const struct usb2_temp_config_desc msc_config_desc = { + .ppIfaceDesc = msc_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = STRING_MSC_CONFIG_INDEX, +}; + +static const struct usb2_temp_config_desc *msc_configs[] = { + &msc_config_desc, + NULL, +}; + +const struct usb2_temp_device_desc usb2_template_msc = { + .getStringDesc = &msc_get_string_desc, + .ppConfigDesc = msc_configs, + .idVendor = 0x0001, + .idProduct = 0x0001, + .bcdDevice = 0x0100, + .bDeviceClass = UDCLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = STRING_MSC_VENDOR_INDEX, + .iProduct = STRING_MSC_PRODUCT_INDEX, + .iSerialNumber = STRING_MSC_SERIAL_INDEX, +}; + +/*------------------------------------------------------------------------* + * msc_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +msc_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[STRING_MSC_MAX] = { + [STRING_LANG_INDEX] = &string_lang, + [STRING_MSC_DATA_INDEX] = &string_msc_data, + [STRING_MSC_CONFIG_INDEX] = &string_msc_config, + [STRING_MSC_VENDOR_INDEX] = &string_msc_vendor, + [STRING_MSC_PRODUCT_INDEX] = &string_msc_product, + [STRING_MSC_SERIAL_INDEX] = &string_msc_serial, + }; + + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < STRING_MSC_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/dev/usb2/template/usb2_template_mtp.c b/sys/dev/usb2/template/usb2_template_mtp.c new file mode 100644 index 000000000000..f1599fc79388 --- /dev/null +++ b/sys/dev/usb2/template/usb2_template_mtp.c @@ -0,0 +1,262 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2008 Hans Petter Selasky + * 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. + */ + +/* + * This file contains the USB templates for an USB Message Transfer + * Protocol device. + * + * NOTE: It is common practice that MTP devices use some dummy + * descriptor cludges to be automatically detected by the host + * operating system. These descriptors are documented in the LibMTP + * library at sourceforge.net. The alternative is to supply the host + * operating system the VID and PID of your device. + */ + +#include +#include + +#include + +#include + +#define MTP_BREQUEST 0x08 + +enum { + STRING_LANG_INDEX, + STRING_MTP_DATA_INDEX, + STRING_MTP_CONFIG_INDEX, + STRING_MTP_VENDOR_INDEX, + STRING_MTP_PRODUCT_INDEX, + STRING_MTP_SERIAL_INDEX, + STRING_MTP_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_MTP_DATA \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'M', 0, 'T', 0, 'P', 0, \ + ' ', 0, 'I', 0, 'n', 0, 't', 0, \ + 'e', 0, 'r', 0, 'f', 0, 'a', 0, \ + 'c', 0, 'e', 0, + +#define STRING_MTP_CONFIG \ + 'D', 0, 'e', 0, 'f', 0, 'a', 0, \ + 'u', 0, 'l', 0, 't', 0, ' ', 0, \ + 'c', 0, 'o', 0, 'n', 0, 'f', 0, \ + 'i', 0, 'g', 0, + +#define STRING_MTP_VENDOR \ + 'F', 0, 'r', 0, 'e', 0, 'e', 0, \ + 'B', 0, 'S', 0, 'D', 0, ' ', 0, \ + 'f', 0, 'o', 0, 'u', 0, 'n', 0, \ + 'd', 0, 'a', 0, 't', 0, 'i', 0, \ + 'o', 0, 'n', 0, + +#define STRING_MTP_PRODUCT \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'M', 0, 'T', 0, 'P', 0, + +#define STRING_MTP_SERIAL \ + 'J', 0, 'u', 0, 'n', 0, 'e', 0, \ + ' ', 0, '2', 0, '0', 0, '0', 0, \ + '8', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_MTP_DATA, string_mtp_data); +USB_MAKE_STRING_DESC(STRING_MTP_CONFIG, string_mtp_config); +USB_MAKE_STRING_DESC(STRING_MTP_VENDOR, string_mtp_vendor); +USB_MAKE_STRING_DESC(STRING_MTP_PRODUCT, string_mtp_product); +USB_MAKE_STRING_DESC(STRING_MTP_SERIAL, string_mtp_serial); + +/* prototypes */ + +static usb2_temp_get_string_desc_t mtp_get_string_desc; +static usb2_temp_get_vendor_desc_t mtp_get_vendor_desc; + +static const struct usb2_temp_packet_size bulk_mps = { + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 512, +}; + +static const struct usb2_temp_packet_size intr_mps = { + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 64, +}; + +static const struct usb2_temp_endpoint_desc bulk_out_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_OUT_EP_0 + .bEndpointAddress = USB_HIP_OUT_EP_0, +#else + .bEndpointAddress = UE_DIR_OUT, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb2_temp_endpoint_desc intr_in_ep = { + .pPacketSize = &intr_mps, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_INTERRUPT, +}; + +static const struct usb2_temp_endpoint_desc bulk_in_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_IN_EP_0 + .bEndpointAddress = USB_HIP_IN_EP_0, +#else + .bEndpointAddress = UE_DIR_IN, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb2_temp_endpoint_desc *mtp_data_endpoints[] = { + &bulk_in_ep, + &bulk_out_ep, + &intr_in_ep, + NULL, +}; + +static const struct usb2_temp_interface_desc mtp_data_interface = { + .ppEndpoints = mtp_data_endpoints, + .bInterfaceClass = UICLASS_IMAGE, + .bInterfaceSubClass = UISUBCLASS_SIC, /* Still Image Class */ + .bInterfaceProtocol = 1, /* PIMA 15740 */ + .iInterface = STRING_MTP_DATA_INDEX, +}; + +static const struct usb2_temp_interface_desc *mtp_interfaces[] = { + &mtp_data_interface, + NULL, +}; + +static const struct usb2_temp_config_desc mtp_config_desc = { + .ppIfaceDesc = mtp_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = STRING_MTP_CONFIG_INDEX, +}; + +static const struct usb2_temp_config_desc *mtp_configs[] = { + &mtp_config_desc, + NULL, +}; + +const struct usb2_temp_device_desc usb2_template_mtp = { + .getStringDesc = &mtp_get_string_desc, + .getVendorDesc = &mtp_get_vendor_desc, + .ppConfigDesc = mtp_configs, + .idVendor = 0x0001, + .idProduct = 0x0001, + .bcdDevice = 0x0100, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = STRING_MTP_VENDOR_INDEX, + .iProduct = STRING_MTP_PRODUCT_INDEX, + .iSerialNumber = STRING_MTP_SERIAL_INDEX, +}; + +/*------------------------------------------------------------------------* + * mtp_get_vendor_desc + * + * Return values: + * NULL: Failure. No such vendor descriptor. + * Else: Success. Pointer to vendor descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +mtp_get_vendor_desc(const struct usb2_device_request *req) +{ + static const uint8_t dummy_desc[0x28] = { + 0x28, 0, 0, 0, 0, 1, 4, 0, + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0x4D, 0x54, 0x50, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + if ((req->bmRequestType == UT_READ_VENDOR_DEVICE) && + (req->bRequest == MTP_BREQUEST) && (req->wValue[0] == 0) && + (req->wValue[1] == 0) && (req->wIndex[1] == 0) && + ((req->wIndex[0] == 4) || (req->wIndex[0] == 5))) { + /* + * By returning this descriptor LibMTP will + * automatically pickup our device. + */ + return (dummy_desc); + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * mtp_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +mtp_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[STRING_MTP_MAX] = { + [STRING_LANG_INDEX] = &string_lang, + [STRING_MTP_DATA_INDEX] = &string_mtp_data, + [STRING_MTP_CONFIG_INDEX] = &string_mtp_config, + [STRING_MTP_VENDOR_INDEX] = &string_mtp_vendor, + [STRING_MTP_PRODUCT_INDEX] = &string_mtp_product, + [STRING_MTP_SERIAL_INDEX] = &string_mtp_serial, + }; + + static const uint8_t dummy_desc[0x12] = { + 0x12, 0x03, 0x4D, 0x00, 0x53, 0x00, 0x46, 0x00, + 0x54, 0x00, 0x31, 0x00, 0x30, 0x00, 0x30, 0x00, + MTP_BREQUEST, 0x00, + }; + + if (string_index == 0xEE) { + /* + * By returning this string LibMTP will automatically + * pickup our device. + */ + return (dummy_desc); + } + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < STRING_MTP_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/dev/usb2/wlan/if_rum2.c b/sys/dev/usb2/wlan/if_rum2.c new file mode 100644 index 000000000000..bae3581d6f62 --- /dev/null +++ b/sys/dev/usb2/wlan/if_rum2.c @@ -0,0 +1,2961 @@ +/*- + * Copyright (c) 2005-2007 Damien Bergamini + * Copyright (c) 2006 Niall O'Higgins + * Copyright (c) 2007-2008 Hans Petter Selasky + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * NOTE: all function names beginning like "rum_cfg_" can only + * be called from within the config thread function ! + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Ralink Technology RT2501USB/RT2601USB chipset driver + * http://www.ralinktech.com.tw/ + */ + +#include +#include +#include +#include + +#define usb2_config_td_cc rum_config_copy +#define usb2_config_td_softc rum_softc + +#define USB_DEBUG_VAR rum_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if USB_DEBUG +static int rum_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, rum, CTLFLAG_RW, 0, "USB rum"); +SYSCTL_INT(_hw_usb2_rum, OID_AUTO, debug, CTLFLAG_RW, &rum_debug, 0, + "Debug level"); +#endif + +/* prototypes */ + +static device_probe_t rum_probe; +static device_attach_t rum_attach; +static device_detach_t rum_detach; + +static usb2_callback_t rum_bulk_read_callback; +static usb2_callback_t rum_bulk_read_clear_stall_callback; +static usb2_callback_t rum_bulk_write_callback; +static usb2_callback_t rum_bulk_write_clear_stall_callback; + +static usb2_config_td_command_t rum_cfg_first_time_setup; +static usb2_config_td_command_t rum_config_copy; +static usb2_config_td_command_t rum_cfg_scan_start; +static usb2_config_td_command_t rum_cfg_scan_end; +static usb2_config_td_command_t rum_cfg_select_band; +static usb2_config_td_command_t rum_cfg_set_chan; +static usb2_config_td_command_t rum_cfg_enable_tsf_sync; +static usb2_config_td_command_t rum_cfg_enable_mrr; +static usb2_config_td_command_t rum_cfg_update_slot; +static usb2_config_td_command_t rum_cfg_select_antenna; +static usb2_config_td_command_t rum_cfg_set_txpreamble; +static usb2_config_td_command_t rum_cfg_update_promisc; +static usb2_config_td_command_t rum_cfg_pre_init; +static usb2_config_td_command_t rum_cfg_init; +static usb2_config_td_command_t rum_cfg_pre_stop; +static usb2_config_td_command_t rum_cfg_stop; +static usb2_config_td_command_t rum_cfg_amrr_timeout; +static usb2_config_td_command_t rum_cfg_prepare_beacon; +static usb2_config_td_command_t rum_cfg_newstate; + +static const char *rum_get_rf(uint32_t rev); +static int rum_ioctl_cb(struct ifnet *ifp, u_long cmd, caddr_t data); +static void rum_std_command(struct ieee80211com *ic, usb2_config_td_command_t *func); +static void rum_scan_start_cb(struct ieee80211com *); +static void rum_scan_end_cb(struct ieee80211com *); +static void rum_set_channel_cb(struct ieee80211com *); +static uint16_t rum_cfg_eeprom_read_2(struct rum_softc *sc, uint16_t addr); +static uint32_t rum_cfg_bbp_disbusy(struct rum_softc *sc); +static uint32_t rum_cfg_read(struct rum_softc *sc, uint16_t reg); +static uint8_t rum_cfg_bbp_init(struct rum_softc *sc); +static uint8_t rum_cfg_bbp_read(struct rum_softc *sc, uint8_t reg); +static void rum_cfg_amrr_start(struct rum_softc *sc); +static void rum_cfg_bbp_write(struct rum_softc *sc, uint8_t reg, uint8_t val); +static void rum_cfg_do_request(struct rum_softc *sc, struct usb2_device_request *req, void *data); +static void rum_cfg_eeprom_read(struct rum_softc *sc, uint16_t addr, void *buf, uint16_t len); +static void rum_cfg_load_microcode(struct rum_softc *sc, const uint8_t *ucode, uint16_t size); +static void rum_cfg_read_eeprom(struct rum_softc *sc); +static void rum_cfg_read_multi(struct rum_softc *sc, uint16_t reg, void *buf, uint16_t len); +static void rum_cfg_rf_write(struct rum_softc *sc, uint8_t reg, uint32_t val); +static void rum_cfg_set_bssid(struct rum_softc *sc, uint8_t *bssid); +static void rum_cfg_set_macaddr(struct rum_softc *sc, uint8_t *addr); +static void rum_cfg_write(struct rum_softc *sc, uint16_t reg, uint32_t val); +static void rum_cfg_write_multi(struct rum_softc *sc, uint16_t reg, void *buf, uint16_t len); +static void rum_end_of_commands(struct rum_softc *sc); +static void rum_init_cb(void *arg); +static void rum_start_cb(struct ifnet *ifp); +static void rum_watchdog(void *arg); +static uint8_t rum_get_rssi(struct rum_softc *sc, uint8_t raw); +static struct ieee80211vap *rum_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, int opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]); +static void rum_vap_delete(struct ieee80211vap *); +static struct ieee80211_node *rum_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]); +static void rum_newassoc(struct ieee80211_node *, int); +static void rum_cfg_disable_tsf_sync(struct rum_softc *sc); +static void rum_cfg_set_run(struct rum_softc *sc, struct rum_config_copy *cc); +static void rum_fill_write_queue(struct rum_softc *sc); +static void rum_tx_clean_queue(struct rum_softc *sc); +static void rum_tx_freem(struct mbuf *m); +static void rum_tx_mgt(struct rum_softc *sc, struct mbuf *m, struct ieee80211_node *ni); +static struct ieee80211vap *rum_get_vap(struct rum_softc *sc); +static void rum_tx_data(struct rum_softc *sc, struct mbuf *m, struct ieee80211_node *ni); +static void rum_tx_prot(struct rum_softc *sc, const struct mbuf *m, struct ieee80211_node *ni, uint8_t prot, uint16_t rate); +static void rum_tx_raw(struct rum_softc *sc, struct mbuf *m, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params); +static int rum_raw_xmit_cb(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params); +static void rum_setup_desc_and_tx(struct rum_softc *sc, struct mbuf *m, uint32_t flags, uint16_t xflags, uint16_t rate); +static int rum_newstate_cb(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg); +static void rum_update_mcast_cb(struct ifnet *ifp); +static void rum_update_promisc_cb(struct ifnet *ifp); + +/* various supported device vendors/products */ +static const struct usb2_device_id rum_devs[] = { + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_HWU54DM, 0)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_2, 0)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_3, 0)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_4, 0)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_WUG2700, 0)}, + {USB_VPI(USB_VENDOR_AMIT, USB_PRODUCT_AMIT_CGWLUSB2GO, 0)}, + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_RT2573_1, 0)}, + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_RT2573_2, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050A, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D9050V3, 0)}, + {USB_VPI(USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GC, 0)}, + {USB_VPI(USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GR, 0)}, + {USB_VPI(USB_VENDOR_CONCEPTRONIC2, USB_PRODUCT_CONCEPTRONIC2_C54RU2, 0)}, + {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_CGWLUSB2GL, 0)}, + {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_CGWLUSB2GPX, 0)}, + {USB_VPI(USB_VENDOR_DICKSMITH, USB_PRODUCT_DICKSMITH_CWD854F, 0)}, + {USB_VPI(USB_VENDOR_DICKSMITH, USB_PRODUCT_DICKSMITH_RT2573, 0)}, + {USB_VPI(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWLG122C1, 0)}, + {USB_VPI(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_WUA1340, 0)}, + {USB_VPI(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA111, 0)}, + {USB_VPI(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA110, 0)}, + {USB_VPI(USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWB01GS, 0)}, + {USB_VPI(USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWI05GS, 0)}, + {USB_VPI(USB_VENDOR_GIGASET, USB_PRODUCT_GIGASET_RT2573, 0)}, + {USB_VPI(USB_VENDOR_GOODWAY, USB_PRODUCT_GOODWAY_RT2573, 0)}, + {USB_VPI(USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254LB, 0)}, + {USB_VPI(USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254V2AP, 0)}, + {USB_VPI(USB_VENDOR_HUAWEI3COM, USB_PRODUCT_HUAWEI3COM_WUB320G, 0)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_G54HP, 0)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_SG54HP, 0)}, + {USB_VPI(USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_1, 0)}, + {USB_VPI(USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_2, 0)}, + {USB_VPI(USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_3, 0)}, + {USB_VPI(USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_4, 0)}, + {USB_VPI(USB_VENDOR_NOVATECH, USB_PRODUCT_NOVATECH_RT2573, 0)}, + {USB_VPI(USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54HP, 0)}, + {USB_VPI(USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54MINI2, 0)}, + {USB_VPI(USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUSMM, 0)}, + {USB_VPI(USB_VENDOR_QCOM, USB_PRODUCT_QCOM_RT2573, 0)}, + {USB_VPI(USB_VENDOR_QCOM, USB_PRODUCT_QCOM_RT2573_2, 0)}, + {USB_VPI(USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2573, 0)}, + {USB_VPI(USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2573_2, 0)}, + {USB_VPI(USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2671, 0)}, + {USB_VPI(USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_WL113R2, 0)}, + {USB_VPI(USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_WL172, 0)}, + {USB_VPI(USB_VENDOR_SPARKLAN, USB_PRODUCT_SPARKLAN_RT2573, 0)}, + {USB_VPI(USB_VENDOR_SURECOM, USB_PRODUCT_SURECOM_RT2573, 0)}, +}; + +struct rum_def_mac { + uint32_t reg; + uint32_t val; +}; + +static const struct rum_def_mac rum_def_mac[] = { + {RT2573_TXRX_CSR0, 0x025fb032}, + {RT2573_TXRX_CSR1, 0x9eaa9eaf}, + {RT2573_TXRX_CSR2, 0x8a8b8c8d}, + {RT2573_TXRX_CSR3, 0x00858687}, + {RT2573_TXRX_CSR7, 0x2e31353b}, + {RT2573_TXRX_CSR8, 0x2a2a2a2c}, + {RT2573_TXRX_CSR15, 0x0000000f}, + {RT2573_MAC_CSR6, 0x00000fff}, + {RT2573_MAC_CSR8, 0x016c030a}, + {RT2573_MAC_CSR10, 0x00000718}, + {RT2573_MAC_CSR12, 0x00000004}, + {RT2573_MAC_CSR13, 0x00007f00}, + {RT2573_SEC_CSR0, 0x00000000}, + {RT2573_SEC_CSR1, 0x00000000}, + {RT2573_SEC_CSR5, 0x00000000}, + {RT2573_PHY_CSR1, 0x000023b0}, + {RT2573_PHY_CSR5, 0x00040a06}, + {RT2573_PHY_CSR6, 0x00080606}, + {RT2573_PHY_CSR7, 0x00000408}, + {RT2573_AIFSN_CSR, 0x00002273}, + {RT2573_CWMIN_CSR, 0x00002344}, + {RT2573_CWMAX_CSR, 0x000034aa} +}; + +struct rum_def_bbp { + uint8_t reg; + uint8_t val; +}; + +static const struct rum_def_bbp rum_def_bbp[] = { + {3, 0x80}, + {15, 0x30}, + {17, 0x20}, + {21, 0xc8}, + {22, 0x38}, + {23, 0x06}, + {24, 0xfe}, + {25, 0x0a}, + {26, 0x0d}, + {32, 0x0b}, + {34, 0x12}, + {37, 0x07}, + {39, 0xf8}, + {41, 0x60}, + {53, 0x10}, + {54, 0x18}, + {60, 0x10}, + {61, 0x04}, + {62, 0x04}, + {75, 0xfe}, + {86, 0xfe}, + {88, 0xfe}, + {90, 0x0f}, + {99, 0x00}, + {102, 0x16}, + {107, 0x04} +}; + +struct rfprog { + uint8_t chan; + uint32_t r1, r2, r3, r4; +}; + +static const struct rfprog rum_rf5226[] = { + {1, 0x00b03, 0x001e1, 0x1a014, 0x30282}, + {2, 0x00b03, 0x001e1, 0x1a014, 0x30287}, + {3, 0x00b03, 0x001e2, 0x1a014, 0x30282}, + {4, 0x00b03, 0x001e2, 0x1a014, 0x30287}, + {5, 0x00b03, 0x001e3, 0x1a014, 0x30282}, + {6, 0x00b03, 0x001e3, 0x1a014, 0x30287}, + {7, 0x00b03, 0x001e4, 0x1a014, 0x30282}, + {8, 0x00b03, 0x001e4, 0x1a014, 0x30287}, + {9, 0x00b03, 0x001e5, 0x1a014, 0x30282}, + {10, 0x00b03, 0x001e5, 0x1a014, 0x30287}, + {11, 0x00b03, 0x001e6, 0x1a014, 0x30282}, + {12, 0x00b03, 0x001e6, 0x1a014, 0x30287}, + {13, 0x00b03, 0x001e7, 0x1a014, 0x30282}, + {14, 0x00b03, 0x001e8, 0x1a014, 0x30284}, + + {34, 0x00b03, 0x20266, 0x36014, 0x30282}, + {38, 0x00b03, 0x20267, 0x36014, 0x30284}, + {42, 0x00b03, 0x20268, 0x36014, 0x30286}, + {46, 0x00b03, 0x20269, 0x36014, 0x30288}, + + {36, 0x00b03, 0x00266, 0x26014, 0x30288}, + {40, 0x00b03, 0x00268, 0x26014, 0x30280}, + {44, 0x00b03, 0x00269, 0x26014, 0x30282}, + {48, 0x00b03, 0x0026a, 0x26014, 0x30284}, + {52, 0x00b03, 0x0026b, 0x26014, 0x30286}, + {56, 0x00b03, 0x0026c, 0x26014, 0x30288}, + {60, 0x00b03, 0x0026e, 0x26014, 0x30280}, + {64, 0x00b03, 0x0026f, 0x26014, 0x30282}, + + {100, 0x00b03, 0x0028a, 0x2e014, 0x30280}, + {104, 0x00b03, 0x0028b, 0x2e014, 0x30282}, + {108, 0x00b03, 0x0028c, 0x2e014, 0x30284}, + {112, 0x00b03, 0x0028d, 0x2e014, 0x30286}, + {116, 0x00b03, 0x0028e, 0x2e014, 0x30288}, + {120, 0x00b03, 0x002a0, 0x2e014, 0x30280}, + {124, 0x00b03, 0x002a1, 0x2e014, 0x30282}, + {128, 0x00b03, 0x002a2, 0x2e014, 0x30284}, + {132, 0x00b03, 0x002a3, 0x2e014, 0x30286}, + {136, 0x00b03, 0x002a4, 0x2e014, 0x30288}, + {140, 0x00b03, 0x002a6, 0x2e014, 0x30280}, + + {149, 0x00b03, 0x002a8, 0x2e014, 0x30287}, + {153, 0x00b03, 0x002a9, 0x2e014, 0x30289}, + {157, 0x00b03, 0x002ab, 0x2e014, 0x30281}, + {161, 0x00b03, 0x002ac, 0x2e014, 0x30283}, + {165, 0x00b03, 0x002ad, 0x2e014, 0x30285} +}; + +static const struct rfprog rum_rf5225[] = { + {1, 0x00b33, 0x011e1, 0x1a014, 0x30282}, + {2, 0x00b33, 0x011e1, 0x1a014, 0x30287}, + {3, 0x00b33, 0x011e2, 0x1a014, 0x30282}, + {4, 0x00b33, 0x011e2, 0x1a014, 0x30287}, + {5, 0x00b33, 0x011e3, 0x1a014, 0x30282}, + {6, 0x00b33, 0x011e3, 0x1a014, 0x30287}, + {7, 0x00b33, 0x011e4, 0x1a014, 0x30282}, + {8, 0x00b33, 0x011e4, 0x1a014, 0x30287}, + {9, 0x00b33, 0x011e5, 0x1a014, 0x30282}, + {10, 0x00b33, 0x011e5, 0x1a014, 0x30287}, + {11, 0x00b33, 0x011e6, 0x1a014, 0x30282}, + {12, 0x00b33, 0x011e6, 0x1a014, 0x30287}, + {13, 0x00b33, 0x011e7, 0x1a014, 0x30282}, + {14, 0x00b33, 0x011e8, 0x1a014, 0x30284}, + + {34, 0x00b33, 0x01266, 0x26014, 0x30282}, + {38, 0x00b33, 0x01267, 0x26014, 0x30284}, + {42, 0x00b33, 0x01268, 0x26014, 0x30286}, + {46, 0x00b33, 0x01269, 0x26014, 0x30288}, + + {36, 0x00b33, 0x01266, 0x26014, 0x30288}, + {40, 0x00b33, 0x01268, 0x26014, 0x30280}, + {44, 0x00b33, 0x01269, 0x26014, 0x30282}, + {48, 0x00b33, 0x0126a, 0x26014, 0x30284}, + {52, 0x00b33, 0x0126b, 0x26014, 0x30286}, + {56, 0x00b33, 0x0126c, 0x26014, 0x30288}, + {60, 0x00b33, 0x0126e, 0x26014, 0x30280}, + {64, 0x00b33, 0x0126f, 0x26014, 0x30282}, + + {100, 0x00b33, 0x0128a, 0x2e014, 0x30280}, + {104, 0x00b33, 0x0128b, 0x2e014, 0x30282}, + {108, 0x00b33, 0x0128c, 0x2e014, 0x30284}, + {112, 0x00b33, 0x0128d, 0x2e014, 0x30286}, + {116, 0x00b33, 0x0128e, 0x2e014, 0x30288}, + {120, 0x00b33, 0x012a0, 0x2e014, 0x30280}, + {124, 0x00b33, 0x012a1, 0x2e014, 0x30282}, + {128, 0x00b33, 0x012a2, 0x2e014, 0x30284}, + {132, 0x00b33, 0x012a3, 0x2e014, 0x30286}, + {136, 0x00b33, 0x012a4, 0x2e014, 0x30288}, + {140, 0x00b33, 0x012a6, 0x2e014, 0x30280}, + + {149, 0x00b33, 0x012a8, 0x2e014, 0x30287}, + {153, 0x00b33, 0x012a9, 0x2e014, 0x30289}, + {157, 0x00b33, 0x012ab, 0x2e014, 0x30281}, + {161, 0x00b33, 0x012ac, 0x2e014, 0x30283}, + {165, 0x00b33, 0x012ad, 0x2e014, 0x30285} +}; + +static const struct usb2_config rum_config[RUM_N_TRANSFER] = { + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = (MCLBYTES + RT2573_TX_DESC_SIZE + 8), + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &rum_bulk_write_callback, + .mh.timeout = 5000, /* ms */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = (MCLBYTES + RT2573_RX_DESC_SIZE), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &rum_bulk_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &rum_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &rum_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static devclass_t rum_devclass; + +static device_method_t rum_methods[] = { + DEVMETHOD(device_probe, rum_probe), + DEVMETHOD(device_attach, rum_attach), + DEVMETHOD(device_detach, rum_detach), + {0, 0} +}; + +static driver_t rum_driver = { + .name = "rum", + .methods = rum_methods, + .size = sizeof(struct rum_softc), +}; + +DRIVER_MODULE(rum, ushub, rum_driver, rum_devclass, NULL, 0); +MODULE_DEPEND(rum, usb2_wlan, 1, 1, 1); +MODULE_DEPEND(rum, usb2_core, 1, 1, 1); +MODULE_DEPEND(rum, wlan, 1, 1, 1); +MODULE_DEPEND(rum, wlan_amrr, 1, 1, 1); + +static int +rum_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != RT2573_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(rum_devs, sizeof(rum_devs), uaa)); +} + +static int +rum_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct rum_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "rum lock", MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + sc->sc_udev = uaa->device; + sc->sc_unit = device_get_unit(dev); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + iface_index = RT2573_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, rum_config, RUM_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "could not allocate USB transfers, " + "err=%s\n", usb2_errstr(error)); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + &rum_end_of_commands, + sizeof(struct usb2_config_td_cc), 24); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &rum_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + rum_watchdog(sc); + + return (0); /* success */ + +detach: + rum_detach(dev); + return (ENXIO); /* failure */ +} + +static int +rum_detach(device_t dev) +{ + struct rum_softc *sc = device_get_softc(dev); + struct ieee80211com *ic; + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + rum_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + ic = ifp->if_l2com; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, RUM_N_TRANSFER); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + bpfdetach(ifp); + ieee80211_ifdetach(ic); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +rum_cfg_do_request(struct rum_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + +repeat: + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTF("device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + + /* wait a little before next try */ + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 4)) { + goto error; + } + /* try until we are detached */ + goto repeat; + +error: + /* the device has been detached */ + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +static void +rum_cfg_eeprom_read(struct rum_softc *sc, uint16_t addr, void *buf, uint16_t len) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2573_READ_EEPROM; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + rum_cfg_do_request(sc, &req, buf); + return; +} + +static uint16_t +rum_cfg_eeprom_read_2(struct rum_softc *sc, uint16_t addr) +{ + uint16_t tmp; + + rum_cfg_eeprom_read(sc, addr, &tmp, sizeof(tmp)); + return (le16toh(tmp)); +} + +static uint32_t +rum_cfg_read(struct rum_softc *sc, uint16_t reg) +{ + uint32_t val; + + rum_cfg_read_multi(sc, reg, &val, sizeof(val)); + return (le32toh(val)); +} + +static void +rum_cfg_read_multi(struct rum_softc *sc, uint16_t reg, void *buf, uint16_t len) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2573_READ_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + rum_cfg_do_request(sc, &req, buf); + return; +} + +static void +rum_cfg_write(struct rum_softc *sc, uint16_t reg, uint32_t val) +{ + uint32_t tmp = htole32(val); + + rum_cfg_write_multi(sc, reg, &tmp, sizeof(tmp)); + return; +} + +static void +rum_cfg_write_multi(struct rum_softc *sc, uint16_t reg, void *buf, uint16_t len) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2573_WRITE_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + rum_cfg_do_request(sc, &req, buf); + return; +} + +static uint32_t +rum_cfg_bbp_disbusy(struct rum_softc *sc) +{ + uint32_t tmp; + uint8_t to; + + for (to = 0;; to++) { + if (to < 100) { + tmp = rum_cfg_read(sc, RT2573_PHY_CSR3); + + if ((tmp & RT2573_BBP_BUSY) == 0) { + return (tmp); + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + break; + } + } else { + break; + } + } + DPRINTF("could not disbusy BBP\n"); + return (RT2573_BBP_BUSY); /* failure */ +} + +static void +rum_cfg_bbp_write(struct rum_softc *sc, uint8_t reg, uint8_t val) +{ + uint32_t tmp; + + if (rum_cfg_bbp_disbusy(sc) & RT2573_BBP_BUSY) { + return; + } + tmp = RT2573_BBP_BUSY | ((reg & 0x7f) << 8) | val; + rum_cfg_write(sc, RT2573_PHY_CSR3, tmp); + return; +} + +static uint8_t +rum_cfg_bbp_read(struct rum_softc *sc, uint8_t reg) +{ + uint32_t val; + + if (rum_cfg_bbp_disbusy(sc) & RT2573_BBP_BUSY) { + return (0); + } + val = RT2573_BBP_BUSY | RT2573_BBP_READ | (reg << 8); + rum_cfg_write(sc, RT2573_PHY_CSR3, val); + + val = rum_cfg_bbp_disbusy(sc); + return (val & 0xff); +} + +static void +rum_cfg_rf_write(struct rum_softc *sc, uint8_t reg, uint32_t val) +{ + uint32_t tmp; + uint8_t to; + + reg &= 3; + + for (to = 0;; to++) { + if (to < 100) { + tmp = rum_cfg_read(sc, RT2573_PHY_CSR4); + if (!(tmp & RT2573_RF_BUSY)) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + return; + } + } else { + DPRINTF("could not write to RF\n"); + return; + } + } + + tmp = RT2573_RF_BUSY | RT2573_RF_20BIT | ((val & 0xfffff) << 2) | reg; + rum_cfg_write(sc, RT2573_PHY_CSR4, tmp); + + DPRINTFN(16, "RF R[%u] <- 0x%05x\n", reg, val & 0xfffff); + return; +} + +static void +rum_cfg_first_time_setup(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ieee80211com *ic; + struct ifnet *ifp; + uint32_t tmp; + uint16_t i; + uint8_t bands; + + /* setup RX tap header */ + sc->sc_rxtap_len = sizeof(sc->sc_rxtap); + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(RT2573_RX_RADIOTAP_PRESENT); + + /* setup TX tap header */ + sc->sc_txtap_len = sizeof(sc->sc_txtap); + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(RT2573_TX_RADIOTAP_PRESENT); + + /* retrieve RT2573 rev. no */ + for (i = 0; i < 100; i++) { + + tmp = rum_cfg_read(sc, RT2573_MAC_CSR0); + if (tmp != 0) { + break; + } + /* wait a little */ + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + /* device detached */ + goto done; + } + } + + if (tmp == 0) { + DPRINTF("chip is maybe not ready\n"); + } + /* retrieve MAC address and various other things from EEPROM */ + rum_cfg_read_eeprom(sc); + + printf("%s: MAC/BBP RT2573 (rev 0x%05x), RF %s\n", + sc->sc_name, tmp, rum_get_rf(sc->sc_rf_rev)); + + rum_cfg_load_microcode(sc, rt2573_ucode, sizeof(rt2573_ucode)); + + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_IEEE80211); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + DPRINTFN(0, "could not if_alloc()!\n"); + goto done; + } + sc->sc_evilhack = ifp; + sc->sc_ifp = ifp; + ic = ifp->if_l2com; + + ifp->if_softc = sc; + if_initname(ifp, "rum", sc->sc_unit); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = &rum_init_cb; + ifp->if_ioctl = &rum_ioctl_cb; + ifp->if_start = &rum_start_cb; + ifp->if_watchdog = NULL; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + bcopy(sc->sc_myaddr, ic->ic_myaddr, sizeof(ic->ic_myaddr)); + + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode supported */ + | IEEE80211_C_IBSS /* IBSS mode supported */ + | IEEE80211_C_MONITOR /* monitor mode supported */ + | IEEE80211_C_HOSTAP /* HostAp mode supported */ + | IEEE80211_C_TXPMGT /* tx power management */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* bg scanning supported */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + if ((sc->sc_rf_rev == RT2573_RF_5225) || + (sc->sc_rf_rev == RT2573_RF_5226)) { + + struct ieee80211_channel *c; + + /* set supported .11a channels */ + for (i = 34; i <= 46; i += 4) { + c = ic->ic_channels + (ic->ic_nchans++); + c->ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_5GHZ); + c->ic_flags = IEEE80211_CHAN_A; + c->ic_ieee = i; + } + for (i = 36; i <= 64; i += 4) { + c = ic->ic_channels + (ic->ic_nchans++); + c->ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_5GHZ); + c->ic_flags = IEEE80211_CHAN_A; + c->ic_ieee = i; + } + for (i = 100; i <= 140; i += 4) { + c = ic->ic_channels + (ic->ic_nchans++); + c->ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_5GHZ); + c->ic_flags = IEEE80211_CHAN_A; + c->ic_ieee = i; + } + for (i = 149; i <= 165; i += 4) { + c = ic->ic_channels + (ic->ic_nchans++); + c->ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_5GHZ); + c->ic_flags = IEEE80211_CHAN_A; + c->ic_ieee = i; + } + } + mtx_unlock(&sc->sc_mtx); + + ieee80211_ifattach(ic); + + mtx_lock(&sc->sc_mtx); + + ic->ic_newassoc = &rum_newassoc; + ic->ic_raw_xmit = &rum_raw_xmit_cb; + ic->ic_node_alloc = &rum_node_alloc; + ic->ic_update_mcast = &rum_update_mcast_cb; + ic->ic_update_promisc = &rum_update_promisc_cb; + ic->ic_scan_start = &rum_scan_start_cb; + ic->ic_scan_end = &rum_scan_end_cb; + ic->ic_set_channel = &rum_set_channel_cb; + ic->ic_vap_create = &rum_vap_create; + ic->ic_vap_delete = &rum_vap_delete; + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); + + mtx_unlock(&sc->sc_mtx); + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof(struct ieee80211_frame) + sizeof(sc->sc_txtap)); + + if (bootverbose) { + ieee80211_announce(ic); + } + mtx_lock(&sc->sc_mtx); +done: + return; +} + +static void +rum_end_of_commands(struct rum_softc *sc) +{ + sc->sc_flags &= ~RUM_FLAG_WAIT_COMMAND; + + /* start write transfer, if not started */ + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +rum_config_copy_chan(struct rum_config_copy_chan *cc, + struct ieee80211com *ic, struct ieee80211_channel *c) +{ + if (!c) + return; + cc->chan_to_ieee = + ieee80211_chan2ieee(ic, c); + if (c != IEEE80211_CHAN_ANYC) { + cc->chan_to_mode = + ieee80211_chan2mode(c); + if (IEEE80211_IS_CHAN_B(c)) + cc->chan_is_b = 1; + if (IEEE80211_IS_CHAN_A(c)) + cc->chan_is_a = 1; + if (IEEE80211_IS_CHAN_2GHZ(c)) + cc->chan_is_2ghz = 1; + if (IEEE80211_IS_CHAN_5GHZ(c)) + cc->chan_is_5ghz = 1; + if (IEEE80211_IS_CHAN_ANYG(c)) + cc->chan_is_g = 1; + } + return; +} + +static void +rum_config_copy(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp; + struct ieee80211com *ic; + struct ieee80211_node *ni; + struct ieee80211vap *vap; + const struct ieee80211_txparam *tp; + + bzero(cc, sizeof(*cc)); + + ifp = sc->sc_ifp; + if (ifp) { + cc->if_flags = ifp->if_flags; + bcopy(ifp->if_broadcastaddr, cc->if_broadcastaddr, + sizeof(cc->if_broadcastaddr)); + + ic = ifp->if_l2com; + if (ic) { + rum_config_copy_chan(&cc->ic_curchan, ic, ic->ic_curchan); + rum_config_copy_chan(&cc->ic_bsschan, ic, ic->ic_bsschan); + vap = TAILQ_FIRST(&ic->ic_vaps); + if (vap) { + ni = vap->iv_bss; + if (ni) { + cc->iv_bss.ni_intval = ni->ni_intval; + bcopy(ni->ni_bssid, cc->iv_bss.ni_bssid, + sizeof(cc->iv_bss.ni_bssid)); + } + tp = vap->iv_txparms + cc->ic_bsschan.chan_to_mode; + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) { + cc->iv_bss.fixed_rate_none = 1; + } + } + cc->ic_opmode = ic->ic_opmode; + cc->ic_flags = ic->ic_flags; + cc->ic_txpowlimit = ic->ic_txpowlimit; + cc->ic_curmode = ic->ic_curmode; + + bcopy(ic->ic_myaddr, cc->ic_myaddr, + sizeof(cc->ic_myaddr)); + } + } + sc->sc_flags |= RUM_FLAG_WAIT_COMMAND; + return; +} + +static const char * +rum_get_rf(uint32_t rev) +{ + ; /* indent fix */ + switch (rev) { + case RT2573_RF_2527: + return "RT2527 (MIMO XR)"; + case RT2573_RF_2528: + return "RT2528"; + case RT2573_RF_5225: + return "RT5225 (MIMO XR)"; + case RT2573_RF_5226: + return "RT5226"; + default: + return "unknown"; + } +} + +static void +rum_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct rum_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_node *ni; + + struct mbuf *m = NULL; + uint32_t flags; + uint32_t max_len; + uint8_t rssi = 0; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(15, "rx done, actlen=%d\n", xfer->actlen); + + if (xfer->actlen < (RT2573_RX_DESC_SIZE + IEEE80211_MIN_LEN)) { + DPRINTF("too short transfer, " + "%d bytes\n", xfer->actlen); + ifp->if_ierrors++; + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, + &sc->sc_rx_desc, RT2573_RX_DESC_SIZE); + + flags = le32toh(sc->sc_rx_desc.flags); + + if (flags & RT2573_RX_CRC_ERROR) { + /* + * This should not happen since we did not + * request to receive those frames when we + * filled RAL_TXRX_CSR2: + */ + DPRINTFN(6, "PHY or CRC error\n"); + ifp->if_ierrors++; + goto tr_setup; + } + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + + if (m == NULL) { + DPRINTF("could not allocate mbuf\n"); + ifp->if_ierrors++; + goto tr_setup; + } + max_len = (xfer->actlen - RT2573_RX_DESC_SIZE); + + usb2_copy_out(xfer->frbuffers, RT2573_RX_DESC_SIZE, + m->m_data, max_len); + + /* finalize mbuf */ + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = (flags >> 16) & 0xfff; + + if (m->m_len > max_len) { + DPRINTF("invalid length in RX " + "descriptor, %u bytes, received %u bytes\n", + m->m_len, max_len); + ifp->if_ierrors++; + m_freem(m); + m = NULL; + goto tr_setup; + } + rssi = rum_get_rssi(sc, sc->sc_rx_desc.rssi); + + DPRINTF("real length=%d bytes, rssi=%d\n", m->m_len, rssi); + + if (bpf_peers_present(ifp->if_bpf)) { + struct rum_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = IEEE80211_RADIOTAP_F_FCS; + tap->wr_rate = ieee80211_plcp2rate(sc->sc_rx_desc.rate, + (sc->sc_rx_desc.flags & htole32(RT2573_RX_OFDM)) ? + IEEE80211_T_OFDM : IEEE80211_T_CCK); + tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_antenna = sc->sc_rx_ant; + tap->wr_antsignal = rssi; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_flags & RUM_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "ieee80211_input" here, and not some lines up! + */ + if (m) { + mtx_unlock(&sc->sc_mtx); + + ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + if (ieee80211_input(ni, m, rssi, RT2573_NOISE_FLOOR, 0)) { + /* ignore */ + } + /* node is no longer needed */ + ieee80211_free_node(ni); + } else { + if (ieee80211_input_all(ic, m, rssi, RT2573_NOISE_FLOOR, 0)) { + /* ignore */ + } + } + + mtx_lock(&sc->sc_mtx); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= RUM_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +rum_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct rum_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~RUM_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static uint8_t +rum_plcp_signal(uint16_t rate) +{ + ; /* indent fix */ + switch (rate) { + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: + return (0x0); + case 4: + return (0x1); + case 11: + return (0x2); + case 22: + return (0x3); + + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: + return (0xb); + case 18: + return (0xf); + case 24: + return (0xa); + case 36: + return (0xe); + case 48: + return (0x9); + case 72: + return (0xd); + case 96: + return (0x8); + case 108: + return (0xc); + + /* XXX unsupported/unknown rate */ + default: + return (0xff); + } +} + +/* + * We assume that "m->m_pkthdr.rcvif" is pointing to the "ni" that + * should be freed, when "rum_setup_desc_and_tx" is called. + */ + +static void +rum_setup_desc_and_tx(struct rum_softc *sc, struct mbuf *m, uint32_t flags, + uint16_t xflags, uint16_t rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct mbuf *mm; + enum ieee80211_phytype phytype; + uint16_t plcp_length; + uint16_t len; + uint8_t remainder; + uint8_t is_beacon; + + if (xflags & RT2573_TX_BEACON) { + xflags &= ~RT2573_TX_BEACON; + is_beacon = 1; + } else { + is_beacon = 0; + } + + if (sc->sc_tx_queue.ifq_len >= IFQ_MAXLEN) { + /* free packet */ + rum_tx_freem(m); + ifp->if_oerrors++; + return; + } + if (!((sc->sc_flags & RUM_FLAG_LL_READY) && + (sc->sc_flags & RUM_FLAG_HL_READY))) { + /* free packet */ + rum_tx_freem(m); + ifp->if_oerrors++; + return; + } + if (rate < 2) { + DPRINTF("rate < 2!\n"); + + /* avoid division by zero */ + rate = 2; + } + ic->ic_lastdata = ticks; + if (bpf_peers_present(ifp->if_bpf)) { + struct rum_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wt_antenna = sc->sc_tx_ant; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m); + } + len = m->m_pkthdr.len; + + flags |= RT2573_TX_VALID; + flags |= (len << 16); + + sc->sc_tx_desc.flags = htole32(flags); + sc->sc_tx_desc.xflags = htole16(xflags); + + sc->sc_tx_desc.wme = htole16(RT2573_QID(0) | RT2573_AIFSN(2) | + RT2573_LOGCWMIN(4) | RT2573_LOGCWMAX(10)); + + /* setup PLCP fields */ + sc->sc_tx_desc.plcp_signal = rum_plcp_signal(rate); + sc->sc_tx_desc.plcp_service = 4; + + len += IEEE80211_CRC_LEN; + + phytype = ieee80211_rate2phytype(sc->sc_rates, rate); + + if (phytype == IEEE80211_T_OFDM) { + sc->sc_tx_desc.flags |= htole32(RT2573_TX_OFDM); + + plcp_length = (len & 0xfff); + sc->sc_tx_desc.plcp_length_hi = plcp_length >> 6; + sc->sc_tx_desc.plcp_length_lo = plcp_length & 0x3f; + } else { + plcp_length = ((16 * len) + rate - 1) / rate; + if (rate == 22) { + remainder = (16 * len) % 22; + if ((remainder != 0) && (remainder < 7)) { + sc->sc_tx_desc.plcp_service |= + RT2573_PLCP_LENGEXT; + } + } + sc->sc_tx_desc.plcp_length_hi = plcp_length >> 8; + sc->sc_tx_desc.plcp_length_lo = plcp_length & 0xff; + + if ((rate != 2) && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) { + sc->sc_tx_desc.plcp_signal |= 0x08; + } + } + + if (sizeof(sc->sc_tx_desc) > MHLEN) { + DPRINTF("No room for header structure!\n"); + rum_tx_freem(m); + return; + } + mm = m_gethdr(M_NOWAIT, MT_DATA); + if (mm == NULL) { + DPRINTF("Could not allocate header mbuf!\n"); + rum_tx_freem(m); + return; + } + bcopy(&sc->sc_tx_desc, mm->m_data, sizeof(sc->sc_tx_desc)); + mm->m_len = sizeof(sc->sc_tx_desc); + mm->m_next = m; + mm->m_pkthdr.len = mm->m_len + m->m_pkthdr.len; + mm->m_pkthdr.rcvif = NULL; + + if (is_beacon) { + + if (mm->m_pkthdr.len > sizeof(sc->sc_beacon_buf)) { + DPRINTFN(0, "Truncating beacon" + ", %u bytes!\n", mm->m_pkthdr.len); + mm->m_pkthdr.len = sizeof(sc->sc_beacon_buf); + } + m_copydata(mm, 0, mm->m_pkthdr.len, sc->sc_beacon_buf); + + /* copy the first 24 bytes of Tx descriptor into NIC memory */ + rum_cfg_write_multi(sc, RT2573_HW_BEACON_BASE0, + sc->sc_beacon_buf, mm->m_pkthdr.len); + rum_tx_freem(mm); + return; + } + /* start write transfer, if not started */ + _IF_ENQUEUE(&sc->sc_tx_queue, mm); + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +rum_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct rum_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint16_t temp_len; + uint8_t align; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + + ifp->if_opackets++; + + case USB_ST_SETUP: + if (sc->sc_flags & RUM_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + break; + } + if (sc->sc_flags & RUM_FLAG_WAIT_COMMAND) { + /* + * don't send anything while a command is pending ! + */ + break; + } + rum_fill_write_queue(sc); + + _IF_DEQUEUE(&sc->sc_tx_queue, m); + + if (m) { + + if (m->m_pkthdr.len > (MCLBYTES + RT2573_TX_DESC_SIZE)) { + DPRINTFN(0, "data overflow, %u bytes\n", + m->m_pkthdr.len); + m->m_pkthdr.len = (MCLBYTES + RT2573_TX_DESC_SIZE); + } + usb2_m_copy_in(xfer->frbuffers, 0, + m, 0, m->m_pkthdr.len); + + /* compute transfer length */ + temp_len = m->m_pkthdr.len; + + /* make transfer length 32-bit aligned */ + align = (-(temp_len)) & 3; + + /* check if we need to add four extra bytes */ + if (((temp_len + align) % 64) == 0) { + align += 4; + } + /* check if we need to align length */ + if (align != 0) { + /* zero the extra bytes */ + usb2_bzero(xfer->frbuffers, temp_len, align); + temp_len += align; + } + DPRINTFN(11, "sending frame len=%u ferlen=%u\n", + m->m_pkthdr.len, temp_len); + + xfer->frlengths[0] = temp_len; + usb2_start_hardware(xfer); + + /* free mbuf and node */ + rum_tx_freem(m); + + } + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= RUM_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + ifp->if_oerrors++; + break; + } + return; +} + +static void +rum_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct rum_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~RUM_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +rum_watchdog(void *arg) +{ + struct rum_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + if (sc->sc_amrr_timer) { + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, + &rum_cfg_amrr_timeout, 0, 0); + } + usb2_callout_reset(&sc->sc_watchdog, + hz, &rum_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +rum_init_cb(void *arg) +{ + struct rum_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &rum_cfg_pre_init, + &rum_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static int +rum_ioctl_cb(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct rum_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + int error; + + switch (cmd) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + usb2_config_td_queue_command + (&sc->sc_config_td, &rum_cfg_pre_init, + &rum_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &rum_cfg_pre_stop, + &rum_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + error = 0; + break; + + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + error = ifmedia_ioctl(ifp, (void *)data, &ic->ic_media, cmd); + break; + + default: + error = ether_ioctl(ifp, cmd, data); + } + return (error); +} + +static void +rum_start_cb(struct ifnet *ifp) +{ + struct rum_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + /* start write transfer, if not started */ + usb2_transfer_start(sc->sc_xfer[0]); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +rum_cfg_newstate(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct rum_vap *uvp = RUM_VAP(vap); + enum ieee80211_state ostate; + enum ieee80211_state nstate; + int arg; + + ostate = vap->iv_state; + nstate = sc->sc_ns_state; + arg = sc->sc_ns_arg; + + if (ostate == IEEE80211_S_INIT) { + /* We are leaving INIT. TSF sync should be off. */ + rum_cfg_disable_tsf_sync(sc); + } + switch (nstate) { + case IEEE80211_S_INIT: + break; + + case IEEE80211_S_RUN: + rum_cfg_set_run(sc, cc); + break; + + default: + break; + } + + mtx_unlock(&sc->sc_mtx); + IEEE80211_LOCK(ic); + uvp->newstate(vap, nstate, arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, nstate, arg); + IEEE80211_UNLOCK(ic); + mtx_lock(&sc->sc_mtx); + return; +} + +static int +rum_newstate_cb(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct rum_vap *uvp = RUM_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct rum_softc *sc = ic->ic_ifp->if_softc; + + DPRINTF("setting new state: %d\n", nstate); + + /* Special case - cannot defer this call and cannot block ! */ + if (nstate == IEEE80211_S_INIT) { + /* stop timers */ + mtx_lock(&sc->sc_mtx); + sc->sc_amrr_timer = 0; + mtx_unlock(&sc->sc_mtx); + return (uvp->newstate(vap, nstate, arg)); + } + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + mtx_unlock(&sc->sc_mtx); + return (0); /* nothing to do */ + } + /* store next state */ + sc->sc_ns_state = nstate; + sc->sc_ns_arg = arg; + + /* stop timers */ + sc->sc_amrr_timer = 0; + + /* + * USB configuration can only be done from the USB configuration + * thread: + */ + usb2_config_td_queue_command + (&sc->sc_config_td, &rum_config_copy, + &rum_cfg_newstate, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return (EINPROGRESS); +} + +static void +rum_std_command(struct ieee80211com *ic, usb2_config_td_command_t *func) +{ + struct rum_softc *sc = ic->ic_ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); + + usb2_config_td_queue_command + (&sc->sc_config_td, &rum_config_copy, func, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +rum_scan_start_cb(struct ieee80211com *ic) +{ + rum_std_command(ic, &rum_cfg_scan_start); + return; +} + +static void +rum_scan_end_cb(struct ieee80211com *ic) +{ + rum_std_command(ic, &rum_cfg_scan_end); + return; +} + +static void +rum_set_channel_cb(struct ieee80211com *ic) +{ + rum_std_command(ic, &rum_cfg_set_chan); + return; +} + +static void +rum_cfg_scan_start(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* abort TSF synchronization */ + rum_cfg_disable_tsf_sync(sc); + rum_cfg_set_bssid(sc, cc->if_broadcastaddr); + return; +} + +static void +rum_cfg_scan_end(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* enable TSF synchronization */ + rum_cfg_enable_tsf_sync(sc, cc, 0); + rum_cfg_set_bssid(sc, cc->iv_bss.ni_bssid); + return; +} + +/* + * Reprogram MAC/BBP to switch to a new band. Values taken from the reference + * driver. + */ +static void +rum_cfg_select_band(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t tmp; + uint8_t bbp17, bbp35, bbp96, bbp97, bbp98, bbp104; + + /* update all BBP registers that depend on the band */ + bbp17 = 0x20; + bbp96 = 0x48; + bbp104 = 0x2c; + bbp35 = 0x50; + bbp97 = 0x48; + bbp98 = 0x48; + + if (cc->ic_curchan.chan_is_5ghz) { + bbp17 += 0x08; + bbp96 += 0x10; + bbp104 += 0x0c; + bbp35 += 0x10; + bbp97 += 0x10; + bbp98 += 0x10; + } + if ((cc->ic_curchan.chan_is_2ghz && sc->sc_ext_2ghz_lna) || + (cc->ic_curchan.chan_is_5ghz && sc->sc_ext_5ghz_lna)) { + bbp17 += 0x10; + bbp96 += 0x10; + bbp104 += 0x10; + } + sc->sc_bbp17 = bbp17; + rum_cfg_bbp_write(sc, 17, bbp17); + rum_cfg_bbp_write(sc, 96, bbp96); + rum_cfg_bbp_write(sc, 104, bbp104); + + if ((cc->ic_curchan.chan_is_2ghz && sc->sc_ext_2ghz_lna) || + (cc->ic_curchan.chan_is_5ghz && sc->sc_ext_5ghz_lna)) { + rum_cfg_bbp_write(sc, 75, 0x80); + rum_cfg_bbp_write(sc, 86, 0x80); + rum_cfg_bbp_write(sc, 88, 0x80); + } + rum_cfg_bbp_write(sc, 35, bbp35); + rum_cfg_bbp_write(sc, 97, bbp97); + rum_cfg_bbp_write(sc, 98, bbp98); + + tmp = rum_cfg_read(sc, RT2573_PHY_CSR0); + tmp &= ~(RT2573_PA_PE_2GHZ | RT2573_PA_PE_5GHZ); + if (cc->ic_curchan.chan_is_2ghz) + tmp |= RT2573_PA_PE_2GHZ; + else + tmp |= RT2573_PA_PE_5GHZ; + rum_cfg_write(sc, RT2573_PHY_CSR0, tmp); + + /* 802.11a uses a 16 microseconds short interframe space */ + sc->sc_sifs = cc->ic_curchan.chan_is_5ghz ? 16 : 10; + + return; +} + +static void +rum_cfg_set_chan(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + enum { + N_RF5225 = (sizeof(rum_rf5225) / sizeof(rum_rf5225[0]))}; + const struct rfprog *rfprog; + uint32_t chan; + uint16_t i; + uint8_t bbp3; + uint8_t bbp94 = RT2573_BBPR94_DEFAULT; + int8_t power; + + chan = cc->ic_curchan.chan_to_ieee; + + if ((chan == 0) || + (chan == IEEE80211_CHAN_ANY)) { + /* nothing to do */ + return; + } + if (chan == sc->sc_last_chan) { + return; + } + sc->sc_last_chan = chan; + + /* select the appropriate RF settings based on what EEPROM says */ + rfprog = ((sc->sc_rf_rev == RT2573_RF_5225) || + (sc->sc_rf_rev == RT2573_RF_2527)) ? rum_rf5225 : rum_rf5226; + + /* find the settings for this channel */ + for (i = 0;; i++) { + if (i == (N_RF5225 - 1)) + break; + if (rfprog[i].chan == chan) + break; + } + + DPRINTF("chan=%d, i=%d\n", chan, i); + + power = sc->sc_txpow[i]; + if (power < 0) { + bbp94 += power; + power = 0; + } else if (power > 31) { + bbp94 += power - 31; + power = 31; + } + /* + * If we are switching from the 2GHz band to the 5GHz band or + * vice-versa, BBP registers need to be reprogrammed. + */ + rum_cfg_select_band(sc, cc, 0); + rum_cfg_select_antenna(sc, cc, 0); + + rum_cfg_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_cfg_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_cfg_rf_write(sc, RT2573_RF3, rfprog[i].r3 | (power << 7)); + rum_cfg_rf_write(sc, RT2573_RF4, rfprog[i].r4 | (sc->sc_rffreq << 10)); + + rum_cfg_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_cfg_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_cfg_rf_write(sc, RT2573_RF3, rfprog[i].r3 | (power << 7) | 1); + rum_cfg_rf_write(sc, RT2573_RF4, rfprog[i].r4 | (sc->sc_rffreq << 10)); + + rum_cfg_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_cfg_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_cfg_rf_write(sc, RT2573_RF3, rfprog[i].r3 | (power << 7)); + rum_cfg_rf_write(sc, RT2573_RF4, rfprog[i].r4 | (sc->sc_rffreq << 10)); + + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + return; + } + /* enable smart mode for MIMO-capable RFs */ + bbp3 = rum_cfg_bbp_read(sc, 3); + + if ((sc->sc_rf_rev == RT2573_RF_5225) || + (sc->sc_rf_rev == RT2573_RF_2527)) + bbp3 &= ~RT2573_SMART_MODE; + else + bbp3 |= RT2573_SMART_MODE; + + rum_cfg_bbp_write(sc, 3, bbp3); + + rum_cfg_bbp_write(sc, 94, bbp94); + + /* update basic rate set */ + + if (cc->ic_curchan.chan_is_b) { + /* 11b basic rates: 1, 2Mbps */ + rum_cfg_write(sc, RT2573_TXRX_CSR5, 0x3); + } else if (cc->ic_curchan.chan_is_a) { + /* 11a basic rates: 6, 12, 24Mbps */ + rum_cfg_write(sc, RT2573_TXRX_CSR5, 0x150); + } else { + /* 11b/g basic rates: 1, 2, 5.5, 11Mbps */ + rum_cfg_write(sc, RT2573_TXRX_CSR5, 0xf); + } + + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + return; + } + return; +} + +static void +rum_cfg_set_run(struct rum_softc *sc, + struct usb2_config_td_cc *cc) +{ + + if (cc->ic_opmode != IEEE80211_M_MONITOR) { + rum_cfg_update_slot(sc, cc, 0); + rum_cfg_enable_mrr(sc, cc, 0); + rum_cfg_set_txpreamble(sc, cc, 0); + + /* update basic rate set */ + + if (cc->ic_bsschan.chan_is_5ghz) { + /* 11a basic rates: 6, 12, 24Mbps */ + rum_cfg_write(sc, RT2573_TXRX_CSR5, 0x150); + } else if (cc->ic_bsschan.chan_is_g) { + /* 11b/g basic rates: 1, 2, 5.5, 11Mbps */ + rum_cfg_write(sc, RT2573_TXRX_CSR5, 0xf); + } else { + /* 11b basic rates: 1, 2Mbps */ + rum_cfg_write(sc, RT2573_TXRX_CSR5, 0x3); + } + rum_cfg_set_bssid(sc, cc->iv_bss.ni_bssid); + } + if ((cc->ic_opmode == IEEE80211_M_HOSTAP) || + (cc->ic_opmode == IEEE80211_M_IBSS)) { + rum_cfg_prepare_beacon(sc, cc, 0); + } + if (cc->ic_opmode != IEEE80211_M_MONITOR) { + rum_cfg_enable_tsf_sync(sc, cc, 0); + } + if (cc->iv_bss.fixed_rate_none) { + /* enable automatic rate adaptation */ + rum_cfg_amrr_start(sc); + } + return; +} + +static void +rum_cfg_enable_tsf_sync(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t tmp; + + if (cc->ic_opmode != IEEE80211_M_STA) { + /* + * Change default 16ms TBTT adjustment to 8ms. + * Must be done before enabling beacon generation. + */ + rum_cfg_write(sc, RT2573_TXRX_CSR10, (1 << 12) | 8); + } + tmp = rum_cfg_read(sc, RT2573_TXRX_CSR9) & 0xff000000; + + /* set beacon interval (in 1/16ms unit) */ + tmp |= cc->iv_bss.ni_intval * 16; + + tmp |= RT2573_TSF_TICKING | RT2573_ENABLE_TBTT; + if (cc->ic_opmode == IEEE80211_M_STA) + tmp |= RT2573_TSF_MODE(1); + else + tmp |= RT2573_TSF_MODE(2) | RT2573_GENERATE_BEACON; + + rum_cfg_write(sc, RT2573_TXRX_CSR9, tmp); + + return; +} + +static void +rum_cfg_disable_tsf_sync(struct rum_softc *sc) +{ + uint32_t tmp; + + /* abort TSF synchronization */ + tmp = rum_cfg_read(sc, RT2573_TXRX_CSR9); + rum_cfg_write(sc, RT2573_TXRX_CSR9, tmp & ~0x00ffffff); + return; +} + +/* + * Enable multi-rate retries for frames sent at OFDM rates. + * In 802.11b/g mode, allow fallback to CCK rates. + */ +static void +rum_cfg_enable_mrr(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t tmp; + + tmp = rum_cfg_read(sc, RT2573_TXRX_CSR4); + + if (cc->ic_curchan.chan_is_5ghz) + tmp &= ~RT2573_MRR_CCK_FALLBACK; + else + tmp |= RT2573_MRR_CCK_FALLBACK; + + tmp |= RT2573_MRR_ENABLED; + + rum_cfg_write(sc, RT2573_TXRX_CSR4, tmp); + + return; +} + +static void +rum_cfg_update_slot(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t tmp; + uint8_t slottime; + + slottime = (cc->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20; + + tmp = rum_cfg_read(sc, RT2573_MAC_CSR9); + tmp = (tmp & ~0xff) | slottime; + rum_cfg_write(sc, RT2573_MAC_CSR9, tmp); + + DPRINTF("setting slot time to %u us\n", slottime); + + return; +} + +static void +rum_cfg_set_txpreamble(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t tmp; + + tmp = rum_cfg_read(sc, RT2573_TXRX_CSR4); + + if (cc->ic_flags & IEEE80211_F_SHPREAMBLE) + tmp |= RT2573_SHORT_PREAMBLE; + else + tmp &= ~RT2573_SHORT_PREAMBLE; + + rum_cfg_write(sc, RT2573_TXRX_CSR4, tmp); + + return; +} + +static void +rum_cfg_set_bssid(struct rum_softc *sc, uint8_t *bssid) +{ + uint32_t tmp; + + tmp = bssid[0] | (bssid[1] << 8) | (bssid[2] << 16) | (bssid[3] << 24); + rum_cfg_write(sc, RT2573_MAC_CSR4, tmp); + + tmp = (bssid[4]) | (bssid[5] << 8) | (RT2573_ONE_BSSID << 16); + rum_cfg_write(sc, RT2573_MAC_CSR5, tmp); + + return; +} + +static void +rum_cfg_set_macaddr(struct rum_softc *sc, uint8_t *addr) +{ + uint32_t tmp; + + tmp = addr[0] | (addr[1] << 8) | (addr[2] << 16) | (addr[3] << 24); + rum_cfg_write(sc, RT2573_MAC_CSR2, tmp); + + tmp = addr[4] | (addr[5] << 8) | (0xff << 16); + rum_cfg_write(sc, RT2573_MAC_CSR3, tmp); + + return; +} + +static void +rum_cfg_update_promisc(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t tmp; + + tmp = rum_cfg_read(sc, RT2573_TXRX_CSR0); + + if (cc->if_flags & IFF_PROMISC) + tmp &= ~RT2573_DROP_NOT_TO_ME; + else + tmp |= RT2573_DROP_NOT_TO_ME; + + rum_cfg_write(sc, RT2573_TXRX_CSR0, tmp); + + DPRINTF("%s promiscuous mode\n", + (cc->if_flags & IFF_PROMISC) ? + "entering" : "leaving"); + return; +} + +static void +rum_cfg_select_antenna(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t tmp; + uint8_t bbp3; + uint8_t bbp4; + uint8_t bbp77; + uint8_t rx_ant; + uint8_t is_5ghz; + + bbp3 = rum_cfg_bbp_read(sc, 3); + bbp4 = rum_cfg_bbp_read(sc, 4); + bbp77 = rum_cfg_bbp_read(sc, 77); + + bbp3 &= ~0x01; + bbp4 &= ~0x23; + + rx_ant = sc->sc_rx_ant; + is_5ghz = cc->ic_curchan.chan_is_5ghz; + + switch (sc->sc_rf_rev) { + case RT2573_RF_5226: + case RT2573_RF_5225: + if (rx_ant == 0) { + /* Diversity */ + bbp4 |= 0x02; + if (is_5ghz == 0) + bbp4 |= 0x20; + } else if (rx_ant == 1) { + /* RX: Antenna A */ + bbp4 |= 0x01; + if (is_5ghz) + bbp77 &= ~0x03; + else + bbp77 |= 0x03; + } else if (rx_ant == 2) { + /* RX: Antenna B */ + bbp4 |= 0x01; + if (is_5ghz) + bbp77 |= 0x03; + else + bbp77 &= ~0x03; + } + break; + + case RT2573_RF_2528: + case RT2573_RF_2527: + if (rx_ant == 0) { + /* Diversity */ + bbp4 |= 0x22; + } else if (rx_ant == 1) { + /* RX: Antenna A */ + bbp4 |= 0x21; + bbp77 |= 0x03; + } else if (rx_ant == 2) { + /* RX: Antenna B */ + bbp4 |= 0x21; + bbp77 &= ~0x03; + } + break; + default: + break; + } + bbp4 &= ~(sc->sc_ftype << 5); + + /* make sure Rx is disabled before switching antenna */ + tmp = rum_cfg_read(sc, RT2573_TXRX_CSR0); + rum_cfg_write(sc, RT2573_TXRX_CSR0, tmp | RT2573_DISABLE_RX); + + rum_cfg_bbp_write(sc, 3, bbp3); + rum_cfg_bbp_write(sc, 4, bbp4); + rum_cfg_bbp_write(sc, 77, bbp77); + + rum_cfg_write(sc, RT2573_TXRX_CSR0, tmp); + + return; +} + +static void +rum_cfg_read_eeprom(struct rum_softc *sc) +{ + uint16_t val; + + /* read MAC address */ + rum_cfg_eeprom_read(sc, RT2573_EEPROM_ADDRESS, sc->sc_myaddr, 6); + + val = rum_cfg_eeprom_read_2(sc, RT2573_EEPROM_ANTENNA); + sc->sc_rf_rev = (val >> 11) & 0x1f; + sc->sc_hw_radio = (val >> 10) & 0x1; + sc->sc_ftype = (val >> 6) & 0x1; + sc->sc_rx_ant = (val >> 4) & 0x3; + sc->sc_tx_ant = (val >> 2) & 0x3; + sc->sc_nb_ant = (val & 0x3); + + DPRINTF("RF revision=%d\n", sc->sc_rf_rev); + + val = rum_cfg_eeprom_read_2(sc, RT2573_EEPROM_CONFIG2); + sc->sc_ext_5ghz_lna = (val >> 6) & 0x1; + sc->sc_ext_2ghz_lna = (val >> 4) & 0x1; + + DPRINTF("External 2GHz LNA=%d, External 5GHz LNA=%d\n", + sc->sc_ext_2ghz_lna, sc->sc_ext_5ghz_lna); + + val = rum_cfg_eeprom_read_2(sc, RT2573_EEPROM_RSSI_2GHZ_OFFSET); + if ((val & 0xff) != 0xff) + sc->sc_rssi_2ghz_corr = (int8_t)(val & 0xff); /* signed */ + else + sc->sc_rssi_2ghz_corr = 0; + + /* range check */ + if ((sc->sc_rssi_2ghz_corr < -10) || + (sc->sc_rssi_2ghz_corr > 10)) { + sc->sc_rssi_2ghz_corr = 0; + } + val = rum_cfg_eeprom_read_2(sc, RT2573_EEPROM_RSSI_5GHZ_OFFSET); + if ((val & 0xff) != 0xff) + sc->sc_rssi_5ghz_corr = (int8_t)(val & 0xff); /* signed */ + else + sc->sc_rssi_5ghz_corr = 0; + + /* range check */ + if ((sc->sc_rssi_5ghz_corr < -10) || + (sc->sc_rssi_5ghz_corr > 10)) { + sc->sc_rssi_5ghz_corr = 0; + } + if (sc->sc_ext_2ghz_lna) { + sc->sc_rssi_2ghz_corr -= 14; + } + if (sc->sc_ext_5ghz_lna) { + sc->sc_rssi_5ghz_corr -= 14; + } + DPRINTF("RSSI 2GHz corr=%d, RSSI 5GHz corr=%d\n", + sc->sc_rssi_2ghz_corr, sc->sc_rssi_5ghz_corr); + + val = rum_cfg_eeprom_read_2(sc, RT2573_EEPROM_FREQ_OFFSET); + if ((val & 0xff) != 0xff) + sc->sc_rffreq = (val & 0xff); + else + sc->sc_rffreq = 0; + + DPRINTF("RF freq=%d\n", sc->sc_rffreq); + + /* read Tx power for all a/b/g channels */ + rum_cfg_eeprom_read(sc, RT2573_EEPROM_TXPOWER, sc->sc_txpow, 14); + + /* XXX default Tx power for 802.11a channels */ + memset(sc->sc_txpow + 14, 24, sizeof(sc->sc_txpow) - 14); + + /* read default values for BBP registers */ + rum_cfg_eeprom_read(sc, RT2573_EEPROM_BBP_BASE, sc->sc_bbp_prom, 2 * 16); + + return; +} + +static uint8_t +rum_cfg_bbp_init(struct rum_softc *sc) +{ + enum { + N_DEF_BBP = (sizeof(rum_def_bbp) / sizeof(rum_def_bbp[0])), + }; + uint16_t i; + uint8_t to; + uint8_t tmp; + + /* wait for BBP to become ready */ + for (to = 0;; to++) { + if (to < 100) { + tmp = rum_cfg_bbp_read(sc, 0); + if ((tmp != 0x00) && + (tmp != 0xff)) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + return (1); /* failure */ + } + } else { + DPRINTF("timeout waiting for BBP\n"); + return (1); /* failure */ + } + } + + /* initialize BBP registers to default values */ + for (i = 0; i < N_DEF_BBP; i++) { + rum_cfg_bbp_write(sc, rum_def_bbp[i].reg, rum_def_bbp[i].val); + } + + /* write vendor-specific BBP values (from EEPROM) */ + for (i = 0; i < 16; i++) { + if ((sc->sc_bbp_prom[i].reg == 0) || + (sc->sc_bbp_prom[i].reg == 0xff)) { + continue; + } + rum_cfg_bbp_write(sc, sc->sc_bbp_prom[i].reg, sc->sc_bbp_prom[i].val); + } + return (0); +} + +static void +rum_cfg_pre_init(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + /* immediate configuration */ + + rum_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= RUM_FLAG_HL_READY; + + IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp)); + return; +} + +static void +rum_cfg_init(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + enum { + N_DEF_MAC = (sizeof(rum_def_mac) / sizeof(rum_def_mac[0])), + }; + + uint32_t tmp; + uint16_t i; + uint8_t to; + + /* delayed configuration */ + + rum_cfg_stop(sc, cc, 0); + + /* initialize MAC registers to default values */ + for (i = 0; i < N_DEF_MAC; i++) { + rum_cfg_write(sc, rum_def_mac[i].reg, rum_def_mac[i].val); + } + + /* set host ready */ + rum_cfg_write(sc, RT2573_MAC_CSR1, 3); + rum_cfg_write(sc, RT2573_MAC_CSR1, 0); + + /* wait for BBP/RF to wakeup */ + for (to = 0;; to++) { + if (to < 100) { + if (rum_cfg_read(sc, RT2573_MAC_CSR12) & 8) { + break; + } + rum_cfg_write(sc, RT2573_MAC_CSR12, 4); /* force wakeup */ + + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + goto fail; + } + } else { + DPRINTF("timeout waiting for " + "BBP/RF to wakeup\n"); + goto fail; + } + } + + if (rum_cfg_bbp_init(sc)) { + goto fail; + } + /* select default channel */ + + sc->sc_last_chan = 0; + + rum_cfg_set_chan(sc, cc, 0); + + /* clear STA registers */ + rum_cfg_read_multi(sc, RT2573_STA_CSR0, sc->sc_sta, sizeof(sc->sc_sta)); + /* set MAC address */ + rum_cfg_set_macaddr(sc, cc->ic_myaddr); + + /* initialize ASIC */ + rum_cfg_write(sc, RT2573_MAC_CSR1, 4); + + /* + * make sure that the first transaction + * clears the stall: + */ + sc->sc_flags |= (RUM_FLAG_READ_STALL | + RUM_FLAG_WRITE_STALL | + RUM_FLAG_LL_READY); + + if ((sc->sc_flags & RUM_FLAG_LL_READY) && + (sc->sc_flags & RUM_FLAG_HL_READY)) { + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + + /* + * start IEEE802.11 layer + */ + mtx_unlock(&sc->sc_mtx); + ieee80211_start_all(ic); + mtx_lock(&sc->sc_mtx); + } + /* update Rx filter */ + tmp = rum_cfg_read(sc, RT2573_TXRX_CSR0) & 0xffff; + + tmp |= RT2573_DROP_PHY_ERROR | RT2573_DROP_CRC_ERROR; + + if (cc->ic_opmode != IEEE80211_M_MONITOR) { + tmp |= RT2573_DROP_CTL | RT2573_DROP_VER_ERROR | + RT2573_DROP_ACKCTS; + if (cc->ic_opmode != IEEE80211_M_HOSTAP) { + tmp |= RT2573_DROP_TODS; + } + if (!(cc->if_flags & IFF_PROMISC)) { + tmp |= RT2573_DROP_NOT_TO_ME; + } + } + rum_cfg_write(sc, RT2573_TXRX_CSR0, tmp); + + return; + +fail: + rum_cfg_pre_stop(sc, NULL, 0); + + if (cc) { + rum_cfg_stop(sc, cc, 0); + } + return; +} + +static void +rum_cfg_pre_stop(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + rum_config_copy(sc, cc, refcount); + } + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(RUM_FLAG_HL_READY | + RUM_FLAG_LL_READY); + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[3]); + + /* clean up transmission */ + rum_tx_clean_queue(sc); + return; +} + +static void +rum_cfg_stop(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t tmp; + + /* disable Rx */ + tmp = rum_cfg_read(sc, RT2573_TXRX_CSR0); + rum_cfg_write(sc, RT2573_TXRX_CSR0, tmp | RT2573_DISABLE_RX); + + /* reset ASIC */ + rum_cfg_write(sc, RT2573_MAC_CSR1, 3); + + /* wait a little */ + usb2_config_td_sleep(&sc->sc_config_td, hz / 10); + + rum_cfg_write(sc, RT2573_MAC_CSR1, 0); + + /* wait a little */ + usb2_config_td_sleep(&sc->sc_config_td, hz / 10); + + return; +} + +static void +rum_cfg_amrr_start(struct rum_softc *sc) +{ + struct ieee80211vap *vap; + struct ieee80211_node *ni; + + vap = rum_get_vap(sc); + + if (vap == NULL) { + return; + } + ni = vap->iv_bss; + if (ni == NULL) { + return; + } + /* init AMRR */ + + ieee80211_amrr_node_init(&RUM_VAP(vap)->amrr, &RUM_NODE(ni)->amn, ni); + + /* enable AMRR timer */ + + sc->sc_amrr_timer = 1; + return; +} + +static void +rum_cfg_amrr_timeout(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211vap *vap; + struct ieee80211_node *ni; + uint32_t ok; + uint32_t fail; + + /* clear statistic registers (STA_CSR0 to STA_CSR5) */ + rum_cfg_read_multi(sc, RT2573_STA_CSR0, sc->sc_sta, sizeof(sc->sc_sta)); + + vap = rum_get_vap(sc); + if (vap == NULL) { + return; + } + ni = vap->iv_bss; + if (ni == NULL) { + return; + } + if ((sc->sc_flags & RUM_FLAG_LL_READY) && + (sc->sc_flags & RUM_FLAG_HL_READY)) { + + ok = (le32toh(sc->sc_sta[4]) >> 16) + /* TX ok w/o retry */ + (le32toh(sc->sc_sta[5]) & 0xffff); /* TX ok w/ retry */ + fail = (le32toh(sc->sc_sta[5]) >> 16); /* TX retry-fail count */ + + if (sc->sc_amrr_timer) { + ieee80211_amrr_tx_update(&RUM_NODE(vap->iv_bss)->amn, + ok + fail, ok, (le32toh(sc->sc_sta[5]) & 0xffff) + fail); + + if (ieee80211_amrr_choose(ni, &RUM_NODE(ni)->amn)) { + /* ignore */ + } + } + ifp->if_oerrors += fail;/* count TX retry-fail as Tx errors */ + } + return; +} + +static void +rum_cfg_load_microcode(struct rum_softc *sc, const uint8_t *ucode, uint16_t size) +{ + struct usb2_device_request req; + uint16_t reg = RT2573_MCU_CODE_BASE; + + /* copy firmware image into NIC */ + while (size >= 4) { + rum_cfg_write(sc, reg, UGETDW(ucode)); + reg += 4; + ucode += 4; + size -= 4; + } + + if (size != 0) { + DPRINTF("possibly invalid firmware\n"); + } + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2573_MCU_CNTL; + USETW(req.wValue, RT2573_MCU_RUN); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + rum_cfg_do_request(sc, &req, NULL); + + return; +} + +static void +rum_cfg_prepare_beacon(struct rum_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ieee80211_node *ni; + struct ieee80211vap *vap; + struct ieee80211com *ic; + const struct ieee80211_txparam *tp; + struct mbuf *m; + + vap = rum_get_vap(sc); + if (vap == NULL) { + return; + } + ni = vap->iv_bss; + if (ni == NULL) { + return; + } + ic = vap->iv_ic; + if (ic == NULL) { + return; + } + DPRINTFN(11, "Sending beacon frame.\n"); + + m = ieee80211_beacon_alloc(ni, &RUM_VAP(vap)->bo); + if (m == NULL) { + DPRINTFN(0, "could not allocate beacon\n"); + return; + } + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; + + m->m_pkthdr.rcvif = (void *)ieee80211_ref_node(ni); + rum_setup_desc_and_tx(sc, m, RT2573_TX_TIMESTAMP, RT2573_TX_HWSEQ | RT2573_TX_BEACON, tp->mgmtrate); + return; +} + +static uint8_t +rum_get_rssi(struct rum_softc *sc, uint8_t raw) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + int16_t rssi; + uint8_t lna; + uint8_t agc; + + lna = (raw >> 5) & 0x3; + agc = raw & 0x1f; + + if (lna == 0) { + /* + * No RSSI mapping + * + * NB: Since RSSI is relative to noise floor, -1 is + * adequate for caller to know error happened. + */ + return (0); + } + rssi = (2 * agc) - RT2573_NOISE_FLOOR; + + if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { + + rssi += sc->sc_rssi_2ghz_corr; + + if (lna == 1) + rssi -= 64; + else if (lna == 2) + rssi -= 74; + else if (lna == 3) + rssi -= 90; + } else { + + rssi += sc->sc_rssi_5ghz_corr; + + if ((!sc->sc_ext_5ghz_lna) && (lna != 1)) + rssi += 4; + + if (lna == 1) + rssi -= 64; + else if (lna == 2) + rssi -= 86; + else if (lna == 3) + rssi -= 100; + } + + /* range check */ + + if (rssi < 0) + rssi = 0; + else if (rssi > 255) + rssi = 255; + + return (rssi); +} + +static struct ieee80211vap * +rum_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct rum_vap *rvp; + struct ieee80211vap *vap; + struct rum_softc *sc = ic->ic_ifp->if_softc; + + DPRINTF("\n"); + + /* Need to sync with config thread: */ + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_sync(&sc->sc_config_td)) { + mtx_unlock(&sc->sc_mtx); + /* config thread is gone */ + return (NULL); + } + mtx_unlock(&sc->sc_mtx); + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + rvp = (struct rum_vap *)malloc(sizeof(struct rum_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (rvp == NULL) + return NULL; + vap = &rvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + rvp->newstate = vap->iv_newstate; + vap->iv_newstate = &rum_newstate_cb; + + ieee80211_amrr_init(&rvp->amrr, vap, + IEEE80211_AMRR_MIN_SUCCESS_THRESHOLD, + IEEE80211_AMRR_MAX_SUCCESS_THRESHOLD, + 1000 /* 1 sec */ ); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status); + + /* store current operation mode */ + ic->ic_opmode = opmode; + + return (vap); +} + +static void +rum_vap_delete(struct ieee80211vap *vap) +{ + struct rum_vap *rvp = RUM_VAP(vap); + struct rum_softc *sc = vap->iv_ic->ic_ifp->if_softc; + + DPRINTF("\n"); + + /* Need to sync with config thread: */ + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_sync(&sc->sc_config_td)) { + /* ignore */ + } + mtx_unlock(&sc->sc_mtx); + + ieee80211_amrr_cleanup(&rvp->amrr); + ieee80211_vap_detach(vap); + free(rvp, M_80211_VAP); + return; +} + +/* ARGUSED */ +static struct ieee80211_node * +rum_node_alloc(struct ieee80211vap *vap __unused, + const uint8_t mac[IEEE80211_ADDR_LEN] __unused) +{ + struct rum_node *rn; + + rn = malloc(sizeof(struct rum_node), M_80211_NODE, M_NOWAIT | M_ZERO); + return ((rn != NULL) ? &rn->ni : NULL); +} + +static void +rum_newassoc(struct ieee80211_node *ni, int isnew) +{ + struct ieee80211vap *vap = ni->ni_vap; + + ieee80211_amrr_node_init(&RUM_VAP(vap)->amrr, &RUM_NODE(ni)->amn, ni); + return; +} + +static void +rum_fill_write_queue(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211_node *ni; + struct mbuf *m; + + /* + * We only fill up half of the queue with data frames. The rest is + * reserved for other kinds of frames. + */ + + while (sc->sc_tx_queue.ifq_len < (IFQ_MAXLEN / 2)) { + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + ni = (void *)(m->m_pkthdr.rcvif); + m = ieee80211_encap(ni, m); + if (m == NULL) { + ieee80211_free_node(ni); + continue; + } + rum_tx_data(sc, m, ni); + } + return; +} + +static void +rum_tx_clean_queue(struct rum_softc *sc) +{ + struct mbuf *m; + + for (;;) { + _IF_DEQUEUE(&sc->sc_tx_queue, m); + + if (!m) { + break; + } + rum_tx_freem(m); + } + return; +} + +static void +rum_tx_freem(struct mbuf *m) +{ + struct ieee80211_node *ni; + + while (m) { + ni = (void *)(m->m_pkthdr.rcvif); + if (!ni) { + m = m_free(m); + continue; + } + if (m->m_flags & M_TXCB) { + ieee80211_process_callback(ni, m, 0); + } + m_freem(m); + ieee80211_free_node(ni); + + break; + } + return; +} + +static void +rum_tx_mgt(struct rum_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_txparam *tp; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + uint32_t flags; + uint16_t dur; + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + + wh = mtod(m, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m); + if (k == NULL) { + m_freem(m); + ieee80211_free_node(ni); + return; + } + wh = mtod(m, struct ieee80211_frame *); + } + flags = 0; + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RT2573_TX_NEED_ACK; + + dur = ieee80211_ack_duration(sc->sc_rates, tp->mgmtrate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + USETW(wh->i_dur, dur); + + /* tell hardware to add timestamp for probe responses */ + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP)) + flags |= RT2573_TX_TIMESTAMP; + } + m->m_pkthdr.rcvif = (void *)ni; + rum_setup_desc_and_tx(sc, m, flags, 0, tp->mgmtrate); + return; +} + +static struct ieee80211vap * +rum_get_vap(struct rum_softc *sc) +{ + struct ifnet *ifp; + struct ieee80211com *ic; + + if (sc == NULL) { + return NULL; + } + ifp = sc->sc_ifp; + if (ifp == NULL) { + return NULL; + } + ic = ifp->if_l2com; + if (ic == NULL) { + return NULL; + } + return TAILQ_FIRST(&ic->ic_vaps); +} + +static void +rum_tx_data(struct rum_softc *sc, struct mbuf *m, + struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_txparam *tp; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + uint32_t flags = 0; + uint16_t dur; + uint16_t rate; + + DPRINTFN(11, "Sending data.\n"); + + wh = mtod(m, struct ieee80211_frame *); + + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + rate = tp->mcastrate; + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + rate = tp->ucastrate; + else + rate = ni->ni_txrate; + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m); + if (k == NULL) { + m_freem(m); + ieee80211_free_node(ni); + return; + } + /* packet header may have moved, reset our local pointer */ + wh = mtod(m, struct ieee80211_frame *); + } + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + uint8_t prot = IEEE80211_PROT_NONE; + + if (m->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) + prot = IEEE80211_PROT_RTSCTS; + else if ((ic->ic_flags & IEEE80211_F_USEPROT) && + ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_OFDM) + prot = ic->ic_protmode; + if (prot != IEEE80211_PROT_NONE) { + rum_tx_prot(sc, m, ni, prot, rate); + flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS; + } + flags |= RT2573_TX_NEED_ACK; + flags |= RT2573_TX_MORE_FRAG; + + dur = ieee80211_ack_duration(sc->sc_rates, rate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + USETW(wh->i_dur, dur); + } + m->m_pkthdr.rcvif = (void *)ni; + rum_setup_desc_and_tx(sc, m, flags, 0, rate); + return; +} + +static void +rum_tx_prot(struct rum_softc *sc, + const struct mbuf *m, struct ieee80211_node *ni, + uint8_t prot, uint16_t rate) +{ + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_frame *wh; + struct mbuf *mprot; + uint32_t flags; + uint16_t protrate; + uint16_t ackrate; + uint16_t pktlen; + uint16_t dur; + uint8_t isshort; + + KASSERT((prot == IEEE80211_PROT_RTSCTS) || + (prot == IEEE80211_PROT_CTSONLY), + ("protection %u", prot)); + + DPRINTFN(11, "Sending protection frame.\n"); + + wh = mtod(m, const struct ieee80211_frame *); + pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + + protrate = ieee80211_ctl_rate(sc->sc_rates, rate); + ackrate = ieee80211_ack_rate(sc->sc_rates, rate); + + isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; + dur = ieee80211_compute_duration(sc->sc_rates, pktlen, rate, isshort); + +ieee80211_ack_duration(sc->sc_rates, rate, isshort); + flags = RT2573_TX_MORE_FRAG; + if (prot == IEEE80211_PROT_RTSCTS) { + /* NB: CTS is the same size as an ACK */ + dur += ieee80211_ack_duration(sc->sc_rates, rate, isshort); + flags |= RT2573_TX_NEED_ACK; + mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); + } else { + mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, dur); + } + if (mprot == NULL) { + return; + } + mprot->m_pkthdr.rcvif = (void *)ieee80211_ref_node(ni); + rum_setup_desc_and_tx(sc, mprot, flags, 0, protrate); + return; +} + +static void +rum_tx_raw(struct rum_softc *sc, struct mbuf *m, struct ieee80211_node *ni, + const struct ieee80211_bpf_params *params) +{ + uint32_t flags; + uint16_t rate; + + DPRINTFN(11, "Sending raw frame.\n"); + + rate = params->ibp_rate0 & IEEE80211_RATE_VAL; + + /* XXX validate */ + if (rate == 0) { + m_freem(m); + ieee80211_free_node(ni); + return; + } + flags = 0; + if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) + flags |= RT2573_TX_NEED_ACK; + if (params->ibp_flags & (IEEE80211_BPF_RTS | IEEE80211_BPF_CTS)) { + rum_tx_prot(sc, m, ni, + params->ibp_flags & IEEE80211_BPF_RTS ? + IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, + rate); + flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS; + } + m->m_pkthdr.rcvif = (void *)ni; + rum_setup_desc_and_tx(sc, m, flags, 0, rate); + return; +} + +static int +rum_raw_xmit_cb(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct rum_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + if (params == NULL) { + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + */ + rum_tx_mgt(sc, m, ni); + } else { + /* + * Caller supplied explicit parameters to use in + * sending the frame. + */ + rum_tx_raw(sc, m, ni, params); + } + mtx_unlock(&sc->sc_mtx); + return (0); +} + +static void +rum_update_mcast_cb(struct ifnet *ifp) +{ + /* not supported */ + return; +} + +static void +rum_update_promisc_cb(struct ifnet *ifp) +{ + struct rum_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &rum_config_copy, + &rum_cfg_update_promisc, 0, 0); + mtx_unlock(&sc->sc_mtx); + return; +} diff --git a/sys/dev/usb2/wlan/if_rum2_fw.h b/sys/dev/usb2/wlan/if_rum2_fw.h new file mode 100644 index 000000000000..0f0867445131 --- /dev/null +++ b/sys/dev/usb2/wlan/if_rum2_fw.h @@ -0,0 +1,213 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005-2006, Ralink Technology, Corp. + * Paul Lin + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file contains the loadable 8051 microcode for the Ralink RT2573 + * chipset. + */ + +static const uint8_t rt2573_ucode[] = { + 0x02, 0x13, 0x25, 0x12, 0x10, 0xd9, 0x02, 0x12, 0x58, 0x02, 0x13, + 0x58, 0x02, 0x13, 0x5a, 0xc0, 0xd0, 0x75, 0xd0, 0x18, 0x12, 0x13, + 0x5c, 0xd0, 0xd0, 0x22, 0x02, 0x14, 0x5c, 0x02, 0x14, 0xe7, 0xed, + 0x4c, 0x70, 0x44, 0x90, 0x01, 0xa8, 0x74, 0x80, 0xf0, 0xef, 0x30, + 0xe5, 0x07, 0xe4, 0x90, 0x00, 0x0f, 0xf0, 0x80, 0x2c, 0xe5, 0x40, + 0x24, 0xc0, 0x60, 0x13, 0x24, 0xc0, 0x60, 0x16, 0x24, 0xc0, 0x60, + 0x19, 0x24, 0xc0, 0x70, 0x1a, 0xe4, 0x90, 0x00, 0x0b, 0xf0, 0x80, + 0x13, 0xe4, 0x90, 0x00, 0x13, 0xf0, 0x80, 0x0c, 0xe4, 0x90, 0x00, + 0x1b, 0xf0, 0x80, 0x05, 0xe4, 0x90, 0x00, 0x23, 0xf0, 0xe4, 0x90, + 0x01, 0xa8, 0xf0, 0xd3, 0x22, 0x90, 0x02, 0x02, 0xed, 0xf0, 0x90, + 0x02, 0x01, 0xef, 0xf0, 0xd3, 0x22, 0xef, 0x24, 0xc0, 0x60, 0x1f, + 0x24, 0xc0, 0x60, 0x2e, 0x24, 0xc0, 0x60, 0x3d, 0x24, 0xc0, 0x70, + 0x53, 0x90, 0x00, 0x0b, 0xe0, 0x30, 0xe1, 0x02, 0xc3, 0x22, 0x90, + 0x00, 0x09, 0xe0, 0xfe, 0x90, 0x00, 0x08, 0x80, 0x37, 0x90, 0x00, + 0x13, 0xe0, 0x30, 0xe1, 0x02, 0xc3, 0x22, 0x90, 0x00, 0x11, 0xe0, + 0xfe, 0x90, 0x00, 0x10, 0x80, 0x24, 0x90, 0x00, 0x1b, 0xe0, 0x30, + 0xe1, 0x02, 0xc3, 0x22, 0x90, 0x00, 0x19, 0xe0, 0xfe, 0x90, 0x00, + 0x18, 0x80, 0x11, 0x90, 0x00, 0x23, 0xe0, 0x30, 0xe1, 0x02, 0xc3, + 0x22, 0x90, 0x00, 0x21, 0xe0, 0xfe, 0x90, 0x00, 0x20, 0xe0, 0xfd, + 0xee, 0xf5, 0x37, 0xed, 0xf5, 0x38, 0xd3, 0x22, 0x30, 0x09, 0x20, + 0x20, 0x04, 0x0b, 0x90, 0x02, 0x08, 0xe0, 0x54, 0x0f, 0x70, 0x03, + 0x02, 0x12, 0x57, 0xc2, 0x09, 0x90, 0x02, 0x00, 0xe0, 0x44, 0x04, + 0xf0, 0x74, 0x04, 0x12, 0x0c, 0x3a, 0xc2, 0x04, 0xc2, 0x07, 0x90, + 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, 0xf6, 0x90, 0x03, + 0x26, 0xe0, 0x20, 0xe2, 0x03, 0x02, 0x12, 0x57, 0x90, 0x02, 0x08, + 0xe0, 0x70, 0x1b, 0x20, 0x07, 0x03, 0x02, 0x12, 0x57, 0x90, 0x03, + 0x12, 0xe0, 0x64, 0x22, 0x60, 0x03, 0x02, 0x12, 0x57, 0xd2, 0x09, + 0xc2, 0x07, 0x74, 0x02, 0x12, 0x0c, 0x3a, 0x22, 0x90, 0x02, 0x03, + 0xe0, 0x30, 0xe4, 0x47, 0x20, 0x06, 0x44, 0xe5, 0x3c, 0x60, 0x34, + 0xe5, 0x40, 0x24, 0xc0, 0x60, 0x14, 0x24, 0xc0, 0x60, 0x18, 0x24, + 0xc0, 0x60, 0x1c, 0x24, 0xc0, 0x70, 0x22, 0x90, 0x00, 0x0b, 0xe0, + 0x30, 0xe1, 0x1b, 0x22, 0x90, 0x00, 0x13, 0xe0, 0x30, 0xe1, 0x13, + 0x22, 0x90, 0x00, 0x1b, 0xe0, 0x30, 0xe1, 0x0b, 0x22, 0x90, 0x00, + 0x23, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, 0x90, 0x02, 0x03, + 0x74, 0x01, 0xf0, 0x00, 0xe0, 0x54, 0xc0, 0xf5, 0x40, 0xe5, 0x40, + 0x24, 0xc0, 0x60, 0x20, 0x24, 0xc0, 0x60, 0x30, 0x24, 0xc0, 0x60, + 0x40, 0x24, 0xc0, 0x70, 0x56, 0x90, 0x00, 0x0b, 0xe0, 0x30, 0xe1, + 0x03, 0x02, 0x12, 0x57, 0x90, 0x00, 0x09, 0xe0, 0xfe, 0x90, 0x00, + 0x08, 0x80, 0x3a, 0x90, 0x00, 0x13, 0xe0, 0x30, 0xe1, 0x03, 0x02, + 0x12, 0x57, 0x90, 0x00, 0x11, 0xe0, 0xfe, 0x90, 0x00, 0x10, 0x80, + 0x26, 0x90, 0x00, 0x1b, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, + 0x90, 0x00, 0x19, 0xe0, 0xfe, 0x90, 0x00, 0x18, 0x80, 0x12, 0x90, + 0x00, 0x23, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, 0x90, 0x00, + 0x21, 0xe0, 0xfe, 0x90, 0x00, 0x20, 0xe0, 0xfd, 0xee, 0xf5, 0x37, + 0xed, 0xf5, 0x38, 0x90, 0x03, 0x27, 0x74, 0x82, 0xf0, 0x90, 0x02, + 0x01, 0xe5, 0x40, 0xf0, 0x90, 0x02, 0x06, 0xe0, 0xf5, 0x3c, 0xc3, + 0xe5, 0x38, 0x95, 0x3a, 0xe5, 0x37, 0x95, 0x39, 0x50, 0x21, 0xe5, + 0x40, 0x44, 0x05, 0xff, 0xe5, 0x37, 0xa2, 0xe7, 0x13, 0xfc, 0xe5, + 0x38, 0x13, 0xfd, 0x12, 0x10, 0x20, 0xe5, 0x3c, 0x30, 0xe2, 0x04, + 0xd2, 0x06, 0x80, 0x02, 0xc2, 0x06, 0x53, 0x3c, 0x01, 0x22, 0x30, + 0x0b, 0x07, 0xe4, 0x90, 0x02, 0x02, 0xf0, 0x80, 0x06, 0x90, 0x02, + 0x02, 0x74, 0x20, 0xf0, 0xe5, 0x40, 0x44, 0x01, 0x90, 0x02, 0x01, + 0xf0, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, 0xf6, + 0x90, 0x03, 0x27, 0x74, 0x02, 0xf0, 0xaf, 0x40, 0x12, 0x10, 0x74, + 0x40, 0xa5, 0x00, 0x80, 0xf6, 0x22, 0x90, 0x7f, 0xf8, 0xe0, 0xb4, + 0x02, 0x03, 0x12, 0x16, 0x38, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, + 0x03, 0x00, 0x80, 0xf6, 0x90, 0x03, 0x26, 0xe0, 0x20, 0xe1, 0x07, + 0xe5, 0x3b, 0x70, 0x03, 0x02, 0x13, 0x24, 0xe5, 0x3b, 0x70, 0x15, + 0x90, 0x03, 0x24, 0xe0, 0x75, 0xf0, 0x40, 0xa4, 0xf5, 0x36, 0x85, + 0xf0, 0x35, 0x75, 0x24, 0x83, 0x75, 0x3b, 0x01, 0x80, 0x03, 0x75, + 0x24, 0x03, 0xd3, 0xe5, 0x36, 0x95, 0x3a, 0xe5, 0x35, 0x95, 0x39, + 0x40, 0x36, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, + 0xf6, 0x90, 0x03, 0x27, 0xe5, 0x24, 0xf0, 0x90, 0x00, 0x0f, 0xe0, + 0x30, 0xe1, 0x04, 0x30, 0x0e, 0xf6, 0x22, 0x30, 0x0b, 0x07, 0xe4, + 0x90, 0x02, 0x02, 0xf0, 0x80, 0x06, 0x90, 0x02, 0x02, 0x74, 0x20, + 0xf0, 0x90, 0x02, 0x01, 0x74, 0x21, 0xf0, 0x75, 0x24, 0x03, 0x80, + 0x3d, 0xe5, 0x35, 0xa2, 0xe7, 0x13, 0xfe, 0xe5, 0x36, 0x13, 0xfd, + 0xac, 0x06, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, + 0xf6, 0x90, 0x03, 0x27, 0xe5, 0x24, 0xf0, 0x90, 0x00, 0x0f, 0xe0, + 0x30, 0xe1, 0x04, 0x30, 0x0e, 0xf6, 0x22, 0x7f, 0x25, 0x12, 0x10, + 0x20, 0xe5, 0x36, 0xb5, 0x3a, 0x08, 0xe5, 0x35, 0xb5, 0x39, 0x03, + 0x00, 0x80, 0x04, 0xe4, 0xf5, 0x3b, 0x22, 0xc3, 0xe5, 0x36, 0x95, + 0x3a, 0xf5, 0x36, 0xe5, 0x35, 0x95, 0x39, 0xf5, 0x35, 0x02, 0x12, + 0x96, 0x22, 0x75, 0xa8, 0x0f, 0x90, 0x03, 0x06, 0x74, 0x01, 0xf0, + 0x90, 0x03, 0x07, 0xf0, 0x90, 0x03, 0x08, 0x04, 0xf0, 0x90, 0x03, + 0x09, 0x74, 0x6c, 0xf0, 0x90, 0x03, 0x0a, 0x74, 0xff, 0xf0, 0x90, + 0x03, 0x02, 0x74, 0x1f, 0xf0, 0x90, 0x03, 0x00, 0x74, 0x04, 0xf0, + 0x90, 0x03, 0x25, 0x74, 0x31, 0xf0, 0xd2, 0xaf, 0x22, 0x00, 0x22, + 0x00, 0x22, 0x90, 0x03, 0x05, 0xe0, 0x30, 0xe0, 0x0b, 0xe0, 0x44, + 0x01, 0xf0, 0x30, 0x09, 0x02, 0xd2, 0x04, 0xc2, 0x07, 0x22, 0x8d, + 0x24, 0xa9, 0x07, 0x90, 0x7f, 0xfc, 0xe0, 0x75, 0x25, 0x00, 0xf5, + 0x26, 0xa3, 0xe0, 0x75, 0x27, 0x00, 0xf5, 0x28, 0xa3, 0xe0, 0xff, + 0xa3, 0xe0, 0xfd, 0xe9, 0x30, 0xe5, 0x14, 0x54, 0xc0, 0x60, 0x05, + 0x43, 0x05, 0x03, 0x80, 0x03, 0x53, 0x05, 0xfc, 0xef, 0x54, 0x3f, + 0x44, 0x40, 0xff, 0x80, 0x06, 0x53, 0x07, 0x3f, 0x53, 0x05, 0xf0, + 0xe5, 0x24, 0x30, 0xe0, 0x05, 0x43, 0x05, 0x10, 0x80, 0x03, 0x53, + 0x05, 0xef, 0x90, 0x7f, 0xfc, 0xe5, 0x26, 0xf0, 0xa3, 0xe5, 0x28, + 0xf0, 0xa3, 0xef, 0xf0, 0xa3, 0xed, 0xf0, 0x22, 0x8f, 0x24, 0xa9, + 0x05, 0x90, 0x7f, 0xfc, 0xe0, 0x75, 0x25, 0x00, 0xf5, 0x26, 0xa3, + 0xe0, 0x75, 0x27, 0x00, 0xf5, 0x28, 0xa3, 0xe0, 0xff, 0xa3, 0xe0, + 0xfd, 0xe5, 0x24, 0x30, 0xe5, 0x0b, 0x43, 0x05, 0x0f, 0xef, 0x54, + 0x3f, 0x44, 0x40, 0xff, 0x80, 0x06, 0x53, 0x05, 0xf0, 0x53, 0x07, + 0x3f, 0xe9, 0x30, 0xe0, 0x05, 0x43, 0x05, 0x10, 0x80, 0x03, 0x53, + 0x05, 0xef, 0x90, 0x7f, 0xfc, 0xe5, 0x26, 0xf0, 0xa3, 0xe5, 0x28, + 0xf0, 0xa3, 0xef, 0xf0, 0xa3, 0xed, 0xf0, 0x22, 0x90, 0x7f, 0xfc, + 0xe0, 0xf9, 0xa3, 0xe0, 0xfe, 0xa3, 0xe0, 0xfc, 0xa3, 0xe0, 0xfb, + 0xef, 0x30, 0xe5, 0x0b, 0x43, 0x03, 0x0f, 0xec, 0x54, 0x3f, 0x44, + 0x40, 0xfc, 0x80, 0x06, 0x53, 0x03, 0xf0, 0x53, 0x04, 0x3f, 0xed, + 0x30, 0xe0, 0x07, 0xef, 0x54, 0xc0, 0x60, 0x07, 0x80, 0x0a, 0xef, + 0x54, 0xc0, 0x60, 0x05, 0x43, 0x03, 0x10, 0x80, 0x03, 0x53, 0x03, + 0xef, 0x90, 0x7f, 0xfc, 0xe9, 0xf0, 0xa3, 0xee, 0xf0, 0xa3, 0xec, + 0xf0, 0xa3, 0xeb, 0xf0, 0x22, 0xe5, 0x4b, 0xfd, 0x54, 0x1f, 0x90, + 0x7f, 0xf8, 0xf0, 0xe5, 0x4a, 0xf5, 0x09, 0x90, 0x30, 0x38, 0xe0, + 0x90, 0x7f, 0xfc, 0xf0, 0x90, 0x30, 0x39, 0xe0, 0x90, 0x7f, 0xfd, + 0xf0, 0x90, 0x30, 0x3a, 0xe0, 0x90, 0x7f, 0xfe, 0xf0, 0x90, 0x30, + 0x3b, 0xe0, 0x90, 0x7f, 0xff, 0xf0, 0xed, 0x30, 0xe5, 0x0c, 0x54, + 0xc0, 0x60, 0x0d, 0x90, 0x7f, 0xf0, 0xe5, 0x47, 0xf0, 0x80, 0x05, + 0xe4, 0x90, 0x7f, 0xf0, 0xf0, 0x90, 0x7f, 0xf8, 0xe0, 0x14, 0x60, + 0x08, 0x24, 0xfe, 0x60, 0x0d, 0x24, 0x03, 0x80, 0x12, 0xaf, 0x05, + 0xad, 0x09, 0x12, 0x13, 0xc5, 0x80, 0x10, 0xaf, 0x05, 0xad, 0x09, + 0x12, 0x14, 0x12, 0x80, 0x07, 0xaf, 0x05, 0xad, 0x09, 0x12, 0x13, + 0x6f, 0x90, 0x7f, 0xfc, 0xe0, 0x90, 0x30, 0x38, 0xf0, 0x90, 0x7f, + 0xfd, 0xe0, 0x90, 0x30, 0x39, 0xf0, 0x90, 0x7f, 0xfe, 0xe0, 0x90, + 0x30, 0x3a, 0xf0, 0x90, 0x7f, 0xff, 0xe0, 0x90, 0x30, 0x3b, 0xf0, + 0x22, 0xe5, 0x4b, 0x64, 0x01, 0x60, 0x03, 0x02, 0x15, 0x71, 0xf5, + 0x4b, 0xe5, 0x44, 0x45, 0x43, 0x70, 0x03, 0x02, 0x15, 0xa0, 0x12, + 0x0c, 0x14, 0x12, 0x0b, 0x86, 0x50, 0xfb, 0x90, 0x00, 0x00, 0xe0, + 0xf5, 0x25, 0x12, 0x15, 0xb4, 0xc2, 0x92, 0xe4, 0xf5, 0x24, 0xe5, + 0x24, 0xc3, 0x95, 0x25, 0x50, 0x49, 0x7e, 0x00, 0x7f, 0x4c, 0x74, + 0x40, 0x25, 0x24, 0xf5, 0x82, 0xe4, 0x34, 0x01, 0xad, 0x82, 0xfc, + 0x75, 0x2b, 0x02, 0x7b, 0x10, 0x12, 0x07, 0x1e, 0xc2, 0x93, 0x12, + 0x15, 0xa1, 0x7d, 0xa0, 0x12, 0x15, 0xd0, 0xe5, 0x24, 0x54, 0x0f, + 0x24, 0x4c, 0xf8, 0xe6, 0xfd, 0xaf, 0x4b, 0xae, 0x4a, 0x12, 0x15, + 0xd8, 0x05, 0x4b, 0xe5, 0x4b, 0x70, 0x02, 0x05, 0x4a, 0x12, 0x0a, + 0x5f, 0x05, 0x24, 0xe5, 0x24, 0x54, 0x0f, 0x70, 0xd5, 0xd2, 0x93, + 0x80, 0xb0, 0xc3, 0xe5, 0x44, 0x95, 0x25, 0xf5, 0x44, 0xe5, 0x43, + 0x94, 0x00, 0xf5, 0x43, 0x02, 0x14, 0xf2, 0x12, 0x15, 0xb4, 0xc2, + 0x93, 0xc2, 0x92, 0x12, 0x15, 0xa1, 0x7d, 0x80, 0x12, 0x15, 0xd0, + 0x7d, 0xaa, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x7d, 0x55, + 0x7f, 0xaa, 0x7e, 0x2a, 0x12, 0x15, 0xd8, 0x7d, 0x30, 0xaf, 0x4b, + 0xae, 0x4a, 0x12, 0x15, 0xd8, 0x12, 0x0a, 0x5f, 0xd2, 0x93, 0x22, + 0x7d, 0xaa, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x7d, 0x55, + 0x7f, 0xaa, 0x7e, 0x2a, 0x12, 0x15, 0xd8, 0x22, 0xad, 0x47, 0x7f, + 0x34, 0x7e, 0x30, 0x12, 0x15, 0xd8, 0x7d, 0xff, 0x7f, 0x35, 0x7e, + 0x30, 0x12, 0x15, 0xd8, 0xe4, 0xfd, 0x7f, 0x37, 0x7e, 0x30, 0x12, + 0x15, 0xd8, 0x22, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x22, + 0x8f, 0x82, 0x8e, 0x83, 0xed, 0xf0, 0x22, 0xe4, 0xfc, 0x90, 0x7f, + 0xf0, 0xe0, 0xaf, 0x09, 0x14, 0x60, 0x14, 0x14, 0x60, 0x15, 0x14, + 0x60, 0x16, 0x14, 0x60, 0x17, 0x14, 0x60, 0x18, 0x24, 0x05, 0x70, + 0x16, 0xe4, 0xfc, 0x80, 0x12, 0x7c, 0x01, 0x80, 0x0e, 0x7c, 0x03, + 0x80, 0x0a, 0x7c, 0x07, 0x80, 0x06, 0x7c, 0x0f, 0x80, 0x02, 0x7c, + 0x1f, 0xec, 0x6f, 0xf4, 0x54, 0x1f, 0xfc, 0x90, 0x30, 0x34, 0xe0, + 0x54, 0xe0, 0x4c, 0xfd, 0xa3, 0xe0, 0xfc, 0x43, 0x04, 0x1f, 0x7f, + 0x34, 0x7e, 0x30, 0x12, 0x15, 0xd8, 0xad, 0x04, 0x0f, 0x12, 0x15, + 0xd8, 0xe4, 0xfd, 0x7f, 0x37, 0x02, 0x15, 0xd8, 0x02, 0x15, 0xdf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, + 0x29, 0xe9 +}; diff --git a/sys/dev/usb2/wlan/if_rum2_reg.h b/sys/dev/usb2/wlan/if_rum2_reg.h new file mode 100644 index 000000000000..cc88ef8eadbe --- /dev/null +++ b/sys/dev/usb2/wlan/if_rum2_reg.h @@ -0,0 +1,235 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 Damien Bergamini + * Copyright (c) 2006 Niall O'Higgins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RT2573_NOISE_FLOOR -95 + +#define RT2573_TX_DESC_SIZE (sizeof (struct rum_tx_desc)) +#define RT2573_RX_DESC_SIZE (sizeof (struct rum_rx_desc)) + +#define RT2573_CONFIG_NO 1 +#define RT2573_IFACE_INDEX 0 + +#define RT2573_MCU_CNTL 0x01 +#define RT2573_WRITE_MAC 0x02 +#define RT2573_READ_MAC 0x03 +#define RT2573_WRITE_MULTI_MAC 0x06 +#define RT2573_READ_MULTI_MAC 0x07 +#define RT2573_READ_EEPROM 0x09 +#define RT2573_WRITE_LED 0x0a + +/* + * Control and status registers. + */ +#define RT2573_AIFSN_CSR 0x0400 +#define RT2573_CWMIN_CSR 0x0404 +#define RT2573_CWMAX_CSR 0x0408 +#define RT2573_MCU_CODE_BASE 0x0800 +#define RT2573_HW_BEACON_BASE0 0x2400 +#define RT2573_MAC_CSR0 0x3000 +#define RT2573_MAC_CSR1 0x3004 +#define RT2573_MAC_CSR2 0x3008 +#define RT2573_MAC_CSR3 0x300c +#define RT2573_MAC_CSR4 0x3010 +#define RT2573_MAC_CSR5 0x3014 +#define RT2573_MAC_CSR6 0x3018 +#define RT2573_MAC_CSR7 0x301c +#define RT2573_MAC_CSR8 0x3020 +#define RT2573_MAC_CSR9 0x3024 +#define RT2573_MAC_CSR10 0x3028 +#define RT2573_MAC_CSR11 0x302c +#define RT2573_MAC_CSR12 0x3030 +#define RT2573_MAC_CSR13 0x3034 +#define RT2573_MAC_CSR14 0x3038 +#define RT2573_MAC_CSR15 0x303c +#define RT2573_TXRX_CSR0 0x3040 +#define RT2573_TXRX_CSR1 0x3044 +#define RT2573_TXRX_CSR2 0x3048 +#define RT2573_TXRX_CSR3 0x304c +#define RT2573_TXRX_CSR4 0x3050 +#define RT2573_TXRX_CSR5 0x3054 +#define RT2573_TXRX_CSR6 0x3058 +#define RT2573_TXRX_CSR7 0x305c +#define RT2573_TXRX_CSR8 0x3060 +#define RT2573_TXRX_CSR9 0x3064 +#define RT2573_TXRX_CSR10 0x3068 +#define RT2573_TXRX_CSR11 0x306c +#define RT2573_TXRX_CSR12 0x3070 +#define RT2573_TXRX_CSR13 0x3074 +#define RT2573_TXRX_CSR14 0x3078 +#define RT2573_TXRX_CSR15 0x307c +#define RT2573_PHY_CSR0 0x3080 +#define RT2573_PHY_CSR1 0x3084 +#define RT2573_PHY_CSR2 0x3088 +#define RT2573_PHY_CSR3 0x308c +#define RT2573_PHY_CSR4 0x3090 +#define RT2573_PHY_CSR5 0x3094 +#define RT2573_PHY_CSR6 0x3098 +#define RT2573_PHY_CSR7 0x309c +#define RT2573_SEC_CSR0 0x30a0 +#define RT2573_SEC_CSR1 0x30a4 +#define RT2573_SEC_CSR2 0x30a8 +#define RT2573_SEC_CSR3 0x30ac +#define RT2573_SEC_CSR4 0x30b0 +#define RT2573_SEC_CSR5 0x30b4 +#define RT2573_STA_CSR0 0x30c0 +#define RT2573_STA_CSR1 0x30c4 +#define RT2573_STA_CSR2 0x30c8 +#define RT2573_STA_CSR3 0x30cc +#define RT2573_STA_CSR4 0x30d0 +#define RT2573_STA_CSR5 0x30d4 + + +/* possible flags for register RT2573_MAC_CSR1 */ +#define RT2573_RESET_ASIC (1 << 0) +#define RT2573_RESET_BBP (1 << 1) +#define RT2573_HOST_READY (1 << 2) + +/* possible flags for register MAC_CSR5 */ +#define RT2573_ONE_BSSID 3 + +/* possible flags for register TXRX_CSR0 */ +/* Tx filter flags are in the low 16 bits */ +#define RT2573_AUTO_TX_SEQ (1 << 15) +/* Rx filter flags are in the high 16 bits */ +#define RT2573_DISABLE_RX (1 << 16) +#define RT2573_DROP_CRC_ERROR (1 << 17) +#define RT2573_DROP_PHY_ERROR (1 << 18) +#define RT2573_DROP_CTL (1 << 19) +#define RT2573_DROP_NOT_TO_ME (1 << 20) +#define RT2573_DROP_TODS (1 << 21) +#define RT2573_DROP_VER_ERROR (1 << 22) +#define RT2573_DROP_MULTICAST (1 << 23) +#define RT2573_DROP_BROADCAST (1 << 24) +#define RT2573_DROP_ACKCTS (1 << 25) + +/* possible flags for register TXRX_CSR4 */ +#define RT2573_SHORT_PREAMBLE (1 << 18) +#define RT2573_MRR_ENABLED (1 << 19) +#define RT2573_MRR_CCK_FALLBACK (1 << 22) + +/* possible flags for register TXRX_CSR9 */ +#define RT2573_TSF_TICKING (1 << 16) +#define RT2573_TSF_MODE(x) (((x) & 0x3) << 17) +/* TBTT stands for Target Beacon Transmission Time */ +#define RT2573_ENABLE_TBTT (1 << 19) +#define RT2573_GENERATE_BEACON (1 << 20) + +/* possible flags for register PHY_CSR0 */ +#define RT2573_PA_PE_2GHZ (1 << 16) +#define RT2573_PA_PE_5GHZ (1 << 17) + +/* possible flags for register PHY_CSR3 */ +#define RT2573_BBP_READ (1 << 15) +#define RT2573_BBP_BUSY (1 << 16) +/* possible flags for register PHY_CSR4 */ +#define RT2573_RF_20BIT (20 << 24) +#define RT2573_RF_BUSY (1 << 31) + +/* LED values */ +#define RT2573_LED_RADIO (1 << 8) +#define RT2573_LED_G (1 << 9) +#define RT2573_LED_A (1 << 10) +#define RT2573_LED_ON 0x1e1e +#define RT2573_LED_OFF 0x0 + +#define RT2573_MCU_RUN (1 << 3) + +#define RT2573_SMART_MODE (1 << 0) + +#define RT2573_BBPR94_DEFAULT 6 + +#define RT2573_BBP_WRITE (1 << 15) + +/* dual-band RF */ +#define RT2573_RF_5226 1 +#define RT2573_RF_5225 3 +/* single-band RF */ +#define RT2573_RF_2528 2 +#define RT2573_RF_2527 4 + +#define RT2573_BBP_VERSION 0 + +struct rum_tx_desc { + uint32_t flags; +#define RT2573_TX_BURST (1 << 0) +#define RT2573_TX_VALID (1 << 1) +#define RT2573_TX_MORE_FRAG (1 << 2) +#define RT2573_TX_NEED_ACK (1 << 3) +#define RT2573_TX_TIMESTAMP (1 << 4) +#define RT2573_TX_OFDM (1 << 5) +#define RT2573_TX_IFS_SIFS (1 << 6) +#define RT2573_TX_LONG_RETRY (1 << 7) + uint16_t wme; +#define RT2573_QID(v) (v) +#define RT2573_AIFSN(v) ((v) << 4) +#define RT2573_LOGCWMIN(v) ((v) << 8) +#define RT2573_LOGCWMAX(v) ((v) << 12) + + uint16_t xflags; +#define RT2573_TX_HWSEQ (1 << 12) +#define RT2573_TX_BEACON (1 << 15) /* Internal flag only! */ + + uint8_t plcp_signal; + uint8_t plcp_service; +#define RT2573_PLCP_LENGEXT 0x80 + + uint8_t plcp_length_lo; + uint8_t plcp_length_hi; + + uint32_t iv; + uint32_t eiv; + + uint8_t offset; + uint8_t qid; + uint8_t txpower; +#define RT2573_DEFAULT_TXPOWER 0 + + uint8_t reserved; +} __packed; + +struct rum_rx_desc { + uint32_t flags; +#define RT2573_RX_BUSY (1 << 0) +#define RT2573_RX_DROP (1 << 1) +#define RT2573_RX_CRC_ERROR (1 << 6) +#define RT2573_RX_OFDM (1 << 7) + + uint8_t rate; + uint8_t rssi; + uint8_t reserved1; + uint8_t offset; + uint32_t iv; + uint32_t eiv; + uint32_t reserved2[2]; +} __packed; + +#define RT2573_RF1 0 +#define RT2573_RF2 2 +#define RT2573_RF3 1 +#define RT2573_RF4 3 + +#define RT2573_EEPROM_MACBBP 0x0000 +#define RT2573_EEPROM_ADDRESS 0x0004 +#define RT2573_EEPROM_ANTENNA 0x0020 +#define RT2573_EEPROM_CONFIG2 0x0022 +#define RT2573_EEPROM_BBP_BASE 0x0026 +#define RT2573_EEPROM_TXPOWER 0x0046 +#define RT2573_EEPROM_FREQ_OFFSET 0x005e +#define RT2573_EEPROM_RSSI_2GHZ_OFFSET 0x009a +#define RT2573_EEPROM_RSSI_5GHZ_OFFSET 0x009c diff --git a/sys/dev/usb2/wlan/if_rum2_var.h b/sys/dev/usb2/wlan/if_rum2_var.h new file mode 100644 index 000000000000..8551a4115162 --- /dev/null +++ b/sys/dev/usb2/wlan/if_rum2_var.h @@ -0,0 +1,172 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 Damien Bergamini + * Copyright (c) 2006 Niall O'Higgins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RUM_N_TRANSFER 4 + +struct rum_node { + struct ieee80211_node ni; + struct ieee80211_amrr_node amn; +}; + +#define RUM_NODE(ni) ((struct rum_node *)(ni)) + +struct rum_vap { + struct ieee80211vap vap; + struct ieee80211_beacon_offsets bo; + struct ieee80211_amrr amrr; + + int (*newstate) (struct ieee80211vap *, + enum ieee80211_state, int); +}; + +#define RUM_VAP(vap) ((struct rum_vap *)(vap)) + +struct rum_config_copy_chan { + uint32_t chan_to_ieee; + enum ieee80211_phymode chan_to_mode; + uint8_t chan_is_5ghz:1; + uint8_t chan_is_2ghz:1; + uint8_t chan_is_b:1; + uint8_t chan_is_a:1; + uint8_t chan_is_g:1; + uint8_t unused:3; +}; + +struct rum_config_copy_bss { + uint16_t ni_intval; + uint8_t ni_bssid[IEEE80211_ADDR_LEN]; + uint8_t fixed_rate_none; +}; + +struct rum_config_copy { + struct rum_config_copy_chan ic_curchan; + struct rum_config_copy_chan ic_bsschan; + struct rum_config_copy_bss iv_bss; + + enum ieee80211_opmode ic_opmode; + uint32_t ic_flags; + uint32_t if_flags; + + uint16_t ic_txpowlimit; + uint16_t ic_curmode; + + uint8_t ic_myaddr[IEEE80211_ADDR_LEN]; + uint8_t if_broadcastaddr[IEEE80211_ADDR_LEN]; +}; + +struct rum_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + uint8_t wr_antenna; + uint8_t wr_antsignal; +}; + +#define RT2573_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)) + +struct rum_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; + uint8_t wt_antenna; +}; + +#define RT2573_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA)) + +struct rum_bbp_prom { + uint8_t val; + uint8_t reg; +} __packed; + +struct rum_ifq { + struct mbuf *ifq_head; + struct mbuf *ifq_tail; + uint16_t ifq_len; +}; + +struct rum_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct rum_ifq sc_tx_queue; + struct usb2_config_td sc_config_td; + struct rum_tx_desc sc_tx_desc; + struct rum_rx_desc sc_rx_desc; + struct mtx sc_mtx; + struct usb2_callout sc_watchdog; + struct rum_bbp_prom sc_bbp_prom[16]; + struct rum_rx_radiotap_header sc_rxtap; + struct rum_tx_radiotap_header sc_txtap; + + struct usb2_xfer *sc_xfer[RUM_N_TRANSFER]; + struct ifnet *sc_ifp; + struct usb2_device *sc_udev; + const struct ieee80211_rate_table *sc_rates; + + int (*sc_newstate) + (struct ieee80211com *, enum ieee80211_state, int); + + enum ieee80211_state sc_ns_state; + uint32_t sc_sta[6]; + uint32_t sc_unit; + int sc_ns_arg; + + uint16_t sc_flags; +#define RUM_FLAG_READ_STALL 0x0001 +#define RUM_FLAG_WRITE_STALL 0x0002 +#define RUM_FLAG_LL_READY 0x0008 +#define RUM_FLAG_HL_READY 0x0010 +#define RUM_FLAG_WAIT_COMMAND 0x0020 + uint16_t sc_txtap_len; + uint16_t sc_rxtap_len; + uint16_t sc_last_chan; + + uint8_t sc_txpow[44]; + uint8_t sc_rf_rev; + uint8_t sc_rffreq; + uint8_t sc_ftype; + uint8_t sc_rx_ant; + uint8_t sc_tx_ant; + uint8_t sc_nb_ant; + uint8_t sc_ext_2ghz_lna; + uint8_t sc_ext_5ghz_lna; + uint8_t sc_sifs; + uint8_t sc_bbp17; + uint8_t sc_hw_radio; + uint8_t sc_amrr_timer; + uint8_t sc_beacon_buf[0x800]; + uint8_t sc_myaddr[IEEE80211_ADDR_LEN]; + + int8_t sc_rssi_2ghz_corr; + int8_t sc_rssi_5ghz_corr; + + char sc_name[32]; +}; diff --git a/sys/dev/usb2/wlan/if_ural2.c b/sys/dev/usb2/wlan/if_ural2.c new file mode 100644 index 000000000000..3c2e12331ad1 --- /dev/null +++ b/sys/dev/usb2/wlan/if_ural2.c @@ -0,0 +1,2788 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 + * Damien Bergamini + * + * Copyright (c) 2006, 2008 + * Hans Petter Selasky + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/* + * + * NOTE: all function names beginning like "ural_cfg_" can only + * be called from within the config thread function ! + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Ralink Technology RT2500USB chipset driver + * http://www.ralinktech.com/ + */ + +#include +#include +#include +#include + +#define usb2_config_td_cc ural_config_copy +#define usb2_config_td_softc ural_softc + +#define USB_DEBUG_VAR ural_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if USB_DEBUG +static int ural_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, ural, CTLFLAG_RW, 0, "USB ural"); +SYSCTL_INT(_hw_usb2_ural, OID_AUTO, debug, CTLFLAG_RW, &ural_debug, 0, + "Debug level"); +#endif + +#define URAL_RSSI(rssi) \ + ((rssi) > (RAL_NOISE_FLOOR + RAL_RSSI_CORR) ? \ + ((rssi) - (RAL_NOISE_FLOOR + RAL_RSSI_CORR)) : 0) + +/* prototypes */ + +static device_probe_t ural_probe; +static device_attach_t ural_attach; +static device_detach_t ural_detach; + +static usb2_callback_t ural_bulk_read_callback; +static usb2_callback_t ural_bulk_read_clear_stall_callback; +static usb2_callback_t ural_bulk_write_callback; +static usb2_callback_t ural_bulk_write_clear_stall_callback; + +static usb2_config_td_command_t ural_cfg_first_time_setup; +static usb2_config_td_command_t ural_config_copy; +static usb2_config_td_command_t ural_cfg_scan_start; +static usb2_config_td_command_t ural_cfg_scan_end; +static usb2_config_td_command_t ural_cfg_set_chan; +static usb2_config_td_command_t ural_cfg_enable_tsf_sync; +static usb2_config_td_command_t ural_cfg_update_slot; +static usb2_config_td_command_t ural_cfg_set_txpreamble; +static usb2_config_td_command_t ural_cfg_update_promisc; +static usb2_config_td_command_t ural_cfg_pre_init; +static usb2_config_td_command_t ural_cfg_init; +static usb2_config_td_command_t ural_cfg_pre_stop; +static usb2_config_td_command_t ural_cfg_stop; +static usb2_config_td_command_t ural_cfg_amrr_timeout; +static usb2_config_td_command_t ural_cfg_newstate; + +static void ural_cfg_do_request(struct ural_softc *sc, struct usb2_device_request *req, void *data); +static void ural_cfg_set_testmode(struct ural_softc *sc); +static void ural_cfg_eeprom_read(struct ural_softc *sc, uint16_t addr, void *buf, uint16_t len); +static uint16_t ural_cfg_read(struct ural_softc *sc, uint16_t reg); +static void ural_cfg_read_multi(struct ural_softc *sc, uint16_t reg, void *buf, uint16_t len); +static void ural_cfg_write(struct ural_softc *sc, uint16_t reg, uint16_t val); +static void ural_cfg_write_multi(struct ural_softc *sc, uint16_t reg, void *buf, uint16_t len); +static void ural_cfg_bbp_write(struct ural_softc *sc, uint8_t reg, uint8_t val); +static uint8_t ural_cfg_bbp_read(struct ural_softc *sc, uint8_t reg); +static void ural_cfg_rf_write(struct ural_softc *sc, uint8_t reg, uint32_t val); +static void ural_end_of_commands(struct ural_softc *sc); +static const char *ural_get_rf(int rev); +static void ural_watchdog(void *arg); +static void ural_init_cb(void *arg); +static int ural_ioctl_cb(struct ifnet *ifp, u_long cmd, caddr_t data); +static void ural_start_cb(struct ifnet *ifp); +static int ural_newstate_cb(struct ieee80211vap *ic, enum ieee80211_state nstate, int arg); +static void ural_std_command(struct ieee80211com *ic, usb2_config_td_command_t *func); +static void ural_scan_start_cb(struct ieee80211com *); +static void ural_scan_end_cb(struct ieee80211com *); +static void ural_set_channel_cb(struct ieee80211com *); +static void ural_cfg_disable_rf_tune(struct ural_softc *sc); +static void ural_cfg_set_bssid(struct ural_softc *sc, uint8_t *bssid); +static void ural_cfg_set_macaddr(struct ural_softc *sc, uint8_t *addr); +static void ural_cfg_set_txantenna(struct ural_softc *sc, uint8_t antenna); +static void ural_cfg_set_rxantenna(struct ural_softc *sc, uint8_t antenna); +static void ural_cfg_read_eeprom(struct ural_softc *sc); +static uint8_t ural_cfg_bbp_init(struct ural_softc *sc); +static void ural_cfg_amrr_start(struct ural_softc *sc); +static struct ieee80211vap *ural_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, int opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]); +static void ural_vap_delete(struct ieee80211vap *); +static struct ieee80211_node *ural_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]); +static void ural_newassoc(struct ieee80211_node *, int); +static void ural_cfg_disable_tsf_sync(struct ural_softc *sc); +static void ural_cfg_set_run(struct ural_softc *sc, struct usb2_config_td_cc *cc); +static void ural_fill_write_queue(struct ural_softc *sc); +static void ural_tx_clean_queue(struct ural_softc *sc); +static void ural_tx_freem(struct mbuf *m); +static void ural_tx_mgt(struct ural_softc *sc, struct mbuf *m, struct ieee80211_node *ni); +static struct ieee80211vap *ural_get_vap(struct ural_softc *sc); +static void ural_tx_bcn(struct ural_softc *sc); +static void ural_tx_data(struct ural_softc *sc, struct mbuf *m, struct ieee80211_node *ni); +static void ural_tx_prot(struct ural_softc *sc, const struct mbuf *m, struct ieee80211_node *ni, uint8_t prot, uint16_t rate); +static void ural_tx_raw(struct ural_softc *sc, struct mbuf *m, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params); +static int ural_raw_xmit_cb(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params); +static void ural_setup_desc_and_tx(struct ural_softc *sc, struct mbuf *m, uint32_t flags, uint16_t rate); +static void ural_update_mcast_cb(struct ifnet *ifp); +static void ural_update_promisc_cb(struct ifnet *ifp); + +/* various supported device vendors/products */ +static const struct usb2_device_id ural_devs[] = { + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_WL167G, 0)}, + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_RALINK_RT2570, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7051, 0)}, + {USB_VPI(USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_HU200TS, 0)}, + {USB_VPI(USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54G, 0)}, + {USB_VPI(USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GP, 0)}, + {USB_VPI(USB_VENDOR_CONCEPTRONIC2, USB_PRODUCT_CONCEPTRONIC2_C54RU, 0)}, + {USB_VPI(USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLG122, 0)}, + {USB_VPI(USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GN54G, 0)}, + {USB_VPI(USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWBKG, 0)}, + {USB_VPI(USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254, 0)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54, 0)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54AI, 0)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54YB, 0)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_NINWIFI, 0)}, + {USB_VPI(USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570, 0)}, + {USB_VPI(USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570_2, 0)}, + {USB_VPI(USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570_3, 0)}, + {USB_VPI(USB_VENDOR_NOVATECH, USB_PRODUCT_NOVATECH_NV902, 0)}, + {USB_VPI(USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570, 0)}, + {USB_VPI(USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570_2, 0)}, + {USB_VPI(USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570_3, 0)}, + {USB_VPI(USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2573, 0)}, + {USB_VPI(USB_VENDOR_SIEMENS2, USB_PRODUCT_SIEMENS2_WL54G, 0)}, + {USB_VPI(USB_VENDOR_SMC, USB_PRODUCT_SMC_2862WG, 0)}, + {USB_VPI(USB_VENDOR_SPHAIRON, USB_PRODUCT_SPHAIRON_UB801R, 0)}, + {USB_VPI(USB_VENDOR_SURECOM, USB_PRODUCT_SURECOM_RT2570, 0)}, + {USB_VPI(USB_VENDOR_VTECH, USB_PRODUCT_VTECH_RT2570, 0)}, + {USB_VPI(USB_VENDOR_ZINWELL, USB_PRODUCT_ZINWELL_RT2570, 0)}, +}; + +/* + * Default values for MAC registers; values taken from + * the reference driver: + */ +struct ural_def_mac { + uint16_t reg; + uint16_t val; +}; + +static const struct ural_def_mac ural_def_mac[] = { + {RAL_TXRX_CSR5, 0x8c8d}, + {RAL_TXRX_CSR6, 0x8b8a}, + {RAL_TXRX_CSR7, 0x8687}, + {RAL_TXRX_CSR8, 0x0085}, + {RAL_MAC_CSR13, 0x1111}, + {RAL_MAC_CSR14, 0x1e11}, + {RAL_TXRX_CSR21, 0xe78f}, + {RAL_MAC_CSR9, 0xff1d}, + {RAL_MAC_CSR11, 0x0002}, + {RAL_MAC_CSR22, 0x0053}, + {RAL_MAC_CSR15, 0x0000}, + {RAL_MAC_CSR8, RAL_FRAME_SIZE}, + {RAL_TXRX_CSR19, 0x0000}, + {RAL_TXRX_CSR18, 0x005a}, + {RAL_PHY_CSR2, 0x0000}, + {RAL_TXRX_CSR0, 0x1ec0}, + {RAL_PHY_CSR4, 0x000f} +}; + +/* + * Default values for BBP registers; values taken from the reference driver. + */ +struct ural_def_bbp { + uint8_t reg; + uint8_t val; +}; + +static const struct ural_def_bbp ural_def_bbp[] = { + {3, 0x02}, + {4, 0x19}, + {14, 0x1c}, + {15, 0x30}, + {16, 0xac}, + {17, 0x48}, + {18, 0x18}, + {19, 0xff}, + {20, 0x1e}, + {21, 0x08}, + {22, 0x08}, + {23, 0x08}, + {24, 0x80}, + {25, 0x50}, + {26, 0x08}, + {27, 0x23}, + {30, 0x10}, + {31, 0x2b}, + {32, 0xb9}, + {34, 0x12}, + {35, 0x50}, + {39, 0xc4}, + {40, 0x02}, + {41, 0x60}, + {53, 0x10}, + {54, 0x18}, + {56, 0x08}, + {57, 0x10}, + {58, 0x08}, + {61, 0x60}, + {62, 0x10}, + {75, 0xff} +}; + +/* + * Default values for RF register R2 indexed by channel numbers. + */ +static const uint32_t ural_rf2522_r2[] = { + 0x307f6, 0x307fb, 0x30800, 0x30805, 0x3080a, 0x3080f, 0x30814, + 0x30819, 0x3081e, 0x30823, 0x30828, 0x3082d, 0x30832, 0x3083e +}; + +static const uint32_t ural_rf2523_r2[] = { + 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, + 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 +}; + +static const uint32_t ural_rf2524_r2[] = { + 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, + 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 +}; + +static const uint32_t ural_rf2525_r2[] = { + 0x20327, 0x20328, 0x20329, 0x2032a, 0x2032b, 0x2032c, 0x2032d, + 0x2032e, 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20346 +}; + +static const uint32_t ural_rf2525_hi_r2[] = { + 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20344, 0x20345, + 0x20346, 0x20347, 0x20348, 0x20349, 0x2034a, 0x2034b, 0x2034e +}; + +static const uint32_t ural_rf2525e_r2[] = { + 0x2044d, 0x2044e, 0x2044f, 0x20460, 0x20461, 0x20462, 0x20463, + 0x20464, 0x20465, 0x20466, 0x20467, 0x20468, 0x20469, 0x2046b +}; + +static const uint32_t ural_rf2526_hi_r2[] = { + 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d, 0x0022d, + 0x0022e, 0x0022e, 0x0022f, 0x0022d, 0x00240, 0x00240, 0x00241 +}; + +static const uint32_t ural_rf2526_r2[] = { + 0x00226, 0x00227, 0x00227, 0x00228, 0x00228, 0x00229, 0x00229, + 0x0022a, 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d +}; + +/* + * For dual-band RF, RF registers R1 and R4 also depend on channel number; + * values taken from the reference driver. + */ +struct ural_rf5222 { + uint8_t chan; + uint32_t r1; + uint32_t r2; + uint32_t r4; +}; + +static const struct ural_rf5222 ural_rf5222[] = { + {1, 0x08808, 0x0044d, 0x00282}, + {2, 0x08808, 0x0044e, 0x00282}, + {3, 0x08808, 0x0044f, 0x00282}, + {4, 0x08808, 0x00460, 0x00282}, + {5, 0x08808, 0x00461, 0x00282}, + {6, 0x08808, 0x00462, 0x00282}, + {7, 0x08808, 0x00463, 0x00282}, + {8, 0x08808, 0x00464, 0x00282}, + {9, 0x08808, 0x00465, 0x00282}, + {10, 0x08808, 0x00466, 0x00282}, + {11, 0x08808, 0x00467, 0x00282}, + {12, 0x08808, 0x00468, 0x00282}, + {13, 0x08808, 0x00469, 0x00282}, + {14, 0x08808, 0x0046b, 0x00286}, + + {36, 0x08804, 0x06225, 0x00287}, + {40, 0x08804, 0x06226, 0x00287}, + {44, 0x08804, 0x06227, 0x00287}, + {48, 0x08804, 0x06228, 0x00287}, + {52, 0x08804, 0x06229, 0x00287}, + {56, 0x08804, 0x0622a, 0x00287}, + {60, 0x08804, 0x0622b, 0x00287}, + {64, 0x08804, 0x0622c, 0x00287}, + + {100, 0x08804, 0x02200, 0x00283}, + {104, 0x08804, 0x02201, 0x00283}, + {108, 0x08804, 0x02202, 0x00283}, + {112, 0x08804, 0x02203, 0x00283}, + {116, 0x08804, 0x02204, 0x00283}, + {120, 0x08804, 0x02205, 0x00283}, + {124, 0x08804, 0x02206, 0x00283}, + {128, 0x08804, 0x02207, 0x00283}, + {132, 0x08804, 0x02208, 0x00283}, + {136, 0x08804, 0x02209, 0x00283}, + {140, 0x08804, 0x0220a, 0x00283}, + + {149, 0x08808, 0x02429, 0x00281}, + {153, 0x08808, 0x0242b, 0x00281}, + {157, 0x08808, 0x0242d, 0x00281}, + {161, 0x08808, 0x0242f, 0x00281} +}; + +static const struct usb2_config ural_config[URAL_N_TRANSFER] = { + [0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE + 4), + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &ural_bulk_write_callback, + .mh.timeout = 5000, /* ms */ + }, + + [1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = (RAL_FRAME_SIZE + RAL_RX_DESC_SIZE), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &ural_bulk_read_callback, + }, + + [2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ural_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.callback = &ural_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static devclass_t ural_devclass; + +static device_method_t ural_methods[] = { + DEVMETHOD(device_probe, ural_probe), + DEVMETHOD(device_attach, ural_attach), + DEVMETHOD(device_detach, ural_detach), + {0, 0} +}; + +static driver_t ural_driver = { + .name = "ural", + .methods = ural_methods, + .size = sizeof(struct ural_softc), +}; + +DRIVER_MODULE(ural, ushub, ural_driver, ural_devclass, NULL, 0); +MODULE_DEPEND(ural, usb2_wlan, 1, 1, 1); +MODULE_DEPEND(ural, usb2_core, 1, 1, 1); +MODULE_DEPEND(ural, wlan, 1, 1, 1); +MODULE_DEPEND(ural, wlan_amrr, 1, 1, 1); + +static int +ural_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != RAL_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(ural_devs, sizeof(ural_devs), uaa)); +} + +static int +ural_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ural_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + device_set_usb2_desc(dev); + + mtx_init(&sc->sc_mtx, "ural lock", MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + sc->sc_udev = uaa->device; + sc->sc_unit = device_get_unit(dev); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + iface_index = RAL_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, + &iface_index, sc->sc_xfer, ural_config, + URAL_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "could not allocate USB transfers, " + "err=%s\n", usb2_errstr(error)); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + &ural_end_of_commands, + sizeof(struct usb2_config_td_cc), 24); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &ural_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + ural_watchdog(sc); + + return (0); /* success */ + +detach: + ural_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ural_detach(device_t dev) +{ + struct ural_softc *sc = device_get_softc(dev); + struct ieee80211com *ic; + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + ural_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + ic = ifp->if_l2com; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, URAL_N_TRANSFER); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + bpfdetach(ifp); + ieee80211_ifdetach(ic); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +/*========================================================================* + * REGISTER READ / WRITE WRAPPER ROUTINES + *========================================================================*/ + +static void +ural_cfg_do_request(struct ural_softc *sc, struct usb2_device_request *req, + void *data) +{ + uint16_t length; + usb2_error_t err; + +repeat: + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTF("device request failed, err=%s " + "(ignored)\n", usb2_errstr(err)); + + /* wait a little before next try */ + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 4)) { + goto error; + } + /* try until we are detached */ + goto repeat; + +error: + /* the device has been detached */ + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +static void +ural_cfg_set_testmode(struct ural_softc *sc) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_VENDOR_REQUEST; + USETW(req.wValue, 4); + USETW(req.wIndex, 1); + USETW(req.wLength, 0); + + ural_cfg_do_request(sc, &req, NULL); + return; +} + +static void +ural_cfg_eeprom_read(struct ural_softc *sc, uint16_t addr, + void *buf, uint16_t len) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_EEPROM; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + ural_cfg_do_request(sc, &req, buf); + return; +} + +static uint16_t +ural_cfg_read(struct ural_softc *sc, uint16_t reg) +{ + struct usb2_device_request req; + uint16_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, sizeof(val)); + + ural_cfg_do_request(sc, &req, &val); + + return (le16toh(val)); +} + +static void +ural_cfg_read_multi(struct ural_softc *sc, uint16_t reg, + void *buf, uint16_t len) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + ural_cfg_do_request(sc, &req, buf); + return; +} + +static void +ural_cfg_write(struct ural_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_WRITE_MAC; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + ural_cfg_do_request(sc, &req, NULL); + return; +} + +static void +ural_cfg_write_multi(struct ural_softc *sc, uint16_t reg, + void *buf, uint16_t len) +{ + struct usb2_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_WRITE_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + ural_cfg_do_request(sc, &req, buf); + return; +} + +static uint8_t +ural_cfg_bbp_disbusy(struct ural_softc *sc) +{ + uint16_t tmp; + uint8_t to; + + for (to = 0;; to++) { + if (to < 100) { + tmp = ural_cfg_read(sc, RAL_PHY_CSR8); + tmp &= RAL_BBP_BUSY; + + if (tmp == 0) { + return (0); + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + break; + } + } else { + break; + } + } + DPRINTF("could not disbusy BBP\n"); + return (1); /* failure */ +} + +static void +ural_cfg_bbp_write(struct ural_softc *sc, uint8_t reg, uint8_t val) +{ + uint16_t tmp; + + if (ural_cfg_bbp_disbusy(sc)) { + return; + } + tmp = (reg << 8) | val; + ural_cfg_write(sc, RAL_PHY_CSR7, tmp); + return; +} + +static uint8_t +ural_cfg_bbp_read(struct ural_softc *sc, uint8_t reg) +{ + uint16_t val; + + if (ural_cfg_bbp_disbusy(sc)) { + return (0); + } + val = RAL_BBP_WRITE | (reg << 8); + ural_cfg_write(sc, RAL_PHY_CSR7, val); + + if (ural_cfg_bbp_disbusy(sc)) { + return (0); + } + return (ural_cfg_read(sc, RAL_PHY_CSR7) & 0xff); +} + +static void +ural_cfg_rf_write(struct ural_softc *sc, uint8_t reg, uint32_t val) +{ + uint32_t tmp; + uint8_t to; + + reg &= 3; + + /* remember last written value */ + sc->sc_rf_regs[reg] = val; + + for (to = 0;; to++) { + if (to < 100) { + tmp = ural_cfg_read(sc, RAL_PHY_CSR10); + + if (!(tmp & RAL_RF_LOBUSY)) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + return; + } + } else { + DPRINTF("could not write to RF\n"); + return; + } + } + + tmp = RAL_RF_BUSY | RAL_RF_20BIT | ((val & 0xfffff) << 2) | reg; + ural_cfg_write(sc, RAL_PHY_CSR9, tmp & 0xffff); + ural_cfg_write(sc, RAL_PHY_CSR10, tmp >> 16); + + DPRINTFN(16, "RF R[%u] <- 0x%05x\n", reg, val & 0xfffff); + return; +} + +static void +ural_cfg_first_time_setup(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ieee80211com *ic; + struct ifnet *ifp; + uint8_t bands; + + /* setup RX tap header */ + sc->sc_rxtap_len = sizeof(sc->sc_rxtap); + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(RAL_RX_RADIOTAP_PRESENT); + + /* setup TX tap header */ + sc->sc_txtap_len = sizeof(sc->sc_txtap); + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(RAL_TX_RADIOTAP_PRESENT); + + /* retrieve RT2570 rev. no */ + sc->sc_asic_rev = ural_cfg_read(sc, RAL_MAC_CSR0); + + /* retrieve MAC address and various other things from EEPROM */ + ural_cfg_read_eeprom(sc); + + printf("%s: MAC/BBP RT2570 (rev 0x%02x), RF %s\n", + sc->sc_name, sc->sc_asic_rev, ural_get_rf(sc->sc_rf_rev)); + + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_IEEE80211); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + DPRINTFN(0, "could not if_alloc()!\n"); + goto done; + } + sc->sc_evilhack = ifp; + sc->sc_ifp = ifp; + ic = ifp->if_l2com; + + ifp->if_softc = sc; + if_initname(ifp, "ural", sc->sc_unit); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = &ural_init_cb; + ifp->if_ioctl = &ural_ioctl_cb; + ifp->if_start = &ural_start_cb; + ifp->if_watchdog = NULL; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + bcopy(sc->sc_myaddr, ic->ic_myaddr, sizeof(ic->ic_myaddr)); + + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode supported */ + | IEEE80211_C_IBSS /* IBSS mode supported */ + | IEEE80211_C_MONITOR /* monitor mode supported */ + | IEEE80211_C_HOSTAP /* HostAp mode supported */ + | IEEE80211_C_TXPMGT /* tx power management */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* bg scanning supported */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + + if (sc->sc_rf_rev == RAL_RF_5222) { + setbit(&bands, IEEE80211_MODE_11A); + } + ieee80211_init_channels(ic, NULL, &bands); + + mtx_unlock(&sc->sc_mtx); + + ieee80211_ifattach(ic); + + mtx_lock(&sc->sc_mtx); + + ic->ic_newassoc = &ural_newassoc; + ic->ic_raw_xmit = &ural_raw_xmit_cb; + ic->ic_node_alloc = &ural_node_alloc; + ic->ic_update_mcast = &ural_update_mcast_cb; + ic->ic_update_promisc = &ural_update_promisc_cb; + ic->ic_scan_start = &ural_scan_start_cb; + ic->ic_scan_end = &ural_scan_end_cb; + ic->ic_set_channel = &ural_set_channel_cb; + ic->ic_vap_create = &ural_vap_create; + ic->ic_vap_delete = &ural_vap_delete; + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); + + mtx_unlock(&sc->sc_mtx); + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof(struct ieee80211_frame) + sizeof(sc->sc_txtap)); + + if (bootverbose) { + ieee80211_announce(ic); + } + mtx_lock(&sc->sc_mtx); +done: + return; +} + +static void +ural_end_of_commands(struct ural_softc *sc) +{ + sc->sc_flags &= ~URAL_FLAG_WAIT_COMMAND; + + /* start write transfer, if not started */ + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +ural_config_copy_chan(struct ural_config_copy_chan *cc, + struct ieee80211com *ic, struct ieee80211_channel *c) +{ + if (!c) + return; + cc->chan_to_ieee = + ieee80211_chan2ieee(ic, c); + if (c != IEEE80211_CHAN_ANYC) { + cc->chan_to_mode = + ieee80211_chan2mode(c); + if (IEEE80211_IS_CHAN_B(c)) + cc->chan_is_b = 1; + if (IEEE80211_IS_CHAN_A(c)) + cc->chan_is_a = 1; + if (IEEE80211_IS_CHAN_2GHZ(c)) + cc->chan_is_2ghz = 1; + if (IEEE80211_IS_CHAN_5GHZ(c)) + cc->chan_is_5ghz = 1; + if (IEEE80211_IS_CHAN_ANYG(c)) + cc->chan_is_g = 1; + } + return; +} + +static void +ural_config_copy(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp; + struct ieee80211com *ic; + struct ieee80211_node *ni; + struct ieee80211vap *vap; + const struct ieee80211_txparam *tp; + + bzero(cc, sizeof(*cc)); + + ifp = sc->sc_ifp; + if (ifp) { + cc->if_flags = ifp->if_flags; + bcopy(ifp->if_broadcastaddr, cc->if_broadcastaddr, + sizeof(cc->if_broadcastaddr)); + + ic = ifp->if_l2com; + if (ic) { + ural_config_copy_chan(&cc->ic_curchan, ic, ic->ic_curchan); + ural_config_copy_chan(&cc->ic_bsschan, ic, ic->ic_bsschan); + vap = TAILQ_FIRST(&ic->ic_vaps); + if (vap) { + ni = vap->iv_bss; + if (ni) { + cc->iv_bss.ni_intval = ni->ni_intval; + bcopy(ni->ni_bssid, cc->iv_bss.ni_bssid, + sizeof(cc->iv_bss.ni_bssid)); + } + tp = vap->iv_txparms + cc->ic_bsschan.chan_to_mode; + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) { + cc->iv_bss.fixed_rate_none = 1; + } + } + cc->ic_opmode = ic->ic_opmode; + cc->ic_flags = ic->ic_flags; + cc->ic_txpowlimit = ic->ic_txpowlimit; + cc->ic_curmode = ic->ic_curmode; + + bcopy(ic->ic_myaddr, cc->ic_myaddr, + sizeof(cc->ic_myaddr)); + } + } + sc->sc_flags |= URAL_FLAG_WAIT_COMMAND; + return; +} + +static const char * +ural_get_rf(int rev) +{ + switch (rev) { + case RAL_RF_2522:return "RT2522"; + case RAL_RF_2523: + return "RT2523"; + case RAL_RF_2524: + return "RT2524"; + case RAL_RF_2525: + return "RT2525"; + case RAL_RF_2525E: + return "RT2525e"; + case RAL_RF_2526: + return "RT2526"; + case RAL_RF_5222: + return "RT5222"; + default: + return "unknown"; + } +} + +/*------------------------------------------------------------------------* + * ural_bulk_read_callback - data read "thread" + *------------------------------------------------------------------------*/ +static void +ural_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct ural_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_node *ni; + struct mbuf *m = NULL; + uint32_t flags; + uint32_t max_len; + uint32_t real_len; + uint8_t rssi = 0; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(15, "rx done, actlen=%d\n", xfer->actlen); + + if (xfer->actlen < RAL_RX_DESC_SIZE) { + DPRINTF("too short transfer, " + "%d bytes\n", xfer->actlen); + ifp->if_ierrors++; + goto tr_setup; + } + max_len = (xfer->actlen - RAL_RX_DESC_SIZE); + + usb2_copy_out(xfer->frbuffers, max_len, + &sc->sc_rx_desc, RAL_RX_DESC_SIZE); + + flags = le32toh(sc->sc_rx_desc.flags); + + if (flags & (RAL_RX_PHY_ERROR | RAL_RX_CRC_ERROR)) { + /* + * This should not happen since we did not + * request to receive those frames when we + * filled RAL_TXRX_CSR2: + */ + DPRINTFN(6, "PHY or CRC error\n"); + ifp->if_ierrors++; + goto tr_setup; + } + if (max_len > MCLBYTES) { + max_len = MCLBYTES; + } + real_len = (flags >> 16) & 0xfff; + + if (real_len > max_len) { + DPRINTF("invalid length in RX " + "descriptor, %u bytes, received %u bytes\n", + real_len, max_len); + ifp->if_ierrors++; + goto tr_setup; + } + /* ieee80211_input() will check if the mbuf is too short */ + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + + if (m == NULL) { + DPRINTF("could not allocate mbuf\n"); + ifp->if_ierrors++; + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, m->m_data, max_len); + + /* finalize mbuf */ + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = real_len; + + DPRINTF("real length=%d bytes\n", real_len); + + rssi = URAL_RSSI(sc->sc_rx_desc.rssi); + + if (bpf_peers_present(ifp->if_bpf)) { + struct ural_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = IEEE80211_RADIOTAP_F_FCS; + tap->wr_rate = ieee80211_plcp2rate(sc->sc_rx_desc.rate, + (sc->sc_rx_desc.flags & htole32(RAL_RX_OFDM)) ? + IEEE80211_T_OFDM : IEEE80211_T_CCK); + + tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_antenna = sc->sc_rx_ant; + tap->wr_antsignal = rssi; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + /* Strip trailing 802.11 MAC FCS. */ + m_adj(m, -IEEE80211_CRC_LEN); + + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_flags & URAL_FLAG_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[3]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "ieee80211_input" here, and not some lines up! + */ + if (m) { + mtx_unlock(&sc->sc_mtx); + + ni = ieee80211_find_rxnode(ic, (void *)(m->m_data)); + + if (ni) { + /* send the frame to the 802.11 layer */ + if (ieee80211_input(ni, m, rssi, RAL_NOISE_FLOOR, 0)) { + /* ignore */ + } + /* node is no longer needed */ + ieee80211_free_node(ni); + } else { + /* broadcast */ + if (ieee80211_input_all(ic, m, rssi, RAL_NOISE_FLOOR, 0)) { + /* ignore */ + } + } + + mtx_lock(&sc->sc_mtx); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= URAL_FLAG_READ_STALL; + usb2_transfer_start(sc->sc_xfer[3]); + } + return; + + } +} + +static void +ural_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ural_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[1]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~URAL_FLAG_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static uint8_t +ural_plcp_signal(uint16_t rate) +{ + ; /* indent fix */ + switch (rate) { + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: + return (0x0); + case 4: + return (0x1); + case 11: + return (0x2); + case 22: + return (0x3); + + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: + return (0xb); + case 18: + return (0xf); + case 24: + return (0xa); + case 36: + return (0xe); + case 48: + return (0x9); + case 72: + return (0xd); + case 96: + return (0x8); + case 108: + return (0xc); + + /* XXX unsupported/unknown rate */ + default: + return (0xff); + } +} + +/* + * We assume that "m->m_pkthdr.rcvif" is pointing to the "ni" that + * should be freed, when "ural_setup_desc_and_tx" is called. + */ +static void +ural_setup_desc_and_tx(struct ural_softc *sc, struct mbuf *m, + uint32_t flags, uint16_t rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct mbuf *mm; + enum ieee80211_phytype phytype; + uint16_t plcp_length; + uint16_t len; + uint8_t remainder; + + DPRINTF("in\n"); + + if (sc->sc_tx_queue.ifq_len >= IFQ_MAXLEN) { + /* free packet */ + ural_tx_freem(m); + ifp->if_oerrors++; + return; + } + if (!((sc->sc_flags & URAL_FLAG_LL_READY) && + (sc->sc_flags & URAL_FLAG_HL_READY))) { + /* free packet */ + ural_tx_freem(m); + ifp->if_oerrors++; + return; + } + if (rate < 2) { + DPRINTF("rate < 2!\n"); + + /* avoid division by zero */ + rate = 2; + } + ic->ic_lastdata = ticks; + + if (bpf_peers_present(ifp->if_bpf)) { + struct ural_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wt_antenna = sc->sc_tx_ant; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m); + } + len = m->m_pkthdr.len; + + sc->sc_tx_desc.flags = htole32(flags); + sc->sc_tx_desc.flags |= htole32(RAL_TX_NEWSEQ); + sc->sc_tx_desc.flags |= htole32(len << 16); + + sc->sc_tx_desc.wme = htole16(RAL_AIFSN(2) | + RAL_LOGCWMIN(3) | + RAL_LOGCWMAX(5) | + RAL_IVOFFSET(sizeof(struct ieee80211_frame))); + + /* setup PLCP fields */ + sc->sc_tx_desc.plcp_signal = ural_plcp_signal(rate); + sc->sc_tx_desc.plcp_service = 4; + + len += IEEE80211_CRC_LEN; + + phytype = ieee80211_rate2phytype(sc->sc_rates, rate); + + if (phytype == IEEE80211_T_OFDM) { + sc->sc_tx_desc.flags |= htole32(RAL_TX_OFDM); + + plcp_length = len & 0xfff; + sc->sc_tx_desc.plcp_length_hi = plcp_length >> 6; + sc->sc_tx_desc.plcp_length_lo = plcp_length & 0x3f; + + } else { + plcp_length = ((16 * len) + rate - 1) / rate; + if (rate == 22) { + remainder = (16 * len) % 22; + if ((remainder != 0) && (remainder < 7)) { + sc->sc_tx_desc.plcp_service |= + RAL_PLCP_LENGEXT; + } + } + sc->sc_tx_desc.plcp_length_hi = plcp_length >> 8; + sc->sc_tx_desc.plcp_length_lo = plcp_length & 0xff; + + if ((rate != 2) && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) { + sc->sc_tx_desc.plcp_signal |= 0x08; + } + } + + sc->sc_tx_desc.iv = 0; + sc->sc_tx_desc.eiv = 0; + + if (sizeof(sc->sc_tx_desc) > MHLEN) { + DPRINTF("No room for header structure!\n"); + ural_tx_freem(m); + return; + } + mm = m_gethdr(M_NOWAIT, MT_DATA); + if (mm == NULL) { + DPRINTF("Could not allocate header mbuf!\n"); + ural_tx_freem(m); + return; + } + DPRINTF(" %zu %u (out)\n", sizeof(sc->sc_tx_desc), m->m_pkthdr.len); + + bcopy(&sc->sc_tx_desc, mm->m_data, sizeof(sc->sc_tx_desc)); + mm->m_len = sizeof(sc->sc_tx_desc); + + mm->m_next = m; + mm->m_pkthdr.len = mm->m_len + m->m_pkthdr.len; + mm->m_pkthdr.rcvif = NULL; + + /* start write transfer, if not started */ + _IF_ENQUEUE(&sc->sc_tx_queue, mm); + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +ural_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct ural_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint16_t temp_len; + uint8_t align; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete, %d bytes\n", xfer->actlen); + + ifp->if_opackets++; + + case USB_ST_SETUP: + if (sc->sc_flags & URAL_FLAG_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[2]); + break; + } + if (sc->sc_flags & URAL_FLAG_WAIT_COMMAND) { + /* + * don't send anything while a command is pending ! + */ + break; + } + ural_fill_write_queue(sc); + + _IF_DEQUEUE(&sc->sc_tx_queue, m); + + if (m) { + + if (m->m_pkthdr.len > (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE)) { + DPRINTFN(0, "data overflow, %u bytes\n", + m->m_pkthdr.len); + m->m_pkthdr.len = (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE); + } + usb2_m_copy_in(xfer->frbuffers, 0, + m, 0, m->m_pkthdr.len); + + /* compute transfer length */ + temp_len = m->m_pkthdr.len; + + /* make transfer length 16-bit aligned */ + align = (temp_len & 1); + + /* check if we need to add two extra bytes */ + if (((temp_len + align) % 64) == 0) { + align += 2; + } + /* check if we need to align length */ + if (align != 0) { + /* zero the extra bytes */ + usb2_bzero(xfer->frbuffers, temp_len, align); + temp_len += align; + } + DPRINTFN(11, "sending frame len=%u xferlen=%u\n", + m->m_pkthdr.len, temp_len); + + xfer->frlengths[0] = temp_len; + + usb2_start_hardware(xfer); + + /* free mbuf and node */ + ural_tx_freem(m); + } + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= URAL_FLAG_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[2]); + } + ifp->if_oerrors++; + break; + } + return; +} + +static void +ural_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct ural_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[0]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~URAL_FLAG_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +ural_watchdog(void *arg) +{ + struct ural_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + if (sc->sc_amrr_timer) { + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, + &ural_cfg_amrr_timeout, 0, 0); + } + usb2_callout_reset(&sc->sc_watchdog, + hz, &ural_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +/*========================================================================* + * IF-net callbacks + *========================================================================*/ + +static void +ural_init_cb(void *arg) +{ + struct ural_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &ural_cfg_pre_init, + &ural_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static int +ural_ioctl_cb(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct ural_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + int error; + + switch (cmd) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + usb2_config_td_queue_command + (&sc->sc_config_td, &ural_cfg_pre_init, + &ural_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &ural_cfg_pre_stop, + &ural_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + error = 0; + break; + + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + error = ifmedia_ioctl(ifp, (void *)data, &ic->ic_media, cmd); + break; + + default: + error = ether_ioctl(ifp, cmd, data); + } + return (error); +} + +static void +ural_start_cb(struct ifnet *ifp) +{ + struct ural_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + /* start write transfer, if not started */ + usb2_transfer_start(sc->sc_xfer[0]); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +ural_cfg_newstate(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ural_vap *uvp = URAL_VAP(vap); + enum ieee80211_state ostate; + enum ieee80211_state nstate; + int arg; + + ostate = vap->iv_state; + nstate = sc->sc_ns_state; + arg = sc->sc_ns_arg; + + if (ostate == IEEE80211_S_INIT) { + /* We are leaving INIT. TSF sync should be off. */ + ural_cfg_disable_tsf_sync(sc); + } + switch (nstate) { + case IEEE80211_S_INIT: + break; + + case IEEE80211_S_RUN: + ural_cfg_set_run(sc, cc); + break; + + default: + break; + } + + mtx_unlock(&sc->sc_mtx); + IEEE80211_LOCK(ic); + uvp->newstate(vap, nstate, arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, nstate, arg); + IEEE80211_UNLOCK(ic); + mtx_lock(&sc->sc_mtx); + return; +} + +static int +ural_newstate_cb(struct ieee80211vap *vap, + enum ieee80211_state nstate, int arg) +{ + struct ural_vap *uvp = URAL_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct ural_softc *sc = ic->ic_ifp->if_softc; + + DPRINTF("setting new state: %d\n", nstate); + + /* Special case - cannot defer this call and cannot block ! */ + if (nstate == IEEE80211_S_INIT) { + /* stop timers */ + mtx_lock(&sc->sc_mtx); + sc->sc_amrr_timer = 0; + mtx_unlock(&sc->sc_mtx); + return (uvp->newstate(vap, nstate, arg)); + } + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + mtx_unlock(&sc->sc_mtx); + return (0); /* nothing to do */ + } + /* store next state */ + sc->sc_ns_state = nstate; + sc->sc_ns_arg = arg; + + /* stop timers */ + sc->sc_amrr_timer = 0; + + /* + * USB configuration can only be done from the USB configuration + * thread: + */ + usb2_config_td_queue_command + (&sc->sc_config_td, &ural_config_copy, + &ural_cfg_newstate, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return (EINPROGRESS); +} + +static void +ural_std_command(struct ieee80211com *ic, usb2_config_td_command_t *func) +{ + struct ural_softc *sc = ic->ic_ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); + + usb2_config_td_queue_command + (&sc->sc_config_td, &ural_config_copy, func, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +ural_scan_start_cb(struct ieee80211com *ic) +{ + ural_std_command(ic, &ural_cfg_scan_start); + return; +} + +static void +ural_scan_end_cb(struct ieee80211com *ic) +{ + ural_std_command(ic, &ural_cfg_scan_end); + return; +} + +static void +ural_set_channel_cb(struct ieee80211com *ic) +{ + ural_std_command(ic, &ural_cfg_set_chan); + return; +} + +/*========================================================================* + * configure sub-routines, ural_cfg_xxx + *========================================================================*/ + +static void +ural_cfg_scan_start(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* abort TSF synchronization */ + ural_cfg_disable_tsf_sync(sc); + ural_cfg_set_bssid(sc, cc->if_broadcastaddr); + return; +} + +static void +ural_cfg_scan_end(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* enable TSF synchronization */ + ural_cfg_enable_tsf_sync(sc, cc, 0); + ural_cfg_set_bssid(sc, cc->iv_bss.ni_bssid); + return; +} + +static void +ural_cfg_set_chan(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + enum { + N_RF5222 = (sizeof(ural_rf5222) / sizeof(ural_rf5222[0])), + }; + uint32_t i; + uint32_t chan; + uint8_t power; + uint8_t tmp; + + chan = cc->ic_curchan.chan_to_ieee; + + if ((chan == 0) || + (chan == IEEE80211_CHAN_ANY)) { + /* nothing to do */ + return; + } + if (cc->ic_curchan.chan_is_2ghz) + power = min(sc->sc_txpow[chan - 1], 31); + else + power = 31; + + /* adjust txpower using ifconfig settings */ + power -= (100 - cc->ic_txpowlimit) / 8; + + DPRINTFN(3, "setting channel to %u, " + "tx-power to %u\n", chan, power); + + switch (sc->sc_rf_rev) { + case RAL_RF_2522: + ural_cfg_rf_write(sc, RAL_RF1, 0x00814); + ural_cfg_rf_write(sc, RAL_RF2, ural_rf2522_r2[chan - 1]); + ural_cfg_rf_write(sc, RAL_RF3, (power << 7) | 0x00040); + break; + + case RAL_RF_2523: + ural_cfg_rf_write(sc, RAL_RF1, 0x08804); + ural_cfg_rf_write(sc, RAL_RF2, ural_rf2523_r2[chan - 1]); + ural_cfg_rf_write(sc, RAL_RF3, (power << 7) | 0x38044); + ural_cfg_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2524: + ural_cfg_rf_write(sc, RAL_RF1, 0x0c808); + ural_cfg_rf_write(sc, RAL_RF2, ural_rf2524_r2[chan - 1]); + ural_cfg_rf_write(sc, RAL_RF3, (power << 7) | 0x00040); + ural_cfg_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2525: + ural_cfg_rf_write(sc, RAL_RF1, 0x08808); + ural_cfg_rf_write(sc, RAL_RF2, ural_rf2525_hi_r2[chan - 1]); + ural_cfg_rf_write(sc, RAL_RF3, (power << 7) | 0x18044); + ural_cfg_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + + ural_cfg_rf_write(sc, RAL_RF1, 0x08808); + ural_cfg_rf_write(sc, RAL_RF2, ural_rf2525_r2[chan - 1]); + ural_cfg_rf_write(sc, RAL_RF3, (power << 7) | 0x18044); + ural_cfg_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2525E: + ural_cfg_rf_write(sc, RAL_RF1, 0x08808); + ural_cfg_rf_write(sc, RAL_RF2, ural_rf2525e_r2[chan - 1]); + ural_cfg_rf_write(sc, RAL_RF3, (power << 7) | 0x18044); + ural_cfg_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00286 : 0x00282); + break; + + case RAL_RF_2526: + ural_cfg_rf_write(sc, RAL_RF2, ural_rf2526_hi_r2[chan - 1]); + ural_cfg_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); + ural_cfg_rf_write(sc, RAL_RF1, 0x08804); + + ural_cfg_rf_write(sc, RAL_RF2, ural_rf2526_r2[chan - 1]); + ural_cfg_rf_write(sc, RAL_RF3, (power << 7) | 0x18044); + ural_cfg_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); + break; + + /* dual-band RF */ + case RAL_RF_5222: + for (i = 0; i < N_RF5222; i++) { + if (ural_rf5222[i].chan == chan) { + ural_cfg_rf_write(sc, RAL_RF1, ural_rf5222[i].r1); + ural_cfg_rf_write(sc, RAL_RF2, ural_rf5222[i].r2); + ural_cfg_rf_write(sc, RAL_RF3, (power << 7) | 0x00040); + ural_cfg_rf_write(sc, RAL_RF4, ural_rf5222[i].r4); + break; + } + } + break; + } + + if ((cc->ic_opmode != IEEE80211_M_MONITOR) && + (!(cc->ic_flags & IEEE80211_F_SCAN))) { + + /* set Japan filter bit for channel 14 */ + tmp = ural_cfg_bbp_read(sc, 70); + + if (chan == 14) { + tmp |= RAL_JAPAN_FILTER; + } else { + tmp &= ~RAL_JAPAN_FILTER; + } + + ural_cfg_bbp_write(sc, 70, tmp); + + /* clear CRC errors */ + ural_cfg_read(sc, RAL_STA_CSR0); + + ural_cfg_disable_rf_tune(sc); + } + /* update basic rate set */ + if (cc->ic_curchan.chan_is_b) { + /* 11b basic rates: 1, 2Mbps */ + ural_cfg_write(sc, RAL_TXRX_CSR11, 0x3); + } else if (cc->ic_curchan.chan_is_a) { + /* 11a basic rates: 6, 12, 24Mbps */ + ural_cfg_write(sc, RAL_TXRX_CSR11, 0x150); + } else { + /* 11g basic rates: 1, 2, 5.5, 11, 6, 12, 24Mbps */ + ural_cfg_write(sc, RAL_TXRX_CSR11, 0x15f); + } + + /* wait a little */ + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + return; + } + return; +} + +static void +ural_cfg_set_run(struct ural_softc *sc, + struct usb2_config_td_cc *cc) +{ + if (cc->ic_opmode != IEEE80211_M_MONITOR) { + ural_cfg_update_slot(sc, cc, 0); + ural_cfg_set_txpreamble(sc, cc, 0); + + /* update basic rate set */ + + if (cc->ic_bsschan.chan_is_5ghz) { + /* 11a basic rates: 6, 12, 24Mbps */ + ural_cfg_write(sc, RAL_TXRX_CSR11, 0x150); + } else if (cc->ic_bsschan.chan_is_g) { + /* 11g basic rates: 1, 2, 5.5, 11, 6, 12, 24Mbps */ + ural_cfg_write(sc, RAL_TXRX_CSR11, 0x15f); + } else { + /* 11b basic rates: 1, 2Mbps */ + ural_cfg_write(sc, RAL_TXRX_CSR11, 0x3); + } + ural_cfg_set_bssid(sc, cc->iv_bss.ni_bssid); + } + if ((cc->ic_opmode == IEEE80211_M_HOSTAP) || + (cc->ic_opmode == IEEE80211_M_IBSS)) { + ural_tx_bcn(sc); + } + /* make tx led blink on tx (controlled by ASIC) */ + ural_cfg_write(sc, RAL_MAC_CSR20, 1); + + if (cc->ic_opmode != IEEE80211_M_MONITOR) { + ural_cfg_enable_tsf_sync(sc, cc, 0); + } + /* clear statistic registers (STA_CSR0 to STA_CSR10) */ + ural_cfg_read_multi(sc, RAL_STA_CSR0, sc->sc_sta, sizeof(sc->sc_sta)); + + if (cc->iv_bss.fixed_rate_none) { + /* enable automatic rate adaptation */ + ural_cfg_amrr_start(sc); + } + return; +} + +/*------------------------------------------------------------------------* + * ural_cfg_disable_rf_tune - disable RF auto-tuning + *------------------------------------------------------------------------*/ +static void +ural_cfg_disable_rf_tune(struct ural_softc *sc) +{ + uint32_t tmp; + + if (sc->sc_rf_rev != RAL_RF_2523) { + tmp = sc->sc_rf_regs[RAL_RF1] & ~RAL_RF1_AUTOTUNE; + ural_cfg_rf_write(sc, RAL_RF1, tmp); + } + tmp = sc->sc_rf_regs[RAL_RF3] & ~RAL_RF3_AUTOTUNE; + ural_cfg_rf_write(sc, RAL_RF3, tmp); + + DPRINTFN(3, "disabling RF autotune\n"); + + return; +} + +/*------------------------------------------------------------------------* + * ural_cfg_enable_tsf_sync - refer to IEEE Std 802.11-1999 pp. 123 + * for more information on TSF synchronization + *------------------------------------------------------------------------*/ +static void +ural_cfg_enable_tsf_sync(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint16_t logcwmin; + uint16_t preload; + uint16_t tmp; + + /* first, disable TSF synchronization */ + ural_cfg_write(sc, RAL_TXRX_CSR19, 0); + + tmp = (16 * cc->iv_bss.ni_intval) << 4; + ural_cfg_write(sc, RAL_TXRX_CSR18, tmp); + + logcwmin = (cc->ic_opmode == IEEE80211_M_IBSS) ? 2 : 0; + preload = (cc->ic_opmode == IEEE80211_M_IBSS) ? 320 : 6; + tmp = (logcwmin << 12) | preload; + ural_cfg_write(sc, RAL_TXRX_CSR20, tmp); + + /* finally, enable TSF synchronization */ + tmp = RAL_ENABLE_TSF | RAL_ENABLE_TBCN; + if (cc->ic_opmode == IEEE80211_M_STA) + tmp |= RAL_ENABLE_TSF_SYNC(1); + else + tmp |= RAL_ENABLE_TSF_SYNC(2) | RAL_ENABLE_BEACON_GENERATOR; + + ural_cfg_write(sc, RAL_TXRX_CSR19, tmp); + + DPRINTF("enabling TSF synchronization\n"); + + return; +} + +static void +ural_cfg_disable_tsf_sync(struct ural_softc *sc) +{ + /* abort TSF synchronization */ + ural_cfg_write(sc, RAL_TXRX_CSR19, 0); + + /* force tx led to stop blinking */ + ural_cfg_write(sc, RAL_MAC_CSR20, 0); + + return; +} + +#define RAL_RXTX_TURNAROUND 5 /* us */ + +static void +ural_cfg_update_slot(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint16_t slottime; + uint16_t sifs; + uint16_t eifs; + + slottime = (cc->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20; + + /* + * These settings may sound a bit inconsistent but this is what the + * reference driver does. + */ + if (cc->ic_curmode == IEEE80211_MODE_11B) { + sifs = 16 - RAL_RXTX_TURNAROUND; + eifs = 364; + } else { + sifs = 10 - RAL_RXTX_TURNAROUND; + eifs = 64; + } + + ural_cfg_write(sc, RAL_MAC_CSR10, slottime); + ural_cfg_write(sc, RAL_MAC_CSR11, sifs); + ural_cfg_write(sc, RAL_MAC_CSR12, eifs); + return; +} + +static void +ural_cfg_set_txpreamble(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint16_t tmp; + + tmp = ural_cfg_read(sc, RAL_TXRX_CSR10); + + if (cc->ic_flags & IEEE80211_F_SHPREAMBLE) { + tmp |= RAL_SHORT_PREAMBLE; + } else { + tmp &= ~RAL_SHORT_PREAMBLE; + } + + ural_cfg_write(sc, RAL_TXRX_CSR10, tmp); + return; +} + +static void +ural_cfg_set_bssid(struct ural_softc *sc, uint8_t *bssid) +{ + ural_cfg_write_multi(sc, RAL_MAC_CSR5, bssid, IEEE80211_ADDR_LEN); + + DPRINTF("setting BSSID to 0x%02x%02x%02x%02x%02x%02x\n", + bssid[5], bssid[4], bssid[3], + bssid[2], bssid[1], bssid[0]); + return; +} + +static void +ural_cfg_set_macaddr(struct ural_softc *sc, uint8_t *addr) +{ + ural_cfg_write_multi(sc, RAL_MAC_CSR2, addr, IEEE80211_ADDR_LEN); + + DPRINTF("setting MAC to 0x%02x%02x%02x%02x%02x%02x\n", + addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0]); + return; +} + +static void +ural_cfg_update_promisc(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint16_t tmp; + + tmp = ural_cfg_read(sc, RAL_TXRX_CSR2); + + if (cc->if_flags & IFF_PROMISC) { + tmp &= ~RAL_DROP_NOT_TO_ME; + } else { + tmp |= RAL_DROP_NOT_TO_ME; + } + + ural_cfg_write(sc, RAL_TXRX_CSR2, tmp); + + DPRINTF("%s promiscuous mode\n", + (cc->if_flags & IFF_PROMISC) ? + "entering" : "leaving"); + return; +} + +static void +ural_cfg_set_txantenna(struct ural_softc *sc, uint8_t antenna) +{ + uint16_t tmp; + uint8_t tx; + + tx = ural_cfg_bbp_read(sc, RAL_BBP_TX) & ~RAL_BBP_ANTMASK; + if (antenna == 1) + tx |= RAL_BBP_ANTA; + else if (antenna == 2) + tx |= RAL_BBP_ANTB; + else + tx |= RAL_BBP_DIVERSITY; + + /* need to force I/Q flip for RF 2525e, 2526 and 5222 */ + if ((sc->sc_rf_rev == RAL_RF_2525E) || + (sc->sc_rf_rev == RAL_RF_2526) || + (sc->sc_rf_rev == RAL_RF_5222)) { + tx |= RAL_BBP_FLIPIQ; + } + ural_cfg_bbp_write(sc, RAL_BBP_TX, tx); + + /* update values in PHY_CSR5 and PHY_CSR6 */ + tmp = ural_cfg_read(sc, RAL_PHY_CSR5) & ~0x7; + ural_cfg_write(sc, RAL_PHY_CSR5, tmp | (tx & 0x7)); + + tmp = ural_cfg_read(sc, RAL_PHY_CSR6) & ~0x7; + ural_cfg_write(sc, RAL_PHY_CSR6, tmp | (tx & 0x7)); + + return; +} + +static void +ural_cfg_set_rxantenna(struct ural_softc *sc, uint8_t antenna) +{ + uint8_t rx; + + rx = ural_cfg_bbp_read(sc, RAL_BBP_RX) & ~RAL_BBP_ANTMASK; + if (antenna == 1) + rx |= RAL_BBP_ANTA; + else if (antenna == 2) + rx |= RAL_BBP_ANTB; + else + rx |= RAL_BBP_DIVERSITY; + + /* need to force no I/Q flip for RF 2525e and 2526 */ + + if ((sc->sc_rf_rev == RAL_RF_2525E) || + (sc->sc_rf_rev == RAL_RF_2526)) { + rx &= ~RAL_BBP_FLIPIQ; + } + ural_cfg_bbp_write(sc, RAL_BBP_RX, rx); + return; +} + +static void +ural_cfg_read_eeprom(struct ural_softc *sc) +{ + uint16_t val; + + ural_cfg_eeprom_read(sc, RAL_EEPROM_CONFIG0, &val, 2); + + val = le16toh(val); + + sc->sc_rf_rev = (val >> 11) & 0x7; + sc->sc_hw_radio = (val >> 10) & 0x1; + sc->sc_led_mode = (val >> 6) & 0x7; + sc->sc_rx_ant = (val >> 4) & 0x3; + sc->sc_tx_ant = (val >> 2) & 0x3; + sc->sc_nb_ant = (val & 0x3); + + DPRINTF("val = 0x%04x\n", val); + + /* read MAC address */ + ural_cfg_eeprom_read(sc, RAL_EEPROM_ADDRESS, sc->sc_myaddr, + sizeof(sc->sc_myaddr)); + + /* read default values for BBP registers */ + ural_cfg_eeprom_read(sc, RAL_EEPROM_BBP_BASE, sc->sc_bbp_prom, + sizeof(sc->sc_bbp_prom)); + + /* read Tx power for all b/g channels */ + ural_cfg_eeprom_read(sc, RAL_EEPROM_TXPOWER, sc->sc_txpow, + sizeof(sc->sc_txpow)); + return; +} + +static uint8_t +ural_cfg_bbp_init(struct ural_softc *sc) +{ + enum { + N_DEF_BBP = (sizeof(ural_def_bbp) / sizeof(ural_def_bbp[0])), + }; + uint16_t i; + uint8_t to; + + /* wait for BBP to become ready */ + for (to = 0;; to++) { + if (to < 100) { + if (ural_cfg_bbp_read(sc, RAL_BBP_VERSION) != 0) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + return (1); /* failure */ + } + } else { + DPRINTF("timeout waiting for BBP\n"); + return (1); /* failure */ + } + } + + /* initialize BBP registers to default values */ + for (i = 0; i < N_DEF_BBP; i++) { + ural_cfg_bbp_write(sc, ural_def_bbp[i].reg, + ural_def_bbp[i].val); + } + +#if 0 + /* initialize BBP registers to values stored in EEPROM */ + for (i = 0; i < 16; i++) { + if (sc->sc_bbp_prom[i].reg == 0xff) { + continue; + } + ural_cfg_bbp_write(sc, sc->sc_bbp_prom[i].reg, + sc->sc_bbp_prom[i].val); + } +#endif + return (0); /* success */ +} + +static void +ural_cfg_pre_init(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + /* immediate configuration */ + + ural_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= URAL_FLAG_HL_READY; + + IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp)); + + return; +} + +static void +ural_cfg_init(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + enum { + N_DEF_MAC = (sizeof(ural_def_mac) / sizeof(ural_def_mac[0])), + }; + uint16_t tmp; + uint16_t i; + uint8_t to; + + /* delayed configuration */ + + ural_cfg_set_testmode(sc); + + ural_cfg_write(sc, 0x308, 0x00f0); /* XXX magic */ + + ural_cfg_stop(sc, cc, 0); + + /* initialize MAC registers to default values */ + for (i = 0; i < N_DEF_MAC; i++) { + ural_cfg_write(sc, ural_def_mac[i].reg, + ural_def_mac[i].val); + } + + /* wait for BBP and RF to wake up (this can take a long time!) */ + for (to = 0;; to++) { + if (to < 100) { + tmp = ural_cfg_read(sc, RAL_MAC_CSR17); + if ((tmp & (RAL_BBP_AWAKE | RAL_RF_AWAKE)) == + (RAL_BBP_AWAKE | RAL_RF_AWAKE)) { + break; + } + if (usb2_config_td_sleep(&sc->sc_config_td, hz / 100)) { + goto fail; + } + } else { + DPRINTF("timeout waiting for " + "BBP/RF to wakeup\n"); + goto fail; + } + } + + /* we're ready! */ + ural_cfg_write(sc, RAL_MAC_CSR1, RAL_HOST_READY); + + /* set basic rate set (will be updated later) */ + ural_cfg_write(sc, RAL_TXRX_CSR11, 0x15f); + + if (ural_cfg_bbp_init(sc)) { + goto fail; + } + /* set default BSS channel */ + ural_cfg_set_chan(sc, cc, 0); + + /* clear statistic registers (STA_CSR0 to STA_CSR10) */ + ural_cfg_read_multi(sc, RAL_STA_CSR0, sc->sc_sta, + sizeof(sc->sc_sta)); + + DPRINTF("rx_ant=%d, tx_ant=%d\n", + sc->sc_rx_ant, sc->sc_tx_ant); + + ural_cfg_set_txantenna(sc, sc->sc_tx_ant); + ural_cfg_set_rxantenna(sc, sc->sc_rx_ant); + + ural_cfg_set_macaddr(sc, cc->ic_myaddr); + + /* + * make sure that the first transaction + * clears the stall: + */ + sc->sc_flags |= (URAL_FLAG_READ_STALL | + URAL_FLAG_WRITE_STALL | + URAL_FLAG_LL_READY); + + if ((sc->sc_flags & URAL_FLAG_LL_READY) && + (sc->sc_flags & URAL_FLAG_HL_READY)) { + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + + /* + * start IEEE802.11 layer + */ + mtx_unlock(&sc->sc_mtx); + ieee80211_start_all(ic); + mtx_lock(&sc->sc_mtx); + } + /* + * start Rx + */ + tmp = RAL_DROP_PHY | RAL_DROP_CRC; + if (cc->ic_opmode != IEEE80211_M_MONITOR) { + + tmp |= (RAL_DROP_CTL | RAL_DROP_BAD_VERSION); + + if (cc->ic_opmode != IEEE80211_M_HOSTAP) { + tmp |= RAL_DROP_TODS; + } + if (!(cc->if_flags & IFF_PROMISC)) { + tmp |= RAL_DROP_NOT_TO_ME; + } + } + ural_cfg_write(sc, RAL_TXRX_CSR2, tmp); + + return; + +fail: + ural_cfg_pre_stop(sc, NULL, 0); + + if (cc) { + ural_cfg_stop(sc, cc, 0); + } + return; +} + +static void +ural_cfg_pre_stop(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + ural_config_copy(sc, cc, refcount); + } + /* immediate configuration */ + + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(URAL_FLAG_HL_READY | + URAL_FLAG_LL_READY); + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[0]); + usb2_transfer_stop(sc->sc_xfer[1]); + usb2_transfer_stop(sc->sc_xfer[2]); + usb2_transfer_stop(sc->sc_xfer[3]); + + /* clean up transmission */ + ural_tx_clean_queue(sc); + return; +} + +static void +ural_cfg_stop(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* disable Rx */ + ural_cfg_write(sc, RAL_TXRX_CSR2, RAL_DISABLE_RX); + + /* reset ASIC and BBP (but won't reset MAC registers!) */ + ural_cfg_write(sc, RAL_MAC_CSR1, RAL_RESET_ASIC | RAL_RESET_BBP); + + /* wait a little */ + usb2_config_td_sleep(&sc->sc_config_td, hz / 10); + + /* clear reset */ + ural_cfg_write(sc, RAL_MAC_CSR1, 0); + + /* wait a little */ + usb2_config_td_sleep(&sc->sc_config_td, hz / 10); + + return; +} + +static void +ural_cfg_amrr_start(struct ural_softc *sc) +{ + struct ieee80211vap *vap; + struct ieee80211_node *ni; + + vap = ural_get_vap(sc); + + if (vap == NULL) { + return; + } + ni = vap->iv_bss; + if (ni == NULL) { + return; + } + /* init AMRR */ + + ieee80211_amrr_node_init(&URAL_VAP(vap)->amrr, &URAL_NODE(ni)->amn, ni); + /* enable AMRR timer */ + + sc->sc_amrr_timer = 1; + return; +} + +static void +ural_cfg_amrr_timeout(struct ural_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211vap *vap; + struct ieee80211_node *ni; + uint32_t ok; + uint32_t fail; + + /* read and clear statistic registers (STA_CSR0 to STA_CSR10) */ + ural_cfg_read_multi(sc, RAL_STA_CSR0, sc->sc_sta, sizeof(sc->sc_sta)); + + vap = ural_get_vap(sc); + if (vap == NULL) { + return; + } + ni = vap->iv_bss; + if (ni == NULL) { + return; + } + if ((sc->sc_flags & URAL_FLAG_LL_READY) && + (sc->sc_flags & URAL_FLAG_HL_READY)) { + + ok = sc->sc_sta[7] + /* TX ok w/o retry */ + sc->sc_sta[8]; /* TX ok w/ retry */ + fail = sc->sc_sta[9]; /* TX retry-fail count */ + + if (sc->sc_amrr_timer) { + + ieee80211_amrr_tx_update(&URAL_NODE(ni)->amn, + ok + fail, ok, sc->sc_sta[8] + fail); + + if (ieee80211_amrr_choose(ni, &URAL_NODE(ni)->amn)) { + /* ignore */ + } + } + ifp->if_oerrors += fail; + } + return; +} + +static struct ieee80211vap * +ural_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, + int opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct ural_vap *uvp; + struct ieee80211vap *vap; + struct ural_softc *sc = ic->ic_ifp->if_softc; + + /* Need to sync with config thread: */ + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_sync(&sc->sc_config_td)) { + mtx_unlock(&sc->sc_mtx); + /* config thread is gone */ + return (NULL); + } + mtx_unlock(&sc->sc_mtx); + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + uvp = (struct ural_vap *)malloc(sizeof(struct ural_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return NULL; + + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = &ural_newstate_cb; + + ieee80211_amrr_init(&uvp->amrr, vap, + IEEE80211_AMRR_MIN_SUCCESS_THRESHOLD, + IEEE80211_AMRR_MAX_SUCCESS_THRESHOLD, + 1000 /* 1 sec */ ); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status); + + /* store current operation mode */ + ic->ic_opmode = opmode; + + return (vap); +} + +static void +ural_vap_delete(struct ieee80211vap *vap) +{ + struct ural_vap *uvp = URAL_VAP(vap); + struct ural_softc *sc = vap->iv_ic->ic_ifp->if_softc; + + /* Need to sync with config thread: */ + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_sync(&sc->sc_config_td)) { + /* ignore */ + } + mtx_unlock(&sc->sc_mtx); + + ieee80211_amrr_cleanup(&uvp->amrr); + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); + return; +} + +/* ARGUSED */ +static struct ieee80211_node * +ural_node_alloc(struct ieee80211vap *vap __unused, + const uint8_t mac[IEEE80211_ADDR_LEN] __unused) +{ + struct ural_node *un; + + un = malloc(sizeof(struct ural_node), M_80211_NODE, M_NOWAIT | M_ZERO); + return ((un != NULL) ? &un->ni : NULL); +} + +static void +ural_newassoc(struct ieee80211_node *ni, int isnew) +{ + struct ieee80211vap *vap = ni->ni_vap; + + ieee80211_amrr_node_init(&URAL_VAP(vap)->amrr, &URAL_NODE(ni)->amn, ni); + return; +} + +static void +ural_fill_write_queue(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211_node *ni; + struct mbuf *m; + + /* + * We only fill up half of the queue with data frames. The rest is + * reserved for other kinds of frames. + */ + + while (sc->sc_tx_queue.ifq_len < (IFQ_MAXLEN / 2)) { + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + ni = (void *)(m->m_pkthdr.rcvif); + m = ieee80211_encap(ni, m); + if (m == NULL) { + ieee80211_free_node(ni); + continue; + } + ural_tx_data(sc, m, ni); + } + return; +} + +static void +ural_tx_clean_queue(struct ural_softc *sc) +{ + struct mbuf *m; + + for (;;) { + _IF_DEQUEUE(&sc->sc_tx_queue, m); + + if (!m) { + break; + } + ural_tx_freem(m); + } + + return; +} + +static void +ural_tx_freem(struct mbuf *m) +{ + struct ieee80211_node *ni; + + while (m) { + ni = (void *)(m->m_pkthdr.rcvif); + if (!ni) { + m = m_free(m); + continue; + } + if (m->m_flags & M_TXCB) { + ieee80211_process_callback(ni, m, 0); + } + m_freem(m); + ieee80211_free_node(ni); + + break; + } + return; +} + +static void +ural_tx_mgt(struct ural_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_txparam *tp; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + uint32_t flags; + uint16_t dur; + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + + wh = mtod(m, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m); + if (k == NULL) { + m_freem(m); + ieee80211_free_node(ni); + return; + } + wh = mtod(m, struct ieee80211_frame *); + } + flags = 0; + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RAL_TX_ACK; + + dur = ieee80211_ack_duration(sc->sc_rates, tp->mgmtrate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + USETW(wh->i_dur, dur); + + /* tell hardware to add timestamp for probe responses */ + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == + IEEE80211_FC0_TYPE_MGT && + (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == + IEEE80211_FC0_SUBTYPE_PROBE_RESP) + flags |= RAL_TX_TIMESTAMP; + } + m->m_pkthdr.rcvif = (void *)ni; + ural_setup_desc_and_tx(sc, m, flags, tp->mgmtrate); + return; +} + +static struct ieee80211vap * +ural_get_vap(struct ural_softc *sc) +{ + struct ifnet *ifp; + struct ieee80211com *ic; + + if (sc == NULL) { + return NULL; + } + ifp = sc->sc_ifp; + if (ifp == NULL) { + return NULL; + } + ic = ifp->if_l2com; + if (ic == NULL) { + return NULL; + } + return TAILQ_FIRST(&ic->ic_vaps); +} + +static void +ural_tx_bcn(struct ural_softc *sc) +{ + struct ieee80211_node *ni; + struct ieee80211vap *vap; + struct ieee80211com *ic; + const struct ieee80211_txparam *tp; + struct mbuf *m; + + vap = ural_get_vap(sc); + if (vap == NULL) { + return; + } + ni = vap->iv_bss; + if (ni == NULL) { + return; + } + ic = vap->iv_ic; + if (ic == NULL) { + return; + } + DPRINTFN(11, "Sending beacon frame.\n"); + + m = ieee80211_beacon_alloc(ni, &URAL_VAP(vap)->bo); + if (m == NULL) { + DPRINTFN(0, "could not allocate beacon\n"); + return; + } + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; + + m->m_pkthdr.rcvif = (void *)ieee80211_ref_node(ni); + ural_setup_desc_and_tx(sc, m, RAL_TX_IFS_NEWBACKOFF | RAL_TX_TIMESTAMP, + tp->mgmtrate); + return; +} + +static void +ural_tx_data(struct ural_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_txparam *tp; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + uint32_t flags = 0; + uint16_t dur; + uint16_t rate; + + DPRINTFN(11, "Sending data.\n"); + + wh = mtod(m, struct ieee80211_frame *); + + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + rate = tp->mcastrate; + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + rate = tp->ucastrate; + else + rate = ni->ni_txrate; + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m); + if (k == NULL) { + m_freem(m); + ieee80211_free_node(ni); + return; + } + /* packet header may have moved, reset our local pointer */ + wh = mtod(m, struct ieee80211_frame *); + } + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + uint8_t prot = IEEE80211_PROT_NONE; + + if (m->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) + prot = IEEE80211_PROT_RTSCTS; + else if ((ic->ic_flags & IEEE80211_F_USEPROT) && + ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_OFDM) + prot = ic->ic_protmode; + if (prot != IEEE80211_PROT_NONE) { + ural_tx_prot(sc, m, ni, prot, rate); + flags |= RAL_TX_IFS_SIFS; + } + flags |= RAL_TX_ACK; + flags |= RAL_TX_RETRY(7); + + dur = ieee80211_ack_duration(sc->sc_rates, rate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + USETW(wh->i_dur, dur); + } + m->m_pkthdr.rcvif = (void *)ni; + ural_setup_desc_and_tx(sc, m, flags, rate); + return; +} + +static void +ural_tx_prot(struct ural_softc *sc, + const struct mbuf *m, struct ieee80211_node *ni, + uint8_t prot, uint16_t rate) +{ + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_frame *wh; + struct mbuf *mprot; + uint32_t flags; + uint16_t protrate; + uint16_t ackrate; + uint16_t pktlen; + uint16_t dur; + uint8_t isshort; + + KASSERT((prot == IEEE80211_PROT_RTSCTS) || + (prot == IEEE80211_PROT_CTSONLY), + ("protection %u", prot)); + + DPRINTFN(16, "Sending protection frame.\n"); + + wh = mtod(m, const struct ieee80211_frame *); + pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + + protrate = ieee80211_ctl_rate(sc->sc_rates, rate); + ackrate = ieee80211_ack_rate(sc->sc_rates, rate); + + isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; + dur = ieee80211_compute_duration(sc->sc_rates, pktlen, rate, isshort); + +ieee80211_ack_duration(sc->sc_rates, rate, isshort); + flags = RAL_TX_RETRY(7); + if (prot == IEEE80211_PROT_RTSCTS) { + /* NB: CTS is the same size as an ACK */ + dur += ieee80211_ack_duration(sc->sc_rates, rate, isshort); + flags |= RAL_TX_ACK; + mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); + } else { + mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, dur); + } + if (mprot == NULL) { + return; + } + mprot->m_pkthdr.rcvif = (void *)ieee80211_ref_node(ni); + ural_setup_desc_and_tx(sc, mprot, flags, protrate); + return; +} + +static void +ural_tx_raw(struct ural_softc *sc, struct mbuf *m, struct ieee80211_node *ni, + const struct ieee80211_bpf_params *params) +{ + uint32_t flags; + uint16_t rate; + + DPRINTFN(11, "Sending raw frame.\n"); + + rate = params->ibp_rate0 & IEEE80211_RATE_VAL; + + /* XXX validate */ + if (rate == 0) { + m_freem(m); + ieee80211_free_node(ni); + return; + } + flags = 0; + if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) + flags |= RAL_TX_ACK; + if (params->ibp_flags & (IEEE80211_BPF_RTS | IEEE80211_BPF_CTS)) { + ural_tx_prot(sc, m, ni, + (params->ibp_flags & IEEE80211_BPF_RTS) ? + IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, + rate); + flags |= RAL_TX_IFS_SIFS; + } + m->m_pkthdr.rcvif = (void *)ni; + ural_setup_desc_and_tx(sc, m, flags, rate); + return; +} + +static int +ural_raw_xmit_cb(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct ural_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + if (params == NULL) { + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + */ + ural_tx_mgt(sc, m, ni); + } else { + /* + * Caller supplied explicit parameters to use in + * sending the frame. + */ + ural_tx_raw(sc, m, ni, params); + } + mtx_unlock(&sc->sc_mtx); + return (0); +} + +static void +ural_update_mcast_cb(struct ifnet *ifp) +{ + /* not supported */ + return; +} + +static void +ural_update_promisc_cb(struct ifnet *ifp) +{ + struct ural_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &ural_config_copy, + &ural_cfg_update_promisc, 0, 0); + mtx_unlock(&sc->sc_mtx); + return; +} diff --git a/sys/dev/usb2/wlan/if_ural2_reg.h b/sys/dev/usb2/wlan/if_ural2_reg.h new file mode 100644 index 000000000000..1837693e03a2 --- /dev/null +++ b/sys/dev/usb2/wlan/if_ural2_reg.h @@ -0,0 +1,198 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 + * Damien Bergamini + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RAL_NOISE_FLOOR -95 +#define RAL_RSSI_CORR 120 + +#define RAL_RX_DESC_SIZE (sizeof (struct ural_rx_desc)) +#define RAL_TX_DESC_SIZE (sizeof (struct ural_tx_desc)) +#define RAL_FRAME_SIZE 0x780 /* NOTE: using 0x980 does not work */ +#if (RAL_FRAME_SIZE % 0x80) +#error "Invalid RAL_FRAME_SIZE" +#endif + +#define RAL_CONFIG_NO 1 +#define RAL_IFACE_INDEX 0 + +#define RAL_VENDOR_REQUEST 0x01 +#define RAL_WRITE_MAC 0x02 +#define RAL_READ_MAC 0x03 +#define RAL_WRITE_MULTI_MAC 0x06 +#define RAL_READ_MULTI_MAC 0x07 +#define RAL_READ_EEPROM 0x09 + +/* MAC registers. */ +#define RAL_MAC_CSR0 0x0400 /* ASIC Version */ +#define RAL_MAC_CSR1 0x0402 /* System control */ +#define RAL_MAC_CSR2 0x0404 /* MAC addr0 */ +#define RAL_MAC_CSR3 0x0406 /* MAC addr1 */ +#define RAL_MAC_CSR4 0x0408 /* MAC addr2 */ +#define RAL_MAC_CSR5 0x040a /* BSSID0 */ +#define RAL_MAC_CSR6 0x040c /* BSSID1 */ +#define RAL_MAC_CSR7 0x040e /* BSSID2 */ +#define RAL_MAC_CSR8 0x0410 /* Max frame length */ +#define RAL_MAC_CSR9 0x0412 /* Timer control */ +#define RAL_MAC_CSR10 0x0414 /* Slot time */ +#define RAL_MAC_CSR11 0x0416 /* IFS */ +#define RAL_MAC_CSR12 0x0418 /* EIFS */ +#define RAL_MAC_CSR13 0x041a /* Power mode0 */ +#define RAL_MAC_CSR14 0x041c /* Power mode1 */ +#define RAL_MAC_CSR15 0x041e /* Power saving transition0 */ +#define RAL_MAC_CSR16 0x0420 /* Power saving transition1 */ +#define RAL_MAC_CSR17 0x0422 /* Power state control */ +#define RAL_MAC_CSR18 0x0424 /* Auto wake-up control */ +#define RAL_MAC_CSR19 0x0426 /* GPIO control */ +#define RAL_MAC_CSR20 0x0428 /* LED control0 */ +#define RAL_MAC_CSR22 0x042c /* XXX not documented */ + +/* Tx/Rx Registers. */ +#define RAL_TXRX_CSR0 0x0440 /* Security control */ +#define RAL_TXRX_CSR2 0x0444 /* Rx control */ +#define RAL_TXRX_CSR5 0x044a /* CCK Tx BBP ID0 */ +#define RAL_TXRX_CSR6 0x044c /* CCK Tx BBP ID1 */ +#define RAL_TXRX_CSR7 0x044e /* OFDM Tx BBP ID0 */ +#define RAL_TXRX_CSR8 0x0450 /* OFDM Tx BBP ID1 */ +#define RAL_TXRX_CSR10 0x0454 /* Auto responder control */ +#define RAL_TXRX_CSR11 0x0456 /* Auto responder basic rate */ +#define RAL_TXRX_CSR18 0x0464 /* Beacon interval */ +#define RAL_TXRX_CSR19 0x0466 /* Beacon/sync control */ +#define RAL_TXRX_CSR20 0x0468 /* Beacon alignment */ +#define RAL_TXRX_CSR21 0x046a /* XXX not documented */ + +/* Security registers. */ +#define RAL_SEC_CSR0 0x0480 /* Shared key 0, word 0 */ + +/* PHY registers. */ +#define RAL_PHY_CSR2 0x04c4 /* Tx MAC configuration */ +#define RAL_PHY_CSR4 0x04c8 /* Interface configuration */ +#define RAL_PHY_CSR5 0x04ca /* BBP Pre-Tx CCK */ +#define RAL_PHY_CSR6 0x04cc /* BBP Pre-Tx OFDM */ +#define RAL_PHY_CSR7 0x04ce /* BBP serial control */ +#define RAL_PHY_CSR8 0x04d0 /* BBP serial status */ +#define RAL_PHY_CSR9 0x04d2 /* RF serial control0 */ +#define RAL_PHY_CSR10 0x04d4 /* RF serial control1 */ + +/* Statistics registers. */ +#define RAL_STA_CSR0 0x04e0 /* FCS error */ + +#define RAL_DISABLE_RX (1 << 0) +#define RAL_DROP_CRC (1 << 1) +#define RAL_DROP_PHY (1 << 2) +#define RAL_DROP_CTL (1 << 3) +#define RAL_DROP_NOT_TO_ME (1 << 4) +#define RAL_DROP_TODS (1 << 5) +#define RAL_DROP_BAD_VERSION (1 << 6) +#define RAL_DROP_MULTICAST (1 << 9) +#define RAL_DROP_BROADCAST (1 << 10) + +#define RAL_SHORT_PREAMBLE (1 << 2) + +#define RAL_RESET_ASIC (1 << 0) +#define RAL_RESET_BBP (1 << 1) +#define RAL_HOST_READY (1 << 2) + +#define RAL_ENABLE_TSF (1 << 0) +#define RAL_ENABLE_TSF_SYNC(x) (((x) & 0x3) << 1) +#define RAL_ENABLE_TBCN (1 << 3) +#define RAL_ENABLE_BEACON_GENERATOR (1 << 4) + +#define RAL_RF_AWAKE (3 << 7) +#define RAL_BBP_AWAKE (3 << 5) + +#define RAL_BBP_WRITE (1 << 15) +#define RAL_BBP_BUSY (1 << 0) + +#define RAL_RF1_AUTOTUNE 0x08000 +#define RAL_RF3_AUTOTUNE 0x00040 + +#define RAL_RF_2522 0x00 +#define RAL_RF_2523 0x01 +#define RAL_RF_2524 0x02 +#define RAL_RF_2525 0x03 +#define RAL_RF_2525E 0x04 +#define RAL_RF_2526 0x05 +/* dual-band RF */ +#define RAL_RF_5222 0x10 + +#define RAL_BBP_VERSION 0 +#define RAL_BBP_TX 2 +#define RAL_BBP_RX 14 + +#define RAL_BBP_ANTA 0x00 +#define RAL_BBP_DIVERSITY 0x01 +#define RAL_BBP_ANTB 0x02 +#define RAL_BBP_ANTMASK 0x03 +#define RAL_BBP_FLIPIQ 0x04 + +#define RAL_JAPAN_FILTER 0x08 + +#define RAL_RF_LOBUSY (1 << 15) +#define RAL_RF_BUSY (1 << 31) +#define RAL_RF_20BIT (20 << 24) + +#define RAL_RF1 0 +#define RAL_RF2 2 +#define RAL_RF3 1 +#define RAL_RF4 3 + +#define RAL_EEPROM_ADDRESS 0x0004 +#define RAL_EEPROM_TXPOWER 0x003c +#define RAL_EEPROM_CONFIG0 0x0016 +#define RAL_EEPROM_BBP_BASE 0x001c + +struct ural_tx_desc { + uint32_t flags; +#define RAL_TX_PACKET_ID(x) ((x) & 0xf) +#define RAL_TX_RETRY(x) ((x) << 4) +#define RAL_TX_MORE_FRAG (1 << 8) +#define RAL_TX_ACK (1 << 9) +#define RAL_TX_TIMESTAMP (1 << 10) +#define RAL_TX_OFDM (1 << 11) +#define RAL_TX_NEWSEQ (1 << 12) +#define RAL_TX_IFS_MASK 0x00006000 +#define RAL_TX_IFS_BACKOFF (0 << 13) +#define RAL_TX_IFS_SIFS (1 << 13) +#define RAL_TX_IFS_NEWBACKOFF (2 << 13) +#define RAL_TX_IFS_NONE (3 << 13) + uint16_t wme; +#define RAL_LOGCWMAX(x) (((x) & 0xf) << 12) +#define RAL_LOGCWMIN(x) (((x) & 0xf) << 8) +#define RAL_AIFSN(x) (((x) & 0x3) << 6) +#define RAL_IVOFFSET(x) (((x) & 0x3f)) + uint16_t reserved1; + uint8_t plcp_signal; + uint8_t plcp_service; +#define RAL_PLCP_LENGEXT 0x80 + uint8_t plcp_length_lo; + uint8_t plcp_length_hi; + uint32_t iv; + uint32_t eiv; +} __packed; + +struct ural_rx_desc { + uint32_t flags; +#define RAL_RX_CRC_ERROR (1 << 5) +#define RAL_RX_OFDM (1 << 6) +#define RAL_RX_PHY_ERROR (1 << 7) + uint8_t rssi; + uint8_t rate; + uint16_t reserved; + uint32_t iv; + uint32_t eiv; +} __packed; diff --git a/sys/dev/usb2/wlan/if_ural2_var.h b/sys/dev/usb2/wlan/if_ural2_var.h new file mode 100644 index 000000000000..c91643c804c1 --- /dev/null +++ b/sys/dev/usb2/wlan/if_ural2_var.h @@ -0,0 +1,161 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005 + * Damien Bergamini + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define URAL_N_TRANSFER 4 + +struct ural_node { + struct ieee80211_node ni; + struct ieee80211_amrr_node amn; +}; + +#define URAL_NODE(ni) ((struct ural_node *)(ni)) + +struct ural_vap { + struct ieee80211vap vap; + struct ieee80211_beacon_offsets bo; + struct ieee80211_amrr amrr; + + int (*newstate) (struct ieee80211vap *, + enum ieee80211_state, int); +}; + +#define URAL_VAP(vap) ((struct ural_vap *)(vap)) + +struct ural_config_copy_chan { + uint32_t chan_to_ieee; + enum ieee80211_phymode chan_to_mode; + uint8_t chan_is_5ghz:1; + uint8_t chan_is_2ghz:1; + uint8_t chan_is_b:1; + uint8_t chan_is_a:1; + uint8_t chan_is_g:1; + uint8_t unused:3; +}; + +struct ural_config_copy_bss { + uint16_t ni_intval; + uint8_t ni_bssid[IEEE80211_ADDR_LEN]; + uint8_t fixed_rate_none; +}; + +struct ural_config_copy { + struct ural_config_copy_chan ic_curchan; + struct ural_config_copy_chan ic_bsschan; + struct ural_config_copy_bss iv_bss; + + enum ieee80211_opmode ic_opmode; + uint32_t ic_flags; + uint32_t if_flags; + + uint16_t ic_txpowlimit; + uint16_t ic_curmode; + + uint8_t ic_myaddr[IEEE80211_ADDR_LEN]; + uint8_t if_broadcastaddr[IEEE80211_ADDR_LEN]; +}; + +struct ural_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + uint8_t wr_antenna; + uint8_t wr_antsignal; +}; + +#define RAL_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)) + +struct ural_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; + uint8_t wt_antenna; +}; + +#define RAL_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA)) + +struct ural_bbp_prom { + uint8_t val; + uint8_t reg; +} __packed; + +struct ural_ifq { + struct mbuf *ifq_head; + struct mbuf *ifq_tail; + uint16_t ifq_len; +}; + +struct ural_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct ural_ifq sc_tx_queue; + struct usb2_config_td sc_config_td; + struct ural_tx_desc sc_tx_desc; + struct ural_rx_desc sc_rx_desc; + struct mtx sc_mtx; + struct usb2_callout sc_watchdog; + struct ural_bbp_prom sc_bbp_prom[16]; + struct ural_rx_radiotap_header sc_rxtap; + struct ural_tx_radiotap_header sc_txtap; + + struct usb2_xfer *sc_xfer[URAL_N_TRANSFER]; + struct ifnet *sc_ifp; + struct usb2_device *sc_udev; + const struct ieee80211_rate_table *sc_rates; + + enum ieee80211_state sc_ns_state; + uint32_t sc_unit; + uint32_t sc_asic_rev; + uint32_t sc_rf_regs[4]; + int sc_ns_arg; + + uint16_t sc_flags; +#define URAL_FLAG_READ_STALL 0x0001 +#define URAL_FLAG_WRITE_STALL 0x0002 +#define URAL_FLAG_LL_READY 0x0004 +#define URAL_FLAG_HL_READY 0x0008 +#define URAL_FLAG_WAIT_COMMAND 0x0010 + uint16_t sc_txtap_len; + uint16_t sc_rxtap_len; + uint16_t sc_sta[11]; + + uint8_t sc_rf_rev; + uint8_t sc_txpow[14]; + uint8_t sc_led_mode; + uint8_t sc_hw_radio; + uint8_t sc_rx_ant; + uint8_t sc_tx_ant; + uint8_t sc_nb_ant; + uint8_t sc_amrr_timer; + uint8_t sc_myaddr[IEEE80211_ADDR_LEN]; + + char sc_name[32]; +}; diff --git a/sys/dev/usb2/wlan/if_zyd2.c b/sys/dev/usb2/wlan/if_zyd2.c new file mode 100644 index 000000000000..5e72593f17d8 --- /dev/null +++ b/sys/dev/usb2/wlan/if_zyd2.c @@ -0,0 +1,3297 @@ +/* $OpenBSD: if_zyd.c,v 1.52 2007/02/11 00:08:04 jsg Exp $ */ +/* $NetBSD: if_zyd.c,v 1.7 2007/06/21 04:04:29 kiyohara Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2006 by Damien Bergamini + * Copyright (c) 2006 by Florian Stoehr + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * ZyDAS ZD1211/ZD1211B USB WLAN driver + * + * NOTE: all function names beginning like "zyd_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include + +#define usb2_config_td_cc zyd_config_copy +#define usb2_config_td_softc zyd_softc + +#define USB_DEBUG_VAR zyd_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if USB_DEBUG +static int zyd_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, zyd, CTLFLAG_RW, 0, "USB zyd"); +SYSCTL_INT(_hw_usb2_zyd, OID_AUTO, debug, CTLFLAG_RW, &zyd_debug, 0, + "zyd debug level"); +#endif + +#undef INDEXES +#define INDEXES(a) (sizeof(a) / sizeof((a)[0])) + +static device_probe_t zyd_probe; +static device_attach_t zyd_attach; +static device_detach_t zyd_detach; + +static usb2_callback_t zyd_intr_read_clear_stall_callback; +static usb2_callback_t zyd_intr_read_callback; +static usb2_callback_t zyd_intr_write_clear_stall_callback; +static usb2_callback_t zyd_intr_write_callback; +static usb2_callback_t zyd_bulk_read_clear_stall_callback; +static usb2_callback_t zyd_bulk_read_callback; +static usb2_callback_t zyd_bulk_write_clear_stall_callback; +static usb2_callback_t zyd_bulk_write_callback; + +static usb2_config_td_command_t zyd_cfg_first_time_setup; +static usb2_config_td_command_t zyd_cfg_update_promisc; +static usb2_config_td_command_t zyd_cfg_set_chan; +static usb2_config_td_command_t zyd_cfg_pre_init; +static usb2_config_td_command_t zyd_cfg_init; +static usb2_config_td_command_t zyd_cfg_pre_stop; +static usb2_config_td_command_t zyd_cfg_stop; +static usb2_config_td_command_t zyd_config_copy; +static usb2_config_td_command_t zyd_cfg_scan_start; +static usb2_config_td_command_t zyd_cfg_scan_end; +static usb2_config_td_command_t zyd_cfg_set_rxfilter; +static usb2_config_td_command_t zyd_cfg_amrr_timeout; + +static uint8_t zyd_plcp2ieee(uint8_t signal, uint8_t isofdm); +static void zyd_cfg_usbrequest(struct zyd_softc *sc, struct usb2_device_request *req, uint8_t *data); +static void zyd_cfg_usb2_intr_read(struct zyd_softc *sc, void *data, uint32_t size); +static void zyd_cfg_usb2_intr_write(struct zyd_softc *sc, const void *data, uint16_t code, uint32_t size); +static void zyd_cfg_read16(struct zyd_softc *sc, uint16_t addr, uint16_t *value); +static void zyd_cfg_read32(struct zyd_softc *sc, uint16_t addr, uint32_t *value); +static void zyd_cfg_write16(struct zyd_softc *sc, uint16_t addr, uint16_t value); +static void zyd_cfg_write32(struct zyd_softc *sc, uint16_t addr, uint32_t value); +static void zyd_cfg_rfwrite(struct zyd_softc *sc, uint32_t value); +static uint8_t zyd_cfg_uploadfirmware(struct zyd_softc *sc, const uint8_t *fw_ptr, uint32_t fw_len); +static void zyd_cfg_lock_phy(struct zyd_softc *sc); +static void zyd_cfg_unlock_phy(struct zyd_softc *sc); +static void zyd_cfg_set_beacon_interval(struct zyd_softc *sc, uint32_t interval); +static const char *zyd_rf_name(uint8_t type); +static void zyd_cfg_rf_rfmd_init(struct zyd_softc *sc, struct zyd_rf *rf); +static void zyd_cfg_rf_rfmd_switch_radio(struct zyd_softc *sc, uint8_t onoff); +static void zyd_cfg_rf_rfmd_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, uint8_t channel); +static void zyd_cfg_rf_al2230_switch_radio(struct zyd_softc *sc, uint8_t onoff); +static void zyd_cfg_rf_al2230_init(struct zyd_softc *sc, struct zyd_rf *rf); +static void zyd_cfg_rf_al2230_init_b(struct zyd_softc *sc, struct zyd_rf *rf); +static void zyd_cfg_rf_al2230_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, uint8_t channel); +static uint8_t zyd_cfg_rf_init_hw(struct zyd_softc *sc, struct zyd_rf *rf); +static uint8_t zyd_cfg_hw_init(struct zyd_softc *sc); +static void zyd_cfg_set_mac_addr(struct zyd_softc *sc, const uint8_t *addr); +static void zyd_cfg_switch_radio(struct zyd_softc *sc, uint8_t onoff); +static void zyd_cfg_set_bssid(struct zyd_softc *sc, uint8_t *addr); +static void zyd_start_cb(struct ifnet *ifp); +static void zyd_init_cb(void *arg); +static int zyd_ioctl_cb(struct ifnet *ifp, u_long command, caddr_t data); +static void zyd_watchdog(void *arg); +static void zyd_end_of_commands(struct zyd_softc *sc); +static void zyd_newassoc_cb(struct ieee80211_node *ni, int isnew); +static void zyd_scan_start_cb(struct ieee80211com *ic); +static void zyd_scan_end_cb(struct ieee80211com *ic); +static void zyd_set_channel_cb(struct ieee80211com *ic); +static void zyd_cfg_set_led(struct zyd_softc *sc, uint32_t which, uint8_t on); +static struct ieee80211vap *zyd_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, int opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]); +static void zyd_vap_delete(struct ieee80211vap *); +static struct ieee80211_node *zyd_node_alloc_cb(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]); +static void zyd_cfg_set_run(struct zyd_softc *sc, struct usb2_config_td_cc *cc); +static void zyd_fill_write_queue(struct zyd_softc *sc); +static void zyd_tx_clean_queue(struct zyd_softc *sc); +static void zyd_tx_freem(struct mbuf *m); +static void zyd_tx_mgt(struct zyd_softc *sc, struct mbuf *m, struct ieee80211_node *ni); +static struct ieee80211vap *zyd_get_vap(struct zyd_softc *sc); +static void zyd_tx_data(struct zyd_softc *sc, struct mbuf *m, struct ieee80211_node *ni); +static int zyd_raw_xmit_cb(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params); +static void zyd_setup_desc_and_tx(struct zyd_softc *sc, struct mbuf *m, uint16_t rate); +static int zyd_newstate_cb(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg); +static void zyd_cfg_amrr_start(struct zyd_softc *sc); +static void zyd_update_mcast_cb(struct ifnet *ifp); +static void zyd_update_promisc_cb(struct ifnet *ifp); + +static const struct zyd_phy_pair zyd_def_phy[] = ZYD_DEF_PHY; +static const struct zyd_phy_pair zyd_def_phyB[] = ZYD_DEF_PHYB; + +/* various supported device vendors/products */ +#define ZYD_ZD1211 0 +#define ZYD_ZD1211B 1 + +static const struct usb2_device_id zyd_devs[] = { + /* ZYD_ZD1211 */ + {USB_VPI(USB_VENDOR_3COM2, USB_PRODUCT_3COM2_3CRUSB10075, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_WL54, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_WL159G, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_CYBERTAN, USB_PRODUCT_CYBERTAN_TG54USB, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_DRAYTEK, USB_PRODUCT_DRAYTEK_VIGOR550, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54GD, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54GZL, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_PLANEX3, USB_PRODUCT_PLANEX3_GWUS54GZ, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_PLANEX3, USB_PRODUCT_PLANEX3_GWUS54MINI, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_SAGEM, USB_PRODUCT_SAGEM_XG760A, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_SENAO, USB_PRODUCT_SENAO_NUB8301, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_WL113, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_SWEEX, USB_PRODUCT_SWEEX_ZD1211, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_TEKRAM, USB_PRODUCT_TEKRAM_QUICKWLAN, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_TEKRAM, USB_PRODUCT_TEKRAM_ZD1211_1, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_TEKRAM, USB_PRODUCT_TEKRAM_ZD1211_2, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_TWINMOS, USB_PRODUCT_TWINMOS_G240, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_ALL0298V2, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_TEW429UB_A, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_TEW429UB, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_UR055G, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_ZD1211, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_ZYDAS, USB_PRODUCT_ZYDAS_ZD1211, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_AG225H, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_ZYAIRG220, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_G200V2, ZYD_ZD1211)}, + {USB_VPI(USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_G202, ZYD_ZD1211)}, + /* ZYD_ZD1211B */ + {USB_VPI(USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_SMCWUSBG, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_ZD1211B, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_A9T_WIFI, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050_V4000, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_ZD1211B, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSBF54G, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_FIBERLINE, USB_PRODUCT_FIBERLINE_WL430U, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54L, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_PHILIPS, USB_PRODUCT_PHILIPS_SNU5600, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GW_US54GXS, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_SAGEM, USB_PRODUCT_SAGEM_XG76NA, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_ZD1211B, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_UMEDIA, USB_PRODUCT_UMEDIA_TEW429UBC1, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_USR, USB_PRODUCT_USR_USR5423, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_VTECH, USB_PRODUCT_VTECH_ZD1211B, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_ZD1211B, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_ZYDAS, USB_PRODUCT_ZYDAS_ZD1211B, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_M202, ZYD_ZD1211B)}, + {USB_VPI(USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_G220V2, ZYD_ZD1211B)}, +}; + +static const struct usb2_config zyd_config[ZYD_N_TRANSFER] = { + [ZYD_TR_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = ZYD_MAX_TXBUFSZ, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &zyd_bulk_write_callback, + .ep_index = 0, + .mh.timeout = 10000, /* 10 seconds */ + }, + + [ZYD_TR_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = ZYX_MAX_RXBUFSZ, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &zyd_bulk_read_callback, + .ep_index = 0, + }, + + [ZYD_TR_BULK_CS_WR] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &zyd_bulk_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [ZYD_TR_BULK_CS_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &zyd_bulk_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [ZYD_TR_INTR_DT_WR] = { + .type = UE_BULK_INTR, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = sizeof(struct zyd_cmd), + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &zyd_intr_write_callback, + .mh.timeout = 1000, /* 1 second */ + .ep_index = 1, + }, + + [ZYD_TR_INTR_DT_RD] = { + .type = UE_BULK_INTR, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = sizeof(struct zyd_cmd), + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &zyd_intr_read_callback, + .ep_index = 1, + }, + + [ZYD_TR_INTR_CS_WR] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &zyd_intr_write_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, + + [ZYD_TR_INTR_CS_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .mh.bufsize = sizeof(struct usb2_device_request), + .mh.flags = {}, + .mh.callback = &zyd_intr_read_clear_stall_callback, + .mh.timeout = 1000, /* 1 second */ + .mh.interval = 50, /* 50ms */ + }, +}; + +static devclass_t zyd_devclass; + +static device_method_t zyd_methods[] = { + DEVMETHOD(device_probe, zyd_probe), + DEVMETHOD(device_attach, zyd_attach), + DEVMETHOD(device_detach, zyd_detach), + {0, 0} +}; + +static driver_t zyd_driver = { + .name = "zyd", + .methods = zyd_methods, + .size = sizeof(struct zyd_softc), +}; + +DRIVER_MODULE(zyd, ushub, zyd_driver, zyd_devclass, NULL, 0); +MODULE_DEPEND(zyd, usb2_wlan, 1, 1, 1); +MODULE_DEPEND(zyd, usb2_core, 1, 1, 1); +MODULE_DEPEND(zyd, wlan, 1, 1, 1); +MODULE_DEPEND(zyd, wlan_amrr, 1, 1, 1); + +static uint8_t +zyd_plcp2ieee(uint8_t signal, uint8_t isofdm) +{ + if (isofdm) { + static const uint8_t ofdmrates[16] = + {0, 0, 0, 0, 0, 0, 0, 96, 48, 24, 12, 108, 72, 36, 18}; + + return ofdmrates[signal & 0xf]; + } else { + static const uint8_t cckrates[16] = + {0, 0, 0, 0, 4, 0, 0, 11, 0, 0, 2, 0, 0, 0, 22, 0}; + + return cckrates[signal & 0xf]; + } +} + +/* + * USB request basic wrapper + */ +static void +zyd_cfg_usbrequest(struct zyd_softc *sc, struct usb2_device_request *req, uint8_t *data) +{ + usb2_error_t err; + uint16_t length; + + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto error; + } + err = usb2_do_request_flags + (sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 1000); + + if (err) { + + DPRINTFN(0, "%s: device request failed, err=%s " + "(ignored)\n", sc->sc_name, usb2_errstr(err)); + +error: + length = UGETW(req->wLength); + + if ((req->bmRequestType & UT_READ) && length) { + bzero(data, length); + } + } + return; +} + +static void +zyd_intr_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct zyd_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[ZYD_TR_INTR_DT_RD]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~ZYD_FLAG_INTR_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +/* + * Callback handler for interrupt transfer + */ +static void +zyd_intr_read_callback(struct usb2_xfer *xfer) +{ + struct zyd_softc *sc = xfer->priv_sc; + struct zyd_cmd *cmd = &sc->sc_intr_ibuf; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + actlen = xfer->actlen; + + DPRINTFN(3, "length=%d\n", actlen); + + if (actlen > sizeof(sc->sc_intr_ibuf)) { + actlen = sizeof(sc->sc_intr_ibuf); + } + usb2_copy_out(xfer->frbuffers, 0, + &sc->sc_intr_ibuf, actlen); + + switch (cmd->code) { + case htole16(ZYD_NOTIF_RETRYSTATUS): + goto handle_notif_retrystatus; + case htole16(ZYD_NOTIF_IORD): + goto handle_notif_iord; + default: + DPRINTFN(2, "unknown indication: 0x%04x\n", + le16toh(cmd->code)); + } + + /* fallthrough */ + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_flags & ZYD_FLAG_INTR_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[ZYD_TR_INTR_CS_RD]); + break; + } + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + break; + + default: /* Error */ + DPRINTFN(3, "error = %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= ZYD_FLAG_INTR_READ_STALL; + usb2_transfer_start(sc->sc_xfer[ZYD_TR_INTR_CS_RD]); + } + break; + } + return; + +handle_notif_retrystatus:{ + + struct zyd_notif_retry *retry = (void *)(cmd->data); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211vap *vap; + struct ieee80211_node *ni; + + DPRINTF("retry intr: rate=0x%x " + "addr=%02x:%02x:%02x:%02x:%02x:%02x count=%d (0x%x)\n", + le16toh(retry->rate), retry->macaddr[0], retry->macaddr[1], + retry->macaddr[2], retry->macaddr[3], retry->macaddr[4], + retry->macaddr[5], le16toh(retry->count) & 0xff, + le16toh(retry->count)); + + vap = zyd_get_vap(sc); + if ((vap != NULL) && (sc->sc_amrr_timer)) { + /* + * Find the node to which the packet was sent + * and update its retry statistics. In BSS + * mode, this node is the AP we're associated + * to so no lookup is actually needed. + */ + ni = ieee80211_find_txnode(vap, retry->macaddr); + if (ni != NULL) { + ieee80211_amrr_tx_complete(&ZYD_NODE(ni)->amn, + IEEE80211_AMRR_FAILURE, 1); + ieee80211_free_node(ni); + } + } + if (retry->count & htole16(0x100)) { + ifp->if_oerrors++; /* too many retries */ + } + goto tr_setup; + } + +handle_notif_iord: + + if (*(uint16_t *)cmd->data == htole16(ZYD_CR_INTERRUPT)) { + goto tr_setup; /* HMAC interrupt */ + } + if (actlen < 4) { + DPRINTFN(0, "too short, %u bytes\n", actlen); + goto tr_setup; /* too short */ + } + actlen -= 4; + + sc->sc_intr_ilen = actlen; + + if (sc->sc_intr_iwakeup) { + sc->sc_intr_iwakeup = 0; + usb2_cv_signal(&sc->sc_intr_cv); + } else { + sc->sc_intr_iwakeup = 1; + } + /* + * We pause reading data from the interrupt endpoint until the + * data has been picked up! + */ + return; +} + +/* + * Interrupt call reply transfer, read + */ +static void +zyd_cfg_usb2_intr_read(struct zyd_softc *sc, void *data, uint32_t size) +{ + uint16_t actlen; + uint16_t x; + + if (size > sizeof(sc->sc_intr_ibuf.data)) { + DPRINTFN(0, "truncating transfer size!\n"); + size = sizeof(sc->sc_intr_ibuf.data); + } + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + bzero(data, size); + goto done; + } + if (sc->sc_intr_iwakeup) { + DPRINTF("got data already!\n"); + sc->sc_intr_iwakeup = 0; + goto skip0; + } +repeat: + sc->sc_intr_iwakeup = 1; + + while (sc->sc_intr_iwakeup) { + + /* wait for data */ + + usb2_transfer_start(sc->sc_xfer[ZYD_TR_INTR_DT_RD]); + + if (usb2_cv_timedwait(&sc->sc_intr_cv, + &sc->sc_mtx, hz / 2)) { + /* should not happen */ + } + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + bzero(data, size); + goto done; + } + } +skip0: + if (size != sc->sc_intr_ilen) { + DPRINTFN(0, "unexpected length %u != %u\n", + size, sc->sc_intr_ilen); + goto repeat; + } + actlen = sc->sc_intr_ilen; + actlen /= 4; + + /* verify register values */ + for (x = 0; x != actlen; x++) { + if (sc->sc_intr_obuf.data[(2 * x)] != + sc->sc_intr_ibuf.data[(4 * x)]) { + /* invalid register */ + DPRINTFN(0, "Invalid register (1) at %u!\n", x); + goto repeat; + } + if (sc->sc_intr_obuf.data[(2 * x) + 1] != + sc->sc_intr_ibuf.data[(4 * x) + 1]) { + /* invalid register */ + DPRINTFN(0, "Invalid register (2) at %u!\n", x); + goto repeat; + } + } + + bcopy(sc->sc_intr_ibuf.data, data, size); + + /* + * We have fetched the data from the shared buffer and it is + * safe to restart the interrupt transfer! + */ + usb2_transfer_start(sc->sc_xfer[ZYD_TR_INTR_DT_RD]); +done: + return; +} + +static void +zyd_intr_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct zyd_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[ZYD_TR_INTR_DT_WR]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~ZYD_FLAG_INTR_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +zyd_intr_write_callback(struct usb2_xfer *xfer) +{ + struct zyd_softc *sc = xfer->priv_sc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(3, "length=%d\n", xfer->actlen); + goto wakeup; + + case USB_ST_SETUP: + + if (sc->sc_flags & ZYD_FLAG_INTR_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[ZYD_TR_INTR_CS_WR]); + goto wakeup; + } + if (sc->sc_intr_owakeup) { + usb2_copy_in(xfer->frbuffers, 0, &sc->sc_intr_obuf, + sc->sc_intr_olen); + + xfer->frlengths[0] = sc->sc_intr_olen; + usb2_start_hardware(xfer); + } + break; + + default: /* Error */ + DPRINTFN(3, "error = %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= ZYD_FLAG_INTR_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[ZYD_TR_INTR_CS_WR]); + } + goto wakeup; + } + return; + +wakeup: + if (sc->sc_intr_owakeup) { + sc->sc_intr_owakeup = 0; + usb2_cv_signal(&sc->sc_intr_cv); + } + return; +} + +/* + * Interrupt transfer, write. + * + * Not always an "interrupt transfer". If operating in + * full speed mode, EP4 is bulk out, not interrupt out. + */ +static void +zyd_cfg_usb2_intr_write(struct zyd_softc *sc, const void *data, + uint16_t code, uint32_t size) +{ + if (size > sizeof(sc->sc_intr_obuf.data)) { + DPRINTFN(0, "truncating transfer size!\n"); + size = sizeof(sc->sc_intr_obuf.data); + } + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + goto done; + } + sc->sc_intr_olen = size + 2; + sc->sc_intr_owakeup = 1; + + sc->sc_intr_obuf.code = htole16(code); + bcopy(data, sc->sc_intr_obuf.data, size); + + usb2_transfer_start(sc->sc_xfer[ZYD_TR_INTR_DT_WR]); + + while (sc->sc_intr_owakeup) { + if (usb2_cv_timedwait(&sc->sc_intr_cv, + &sc->sc_mtx, hz / 2)) { + /* should not happen */ + } + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + sc->sc_intr_owakeup = 0; + goto done; + } + } +done: + return; +} + +static void +zyd_cfg_cmd(struct zyd_softc *sc, uint16_t code, const void *idata, uint16_t ilen, + void *odata, uint16_t olen, uint16_t flags) +{ + zyd_cfg_usb2_intr_write(sc, idata, code, ilen); + + if (flags & ZYD_CMD_FLAG_READ) { + zyd_cfg_usb2_intr_read(sc, odata, olen); + } + return; +} + +static void +zyd_cfg_read16(struct zyd_softc *sc, uint16_t addr, uint16_t *value) +{ + struct zyd_pair tmp[1]; + + addr = htole16(addr); + zyd_cfg_cmd(sc, ZYD_CMD_IORD, &addr, sizeof(addr), + tmp, sizeof(tmp), ZYD_CMD_FLAG_READ); + *value = le16toh(tmp[0].val); + return; +} + +static void +zyd_cfg_read32(struct zyd_softc *sc, uint16_t addr, uint32_t *value) +{ + struct zyd_pair tmp[2]; + uint16_t regs[2]; + + regs[0] = ZYD_REG32_HI(addr); + regs[1] = ZYD_REG32_LO(addr); + regs[0] = htole16(regs[0]); + regs[1] = htole16(regs[1]); + + zyd_cfg_cmd(sc, ZYD_CMD_IORD, regs, sizeof(regs), + tmp, sizeof(tmp), ZYD_CMD_FLAG_READ); + *value = (le16toh(tmp[0].val) << 16) | le16toh(tmp[1].val); + return; +} + +static void +zyd_cfg_write16(struct zyd_softc *sc, uint16_t reg, uint16_t val) +{ + struct zyd_pair pair[1]; + + pair[0].reg = htole16(reg); + pair[0].val = htole16(val); + + zyd_cfg_cmd(sc, ZYD_CMD_IOWR, pair, sizeof(pair), NULL, 0, 0); + return; +} + +static void +zyd_cfg_write32(struct zyd_softc *sc, uint16_t reg, uint32_t val) +{ + struct zyd_pair pair[2]; + + pair[0].reg = htole16(ZYD_REG32_HI(reg)); + pair[0].val = htole16(val >> 16); + pair[1].reg = htole16(ZYD_REG32_LO(reg)); + pair[1].val = htole16(val & 0xffff); + + zyd_cfg_cmd(sc, ZYD_CMD_IOWR, pair, sizeof(pair), NULL, 0, 0); + return; +} + +/*------------------------------------------------------------------------* + * zyd_cfg_rfwrite - write RF registers + *------------------------------------------------------------------------*/ +static void +zyd_cfg_rfwrite(struct zyd_softc *sc, uint32_t value) +{ + struct zyd_rf *rf = &sc->sc_rf; + struct zyd_rfwrite req; + uint16_t cr203; + uint16_t i; + + zyd_cfg_read16(sc, ZYD_CR203, &cr203); + cr203 &= ~(ZYD_RF_IF_LE | ZYD_RF_CLK | ZYD_RF_DATA); + + req.code = htole16(2); + req.width = htole16(rf->width); + for (i = 0; i != rf->width; i++) { + req.bit[i] = htole16(cr203); + if (value & (1 << (rf->width - 1 - i))) + req.bit[i] |= htole16(ZYD_RF_DATA); + } + zyd_cfg_cmd(sc, ZYD_CMD_RFCFG, &req, 4 + (2 * rf->width), NULL, 0, 0); + return; +} + +static void +zyd_bulk_read_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct zyd_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[ZYD_TR_BULK_DT_RD]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~ZYD_FLAG_BULK_READ_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +static void +zyd_bulk_read_callback_sub(struct usb2_xfer *xfer, struct zyd_ifq *mq, + uint32_t offset, uint16_t len) +{ + enum { + ZYD_OVERHEAD = (ZYD_HW_PADDING + IEEE80211_CRC_LEN), + }; + struct zyd_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct zyd_plcphdr plcp; + struct zyd_rx_stat stat; + struct mbuf *m; + + if (len < ZYD_OVERHEAD) { + DPRINTF("frame too " + "short (length=%d)\n", len); + ifp->if_ierrors++; + return; + } + usb2_copy_out(xfer->frbuffers, offset, &plcp, sizeof(plcp)); + usb2_copy_out(xfer->frbuffers, offset + len - sizeof(stat), + &stat, sizeof(stat)); + + if (stat.flags & ZYD_RX_ERROR) { + DPRINTF("RX status indicated " + "error (0x%02x)\n", stat.flags); + ifp->if_ierrors++; + return; + } + /* compute actual frame length */ + len -= ZYD_OVERHEAD; + + /* allocate a mbuf to store the frame */ + if (len > MCLBYTES) { + DPRINTF("too large frame, " + "%u bytes\n", len); + return; + } else if (len > MHLEN) + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + else + m = m_gethdr(M_DONTWAIT, MT_DATA); + + if (m == NULL) { + DPRINTF("could not allocate rx mbuf\n"); + ifp->if_ierrors++; + return; + } + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = len; + m->m_len = len; + + usb2_copy_out(xfer->frbuffers, offset + + sizeof(plcp), m->m_data, len); + + if (bpf_peers_present(ifp->if_bpf)) { + struct zyd_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = 0; + if (stat.flags & (ZYD_RX_BADCRC16 | ZYD_RX_BADCRC32)) + tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; + /* XXX toss, no way to express errors */ + if (stat.flags & ZYD_RX_DECRYPTERR) + tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; + tap->wr_rate = + zyd_plcp2ieee(plcp.signal, stat.flags & ZYD_RX_OFDM); + tap->wr_antsignal = stat.rssi + -95; + tap->wr_antnoise = -95; /* XXX */ + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + if (sizeof(m->m_hdr.pad) > 0) { + m->m_hdr.pad[0] = stat.rssi; /* XXX hack */ + } + _IF_ENQUEUE(mq, m); + + return; +} + +static void +zyd_bulk_read_callback(struct usb2_xfer *xfer) +{ + struct zyd_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_node *ni; + struct zyd_rx_desc rx_desc; + struct zyd_ifq mq = {NULL, NULL, 0}; + struct mbuf *m; + uint32_t offset; + uint16_t len16; + uint8_t x; + uint8_t rssi; + int8_t nf; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen < MAX(sizeof(rx_desc), ZYD_MIN_FRAGSZ)) { + DPRINTFN(0, "xfer too short, %d bytes\n", xfer->actlen); + ifp->if_ierrors++; + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, xfer->actlen - sizeof(rx_desc), + &rx_desc, sizeof(rx_desc)); + + if (UGETW(rx_desc.tag) == ZYD_TAG_MULTIFRAME) { + + offset = 0; + + DPRINTFN(4, "received multi-frame transfer, " + "%u bytes\n", xfer->actlen); + + for (x = 0; x < ZYD_MAX_RXFRAMECNT; x++) { + len16 = UGETW(rx_desc.len[x]); + + if ((len16 == 0) || (len16 > xfer->actlen)) { + break; + } + zyd_bulk_read_callback_sub(xfer, &mq, offset, len16); + + /* + * next frame is aligned on a 32-bit + * boundary + */ + len16 = (len16 + 3) & ~3; + offset += len16; + if (len16 > xfer->actlen) { + break; + } + xfer->actlen -= len16; + } + } else { + DPRINTFN(4, "received single-frame transfer, " + "%u bytes\n", xfer->actlen); + zyd_bulk_read_callback_sub(xfer, &mq, 0, xfer->actlen); + } + + case USB_ST_SETUP: +tr_setup: + DPRINTF("setup\n"); + + if (sc->sc_flags & ZYD_FLAG_BULK_READ_STALL) { + usb2_transfer_start(sc->sc_xfer[ZYD_TR_BULK_CS_RD]); + } else { + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + } + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "ieee80211_input" here, and not some lines up! + */ + if (mq.ifq_head) { + + mtx_unlock(&sc->sc_mtx); + + while (1) { + + _IF_DEQUEUE(&mq, m); + + if (m == NULL) + break; + + rssi = m->m_hdr.pad[0]; /* XXX hack */ + + rssi = (rssi > 63) ? 127 : 2 * rssi; + nf = -95; /* XXX */ + + ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + if (ieee80211_input(ni, m, rssi, nf, 0)) { + /* ignore */ + } + ieee80211_free_node(ni); + } else { + if (ieee80211_input_all(ic, m, rssi, nf, 0)) { + /* ignore */ + } + } + } + + mtx_lock(&sc->sc_mtx); + } + break; + + default: /* Error */ + DPRINTF("frame error: %s\n", usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= ZYD_FLAG_BULK_READ_STALL; + usb2_transfer_start(sc->sc_xfer[ZYD_TR_BULK_CS_RD]); + } + break; + } + return; +} + +/*------------------------------------------------------------------------* + * zyd_cfg_uploadfirmware + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +zyd_cfg_uploadfirmware(struct zyd_softc *sc, const uint8_t *fw_ptr, + uint32_t fw_len) +{ + struct usb2_device_request req; + uint16_t temp; + uint16_t addr; + uint8_t stat; + + DPRINTF("firmware %p size=%u\n", fw_ptr, fw_len); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = ZYD_DOWNLOADREQ; + USETW(req.wIndex, 0); + + temp = 64; + + addr = ZYD_FIRMWARE_START_ADDR; + while (fw_len > 0) { + + if (fw_len < 64) { + temp = fw_len; + } + DPRINTF("firmware block: fw_len=%u\n", fw_len); + + USETW(req.wValue, addr); + USETW(req.wLength, temp); + + zyd_cfg_usbrequest(sc, &req, + USB_ADD_BYTES(fw_ptr, 0)); + + addr += (temp / 2); + fw_len -= temp; + fw_ptr += temp; + } + + /* check whether the upload succeeded */ + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = ZYD_DOWNLOADSTS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(stat)); + + zyd_cfg_usbrequest(sc, &req, &stat); + + return ((stat & 0x80) ? 1 : 0); +} + +/* + * Driver OS interface + */ + +/* + * Probe for a ZD1211-containing product + */ +static int +zyd_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != ZYD_IFACE_INDEX) { + return (ENXIO); + } + return (usb2_lookup_id_by_uaa(zyd_devs, sizeof(zyd_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do + * setup and ethernet/BPF attach. + */ +static int +zyd_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct zyd_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + + if (sc == NULL) { + return (ENOMEM); + } + if (uaa->info.bcdDevice < 0x4330) { + device_printf(dev, "device version mismatch: 0x%X " + "(only >= 43.30 supported)\n", + uaa->info.bcdDevice); + return (EINVAL); + } + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + sc->sc_unit = device_get_unit(dev); + sc->sc_udev = uaa->device; + sc->sc_mac_rev = USB_GET_DRIVER_INFO(uaa); + + mtx_init(&sc->sc_mtx, "zyd lock", MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + + usb2_cv_init(&sc->sc_intr_cv, "IWAIT"); + + usb2_callout_init_mtx(&sc->sc_watchdog, + &sc->sc_mtx, CALLOUT_RETURNUNLOCKED); + + /* + * Endpoint 1 = Bulk out (512b @ high speed / 64b @ full speed) + * Endpoint 2 = Bulk in (512b @ high speed / 64b @ full speed) + * Endpoint 3 = Intr in (64b) + * Endpoint 4 = Intr out @ high speed / bulk out @ full speed (64b) + */ + iface_index = ZYD_IFACE_INDEX; + error = usb2_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, zyd_config, ZYD_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "could not allocate USB " + "transfers: %s\n", usb2_errstr(error)); + goto detach; + } + error = usb2_config_td_setup(&sc->sc_config_td, sc, &sc->sc_mtx, + &zyd_end_of_commands, sizeof(struct usb2_config_td_cc), 16); + if (error) { + device_printf(dev, "could not setup config " + "thread!\n"); + goto detach; + } + mtx_lock(&sc->sc_mtx); + + /* start setup */ + + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, &zyd_cfg_first_time_setup, 0, 0); + + /* start watchdog (will exit mutex) */ + + zyd_watchdog(sc); + + return (0); + +detach: + zyd_detach(dev); + return (ENXIO); +} + +/* + * Lock PHY registers + */ +static void +zyd_cfg_lock_phy(struct zyd_softc *sc) +{ + uint32_t temp; + + zyd_cfg_read32(sc, ZYD_MAC_MISC, &temp); + temp &= ~ZYD_UNLOCK_PHY_REGS; + zyd_cfg_write32(sc, ZYD_MAC_MISC, temp); +} + +/* + * Unlock PHY registers + */ +static void +zyd_cfg_unlock_phy(struct zyd_softc *sc) +{ + uint32_t temp; + + zyd_cfg_read32(sc, ZYD_MAC_MISC, &temp); + temp |= ZYD_UNLOCK_PHY_REGS; + zyd_cfg_write32(sc, ZYD_MAC_MISC, temp); +} + +static void +zyd_cfg_set_beacon_interval(struct zyd_softc *sc, uint32_t bintval) +{ + /* XXX this is probably broken.. */ + zyd_cfg_write32(sc, ZYD_CR_ATIM_WND_PERIOD, bintval - 2); + zyd_cfg_write32(sc, ZYD_CR_PRE_TBTT, bintval - 1); + zyd_cfg_write32(sc, ZYD_CR_BCN_INTERVAL, bintval); + return; +} + +/* + * Get RF name + */ +static const char * +zyd_rf_name(uint8_t type) +{ + static const char *const zyd_rfs[] = { + "unknown", "unknown", "UW2451", "UCHIP", "AL2230", + "AL7230B", "THETA", "AL2210", "MAXIM_NEW", "GCT", + "PV2000", "RALINK", "INTERSIL", "RFMD", "MAXIM_NEW2", + "PHILIPS" + }; + + return (zyd_rfs[(type > 15) ? 0 : type]); +} + +/* + * RF driver: Init for RFMD chip + */ +static void +zyd_cfg_rf_rfmd_init(struct zyd_softc *sc, struct zyd_rf *rf) +{ + static const struct zyd_phy_pair phyini[] = ZYD_RFMD_PHY; + static const uint32_t rfini[] = ZYD_RFMD_RF; + uint32_t i; + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + + /* init RFMD radio */ + for (i = 0; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + return; +} + +/* + * RF driver: Switch radio on/off for RFMD chip + */ +static void +zyd_cfg_rf_rfmd_switch_radio(struct zyd_softc *sc, uint8_t on) +{ + zyd_cfg_write16(sc, ZYD_CR10, on ? 0x89 : 0x15); + zyd_cfg_write16(sc, ZYD_CR11, on ? 0x00 : 0x81); + return; +} + +/* + * RF driver: Channel setting for RFMD chip + */ +static void +zyd_cfg_rf_rfmd_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, + uint8_t channel) +{ + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_RFMD_CHANTABLE; + + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r1); + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r2); + return; +} + +/* + * RF driver: Switch radio on/off for AL2230 chip + */ +static void +zyd_cfg_rf_al2230_switch_radio(struct zyd_softc *sc, uint8_t on) +{ + uint8_t on251 = (sc->sc_mac_rev == ZYD_ZD1211) ? 0x3f : 0x7f; + + zyd_cfg_write16(sc, ZYD_CR11, on ? 0x00 : 0x04); + zyd_cfg_write16(sc, ZYD_CR251, on ? on251 : 0x2f); + return; +} + +/* + * RF driver: Init for AL2230 chip + */ +static void +zyd_cfg_rf_al2230_init(struct zyd_softc *sc, struct zyd_rf *rf) +{ + static const struct zyd_phy_pair phyini[] = ZYD_AL2230_PHY; + static const uint32_t rfini[] = ZYD_AL2230_RF; + uint32_t i; + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + + /* init AL2230 radio */ + for (i = 0; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + return; +} + +static void +zyd_cfg_rf_al2230_init_b(struct zyd_softc *sc, struct zyd_rf *rf) +{ + static const struct zyd_phy_pair phyini[] = ZYD_AL2230_PHY_B; + static const uint32_t rfini[] = ZYD_AL2230_RF_B; + uint32_t i; + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + + /* init AL2230 radio */ + for (i = 0; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + return; +} + +/* + * RF driver: Channel setting for AL2230 chip + */ +static void +zyd_cfg_rf_al2230_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, + uint8_t channel) +{ + static const struct { + uint32_t r1, r2, r3; + } rfprog[] = ZYD_AL2230_CHANTABLE; + + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r1); + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r2); + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r3); + + zyd_cfg_write16(sc, ZYD_CR138, 0x28); + zyd_cfg_write16(sc, ZYD_CR203, 0x06); + return; +} + +/* + * AL7230B RF methods. + */ +static void +zyd_cfg_rf_al7230b_switch_radio(struct zyd_softc *sc, uint8_t on) +{ + zyd_cfg_write16(sc, ZYD_CR11, on ? 0x00 : 0x04); + zyd_cfg_write16(sc, ZYD_CR251, on ? 0x3f : 0x2f); + return; +} + +static void +zyd_cfg_rf_al7230b_init(struct zyd_softc *sc, struct zyd_rf *rf) +{ + static const struct zyd_phy_pair phyini_1[] = ZYD_AL7230B_PHY_1; + static const struct zyd_phy_pair phyini_2[] = ZYD_AL7230B_PHY_2; + static const struct zyd_phy_pair phyini_3[] = ZYD_AL7230B_PHY_3; + static const uint32_t rfini_1[] = ZYD_AL7230B_RF_1; + static const uint32_t rfini_2[] = ZYD_AL7230B_RF_2; + uint32_t i; + + /* for AL7230B, PHY and RF need to be initialized in "phases" */ + + /* init RF-dependent PHY registers, part one */ + for (i = 0; i != INDEXES(phyini_1); i++) { + zyd_cfg_write16(sc, phyini_1[i].reg, phyini_1[i].val); + } + /* init AL7230B radio, part one */ + for (i = 0; i != INDEXES(rfini_1); i++) { + zyd_cfg_rfwrite(sc, rfini_1[i]); + } + /* init RF-dependent PHY registers, part two */ + for (i = 0; i != INDEXES(phyini_2); i++) { + zyd_cfg_write16(sc, phyini_2[i].reg, phyini_2[i].val); + } + /* init AL7230B radio, part two */ + for (i = 0; i != INDEXES(rfini_2); i++) { + zyd_cfg_rfwrite(sc, rfini_2[i]); + } + /* init RF-dependent PHY registers, part three */ + for (i = 0; i != INDEXES(phyini_3); i++) { + zyd_cfg_write16(sc, phyini_3[i].reg, phyini_3[i].val); + } + return; +} + +static void +zyd_cfg_rf_al7230b_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, + uint8_t channel) +{ + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_AL7230B_CHANTABLE; + static const uint32_t rfsc[] = ZYD_AL7230B_RF_SETCHANNEL; + uint32_t i; + + zyd_cfg_write16(sc, ZYD_CR240, 0x57); + zyd_cfg_write16(sc, ZYD_CR251, 0x2f); + + for (i = 0; i != INDEXES(rfsc); i++) { + zyd_cfg_rfwrite(sc, rfsc[i]); + } + + zyd_cfg_write16(sc, ZYD_CR128, 0x14); + zyd_cfg_write16(sc, ZYD_CR129, 0x12); + zyd_cfg_write16(sc, ZYD_CR130, 0x10); + zyd_cfg_write16(sc, ZYD_CR38, 0x38); + zyd_cfg_write16(sc, ZYD_CR136, 0xdf); + + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r1); + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r2); + zyd_cfg_rfwrite(sc, 0x3c9000); + + zyd_cfg_write16(sc, ZYD_CR251, 0x3f); + zyd_cfg_write16(sc, ZYD_CR203, 0x06); + zyd_cfg_write16(sc, ZYD_CR240, 0x08); + + return; +} + +/* + * AL2210 RF methods. + */ +static void +zyd_cfg_rf_al2210_switch_radio(struct zyd_softc *sc, uint8_t on) +{ + +} + +static void +zyd_cfg_rf_al2210_init(struct zyd_softc *sc, struct zyd_rf *rf) +{ + static const struct zyd_phy_pair phyini[] = ZYD_AL2210_PHY; + static const uint32_t rfini[] = ZYD_AL2210_RF; + uint32_t tmp; + uint32_t i; + + zyd_cfg_write32(sc, ZYD_CR18, 2); + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + /* init AL2210 radio */ + for (i = 0; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + zyd_cfg_write16(sc, ZYD_CR47, 0x1e); + zyd_cfg_read32(sc, ZYD_CR_RADIO_PD, &tmp); + zyd_cfg_write32(sc, ZYD_CR_RADIO_PD, tmp & ~1); + zyd_cfg_write32(sc, ZYD_CR_RADIO_PD, tmp | 1); + zyd_cfg_write32(sc, ZYD_CR_RFCFG, 0x05); + zyd_cfg_write32(sc, ZYD_CR_RFCFG, 0x00); + zyd_cfg_write16(sc, ZYD_CR47, 0x1e); + zyd_cfg_write32(sc, ZYD_CR18, 3); + + return; +} + +static void +zyd_cfg_rf_al2210_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, + uint8_t channel) +{ + static const uint32_t rfprog[] = ZYD_AL2210_CHANTABLE; + uint32_t tmp; + + zyd_cfg_write32(sc, ZYD_CR18, 2); + zyd_cfg_write16(sc, ZYD_CR47, 0x1e); + zyd_cfg_read32(sc, ZYD_CR_RADIO_PD, &tmp); + zyd_cfg_write32(sc, ZYD_CR_RADIO_PD, tmp & ~1); + zyd_cfg_write32(sc, ZYD_CR_RADIO_PD, tmp | 1); + zyd_cfg_write32(sc, ZYD_CR_RFCFG, 0x05); + + zyd_cfg_write32(sc, ZYD_CR_RFCFG, 0x00); + zyd_cfg_write16(sc, ZYD_CR47, 0x1e); + + /* actually set the channel */ + zyd_cfg_rfwrite(sc, rfprog[channel - 1]); + + zyd_cfg_write32(sc, ZYD_CR18, 3); + return; +} + +/* + * GCT RF methods. + */ +static void +zyd_cfg_rf_gct_switch_radio(struct zyd_softc *sc, uint8_t on) +{ + /* vendor driver does nothing for this RF chip */ + + return; +} + +static void +zyd_cfg_rf_gct_init(struct zyd_softc *sc, struct zyd_rf *rf) +{ + static const struct zyd_phy_pair phyini[] = ZYD_GCT_PHY; + static const uint32_t rfini[] = ZYD_GCT_RF; + uint32_t i; + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + /* init cgt radio */ + for (i = 0; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + return; +} + +static void +zyd_cfg_rf_gct_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, + uint8_t channel) +{ + static const uint32_t rfprog[] = ZYD_GCT_CHANTABLE; + + zyd_cfg_rfwrite(sc, 0x1c0000); + zyd_cfg_rfwrite(sc, rfprog[channel - 1]); + zyd_cfg_rfwrite(sc, 0x1c0008); + + return; +} + +/* + * Maxim RF methods. + */ +static void +zyd_cfg_rf_maxim_switch_radio(struct zyd_softc *sc, uint8_t on) +{ + /* vendor driver does nothing for this RF chip */ + + return; +} + +static void +zyd_cfg_rf_maxim_init(struct zyd_softc *sc, struct zyd_rf *rf) +{ + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM_PHY; + static const uint32_t rfini[] = ZYD_MAXIM_RF; + uint16_t tmp; + uint32_t i; + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + zyd_cfg_read16(sc, ZYD_CR203, &tmp); + zyd_cfg_write16(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* init maxim radio */ + for (i = 0; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + zyd_cfg_read16(sc, ZYD_CR203, &tmp); + zyd_cfg_write16(sc, ZYD_CR203, tmp | (1 << 4)); + + return; +} + +static void +zyd_cfg_rf_maxim_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, + uint8_t channel) +{ + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM_PHY; + static const uint32_t rfini[] = ZYD_MAXIM_RF; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_MAXIM_CHANTABLE; + uint16_t tmp; + uint32_t i; + + /* + * Do the same as we do when initializing it, except for the channel + * values coming from the two channel tables. + */ + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + zyd_cfg_read16(sc, ZYD_CR203, &tmp); + zyd_cfg_write16(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* first two values taken from the chantables */ + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r1); + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r2); + + /* init maxim radio - skipping the two first values */ + if (INDEXES(rfini) > 2) { + for (i = 2; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + } + zyd_cfg_read16(sc, ZYD_CR203, &tmp); + zyd_cfg_write16(sc, ZYD_CR203, tmp | (1 << 4)); + + return; +} + +/* + * Maxim2 RF methods. + */ +static void +zyd_cfg_rf_maxim2_switch_radio(struct zyd_softc *sc, uint8_t on) +{ + /* vendor driver does nothing for this RF chip */ + return; +} + +static void +zyd_cfg_rf_maxim2_init(struct zyd_softc *sc, struct zyd_rf *rf) +{ + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM2_PHY; + static const uint32_t rfini[] = ZYD_MAXIM2_RF; + uint16_t tmp; + uint32_t i; + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + zyd_cfg_read16(sc, ZYD_CR203, &tmp); + zyd_cfg_write16(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* init maxim2 radio */ + for (i = 0; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + zyd_cfg_read16(sc, ZYD_CR203, &tmp); + zyd_cfg_write16(sc, ZYD_CR203, tmp | (1 << 4)); + return; +} + +static void +zyd_cfg_rf_maxim2_set_channel(struct zyd_softc *sc, struct zyd_rf *rf, + uint8_t channel) +{ + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM2_PHY; + static const uint32_t rfini[] = ZYD_MAXIM2_RF; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_MAXIM2_CHANTABLE; + uint16_t tmp; + uint32_t i; + + /* + * Do the same as we do when initializing it, except for the channel + * values coming from the two channel tables. + */ + + /* init RF-dependent PHY registers */ + for (i = 0; i != INDEXES(phyini); i++) { + zyd_cfg_write16(sc, phyini[i].reg, phyini[i].val); + } + zyd_cfg_read16(sc, ZYD_CR203, &tmp); + zyd_cfg_write16(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* first two values taken from the chantables */ + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r1); + zyd_cfg_rfwrite(sc, rfprog[channel - 1].r2); + + /* init maxim2 radio - skipping the two first values */ + if (INDEXES(rfini) > 2) { + for (i = 2; i != INDEXES(rfini); i++) { + zyd_cfg_rfwrite(sc, rfini[i]); + } + } + zyd_cfg_read16(sc, ZYD_CR203, &tmp); + zyd_cfg_write16(sc, ZYD_CR203, tmp | (1 << 4)); + return; +} + +/* + * Assign drivers and init the RF + */ +static uint8_t +zyd_cfg_rf_init_hw(struct zyd_softc *sc, struct zyd_rf *rf) +{ + ; /* fix for indent */ + + switch (sc->sc_rf_rev) { + case ZYD_RF_RFMD: + rf->cfg_init_hw = zyd_cfg_rf_rfmd_init; + rf->cfg_switch_radio = zyd_cfg_rf_rfmd_switch_radio; + rf->cfg_set_channel = zyd_cfg_rf_rfmd_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL2230: + if (sc->sc_mac_rev == ZYD_ZD1211B) + rf->cfg_init_hw = zyd_cfg_rf_al2230_init_b; + else + rf->cfg_init_hw = zyd_cfg_rf_al2230_init; + rf->cfg_switch_radio = zyd_cfg_rf_al2230_switch_radio; + rf->cfg_set_channel = zyd_cfg_rf_al2230_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL7230B: + rf->cfg_init_hw = zyd_cfg_rf_al7230b_init; + rf->cfg_switch_radio = zyd_cfg_rf_al7230b_switch_radio; + rf->cfg_set_channel = zyd_cfg_rf_al7230b_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL2210: + rf->cfg_init_hw = zyd_cfg_rf_al2210_init; + rf->cfg_switch_radio = zyd_cfg_rf_al2210_switch_radio; + rf->cfg_set_channel = zyd_cfg_rf_al2210_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_GCT: + rf->cfg_init_hw = zyd_cfg_rf_gct_init; + rf->cfg_switch_radio = zyd_cfg_rf_gct_switch_radio; + rf->cfg_set_channel = zyd_cfg_rf_gct_set_channel; + rf->width = 21; /* 21-bit RF values */ + break; + case ZYD_RF_MAXIM_NEW: + rf->cfg_init_hw = zyd_cfg_rf_maxim_init; + rf->cfg_switch_radio = zyd_cfg_rf_maxim_switch_radio; + rf->cfg_set_channel = zyd_cfg_rf_maxim_set_channel; + rf->width = 18; /* 18-bit RF values */ + break; + case ZYD_RF_MAXIM_NEW2: + rf->cfg_init_hw = zyd_cfg_rf_maxim2_init; + rf->cfg_switch_radio = zyd_cfg_rf_maxim2_switch_radio; + rf->cfg_set_channel = zyd_cfg_rf_maxim2_set_channel; + rf->width = 18; /* 18-bit RF values */ + break; + default: + DPRINTFN(0, "%s: Sorry, radio %s is not supported yet\n", + sc->sc_name, zyd_rf_name(sc->sc_rf_rev)); + return (1); + } + + zyd_cfg_lock_phy(sc); + (rf->cfg_init_hw) (sc, rf); + zyd_cfg_unlock_phy(sc); + + return (0); /* success */ +} + +/* + * Init the hardware + */ +static uint8_t +zyd_cfg_hw_init(struct zyd_softc *sc) +{ + const struct zyd_phy_pair *phyp; + uint32_t tmp; + + /* specify that the plug and play is finished */ + zyd_cfg_write32(sc, ZYD_MAC_AFTER_PNP, 1); + + zyd_cfg_read16(sc, ZYD_FIRMWARE_BASE_ADDR, &sc->sc_firmware_base); + DPRINTF("firmware base address=0x%04x\n", sc->sc_firmware_base); + + /* retrieve firmware revision number */ + zyd_cfg_read16(sc, sc->sc_firmware_base + ZYD_FW_FIRMWARE_REV, &sc->sc_fw_rev); + + zyd_cfg_write32(sc, ZYD_CR_GPI_EN, 0); + zyd_cfg_write32(sc, ZYD_MAC_CONT_WIN_LIMIT, 0x7f043f); + + /* disable interrupts */ + zyd_cfg_write32(sc, ZYD_CR_INTERRUPT, 0); + + /* PHY init */ + zyd_cfg_lock_phy(sc); + phyp = (sc->sc_mac_rev == ZYD_ZD1211B) ? zyd_def_phyB : zyd_def_phy; + for (; phyp->reg != 0; phyp++) { + zyd_cfg_write16(sc, phyp->reg, phyp->val); + } + if (sc->sc_fix_cr157) { + zyd_cfg_read32(sc, ZYD_EEPROM_PHY_REG, &tmp); + zyd_cfg_write32(sc, ZYD_CR157, tmp >> 8); + } + zyd_cfg_unlock_phy(sc); + + /* HMAC init */ + zyd_cfg_write32(sc, ZYD_MAC_ACK_EXT, 0x00000020); + zyd_cfg_write32(sc, ZYD_CR_ADDA_MBIAS_WT, 0x30000808); + + if (sc->sc_mac_rev == ZYD_ZD1211) { + zyd_cfg_write32(sc, ZYD_MAC_RETRY, 0x00000002); + } else { + zyd_cfg_write32(sc, ZYD_MACB_MAX_RETRY, 0x02020202); + zyd_cfg_write32(sc, ZYD_MACB_TXPWR_CTL4, 0x007f003f); + zyd_cfg_write32(sc, ZYD_MACB_TXPWR_CTL3, 0x007f003f); + zyd_cfg_write32(sc, ZYD_MACB_TXPWR_CTL2, 0x003f001f); + zyd_cfg_write32(sc, ZYD_MACB_TXPWR_CTL1, 0x001f000f); + zyd_cfg_write32(sc, ZYD_MACB_AIFS_CTL1, 0x00280028); + zyd_cfg_write32(sc, ZYD_MACB_AIFS_CTL2, 0x008C003C); + zyd_cfg_write32(sc, ZYD_MACB_TXOP, 0x01800824); + } + + zyd_cfg_write32(sc, ZYD_MAC_SNIFFER, 0x00000000); + zyd_cfg_write32(sc, ZYD_MAC_RXFILTER, 0x00000000); + zyd_cfg_write32(sc, ZYD_MAC_GHTBL, 0x00000000); + zyd_cfg_write32(sc, ZYD_MAC_GHTBH, 0x80000000); + zyd_cfg_write32(sc, ZYD_MAC_MISC, 0x000000a4); + zyd_cfg_write32(sc, ZYD_CR_ADDA_PWR_DWN, 0x0000007f); + zyd_cfg_write32(sc, ZYD_MAC_BCNCFG, 0x00f00401); + zyd_cfg_write32(sc, ZYD_MAC_PHY_DELAY2, 0x00000000); + zyd_cfg_write32(sc, ZYD_MAC_ACK_EXT, 0x00000080); + zyd_cfg_write32(sc, ZYD_CR_ADDA_PWR_DWN, 0x00000000); + zyd_cfg_write32(sc, ZYD_MAC_SIFS_ACK_TIME, 0x00000100); + zyd_cfg_write32(sc, ZYD_MAC_DIFS_EIFS_SIFS, 0x0547c032); + zyd_cfg_write32(sc, ZYD_CR_RX_PE_DELAY, 0x00000070); + zyd_cfg_write32(sc, ZYD_CR_PS_CTRL, 0x10000000); + zyd_cfg_write32(sc, ZYD_MAC_RTSCTSRATE, 0x02030203); + zyd_cfg_write32(sc, ZYD_MAC_RX_THRESHOLD, 0x000c0640); + zyd_cfg_write32(sc, ZYD_MAC_BACKOFF_PROTECT, 0x00000114); + + /* init beacon interval to 100ms */ + zyd_cfg_set_beacon_interval(sc, 100); + + return (0); /* success */ +} + +/* + * Read information from EEPROM + */ +static void +zyd_cfg_read_eeprom(struct zyd_softc *sc) +{ + uint32_t tmp; + uint16_t i; + uint16_t val; + + /* read MAC address */ + zyd_cfg_read32(sc, ZYD_EEPROM_MAC_ADDR_P1, &tmp); + sc->sc_myaddr[0] = tmp & 0xff; + sc->sc_myaddr[1] = tmp >> 8; + sc->sc_myaddr[2] = tmp >> 16; + sc->sc_myaddr[3] = tmp >> 24; + zyd_cfg_read32(sc, ZYD_EEPROM_MAC_ADDR_P2, &tmp); + sc->sc_myaddr[4] = tmp & 0xff; + sc->sc_myaddr[5] = tmp >> 8; + + zyd_cfg_read32(sc, ZYD_EEPROM_POD, &tmp); + sc->sc_rf_rev = tmp & 0x0f; + sc->sc_fix_cr47 = (tmp >> 8) & 0x01; + sc->sc_fix_cr157 = (tmp >> 13) & 0x01; + sc->sc_pa_rev = (tmp >> 16) & 0x0f; + + /* read regulatory domain (currently unused) */ + zyd_cfg_read32(sc, ZYD_EEPROM_SUBID, &tmp); + sc->sc_regdomain = tmp >> 16; + DPRINTF("regulatory domain %x\n", sc->sc_regdomain); + + /* read Tx power calibration tables */ + for (i = 0; i < 7; i++) { + zyd_cfg_read16(sc, ZYD_EEPROM_PWR_CAL + i, &val); + sc->sc_pwr_cal[(i * 2)] = val >> 8; + sc->sc_pwr_cal[(i * 2) + 1] = val & 0xff; + + zyd_cfg_read16(sc, ZYD_EEPROM_PWR_INT + i, &val); + sc->sc_pwr_int[(i * 2)] = val >> 8; + sc->sc_pwr_int[(i * 2) + 1] = val & 0xff; + + zyd_cfg_read16(sc, ZYD_EEPROM_36M_CAL + i, &val); + sc->sc_ofdm36_cal[(i * 2)] = val >> 8; + sc->sc_ofdm36_cal[(i * 2) + 1] = val & 0xff; + + zyd_cfg_read16(sc, ZYD_EEPROM_48M_CAL + i, &val); + sc->sc_ofdm48_cal[(i * 2)] = val >> 8; + sc->sc_ofdm48_cal[(i * 2) + 1] = val & 0xff; + + zyd_cfg_read16(sc, ZYD_EEPROM_54M_CAL + i, &val); + sc->sc_ofdm54_cal[(i * 2)] = val >> 8; + sc->sc_ofdm54_cal[(i * 2) + 1] = val & 0xff; + } + return; +} + +static void +zyd_cfg_set_mac_addr(struct zyd_softc *sc, const uint8_t *addr) +{ + uint32_t tmp; + + tmp = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; + zyd_cfg_write32(sc, ZYD_MAC_MACADRL, tmp); + + tmp = (addr[5] << 8) | addr[4]; + zyd_cfg_write32(sc, ZYD_MAC_MACADRH, tmp); + return; +} + +/* + * Switch radio on/off + */ +static void +zyd_cfg_switch_radio(struct zyd_softc *sc, uint8_t onoff) +{ + zyd_cfg_lock_phy(sc); + (sc->sc_rf.cfg_switch_radio) (sc, onoff); + zyd_cfg_unlock_phy(sc); + + return; +} + +/* + * Set BSSID + */ +static void +zyd_cfg_set_bssid(struct zyd_softc *sc, uint8_t *addr) +{ + uint32_t tmp; + + tmp = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; + zyd_cfg_write32(sc, ZYD_MAC_BSSADRL, tmp); + + tmp = (addr[5] << 8) | addr[4]; + zyd_cfg_write32(sc, ZYD_MAC_BSSADRH, tmp); + return; +} + +/* + * Complete the attach process + */ +static void +zyd_cfg_first_time_setup(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct usb2_config_descriptor *cd; + struct ieee80211com *ic; + struct ifnet *ifp; + const uint8_t *fw_ptr; + uint32_t fw_len; + uint8_t bands; + usb2_error_t err; + + /* setup RX tap header */ + sc->sc_rxtap_len = sizeof(sc->sc_rxtap); + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(ZYD_RX_RADIOTAP_PRESENT); + + /* setup TX tap header */ + sc->sc_txtap_len = sizeof(sc->sc_txtap); + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(ZYD_TX_RADIOTAP_PRESENT); + + if (sc->sc_mac_rev == ZYD_ZD1211) { + fw_ptr = zd1211_firmware; + fw_len = sizeof(zd1211_firmware); + } else { + fw_ptr = zd1211b_firmware; + fw_len = sizeof(zd1211b_firmware); + } + + if (zyd_cfg_uploadfirmware(sc, fw_ptr, fw_len)) { + DPRINTFN(0, "%s: could not " + "upload firmware!\n", sc->sc_name); + return; + } + cd = usb2_get_config_descriptor(sc->sc_udev); + + /* reset device */ + err = usb2_req_set_config(sc->sc_udev, &sc->sc_mtx, + cd->bConfigurationValue); + if (err) { + DPRINTF("reset failed (ignored)\n"); + } + /* Read MAC and other stuff rom EEPROM */ + zyd_cfg_read_eeprom(sc); + + /* Init hardware */ + if (zyd_cfg_hw_init(sc)) { + DPRINTFN(0, "%s: HW init failed!\n", sc->sc_name); + return; + } + /* Now init the RF chip */ + if (zyd_cfg_rf_init_hw(sc, &sc->sc_rf)) { + DPRINTFN(0, "%s: RF init failed!\n", sc->sc_name); + return; + } + printf("%s: HMAC ZD1211%s, FW %02x.%02x, RF %s, PA %x, address %02x:%02x:%02x:%02x:%02x:%02x\n", + sc->sc_name, (sc->sc_mac_rev == ZYD_ZD1211) ? "" : "B", + sc->sc_fw_rev >> 8, sc->sc_fw_rev & 0xff, zyd_rf_name(sc->sc_rf_rev), + sc->sc_pa_rev, sc->sc_myaddr[0], + sc->sc_myaddr[1], sc->sc_myaddr[2], + sc->sc_myaddr[3], sc->sc_myaddr[4], + sc->sc_myaddr[5]); + + mtx_unlock(&sc->sc_mtx); + + ifp = if_alloc(IFT_IEEE80211); + + mtx_lock(&sc->sc_mtx); + + if (ifp == NULL) { + DPRINTFN(0, "%s: could not if_alloc()!\n", + sc->sc_name); + goto done; + } + sc->sc_evilhack = ifp; + sc->sc_ifp = ifp; + ic = ifp->if_l2com; + + ifp->if_softc = sc; + if_initname(ifp, "zyd", sc->sc_unit); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = &zyd_init_cb; + ifp->if_ioctl = &zyd_ioctl_cb; + ifp->if_start = &zyd_start_cb; + ifp->if_watchdog = NULL; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + bcopy(sc->sc_myaddr, ic->ic_myaddr, sizeof(ic->ic_myaddr)); + + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; + ic->ic_opmode = IEEE80211_M_STA; + + /* Set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode supported */ + | IEEE80211_C_MONITOR /* monitor mode */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* capable of bg scanning */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + mtx_unlock(&sc->sc_mtx); + + ieee80211_ifattach(ic); + + mtx_lock(&sc->sc_mtx); + + ic->ic_node_alloc = &zyd_node_alloc_cb; + ic->ic_raw_xmit = &zyd_raw_xmit_cb; + ic->ic_newassoc = &zyd_newassoc_cb; + + ic->ic_scan_start = &zyd_scan_start_cb; + ic->ic_scan_end = &zyd_scan_end_cb; + ic->ic_set_channel = &zyd_set_channel_cb; + ic->ic_vap_create = &zyd_vap_create; + ic->ic_vap_delete = &zyd_vap_delete; + ic->ic_update_mcast = &zyd_update_mcast_cb; + ic->ic_update_promisc = &zyd_update_promisc_cb; + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); + + mtx_unlock(&sc->sc_mtx); + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof(struct ieee80211_frame) + + sizeof(sc->sc_txtap)); + + mtx_lock(&sc->sc_mtx); + + if (bootverbose) { + ieee80211_announce(ic); + } + usb2_transfer_start(sc->sc_xfer[ZYD_TR_INTR_DT_RD]); +done: + return; +} + +/* + * Detach device + */ +static int +zyd_detach(device_t dev) +{ + struct zyd_softc *sc = device_get_softc(dev); + struct ieee80211com *ic; + struct ifnet *ifp; + + usb2_config_td_drain(&sc->sc_config_td); + + mtx_lock(&sc->sc_mtx); + + usb2_callout_stop(&sc->sc_watchdog); + + zyd_cfg_pre_stop(sc, NULL, 0); + + ifp = sc->sc_ifp; + ic = ifp->if_l2com; + + mtx_unlock(&sc->sc_mtx); + + /* stop all USB transfers first */ + usb2_transfer_unsetup(sc->sc_xfer, ZYD_N_TRANSFER); + + /* get rid of any late children */ + bus_generic_detach(dev); + + if (ifp) { + bpfdetach(ifp); + ieee80211_ifdetach(ic); + if_free(ifp); + } + usb2_config_td_unsetup(&sc->sc_config_td); + + usb2_callout_drain(&sc->sc_watchdog); + + usb2_cv_destroy(&sc->sc_intr_cv); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +zyd_cfg_newstate(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct zyd_vap *uvp = ZYD_VAP(vap); + enum ieee80211_state ostate; + enum ieee80211_state nstate; + int arg; + + ostate = vap->iv_state; + nstate = sc->sc_ns_state; + arg = sc->sc_ns_arg; + + switch (nstate) { + case IEEE80211_S_INIT: + break; + + case IEEE80211_S_RUN: + zyd_cfg_set_run(sc, cc); + break; + + default: + break; + } + + mtx_unlock(&sc->sc_mtx); + IEEE80211_LOCK(ic); + uvp->newstate(vap, nstate, arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, nstate, arg); + IEEE80211_UNLOCK(ic); + mtx_lock(&sc->sc_mtx); + return; +} + +static void +zyd_cfg_set_run(struct zyd_softc *sc, + struct usb2_config_td_cc *cc) +{ + zyd_cfg_set_chan(sc, cc, 0); + + if (cc->ic_opmode != IEEE80211_M_MONITOR) { + /* turn link LED on */ + zyd_cfg_set_led(sc, ZYD_LED1, 1); + + /* make data LED blink upon Tx */ + zyd_cfg_write32(sc, sc->sc_firmware_base + ZYD_FW_LINK_STATUS, 1); + + zyd_cfg_set_bssid(sc, cc->iv_bss.ni_bssid); + } + if (cc->iv_bss.fixed_rate_none) { + /* enable automatic rate adaptation */ + zyd_cfg_amrr_start(sc); + } + return; +} + +static int +zyd_newstate_cb(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct zyd_vap *uvp = ZYD_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + DPRINTF("setting new state: %d\n", nstate); + + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_is_gone(&sc->sc_config_td)) { + mtx_unlock(&sc->sc_mtx); + /* Special case which happens at detach. */ + if (nstate == IEEE80211_S_INIT) { + (uvp->newstate) (vap, nstate, arg); + } + return (0); /* nothing to do */ + } + /* store next state */ + sc->sc_ns_state = nstate; + sc->sc_ns_arg = arg; + + /* stop timers */ + sc->sc_amrr_timer = 0; + + /* + * USB configuration can only be done from the USB configuration + * thread: + */ + usb2_config_td_queue_command + (&sc->sc_config_td, &zyd_config_copy, + &zyd_cfg_newstate, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return EINPROGRESS; +} + +static void +zyd_cfg_update_promisc(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t low; + uint32_t high; + + if ((cc->ic_opmode == IEEE80211_M_MONITOR) || + (cc->if_flags & (IFF_ALLMULTI | IFF_PROMISC))) { + low = 0xffffffff; + high = 0xffffffff; + } else { + low = cc->zyd_multi_low; + high = cc->zyd_multi_high; + } + + /* reprogram multicast global hash table */ + zyd_cfg_write32(sc, ZYD_MAC_GHTBL, low); + zyd_cfg_write32(sc, ZYD_MAC_GHTBH, high); + return; +} + +/* + * Rate-to-bit-converter (Field "rate" in zyd_controlsetformat) + */ +static uint8_t +zyd_plcp_signal(uint8_t rate) +{ + ; /* fix for indent */ + + switch (rate) { + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: + return (0x0); + case 4: + return (0x1); + case 11: + return (0x2); + case 22: + return (0x3); + + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: + return (0xb); + case 18: + return (0xf); + case 24: + return (0xa); + case 36: + return (0xe); + case 48: + return (0x9); + case 72: + return (0xd); + case 96: + return (0x8); + case 108: + return (0xc); + + /* XXX unsupported/unknown rate */ + default: + return (0xff); + } +} + +static void +zyd_std_command(struct ieee80211com *ic, usb2_config_td_command_t *func) +{ + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); + + usb2_config_td_queue_command + (&sc->sc_config_td, &zyd_config_copy, func, 0, 0); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +zyd_scan_start_cb(struct ieee80211com *ic) +{ + zyd_std_command(ic, &zyd_cfg_scan_start); + return; +} + +static void +zyd_scan_end_cb(struct ieee80211com *ic) +{ + zyd_std_command(ic, &zyd_cfg_scan_end); + return; +} + +static void +zyd_set_channel_cb(struct ieee80211com *ic) +{ + zyd_std_command(ic, &zyd_cfg_set_chan); + return; +} + +/*========================================================================* + * configure sub-routines, zyd_cfg_xxx + *========================================================================*/ + +static void +zyd_cfg_scan_start(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + zyd_cfg_set_bssid(sc, cc->if_broadcastaddr); + return; +} + +static void +zyd_cfg_scan_end(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + zyd_cfg_set_bssid(sc, cc->iv_bss.ni_bssid); + return; +} + +static void +zyd_cfg_set_chan(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t chan; + uint32_t tmp; + + chan = cc->ic_curchan.chan_to_ieee; + + DPRINTF("Will try %d\n", chan); + + if ((chan == 0) || (chan == IEEE80211_CHAN_ANY)) { + DPRINTF("0 or ANY, exiting\n"); + return; + } + zyd_cfg_lock_phy(sc); + + (sc->sc_rf.cfg_set_channel) (sc, &sc->sc_rf, chan); + + /* update Tx power */ + zyd_cfg_write16(sc, ZYD_CR31, sc->sc_pwr_int[chan - 1]); + + if (sc->sc_mac_rev == ZYD_ZD1211B) { + zyd_cfg_write16(sc, ZYD_CR67, sc->sc_ofdm36_cal[chan - 1]); + zyd_cfg_write16(sc, ZYD_CR66, sc->sc_ofdm48_cal[chan - 1]); + zyd_cfg_write16(sc, ZYD_CR65, sc->sc_ofdm54_cal[chan - 1]); + + zyd_cfg_write16(sc, ZYD_CR68, sc->sc_pwr_cal[chan - 1]); + + zyd_cfg_write16(sc, ZYD_CR69, 0x28); + zyd_cfg_write16(sc, ZYD_CR69, 0x2a); + } + if (sc->sc_fix_cr47) { + /* set CCK baseband gain from EEPROM */ + zyd_cfg_read32(sc, ZYD_EEPROM_PHY_REG, &tmp); + zyd_cfg_write16(sc, ZYD_CR47, tmp & 0xff); + } + zyd_cfg_write32(sc, ZYD_CR_CONFIG_PHILIPS, 0); + + zyd_cfg_unlock_phy(sc); + + sc->sc_rxtap.wr_chan_freq = + sc->sc_txtap.wt_chan_freq = + htole16(cc->ic_curchan.ic_freq); + + sc->sc_rxtap.wr_chan_flags = + sc->sc_txtap.wt_chan_flags = + htole16(cc->ic_flags); + + return; +} + +/* + * Interface: init + */ + +/* immediate configuration */ + +static void +zyd_cfg_pre_init(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + zyd_cfg_pre_stop(sc, cc, 0); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + sc->sc_flags |= ZYD_FLAG_HL_READY; + + IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp)); + + return; +} + +/* delayed configuration */ + +static void +zyd_cfg_init(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + zyd_cfg_stop(sc, cc, 0); + + /* Do initial setup */ + + zyd_cfg_set_mac_addr(sc, cc->ic_myaddr); + + zyd_cfg_write32(sc, ZYD_MAC_ENCRYPTION_TYPE, ZYD_ENC_SNIFFER); + + /* promiscuous mode */ + zyd_cfg_write32(sc, ZYD_MAC_SNIFFER, + (cc->ic_opmode == IEEE80211_M_MONITOR) ? 1 : 0); + + /* multicast setup */ + zyd_cfg_update_promisc(sc, cc, refcount); + + zyd_cfg_set_rxfilter(sc, cc, refcount); + + /* switch radio transmitter ON */ + zyd_cfg_switch_radio(sc, 1); + + /* XXX wrong, can't set here */ + /* set basic rates */ + if (cc->ic_curmode == IEEE80211_MODE_11B) + zyd_cfg_write32(sc, ZYD_MAC_BAS_RATE, 0x0003); + else if (cc->ic_curmode == IEEE80211_MODE_11A) + zyd_cfg_write32(sc, ZYD_MAC_BAS_RATE, 0x1500); + else /* assumes 802.11b/g */ + zyd_cfg_write32(sc, ZYD_MAC_BAS_RATE, 0x000f); + + /* set mandatory rates */ + if (cc->ic_curmode == IEEE80211_MODE_11B) + zyd_cfg_write32(sc, ZYD_MAC_MAN_RATE, 0x000f); + else if (cc->ic_curmode == IEEE80211_MODE_11A) + zyd_cfg_write32(sc, ZYD_MAC_MAN_RATE, 0x1500); + else /* assumes 802.11b/g */ + zyd_cfg_write32(sc, ZYD_MAC_MAN_RATE, 0x150f); + + /* set default BSS channel */ + zyd_cfg_set_chan(sc, cc, 0); + + /* enable interrupts */ + zyd_cfg_write32(sc, ZYD_CR_INTERRUPT, ZYD_HWINT_MASK); + + /* make sure that the transfers get started */ + sc->sc_flags |= ( + ZYD_FLAG_BULK_READ_STALL | + ZYD_FLAG_BULK_WRITE_STALL | + ZYD_FLAG_LL_READY); + + if ((sc->sc_flags & ZYD_FLAG_LL_READY) && + (sc->sc_flags & ZYD_FLAG_HL_READY)) { + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + /* + * start the USB transfers, if not already started: + */ + usb2_transfer_start(sc->sc_xfer[1]); + usb2_transfer_start(sc->sc_xfer[0]); + + /* + * start IEEE802.11 layer + */ + mtx_unlock(&sc->sc_mtx); + ieee80211_start_all(ic); + mtx_lock(&sc->sc_mtx); + } + return; +} + +/* immediate configuration */ + +static void +zyd_cfg_pre_stop(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ifnet *ifp = sc->sc_ifp; + + if (cc) { + /* copy the needed configuration */ + zyd_config_copy(sc, cc, refcount); + } + if (ifp) { + /* clear flags */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + sc->sc_flags &= ~(ZYD_FLAG_HL_READY | + ZYD_FLAG_LL_READY); + + /* + * stop all the transfers, if not already stopped: + */ + usb2_transfer_stop(sc->sc_xfer[ZYD_TR_BULK_DT_WR]); + usb2_transfer_stop(sc->sc_xfer[ZYD_TR_BULK_DT_RD]); + usb2_transfer_stop(sc->sc_xfer[ZYD_TR_BULK_CS_WR]); + usb2_transfer_stop(sc->sc_xfer[ZYD_TR_BULK_CS_RD]); + + /* clean up transmission */ + zyd_tx_clean_queue(sc); + return; +} + +/* delayed configuration */ + +static void +zyd_cfg_stop(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + /* switch radio transmitter OFF */ + zyd_cfg_switch_radio(sc, 0); + + /* disable Rx */ + zyd_cfg_write32(sc, ZYD_MAC_RXFILTER, 0); + + /* disable interrupts */ + zyd_cfg_write32(sc, ZYD_CR_INTERRUPT, 0); + + return; +} + +static void +zyd_update_mcast_cb(struct ifnet *ifp) +{ + struct zyd_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &zyd_config_copy, + &zyd_cfg_update_promisc, 0, 0); + mtx_unlock(&sc->sc_mtx); + return; +} + +static void +zyd_update_promisc_cb(struct ifnet *ifp) +{ + struct zyd_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &zyd_config_copy, + &zyd_cfg_update_promisc, 0, 0); + mtx_unlock(&sc->sc_mtx); + return; +} + +static void +zyd_cfg_set_rxfilter(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + uint32_t rxfilter; + + switch (cc->ic_opmode) { + case IEEE80211_M_STA: + rxfilter = ZYD_FILTER_BSS; + break; + case IEEE80211_M_IBSS: + case IEEE80211_M_HOSTAP: + rxfilter = ZYD_FILTER_HOSTAP; + break; + case IEEE80211_M_MONITOR: + rxfilter = ZYD_FILTER_MONITOR; + break; + default: + /* should not get there */ + return; + } + zyd_cfg_write32(sc, ZYD_MAC_RXFILTER, rxfilter); + return; +} + +static void +zyd_cfg_set_led(struct zyd_softc *sc, uint32_t which, uint8_t on) +{ + uint32_t tmp; + + zyd_cfg_read32(sc, ZYD_MAC_TX_PE_CONTROL, &tmp); + if (on) + tmp |= which; + else + tmp &= ~which; + + zyd_cfg_write32(sc, ZYD_MAC_TX_PE_CONTROL, tmp); + return; +} + +static void +zyd_start_cb(struct ifnet *ifp) +{ + struct zyd_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + usb2_transfer_start(sc->sc_xfer[ZYD_TR_BULK_DT_WR]); + mtx_unlock(&sc->sc_mtx); + return; +} + +static void +zyd_bulk_write_clear_stall_callback(struct usb2_xfer *xfer) +{ + struct zyd_softc *sc = xfer->priv_sc; + struct usb2_xfer *xfer_other = sc->sc_xfer[ZYD_TR_BULK_DT_WR]; + + if (usb2_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~ZYD_FLAG_BULK_WRITE_STALL; + usb2_transfer_start(xfer_other); + } + return; +} + +/* + * We assume that "m->m_pkthdr.rcvif" is pointing to the "ni" that + * should be freed, when "zyd_setup_desc_and_tx" is called. + */ +static void +zyd_setup_desc_and_tx(struct zyd_softc *sc, struct mbuf *m, + uint16_t rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct mbuf *mm; + enum ieee80211_phytype phytype; + uint16_t len; + uint16_t totlen; + uint16_t pktlen; + uint8_t remainder; + + if (sc->sc_tx_queue.ifq_len >= IFQ_MAXLEN) { + /* free packet */ + zyd_tx_freem(m); + ifp->if_oerrors++; + return; + } + if (!((sc->sc_flags & ZYD_FLAG_LL_READY) && + (sc->sc_flags & ZYD_FLAG_HL_READY))) { + /* free packet */ + zyd_tx_freem(m); + ifp->if_oerrors++; + return; + } + if (rate < 2) { + DPRINTF("rate < 2!\n"); + + /* avoid division by zero */ + rate = 2; + } + ic->ic_lastdata = ticks; + + if (bpf_peers_present(ifp->if_bpf)) { + struct zyd_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m); + } + len = m->m_pkthdr.len; + totlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + phytype = ieee80211_rate2phytype(sc->sc_rates, rate); + + sc->sc_tx_desc.len = htole16(totlen); + sc->sc_tx_desc.phy = zyd_plcp_signal(rate); + if (phytype == IEEE80211_T_OFDM) { + sc->sc_tx_desc.phy |= ZYD_TX_PHY_OFDM; + if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) + sc->sc_tx_desc.phy |= ZYD_TX_PHY_5GHZ; + } else if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + sc->sc_tx_desc.phy |= ZYD_TX_PHY_SHPREAMBLE; + + /* actual transmit length (XXX why +10?) */ + pktlen = sizeof(struct zyd_tx_desc) + 10; + if (sc->sc_mac_rev == ZYD_ZD1211) + pktlen += totlen; + sc->sc_tx_desc.pktlen = htole16(pktlen); + + sc->sc_tx_desc.plcp_length = ((16 * totlen) + rate - 1) / rate; + sc->sc_tx_desc.plcp_service = 0; + if (rate == 22) { + remainder = (16 * totlen) % 22; + if ((remainder != 0) && (remainder < 7)) + sc->sc_tx_desc.plcp_service |= ZYD_PLCP_LENGEXT; + } + if (sizeof(sc->sc_tx_desc) > MHLEN) { + DPRINTF("No room for header structure!\n"); + zyd_tx_freem(m); + return; + } + mm = m_gethdr(M_NOWAIT, MT_DATA); + if (mm == NULL) { + DPRINTF("Could not allocate header mbuf!\n"); + zyd_tx_freem(m); + return; + } + bcopy(&sc->sc_tx_desc, mm->m_data, sizeof(sc->sc_tx_desc)); + mm->m_len = sizeof(sc->sc_tx_desc); + + mm->m_next = m; + mm->m_pkthdr.len = mm->m_len + m->m_pkthdr.len; + mm->m_pkthdr.rcvif = NULL; + + /* start write transfer, if not started */ + _IF_ENQUEUE(&sc->sc_tx_queue, mm); + + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +zyd_bulk_write_callback(struct usb2_xfer *xfer) +{ + struct zyd_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint16_t temp_len; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + + ifp->if_opackets++; + + case USB_ST_SETUP: + if (sc->sc_flags & ZYD_FLAG_BULK_WRITE_STALL) { + usb2_transfer_start(sc->sc_xfer[ZYD_TR_BULK_CS_WR]); + DPRINTFN(11, "write stalled\n"); + break; + } + if (sc->sc_flags & ZYD_FLAG_WAIT_COMMAND) { + /* + * don't send anything while a command is pending ! + */ + DPRINTFN(11, "wait command\n"); + break; + } + zyd_fill_write_queue(sc); + + _IF_DEQUEUE(&sc->sc_tx_queue, m); + + if (m) { + if (m->m_pkthdr.len > ZYD_MAX_TXBUFSZ) { + DPRINTFN(0, "data overflow, %u bytes\n", + m->m_pkthdr.len); + m->m_pkthdr.len = ZYD_MAX_TXBUFSZ; + } + usb2_m_copy_in(xfer->frbuffers, 0, + m, 0, m->m_pkthdr.len); + + /* get transfer length */ + temp_len = m->m_pkthdr.len; + + DPRINTFN(11, "sending frame len=%u xferlen=%u\n", + m->m_pkthdr.len, temp_len); + + xfer->frlengths[0] = temp_len; + + usb2_start_hardware(xfer); + + /* free mbuf and node */ + zyd_tx_freem(m); + } + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usb2_errstr(xfer->error)); + + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= ZYD_FLAG_BULK_WRITE_STALL; + usb2_transfer_start(sc->sc_xfer[ZYD_TR_BULK_CS_WR]); + } + ifp->if_oerrors++; + break; + } + return; +} + +static void +zyd_init_cb(void *arg) +{ + struct zyd_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + usb2_config_td_queue_command + (&sc->sc_config_td, &zyd_cfg_pre_init, + &zyd_cfg_init, 0, 0); + mtx_unlock(&sc->sc_mtx); + + return; +} + +static int +zyd_ioctl_cb(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct zyd_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + int error; + + switch (cmd) { + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mtx); + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + usb2_config_td_queue_command + (&sc->sc_config_td, &zyd_cfg_pre_init, + &zyd_cfg_init, 0, 0); + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + usb2_config_td_queue_command + (&sc->sc_config_td, &zyd_cfg_pre_stop, + &zyd_cfg_stop, 0, 0); + } + } + mtx_unlock(&sc->sc_mtx); + error = 0; + break; + + case SIOCGIFMEDIA: + case SIOCADDMULTI: + case SIOCDELMULTI: + error = ifmedia_ioctl(ifp, (void *)data, &ic->ic_media, cmd); + break; + + default: + error = ether_ioctl(ifp, cmd, data); + break; + } + return (error); +} + +static void +zyd_watchdog(void *arg) +{ + struct zyd_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + if (sc->sc_amrr_timer) { + usb2_config_td_queue_command + (&sc->sc_config_td, NULL, + &zyd_cfg_amrr_timeout, 0, 0); + } + usb2_callout_reset(&sc->sc_watchdog, + hz, &zyd_watchdog, sc); + + mtx_unlock(&sc->sc_mtx); + + return; +} + +static void +zyd_config_copy_chan(struct zyd_config_copy_chan *cc, + struct ieee80211com *ic, struct ieee80211_channel *c) +{ + if (!c) + return; + cc->chan_to_ieee = + ieee80211_chan2ieee(ic, c); + if (c != IEEE80211_CHAN_ANYC) { + cc->chan_to_mode = + ieee80211_chan2mode(c); + cc->ic_freq = c->ic_freq; + if (IEEE80211_IS_CHAN_B(c)) + cc->chan_is_b = 1; + if (IEEE80211_IS_CHAN_A(c)) + cc->chan_is_a = 1; + if (IEEE80211_IS_CHAN_2GHZ(c)) + cc->chan_is_2ghz = 1; + if (IEEE80211_IS_CHAN_5GHZ(c)) + cc->chan_is_5ghz = 1; + if (IEEE80211_IS_CHAN_ANYG(c)) + cc->chan_is_g = 1; + } + return; +} + +static void +zyd_config_copy(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + const struct ieee80211_txparam *tp; + struct ieee80211vap *vap; + struct ifmultiaddr *ifma; + struct ieee80211_node *ni; + struct ieee80211com *ic; + struct ifnet *ifp; + + bzero(cc, sizeof(*cc)); + + ifp = sc->sc_ifp; + if (ifp) { + cc->if_flags = ifp->if_flags; + bcopy(ifp->if_broadcastaddr, cc->if_broadcastaddr, + sizeof(cc->if_broadcastaddr)); + + cc->zyd_multi_low = 0x00000000; + cc->zyd_multi_high = 0x80000000; + + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + uint8_t v; + + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + v = ((uint8_t *)LLADDR((struct sockaddr_dl *) + ifma->ifma_addr))[5] >> 2; + if (v < 32) + cc->zyd_multi_low |= 1 << v; + else + cc->zyd_multi_high |= 1 << (v - 32); + } + IF_ADDR_UNLOCK(ifp); + + ic = ifp->if_l2com; + if (ic) { + zyd_config_copy_chan(&cc->ic_curchan, ic, ic->ic_curchan); + zyd_config_copy_chan(&cc->ic_bsschan, ic, ic->ic_bsschan); + vap = TAILQ_FIRST(&ic->ic_vaps); + if (vap) { + ni = vap->iv_bss; + if (ni) { + cc->iv_bss.ni_intval = ni->ni_intval; + bcopy(ni->ni_bssid, cc->iv_bss.ni_bssid, + sizeof(cc->iv_bss.ni_bssid)); + } + tp = vap->iv_txparms + cc->ic_bsschan.chan_to_mode; + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) { + cc->iv_bss.fixed_rate_none = 1; + } + } + cc->ic_opmode = ic->ic_opmode; + cc->ic_flags = ic->ic_flags; + cc->ic_txpowlimit = ic->ic_txpowlimit; + cc->ic_curmode = ic->ic_curmode; + + bcopy(ic->ic_myaddr, cc->ic_myaddr, + sizeof(cc->ic_myaddr)); + } + } + sc->sc_flags |= ZYD_FLAG_WAIT_COMMAND; + return; +} + +static void +zyd_end_of_commands(struct zyd_softc *sc) +{ + sc->sc_flags &= ~ZYD_FLAG_WAIT_COMMAND; + + /* start write transfer, if not started */ + usb2_transfer_start(sc->sc_xfer[0]); + return; +} + +static void +zyd_newassoc_cb(struct ieee80211_node *ni, int isnew) +{ + struct ieee80211vap *vap = ni->ni_vap; + + ieee80211_amrr_node_init(&ZYD_VAP(vap)->amrr, &ZYD_NODE(ni)->amn, ni); + return; +} + +static void +zyd_cfg_amrr_timeout(struct zyd_softc *sc, + struct usb2_config_td_cc *cc, uint16_t refcount) +{ + struct ieee80211vap *vap; + struct ieee80211_node *ni; + + vap = zyd_get_vap(sc); + if (vap == NULL) { + return; + } + ni = vap->iv_bss; + if (ni == NULL) { + return; + } + if ((sc->sc_flags & ZYD_FLAG_LL_READY) && + (sc->sc_flags & ZYD_FLAG_HL_READY)) { + + if (sc->sc_amrr_timer) { + + if (ieee80211_amrr_choose(ni, &ZYD_NODE(ni)->amn)) { + /* ignore */ + } + } + } + return; +} + +static void +zyd_cfg_amrr_start(struct zyd_softc *sc) +{ + struct ieee80211vap *vap; + struct ieee80211_node *ni; + + vap = zyd_get_vap(sc); + + if (vap == NULL) { + return; + } + ni = vap->iv_bss; + if (ni == NULL) { + return; + } + /* init AMRR */ + + ieee80211_amrr_node_init(&ZYD_VAP(vap)->amrr, &ZYD_NODE(ni)->amn, ni); + + /* enable AMRR timer */ + + sc->sc_amrr_timer = 1; + return; +} + +static struct ieee80211vap * +zyd_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct zyd_vap *zvp; + struct ieee80211vap *vap; + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + /* Need to sync with config thread: */ + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_sync(&sc->sc_config_td)) { + mtx_unlock(&sc->sc_mtx); + /* config thread is gone */ + return (NULL); + } + mtx_unlock(&sc->sc_mtx); + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + zvp = (struct zyd_vap *)malloc(sizeof(struct zyd_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (zvp == NULL) + return NULL; + vap = &zvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + zvp->newstate = vap->iv_newstate; + vap->iv_newstate = &zyd_newstate_cb; + + ieee80211_amrr_init(&zvp->amrr, vap, + IEEE80211_AMRR_MIN_SUCCESS_THRESHOLD, + IEEE80211_AMRR_MAX_SUCCESS_THRESHOLD, + 1000 /* 1 sec */ ); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status); + ic->ic_opmode = opmode; + + return (vap); +} + +static void +zyd_vap_delete(struct ieee80211vap *vap) +{ + struct zyd_vap *zvp = ZYD_VAP(vap); + struct zyd_softc *sc = vap->iv_ic->ic_ifp->if_softc; + + /* Need to sync with config thread: */ + mtx_lock(&sc->sc_mtx); + if (usb2_config_td_sync(&sc->sc_config_td)) { + /* ignore */ + } + mtx_unlock(&sc->sc_mtx); + + ieee80211_amrr_cleanup(&zvp->amrr); + ieee80211_vap_detach(vap); + free(zvp, M_80211_VAP); + return; +} + +/* ARGUSED */ +static struct ieee80211_node * +zyd_node_alloc_cb(struct ieee80211vap *vap __unused, + const uint8_t mac[IEEE80211_ADDR_LEN] __unused) +{ + struct zyd_node *zn; + + zn = malloc(sizeof(struct zyd_node), M_80211_NODE, M_NOWAIT | M_ZERO); + return ((zn != NULL) ? &zn->ni : NULL); +} + +static void +zyd_fill_write_queue(struct zyd_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211_node *ni; + struct mbuf *m; + + /* + * We only fill up half of the queue with data frames. The rest is + * reserved for other kinds of frames. + */ + + while (sc->sc_tx_queue.ifq_len < (IFQ_MAXLEN / 2)) { + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + ni = (void *)(m->m_pkthdr.rcvif); + m = ieee80211_encap(ni, m); + if (m == NULL) { + ieee80211_free_node(ni); + continue; + } + zyd_tx_data(sc, m, ni); + } + return; +} + +static void +zyd_tx_clean_queue(struct zyd_softc *sc) +{ + struct mbuf *m; + + for (;;) { + _IF_DEQUEUE(&sc->sc_tx_queue, m); + + if (!m) { + break; + } + zyd_tx_freem(m); + } + + return; +} + +static void +zyd_tx_freem(struct mbuf *m) +{ + struct ieee80211_node *ni; + + while (m) { + ni = (void *)(m->m_pkthdr.rcvif); + if (!ni) { + m = m_free(m); + continue; + } + if (m->m_flags & M_TXCB) { + ieee80211_process_callback(ni, m, 0); + } + m_freem(m); + ieee80211_free_node(ni); + + break; + } + return; +} + +static void +zyd_tx_mgt(struct zyd_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_txparam *tp; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + uint16_t totlen; + uint16_t rate; + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + rate = tp->mgmtrate; + + wh = mtod(m, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m); + if (k == NULL) { + m_freem(m); + ieee80211_free_node(ni); + return; + } + wh = mtod(m, struct ieee80211_frame *); + } + /* fill Tx descriptor */ + + sc->sc_tx_desc.flags = ZYD_TX_FLAG_BACKOFF; + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + /* get total length */ + totlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + /* multicast frames are not sent at OFDM rates in 802.11b/g */ + if (totlen > vap->iv_rtsthreshold) { + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_RTS; + } else if (ZYD_RATE_IS_OFDM(rate) && + (ic->ic_flags & IEEE80211_F_USEPROT)) { + if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_CTS_TO_SELF; + else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_RTS; + } + } else + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_MULTICAST; + + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_PS_POLL)) + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL); + + m->m_pkthdr.rcvif = (void *)ni; + zyd_setup_desc_and_tx(sc, m, rate); + return; +} + +static void +zyd_tx_data(struct zyd_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_txparam *tp; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + uint16_t rate; + + wh = mtod(m, struct ieee80211_frame *); + + sc->sc_tx_desc.flags = ZYD_TX_FLAG_BACKOFF; + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { + rate = tp->mcastrate; + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_MULTICAST; + } else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { + rate = tp->ucastrate; + } else + rate = ni->ni_txrate; + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m); + if (k == NULL) { + m_freem(m); + ieee80211_free_node(ni); + return; + } + /* packet header may have moved, reset our local pointer */ + wh = mtod(m, struct ieee80211_frame *); + } + /* fill Tx descriptor */ + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + uint16_t totlen; + + totlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + + /* multicast frames are not sent at OFDM rates in 802.11b/g */ + if (totlen > vap->iv_rtsthreshold) { + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_RTS; + } else if (ZYD_RATE_IS_OFDM(rate) && + (ic->ic_flags & IEEE80211_F_USEPROT)) { + if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_CTS_TO_SELF; + else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_RTS; + } + } + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_PS_POLL)) + sc->sc_tx_desc.flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL); + + m->m_pkthdr.rcvif = (void *)ni; + zyd_setup_desc_and_tx(sc, m, rate); + return; +} + +static int +zyd_raw_xmit_cb(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct zyd_softc *sc = ifp->if_softc; + + mtx_lock(&sc->sc_mtx); + if (params == NULL) { + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + */ + zyd_tx_mgt(sc, m, ni); + } else { + /* + * Caller supplied explicit parameters to use in + * sending the frame. + */ + zyd_tx_mgt(sc, m, ni); /* XXX zyd_tx_raw() */ + } + mtx_unlock(&sc->sc_mtx); + return (0); +} + +static struct ieee80211vap * +zyd_get_vap(struct zyd_softc *sc) +{ + struct ifnet *ifp; + struct ieee80211com *ic; + + if (sc == NULL) { + return NULL; + } + ifp = sc->sc_ifp; + if (ifp == NULL) { + return NULL; + } + ic = ifp->if_l2com; + if (ic == NULL) { + return NULL; + } + return TAILQ_FIRST(&ic->ic_vaps); +} diff --git a/sys/dev/usb2/wlan/if_zyd2_fw.h b/sys/dev/usb2/wlan/if_zyd2_fw.h new file mode 100644 index 000000000000..3c176e45576d --- /dev/null +++ b/sys/dev/usb2/wlan/if_zyd2_fw.h @@ -0,0 +1,1144 @@ +/* + * Copyright (C) 2001, 2002, 2003,2004 ZyDAS Technology Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted provided + * that the following conditions are met: + * 1. Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistribution 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * 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. + */ + +/* $FreeBSD$ */ + +static const uint8_t zd1211_firmware[] = { + 0x08, 0x91, 0xFF, 0xED, 0x09, 0x93, 0x1E, 0xEE, + 0xD1, 0x94, 0x11, 0xEE, 0x88, 0xD4, 0xD1, 0x96, + 0xD1, 0x98, 0x5C, 0x99, 0x5C, 0x99, 0x4C, 0x99, + 0x04, 0x9D, 0xD1, 0x98, 0xD1, 0x9A, 0x03, 0xEE, + 0xF4, 0x94, 0xD3, 0xD4, 0x41, 0x2A, 0x40, 0x4A, + 0x45, 0xBE, 0x88, 0x92, 0x41, 0x24, 0x40, 0x44, + 0x53, 0xBE, 0x40, 0xF0, 0x93, 0xEE, 0x41, 0xEE, + 0x98, 0x9A, 0xD4, 0xF7, 0x02, 0x00, 0x1F, 0xEC, + 0x00, 0x00, 0xB2, 0xF8, 0x4D, 0x00, 0xA1, 0xEC, + 0x00, 0x00, 0xA6, 0xF7, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xD8, + 0xA0, 0x90, 0x98, 0x9A, 0x98, 0x9A, 0xA0, 0xD8, + 0x40, 0xF0, 0xB4, 0xF0, 0xA0, 0x90, 0x98, 0x9A, + 0xA0, 0xD8, 0x40, 0xF0, 0x64, 0xEF, 0xA0, 0x90, + 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, 0xF6, 0xF0, + 0xA0, 0x90, 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, + 0xF7, 0xF6, 0xA0, 0x90, 0x98, 0x9A, 0xA0, 0xD8, + 0x40, 0xF0, 0xF8, 0xF5, 0xA0, 0x90, 0x98, 0x9A, + 0xA0, 0xD8, 0x40, 0xF0, 0xF1, 0xF0, 0xA0, 0x90, + 0x98, 0x9A, 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, + 0x97, 0xF7, 0xA0, 0x90, 0x98, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x0D, 0x03, 0x03, 0x00, + 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, 0x42, 0x02, + 0xC1, 0x92, 0x03, 0x96, 0x1B, 0xD7, 0x2A, 0x86, + 0x1A, 0xD5, 0x2B, 0x86, 0x09, 0xA3, 0x00, 0x80, + 0x19, 0xD3, 0x2C, 0x86, 0x00, 0xEE, 0x0A, 0x65, + 0xC0, 0x7A, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xFE, 0xFF, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x0D, 0x03, 0x05, 0x00, 0x05, 0x94, 0xC5, 0xD4, + 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, 0x01, 0xD4, + 0x42, 0x02, 0xC1, 0x96, 0x0A, 0x65, 0xC0, 0x7A, + 0x02, 0x99, 0xC4, 0x92, 0x41, 0xA2, 0xC4, 0xD2, + 0xC5, 0x98, 0x1C, 0xD9, 0x2A, 0x86, 0x01, 0x98, + 0x1C, 0xD9, 0x2B, 0x86, 0x1B, 0xD7, 0x2C, 0x86, + 0x00, 0xEE, 0x09, 0xB3, 0xFE, 0xFF, 0xC2, 0xD2, + 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x41, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0xE5, 0xEE, 0x11, 0x93, 0xD8, 0xF7, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xAE, 0xEE, 0x40, 0xF1, + 0x40, 0x92, 0x19, 0xD3, 0xD8, 0xF7, 0xC5, 0x92, + 0x41, 0x92, 0x19, 0xD3, 0x00, 0x83, 0x40, 0x92, + 0x19, 0xD3, 0x00, 0x83, 0x0F, 0x9F, 0x95, 0xF8, + 0x0F, 0x9F, 0x99, 0xEE, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x99, 0xEE, 0x40, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x09, 0x93, 0xC7, 0xF7, 0x19, 0xD3, + 0x91, 0xEC, 0x40, 0xF0, 0x5F, 0xF2, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, 0x0F, 0x9F, + 0x99, 0xEE, 0x41, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0x92, + 0x19, 0xD3, 0x12, 0x95, 0x19, 0xD3, 0x10, 0x95, + 0x19, 0xD3, 0x02, 0x80, 0x19, 0xD3, 0x03, 0x82, + 0x09, 0x93, 0xC7, 0xF7, 0x19, 0xD3, 0x91, 0xEC, + 0x40, 0xF0, 0x5F, 0xF2, 0x40, 0xF0, 0xDE, 0xF3, + 0x11, 0x93, 0x04, 0xEC, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xE3, 0xEE, 0x40, 0x92, 0x19, 0xD3, + 0x04, 0xEC, 0x40, 0xF0, 0x38, 0xF2, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x11, 0x93, 0x44, 0x96, 0x09, 0xB3, 0xFF, 0xFD, + 0x19, 0xD3, 0x44, 0x96, 0x40, 0xF0, 0x90, 0xF7, + 0x6E, 0x92, 0x19, 0xD3, 0x05, 0x84, 0x40, 0xF0, + 0xC4, 0xEE, 0x4B, 0x62, 0x0A, 0x95, 0x2E, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x2B, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x00, 0xEE, 0xD1, 0xD4, 0x0B, 0x97, + 0x2F, 0xEE, 0xD1, 0xD6, 0x0A, 0x95, 0x34, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x39, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x3E, 0xEE, 0xD1, 0xD4, 0x0B, 0x97, + 0x43, 0xEE, 0xD1, 0xD6, 0x0A, 0x95, 0x48, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x4D, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x4E, 0xEE, 0xC1, 0xD4, 0x0A, 0x65, + 0x00, 0x44, 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, + 0xC2, 0xD2, 0x43, 0xF1, 0x09, 0x93, 0x01, 0x3F, + 0x19, 0xD3, 0xC0, 0x85, 0x11, 0x93, 0x44, 0x96, + 0x09, 0xB3, 0xFF, 0xFC, 0x19, 0xD3, 0x44, 0x96, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x03, 0x00, 0x03, 0x96, + 0x41, 0x02, 0x03, 0x99, 0xC4, 0x94, 0x42, 0x04, + 0xC1, 0x04, 0xC2, 0x94, 0xC3, 0xD4, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x40, 0x92, 0x19, 0xD3, 0x94, 0xEC, 0x13, 0x97, + 0x95, 0xEC, 0x1B, 0xD7, 0x02, 0x80, 0x11, 0x93, + 0x99, 0xEC, 0x19, 0xD3, 0x7C, 0x96, 0x0B, 0x97, + 0xA0, 0x00, 0x1B, 0xD7, 0x6E, 0xEC, 0x0A, 0x65, + 0x0E, 0x42, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xFF, 0xBF, 0x11, 0xA3, 0x9A, 0xEC, 0xC2, 0xD2, + 0x0A, 0x65, 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xA3, 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, + 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xBF, 0xFF, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x47, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x14, 0x99, 0x03, 0x80, 0x0C, 0xB3, 0x00, 0x10, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x97, 0xF0, + 0x11, 0x93, 0x9F, 0xEC, 0x41, 0x02, 0x19, 0xD3, + 0x9F, 0xEC, 0x11, 0x93, 0xD6, 0xF7, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x84, 0xEF, 0x0A, 0x65, + 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0x00, 0x04, 0xC2, 0xD2, 0x0F, 0x9F, 0xB1, 0xF0, + 0x11, 0x93, 0x94, 0xEC, 0x02, 0xD2, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xD0, 0xEF, 0x41, 0x92, + 0x19, 0xD3, 0x94, 0xEC, 0x19, 0xD3, 0x9F, 0xEC, + 0x12, 0x95, 0x02, 0x80, 0x1A, 0xD5, 0x95, 0xEC, + 0x13, 0x97, 0x7C, 0x96, 0x1B, 0xD7, 0x99, 0xEC, + 0x0A, 0x65, 0x0E, 0x42, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0x00, 0x40, 0x19, 0xD3, 0x9A, 0xEC, + 0x09, 0x63, 0x00, 0x40, 0xC2, 0xD2, 0x02, 0x94, + 0x1A, 0xD5, 0x7C, 0x96, 0x0C, 0xB3, 0x00, 0x08, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xB0, 0xEF, + 0x0C, 0xB3, 0xFF, 0x07, 0x0F, 0x9F, 0xB4, 0xEF, + 0x11, 0x93, 0x06, 0x80, 0x09, 0xB3, 0xFF, 0x07, + 0x09, 0x03, 0x00, 0xA0, 0x19, 0xD3, 0x97, 0xEC, + 0x40, 0x98, 0x0B, 0x97, 0x9C, 0xEC, 0x04, 0x95, + 0x03, 0x05, 0x14, 0x03, 0x97, 0xEC, 0x46, 0x02, + 0xC1, 0x92, 0xC2, 0xD2, 0x41, 0x08, 0x42, 0x48, + 0x02, 0x9E, 0x0F, 0x9F, 0xBB, 0xEF, 0x11, 0x93, + 0x97, 0xEC, 0xC1, 0x92, 0xC5, 0xD2, 0x5F, 0xB2, + 0x19, 0xD3, 0x9B, 0xEC, 0x0F, 0x9F, 0xD3, 0xEF, + 0x13, 0x97, 0x98, 0xEC, 0xC5, 0xD6, 0x11, 0x93, + 0x03, 0x80, 0x09, 0xB3, 0x00, 0x08, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xE9, 0xEF, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x10, + 0x19, 0xD3, 0xDB, 0xF7, 0x40, 0x98, 0x1C, 0xD9, + 0x9B, 0xEC, 0x12, 0x95, 0x9B, 0xEC, 0x40, 0x44, + 0x02, 0x4E, 0x0F, 0x9F, 0x86, 0xF0, 0x0A, 0xB3, + 0x08, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x07, 0xF0, 0x0A, 0xB3, 0x07, 0x00, 0x09, 0x05, + 0xA9, 0xEC, 0xC2, 0x94, 0x01, 0xD4, 0x09, 0x03, + 0xA1, 0xEC, 0xC1, 0x92, 0x19, 0xD3, 0x9B, 0xEC, + 0xC5, 0x94, 0x0A, 0xB5, 0x00, 0xFF, 0x01, 0xA5, + 0xC5, 0xD4, 0x0F, 0x9F, 0x13, 0xF0, 0x0A, 0x05, + 0xFF, 0xFF, 0x0A, 0x03, 0xB1, 0xEC, 0xC1, 0x92, + 0x01, 0xD2, 0x1A, 0xD5, 0x9B, 0xEC, 0xC5, 0x96, + 0x0B, 0x07, 0xFF, 0xFF, 0xC5, 0xD6, 0x11, 0x93, + 0x97, 0xEC, 0xC5, 0x98, 0xC1, 0xD8, 0x11, 0x93, + 0x97, 0xEC, 0x09, 0x05, 0x0B, 0x00, 0x03, 0xD4, + 0xC2, 0x96, 0x06, 0xD6, 0x7B, 0x95, 0x7A, 0x95, + 0x4C, 0x02, 0xC1, 0x92, 0x59, 0x93, 0x59, 0x93, + 0x01, 0xA5, 0x01, 0x98, 0x0C, 0xF5, 0x7B, 0x93, + 0x09, 0x09, 0x01, 0x00, 0x06, 0x92, 0x09, 0xB3, + 0xFF, 0x00, 0x04, 0xD2, 0x5C, 0x93, 0x59, 0x93, + 0x04, 0x94, 0x01, 0xA5, 0x03, 0x96, 0xC3, 0xD4, + 0x11, 0x93, 0x97, 0xEC, 0x4C, 0x02, 0x05, 0xD2, + 0xC1, 0x92, 0x09, 0xB3, 0x00, 0xFF, 0x7C, 0x95, + 0x7A, 0x95, 0x02, 0xA3, 0x05, 0x98, 0xC4, 0xD2, + 0x12, 0x95, 0x97, 0xEC, 0x45, 0x04, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x00, 0x01, 0xC2, 0xD2, + 0x12, 0x95, 0x9B, 0xEC, 0x0A, 0xB3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x5B, 0xF0, + 0x12, 0x95, 0x97, 0xEC, 0x4A, 0x04, 0x02, 0x99, + 0xC4, 0x92, 0x01, 0x98, 0x0C, 0xF3, 0x7B, 0x93, + 0x41, 0x02, 0x0F, 0x9F, 0x7C, 0xF0, 0x43, 0x44, + 0x02, 0x8E, 0x0F, 0x9F, 0x7D, 0xF0, 0x11, 0x93, + 0x97, 0xEC, 0x42, 0x02, 0x0A, 0x05, 0xFF, 0xFF, + 0xC1, 0xD4, 0x11, 0x93, 0x97, 0xEC, 0x4A, 0x02, + 0x12, 0x95, 0x60, 0x96, 0xC1, 0xD4, 0x12, 0x95, + 0x97, 0xEC, 0x4B, 0x04, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0x1F, 0xFF, 0xC2, 0xD2, 0x12, 0x95, + 0x97, 0xEC, 0x4B, 0x04, 0x11, 0x93, 0x62, 0x96, + 0x41, 0x93, 0x59, 0x93, 0x02, 0x99, 0xC4, 0xA2, + 0xC2, 0xD2, 0xC5, 0x92, 0x19, 0xD3, 0x98, 0xEC, + 0x0A, 0x95, 0x0C, 0x02, 0x1A, 0xD5, 0x02, 0x80, + 0x0F, 0x9F, 0xB1, 0xF0, 0x09, 0x63, 0xFE, 0x7F, + 0x01, 0x97, 0xC3, 0x94, 0x0A, 0xA5, 0x00, 0x04, + 0xC1, 0xD4, 0x11, 0x93, 0x9F, 0xEC, 0x09, 0xA3, + 0x00, 0x01, 0x19, 0xD3, 0x9F, 0xEC, 0x40, 0xF0, + 0x39, 0xEF, 0x0F, 0x9F, 0xB1, 0xF0, 0x11, 0x93, + 0x94, 0xEC, 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0xA6, 0xF0, 0x40, 0xF0, 0x39, 0xEF, 0x11, 0x93, + 0x95, 0xEC, 0x44, 0xB2, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xB1, 0xF0, 0x48, 0x98, 0x1C, 0xD9, + 0x02, 0x80, 0x11, 0x93, 0x91, 0xEC, 0x41, 0x22, + 0x0A, 0x95, 0xB1, 0xF0, 0x88, 0xD4, 0x88, 0xDC, + 0x91, 0x9A, 0x47, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0x04, 0x82, 0x48, 0xB2, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xC8, 0xF0, 0x0A, 0x65, 0xFD, 0x7D, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xFF, 0xFE, + 0xC2, 0xD2, 0x41, 0x92, 0x19, 0xD3, 0xBF, 0xEC, + 0x11, 0x93, 0x04, 0x82, 0x43, 0xB2, 0x12, 0x95, + 0x03, 0x82, 0x02, 0xB3, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xEF, 0xF0, 0x0A, 0xB3, 0x00, 0xFF, + 0x48, 0xA2, 0x19, 0xD3, 0x03, 0x82, 0x40, 0xF0, + 0xEB, 0xF3, 0x11, 0x93, 0xBF, 0xEC, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xEF, 0xF0, 0x11, 0x93, + 0x07, 0x82, 0x11, 0x43, 0x03, 0xEC, 0x02, 0x0E, + 0x0F, 0x9F, 0xEF, 0xF0, 0x11, 0x93, 0x03, 0x82, + 0x09, 0xA3, 0x00, 0x01, 0x19, 0xD3, 0x03, 0x82, + 0x40, 0x96, 0x1B, 0xD7, 0xBF, 0xEC, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x11, 0x93, 0x20, 0xBC, 0xC8, 0xD2, + 0x40, 0xF0, 0x48, 0xF1, 0x41, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x05, 0x00, 0x05, 0x94, + 0x41, 0x02, 0xC1, 0x92, 0x01, 0x97, 0xC3, 0x96, + 0xC2, 0xD6, 0x0A, 0x45, 0x00, 0x95, 0x02, 0x5E, + 0x0F, 0x9F, 0x45, 0xF1, 0xC1, 0x92, 0x41, 0xB2, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x45, 0xF1, + 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x45, 0xF1, 0x41, 0x98, 0x1C, 0xD9, + 0xC0, 0xEC, 0x12, 0x95, 0x02, 0x80, 0x01, 0xD4, + 0x40, 0xF0, 0x56, 0xF2, 0x0B, 0x67, 0xFD, 0x7D, + 0x03, 0x99, 0xC4, 0x92, 0x0C, 0x99, 0x96, 0x03, + 0x1C, 0xD9, 0x06, 0x82, 0x41, 0x98, 0x1C, 0xD9, + 0x02, 0x82, 0x42, 0x98, 0x1C, 0xD9, 0x05, 0x82, + 0x0C, 0x69, 0x80, 0x7F, 0x1C, 0xD9, 0x00, 0xB0, + 0x09, 0xA3, 0x00, 0x01, 0xC3, 0xD2, 0x01, 0x94, + 0x0A, 0xB3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x43, 0xF1, 0x42, 0xA4, 0x1A, 0xD5, + 0x02, 0x80, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x05, 0x92, 0xC5, 0xD2, 0x60, 0xB2, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x55, 0xF1, 0x40, 0xF0, + 0x35, 0xF7, 0xC5, 0x94, 0x0A, 0xB3, 0x10, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x5E, 0xF1, + 0x40, 0xF0, 0x23, 0xF6, 0xC5, 0x96, 0x0B, 0xB3, + 0x40, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x67, 0xF1, 0x40, 0xF0, 0x5D, 0xF5, 0xC5, 0x94, + 0x0A, 0xB3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xC8, 0xF1, 0x13, 0x97, 0x21, 0xBC, + 0x01, 0xD6, 0x0B, 0xB3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x79, 0xF1, 0x40, 0xF0, + 0x62, 0xFB, 0x01, 0x94, 0x0A, 0xB3, 0x04, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x82, 0xF1, + 0x40, 0xF0, 0x6C, 0xFB, 0x01, 0x96, 0x0B, 0xB3, + 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xA2, 0xF1, 0x40, 0xF0, 0xB0, 0xFA, 0x41, 0x92, + 0x19, 0xD3, 0xD5, 0xF7, 0x11, 0x93, 0x03, 0xEC, + 0x09, 0x43, 0x40, 0x00, 0x02, 0x5E, 0x0F, 0x9F, + 0x98, 0xF1, 0x40, 0x94, 0x1A, 0xD5, 0xD5, 0xF7, + 0x11, 0x93, 0x00, 0xEC, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xAB, 0xF1, 0x40, 0xF0, 0x38, 0xF2, + 0x0F, 0x9F, 0xAB, 0xF1, 0x01, 0x96, 0x0B, 0xB3, + 0x08, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xAB, 0xF1, 0x40, 0xF0, 0x7C, 0xFB, 0x01, 0x94, + 0x0A, 0xB3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xB4, 0xF1, 0x40, 0xF0, 0x87, 0xFB, + 0x11, 0x93, 0x10, 0xEC, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xBF, 0xF1, 0x44, 0x96, 0x1B, 0xD7, + 0x0B, 0xBC, 0x0F, 0x9F, 0xC5, 0xF1, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xC5, 0xF1, 0x19, 0xD3, + 0x0B, 0xBC, 0x40, 0x92, 0x19, 0xD3, 0x10, 0xEC, + 0xC5, 0x94, 0x0A, 0xB3, 0x80, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x12, 0xF2, 0x13, 0x97, + 0x28, 0xBC, 0x01, 0xD6, 0x0B, 0xB3, 0x40, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0xDA, 0xF1, + 0x40, 0xF0, 0x18, 0xF7, 0x01, 0x94, 0x0A, 0xB3, + 0x02, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xED, 0xF1, 0x40, 0xF0, 0xC4, 0xEE, 0x40, 0xF0, + 0x8F, 0xFB, 0x40, 0xF0, 0x1B, 0xF2, 0x40, 0x96, + 0x1B, 0xD7, 0x00, 0xEC, 0x41, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x01, 0x94, 0x0A, 0xB3, 0x04, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x09, 0xF2, + 0x40, 0xF0, 0x9E, 0xFB, 0x09, 0x63, 0x00, 0x44, + 0x01, 0x97, 0xC3, 0x94, 0x48, 0xA4, 0xC1, 0xD4, + 0x00, 0xEE, 0x40, 0x92, 0x19, 0xD3, 0x12, 0x95, + 0x19, 0xD3, 0x10, 0x95, 0x19, 0xD3, 0x02, 0x80, + 0x19, 0xD3, 0x03, 0x82, 0x41, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x01, 0x94, 0x0A, 0xB3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x12, 0xF2, + 0x40, 0xF0, 0xAE, 0xFB, 0x0A, 0x65, 0x00, 0x44, + 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, + 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xD3, 0xF2, 0xBD, 0x0A, 0x65, 0xEA, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, + 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xA3, 0x40, 0x00, 0xC2, 0xD2, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, 0x0A, 0x65, + 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xEB, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xBF, 0xFF, + 0xC2, 0xD2, 0x0A, 0x65, 0xEA, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, 0xC2, 0xD2, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x09, 0x93, 0x00, 0x01, 0x19, 0xD3, + 0x02, 0x80, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x93, 0x00, 0x09, + 0x19, 0xD3, 0x02, 0x80, 0x40, 0xF0, 0x56, 0xF2, + 0x40, 0x92, 0x19, 0xD3, 0x94, 0xEC, 0xC8, 0xD2, + 0x09, 0x93, 0x91, 0xEC, 0xC8, 0xD2, 0x40, 0xF0, + 0x2A, 0xEF, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0x3B, 0xF5, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x85, 0xF2, 0x0A, 0x65, 0xFE, 0x7F, 0x02, 0x97, + 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, 0x0F, 0x9F, + 0x92, 0xF2, 0x40, 0xF0, 0x94, 0xF2, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0x92, 0xF2, 0xC8, 0xD2, + 0x09, 0x93, 0x91, 0xEC, 0xC8, 0xD2, 0x40, 0xF0, + 0x2A, 0xEF, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0xF1, 0xBD, 0x19, 0xD3, 0xB6, 0xEC, 0x11, 0x93, + 0xB4, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0xAC, 0xF2, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xC3, 0x94, 0x0A, 0x07, 0x07, 0x00, 0xC1, 0xD6, + 0x0A, 0x05, 0x00, 0xA0, 0x1A, 0xD5, 0x96, 0xEC, + 0x11, 0x93, 0xB6, 0xEC, 0x19, 0xD3, 0x01, 0x80, + 0x0A, 0x65, 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, + 0x41, 0xA2, 0xC2, 0xD2, 0x40, 0x92, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x41, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x13, 0x97, 0xB4, 0xEC, 0x40, 0x46, + 0x02, 0x5E, 0x0F, 0x9F, 0x2C, 0xF3, 0x12, 0x95, + 0x96, 0xEC, 0x0A, 0x03, 0x07, 0x00, 0xC1, 0x92, + 0xC2, 0xD2, 0x11, 0x93, 0x96, 0xEC, 0x09, 0x05, + 0x01, 0x00, 0x48, 0x02, 0xC1, 0x92, 0xC2, 0xD2, + 0x11, 0x93, 0x96, 0xEC, 0x4E, 0x02, 0xC1, 0x94, + 0xC5, 0xD6, 0xC5, 0x92, 0x11, 0x07, 0x96, 0xEC, + 0x0B, 0x03, 0x0F, 0x00, 0xC1, 0x98, 0x46, 0x06, + 0x7A, 0x93, 0x79, 0x93, 0x5C, 0x95, 0x5A, 0x95, + 0x02, 0xA3, 0xC3, 0xD2, 0x04, 0x95, 0xC5, 0x96, + 0x41, 0x06, 0xC5, 0xD6, 0x42, 0x46, 0x02, 0x9E, + 0x0F, 0x9F, 0xD5, 0xF2, 0x11, 0x93, 0x96, 0xEC, + 0x09, 0x05, 0x05, 0x00, 0x41, 0x02, 0xC1, 0x92, + 0xC2, 0xD2, 0x11, 0x93, 0x96, 0xEC, 0xC1, 0x92, + 0x09, 0xB5, 0x1F, 0x00, 0x43, 0x44, 0x02, 0x8E, + 0x0F, 0x9F, 0x02, 0xF3, 0x40, 0x44, 0x02, 0x4E, + 0x0F, 0x9F, 0x03, 0xF3, 0x0A, 0x05, 0xFF, 0xFF, + 0x0F, 0x9F, 0x03, 0xF3, 0x43, 0x94, 0x11, 0x93, + 0x96, 0xEC, 0x42, 0x02, 0xC1, 0xD4, 0x11, 0x93, + 0x96, 0xEC, 0x49, 0x02, 0xC1, 0x92, 0x19, 0xD3, + 0xB4, 0xEC, 0x09, 0x05, 0xF2, 0xFF, 0x1A, 0xD5, + 0x92, 0xEC, 0x09, 0x43, 0xD0, 0x07, 0x02, 0x9E, + 0x0F, 0x9F, 0x2C, 0xF3, 0x11, 0x93, 0xDC, 0xF7, + 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, 0x11, 0x93, + 0xDB, 0xF7, 0x09, 0xA3, 0x40, 0x00, 0x19, 0xD3, + 0xDB, 0xF7, 0x09, 0x63, 0x00, 0x80, 0x01, 0x95, + 0xC2, 0x94, 0x1A, 0xD5, 0xB5, 0xEC, 0x40, 0x96, + 0x1B, 0xD7, 0xB4, 0xEC, 0x0F, 0x9F, 0x92, 0xF3, + 0x11, 0x93, 0x92, 0xEC, 0x12, 0x95, 0xB6, 0xEC, + 0x02, 0x43, 0x02, 0x8E, 0x0F, 0x9F, 0x7A, 0xF3, + 0x02, 0x0E, 0x0F, 0x9F, 0x4D, 0xF3, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x80, 0x00, + 0x19, 0xD3, 0xDB, 0xF7, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x95, 0xC2, 0x94, 0x1A, 0xD5, 0xB5, 0xEC, + 0x40, 0x96, 0x1B, 0xD7, 0xB4, 0xEC, 0x0F, 0x9F, + 0x92, 0xF3, 0x11, 0x93, 0x03, 0x80, 0x09, 0xB3, + 0x00, 0x40, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x5F, 0xF3, 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0x5F, 0xF3, 0x40, 0xF0, + 0xA6, 0xF3, 0x0F, 0x9F, 0x94, 0xF3, 0x41, 0x92, + 0xC8, 0xD2, 0x0A, 0x95, 0x91, 0xEC, 0xC8, 0xD4, + 0x40, 0xF0, 0x2A, 0xEF, 0x42, 0x00, 0x11, 0x93, + 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x72, 0xF3, 0x42, 0x96, 0x1B, 0xD7, 0xC0, 0xEC, + 0x0F, 0x9F, 0x94, 0xF3, 0x0A, 0x65, 0xFE, 0x7F, + 0x02, 0x97, 0xC3, 0x92, 0x42, 0xA2, 0xC2, 0xD2, + 0x0F, 0x9F, 0x94, 0xF3, 0x12, 0x45, 0x03, 0xEC, + 0x02, 0x4E, 0x0F, 0x9F, 0x8C, 0xF3, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x08, + 0x19, 0xD3, 0xDB, 0xF7, 0x1A, 0xD5, 0x92, 0xEC, + 0x11, 0x93, 0x92, 0xEC, 0x19, 0x25, 0x92, 0xEC, + 0x09, 0x63, 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, + 0x41, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, 0xA6, 0xF3, + 0x40, 0x92, 0xC8, 0xD2, 0x09, 0x93, 0x91, 0xEC, + 0xC8, 0xD2, 0x40, 0xF0, 0x2A, 0xEF, 0x42, 0x00, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x11, 0x93, 0xD7, 0xF7, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xB6, 0xF3, 0x0A, 0x65, + 0xBC, 0x69, 0x02, 0x97, 0xC3, 0x92, 0x09, 0x83, + 0x00, 0x02, 0xC2, 0xD2, 0x11, 0x93, 0x03, 0x80, + 0x09, 0xB3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xC9, 0xF3, 0x11, 0x93, 0xDC, 0xF7, + 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, 0x11, 0x93, + 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x20, 0x19, 0xD3, + 0xDB, 0xF7, 0x11, 0x93, 0xB5, 0xEC, 0x19, 0xD3, + 0x04, 0x80, 0x12, 0x95, 0xB4, 0xEC, 0x1A, 0xD5, + 0x05, 0x80, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xC3, 0x96, 0x1B, 0xD7, 0xB5, 0xEC, 0x40, 0x94, + 0x1A, 0xD5, 0xB4, 0xEC, 0x19, 0xD3, 0xF2, 0xBD, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x09, 0x93, 0x96, 0x03, 0x19, 0xD3, + 0x06, 0x82, 0x09, 0x93, 0x00, 0x01, 0x19, 0xD3, + 0x03, 0x82, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x47, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0x01, 0x82, 0xC5, 0xD2, 0x40, 0x94, 0x01, 0xD4, + 0x13, 0x97, 0xB8, 0xEC, 0x02, 0xD6, 0x03, 0x95, + 0x0C, 0x99, 0xBB, 0xEC, 0x04, 0x05, 0x13, 0x97, + 0x03, 0xEC, 0x01, 0x27, 0x02, 0x99, 0xC4, 0x92, + 0x03, 0x03, 0xC2, 0xD2, 0x14, 0x99, 0xBA, 0xEC, + 0x03, 0x09, 0x1C, 0xD9, 0xBA, 0xEC, 0x12, 0x95, + 0x04, 0x82, 0x0A, 0xB3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x29, 0xF5, 0x01, 0x92, + 0x03, 0xD2, 0x0A, 0xA3, 0x02, 0x00, 0x19, 0xD3, + 0x04, 0x82, 0x02, 0x96, 0x0B, 0x05, 0x01, 0x00, + 0x1A, 0xD5, 0xB8, 0xEC, 0xC5, 0x92, 0x43, 0x42, + 0x02, 0x9E, 0x0F, 0x9F, 0x37, 0xF4, 0x42, 0x44, + 0x02, 0x8E, 0x0F, 0x9F, 0x37, 0xF4, 0x11, 0x93, + 0xBF, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x37, 0xF4, 0x0C, 0x49, 0xD3, 0x08, 0x02, 0x8E, + 0x0F, 0x9F, 0x37, 0xF4, 0x11, 0x63, 0x07, 0x82, + 0x11, 0xA3, 0x07, 0x82, 0x71, 0x93, 0x79, 0x93, + 0x79, 0x93, 0x79, 0x93, 0x03, 0xD2, 0xC5, 0x94, + 0x0A, 0xB5, 0xFC, 0xFF, 0x04, 0xD4, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x46, 0xF4, + 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, 0x02, 0x8E, + 0x0F, 0x9F, 0x4D, 0xF4, 0xC5, 0x98, 0x0C, 0x03, + 0xFF, 0xFF, 0x42, 0x42, 0x02, 0x8E, 0x0F, 0x9F, + 0x74, 0xF4, 0x0A, 0x95, 0xBB, 0xEC, 0x42, 0x92, + 0x19, 0xD3, 0xB9, 0xEC, 0xC5, 0x96, 0x43, 0x46, + 0x02, 0x9E, 0x0F, 0x9F, 0x66, 0xF4, 0x0B, 0x07, + 0xFC, 0xFF, 0xC5, 0xD6, 0xD2, 0x98, 0x1C, 0xD9, + 0xC8, 0xBC, 0xD2, 0x96, 0x1B, 0xD7, 0xCA, 0xBC, + 0x09, 0x03, 0xFF, 0xFF, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x52, 0xF4, 0x19, 0xD3, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x72, 0xF4, + 0x0A, 0x05, 0xFE, 0xFF, 0xCA, 0xD2, 0xC2, 0xD2, + 0x0F, 0x9F, 0x74, 0xF4, 0x1A, 0xD5, 0x93, 0xEC, + 0x03, 0x98, 0x40, 0x48, 0x02, 0x5E, 0x0F, 0x9F, + 0xA1, 0xF4, 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, + 0x02, 0x9E, 0x0F, 0x9F, 0x84, 0xF4, 0x04, 0x94, + 0x48, 0x44, 0x02, 0x4E, 0x0F, 0x9F, 0x8F, 0xF4, + 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xA1, 0xF4, + 0x11, 0x93, 0x04, 0x82, 0x41, 0xB2, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xA1, 0xF4, 0x41, 0x96, + 0x01, 0xD6, 0x0A, 0x65, 0xBD, 0x43, 0x02, 0x99, + 0xC4, 0x92, 0x09, 0xA3, 0x80, 0x00, 0xC2, 0xD2, + 0x0A, 0x65, 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x0F, 0x9F, + 0xFA, 0xF4, 0xC5, 0x98, 0x43, 0x48, 0x02, 0x9E, + 0x0F, 0x9F, 0xFA, 0xF4, 0x4F, 0x96, 0x0C, 0xB3, + 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xAE, 0xF4, 0x47, 0x96, 0x11, 0x93, 0xB7, 0xEC, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0xD6, 0xF4, + 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xD6, 0xF4, 0x12, 0x95, 0x00, 0x82, + 0x0A, 0x05, 0xFF, 0xAF, 0x05, 0xD4, 0xC8, 0xD6, + 0xC8, 0xD2, 0x40, 0xF0, 0x7B, 0xF7, 0x42, 0x00, + 0x05, 0x96, 0xC3, 0x94, 0x01, 0xB5, 0x40, 0x44, + 0x02, 0x4E, 0x0F, 0x9F, 0xD6, 0xF4, 0x06, 0x98, + 0x50, 0x98, 0x1C, 0xD9, 0xA2, 0xBC, 0x40, 0x98, + 0x1C, 0xD9, 0xA2, 0xBC, 0x40, 0x92, 0x03, 0xD2, + 0x0F, 0x9F, 0xFF, 0xF4, 0x03, 0x94, 0x40, 0x44, + 0x02, 0x5E, 0x0F, 0x9F, 0xE3, 0xF4, 0x0A, 0x65, + 0x5E, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x48, 0xA2, + 0xC2, 0xD2, 0x0F, 0x9F, 0xFF, 0xF4, 0x11, 0x93, + 0xB8, 0xEC, 0x0C, 0x99, 0xBB, 0xEC, 0x04, 0x03, + 0x04, 0x96, 0x13, 0x25, 0x03, 0xEC, 0xC1, 0xD4, + 0x11, 0x93, 0xBA, 0xEC, 0x19, 0x05, 0xBA, 0xEC, + 0x1B, 0xD7, 0x01, 0x82, 0x0A, 0x65, 0xFD, 0x7D, + 0x02, 0x99, 0xC4, 0x92, 0x43, 0xA2, 0xC2, 0xD2, + 0x41, 0x92, 0x01, 0xD2, 0x03, 0x94, 0x40, 0x44, + 0x02, 0x5E, 0x0F, 0x9F, 0x13, 0xF5, 0x11, 0x93, + 0xB9, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x0B, 0xF5, 0x19, 0xD3, 0xB8, 0xEC, 0x19, 0xD3, + 0xBA, 0xEC, 0x19, 0xD3, 0xBB, 0xEC, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x13, 0xF5, + 0x41, 0x98, 0x1C, 0xD9, 0xB7, 0xEC, 0x11, 0x93, + 0xBF, 0xEC, 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x24, 0xF5, 0x11, 0x93, 0x00, 0x82, 0x19, 0xD3, + 0x02, 0x82, 0x0A, 0x65, 0xFD, 0x7D, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x00, 0x01, 0xC2, 0xD2, + 0x40, 0x98, 0x1C, 0xD9, 0xBF, 0xEC, 0x0F, 0x9F, + 0x2C, 0xF5, 0x01, 0x92, 0x19, 0xD3, 0xB7, 0xEC, + 0x01, 0x94, 0x40, 0x44, 0x02, 0x5E, 0x0F, 0x9F, + 0x38, 0xF5, 0x0A, 0x65, 0xEA, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, 0xC2, 0xD2, + 0x47, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x12, 0x95, 0x03, 0x80, + 0x0A, 0xB3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x57, 0xF5, 0x0A, 0xB7, 0x00, 0x08, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x5A, 0xF5, + 0x11, 0x93, 0x03, 0xEC, 0x41, 0x02, 0x09, 0xB3, + 0xFE, 0xFF, 0x12, 0x95, 0x07, 0x80, 0x01, 0x45, + 0x02, 0x8E, 0x0F, 0x9F, 0x5A, 0xF5, 0x41, 0x92, + 0x0F, 0x9F, 0x5B, 0xF5, 0x40, 0x92, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x41, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x40, 0x00, 0xC2, 0xD2, + 0x13, 0x97, 0x6E, 0xEC, 0x0B, 0x47, 0xA0, 0x00, + 0x02, 0x5E, 0x0F, 0x9F, 0x86, 0xF5, 0x09, 0x63, + 0x08, 0x43, 0x0A, 0x65, 0xFF, 0x5F, 0x01, 0x99, + 0xC4, 0xD4, 0x0A, 0x95, 0x9B, 0xEC, 0xD2, 0x96, + 0x1B, 0xD7, 0xFA, 0xBC, 0xD2, 0x96, 0xC4, 0xD6, + 0xD2, 0x98, 0x1C, 0xD9, 0xFA, 0xBC, 0xD2, 0x96, + 0xC1, 0xD6, 0xC2, 0x94, 0x1A, 0xD5, 0xFA, 0xBC, + 0x0F, 0x9F, 0xC4, 0xF5, 0x0C, 0x69, 0xFF, 0x6F, + 0x1C, 0xD9, 0xF8, 0xBC, 0x0B, 0x47, 0x10, 0x95, + 0x02, 0x5E, 0x0F, 0x9F, 0x9E, 0xF5, 0x0A, 0x95, + 0x6F, 0xEC, 0x09, 0x63, 0x06, 0x43, 0x01, 0x99, + 0xC4, 0xD6, 0xD2, 0x96, 0x1B, 0xD7, 0xF8, 0xBC, + 0x0C, 0x69, 0xEE, 0x6A, 0xC1, 0xD8, 0xC2, 0x94, + 0x1A, 0xD5, 0xF8, 0xBC, 0x40, 0x92, 0xC5, 0xD2, + 0x11, 0x43, 0xC1, 0xEC, 0x02, 0x0E, 0x0F, 0x9F, + 0xC1, 0xF5, 0xC5, 0x94, 0x0A, 0x03, 0x71, 0xEC, + 0xC1, 0x94, 0x1A, 0xD5, 0xFA, 0xBC, 0x11, 0x93, + 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xB3, 0xF5, 0x0A, 0x95, 0x6F, 0xEC, 0xC8, 0xD4, + 0x40, 0xF0, 0x9C, 0xF7, 0x19, 0xD3, 0xF8, 0xBC, + 0x41, 0x00, 0xC5, 0x96, 0x41, 0x06, 0xC5, 0xD6, + 0x13, 0x47, 0xC1, 0xEC, 0x02, 0x1E, 0x0F, 0x9F, + 0xA5, 0xF5, 0x40, 0x98, 0x1C, 0xD9, 0xFA, 0xBC, + 0x40, 0x92, 0x19, 0xD3, 0x6E, 0xEC, 0x19, 0xD3, + 0xC1, 0xEC, 0x0A, 0x65, 0x52, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x48, 0xA2, 0xC2, 0xD2, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x99, 0xC4, 0x92, 0x09, 0xB3, + 0xBF, 0xFF, 0xC2, 0xD2, 0x41, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x43, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x06, 0x92, 0x01, 0xD2, 0x0A, 0x65, + 0xF0, 0x6A, 0x0B, 0x97, 0x6F, 0xEC, 0x02, 0x99, + 0xC4, 0x98, 0xD3, 0xD8, 0x02, 0xD6, 0x0A, 0x03, + 0x02, 0x00, 0x01, 0x97, 0xC3, 0x98, 0x02, 0x96, + 0xC3, 0xD8, 0x01, 0x96, 0xC1, 0xD6, 0x1A, 0xD5, + 0x6E, 0xEC, 0xC5, 0x98, 0x14, 0x99, 0x6F, 0xEC, + 0xC2, 0xD8, 0x43, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0x92, + 0xC8, 0xD2, 0x40, 0xF0, 0xD9, 0xF5, 0x41, 0x00, + 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x13, 0xF6, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x10, 0xF6, 0x0A, 0x65, 0xFE, 0x7F, + 0x02, 0x97, 0xC3, 0x92, 0x42, 0xA2, 0xC2, 0xD2, + 0x40, 0x92, 0x19, 0xD3, 0xC0, 0xEC, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xE9, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xBF, 0xFF, + 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x63, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0xAF, 0xBC, 0x47, 0xB2, 0x59, 0x95, 0x5A, 0x95, + 0x12, 0xA5, 0xBF, 0xBC, 0x0A, 0xB3, 0x01, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x35, 0xF6, + 0x41, 0x04, 0x05, 0x93, 0x40, 0x96, 0x20, 0xD6, + 0x62, 0x97, 0x0F, 0x9F, 0x44, 0xF6, 0x14, 0x99, + 0xFC, 0xBC, 0xD1, 0xD8, 0x14, 0x99, 0xFE, 0xBC, + 0xD1, 0xD8, 0x20, 0x98, 0x42, 0x08, 0x20, 0xD8, + 0x20, 0x98, 0x03, 0x49, 0x02, 0x1E, 0x0F, 0x9F, + 0x3B, 0xF6, 0xC5, 0x92, 0x62, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x5D, 0xF6, 0x02, 0x8E, 0x0F, 0x9F, + 0x57, 0xF6, 0x61, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x81, 0xF6, 0x0F, 0x9F, 0xAE, 0xF6, 0x63, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xA4, 0xF6, 0x0F, 0x9F, + 0xAE, 0xF6, 0x0D, 0x03, 0x01, 0x00, 0x0C, 0x99, + 0x71, 0xEC, 0x0B, 0x05, 0xFF, 0xFF, 0x40, 0x96, + 0x0F, 0x9F, 0x6A, 0xF6, 0xD1, 0x96, 0xD4, 0xD6, + 0x20, 0x96, 0x41, 0x06, 0x20, 0xD6, 0x02, 0x47, + 0x02, 0x1E, 0x0F, 0x9F, 0x66, 0xF6, 0x1A, 0xD5, + 0xC1, 0xEC, 0x0A, 0x65, 0xEB, 0x43, 0x02, 0x99, + 0xC4, 0x92, 0x09, 0xA3, 0xC0, 0x00, 0xC2, 0xD2, + 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x0F, 0x9F, + 0xAE, 0xF6, 0x0A, 0x03, 0xFE, 0xFF, 0x61, 0x95, + 0x40, 0x98, 0x20, 0xD8, 0x02, 0x49, 0x02, 0x0E, + 0x0F, 0x9F, 0xAE, 0xF6, 0x0D, 0x03, 0x01, 0x00, + 0x21, 0xD2, 0x20, 0x92, 0x05, 0x03, 0x42, 0x02, + 0xC8, 0xD2, 0x21, 0x96, 0xC3, 0x92, 0x42, 0x06, + 0x21, 0xD6, 0xC8, 0xD2, 0x22, 0xD4, 0x40, 0xF0, + 0x01, 0xF1, 0x42, 0x00, 0x20, 0x98, 0x42, 0x08, + 0x20, 0xD8, 0x22, 0x94, 0x02, 0x49, 0x02, 0x1E, + 0x0F, 0x9F, 0x8D, 0xF6, 0x0F, 0x9F, 0xAE, 0xF6, + 0x0D, 0x03, 0x03, 0x00, 0xC8, 0xD2, 0x02, 0x92, + 0xC8, 0xD2, 0x01, 0x96, 0xC8, 0xD6, 0x40, 0xF0, + 0xB1, 0xF6, 0x43, 0x00, 0x63, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x45, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x08, 0x00, 0x08, 0x94, + 0xC5, 0xD4, 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, + 0x03, 0xD4, 0x42, 0x02, 0xC1, 0x92, 0x01, 0xD2, + 0x02, 0x97, 0xC5, 0x94, 0x0A, 0x83, 0xFF, 0xFF, + 0x11, 0xB3, 0x2C, 0x93, 0x09, 0xB3, 0xFB, 0xFF, + 0x19, 0xD3, 0x2C, 0x93, 0x03, 0x92, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xE4, 0xF6, 0x01, 0x94, + 0xD2, 0x92, 0x19, 0xD3, 0x2C, 0x93, 0x01, 0xD4, + 0x02, 0x94, 0x12, 0x95, 0x2C, 0x93, 0x44, 0xA4, + 0x1A, 0xD5, 0x2C, 0x93, 0x0A, 0xB5, 0xFB, 0xFF, + 0x1A, 0xD5, 0x2C, 0x93, 0x0B, 0x07, 0xFF, 0xFF, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0xCF, 0xF6, + 0x09, 0x63, 0xD4, 0x6C, 0x01, 0x95, 0xC2, 0x96, + 0xC5, 0x94, 0x02, 0xA7, 0xC1, 0xD6, 0x03, 0x92, + 0x54, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xF4, 0xF6, + 0x0A, 0x83, 0xFF, 0xFF, 0x1B, 0xB3, 0x2C, 0x93, + 0x45, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xD3, 0xF2, 0xBD, 0x40, 0xF0, 0x3B, 0xF5, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x08, 0xF7, + 0x40, 0xF0, 0x94, 0xF2, 0x0F, 0x9F, 0x16, 0xF7, + 0x40, 0x96, 0xC8, 0xD6, 0x09, 0x93, 0x91, 0xEC, + 0xC8, 0xD2, 0x40, 0xF0, 0x2A, 0xEF, 0x0A, 0x65, + 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, + 0xC2, 0xD2, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x0A, 0x65, + 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0x40, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xEA, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, + 0xC2, 0xD2, 0x40, 0x92, 0x19, 0xD3, 0x2D, 0xBC, + 0x0A, 0x65, 0xD8, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x09, 0x63, 0xEA, 0x43, 0x01, 0x97, 0xC3, 0x94, + 0x44, 0xA4, 0xC1, 0xD4, 0x11, 0x93, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x6F, 0xF7, + 0x12, 0x95, 0x93, 0xEC, 0x0B, 0x67, 0x36, 0x43, + 0xD2, 0x98, 0x1C, 0xD9, 0xC8, 0xBC, 0xD2, 0x98, + 0x03, 0x93, 0xC1, 0xD8, 0x11, 0x93, 0xB9, 0xEC, + 0x09, 0x03, 0xFF, 0xFF, 0x19, 0xD3, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x48, 0xF7, + 0x19, 0xD3, 0xB8, 0xEC, 0x19, 0xD3, 0xBA, 0xEC, + 0x0A, 0x05, 0xFE, 0xFF, 0xCA, 0xD2, 0xCA, 0xD2, + 0xC2, 0xD2, 0x0A, 0x65, 0x5E, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x48, 0xA2, 0xC2, 0xD2, 0x0A, 0x65, + 0xEA, 0x43, 0x02, 0x99, 0xC4, 0x92, 0x09, 0xB3, + 0xFB, 0xFF, 0x0F, 0x9F, 0x78, 0xF7, 0x11, 0x93, + 0x03, 0xEC, 0x19, 0xD3, 0x01, 0x82, 0x0A, 0x65, + 0xFD, 0x7D, 0x02, 0x97, 0xC3, 0x92, 0x43, 0xA2, + 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x03, 0x92, 0x04, 0x96, + 0x0D, 0x5E, 0x50, 0x46, 0x02, 0x0E, 0x40, 0x92, + 0x09, 0xEE, 0x44, 0x46, 0x04, 0x0E, 0x59, 0x93, + 0x44, 0x26, 0x04, 0x5E, 0x46, 0xEE, 0x41, 0x93, + 0x41, 0x26, 0x43, 0x4E, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0xB1, 0xFE, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x03, 0x94, + 0x1A, 0xD5, 0xA3, 0xF7, 0x11, 0x93, 0x00, 0x90, + 0x88, 0x98, 0x90, 0x9A, 0x1D, 0x00, 0x1A, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x18, 0x00, 0x19, 0x00, + 0x1A, 0x00, 0x1B, 0x00, 0x16, 0x00, 0x21, 0x00, + 0x12, 0x00, 0x09, 0x00, 0x13, 0x00, 0x19, 0x00, + 0x19, 0x00, 0x19, 0x00, 0x21, 0x00, 0x2D, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x69, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5F, 0xF2, 0xCD, 0xF7, 0x00, 0x00, 0x74, 0xF2, + 0xCD, 0xF7, 0x00, 0x00, 0xB9, 0xF2, 0xCA, 0xF7, + 0xD1, 0xF7, 0x00, 0x00, 0x97, 0xF3, 0xCD, 0xF7, + 0x05, 0x46, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * current zd1211b firmware version. + */ +#define ZD1211B_FIRMWARE_VER 4705 + +static const uint8_t zd1211b_firmware[] = { + 0x08, 0x91, 0xff, 0xed, 0x09, 0x93, 0x1e, 0xee, 0xd1, 0x94, 0x11, + 0xee, 0x88, 0xd4, 0xd1, 0x96, 0xd1, 0x98, 0x5c, 0x99, 0x5c, 0x99, + 0x4c, 0x99, 0x04, 0x9d, 0xd1, 0x98, 0xd1, 0x9a, 0x03, 0xee, 0xf4, + 0x94, 0xd3, 0xd4, 0x41, 0x2a, 0x40, 0x4a, 0x45, 0xbe, 0x88, 0x92, + 0x41, 0x24, 0x40, 0x44, 0x53, 0xbe, 0x40, 0xf0, 0x4e, 0xee, 0x41, + 0xee, 0x98, 0x9a, 0x72, 0xf7, 0x02, 0x00, 0x1f, 0xec, 0x00, 0x00, + 0xb2, 0xf8, 0x4d, 0x00, 0xa1, 0xec, 0x00, 0x00, 0x43, 0xf7, 0x22, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xd8, + 0xa0, 0x90, 0x98, 0x9a, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x5a, + 0xf0, 0xa0, 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x0a, 0xef, + 0xa0, 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x97, 0xf0, 0xa0, + 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x94, 0xf6, 0xa0, 0x90, + 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x95, 0xf5, 0xa0, 0x90, 0x98, + 0x9a, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x34, 0xf7, 0xa0, 0x90, + 0x98, 0x9a, 0x88, 0xda, 0x41, 0x20, 0x08, 0x0b, 0x01, 0x00, 0x40, + 0xf0, 0x8e, 0xee, 0x40, 0x96, 0x0a, 0x65, 0x00, 0x7d, 0x11, 0x93, + 0x76, 0xf7, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x57, 0xee, 0x40, + 0xf1, 0x1b, 0xd7, 0x76, 0xf7, 0xc5, 0x92, 0x02, 0x99, 0x41, 0x92, + 0xc4, 0xd2, 0x40, 0x92, 0xc4, 0xd2, 0x0f, 0x9f, 0x95, 0xf8, 0x0f, + 0x9f, 0x57, 0xee, 0x41, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x40, 0x92, 0x19, 0xd3, 0x12, 0x95, 0x19, + 0xd3, 0x10, 0x95, 0x19, 0xd3, 0x02, 0x80, 0x19, 0xd3, 0x03, 0x82, + 0x09, 0x93, 0x65, 0xf7, 0x19, 0xd3, 0x91, 0xec, 0x40, 0xf0, 0x07, + 0xf2, 0x40, 0xf0, 0x75, 0xf3, 0x11, 0x93, 0x04, 0xec, 0x42, 0x42, + 0x02, 0x5e, 0x0f, 0x9f, 0x8c, 0xee, 0x40, 0x92, 0x19, 0xd3, 0x04, + 0xec, 0x40, 0xf0, 0xe0, 0xf1, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0x44, 0x96, 0x09, 0xb3, 0xff, + 0xfd, 0x19, 0xd3, 0x44, 0x96, 0x40, 0xf0, 0x2d, 0xf7, 0x40, 0xf0, + 0x6d, 0xee, 0x4b, 0x62, 0x0a, 0x95, 0x2e, 0xee, 0xd1, 0xd4, 0x0b, + 0x97, 0x2b, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x00, 0xee, 0xd1, 0xd4, + 0x0b, 0x97, 0x2f, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x34, 0xee, 0xd1, + 0xd4, 0x0b, 0x97, 0x39, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x3e, 0xee, + 0xd1, 0xd4, 0x0b, 0x97, 0x43, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x2e, + 0xee, 0xd1, 0xd4, 0x0b, 0x97, 0x48, 0xee, 0xd1, 0xd6, 0x0a, 0x95, + 0x49, 0xee, 0xc1, 0xd4, 0x0a, 0x65, 0x00, 0x44, 0x02, 0x97, 0xc3, + 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x43, 0xf1, 0x09, 0x93, 0x01, 0x3f, + 0x19, 0xd3, 0xc0, 0x85, 0x11, 0x93, 0x44, 0x96, 0x09, 0xb3, 0xff, + 0xfc, 0x19, 0xd3, 0x44, 0x96, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x03, 0x00, 0x03, 0x96, 0x41, + 0x02, 0x03, 0x99, 0xc4, 0x94, 0x42, 0x04, 0xc1, 0x04, 0xc2, 0x94, + 0xc3, 0xd4, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x40, 0x92, 0x19, 0xd3, 0x94, 0xec, 0x13, 0x97, 0x95, 0xec, + 0x1b, 0xd7, 0x02, 0x80, 0x11, 0x93, 0x99, 0xec, 0x19, 0xd3, 0x7c, + 0x96, 0x0b, 0x97, 0xa0, 0x00, 0x1b, 0xd7, 0x6e, 0xec, 0x0a, 0x65, + 0x0e, 0x42, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xff, 0xbf, 0x11, + 0xa3, 0x9a, 0xec, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, 0x0a, 0x65, 0xe9, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, 0xd2, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x47, 0x20, 0x08, 0x0b, 0x01, + 0x00, 0x14, 0x99, 0x03, 0x80, 0x0c, 0xb3, 0x00, 0x10, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x3d, 0xf0, 0x11, 0x93, 0x9f, 0xec, 0x41, + 0x02, 0x19, 0xd3, 0x9f, 0xec, 0x11, 0x93, 0x74, 0xf7, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x2a, 0xef, 0x0a, 0x65, 0xfe, 0x7f, 0x02, + 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x00, 0x04, 0xc2, 0xd2, 0x0f, 0x9f, + 0x57, 0xf0, 0x11, 0x93, 0x94, 0xec, 0x02, 0xd2, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x76, 0xef, 0x41, 0x92, 0x19, 0xd3, 0x94, 0xec, + 0x19, 0xd3, 0x9f, 0xec, 0x12, 0x95, 0x02, 0x80, 0x1a, 0xd5, 0x95, + 0xec, 0x13, 0x97, 0x7c, 0x96, 0x1b, 0xd7, 0x99, 0xec, 0x0a, 0x65, + 0x0e, 0x42, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0x00, 0x40, 0x19, + 0xd3, 0x9a, 0xec, 0x09, 0x63, 0x00, 0x40, 0xc2, 0xd2, 0x02, 0x94, + 0x1a, 0xd5, 0x7c, 0x96, 0x0c, 0xb3, 0x00, 0x08, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x56, 0xef, 0x0c, 0xb3, 0xff, 0x07, 0x0f, 0x9f, + 0x5a, 0xef, 0x11, 0x93, 0x06, 0x80, 0x09, 0xb3, 0xff, 0x07, 0x09, + 0x03, 0x00, 0xa0, 0x19, 0xd3, 0x97, 0xec, 0x40, 0x98, 0x0b, 0x97, + 0x9c, 0xec, 0x04, 0x95, 0x03, 0x05, 0x14, 0x03, 0x97, 0xec, 0x46, + 0x02, 0xc1, 0x92, 0xc2, 0xd2, 0x41, 0x08, 0x42, 0x48, 0x02, 0x9e, + 0x0f, 0x9f, 0x61, 0xef, 0x11, 0x93, 0x97, 0xec, 0xc1, 0x92, 0xc5, + 0xd2, 0x5f, 0xb2, 0x19, 0xd3, 0x9b, 0xec, 0x0f, 0x9f, 0x79, 0xef, + 0x13, 0x97, 0x98, 0xec, 0xc5, 0xd6, 0x11, 0x93, 0x03, 0x80, 0x09, + 0xb3, 0x00, 0x08, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x8f, 0xef, + 0x11, 0x93, 0x7a, 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, + 0x93, 0x79, 0xf7, 0x09, 0xa3, 0x00, 0x10, 0x19, 0xd3, 0x79, 0xf7, + 0x40, 0x98, 0x1c, 0xd9, 0x9b, 0xec, 0x12, 0x95, 0x9b, 0xec, 0x40, + 0x44, 0x02, 0x4e, 0x0f, 0x9f, 0x2c, 0xf0, 0x0a, 0xb3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xad, 0xef, 0x0a, 0xb3, 0x07, + 0x00, 0x09, 0x05, 0xa9, 0xec, 0xc2, 0x94, 0x01, 0xd4, 0x09, 0x03, + 0xa1, 0xec, 0xc1, 0x92, 0x19, 0xd3, 0x9b, 0xec, 0xc5, 0x94, 0x0a, + 0xb5, 0x00, 0xff, 0x01, 0xa5, 0xc5, 0xd4, 0x0f, 0x9f, 0xb9, 0xef, + 0x0a, 0x05, 0xff, 0xff, 0x0a, 0x03, 0xb1, 0xec, 0xc1, 0x92, 0x01, + 0xd2, 0x1a, 0xd5, 0x9b, 0xec, 0xc5, 0x96, 0x0b, 0x07, 0xff, 0xff, + 0xc5, 0xd6, 0x11, 0x93, 0x97, 0xec, 0xc5, 0x98, 0xc1, 0xd8, 0x11, + 0x93, 0x97, 0xec, 0x09, 0x05, 0x0b, 0x00, 0x03, 0xd4, 0xc2, 0x96, + 0x06, 0xd6, 0x7b, 0x95, 0x7a, 0x95, 0x4c, 0x02, 0xc1, 0x92, 0x59, + 0x93, 0x59, 0x93, 0x01, 0xa5, 0x01, 0x98, 0x0c, 0xf5, 0x7b, 0x93, + 0x09, 0x09, 0x01, 0x00, 0x06, 0x92, 0x09, 0xb3, 0xff, 0x00, 0x04, + 0xd2, 0x5c, 0x93, 0x59, 0x93, 0x04, 0x94, 0x01, 0xa5, 0x03, 0x96, + 0xc3, 0xd4, 0x11, 0x93, 0x97, 0xec, 0x4c, 0x02, 0x05, 0xd2, 0xc1, + 0x92, 0x09, 0xb3, 0x00, 0xff, 0x7c, 0x95, 0x7a, 0x95, 0x02, 0xa3, + 0x05, 0x98, 0xc4, 0xd2, 0x12, 0x95, 0x97, 0xec, 0x45, 0x04, 0x02, + 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x00, 0x01, 0xc2, 0xd2, 0x12, 0x95, + 0x9b, 0xec, 0x0a, 0xb3, 0x08, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x01, 0xf0, 0x12, 0x95, 0x97, 0xec, 0x4a, 0x04, 0x02, 0x99, + 0xc4, 0x92, 0x01, 0x98, 0x0c, 0xf3, 0x7b, 0x93, 0x41, 0x02, 0x0f, + 0x9f, 0x22, 0xf0, 0x43, 0x44, 0x02, 0x8e, 0x0f, 0x9f, 0x23, 0xf0, + 0x11, 0x93, 0x97, 0xec, 0x42, 0x02, 0x0a, 0x05, 0xff, 0xff, 0xc1, + 0xd4, 0x11, 0x93, 0x97, 0xec, 0x4a, 0x02, 0x12, 0x95, 0x60, 0x96, + 0xc1, 0xd4, 0x12, 0x95, 0x97, 0xec, 0x4b, 0x04, 0x02, 0x97, 0xc3, + 0x92, 0x09, 0xb3, 0x1f, 0xff, 0xc2, 0xd2, 0x12, 0x95, 0x97, 0xec, + 0x4b, 0x04, 0x11, 0x93, 0x62, 0x96, 0x41, 0x93, 0x59, 0x93, 0x02, + 0x99, 0xc4, 0xa2, 0xc2, 0xd2, 0xc5, 0x92, 0x19, 0xd3, 0x98, 0xec, + 0x0a, 0x95, 0x0c, 0x02, 0x1a, 0xd5, 0x02, 0x80, 0x0f, 0x9f, 0x57, + 0xf0, 0x09, 0x63, 0xfe, 0x7f, 0x01, 0x97, 0xc3, 0x94, 0x0a, 0xa5, + 0x00, 0x04, 0xc1, 0xd4, 0x11, 0x93, 0x9f, 0xec, 0x09, 0xa3, 0x00, + 0x01, 0x19, 0xd3, 0x9f, 0xec, 0x40, 0xf0, 0xdf, 0xee, 0x0f, 0x9f, + 0x57, 0xf0, 0x11, 0x93, 0x94, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, + 0x9f, 0x4c, 0xf0, 0x40, 0xf0, 0xdf, 0xee, 0x11, 0x93, 0x95, 0xec, + 0x44, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x57, 0xf0, 0x48, + 0x98, 0x1c, 0xd9, 0x02, 0x80, 0x11, 0x93, 0x91, 0xec, 0x41, 0x22, + 0x0a, 0x95, 0x57, 0xf0, 0x88, 0xd4, 0x88, 0xdc, 0x91, 0x9a, 0x47, + 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, + 0x11, 0x93, 0x04, 0x82, 0x48, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x6e, 0xf0, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, + 0x09, 0xb3, 0xff, 0xfe, 0xc2, 0xd2, 0x41, 0x92, 0x19, 0xd3, 0xbf, + 0xec, 0x11, 0x93, 0x04, 0x82, 0x43, 0xb2, 0x12, 0x95, 0x03, 0x82, + 0x02, 0xb3, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x95, 0xf0, 0x0a, + 0xb3, 0x00, 0xff, 0x48, 0xa2, 0x19, 0xd3, 0x03, 0x82, 0x40, 0xf0, + 0x82, 0xf3, 0x11, 0x93, 0xbf, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, + 0x9f, 0x95, 0xf0, 0x11, 0x93, 0x07, 0x82, 0x11, 0x43, 0x03, 0xec, + 0x02, 0x0e, 0x0f, 0x9f, 0x95, 0xf0, 0x11, 0x93, 0x03, 0x82, 0x09, + 0xa3, 0x00, 0x01, 0x19, 0xd3, 0x03, 0x82, 0x40, 0x96, 0x1b, 0xd7, + 0xbf, 0xec, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x11, 0x93, 0x20, 0xbc, 0xc8, 0xd2, 0x40, 0xf0, 0xe9, 0xf0, + 0x41, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x42, 0x20, 0x08, + 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x05, 0x00, 0x05, 0x94, 0x41, 0x02, + 0xc1, 0x92, 0x01, 0x97, 0xc3, 0x96, 0xc2, 0xd6, 0x0a, 0x45, 0x00, + 0x95, 0x02, 0x5e, 0x0f, 0x9f, 0xe6, 0xf0, 0xc1, 0x92, 0x41, 0xb2, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xe6, 0xf0, 0x11, 0x93, 0xc0, + 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe6, 0xf0, 0x41, 0x98, + 0x1c, 0xd9, 0xc0, 0xec, 0x12, 0x95, 0x02, 0x80, 0x01, 0xd4, 0x40, + 0xf0, 0xfe, 0xf1, 0x0b, 0x67, 0xfd, 0x7d, 0x03, 0x99, 0xc4, 0x92, + 0x0c, 0x99, 0x96, 0x03, 0x1c, 0xd9, 0x06, 0x82, 0x41, 0x98, 0x1c, + 0xd9, 0x02, 0x82, 0x42, 0x98, 0x1c, 0xd9, 0x05, 0x82, 0x0c, 0x69, + 0x80, 0x7f, 0x1c, 0xd9, 0x00, 0xb0, 0x09, 0xa3, 0x00, 0x01, 0xc3, + 0xd2, 0x01, 0x94, 0x0a, 0xb3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0xe4, 0xf0, 0x42, 0xa4, 0x1a, 0xd5, 0x02, 0x80, 0x42, + 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x42, 0x20, 0x08, 0x0b, + 0x01, 0x00, 0x05, 0x92, 0xc5, 0xd2, 0x60, 0xb2, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0xf6, 0xf0, 0x40, 0xf0, 0xd2, 0xf6, 0xc5, 0x94, + 0x0a, 0xb3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xff, + 0xf0, 0x40, 0xf0, 0xc0, 0xf5, 0xc5, 0x96, 0x0b, 0xb3, 0x40, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x08, 0xf1, 0x40, 0xf0, 0xfa, + 0xf4, 0xc5, 0x94, 0x0a, 0xb3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0x70, 0xf1, 0x13, 0x97, 0x21, 0xbc, 0x01, 0xd6, 0x0b, + 0xb3, 0x02, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x1a, 0xf1, + 0x40, 0xf0, 0x62, 0xfb, 0x01, 0x94, 0x0a, 0xb3, 0x04, 0x00, 0x40, + 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x23, 0xf1, 0x40, 0xf0, 0x6c, 0xfb, + 0x01, 0x96, 0x0b, 0xb3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x4c, 0xf1, 0x40, 0xf0, 0xb0, 0xfa, 0x41, 0x92, 0x19, 0xd3, + 0x73, 0xf7, 0x11, 0x93, 0x03, 0xec, 0x09, 0x43, 0x40, 0x00, 0x02, + 0x5e, 0x0f, 0x9f, 0x39, 0xf1, 0x40, 0x94, 0x1a, 0xd5, 0x73, 0xf7, + 0x11, 0x93, 0x00, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x55, + 0xf1, 0x11, 0x93, 0xc1, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0x55, 0xf1, 0x40, 0xf0, 0xe0, 0xf1, 0x41, 0x96, 0x1b, 0xd7, 0xc1, + 0xec, 0x0f, 0x9f, 0x55, 0xf1, 0x01, 0x94, 0x0a, 0xb3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x55, 0xf1, 0x40, 0xf0, 0x7c, + 0xfb, 0x01, 0x96, 0x0b, 0xb3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0x5e, 0xf1, 0x40, 0xf0, 0x87, 0xfb, 0x11, 0x93, 0x10, + 0xec, 0x42, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x67, 0xf1, 0x44, 0x92, + 0x0f, 0x9f, 0x6b, 0xf1, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x6d, + 0xf1, 0x19, 0xd3, 0x0b, 0xbc, 0x40, 0x94, 0x1a, 0xd5, 0x10, 0xec, + 0xc5, 0x96, 0x0b, 0xb3, 0x80, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0xba, 0xf1, 0x11, 0x93, 0x28, 0xbc, 0x01, 0xd2, 0x09, 0xb3, + 0x40, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x82, 0xf1, 0x40, + 0xf0, 0xb5, 0xf6, 0x01, 0x94, 0x0a, 0xb3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x95, 0xf1, 0x40, 0xf0, 0x6d, 0xee, 0x40, + 0xf0, 0x8f, 0xfb, 0x40, 0xf0, 0xc3, 0xf1, 0x40, 0x96, 0x1b, 0xd7, + 0x00, 0xec, 0x41, 0x92, 0x19, 0xd3, 0x76, 0xf7, 0x01, 0x94, 0x0a, + 0xb3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xb1, 0xf1, + 0x40, 0xf0, 0x9e, 0xfb, 0x09, 0x63, 0x00, 0x44, 0x01, 0x97, 0xc3, + 0x94, 0x48, 0xa4, 0xc1, 0xd4, 0x00, 0xee, 0x40, 0x92, 0x19, 0xd3, + 0x12, 0x95, 0x19, 0xd3, 0x10, 0x95, 0x19, 0xd3, 0x02, 0x80, 0x19, + 0xd3, 0x03, 0x82, 0x41, 0x92, 0x19, 0xd3, 0x76, 0xf7, 0x01, 0x94, + 0x0a, 0xb3, 0x08, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xba, + 0xf1, 0x40, 0xf0, 0xae, 0xfb, 0x0a, 0x65, 0x00, 0x44, 0x02, 0x97, + 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x42, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xd3, 0xf2, 0xbd, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, + 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, + 0x63, 0x00, 0x80, 0x19, 0xd3, 0xf2, 0xbd, 0x0a, 0x65, 0xe8, 0x43, + 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, 0x0a, + 0x65, 0xeb, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, + 0xc2, 0xd2, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, + 0xb3, 0xfb, 0xff, 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x09, 0x93, 0x00, 0x01, 0x19, 0xd3, 0x02, + 0x80, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, + 0x09, 0x93, 0x00, 0x09, 0x19, 0xd3, 0x02, 0x80, 0x40, 0xf0, 0xfe, + 0xf1, 0x40, 0x92, 0x19, 0xd3, 0x94, 0xec, 0xc8, 0xd2, 0x09, 0x93, + 0x91, 0xec, 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, + 0xd8, 0xf4, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x2d, 0xf2, 0x0a, + 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, + 0x0f, 0x9f, 0x3a, 0xf2, 0x40, 0xf0, 0x3c, 0xf2, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x3a, 0xf2, 0xc8, 0xd2, 0x09, 0x93, 0x91, 0xec, + 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0xf1, 0xbd, + 0x19, 0xd3, 0xb6, 0xec, 0x11, 0x93, 0xb4, 0xec, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x54, 0xf2, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xc3, 0x94, 0x0a, 0x07, 0x07, 0x00, 0xc1, 0xd6, 0x0a, 0x05, 0x00, + 0xa0, 0x1a, 0xd5, 0x96, 0xec, 0x11, 0x93, 0xb6, 0xec, 0x19, 0xd3, + 0x01, 0x80, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x41, + 0xa2, 0xc2, 0xd2, 0x40, 0x92, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x41, 0x20, 0x08, 0x0b, 0x01, 0x00, 0x13, 0x97, 0xb4, 0xec, 0x40, + 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xc3, 0xf2, 0x12, 0x95, 0x96, 0xec, + 0x0a, 0x03, 0x07, 0x00, 0xc1, 0x92, 0xc2, 0xd2, 0x11, 0x93, 0x96, + 0xec, 0x09, 0x05, 0x01, 0x00, 0x48, 0x02, 0xc1, 0x92, 0xc2, 0xd2, + 0x11, 0x93, 0x96, 0xec, 0x4e, 0x02, 0xc1, 0x94, 0xc5, 0xd6, 0xc5, + 0x92, 0x11, 0x07, 0x96, 0xec, 0x0b, 0x03, 0x0f, 0x00, 0xc1, 0x98, + 0x46, 0x06, 0x7a, 0x93, 0x79, 0x93, 0x5c, 0x95, 0x5a, 0x95, 0x02, + 0xa3, 0xc3, 0xd2, 0x04, 0x95, 0xc5, 0x96, 0x41, 0x06, 0xc5, 0xd6, + 0x42, 0x46, 0x02, 0x9e, 0x0f, 0x9f, 0x7d, 0xf2, 0x11, 0x93, 0x96, + 0xec, 0x09, 0x05, 0x05, 0x00, 0x41, 0x02, 0xc1, 0x92, 0xc2, 0xd2, + 0x11, 0x93, 0x96, 0xec, 0xc1, 0x92, 0x09, 0xb5, 0x1f, 0x00, 0x43, + 0x44, 0x02, 0x8e, 0x0f, 0x9f, 0xaa, 0xf2, 0x40, 0x44, 0x02, 0x4e, + 0x0f, 0x9f, 0xab, 0xf2, 0x0a, 0x05, 0xff, 0xff, 0x0f, 0x9f, 0xab, + 0xf2, 0x43, 0x94, 0x11, 0x93, 0x96, 0xec, 0x42, 0x02, 0xc1, 0xd4, + 0x13, 0x97, 0x96, 0xec, 0x03, 0x93, 0xd1, 0x94, 0x7a, 0x95, 0x7a, + 0x95, 0xc1, 0x92, 0x59, 0x93, 0x59, 0x93, 0x01, 0x05, 0x49, 0x06, + 0xc3, 0x92, 0x7f, 0xb2, 0x01, 0x05, 0x1a, 0xd5, 0xb4, 0xec, 0x0a, + 0x05, 0xf2, 0xff, 0x1a, 0xd5, 0x92, 0xec, 0x11, 0x93, 0x92, 0xec, + 0x12, 0x95, 0xb6, 0xec, 0x02, 0x43, 0x02, 0x8e, 0x0f, 0x9f, 0x11, + 0xf3, 0x02, 0x0e, 0x0f, 0x9f, 0xe4, 0xf2, 0x11, 0x93, 0x7a, 0xf7, + 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, 0x93, 0x79, 0xf7, 0x09, + 0xa3, 0x80, 0x00, 0x19, 0xd3, 0x79, 0xf7, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x95, 0xc2, 0x94, 0x1a, 0xd5, 0xb5, 0xec, 0x40, 0x96, 0x1b, + 0xd7, 0xb4, 0xec, 0x0f, 0x9f, 0x29, 0xf3, 0x11, 0x93, 0x03, 0x80, + 0x09, 0xb3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xf6, + 0xf2, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0xf6, 0xf2, 0x40, 0xf0, 0x3d, 0xf3, 0x0f, 0x9f, 0x2b, 0xf3, 0x41, + 0x92, 0xc8, 0xd2, 0x0a, 0x95, 0x91, 0xec, 0xc8, 0xd4, 0x40, 0xf0, + 0xd0, 0xee, 0x42, 0x00, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0x09, 0xf3, 0x42, 0x96, 0x1b, 0xd7, 0xc0, 0xec, + 0x0f, 0x9f, 0x2b, 0xf3, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, + 0x92, 0x42, 0xa2, 0xc2, 0xd2, 0x0f, 0x9f, 0x2b, 0xf3, 0x12, 0x45, + 0x03, 0xec, 0x02, 0x4e, 0x0f, 0x9f, 0x23, 0xf3, 0x11, 0x93, 0x7a, + 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, 0x93, 0x79, 0xf7, + 0x09, 0xa3, 0x00, 0x08, 0x19, 0xd3, 0x79, 0xf7, 0x1a, 0xd5, 0x92, + 0xec, 0x11, 0x93, 0x92, 0xec, 0x19, 0x25, 0x92, 0xec, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xd3, 0xf2, 0xbd, 0x41, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, 0x3d, 0xf3, + 0x40, 0x92, 0xc8, 0xd2, 0x09, 0x93, 0x91, 0xec, 0xc8, 0xd2, 0x40, + 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0x75, 0xf7, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0x4d, 0xf3, 0x0a, 0x65, 0xbc, 0x69, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0x83, 0x00, 0x02, 0xc2, 0xd2, 0x11, 0x93, 0x03, + 0x80, 0x09, 0xb3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0x60, 0xf3, 0x11, 0x93, 0x7a, 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, + 0xf7, 0x11, 0x93, 0x79, 0xf7, 0x09, 0xa3, 0x00, 0x20, 0x19, 0xd3, + 0x79, 0xf7, 0x11, 0x93, 0xb5, 0xec, 0x19, 0xd3, 0x04, 0x80, 0x12, + 0x95, 0xb4, 0xec, 0x1a, 0xd5, 0x05, 0x80, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x97, 0xc3, 0x96, 0x1b, 0xd7, 0xb5, 0xec, 0x40, 0x94, 0x1a, + 0xd5, 0xb4, 0xec, 0x19, 0xd3, 0xf2, 0xbd, 0x88, 0x98, 0x90, 0x9a, + 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x93, 0x96, 0x03, 0x19, + 0xd3, 0x06, 0x82, 0x09, 0x93, 0x00, 0x01, 0x19, 0xd3, 0x03, 0x82, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x47, 0x20, 0x08, 0x0b, 0x01, + 0x00, 0x11, 0x93, 0x01, 0x82, 0xc5, 0xd2, 0x40, 0x94, 0x01, 0xd4, + 0x13, 0x97, 0xb8, 0xec, 0x02, 0xd6, 0x03, 0x95, 0x0c, 0x99, 0xbb, + 0xec, 0x04, 0x05, 0x13, 0x97, 0x03, 0xec, 0x01, 0x27, 0x02, 0x99, + 0xc4, 0x92, 0x03, 0x03, 0xc2, 0xd2, 0x14, 0x99, 0xba, 0xec, 0x03, + 0x09, 0x1c, 0xd9, 0xba, 0xec, 0x12, 0x95, 0x04, 0x82, 0x0a, 0xb3, + 0x02, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xc6, 0xf4, 0x01, + 0x92, 0x03, 0xd2, 0x0a, 0xa3, 0x02, 0x00, 0x19, 0xd3, 0x04, 0x82, + 0x02, 0x96, 0x0b, 0x05, 0x01, 0x00, 0x1a, 0xd5, 0xb8, 0xec, 0xc5, + 0x92, 0x43, 0x42, 0x02, 0x9e, 0x0f, 0x9f, 0xce, 0xf3, 0x42, 0x44, + 0x02, 0x8e, 0x0f, 0x9f, 0xce, 0xf3, 0x11, 0x93, 0xbf, 0xec, 0x40, + 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xce, 0xf3, 0x0c, 0x49, 0xd3, 0x08, + 0x02, 0x8e, 0x0f, 0x9f, 0xce, 0xf3, 0x11, 0x63, 0x07, 0x82, 0x11, + 0xa3, 0x07, 0x82, 0x71, 0x93, 0x79, 0x93, 0x79, 0x93, 0x79, 0x93, + 0x03, 0xd2, 0xc5, 0x94, 0x0a, 0xb5, 0xfc, 0xff, 0x04, 0xd4, 0x03, + 0x96, 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xdd, 0xf3, 0x11, 0x93, + 0xb8, 0xec, 0x41, 0x42, 0x02, 0x8e, 0x0f, 0x9f, 0xe4, 0xf3, 0xc5, + 0x98, 0x0c, 0x03, 0xff, 0xff, 0x42, 0x42, 0x02, 0x8e, 0x0f, 0x9f, + 0x0b, 0xf4, 0x0a, 0x95, 0xbb, 0xec, 0x42, 0x92, 0x19, 0xd3, 0xb9, + 0xec, 0xc5, 0x96, 0x43, 0x46, 0x02, 0x9e, 0x0f, 0x9f, 0xfd, 0xf3, + 0x0b, 0x07, 0xfc, 0xff, 0xc5, 0xd6, 0xd2, 0x98, 0x1c, 0xd9, 0xc8, + 0xbc, 0xd2, 0x96, 0x1b, 0xd7, 0xca, 0xbc, 0x09, 0x03, 0xff, 0xff, + 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe9, 0xf3, 0x19, 0xd3, 0xb9, + 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x09, 0xf4, 0x0a, 0x05, + 0xfe, 0xff, 0xca, 0xd2, 0xc2, 0xd2, 0x0f, 0x9f, 0x0b, 0xf4, 0x1a, + 0xd5, 0x93, 0xec, 0x03, 0x98, 0x40, 0x48, 0x02, 0x5e, 0x0f, 0x9f, + 0x38, 0xf4, 0x11, 0x93, 0xb8, 0xec, 0x41, 0x42, 0x02, 0x9e, 0x0f, + 0x9f, 0x1b, 0xf4, 0x04, 0x94, 0x48, 0x44, 0x02, 0x4e, 0x0f, 0x9f, + 0x26, 0xf4, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x38, 0xf4, 0x11, + 0x93, 0x04, 0x82, 0x41, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, + 0x38, 0xf4, 0x41, 0x96, 0x01, 0xd6, 0x0a, 0x65, 0xbd, 0x43, 0x02, + 0x99, 0xc4, 0x92, 0x09, 0xa3, 0x80, 0x00, 0xc2, 0xd2, 0x0a, 0x65, + 0xe8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, + 0xd2, 0x0f, 0x9f, 0x97, 0xf4, 0xc5, 0x98, 0x43, 0x48, 0x02, 0x9e, + 0x0f, 0x9f, 0x97, 0xf4, 0x4f, 0x96, 0x0c, 0xb3, 0x01, 0x00, 0x40, + 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x45, 0xf4, 0x47, 0x96, 0x11, 0x93, + 0xb7, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x73, 0xf4, 0x11, + 0x93, 0xb8, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x73, 0xf4, + 0x12, 0x95, 0x00, 0x82, 0x0a, 0x05, 0xff, 0xaf, 0x05, 0xd4, 0xc8, + 0xd6, 0xc8, 0xd2, 0x40, 0xf0, 0x18, 0xf7, 0x42, 0x00, 0x05, 0x96, + 0xc3, 0x94, 0x01, 0xb5, 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0x68, + 0xf4, 0x11, 0x93, 0xba, 0xec, 0x4d, 0x42, 0x02, 0x8e, 0x0f, 0x9f, + 0x73, 0xf4, 0x06, 0x98, 0x50, 0x98, 0x1c, 0xd9, 0xa2, 0xbc, 0x40, + 0x98, 0x1c, 0xd9, 0xa2, 0xbc, 0x40, 0x92, 0x03, 0xd2, 0x0f, 0x9f, + 0x9c, 0xf4, 0x03, 0x94, 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0x80, + 0xf4, 0x0a, 0x65, 0x5e, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x48, 0xa2, + 0xc2, 0xd2, 0x0f, 0x9f, 0x9c, 0xf4, 0x11, 0x93, 0xb8, 0xec, 0x0c, + 0x99, 0xbb, 0xec, 0x04, 0x03, 0x04, 0x96, 0x13, 0x25, 0x03, 0xec, + 0xc1, 0xd4, 0x11, 0x93, 0xba, 0xec, 0x19, 0x05, 0xba, 0xec, 0x1b, + 0xd7, 0x01, 0x82, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x99, 0xc4, 0x92, + 0x43, 0xa2, 0xc2, 0xd2, 0x41, 0x92, 0x01, 0xd2, 0x03, 0x94, 0x40, + 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0xb0, 0xf4, 0x11, 0x93, 0xb9, 0xec, + 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xa8, 0xf4, 0x19, 0xd3, 0xb8, + 0xec, 0x19, 0xd3, 0xba, 0xec, 0x19, 0xd3, 0xbb, 0xec, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xb0, 0xf4, 0x41, 0x98, 0x1c, + 0xd9, 0xb7, 0xec, 0x11, 0x93, 0xbf, 0xec, 0x41, 0x42, 0x02, 0x5e, + 0x0f, 0x9f, 0xc1, 0xf4, 0x11, 0x93, 0x00, 0x82, 0x19, 0xd3, 0x02, + 0x82, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, + 0x00, 0x01, 0xc2, 0xd2, 0x40, 0x98, 0x1c, 0xd9, 0xbf, 0xec, 0x0f, + 0x9f, 0xc9, 0xf4, 0x01, 0x92, 0x19, 0xd3, 0xb7, 0xec, 0x01, 0x94, + 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0xd5, 0xf4, 0x0a, 0x65, 0xea, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xfb, 0xff, 0xc2, 0xd2, + 0x47, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x12, 0x95, 0x03, 0x80, 0x0a, 0xb3, 0x00, 0x40, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0xf4, 0xf4, 0x0a, 0xb7, 0x00, 0x08, 0x40, + 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xf7, 0xf4, 0x11, 0x93, 0x03, 0xec, + 0x41, 0x02, 0x09, 0xb3, 0xfe, 0xff, 0x12, 0x95, 0x07, 0x80, 0x01, + 0x45, 0x02, 0x8e, 0x0f, 0x9f, 0xf7, 0xf4, 0x41, 0x92, 0x0f, 0x9f, + 0xf8, 0xf4, 0x40, 0x92, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x41, + 0x20, 0x08, 0x0b, 0x01, 0x00, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, 0xd2, 0x13, 0x97, 0x6e, + 0xec, 0x0b, 0x47, 0xa0, 0x00, 0x02, 0x5e, 0x0f, 0x9f, 0x23, 0xf5, + 0x09, 0x63, 0x08, 0x43, 0x0a, 0x65, 0xff, 0x5f, 0x01, 0x99, 0xc4, + 0xd4, 0x0a, 0x95, 0x9b, 0xec, 0xd2, 0x96, 0x1b, 0xd7, 0xfa, 0xbc, + 0xd2, 0x96, 0xc4, 0xd6, 0xd2, 0x98, 0x1c, 0xd9, 0xfa, 0xbc, 0xd2, + 0x96, 0xc1, 0xd6, 0xc2, 0x94, 0x1a, 0xd5, 0xfa, 0xbc, 0x0f, 0x9f, + 0x61, 0xf5, 0x0c, 0x69, 0xff, 0x6f, 0x1c, 0xd9, 0xf8, 0xbc, 0x0b, + 0x47, 0x10, 0x95, 0x02, 0x5e, 0x0f, 0x9f, 0x3b, 0xf5, 0x0a, 0x95, + 0x6f, 0xec, 0x09, 0x63, 0x06, 0x43, 0x01, 0x99, 0xc4, 0xd6, 0xd2, + 0x96, 0x1b, 0xd7, 0xf8, 0xbc, 0x0c, 0x69, 0xee, 0x6a, 0xc1, 0xd8, + 0xc2, 0x94, 0x1a, 0xd5, 0xf8, 0xbc, 0x40, 0x92, 0xc5, 0xd2, 0x11, + 0x43, 0xc2, 0xec, 0x02, 0x0e, 0x0f, 0x9f, 0x5e, 0xf5, 0xc5, 0x94, + 0x0a, 0x03, 0x71, 0xec, 0xc1, 0x94, 0x1a, 0xd5, 0xfa, 0xbc, 0x11, + 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x50, 0xf5, + 0x0a, 0x95, 0x6f, 0xec, 0xc8, 0xd4, 0x40, 0xf0, 0x39, 0xf7, 0x19, + 0xd3, 0xf8, 0xbc, 0x41, 0x00, 0xc5, 0x96, 0x41, 0x06, 0xc5, 0xd6, + 0x13, 0x47, 0xc2, 0xec, 0x02, 0x1e, 0x0f, 0x9f, 0x42, 0xf5, 0x40, + 0x98, 0x1c, 0xd9, 0xfa, 0xbc, 0x40, 0x92, 0x19, 0xd3, 0x6e, 0xec, + 0x19, 0xd3, 0xc2, 0xec, 0x0a, 0x65, 0x52, 0x43, 0x02, 0x97, 0xc3, + 0x92, 0x48, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, 0x43, 0x02, 0x99, + 0xc4, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, 0xd2, 0x41, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x43, 0x20, 0x08, 0x0b, 0x01, 0x00, + 0x06, 0x92, 0x01, 0xd2, 0x0a, 0x65, 0xf0, 0x6a, 0x0b, 0x97, 0x6f, + 0xec, 0x02, 0x99, 0xc4, 0x98, 0xd3, 0xd8, 0x02, 0xd6, 0x0a, 0x03, + 0x02, 0x00, 0x01, 0x97, 0xc3, 0x98, 0x02, 0x96, 0xc3, 0xd8, 0x01, + 0x96, 0xc1, 0xd6, 0x1a, 0xd5, 0x6e, 0xec, 0xc5, 0x98, 0x14, 0x99, + 0x6f, 0xec, 0xc2, 0xd8, 0x43, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, + 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0x92, 0xc8, 0xd2, 0x40, 0xf0, + 0x76, 0xf5, 0x41, 0x00, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0xb0, 0xf5, 0x42, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0xad, 0xf5, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x42, + 0xa2, 0xc2, 0xd2, 0x40, 0x92, 0x19, 0xd3, 0xc0, 0xec, 0x0a, 0x65, + 0xeb, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, + 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, + 0xbf, 0xff, 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x63, + 0x20, 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0xaf, 0xbc, 0x47, 0xb2, + 0x59, 0x95, 0x5a, 0x95, 0x12, 0xa5, 0xbf, 0xbc, 0x0a, 0xb3, 0x01, + 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xd2, 0xf5, 0x41, 0x04, + 0x05, 0x93, 0x40, 0x96, 0x20, 0xd6, 0x62, 0x97, 0x0f, 0x9f, 0xe1, + 0xf5, 0x14, 0x99, 0xfc, 0xbc, 0xd1, 0xd8, 0x14, 0x99, 0xfe, 0xbc, + 0xd1, 0xd8, 0x20, 0x98, 0x42, 0x08, 0x20, 0xd8, 0x20, 0x98, 0x03, + 0x49, 0x02, 0x1e, 0x0f, 0x9f, 0xd8, 0xf5, 0xc5, 0x92, 0x62, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0xfa, 0xf5, 0x02, 0x8e, 0x0f, 0x9f, 0xf4, + 0xf5, 0x61, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x1e, 0xf6, 0x0f, 0x9f, + 0x4b, 0xf6, 0x63, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x41, 0xf6, 0x0f, + 0x9f, 0x4b, 0xf6, 0x0d, 0x03, 0x01, 0x00, 0x0c, 0x99, 0x71, 0xec, + 0x0b, 0x05, 0xff, 0xff, 0x40, 0x96, 0x0f, 0x9f, 0x07, 0xf6, 0xd1, + 0x96, 0xd4, 0xd6, 0x20, 0x96, 0x41, 0x06, 0x20, 0xd6, 0x02, 0x47, + 0x02, 0x1e, 0x0f, 0x9f, 0x03, 0xf6, 0x1a, 0xd5, 0xc2, 0xec, 0x0a, + 0x65, 0xeb, 0x43, 0x02, 0x99, 0xc4, 0x92, 0x09, 0xa3, 0xc0, 0x00, + 0xc2, 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, + 0xb3, 0xbf, 0xff, 0xc2, 0xd2, 0x0f, 0x9f, 0x4b, 0xf6, 0x0a, 0x03, + 0xfe, 0xff, 0x61, 0x95, 0x40, 0x98, 0x20, 0xd8, 0x02, 0x49, 0x02, + 0x0e, 0x0f, 0x9f, 0x4b, 0xf6, 0x0d, 0x03, 0x01, 0x00, 0x21, 0xd2, + 0x20, 0x92, 0x05, 0x03, 0x42, 0x02, 0xc8, 0xd2, 0x21, 0x96, 0xc3, + 0x92, 0x42, 0x06, 0x21, 0xd6, 0xc8, 0xd2, 0x22, 0xd4, 0x40, 0xf0, + 0xa2, 0xf0, 0x42, 0x00, 0x20, 0x98, 0x42, 0x08, 0x20, 0xd8, 0x22, + 0x94, 0x02, 0x49, 0x02, 0x1e, 0x0f, 0x9f, 0x2a, 0xf6, 0x0f, 0x9f, + 0x4b, 0xf6, 0x0d, 0x03, 0x03, 0x00, 0xc8, 0xd2, 0x02, 0x92, 0xc8, + 0xd2, 0x01, 0x96, 0xc8, 0xd6, 0x40, 0xf0, 0x4e, 0xf6, 0x43, 0x00, + 0x63, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x45, 0x20, 0x08, + 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x08, 0x00, 0x08, 0x94, 0xc5, 0xd4, + 0x09, 0x05, 0x01, 0x00, 0xc2, 0x94, 0x03, 0xd4, 0x42, 0x02, 0xc1, + 0x92, 0x01, 0xd2, 0x02, 0x97, 0xc5, 0x94, 0x0a, 0x83, 0xff, 0xff, + 0x11, 0xb3, 0x2c, 0x93, 0x09, 0xb3, 0xfb, 0xff, 0x19, 0xd3, 0x2c, + 0x93, 0x03, 0x92, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x81, 0xf6, + 0x01, 0x94, 0xd2, 0x92, 0x19, 0xd3, 0x2c, 0x93, 0x01, 0xd4, 0x02, + 0x94, 0x12, 0x95, 0x2c, 0x93, 0x44, 0xa4, 0x1a, 0xd5, 0x2c, 0x93, + 0x0a, 0xb5, 0xfb, 0xff, 0x1a, 0xd5, 0x2c, 0x93, 0x0b, 0x07, 0xff, + 0xff, 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0x6c, 0xf6, 0x09, 0x63, + 0xd4, 0x6c, 0x01, 0x95, 0xc2, 0x96, 0xc5, 0x94, 0x02, 0xa7, 0xc1, + 0xd6, 0x03, 0x92, 0x54, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x91, 0xf6, + 0x0a, 0x83, 0xff, 0xff, 0x1b, 0xb3, 0x2c, 0x93, 0x45, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x63, + 0x00, 0x40, 0x19, 0xd3, 0xf2, 0xbd, 0x40, 0xf0, 0xd8, 0xf4, 0x40, + 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xa5, 0xf6, 0x40, 0xf0, 0x3c, 0xf2, + 0x0f, 0x9f, 0xb3, 0xf6, 0x40, 0x96, 0xc8, 0xd6, 0x09, 0x93, 0x91, + 0xec, 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x0a, 0x65, 0xfe, 0x7f, + 0x02, 0x97, 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x42, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x0a, 0x65, + 0xe8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, + 0xd2, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, + 0xfb, 0xff, 0xc2, 0xd2, 0x40, 0x92, 0x19, 0xd3, 0x2d, 0xbc, 0x0a, + 0x65, 0xd8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, + 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x09, 0x63, 0xea, 0x43, 0x01, 0x97, 0xc3, 0x94, 0x44, 0xa4, + 0xc1, 0xd4, 0x11, 0x93, 0xb9, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x0c, 0xf7, 0x12, 0x95, 0x93, 0xec, 0x0b, 0x67, 0x36, 0x43, + 0xd2, 0x98, 0x1c, 0xd9, 0xc8, 0xbc, 0xd2, 0x98, 0x03, 0x93, 0xc1, + 0xd8, 0x11, 0x93, 0xb9, 0xec, 0x09, 0x03, 0xff, 0xff, 0x19, 0xd3, + 0xb9, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe5, 0xf6, 0x19, + 0xd3, 0xb8, 0xec, 0x19, 0xd3, 0xba, 0xec, 0x0a, 0x05, 0xfe, 0xff, + 0xca, 0xd2, 0xca, 0xd2, 0xc2, 0xd2, 0x0a, 0x65, 0x5e, 0x43, 0x02, + 0x97, 0xc3, 0x92, 0x48, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xea, 0x43, + 0x02, 0x99, 0xc4, 0x92, 0x09, 0xb3, 0xfb, 0xff, 0x0f, 0x9f, 0x15, + 0xf7, 0x11, 0x93, 0x03, 0xec, 0x19, 0xd3, 0x01, 0x82, 0x0a, 0x65, + 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, 0x43, 0xa2, 0xc2, 0xd2, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x03, 0x92, + 0x04, 0x96, 0x0d, 0x5e, 0x50, 0x46, 0x02, 0x0e, 0x40, 0x92, 0x09, + 0xee, 0x44, 0x46, 0x04, 0x0e, 0x59, 0x93, 0x44, 0x26, 0x04, 0x5e, + 0x46, 0xee, 0x41, 0x93, 0x41, 0x26, 0x43, 0x4e, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, 0xb1, 0xfe, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x03, 0x94, + 0x1a, 0xd5, 0x40, 0xf7, 0x11, 0x93, 0x00, 0x90, 0x88, 0x98, 0x90, + 0x9a, 0x1d, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x03, 0x00, 0x18, 0x00, + 0x19, 0x00, 0x1a, 0x00, 0x1b, 0x00, 0x16, 0x00, 0x21, 0x00, 0x12, + 0x00, 0x09, 0x00, 0x13, 0x00, 0x19, 0x00, 0x19, 0x00, 0x19, 0x00, + 0x21, 0x00, 0x2d, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7e, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf2, 0x6b, 0xf7, 0x00, 0x00, + 0x1c, 0xf2, 0x6b, 0xf7, 0x00, 0x00, 0x61, 0xf2, 0x68, 0xf7, 0x6f, + 0xf7, 0x00, 0x00, 0x2e, 0xf3, 0x6b, 0xf7, 0x25, 0x47, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/sys/dev/usb2/wlan/if_zyd2_reg.h b/sys/dev/usb2/wlan/if_zyd2_reg.h new file mode 100644 index 000000000000..bdf1bc0e6f42 --- /dev/null +++ b/sys/dev/usb2/wlan/if_zyd2_reg.h @@ -0,0 +1,1280 @@ +/* $OpenBSD: if_zydreg.h,v 1.19 2006/11/30 19:28:07 damien Exp $ */ +/* $NetBSD: if_zydreg.h,v 1.2 2007/06/16 11:18:45 kiyohara Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2006 by Damien Bergamini + * Copyright (c) 2006 by Florian Stoehr + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * ZyDAS ZD1211/ZD1211B USB WLAN driver. + */ + +#define ZYD_CR_GPI_EN 0x9418 +#define ZYD_CR_RADIO_PD 0x942c +#define ZYD_CR_RF2948_PD 0x942c +#define ZYD_CR_EN_PS_MANUAL_AGC 0x943c +#define ZYD_CR_CONFIG_PHILIPS 0x9440 +#define ZYD_CR_I2C_WRITE 0x9444 +#define ZYD_CR_SA2400_SER_RP 0x9448 +#define ZYD_CR_RADIO_PE 0x9458 +#define ZYD_CR_RST_BUS_MASTER 0x945c +#define ZYD_CR_RFCFG 0x9464 +#define ZYD_CR_HSTSCHG 0x946c +#define ZYD_CR_PHY_ON 0x9474 +#define ZYD_CR_RX_DELAY 0x9478 +#define ZYD_CR_RX_PE_DELAY 0x947c +#define ZYD_CR_GPIO_1 0x9490 +#define ZYD_CR_GPIO_2 0x9494 +#define ZYD_CR_EnZYD_CRyBufMux 0x94a8 +#define ZYD_CR_PS_CTRL 0x9500 +#define ZYD_CR_ADDA_PWR_DWN 0x9504 +#define ZYD_CR_ADDA_MBIAS_WT 0x9508 +#define ZYD_CR_INTERRUPT 0x9510 +#define ZYD_CR_MAC_PS_STATE 0x950c +#define ZYD_CR_ATIM_WND_PERIOD 0x951c +#define ZYD_CR_BCN_INTERVAL 0x9520 +#define ZYD_CR_PRE_TBTT 0x9524 + +/* + * MAC registers. + */ +#define ZYD_MAC_MACADRL 0x9610 /* MAC address (low) */ +#define ZYD_MAC_MACADRH 0x9614 /* MAC address (high) */ +#define ZYD_MAC_BSSADRL 0x9618 /* BSS address (low) */ +#define ZYD_MAC_BSSADRH 0x961c /* BSS address (high) */ +#define ZYD_MAC_BCNCFG 0x9620 /* BCN configuration */ +#define ZYD_MAC_GHTBL 0x9624 /* Group hash table (low) */ +#define ZYD_MAC_GHTBH 0x9628 /* Group hash table (high) */ +#define ZYD_MAC_RX_TIMEOUT 0x962c /* Rx timeout value */ +#define ZYD_MAC_BAS_RATE 0x9630 /* Basic rate setting */ +#define ZYD_MAC_MAN_RATE 0x9634 /* Mandatory rate setting */ +#define ZYD_MAC_RTSCTSRATE 0x9638 /* RTS CTS rate */ +#define ZYD_MAC_BACKOFF_PROTECT 0x963c /* Backoff protection */ +#define ZYD_MAC_RX_THRESHOLD 0x9640 /* Rx threshold */ +#define ZYD_MAC_TX_PE_CONTROL 0x9644 /* Tx_PE control */ +#define ZYD_MAC_AFTER_PNP 0x9648 /* After PnP */ +#define ZYD_MAC_RX_PE_DELAY 0x964c /* Rx_pe delay */ +#define ZYD_MAC_RX_ADDR2_L 0x9650 /* RX address2 (low) */ +#define ZYD_MAC_RX_ADDR2_H 0x9654 /* RX address2 (high) */ +#define ZYD_MAC_SIFS_ACK_TIME 0x9658 /* Dynamic SIFS ack time */ +#define ZYD_MAC_PHY_DELAY 0x9660 /* PHY delay */ +#define ZYD_MAC_PHY_DELAY2 0x966c /* PHY delay */ +#define ZYD_MAC_BCNFIFO 0x9670 /* Beacon FIFO I/O port */ +#define ZYD_MAC_SNIFFER 0x9674 /* Sniffer on/off */ +#define ZYD_MAC_ENCRYPTION_TYPE 0x9678 /* Encryption type */ +#define ZYD_MAC_RETRY 0x967c /* Retry time */ +#define ZYD_MAC_MISC 0x9680 /* Misc */ +#define ZYD_MAC_STMACHINESTAT 0x9684 /* State machine status */ +#define ZYD_MAC_TX_UNDERRUN_CNT 0x9688 /* TX underrun counter */ +#define ZYD_MAC_RXFILTER 0x968c /* Send to host settings */ +#define ZYD_MAC_ACK_EXT 0x9690 /* Acknowledge extension */ +#define ZYD_MAC_BCNFIFOST 0x9694 /* BCN FIFO set and status */ +#define ZYD_MAC_DIFS_EIFS_SIFS 0x9698 /* DIFS, EIFS & SIFS settings */ +#define ZYD_MAC_RX_TIMEOUT_CNT 0x969c /* RX timeout count */ +#define ZYD_MAC_RX_TOTAL_FRAME 0x96a0 /* RX total frame count */ +#define ZYD_MAC_RX_CRC32_CNT 0x96a4 /* RX CRC32 frame count */ +#define ZYD_MAC_RX_CRC16_CNT 0x96a8 /* RX CRC16 frame count */ +#define ZYD_MAC_RX_UDEC 0x96ac /* RX unicast decr. error count */ +#define ZYD_MAC_RX_OVERRUN_CNT 0x96b0 /* RX FIFO overrun count */ +#define ZYD_MAC_RX_MDEC 0x96bc /* RX multicast decr. err. cnt. */ +#define ZYD_MAC_NAV_TCR 0x96c4 /* NAV timer count read */ +#define ZYD_MAC_BACKOFF_ST_RD 0x96c8 /* Backoff status read */ +#define ZYD_MAC_DM_RETRY_CNT_RD 0x96cc /* DM retry count read */ +#define ZYD_MAC_RX_ACR 0x96d0 /* RX arbitration count read */ +#define ZYD_MAC_TX_CCR 0x96d4 /* Tx complete count read */ +#define ZYD_MAC_TCB_ADDR 0x96e8 /* Current PCI process TCP addr */ +#define ZYD_MAC_RCB_ADDR 0x96ec /* Next RCB address */ +#define ZYD_MAC_CONT_WIN_LIMIT 0x96f0 /* Contention window limit */ +#define ZYD_MAC_TX_PKT 0x96f4 /* Tx total packet count read */ +#define ZYD_MAC_DL_CTRL 0x96f8 /* Download control */ +#define ZYD_MACB_TXPWR_CTL1 0x9b00 +#define ZYD_MACB_TXPWR_CTL2 0x9b04 +#define ZYD_MACB_TXPWR_CTL3 0x9b08 +#define ZYD_MACB_TXPWR_CTL4 0x9b0c +#define ZYD_MACB_AIFS_CTL1 0x9b10 +#define ZYD_MACB_AIFS_CTL2 0x9b14 +#define ZYD_MACB_TXOP 0x9b20 +#define ZYD_MACB_MAX_RETRY 0x9b28 + +/* + * Miscellanous registers. + */ +#define ZYD_FIRMWARE_START_ADDR 0xee00 +#define ZYD_FIRMWARE_BASE_ADDR 0xee1d /* Firmware base address */ + +/* + * EEPROM registers. + */ +#define ZYD_EEPROM_START_HEAD 0xf800 /* EEPROM start */ +#define ZYD_EEPROM_SUBID 0xf817 +#define ZYD_EEPROM_POD 0xf819 +#define ZYD_EEPROM_MAC_ADDR_P1 0xf81b /* Part 1 of the MAC address */ +#define ZYD_EEPROM_MAC_ADDR_P2 0xf81d /* Part 2 of the MAC address */ +#define ZYD_EEPROM_PWR_CAL 0xf81f /* Calibration */ +#define ZYD_EEPROM_PWR_INT 0xf827 /* Calibration */ +#define ZYD_EEPROM_ALLOWEDCHAN 0xf82f /* Allowed CH mask, 1 bit each */ +#define ZYD_EEPROM_PHY_REG 0xf831 /* PHY registers */ +#define ZYD_EEPROM_DEVICE_VER 0xf837 /* Device version */ +#define ZYD_EEPROM_36M_CAL 0xf83f /* Calibration */ +#define ZYD_EEPROM_11A_INT 0xf847 /* Interpolation */ +#define ZYD_EEPROM_48M_CAL 0xf84f /* Calibration */ +#define ZYD_EEPROM_48M_INT 0xf857 /* Interpolation */ +#define ZYD_EEPROM_54M_CAL 0xf85f /* Calibration */ +#define ZYD_EEPROM_54M_INT 0xf867 /* Interpolation */ + +/* + * Firmware registers offsets (relative to fwbase). + */ +#define ZYD_FW_FIRMWARE_REV 0x0000 /* Firmware version */ +#define ZYD_FW_USB_SPEED 0x0001 /* USB speed (!=0 if highspeed) */ +#define ZYD_FW_FIX_TX_RATE 0x0002 /* Fixed TX rate */ +#define ZYD_FW_LINK_STATUS 0x0003 +#define ZYD_FW_SOFT_RESET 0x0004 +#define ZYD_FW_FLASH_CHK 0x0005 + +/* possible flags for register ZYD_FW_LINK_STATUS */ +#define ZYD_LED1 (1 << 8) +#define ZYD_LED2 (1 << 9) + +/* + * RF IDs. + */ +#define ZYD_RF_UW2451 0x2 /* not supported yet */ +#define ZYD_RF_UCHIP 0x3 /* not supported yet */ +#define ZYD_RF_AL2230 0x4 +#define ZYD_RF_AL7230B 0x5 +#define ZYD_RF_THETA 0x6 /* not supported yet */ +#define ZYD_RF_AL2210 0x7 +#define ZYD_RF_MAXIM_NEW 0x8 +#define ZYD_RF_GCT 0x9 +#define ZYD_RF_PV2000 0xa /* not supported yet */ +#define ZYD_RF_RALINK 0xb /* not supported yet */ +#define ZYD_RF_INTERSIL 0xc /* not supported yet */ +#define ZYD_RF_RFMD 0xd +#define ZYD_RF_MAXIM_NEW2 0xe +#define ZYD_RF_PHILIPS 0xf /* not supported yet */ + +/* + * PHY registers (8 bits, not documented). + */ +#define ZYD_CR0 0x9000 +#define ZYD_CR1 0x9004 +#define ZYD_CR2 0x9008 +#define ZYD_CR3 0x900c +#define ZYD_CR5 0x9010 +#define ZYD_CR6 0x9014 +#define ZYD_CR7 0x9018 +#define ZYD_CR8 0x901c +#define ZYD_CR4 0x9020 +#define ZYD_CR9 0x9024 +#define ZYD_CR10 0x9028 +#define ZYD_CR11 0x902c +#define ZYD_CR12 0x9030 +#define ZYD_CR13 0x9034 +#define ZYD_CR14 0x9038 +#define ZYD_CR15 0x903c +#define ZYD_CR16 0x9040 +#define ZYD_CR17 0x9044 +#define ZYD_CR18 0x9048 +#define ZYD_CR19 0x904c +#define ZYD_CR20 0x9050 +#define ZYD_CR21 0x9054 +#define ZYD_CR22 0x9058 +#define ZYD_CR23 0x905c +#define ZYD_CR24 0x9060 +#define ZYD_CR25 0x9064 +#define ZYD_CR26 0x9068 +#define ZYD_CR27 0x906c +#define ZYD_CR28 0x9070 +#define ZYD_CR29 0x9074 +#define ZYD_CR30 0x9078 +#define ZYD_CR31 0x907c +#define ZYD_CR32 0x9080 +#define ZYD_CR33 0x9084 +#define ZYD_CR34 0x9088 +#define ZYD_CR35 0x908c +#define ZYD_CR36 0x9090 +#define ZYD_CR37 0x9094 +#define ZYD_CR38 0x9098 +#define ZYD_CR39 0x909c +#define ZYD_CR40 0x90a0 +#define ZYD_CR41 0x90a4 +#define ZYD_CR42 0x90a8 +#define ZYD_CR43 0x90ac +#define ZYD_CR44 0x90b0 +#define ZYD_CR45 0x90b4 +#define ZYD_CR46 0x90b8 +#define ZYD_CR47 0x90bc +#define ZYD_CR48 0x90c0 +#define ZYD_CR49 0x90c4 +#define ZYD_CR50 0x90c8 +#define ZYD_CR51 0x90cc +#define ZYD_CR52 0x90d0 +#define ZYD_CR53 0x90d4 +#define ZYD_CR54 0x90d8 +#define ZYD_CR55 0x90dc +#define ZYD_CR56 0x90e0 +#define ZYD_CR57 0x90e4 +#define ZYD_CR58 0x90e8 +#define ZYD_CR59 0x90ec +#define ZYD_CR60 0x90f0 +#define ZYD_CR61 0x90f4 +#define ZYD_CR62 0x90f8 +#define ZYD_CR63 0x90fc +#define ZYD_CR64 0x9100 +#define ZYD_CR65 0x9104 +#define ZYD_CR66 0x9108 +#define ZYD_CR67 0x910c +#define ZYD_CR68 0x9110 +#define ZYD_CR69 0x9114 +#define ZYD_CR70 0x9118 +#define ZYD_CR71 0x911c +#define ZYD_CR72 0x9120 +#define ZYD_CR73 0x9124 +#define ZYD_CR74 0x9128 +#define ZYD_CR75 0x912c +#define ZYD_CR76 0x9130 +#define ZYD_CR77 0x9134 +#define ZYD_CR78 0x9138 +#define ZYD_CR79 0x913c +#define ZYD_CR80 0x9140 +#define ZYD_CR81 0x9144 +#define ZYD_CR82 0x9148 +#define ZYD_CR83 0x914c +#define ZYD_CR84 0x9150 +#define ZYD_CR85 0x9154 +#define ZYD_CR86 0x9158 +#define ZYD_CR87 0x915c +#define ZYD_CR88 0x9160 +#define ZYD_CR89 0x9164 +#define ZYD_CR90 0x9168 +#define ZYD_CR91 0x916c +#define ZYD_CR92 0x9170 +#define ZYD_CR93 0x9174 +#define ZYD_CR94 0x9178 +#define ZYD_CR95 0x917c +#define ZYD_CR96 0x9180 +#define ZYD_CR97 0x9184 +#define ZYD_CR98 0x9188 +#define ZYD_CR99 0x918c +#define ZYD_CR100 0x9190 +#define ZYD_CR101 0x9194 +#define ZYD_CR102 0x9198 +#define ZYD_CR103 0x919c +#define ZYD_CR104 0x91a0 +#define ZYD_CR105 0x91a4 +#define ZYD_CR106 0x91a8 +#define ZYD_CR107 0x91ac +#define ZYD_CR108 0x91b0 +#define ZYD_CR109 0x91b4 +#define ZYD_CR110 0x91b8 +#define ZYD_CR111 0x91bc +#define ZYD_CR112 0x91c0 +#define ZYD_CR113 0x91c4 +#define ZYD_CR114 0x91c8 +#define ZYD_CR115 0x91cc +#define ZYD_CR116 0x91d0 +#define ZYD_CR117 0x91d4 +#define ZYD_CR118 0x91d8 +#define ZYD_CR119 0x91dc +#define ZYD_CR120 0x91e0 +#define ZYD_CR121 0x91e4 +#define ZYD_CR122 0x91e8 +#define ZYD_CR123 0x91ec +#define ZYD_CR124 0x91f0 +#define ZYD_CR125 0x91f4 +#define ZYD_CR126 0x91f8 +#define ZYD_CR127 0x91fc +#define ZYD_CR128 0x9200 +#define ZYD_CR129 0x9204 +#define ZYD_CR130 0x9208 +#define ZYD_CR131 0x920c +#define ZYD_CR132 0x9210 +#define ZYD_CR133 0x9214 +#define ZYD_CR134 0x9218 +#define ZYD_CR135 0x921c +#define ZYD_CR136 0x9220 +#define ZYD_CR137 0x9224 +#define ZYD_CR138 0x9228 +#define ZYD_CR139 0x922c +#define ZYD_CR140 0x9230 +#define ZYD_CR141 0x9234 +#define ZYD_CR142 0x9238 +#define ZYD_CR143 0x923c +#define ZYD_CR144 0x9240 +#define ZYD_CR145 0x9244 +#define ZYD_CR146 0x9248 +#define ZYD_CR147 0x924c +#define ZYD_CR148 0x9250 +#define ZYD_CR149 0x9254 +#define ZYD_CR150 0x9258 +#define ZYD_CR151 0x925c +#define ZYD_CR152 0x9260 +#define ZYD_CR153 0x9264 +#define ZYD_CR154 0x9268 +#define ZYD_CR155 0x926c +#define ZYD_CR156 0x9270 +#define ZYD_CR157 0x9274 +#define ZYD_CR158 0x9278 +#define ZYD_CR159 0x927c +#define ZYD_CR160 0x9280 +#define ZYD_CR161 0x9284 +#define ZYD_CR162 0x9288 +#define ZYD_CR163 0x928c +#define ZYD_CR164 0x9290 +#define ZYD_CR165 0x9294 +#define ZYD_CR166 0x9298 +#define ZYD_CR167 0x929c +#define ZYD_CR168 0x92a0 +#define ZYD_CR169 0x92a4 +#define ZYD_CR170 0x92a8 +#define ZYD_CR171 0x92ac +#define ZYD_CR172 0x92b0 +#define ZYD_CR173 0x92b4 +#define ZYD_CR174 0x92b8 +#define ZYD_CR175 0x92bc +#define ZYD_CR176 0x92c0 +#define ZYD_CR177 0x92c4 +#define ZYD_CR178 0x92c8 +#define ZYD_CR179 0x92cc +#define ZYD_CR180 0x92d0 +#define ZYD_CR181 0x92d4 +#define ZYD_CR182 0x92d8 +#define ZYD_CR183 0x92dc +#define ZYD_CR184 0x92e0 +#define ZYD_CR185 0x92e4 +#define ZYD_CR186 0x92e8 +#define ZYD_CR187 0x92ec +#define ZYD_CR188 0x92f0 +#define ZYD_CR189 0x92f4 +#define ZYD_CR190 0x92f8 +#define ZYD_CR191 0x92fc +#define ZYD_CR192 0x9300 +#define ZYD_CR193 0x9304 +#define ZYD_CR194 0x9308 +#define ZYD_CR195 0x930c +#define ZYD_CR196 0x9310 +#define ZYD_CR197 0x9314 +#define ZYD_CR198 0x9318 +#define ZYD_CR199 0x931c +#define ZYD_CR200 0x9320 +#define ZYD_CR201 0x9324 +#define ZYD_CR202 0x9328 +#define ZYD_CR203 0x932c +#define ZYD_CR204 0x9330 +#define ZYD_CR205 0x9334 +#define ZYD_CR206 0x9338 +#define ZYD_CR207 0x933c +#define ZYD_CR208 0x9340 +#define ZYD_CR209 0x9344 +#define ZYD_CR210 0x9348 +#define ZYD_CR211 0x934c +#define ZYD_CR212 0x9350 +#define ZYD_CR213 0x9354 +#define ZYD_CR214 0x9358 +#define ZYD_CR215 0x935c +#define ZYD_CR216 0x9360 +#define ZYD_CR217 0x9364 +#define ZYD_CR218 0x9368 +#define ZYD_CR219 0x936c +#define ZYD_CR220 0x9370 +#define ZYD_CR221 0x9374 +#define ZYD_CR222 0x9378 +#define ZYD_CR223 0x937c +#define ZYD_CR224 0x9380 +#define ZYD_CR225 0x9384 +#define ZYD_CR226 0x9388 +#define ZYD_CR227 0x938c +#define ZYD_CR228 0x9390 +#define ZYD_CR229 0x9394 +#define ZYD_CR230 0x9398 +#define ZYD_CR231 0x939c +#define ZYD_CR232 0x93a0 +#define ZYD_CR233 0x93a4 +#define ZYD_CR234 0x93a8 +#define ZYD_CR235 0x93ac +#define ZYD_CR236 0x93b0 +#define ZYD_CR240 0x93c0 +#define ZYD_CR241 0x93c4 +#define ZYD_CR242 0x93c8 +#define ZYD_CR243 0x93cc +#define ZYD_CR244 0x93d0 +#define ZYD_CR245 0x93d4 +#define ZYD_CR251 0x93ec +#define ZYD_CR252 0x93f0 +#define ZYD_CR253 0x93f4 +#define ZYD_CR254 0x93f8 +#define ZYD_CR255 0x93fc + +/* copied nearly verbatim from the Linux driver rewrite */ +#define ZYD_DEF_PHY \ +{ \ + { ZYD_CR0, 0x0a }, { ZYD_CR1, 0x06 }, { ZYD_CR2, 0x26 }, \ + { ZYD_CR3, 0x38 }, { ZYD_CR4, 0x80 }, { ZYD_CR9, 0xa0 }, \ + { ZYD_CR10, 0x81 }, { ZYD_CR11, 0x00 }, { ZYD_CR12, 0x7f }, \ + { ZYD_CR13, 0x8c }, { ZYD_CR14, 0x80 }, { ZYD_CR15, 0x3d }, \ + { ZYD_CR16, 0x20 }, { ZYD_CR17, 0x1e }, { ZYD_CR18, 0x0a }, \ + { ZYD_CR19, 0x48 }, { ZYD_CR20, 0x0c }, { ZYD_CR21, 0x0c }, \ + { ZYD_CR22, 0x23 }, { ZYD_CR23, 0x90 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR25, 0x40 }, { ZYD_CR26, 0x10 }, { ZYD_CR27, 0x19 }, \ + { ZYD_CR28, 0x7f }, { ZYD_CR29, 0x80 }, { ZYD_CR30, 0x4b }, \ + { ZYD_CR31, 0x60 }, { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x08 }, \ + { ZYD_CR34, 0x06 }, { ZYD_CR35, 0x0a }, { ZYD_CR36, 0x00 }, \ + { ZYD_CR37, 0x00 }, { ZYD_CR38, 0x38 }, { ZYD_CR39, 0x0c }, \ + { ZYD_CR40, 0x84 }, { ZYD_CR41, 0x2a }, { ZYD_CR42, 0x80 }, \ + { ZYD_CR43, 0x10 }, { ZYD_CR44, 0x12 }, { ZYD_CR46, 0xff }, \ + { ZYD_CR47, 0x08 }, { ZYD_CR48, 0x26 }, { ZYD_CR49, 0x5b }, \ + { ZYD_CR64, 0xd0 }, { ZYD_CR65, 0x04 }, { ZYD_CR66, 0x58 }, \ + { ZYD_CR67, 0xc9 }, { ZYD_CR68, 0x88 }, { ZYD_CR69, 0x41 }, \ + { ZYD_CR70, 0x23 }, { ZYD_CR71, 0x10 }, { ZYD_CR72, 0xff }, \ + { ZYD_CR73, 0x32 }, { ZYD_CR74, 0x30 }, { ZYD_CR75, 0x65 }, \ + { ZYD_CR76, 0x41 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x30 }, \ + { ZYD_CR79, 0x68 }, { ZYD_CR80, 0x64 }, { ZYD_CR81, 0x64 }, \ + { ZYD_CR82, 0x00 }, { ZYD_CR83, 0x00 }, { ZYD_CR84, 0x00 }, \ + { ZYD_CR85, 0x02 }, { ZYD_CR86, 0x00 }, { ZYD_CR87, 0x00 }, \ + { ZYD_CR88, 0xff }, { ZYD_CR89, 0xfc }, { ZYD_CR90, 0x00 }, \ + { ZYD_CR91, 0x00 }, { ZYD_CR92, 0x00 }, { ZYD_CR93, 0x08 }, \ + { ZYD_CR94, 0x00 }, { ZYD_CR95, 0x00 }, { ZYD_CR96, 0xff }, \ + { ZYD_CR97, 0xe7 }, { ZYD_CR98, 0x00 }, { ZYD_CR99, 0x00 }, \ + { ZYD_CR100, 0x00 }, { ZYD_CR101, 0xae }, { ZYD_CR102, 0x02 }, \ + { ZYD_CR103, 0x00 }, { ZYD_CR104, 0x03 }, { ZYD_CR105, 0x65 }, \ + { ZYD_CR106, 0x04 }, { ZYD_CR107, 0x00 }, { ZYD_CR108, 0x0a }, \ + { ZYD_CR109, 0xaa }, { ZYD_CR110, 0xaa }, { ZYD_CR111, 0x25 }, \ + { ZYD_CR112, 0x25 }, { ZYD_CR113, 0x00 }, { ZYD_CR119, 0x1e }, \ + { ZYD_CR125, 0x90 }, { ZYD_CR126, 0x00 }, { ZYD_CR127, 0x00 }, \ + { ZYD_CR5, 0x00 }, { ZYD_CR6, 0x00 }, { ZYD_CR7, 0x00 }, \ + { ZYD_CR8, 0x00 }, { ZYD_CR9, 0x20 }, { ZYD_CR12, 0xf0 }, \ + { ZYD_CR20, 0x0e }, { ZYD_CR21, 0x0e }, { ZYD_CR27, 0x10 }, \ + { ZYD_CR44, 0x33 }, { ZYD_CR47, 0x30 }, { ZYD_CR83, 0x24 }, \ + { ZYD_CR84, 0x04 }, { ZYD_CR85, 0x00 }, { ZYD_CR86, 0x0C }, \ + { ZYD_CR87, 0x12 }, { ZYD_CR88, 0x0C }, { ZYD_CR89, 0x00 }, \ + { ZYD_CR90, 0x10 }, { ZYD_CR91, 0x08 }, { ZYD_CR93, 0x00 }, \ + { ZYD_CR94, 0x01 }, { ZYD_CR95, 0x00 }, { ZYD_CR96, 0x50 }, \ + { ZYD_CR97, 0x37 }, { ZYD_CR98, 0x35 }, { ZYD_CR101, 0x13 }, \ + { ZYD_CR102, 0x27 }, { ZYD_CR103, 0x27 }, { ZYD_CR104, 0x18 }, \ + { ZYD_CR105, 0x12 }, { ZYD_CR109, 0x27 }, { ZYD_CR110, 0x27 }, \ + { ZYD_CR111, 0x27 }, { ZYD_CR112, 0x27 }, { ZYD_CR113, 0x27 }, \ + { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x26 }, { ZYD_CR116, 0x24 }, \ + { ZYD_CR117, 0xfc }, { ZYD_CR118, 0xfa }, { ZYD_CR120, 0x4f }, \ + { ZYD_CR123, 0x27 }, { ZYD_CR125, 0xaa }, { ZYD_CR127, 0x03 }, \ + { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, \ + { ZYD_CR131, 0x0C }, { ZYD_CR136, 0xdf }, { ZYD_CR137, 0x40 }, \ + { ZYD_CR138, 0xa0 }, { ZYD_CR139, 0xb0 }, { ZYD_CR140, 0x99 }, \ + { ZYD_CR141, 0x82 }, { ZYD_CR142, 0x54 }, { ZYD_CR143, 0x1c }, \ + { ZYD_CR144, 0x6c }, { ZYD_CR147, 0x07 }, { ZYD_CR148, 0x4c }, \ + { ZYD_CR149, 0x50 }, { ZYD_CR150, 0x0e }, { ZYD_CR151, 0x18 }, \ + { ZYD_CR160, 0xfe }, { ZYD_CR161, 0xee }, { ZYD_CR162, 0xaa }, \ + { ZYD_CR163, 0xfa }, { ZYD_CR164, 0xfa }, { ZYD_CR165, 0xea }, \ + { ZYD_CR166, 0xbe }, { ZYD_CR167, 0xbe }, { ZYD_CR168, 0x6a }, \ + { ZYD_CR169, 0xba }, { ZYD_CR170, 0xba }, { ZYD_CR171, 0xba }, \ + { ZYD_CR204, 0x7d }, { ZYD_CR203, 0x30 }, \ + { 0, 0 } \ +} + +#define ZYD_DEF_PHYB \ +{ \ + { ZYD_CR0, 0x14 }, { ZYD_CR1, 0x06 }, { ZYD_CR2, 0x26 }, \ + { ZYD_CR3, 0x38 }, { ZYD_CR4, 0x80 }, { ZYD_CR9, 0xe0 }, \ + { ZYD_CR10, 0x81 }, { ZYD_CR11, 0x00 }, { ZYD_CR12, 0xf0 }, \ + { ZYD_CR13, 0x8c }, { ZYD_CR14, 0x80 }, { ZYD_CR15, 0x3d }, \ + { ZYD_CR16, 0x20 }, { ZYD_CR17, 0x1e }, { ZYD_CR18, 0x0a }, \ + { ZYD_CR19, 0x48 }, { ZYD_CR20, 0x10 }, { ZYD_CR21, 0x0e }, \ + { ZYD_CR22, 0x23 }, { ZYD_CR23, 0x90 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR25, 0x40 }, { ZYD_CR26, 0x10 }, { ZYD_CR27, 0x10 }, \ + { ZYD_CR28, 0x7f }, { ZYD_CR29, 0x80 }, { ZYD_CR30, 0x4b }, \ + { ZYD_CR31, 0x60 }, { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x08 }, \ + { ZYD_CR34, 0x06 }, { ZYD_CR35, 0x0a }, { ZYD_CR36, 0x00 }, \ + { ZYD_CR37, 0x00 }, { ZYD_CR38, 0x38 }, { ZYD_CR39, 0x0c }, \ + { ZYD_CR40, 0x84 }, { ZYD_CR41, 0x2a }, { ZYD_CR42, 0x80 }, \ + { ZYD_CR43, 0x10 }, { ZYD_CR44, 0x33 }, { ZYD_CR46, 0xff }, \ + { ZYD_CR47, 0x1E }, { ZYD_CR48, 0x26 }, { ZYD_CR49, 0x5b }, \ + { ZYD_CR64, 0xd0 }, { ZYD_CR65, 0x04 }, { ZYD_CR66, 0x58 }, \ + { ZYD_CR67, 0xc9 }, { ZYD_CR68, 0x88 }, { ZYD_CR69, 0x41 }, \ + { ZYD_CR70, 0x23 }, { ZYD_CR71, 0x10 }, { ZYD_CR72, 0xff }, \ + { ZYD_CR73, 0x32 }, { ZYD_CR74, 0x30 }, { ZYD_CR75, 0x65 }, \ + { ZYD_CR76, 0x41 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x30 }, \ + { ZYD_CR79, 0xf0 }, { ZYD_CR80, 0x64 }, { ZYD_CR81, 0x64 }, \ + { ZYD_CR82, 0x00 }, { ZYD_CR83, 0x24 }, { ZYD_CR84, 0x04 }, \ + { ZYD_CR85, 0x00 }, { ZYD_CR86, 0x0c }, { ZYD_CR87, 0x12 }, \ + { ZYD_CR88, 0x0c }, { ZYD_CR89, 0x00 }, { ZYD_CR90, 0x58 }, \ + { ZYD_CR91, 0x04 }, { ZYD_CR92, 0x00 }, { ZYD_CR93, 0x00 }, \ + { ZYD_CR94, 0x01 }, { ZYD_CR95, 0x20 }, { ZYD_CR96, 0x50 }, \ + { ZYD_CR97, 0x37 }, { ZYD_CR98, 0x35 }, { ZYD_CR99, 0x00 }, \ + { ZYD_CR100, 0x01 }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR103, 0x27 }, { ZYD_CR104, 0x18 }, { ZYD_CR105, 0x12 }, \ + { ZYD_CR106, 0x04 }, { ZYD_CR107, 0x00 }, { ZYD_CR108, 0x0a }, \ + { ZYD_CR109, 0x27 }, { ZYD_CR110, 0x27 }, { ZYD_CR111, 0x27 }, \ + { ZYD_CR112, 0x27 }, { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, \ + { ZYD_CR115, 0x26 }, { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xfc }, \ + { ZYD_CR118, 0xfa }, { ZYD_CR119, 0x1e }, { ZYD_CR125, 0x90 }, \ + { ZYD_CR126, 0x00 }, { ZYD_CR127, 0x00 }, { ZYD_CR128, 0x14 }, \ + { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, { ZYD_CR131, 0x0c }, \ + { ZYD_CR136, 0xdf }, { ZYD_CR137, 0xa0 }, { ZYD_CR138, 0xa8 }, \ + { ZYD_CR139, 0xb4 }, { ZYD_CR140, 0x98 }, { ZYD_CR141, 0x82 }, \ + { ZYD_CR142, 0x53 }, { ZYD_CR143, 0x1c }, { ZYD_CR144, 0x6c }, \ + { ZYD_CR147, 0x07 }, { ZYD_CR148, 0x40 }, { ZYD_CR149, 0x40 }, \ + { ZYD_CR150, 0x14 }, { ZYD_CR151, 0x18 }, { ZYD_CR159, 0x70 }, \ + { ZYD_CR160, 0xfe }, { ZYD_CR161, 0xee }, { ZYD_CR162, 0xaa }, \ + { ZYD_CR163, 0xfa }, { ZYD_CR164, 0xfa }, { ZYD_CR165, 0xea }, \ + { ZYD_CR166, 0xbe }, { ZYD_CR167, 0xbe }, { ZYD_CR168, 0x6a }, \ + { ZYD_CR169, 0xba }, { ZYD_CR170, 0xba }, { ZYD_CR171, 0xba }, \ + { ZYD_CR204, 0x7d }, { ZYD_CR203, 0x30 }, \ + { 0, 0 } \ +} + +#define ZYD_RFMD_PHY \ +{ \ + { ZYD_CR2, 0x1e }, { ZYD_CR9, 0x20 }, { ZYD_CR10, 0x89 }, \ + { ZYD_CR11, 0x00 }, { ZYD_CR15, 0xd0 }, { ZYD_CR17, 0x68 }, \ + { ZYD_CR19, 0x4a }, { ZYD_CR20, 0x0c }, { ZYD_CR21, 0x0e }, \ + { ZYD_CR23, 0x48 }, { ZYD_CR24, 0x14 }, { ZYD_CR26, 0x90 }, \ + { ZYD_CR27, 0x30 }, { ZYD_CR29, 0x20 }, { ZYD_CR31, 0xb2 }, \ + { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x28 }, { ZYD_CR38, 0x30 }, \ + { ZYD_CR34, 0x0f }, { ZYD_CR35, 0xf0 }, { ZYD_CR41, 0x2a }, \ + { ZYD_CR46, 0x7f }, { ZYD_CR47, 0x1e }, { ZYD_CR51, 0xc5 }, \ + { ZYD_CR52, 0xc5 }, { ZYD_CR53, 0xc5 }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR82, 0x00 }, \ + { ZYD_CR83, 0x24 }, { ZYD_CR84, 0x04 }, { ZYD_CR85, 0x00 }, \ + { ZYD_CR86, 0x10 }, { ZYD_CR87, 0x2a }, { ZYD_CR88, 0x10 }, \ + { ZYD_CR89, 0x24 }, { ZYD_CR90, 0x18 }, { ZYD_CR91, 0x00 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR93, 0x00 }, { ZYD_CR94, 0x01 }, \ + { ZYD_CR95, 0x00 }, { ZYD_CR96, 0x40 }, { ZYD_CR97, 0x37 }, \ + { ZYD_CR98, 0x05 }, { ZYD_CR99, 0x28 }, { ZYD_CR100, 0x00 }, \ + { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, { ZYD_CR103, 0x27 }, \ + { ZYD_CR104, 0x18 }, { ZYD_CR105, 0x12 }, { ZYD_CR106, 0x1a }, \ + { ZYD_CR107, 0x24 }, { ZYD_CR108, 0x0a }, { ZYD_CR109, 0x13 }, \ + { ZYD_CR110, 0x2f }, { ZYD_CR111, 0x27 }, { ZYD_CR112, 0x27 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x40 }, \ + { ZYD_CR116, 0x40 }, { ZYD_CR117, 0xf0 }, { ZYD_CR118, 0xf0 }, \ + { ZYD_CR119, 0x16 }, { ZYD_CR122, 0x00 }, { ZYD_CR127, 0x03 }, \ + { ZYD_CR131, 0x08 }, { ZYD_CR138, 0x28 }, { ZYD_CR148, 0x44 }, \ + { ZYD_CR150, 0x10 }, { ZYD_CR169, 0xbb }, { ZYD_CR170, 0xbb } \ +} + +#define ZYD_RFMD_RF \ +{ \ + 0x000007, 0x07dd43, 0x080959, 0x0e6666, 0x116a57, 0x17dd43, \ + 0x1819f9, 0x1e6666, 0x214554, 0x25e7fa, 0x27fffa, 0x294128, \ + 0x2c0000, 0x300000, 0x340000, 0x381e0f, 0x6c180f \ +} + +#define ZYD_RFMD_CHANTABLE \ +{ \ + { 0x181979, 0x1e6666 }, \ + { 0x181989, 0x1e6666 }, \ + { 0x181999, 0x1e6666 }, \ + { 0x1819a9, 0x1e6666 }, \ + { 0x1819b9, 0x1e6666 }, \ + { 0x1819c9, 0x1e6666 }, \ + { 0x1819d9, 0x1e6666 }, \ + { 0x1819e9, 0x1e6666 }, \ + { 0x1819f9, 0x1e6666 }, \ + { 0x181a09, 0x1e6666 }, \ + { 0x181a19, 0x1e6666 }, \ + { 0x181a29, 0x1e6666 }, \ + { 0x181a39, 0x1e6666 }, \ + { 0x181a60, 0x1c0000 } \ +} + + + +#define ZYD_AL2230_PHY \ +{ \ + { ZYD_CR15, 0x20 }, { ZYD_CR23, 0x40 }, { ZYD_CR24, 0x20 }, \ + { ZYD_CR26, 0x11 }, { ZYD_CR28, 0x3e }, { ZYD_CR29, 0x00 }, \ + { ZYD_CR44, 0x33 }, { ZYD_CR106, 0x2a }, { ZYD_CR107, 0x1a }, \ + { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x27 }, { ZYD_CR111, 0x2b }, \ + { ZYD_CR112, 0x2b }, { ZYD_CR119, 0x0a }, { ZYD_CR10, 0x89 }, \ + { ZYD_CR17, 0x28 }, { ZYD_CR26, 0x93 }, { ZYD_CR34, 0x30 }, \ + { ZYD_CR35, 0x3e }, { ZYD_CR41, 0x24 }, { ZYD_CR44, 0x32 }, \ + { ZYD_CR46, 0x96 }, { ZYD_CR47, 0x1e }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, \ + { ZYD_CR89, 0x04 }, { ZYD_CR92, 0x0a }, { ZYD_CR99, 0x28 }, \ + { ZYD_CR100, 0x00 }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x24 }, { ZYD_CR107, 0x2a }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x1f }, { ZYD_CR112, 0x1f }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0xfc }, \ + { ZYD_CR119, 0x10 }, { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, \ + { ZYD_CR122, 0xe0 }, { ZYD_CR137, 0x88 }, { ZYD_CR252, 0xff }, \ + { ZYD_CR253, 0xff }, { ZYD_CR251, 0x2f }, { ZYD_CR251, 0x3f }, \ + { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 } \ +} + +#define ZYD_AL2230_PHY_B \ +{ \ + { ZYD_CR10, 0x89 }, { ZYD_CR15, 0x20 }, { ZYD_CR17, 0x2b }, \ + { ZYD_CR23, 0x40 }, { ZYD_CR24, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR28, 0x3e }, { ZYD_CR29, 0x00 }, { ZYD_CR33, 0x28 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x3e }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x99 }, { ZYD_CR47, 0x1e }, \ + { ZYD_CR48, 0x00 }, { ZYD_CR49, 0x00 }, { ZYD_CR51, 0x01 }, \ + { ZYD_CR52, 0x80 }, { ZYD_CR53, 0x7e }, { ZYD_CR65, 0x00 }, \ + { ZYD_CR66, 0x00 }, { ZYD_CR67, 0x00 }, { ZYD_CR68, 0x00 }, \ + { ZYD_CR69, 0x28 }, { ZYD_CR79, 0x58 }, { ZYD_CR80, 0x30 }, \ + { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, { ZYD_CR89, 0x04 }, \ + { ZYD_CR91, 0x00 }, { ZYD_CR92, 0x0a }, { ZYD_CR98, 0x8d }, \ + { ZYD_CR99, 0x00 }, { ZYD_CR101, 0x13 }, { ZYD_CR106, 0x24 }, \ + { ZYD_CR107, 0x2a }, { ZYD_CR109, 0x13 }, { ZYD_CR110, 0x1f }, \ + { ZYD_CR111, 0x1f }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x26 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xfa }, { ZYD_CR118, 0xfa }, \ + { ZYD_CR119, 0x10 }, { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x6c }, \ + { ZYD_CR122, 0xfc }, { ZYD_CR123, 0x57 }, { ZYD_CR125, 0xad }, \ + { ZYD_CR126, 0x6c }, { ZYD_CR127, 0x03 }, { ZYD_CR137, 0x50 }, \ + { ZYD_CR138, 0xa8 }, { ZYD_CR144, 0xac }, { ZYD_CR150, 0x0d }, \ + { ZYD_CR252, 0x00 }, { ZYD_CR253, 0x00 } \ +} + +#define ZYD_AL2230_RF \ +{ \ + 0x03f790, 0x033331, 0x00000d, 0x0b3331, 0x03b812, 0x00fff3, \ + 0x000da4, 0x0f4dc5, 0x0805b6, 0x011687, 0x000688, 0x0403b9, \ + 0x00dbba, 0x00099b, 0x0bdffc, 0x00000d, 0x00500f, 0x00d00f, \ + 0x004c0f, 0x00540f, 0x00700f, 0x00500f \ +} + +#define ZYD_AL2230_RF_B \ +{ \ + 0x03f790, 0x033331, 0x00000d, 0x0b3331, 0x03b812, 0x00fff3, \ + 0x0005a4, 0x0f4dc5, 0x0805b6, 0x0146c7, 0x000688, 0x0403b9, \ + 0x00dbba, 0x00099b, 0x0bdffc, 0x00000d, 0x00580f \ +} + +#define ZYD_AL2230_CHANTABLE \ +{ \ + { 0x03f790, 0x033331, 0x00000d }, \ + { 0x03f790, 0x0b3331, 0x00000d }, \ + { 0x03e790, 0x033331, 0x00000d }, \ + { 0x03e790, 0x0b3331, 0x00000d }, \ + { 0x03f7a0, 0x033331, 0x00000d }, \ + { 0x03f7a0, 0x0b3331, 0x00000d }, \ + { 0x03e7a0, 0x033331, 0x00000d }, \ + { 0x03e7a0, 0x0b3331, 0x00000d }, \ + { 0x03f7b0, 0x033331, 0x00000d }, \ + { 0x03f7b0, 0x0b3331, 0x00000d }, \ + { 0x03e7b0, 0x033331, 0x00000d }, \ + { 0x03e7b0, 0x0b3331, 0x00000d }, \ + { 0x03f7c0, 0x033331, 0x00000d }, \ + { 0x03e7c0, 0x066661, 0x00000d } \ +} + + + +#define ZYD_AL7230B_PHY_1 \ +{ \ + { ZYD_CR240, 0x57 }, { ZYD_CR15, 0x20 }, { ZYD_CR23, 0x40 }, \ + { ZYD_CR24, 0x20 }, { ZYD_CR26, 0x11 }, { ZYD_CR28, 0x3e }, \ + { ZYD_CR29, 0x00 }, { ZYD_CR44, 0x33 }, { ZYD_CR106, 0x22 }, \ + { ZYD_CR107, 0x1a }, { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x27 }, \ + { ZYD_CR111, 0x2b }, { ZYD_CR112, 0x2b }, { ZYD_CR119, 0x0a }, \ + { ZYD_CR122, 0xfc }, { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x28 }, \ + { ZYD_CR26, 0x93 }, { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x3e }, \ + { ZYD_CR41, 0x24 }, { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x96 }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR79, 0x58 }, { ZYD_CR80, 0x30 }, \ + { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, { ZYD_CR89, 0x04 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR99, 0x28 }, { ZYD_CR100, 0x02 }, \ + { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, { ZYD_CR106, 0x22 }, \ + { ZYD_CR107, 0x3f }, { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x1f }, \ + { ZYD_CR111, 0x1f }, { ZYD_CR112, 0x1f }, { ZYD_CR113, 0x27 }, \ + { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, { ZYD_CR116, 0x3f }, \ + { ZYD_CR117, 0xfa }, { ZYD_CR118, 0xfc }, { ZYD_CR119, 0x10 }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, { ZYD_CR137, 0x88 }, \ + { ZYD_CR138, 0xa8 }, { ZYD_CR252, 0x34 }, { ZYD_CR253, 0x34 }, \ + { ZYD_CR251, 0x2f } \ +} + +#define ZYD_AL7230B_PHY_2 \ +{ \ + { ZYD_CR251, 0x3f }, { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, \ + { ZYD_CR130, 0x10 }, { ZYD_CR38, 0x38 }, { ZYD_CR136, 0xdf } \ +} + +#define ZYD_AL7230B_PHY_3 \ +{ \ + { ZYD_CR203, 0x06 }, { ZYD_CR240, 0x80 } \ +} + +#define ZYD_AL7230B_RF_1 \ +{ \ + 0x09ec04, 0x8cccc8, 0x4ff821, 0xc5fbfc, 0x21ebfe, 0xafd401, \ + 0x6cf56a, 0xe04073, 0x193d76, 0x9dd844, 0x500007, 0xd8c010, \ + 0x3c9000, 0xbfffff, 0x700000, 0xf15d58 \ +} + +#define ZYD_AL7230B_RF_2 \ +{ \ + 0xf15d59, 0xf15d5c, 0xf15d58 \ +} + +#define ZYD_AL7230B_RF_SETCHANNEL \ +{ \ + 0x4ff821, 0xc5fbfc, 0x21ebfe, 0xafd401, 0x6cf56a, 0xe04073, \ + 0x193d76, 0x9dd844, 0x500007, 0xd8c010, 0x3c9000, 0xf15d58 \ +} + +#define ZYD_AL7230B_CHANTABLE \ +{ \ + { 0x09ec00, 0x8cccc8 }, \ + { 0x09ec00, 0x8cccd8 }, \ + { 0x09ec00, 0x8cccc0 }, \ + { 0x09ec00, 0x8cccd0 }, \ + { 0x05ec00, 0x8cccc8 }, \ + { 0x05ec00, 0x8cccd8 }, \ + { 0x05ec00, 0x8cccc0 }, \ + { 0x05ec00, 0x8cccd0 }, \ + { 0x0dec00, 0x8cccc8 }, \ + { 0x0dec00, 0x8cccd8 }, \ + { 0x0dec00, 0x8cccc0 }, \ + { 0x0dec00, 0x8cccd0 }, \ + { 0x03ec00, 0x8cccc8 }, \ + { 0x03ec00, 0x866660 } \ +} + + + +#define ZYD_AL2210_PHY \ +{ \ + { ZYD_CR9, 0xe0 }, { ZYD_CR10, 0x91 }, { ZYD_CR12, 0x90 }, \ + { ZYD_CR15, 0xd0 }, { ZYD_CR16, 0x40 }, { ZYD_CR17, 0x58 }, \ + { ZYD_CR18, 0x04 }, { ZYD_CR23, 0x66 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR26, 0x90 }, { ZYD_CR31, 0x80 }, { ZYD_CR34, 0x06 }, \ + { ZYD_CR35, 0x3e }, { ZYD_CR38, 0x38 }, { ZYD_CR46, 0x90 }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR64, 0x64 }, { ZYD_CR79, 0xb5 }, \ + { ZYD_CR80, 0x38 }, { ZYD_CR81, 0x30 }, { ZYD_CR113, 0xc0 }, \ + { ZYD_CR127, 0x03 } \ +} + +#define ZYD_AL2210_RF \ +{ \ + 0x2396c0, 0x00fcb1, 0x358132, 0x0108b3, 0xc77804, 0x456415, \ + 0xff2226, 0x806667, 0x7860f8, 0xbb01c9, 0x00000a, 0x00000b \ +} + +#define ZYD_AL2210_CHANTABLE \ +{ \ + 0x0196c0, 0x019710, 0x019760, 0x0197b0, 0x019800, 0x019850, \ + 0x0198a0, 0x0198f0, 0x019940, 0x019990, 0x0199e0, 0x019a30, \ + 0x019a80, 0x019b40 \ +} + + + +#define ZYD_GCT_PHY \ +{ \ + { ZYD_CR47, 0x1e }, { ZYD_CR15, 0xdc }, { ZYD_CR113, 0xc0 }, \ + { ZYD_CR20, 0x0c }, { ZYD_CR17, 0x65 }, { ZYD_CR34, 0x04 }, \ + { ZYD_CR35, 0x35 }, { ZYD_CR24, 0x20 }, { ZYD_CR9, 0xe0 }, \ + { ZYD_CR127, 0x02 }, { ZYD_CR10, 0x91 }, { ZYD_CR23, 0x7f }, \ + { ZYD_CR27, 0x10 }, { ZYD_CR28, 0x7a }, { ZYD_CR79, 0xb5 }, \ + { ZYD_CR64, 0x80 }, { ZYD_CR33, 0x28 }, { ZYD_CR38, 0x30 } \ +} + +#define ZYD_GCT_RF \ +{ \ + 0x1f0000, 0x1f0000, 0x1f0200, 0x1f0600, 0x1f8600, 0x1f8600, \ + 0x002050, 0x1f8000, 0x1f8200, 0x1f8600, 0x1c0000, 0x10c458, \ + 0x088e92, 0x187b82, 0x0401b4, 0x140816, 0x0c7000, 0x1c0000, \ + 0x02ccae, 0x128023, 0x0a0000, 0x1a0000, 0x06e380, 0x16cb94, \ + 0x0e1740, 0x014980, 0x116240, 0x090000, 0x192304, 0x05112f, \ + 0x0d54a8, 0x0f8000, 0x1c0008, 0x1c0000, 0x1a0000, 0x1c0008, \ + 0x150000, 0x0c7000, 0x150800, 0x150000 \ +} + +#define ZYD_GCT_CHANTABLE \ +{ \ + 0x1a0000, 0x1a8000, 0x1a4000, 0x1ac000, 0x1a2000, 0x1aa000, \ + 0x1a6000, 0x1ae000, 0x1a1000, 0x1a9000, 0x1a5000, 0x1ad000, \ + 0x1a3000, 0x1ab000 \ +} + + + +#define ZYD_MAXIM_PHY \ +{ \ + { ZYD_CR23, 0x40 }, { ZYD_CR15, 0x20 }, { ZYD_CR28, 0x3e }, \ + { ZYD_CR29, 0x00 }, { ZYD_CR26, 0x11 }, { ZYD_CR44, 0x33 }, \ + { ZYD_CR106, 0x2a }, { ZYD_CR107, 0x1a }, { ZYD_CR109, 0x2b }, \ + { ZYD_CR110, 0x2b }, { ZYD_CR111, 0x2b }, { ZYD_CR112, 0x2b }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0xfa }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, { ZYD_CR122, 0xfe }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x13 }, \ + { ZYD_CR110, 0x27 }, { ZYD_CR111, 0x27 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0x00 }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x06 }, { ZYD_CR122, 0xfe }, \ + { ZYD_CR150, 0x0d } \ +} + +#define ZYD_MAXIM_RF \ +{ \ + 0x00ccd4, 0x030a03, 0x000400, 0x000ca1, 0x010072, 0x018645, \ + 0x004006, 0x0000a7, 0x008258, 0x003fc9, 0x00040a, 0x00000b, \ + 0x00026c \ +} + +#define ZYD_MAXIM_CHANTABLE \ +{ \ + { 0x0ccd4, 0x30a03 }, \ + { 0x22224, 0x00a13 }, \ + { 0x37774, 0x10a13 }, \ + { 0x0ccd4, 0x30a13 }, \ + { 0x22224, 0x00a23 }, \ + { 0x37774, 0x10a23 }, \ + { 0x0ccd4, 0x30a23 }, \ + { 0x22224, 0x00a33 }, \ + { 0x37774, 0x10a33 }, \ + { 0x0ccd4, 0x30a33 }, \ + { 0x22224, 0x00a43 }, \ + { 0x37774, 0x10a43 }, \ + { 0x0ccd4, 0x30a43 }, \ + { 0x199a4, 0x20a53 } \ +} + + + +#define ZYD_MAXIM2_PHY \ +{ \ + { ZYD_CR23, 0x40 }, { ZYD_CR15, 0x20 }, { ZYD_CR28, 0x3e }, \ + { ZYD_CR29, 0x00 }, { ZYD_CR26, 0x11 }, { ZYD_CR44, 0x33 }, \ + { ZYD_CR106, 0x2a }, { ZYD_CR107, 0x1a }, { ZYD_CR109, 0x2b }, \ + { ZYD_CR110, 0x2b }, { ZYD_CR111, 0x2b }, { ZYD_CR112, 0x2b }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0xfa }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, { ZYD_CR122, 0xfe }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0x00 }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x06 }, { ZYD_CR122, 0xfe } \ +} + +#define ZYD_MAXIM2_RF \ +{ \ + 0x33334, 0x10a03, 0x00400, 0x00ca1, 0x10072, 0x18645, 0x04006, \ + 0x000a7, 0x08258, 0x03fc9, 0x0040a, 0x0000b, 0x0026c \ +} + +#define ZYD_MAXIM2_CHANTABLE_F \ +{ \ + 0x33334, 0x08884, 0x1ddd4, 0x33334, 0x08884, 0x1ddd4, 0x33334, \ + 0x08884, 0x1ddd4, 0x33334, 0x08884, 0x1ddd4, 0x33334, 0x26664 \ +} + +#define ZYD_MAXIM2_CHANTABLE \ +{ \ + { 0x33334, 0x10a03 }, \ + { 0x08884, 0x20a13 }, \ + { 0x1ddd4, 0x30a13 }, \ + { 0x33334, 0x10a13 }, \ + { 0x08884, 0x20a23 }, \ + { 0x1ddd4, 0x30a23 }, \ + { 0x33334, 0x10a23 }, \ + { 0x08884, 0x20a33 }, \ + { 0x1ddd4, 0x30a33 }, \ + { 0x33334, 0x10a33 }, \ + { 0x08884, 0x20a43 }, \ + { 0x1ddd4, 0x30a43 }, \ + { 0x33334, 0x10a43 }, \ + { 0x26664, 0x20a53 } \ +} + +/* + * Control pipe requests. + */ +#define ZYD_DOWNLOADREQ 0x30 +#define ZYD_DOWNLOADSTS 0x31 + +/* possible values for register ZYD_CR_INTERRUPT */ +#define ZYD_HWINT_MASK 0x004f0000 + +/* possible values for register ZYD_MAC_MISC */ +#define ZYD_UNLOCK_PHY_REGS 0x80 + +/* possible values for register ZYD_MAC_ENCRYPTION_TYPE */ +#define ZYD_ENC_SNIFFER 8 + +/* flags for register ZYD_MAC_RXFILTER */ +#define ZYD_FILTER_ASS_REQ (1 << 0) +#define ZYD_FILTER_ASS_RSP (1 << 1) +#define ZYD_FILTER_REASS_REQ (1 << 2) +#define ZYD_FILTER_REASS_RSP (1 << 3) +#define ZYD_FILTER_PRB_REQ (1 << 4) +#define ZYD_FILTER_PRB_RSP (1 << 5) +#define ZYD_FILTER_BCN (1 << 8) +#define ZYD_FILTER_ATIM (1 << 9) +#define ZYD_FILTER_DEASS (1 << 10) +#define ZYD_FILTER_AUTH (1 << 11) +#define ZYD_FILTER_DEAUTH (1 << 12) +#define ZYD_FILTER_PS_POLL (1 << 26) +#define ZYD_FILTER_RTS (1 << 27) +#define ZYD_FILTER_CTS (1 << 28) +#define ZYD_FILTER_ACK (1 << 29) +#define ZYD_FILTER_CFE (1 << 30) +#define ZYD_FILTER_CFE_A (1 << 31) + +/* helpers for register ZYD_MAC_RXFILTER */ +#define ZYD_FILTER_MONITOR 0xffffffff +#define ZYD_FILTER_BSS \ + (ZYD_FILTER_ASS_RSP | ZYD_FILTER_REASS_RSP | \ + ZYD_FILTER_PRB_RSP | ZYD_FILTER_BCN | ZYD_FILTER_DEASS | \ + ZYD_FILTER_AUTH | ZYD_FILTER_DEAUTH) +#define ZYD_FILTER_HOSTAP \ + (ZYD_FILTER_ASS_REQ | ZYD_FILTER_REASS_REQ | \ + ZYD_FILTER_PRB_REQ | ZYD_FILTER_DEASS | ZYD_FILTER_AUTH | \ + ZYD_FILTER_DEAUTH | ZYD_FILTER_PS_POLL) + +struct zyd_tx_desc { + uint8_t phy; +#define ZYD_TX_PHY_SIGNAL(x) ((x) & 0xf) +#define ZYD_TX_PHY_OFDM (1 << 4) +#define ZYD_TX_PHY_SHPREAMBLE (1 << 5)/* CCK */ +#define ZYD_TX_PHY_5GHZ (1 << 5)/* OFDM */ + + uint16_t len; + uint8_t flags; +#define ZYD_TX_FLAG_BACKOFF (1 << 0) +#define ZYD_TX_FLAG_MULTICAST (1 << 1) +#define ZYD_TX_FLAG_TYPE(x) (((x) & 0x3) << 2) +#define ZYD_TX_TYPE_DATA 0 +#define ZYD_TX_TYPE_PS_POLL 1 +#define ZYD_TX_TYPE_MGMT 2 +#define ZYD_TX_TYPE_CTL 3 +#define ZYD_TX_FLAG_WAKEUP (1 << 4) +#define ZYD_TX_FLAG_RTS (1 << 5) +#define ZYD_TX_FLAG_ENCRYPT (1 << 6) +#define ZYD_TX_FLAG_CTS_TO_SELF (1 << 7) + + uint16_t pktlen; + uint16_t plcp_length; + uint8_t plcp_service; +#define ZYD_PLCP_LENGEXT 0x80 + + uint16_t nextlen; +} __packed; + +struct zyd_plcphdr { + uint8_t signal; + uint8_t reserved[2]; + uint16_t service; /* unaligned! */ +} __packed; + +struct zyd_rx_stat { + uint8_t signal_cck; + uint8_t rssi; + uint8_t signal_ofdm; + uint8_t cipher; +#define ZYD_RX_CIPHER_WEP64 1 +#define ZYD_RX_CIPHER_TKIP 2 +#define ZYD_RX_CIPHER_AES 4 +#define ZYD_RX_CIPHER_WEP128 5 +#define ZYD_RX_CIPHER_WEP256 6 +#define ZYD_RX_CIPHER_WEP \ + (ZYD_RX_CIPHER_WEP64 | ZYD_RX_CIPHER_WEP128 | ZYD_RX_CIPHER_WEP256) + + uint8_t flags; +#define ZYD_RX_OFDM (1 << 0) +#define ZYD_RX_TIMEOUT (1 << 1) +#define ZYD_RX_OVERRUN (1 << 2) +#define ZYD_RX_DECRYPTERR (1 << 3) +#define ZYD_RX_BADCRC32 (1 << 4) +#define ZYD_RX_NOT2ME (1 << 5) +#define ZYD_RX_BADCRC16 (1 << 6) +#define ZYD_RX_ERROR (1 << 7) +} __packed; + +/* this structure may be unaligned */ +struct zyd_rx_desc { +#define ZYD_MAX_RXFRAMECNT 3 + uWord len[ZYD_MAX_RXFRAMECNT]; + uWord tag; +#define ZYD_TAG_MULTIFRAME 0x697e +} __packed; + +/* I2C bus alike */ +struct zyd_rfwrite { + uint16_t code; + uint16_t width; + uint16_t bit[32]; +#define ZYD_RF_IF_LE (1 << 1) +#define ZYD_RF_CLK (1 << 2) +#define ZYD_RF_DATA (1 << 3) +} __packed; + +struct zyd_cmd { + uint16_t code; +#define ZYD_CMD_IOWR 0x0021 /* write HMAC or PHY register */ +#define ZYD_CMD_IORD 0x0022 /* read HMAC or PHY register */ +#define ZYD_CMD_RFCFG 0x0023 /* write RF register */ +#define ZYD_NOTIF_IORD 0x9001 /* response for ZYD_CMD_IORD */ +#define ZYD_NOTIF_MACINTR 0x9001 /* interrupt notification */ +#define ZYD_NOTIF_RETRYSTATUS 0xa001 /* Tx retry notification */ + + uint8_t data[64]; +} __packed; + +/* structure for command ZYD_CMD_IOWR */ +struct zyd_pair { + uint16_t reg; +/* helpers macros to read/write 32-bit registers */ +#define ZYD_REG32_LO(reg) (reg) +#define ZYD_REG32_HI(reg) \ + ((reg) + ((((reg) & 0xf000) == 0x9000) ? 2 : 1)) + + uint16_t val; +} __packed; + +/* structure for notification ZYD_NOTIF_RETRYSTATUS */ +struct zyd_notif_retry { + uint16_t rate; + uint8_t macaddr[IEEE80211_ADDR_LEN]; + uint16_t count; +} __packed; + +#define ZYD_HW_PADDING 10 /* bytes */ + +#define ZYD_CONFIG_INDEX 0 +#define ZYD_IFACE_INDEX 0 + +#define ZYD_INTR_TIMEOUT 1000 +#define ZYD_TX_TIMEOUT 10000 + +#define ZYD_MAX_TXBUFSZ \ + (sizeof(struct zyd_tx_desc) + MCLBYTES) + +#define ZYD_MIN_FRAGSZ \ + (sizeof(struct zyd_plcphdr) + IEEE80211_MIN_LEN + \ + sizeof(struct zyd_rx_stat)) +#define ZYX_MAX_RXBUFSZ \ + ((sizeof (struct zyd_plcphdr) + IEEE80211_MAX_LEN + \ + sizeof (struct zyd_rx_stat)) * ZYD_MAX_RXFRAMECNT + \ + sizeof (struct zyd_rx_desc)) + + +#define ZYD_CMD_FLAG_READ (1 << 0) + +/* quickly determine if a given rate is CCK or OFDM */ +#define ZYD_RATE_IS_OFDM(rate) ((rate) >= 12 && (rate) != 22) + +struct zyd_phy_pair { + uint16_t reg; + uint8_t val; +}; + +struct zyd_mac_pair { + uint16_t reg; + uint32_t val; +}; + +struct zyd_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_antsignal; + int8_t wr_antnoise; +} __packed; + +#define ZYD_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct zyd_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; +} __packed; + +#define ZYD_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct zyd_softc; /* forward declaration */ + +struct zyd_rf { + /* RF methods */ + void (*cfg_init_hw) (struct zyd_softc *, struct zyd_rf *); + void (*cfg_switch_radio) (struct zyd_softc *, uint8_t on); + void (*cfg_set_channel) (struct zyd_softc *, struct zyd_rf *, uint8_t); + uint8_t width; +}; + +enum { + ZYD_TR_BULK_DT_WR, + ZYD_TR_BULK_DT_RD, + ZYD_TR_BULK_CS_WR, + ZYD_TR_BULK_CS_RD, + ZYD_TR_INTR_DT_WR, + ZYD_TR_INTR_DT_RD, + ZYD_TR_INTR_CS_WR, + ZYD_TR_INTR_CS_RD, + ZYD_N_TRANSFER, +}; + +struct zyd_ifq { + struct mbuf *ifq_head; + struct mbuf *ifq_tail; + uint16_t ifq_len; +}; + +struct zyd_node { + struct ieee80211_node ni; + struct ieee80211_amrr_node amn; +}; + +#define ZYD_NODE(ni) ((struct zyd_node *)(ni)) + +struct zyd_vap { + struct ieee80211vap vap; + struct ieee80211_beacon_offsets bo; + struct ieee80211_amrr amrr; + + int (*newstate) (struct ieee80211vap *, + enum ieee80211_state, int); +}; + +#define ZYD_VAP(vap) ((struct zyd_vap *)(vap)) + +struct zyd_config_copy_chan { + uint32_t chan_to_ieee; + enum ieee80211_phymode chan_to_mode; + uint16_t ic_freq; + uint8_t chan_is_5ghz:1; + uint8_t chan_is_2ghz:1; + uint8_t chan_is_b:1; + uint8_t chan_is_a:1; + uint8_t chan_is_g:1; + uint8_t unused:3; +}; + +struct zyd_config_copy_bss { + uint16_t ni_intval; + uint8_t ni_bssid[IEEE80211_ADDR_LEN]; + uint8_t fixed_rate_none; +}; + +struct zyd_config_copy { + struct zyd_config_copy_chan ic_curchan; + struct zyd_config_copy_chan ic_bsschan; + struct zyd_config_copy_bss iv_bss; + + enum ieee80211_opmode ic_opmode; + + uint32_t ic_flags; + uint32_t if_flags; + + uint32_t zyd_multi_low; + uint32_t zyd_multi_high; + + uint16_t ic_txpowlimit; + uint16_t ic_curmode; + + uint8_t ic_myaddr[IEEE80211_ADDR_LEN]; + uint8_t if_broadcastaddr[IEEE80211_ADDR_LEN]; +}; + +struct zyd_softc { + void *sc_evilhack; /* XXX this pointer must be first */ + + struct zyd_rf sc_rf; + struct usb2_callout sc_watchdog; + struct mtx sc_mtx; + struct usb2_config_td sc_config_td; + struct zyd_rx_radiotap_header sc_rxtap; + struct zyd_tx_radiotap_header sc_txtap; + struct zyd_cmd sc_intr_ibuf; + struct zyd_cmd sc_intr_obuf; + struct zyd_tx_desc sc_tx_desc; + struct zyd_ifq sc_tx_queue; + struct cv sc_intr_cv; + + struct ifnet *sc_ifp; + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[ZYD_N_TRANSFER]; + const struct ieee80211_rate_table *sc_rates; + + enum ieee80211_state sc_ns_state; + uint32_t sc_rxtap_len; + uint32_t sc_txtap_len; + uint32_t sc_unit; + int sc_ns_arg; + + uint16_t sc_firmware_base; + uint16_t sc_fw_ver; + uint16_t sc_fwbase; + uint16_t sc_fw_rev; + + uint8_t sc_intr_iwakeup; + uint8_t sc_intr_owakeup; + uint8_t sc_intr_ilen; + uint8_t sc_intr_olen; + uint8_t sc_regdomain; + uint8_t sc_mac_rev; + uint8_t sc_rf_rev; + uint8_t sc_pa_rev; + uint8_t sc_fix_cr47; + uint8_t sc_fix_cr157; + uint8_t sc_pwr_cal[14]; + uint8_t sc_pwr_int[14]; + uint8_t sc_ofdm36_cal[14]; + uint8_t sc_ofdm48_cal[14]; + uint8_t sc_ofdm54_cal[14]; + + uint8_t sc_flags; +#define ZYD_FLAG_INTR_READ_STALL 0x01 +#define ZYD_FLAG_INTR_WRITE_STALL 0x02 +#define ZYD_FLAG_BULK_READ_STALL 0x04 +#define ZYD_FLAG_BULK_WRITE_STALL 0x08 +#define ZYD_FLAG_TX_BEACON 0x10 +#define ZYD_FLAG_HL_READY 0x20 +#define ZYD_FLAG_LL_READY 0x40 +#define ZYD_FLAG_WAIT_COMMAND 0x80 + + uint8_t sc_amrr_timer; + + uint8_t sc_myaddr[IEEE80211_ADDR_LEN]; + + char sc_name[16]; +}; diff --git a/sys/dev/usb2/wlan/usb2_wlan.c b/sys/dev/usb2/wlan/usb2_wlan.c new file mode 100644 index 000000000000..35c9cbb7231a --- /dev/null +++ b/sys/dev/usb2/wlan/usb2_wlan.c @@ -0,0 +1,31 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include + +MODULE_VERSION(usb2_wlan, 1); +MODULE_DEPEND(usb2_wlan, usb2_core, 1, 1, 1); diff --git a/sys/dev/usb2/wlan/usb2_wlan.h b/sys/dev/usb2/wlan/usb2_wlan.h new file mode 100644 index 000000000000..9db120e572ee --- /dev/null +++ b/sys/dev/usb2/wlan/usb2_wlan.h @@ -0,0 +1,57 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#ifndef _USB2_WLAN_H_ +#define _USB2_WLAN_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#endif /* _USB2_WLAN_H_ */ diff --git a/sys/modules/Makefile b/sys/modules/Makefile index 2d4300d2ed9d..403874ad0458 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -302,6 +302,7 @@ SUBDIR= ${_3dfx} \ ural \ urio \ usb \ + usb2 \ uscanner \ uslcom \ utopia \ diff --git a/sys/modules/usb2/Makefile b/sys/modules/usb2/Makefile new file mode 100644 index 000000000000..183ebede4c29 --- /dev/null +++ b/sys/modules/usb2/Makefile @@ -0,0 +1,89 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +SUBDIR = +SUBDIR += bluetooth +SUBDIR += bluetooth_ng +SUBDIR += bluetooth_fw +SUBDIR += controller +SUBDIR += controller_at91dci +SUBDIR += controller_ehci +SUBDIR += controller_musb +SUBDIR += controller_ohci +SUBDIR += controller_uhci +SUBDIR += controller_uss820dci +SUBDIR += core +SUBDIR += ethernet +SUBDIR += ethernet_aue +SUBDIR += ethernet_axe +SUBDIR += ethernet_cdce +SUBDIR += ethernet_cue +SUBDIR += ethernet_dav +SUBDIR += ethernet_kue +SUBDIR += ethernet_rue +SUBDIR += image +SUBDIR += input +SUBDIR += input_hid +SUBDIR += input_kbd +SUBDIR += input_ms +SUBDIR += misc +SUBDIR += misc_dbp +SUBDIR += misc_fm +SUBDIR += ndis +SUBDIR += quirk +SUBDIR += scanner +SUBDIR += serial +SUBDIR += serial_ark +SUBDIR += serial_bsa +SUBDIR += serial_bser +SUBDIR += serial_chcom +SUBDIR += serial_cycom +SUBDIR += serial_foma +SUBDIR += serial_ftdi +SUBDIR += serial_gensa +SUBDIR += serial_ipaq +SUBDIR += serial_lpt +SUBDIR += serial_mct +SUBDIR += serial_modem +SUBDIR += serial_moscom +SUBDIR += serial_plcom +SUBDIR += serial_visor +SUBDIR += serial_vscom +SUBDIR += sound +SUBDIR += storage +SUBDIR += storage_ata +SUBDIR += storage_fs +SUBDIR += storage_mass +SUBDIR += storage_rio +SUBDIR += template +SUBDIR += wlan +SUBDIR += wlan_ral +SUBDIR += wlan_rum +SUBDIR += wlan_zyd + +.include + diff --git a/sys/modules/usb2/bluetooth/Makefile b/sys/modules/usb2/bluetooth/Makefile new file mode 100644 index 000000000000..b52a526d1d2e --- /dev/null +++ b/sys/modules/usb2/bluetooth/Makefile @@ -0,0 +1,15 @@ +# +# $FreeBSD$ +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/bluetooth + +KMOD= usb2_bluetooth +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_netgraph.h +SRCS+= usb2_bluetooth.c + +.include diff --git a/sys/modules/usb2/bluetooth_fw/Makefile b/sys/modules/usb2/bluetooth_fw/Makefile new file mode 100644 index 000000000000..8b24300b5ba1 --- /dev/null +++ b/sys/modules/usb2/bluetooth_fw/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/bluetooth + +KMOD=usb2_bluetooth_fw +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_netgraph.h +SRCS+= ubtbcmfw2.c + +.include diff --git a/sys/modules/usb2/bluetooth_ng/Makefile b/sys/modules/usb2/bluetooth_ng/Makefile new file mode 100644 index 000000000000..d40f9fc5f2f6 --- /dev/null +++ b/sys/modules/usb2/bluetooth_ng/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/bluetooth + +KMOD=usb2_bluetooth_ng +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_netgraph.h +SRCS+= ng_ubt2.c + +.include diff --git a/sys/modules/usb2/controller/Makefile b/sys/modules/usb2/controller/Makefile new file mode 100644 index 000000000000..4c141663b94c --- /dev/null +++ b/sys/modules/usb2/controller/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/controller + +KMOD= usb2_controller +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h pci_if.h opt_bus.h card_if.h +SRCS+= usb2_controller.c + +.include diff --git a/sys/modules/usb2/controller_at91dci/Makefile b/sys/modules/usb2/controller_at91dci/Makefile new file mode 100644 index 000000000000..9810ac1f4117 --- /dev/null +++ b/sys/modules/usb2/controller_at91dci/Makefile @@ -0,0 +1,41 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/controller + +KMOD=usb2_controller_at91dci +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h pci_if.h opt_bus.h card_if.h +SRCS+= at91dci.c +.if defined(HAS_ATMELARM) +SRCS+= at91dci_atmelarm.c +.endif + +.include diff --git a/sys/modules/usb2/controller_ehci/Makefile b/sys/modules/usb2/controller_ehci/Makefile new file mode 100644 index 000000000000..765b31034b3f --- /dev/null +++ b/sys/modules/usb2/controller_ehci/Makefile @@ -0,0 +1,39 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/controller + +KMOD=usb2_controller_ehci +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h pci_if.h opt_bus.h card_if.h +SRCS+= ehci2.c +SRCS+= ehci2_pci.c + +.include diff --git a/sys/modules/usb2/controller_musb/Makefile b/sys/modules/usb2/controller_musb/Makefile new file mode 100644 index 000000000000..1f720f88e3d4 --- /dev/null +++ b/sys/modules/usb2/controller_musb/Makefile @@ -0,0 +1,41 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/controller + +KMOD=usb2_controller_musb +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h pci_if.h opt_bus.h card_if.h +SRCS+= musb2_otg.c +.if defined(HAS_ATMELARM) +SRCS+= musb2_otg_atmelarm.c +.endif + +.include diff --git a/sys/modules/usb2/controller_ohci/Makefile b/sys/modules/usb2/controller_ohci/Makefile new file mode 100644 index 000000000000..78773b9aff4f --- /dev/null +++ b/sys/modules/usb2/controller_ohci/Makefile @@ -0,0 +1,42 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/controller + +KMOD=usb2_controller_ohci +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h pci_if.h opt_bus.h card_if.h +SRCS+= ohci2.c +SRCS+= ohci2_pci.c +.if defined(HAS_ATMELARM) +SRCS+= ohci2_atmelarm.c +.endif + +.include diff --git a/sys/modules/usb2/controller_uhci/Makefile b/sys/modules/usb2/controller_uhci/Makefile new file mode 100644 index 000000000000..5d91c36c0817 --- /dev/null +++ b/sys/modules/usb2/controller_uhci/Makefile @@ -0,0 +1,39 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/controller + +KMOD=usb2_controller_uhci +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h pci_if.h opt_bus.h card_if.h +SRCS+= uhci2.c +SRCS+= uhci2_pci.c + +.include diff --git a/sys/modules/usb2/controller_uss820dci/Makefile b/sys/modules/usb2/controller_uss820dci/Makefile new file mode 100644 index 000000000000..91e10954b3d4 --- /dev/null +++ b/sys/modules/usb2/controller_uss820dci/Makefile @@ -0,0 +1,41 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/controller + +KMOD=usb2_controller_uss820dci +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h pci_if.h opt_bus.h card_if.h +SRCS+= uss820dci.c +.if defined(HAS_ATMELARM) +SRCS+= uss820dci_atmelarm.c +.endif + +.include diff --git a/sys/modules/usb2/core/Makefile b/sys/modules/usb2/core/Makefile new file mode 100644 index 000000000000..f9e95dba213c --- /dev/null +++ b/sys/modules/usb2/core/Makefile @@ -0,0 +1,60 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/core + +KMOD= usb2_core +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h opt_usb.h opt_bus.h +SRCS+= usb2_if.c +SRCS+= usb2_busdma.c +SRCS+= usb2_compat_linux.c +SRCS+= usb2_config_td.c +SRCS+= usb2_core.c +SRCS+= usb2_debug.c +SRCS+= usb2_dev.c +SRCS+= usb2_device.c +SRCS+= usb2_dynamic.c +SRCS+= usb2_error.c +SRCS+= usb2_generic.c +SRCS+= usb2_handle_request.c +SRCS+= usb2_hid.c +SRCS+= usb2_hub.c +SRCS+= usb2_lookup.c +SRCS+= usb2_mbuf.c +SRCS+= usb2_msctest.c +SRCS+= usb2_parse.c +SRCS+= usb2_process.c +SRCS+= usb2_request.c +SRCS+= usb2_sw_transfer.c +SRCS+= usb2_transfer.c +SRCS+= usb2_util.c + +.include + diff --git a/sys/modules/usb2/ethernet/Makefile b/sys/modules/usb2/ethernet/Makefile new file mode 100644 index 000000000000..604aba4a4852 --- /dev/null +++ b/sys/modules/usb2/ethernet/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ethernet + +KMOD= usb2_ethernet +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= usb2_ethernet.c + +.include diff --git a/sys/modules/usb2/ethernet_aue/Makefile b/sys/modules/usb2/ethernet_aue/Makefile new file mode 100644 index 000000000000..c328055f8e1c --- /dev/null +++ b/sys/modules/usb2/ethernet_aue/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ethernet + +KMOD=usb2_ethernet_aue +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_aue2.c + +.include diff --git a/sys/modules/usb2/ethernet_axe/Makefile b/sys/modules/usb2/ethernet_axe/Makefile new file mode 100644 index 000000000000..96590124f4b1 --- /dev/null +++ b/sys/modules/usb2/ethernet_axe/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ethernet + +KMOD=usb2_ethernet_axe +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_axe2.c + +.include diff --git a/sys/modules/usb2/ethernet_cdce/Makefile b/sys/modules/usb2/ethernet_cdce/Makefile new file mode 100644 index 000000000000..329e1a68edf2 --- /dev/null +++ b/sys/modules/usb2/ethernet_cdce/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ethernet + +KMOD=usb2_ethernet_cdce +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_cdce2.c + +.include diff --git a/sys/modules/usb2/ethernet_cue/Makefile b/sys/modules/usb2/ethernet_cue/Makefile new file mode 100644 index 000000000000..96af01b66bce --- /dev/null +++ b/sys/modules/usb2/ethernet_cue/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ethernet + +KMOD=usb2_ethernet_cue +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_cue2.c + +.include diff --git a/sys/modules/usb2/ethernet_dav/Makefile b/sys/modules/usb2/ethernet_dav/Makefile new file mode 100644 index 000000000000..0fd94684dd58 --- /dev/null +++ b/sys/modules/usb2/ethernet_dav/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ethernet + +KMOD=usb2_ethernet_dav +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_udav2.c + +.include diff --git a/sys/modules/usb2/ethernet_kue/Makefile b/sys/modules/usb2/ethernet_kue/Makefile new file mode 100644 index 000000000000..28e253116c50 --- /dev/null +++ b/sys/modules/usb2/ethernet_kue/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ethernet + +KMOD=usb2_ethernet_kue +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_kue2.c + +.include diff --git a/sys/modules/usb2/ethernet_rue/Makefile b/sys/modules/usb2/ethernet_rue/Makefile new file mode 100644 index 000000000000..dcbd2022d016 --- /dev/null +++ b/sys/modules/usb2/ethernet_rue/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ethernet + +KMOD=usb2_ethernet_rue +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_rue2.c + +.include diff --git a/sys/modules/usb2/image/Makefile b/sys/modules/usb2/image/Makefile new file mode 100644 index 000000000000..24b2c80add9c --- /dev/null +++ b/sys/modules/usb2/image/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/image + +KMOD= usb2_image +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h +SRCS+= usb2_image.c + +.include diff --git a/sys/modules/usb2/input/Makefile b/sys/modules/usb2/input/Makefile new file mode 100644 index 000000000000..74a28ab05df2 --- /dev/null +++ b/sys/modules/usb2/input/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/input + +KMOD= usb2_input +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h opt_kbd.h opt_ukbd.h +SRCS+= usb2_input.c + +.include diff --git a/sys/modules/usb2/input_hid/Makefile b/sys/modules/usb2/input_hid/Makefile new file mode 100644 index 000000000000..27f5c5af78f1 --- /dev/null +++ b/sys/modules/usb2/input_hid/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/input + +KMOD=usb2_input_hid +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h opt_kbd.h opt_ukbd.h +SRCS+= uhid2.c + +.include diff --git a/sys/modules/usb2/input_kbd/Makefile b/sys/modules/usb2/input_kbd/Makefile new file mode 100644 index 000000000000..23ec6d8082bf --- /dev/null +++ b/sys/modules/usb2/input_kbd/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/input + +KMOD=usb2_input_kbd +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h opt_kbd.h opt_ukbd.h +SRCS+= ukbd2.c + +.include diff --git a/sys/modules/usb2/input_ms/Makefile b/sys/modules/usb2/input_ms/Makefile new file mode 100644 index 000000000000..a3ff3e9ffda3 --- /dev/null +++ b/sys/modules/usb2/input_ms/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/input + +KMOD=usb2_input_ms +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h opt_kbd.h opt_ukbd.h +SRCS+= ums2.c + +.include diff --git a/sys/modules/usb2/misc/Makefile b/sys/modules/usb2/misc/Makefile new file mode 100644 index 000000000000..71c18ccc88d0 --- /dev/null +++ b/sys/modules/usb2/misc/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/misc + +KMOD= usb2_misc +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_netgraph.h +SRCS+= usb2_misc.c + +.include diff --git a/sys/modules/usb2/misc_dbp/Makefile b/sys/modules/usb2/misc_dbp/Makefile new file mode 100644 index 000000000000..44a0681d5e15 --- /dev/null +++ b/sys/modules/usb2/misc_dbp/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/misc + +KMOD=usb2_misc_dbp +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_netgraph.h +SRCS+= udbp2.c + +.include diff --git a/sys/modules/usb2/misc_fm/Makefile b/sys/modules/usb2/misc_fm/Makefile new file mode 100644 index 000000000000..f5e377d53fd3 --- /dev/null +++ b/sys/modules/usb2/misc_fm/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/misc + +KMOD=usb2_misc_fm +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_netgraph.h +SRCS+= ufm2.c + +.include diff --git a/sys/modules/usb2/ndis/Makefile b/sys/modules/usb2/ndis/Makefile new file mode 100644 index 000000000000..89c87b18274a --- /dev/null +++ b/sys/modules/usb2/ndis/Makefile @@ -0,0 +1,40 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/ndis + +KMOD= usb2_ndis +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h + +SRCS+= usb2_ndis.c +SRCS+= if_ndis_usb2.c + +.include diff --git a/sys/modules/usb2/quirk/Makefile b/sys/modules/usb2/quirk/Makefile new file mode 100644 index 000000000000..aae5dda648d9 --- /dev/null +++ b/sys/modules/usb2/quirk/Makefile @@ -0,0 +1,37 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/quirk + +KMOD= usb2_quirk +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h opt_usb.h opt_bus.h +SRCS+= usb2_quirk.c + +.include diff --git a/sys/modules/usb2/scanner/Makefile b/sys/modules/usb2/scanner/Makefile new file mode 100644 index 000000000000..db82e48bd001 --- /dev/null +++ b/sys/modules/usb2/scanner/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/image + +KMOD=usb2_scanner +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h +SRCS+= uscanner2.c + +.include diff --git a/sys/modules/usb2/serial/Makefile b/sys/modules/usb2/serial/Makefile new file mode 100644 index 000000000000..d37b3d51e47d --- /dev/null +++ b/sys/modules/usb2/serial/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD= usb2_serial +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= usb2_serial.c + +.include diff --git a/sys/modules/usb2/serial_ark/Makefile b/sys/modules/usb2/serial_ark/Makefile new file mode 100644 index 000000000000..e0919c4d732a --- /dev/null +++ b/sys/modules/usb2/serial_ark/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_ark +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= uark2.c + +.include diff --git a/sys/modules/usb2/serial_bsa/Makefile b/sys/modules/usb2/serial_bsa/Makefile new file mode 100644 index 000000000000..782bef1f2cd1 --- /dev/null +++ b/sys/modules/usb2/serial_bsa/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_bsa +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= ubsa2.c + +.include diff --git a/sys/modules/usb2/serial_bser/Makefile b/sys/modules/usb2/serial_bser/Makefile new file mode 100644 index 000000000000..7b2921c757f6 --- /dev/null +++ b/sys/modules/usb2/serial_bser/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_bser +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= ubser2.c + +.include diff --git a/sys/modules/usb2/serial_chcom/Makefile b/sys/modules/usb2/serial_chcom/Makefile new file mode 100644 index 000000000000..726f711f6151 --- /dev/null +++ b/sys/modules/usb2/serial_chcom/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_chcom +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= uchcom2.c + +.include diff --git a/sys/modules/usb2/serial_cycom/Makefile b/sys/modules/usb2/serial_cycom/Makefile new file mode 100644 index 000000000000..74a1865fb977 --- /dev/null +++ b/sys/modules/usb2/serial_cycom/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_cycom +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= ucycom2.c + +.include diff --git a/sys/modules/usb2/serial_foma/Makefile b/sys/modules/usb2/serial_foma/Makefile new file mode 100644 index 000000000000..520abee8001f --- /dev/null +++ b/sys/modules/usb2/serial_foma/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_foma +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= ufoma2.c + +.include diff --git a/sys/modules/usb2/serial_ftdi/Makefile b/sys/modules/usb2/serial_ftdi/Makefile new file mode 100644 index 000000000000..c6735e3c2e90 --- /dev/null +++ b/sys/modules/usb2/serial_ftdi/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_ftdi +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= uftdi2.c + +.include diff --git a/sys/modules/usb2/serial_gensa/Makefile b/sys/modules/usb2/serial_gensa/Makefile new file mode 100644 index 000000000000..6511d860031f --- /dev/null +++ b/sys/modules/usb2/serial_gensa/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_gensa +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= ugensa2.c + +.include diff --git a/sys/modules/usb2/serial_ipaq/Makefile b/sys/modules/usb2/serial_ipaq/Makefile new file mode 100644 index 000000000000..159eb724f356 --- /dev/null +++ b/sys/modules/usb2/serial_ipaq/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_ipaq +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= uipaq2.c + +.include diff --git a/sys/modules/usb2/serial_lpt/Makefile b/sys/modules/usb2/serial_lpt/Makefile new file mode 100644 index 000000000000..2135e9f191b3 --- /dev/null +++ b/sys/modules/usb2/serial_lpt/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_lpt +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= ulpt2.c + +.include diff --git a/sys/modules/usb2/serial_mct/Makefile b/sys/modules/usb2/serial_mct/Makefile new file mode 100644 index 000000000000..76b2f3ed6107 --- /dev/null +++ b/sys/modules/usb2/serial_mct/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_mct +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= umct2.c + +.include diff --git a/sys/modules/usb2/serial_modem/Makefile b/sys/modules/usb2/serial_modem/Makefile new file mode 100644 index 000000000000..f1fbf45be320 --- /dev/null +++ b/sys/modules/usb2/serial_modem/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_modem +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= umodem2.c + +.include diff --git a/sys/modules/usb2/serial_moscom/Makefile b/sys/modules/usb2/serial_moscom/Makefile new file mode 100644 index 000000000000..46bf537a89a0 --- /dev/null +++ b/sys/modules/usb2/serial_moscom/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_moscom +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= umoscom2.c + +.include diff --git a/sys/modules/usb2/serial_plcom/Makefile b/sys/modules/usb2/serial_plcom/Makefile new file mode 100644 index 000000000000..5f4353ab2860 --- /dev/null +++ b/sys/modules/usb2/serial_plcom/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_plcom +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= uplcom2.c + +.include diff --git a/sys/modules/usb2/serial_visor/Makefile b/sys/modules/usb2/serial_visor/Makefile new file mode 100644 index 000000000000..cbda332b7fe8 --- /dev/null +++ b/sys/modules/usb2/serial_visor/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_visor +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= uvisor2.c + +.include diff --git a/sys/modules/usb2/serial_vscom/Makefile b/sys/modules/usb2/serial_vscom/Makefile new file mode 100644 index 000000000000..8c4114ae24e6 --- /dev/null +++ b/sys/modules/usb2/serial_vscom/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/serial + +KMOD=usb2_serial_vscom +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_compat.h +SRCS+= uvscom2.c + +.include diff --git a/sys/modules/usb2/sound/Makefile b/sys/modules/usb2/sound/Makefile new file mode 100644 index 000000000000..0f1bbb99be19 --- /dev/null +++ b/sys/modules/usb2/sound/Makefile @@ -0,0 +1,42 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/sound + +KMOD= usb2_sound +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h feeder_if.h channel_if.h +SRCS+= mixer_if.h + +SRCS+= usb2_sound.c +SRCS+= uaudio2.c +SRCS+= uaudio2_pcm.c + +.include diff --git a/sys/modules/usb2/storage/Makefile b/sys/modules/usb2/storage/Makefile new file mode 100644 index 000000000000..16b657d34797 --- /dev/null +++ b/sys/modules/usb2/storage/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/storage + +KMOD= usb2_storage +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_cam.h opt_ata.h ata_if.h +SRCS+= usb2_storage.c + +.include diff --git a/sys/modules/usb2/storage_ata/Makefile b/sys/modules/usb2/storage_ata/Makefile new file mode 100644 index 000000000000..c4d7b592be65 --- /dev/null +++ b/sys/modules/usb2/storage_ata/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/storage + +KMOD=usb2_storage_ata +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_cam.h opt_ata.h ata_if.h +SRCS+= ata-usb2.c + +.include diff --git a/sys/modules/usb2/storage_fs/Makefile b/sys/modules/usb2/storage_fs/Makefile new file mode 100644 index 000000000000..3392862522d6 --- /dev/null +++ b/sys/modules/usb2/storage_fs/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/storage + +KMOD=usb2_storage_fs +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_cam.h opt_ata.h ata_if.h +SRCS+= ustorage2_fs.c + +.include diff --git a/sys/modules/usb2/storage_mass/Makefile b/sys/modules/usb2/storage_mass/Makefile new file mode 100644 index 000000000000..6ab6613cfd3f --- /dev/null +++ b/sys/modules/usb2/storage_mass/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/storage + +KMOD=usb2_storage_mass +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_cam.h opt_ata.h ata_if.h +SRCS+= umass2.c + +.include diff --git a/sys/modules/usb2/storage_rio/Makefile b/sys/modules/usb2/storage_rio/Makefile new file mode 100644 index 000000000000..74a1e9e95fd8 --- /dev/null +++ b/sys/modules/usb2/storage_rio/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/storage + +KMOD=usb2_storage_rio +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h opt_bus.h opt_cam.h opt_ata.h ata_if.h +SRCS+= urio2.c + +.include diff --git a/sys/modules/usb2/template/Makefile b/sys/modules/usb2/template/Makefile new file mode 100644 index 000000000000..64c8054a3b0d --- /dev/null +++ b/sys/modules/usb2/template/Makefile @@ -0,0 +1,40 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/template + +KMOD= usb2_template +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h opt_usb.h opt_bus.h +SRCS+= usb2_template.c +SRCS+= usb2_template_cdce.c +SRCS+= usb2_template_msc.c +SRCS+= usb2_template_mtp.c + +.include diff --git a/sys/modules/usb2/wlan/Makefile b/sys/modules/usb2/wlan/Makefile new file mode 100644 index 000000000000..482947c08e73 --- /dev/null +++ b/sys/modules/usb2/wlan/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S= ${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/wlan + +KMOD= usb2_wlan +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= usb2_wlan.c + +.include diff --git a/sys/modules/usb2/wlan_ral/Makefile b/sys/modules/usb2/wlan_ral/Makefile new file mode 100644 index 000000000000..bf653e882b66 --- /dev/null +++ b/sys/modules/usb2/wlan_ral/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/wlan + +KMOD=usb2_wlan_ral +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_ural2.c + +.include diff --git a/sys/modules/usb2/wlan_rum/Makefile b/sys/modules/usb2/wlan_rum/Makefile new file mode 100644 index 000000000000..0eed54ba5cd3 --- /dev/null +++ b/sys/modules/usb2/wlan_rum/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/wlan + +KMOD=usb2_wlan_rum +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_rum2.c + +.include diff --git a/sys/modules/usb2/wlan_zyd/Makefile b/sys/modules/usb2/wlan_zyd/Makefile new file mode 100644 index 000000000000..0a231ead2b79 --- /dev/null +++ b/sys/modules/usb2/wlan_zyd/Makefile @@ -0,0 +1,38 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2008 Hans Petter Selasky. 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. +# + +S=${.CURDIR}/../../.. + +.PATH: $S/dev/usb2/wlan + +KMOD=usb2_wlan_zyd +SRCS= +SRCS+= bus_if.h usb2_if.h device_if.h vnode_if.h +SRCS+= opt_usb.h miibus_if.h opt_inet.h opt_bus.h +SRCS+= if_zyd2.c + +.include diff --git a/usr.sbin/usbconfig/Makefile b/usr.sbin/usbconfig/Makefile new file mode 100644 index 000000000000..6356963498db --- /dev/null +++ b/usr.sbin/usbconfig/Makefile @@ -0,0 +1,9 @@ +# +# $FreeBSD$ +# +PROG= usbconfig +MAN= usbconfig.8 +SRCS= usbconfig.c dump.c +LDADD+= -lusb20 + +.include diff --git a/usr.sbin/usbconfig/dump.c b/usr.sbin/usbconfig/dump.c new file mode 100644 index 000000000000..89a47ac3f79e --- /dev/null +++ b/usr.sbin/usbconfig/dump.c @@ -0,0 +1,459 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dump.h" + +#define DUMP0(n,type,field,...) dump_field(pdev, " ", #field, n->field); +#define DUMP1(n,type,field,...) dump_field(pdev, " ", #field, n->field); +#define DUMP2(n,type,field,...) dump_field(pdev, " ", #field, n->field); +#define DUMP3(n,type,field,...) dump_field(pdev, " ", #field, n->field); + +const char * +dump_mode(uint8_t value) +{ + if (value == LIBUSB20_MODE_HOST) + return ("HOST"); + return ("DEVICE"); +} + +const char * +dump_speed(uint8_t value) +{ + ; /* style fix */ + switch (value) { + case LIBUSB20_SPEED_LOW: + return ("LOW (1.5Mbps)"); + case LIBUSB20_SPEED_FULL: + return ("FULL (12Mbps)"); + case LIBUSB20_SPEED_HIGH: + return ("HIGH (480Mbps)"); + case LIBUSB20_SPEED_VARIABLE: + return ("VARIABLE (52-480Mbps)"); + case LIBUSB20_SPEED_SUPER: + return ("SUPER (4.8Gbps)"); + default: + break; + } + return ("unknown"); +} + +const char * +dump_power_mode(uint8_t value) +{ + ; /* style fix */ + switch (value) { + case LIBUSB20_POWER_OFF: + return ("OFF"); + case LIBUSB20_POWER_ON: + return ("ON"); + case LIBUSB20_POWER_SAVE: + return ("SAVE"); + case LIBUSB20_POWER_SUSPEND: + return ("SUSPEND"); + case LIBUSB20_POWER_RESUME: + return ("RESUME"); + default: + return ("UNKNOWN"); + } +} + +static void +dump_field(struct libusb20_device *pdev, const char *plevel, + const char *field, uint32_t value) +{ + struct LIBUSB20_CONTROL_SETUP_DECODED req; + uint16_t lang_id; + uint8_t index; + uint8_t temp_string[256]; + + printf("%s%s = 0x%04x ", plevel, field, value); + + if ((field[0] != 'i') || (field[1] == 'd')) { + printf("\n"); + return; + } + if (value == 0) { + printf(" \n"); + return; + } + LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &req); + + lang_id = 0; + index = 0; + + req.bmRequestType = + LIBUSB20_REQUEST_TYPE_STANDARD | + LIBUSB20_RECIPIENT_DEVICE | + LIBUSB20_ENDPOINT_IN; + req.bRequest = LIBUSB20_REQUEST_GET_DESCRIPTOR; + req.wValue = (256 * LIBUSB20_DT_STRING) | index; + req.wIndex = lang_id; + req.wLength = 4; /* bytes */ + + if (libusb20_dev_request_sync(pdev, &req, + temp_string, NULL, 1000, 0)) { + goto done; + } + lang_id = temp_string[2] | (temp_string[3] << 8); + + printf(" LangId:0x%04x <", lang_id); + + index = value; + + req.wValue = (256 * LIBUSB20_DT_STRING) | index; + req.wIndex = lang_id; + req.wLength = 4; /* bytes */ + + if (libusb20_dev_request_sync(pdev, &req, + temp_string, NULL, 1000, 0)) { + printf("ERROR>\n"); + goto done; + } + req.wValue = (256 * LIBUSB20_DT_STRING) | index; + req.wIndex = lang_id; + req.wLength = temp_string[0]; /* bytes */ + + if (libusb20_dev_request_sync(pdev, &req, + temp_string, NULL, 1000, 0)) { + printf("ERROR>\n"); + goto done; + } + req.wLength /= 2; + + for (index = 1; index != req.wLength; index++) { + if (isprint(temp_string[(2 * index) + 0])) { + printf("%c", temp_string[(2 * index) + 0]); + } else if (isprint(temp_string[(2 * index) + 1])) { + printf("%c", temp_string[(2 * index) + 1]); + } else { + printf("?"); + } + } + printf(">\n"); +done: + return; +} + +static void +dump_extra(struct libusb20_me_struct *str, const char *plevel) +{ + const uint8_t *ptr; + uint8_t x; + + ptr = NULL; + + while ((ptr = libusb20_desc_foreach(str, ptr))) { + printf("\n" "%sAdditional Descriptor\n\n", plevel); + printf("%sbLength = 0x%02x\n", plevel, ptr[0]); + printf("%sbDescriptorType = 0x%02x\n", plevel, ptr[1]); + if (ptr[0] > 1) + printf("%sbDescriptorSubType = 0x%02x\n", + plevel, ptr[2]); + printf("%s RAW dump: ", plevel); + for (x = 0; x != ptr[0]; x++) { + if ((x % 8) == 0) { + printf("\n%s 0x%02x | ", plevel, x); + } + printf("0x%02x%s", ptr[x], + (x != (ptr[0] - 1)) ? ", " : (x % 8) ? "\n" : ""); + } + printf("\n"); + } + return; +} + +static void +dump_endpoint(struct libusb20_device *pdev, + struct libusb20_endpoint *ep) +{ + struct LIBUSB20_ENDPOINT_DESC_DECODED *edesc; + + edesc = &ep->desc; + LIBUSB20_ENDPOINT_DESC(DUMP3, edesc); + dump_extra(&ep->extra, " " " " " "); + return; +} + +static void +dump_iface(struct libusb20_device *pdev, + struct libusb20_interface *iface) +{ + struct LIBUSB20_INTERFACE_DESC_DECODED *idesc; + uint8_t z; + + idesc = &iface->desc; + LIBUSB20_INTERFACE_DESC(DUMP2, idesc); + dump_extra(&iface->extra, " " " " " "); + + for (z = 0; z != iface->num_endpoints; z++) { + printf("\n Endpoint %u\n", z); + dump_endpoint(pdev, iface->endpoints + z); + } + return; +} + +void +dump_device_info(struct libusb20_device *pdev) +{ + printf("%s, cfg=%u md=%s spd=%s pwr=%s\n", + libusb20_dev_get_desc(pdev), + libusb20_dev_get_config_index(pdev), + dump_mode(libusb20_dev_get_mode(pdev)), + dump_speed(libusb20_dev_get_speed(pdev)), + dump_power_mode(libusb20_dev_get_power_mode(pdev))); + return; +} + +void +dump_be_quirk_names(struct libusb20_backend *pbe) +{ + struct libusb20_quirk q; + uint16_t x; + int err; + + memset(&q, 0, sizeof(q)); + + printf("\nDumping list of supported quirks:\n\n"); + + for (x = 0; x != 0xFFFF; x++) { + + err = libusb20_be_get_quirk_name(pbe, x, &q); + if (err) { + if (x == 0) { + printf("No quirk names - maybe the USB quirk " + "module has not been loaded.\n"); + } + break; + } + if (strcmp(q.quirkname, "UQ_NONE")) + printf("%s\n", q.quirkname); + } + printf("\n"); + return; +} + +void +dump_be_dev_quirks(struct libusb20_backend *pbe) +{ + struct libusb20_quirk q; + uint16_t x; + int err; + + memset(&q, 0, sizeof(q)); + + printf("\nDumping current device quirks:\n\n"); + + for (x = 0; x != 0xFFFF; x++) { + + err = libusb20_be_get_dev_quirk(pbe, x, &q); + if (err) { + if (x == 0) { + printf("No device quirks - maybe the USB quirk " + "module has not been loaded.\n"); + } + break; + } + if (strcmp(q.quirkname, "UQ_NONE")) { + printf("VID=0x%04x PID=0x%04x REVLO=0x%04x " + "REVHI=0x%04x QUIRK=%s\n", + q.vid, q.pid, q.bcdDeviceLow, + q.bcdDeviceHigh, q.quirkname); + } + } + printf("\n"); + return; +} + +void +dump_be_access(struct libusb20_backend *pbe) +{ + struct group *gr; + struct passwd *pw; + const char *owner; + const char *group; + uid_t uid; + gid_t gid; + mode_t mode; + + if (libusb20_be_get_owner(pbe, &uid, &gid)) { + err(1, "could not get owner"); + } + if (libusb20_be_get_perm(pbe, &mode)) { + err(1, "could not get permission"); + } + owner = (pw = getpwuid(uid)) ? pw->pw_name : "UNKNOWN"; + group = (gr = getgrgid(gid)) ? gr->gr_name : "UNKNOWN"; + + if (mode || 1) { + printf("Global Access: %s:%s 0%o\n", owner, group, mode); + } else { + printf("Global Access: \n"); + } + return; +} + +void +dump_device_access(struct libusb20_device *pdev, uint8_t iface) +{ + struct group *gr; + struct passwd *pw; + const char *owner; + const char *group; + uid_t uid; + gid_t gid; + mode_t mode; + + if (libusb20_dev_get_owner(pdev, &uid, &gid)) { + err(1, "could not get owner"); + } + if (libusb20_dev_get_perm(pdev, &mode)) { + err(1, "could not get permission"); + } + if (mode) { + owner = (pw = getpwuid(uid)) ? pw->pw_name : "UNKNOWN"; + group = (gr = getgrgid(gid)) ? gr->gr_name : "UNKNOWN"; + + printf(" " "Device Access: %s:%s 0%o\n", owner, group, mode); + + } else { + printf(" " "Device Access: \n"); + } + + if (iface == 0xFF) { + for (iface = 0; iface != 0xFF; iface++) { + if (dump_device_iface_access(pdev, iface)) { + break; + } + } + } else { + if (dump_device_iface_access(pdev, iface)) { + err(1, "could not get interface access info"); + } + } + return; +} + +int +dump_device_iface_access(struct libusb20_device *pdev, uint8_t iface) +{ + struct group *gr; + struct passwd *pw; + const char *owner; + const char *group; + uid_t uid; + gid_t gid; + mode_t mode; + int error; + + if ((error = libusb20_dev_get_iface_owner(pdev, iface, &uid, &gid))) { + return (error); + } + if ((error = libusb20_dev_get_iface_perm(pdev, iface, &mode))) { + return (error); + } + if (mode) { + + owner = (pw = getpwuid(uid)) ? pw->pw_name : "UNKNOWN"; + group = (gr = getgrgid(gid)) ? gr->gr_name : "UNKNOWN"; + + printf(" " "Interface %u Access: %s:%s 0%o\n", iface, owner, group, mode); + } else { + printf(" " "Interface %u Access: \n", iface); + } + + return (0); +} + +void +dump_device_desc(struct libusb20_device *pdev) +{ + struct LIBUSB20_DEVICE_DESC_DECODED *ddesc; + + ddesc = libusb20_dev_get_device_desc(pdev); + LIBUSB20_DEVICE_DESC(DUMP0, ddesc); + return; +} + +void +dump_config(struct libusb20_device *pdev, uint8_t all_cfg) +{ + struct LIBUSB20_CONFIG_DESC_DECODED *cdesc; + struct LIBUSB20_DEVICE_DESC_DECODED *ddesc; + struct libusb20_config *pcfg = NULL; + uint8_t cfg_index; + uint8_t cfg_index_end; + uint8_t x; + uint8_t y; + + ddesc = libusb20_dev_get_device_desc(pdev); + + if (all_cfg) { + cfg_index = 0; + cfg_index_end = ddesc->bNumConfigurations; + } else { + cfg_index = libusb20_dev_get_config_index(pdev); + cfg_index_end = cfg_index + 1; + } + + for (; cfg_index != cfg_index_end; cfg_index++) { + + pcfg = libusb20_dev_alloc_config(pdev, cfg_index); + if (!pcfg) { + continue; + } + printf("\n Configuration index %u\n\n", cfg_index); + cdesc = &(pcfg->desc); + LIBUSB20_CONFIG_DESC(DUMP1, cdesc); + dump_extra(&(pcfg->extra), " " " "); + + for (x = 0; x != pcfg->num_interface; x++) { + printf("\n Interface %u\n", x); + dump_iface(pdev, pcfg->interface + x); + printf("\n"); + for (y = 0; y != (pcfg->interface + x)->num_altsetting; y++) { + printf("\n Interface %u Alt %u\n", x, y + 1); + dump_iface(pdev, + (pcfg->interface + x)->altsetting + y); + printf("\n"); + } + } + printf("\n"); + free(pcfg); + } + return; +} diff --git a/usr.sbin/usbconfig/dump.h b/usr.sbin/usbconfig/dump.h new file mode 100644 index 000000000000..95ef7209d0d0 --- /dev/null +++ b/usr.sbin/usbconfig/dump.h @@ -0,0 +1,37 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +const char *dump_mode(uint8_t value); +const char *dump_speed(uint8_t value); +const char *dump_power_mode(uint8_t value); +void dump_device_info(struct libusb20_device *pdev); +void dump_be_access(struct libusb20_backend *pbe); +void dump_be_quirk_names(struct libusb20_backend *pbe); +void dump_be_dev_quirks(struct libusb20_backend *pbe); +void dump_device_access(struct libusb20_device *pdev, uint8_t iface); +int dump_device_iface_access(struct libusb20_device *pdev, uint8_t iface); +void dump_device_desc(struct libusb20_device *pdev); +void dump_config(struct libusb20_device *pdev, uint8_t all_cfg); diff --git a/usr.sbin/usbconfig/usbconfig.8 b/usr.sbin/usbconfig/usbconfig.8 new file mode 100644 index 000000000000..ffdb4ce7112e --- /dev/null +++ b/usr.sbin/usbconfig/usbconfig.8 @@ -0,0 +1,53 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Hans Petter Selasky. 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. +.\" +.Dd Sep 28, 2008 +.Dt USBCONFIG 8 +.Os +.Sh NAME +.Nm usbconfig +.Nd configure the USB subsystem +.Sh SYNOPSIS +.Nm +.Op Fl u Ar unit +.Op Fl a Ar addr +.Op cmds... +.Sh DESCRIPTION +The +.Nm +utility is used to configure and dump information about the USB subsystem. +.Pp +The options are as follows: +.Bl -tag -width " " +.It Fl u Ar unit +Limit device range to USB devices connected to the given USBUS unit. +.It Fl a Ar addr +Limit device range to the given USB device index. +Should only be used in conjunction with the unit argument. +.It Fl h +Show help and available commands. +.El +.Sh SEE ALSO +.Xr usb2_core 4 diff --git a/usr.sbin/usbconfig/usbconfig.c b/usr.sbin/usbconfig/usbconfig.c new file mode 100644 index 000000000000..eb10b5f59bfc --- /dev/null +++ b/usr.sbin/usbconfig/usbconfig.c @@ -0,0 +1,683 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dump.h" + +struct options { + const char *quirkname; + gid_t gid; + uid_t uid; + mode_t mode; + uint32_t got_any; + uint16_t bus; + uint16_t addr; + uint16_t iface; + uint16_t vid; + uint16_t pid; + uint16_t lo_rev; /* inclusive */ + uint16_t hi_rev; /* inclusive */ + uint8_t config_index; + uint8_t alt_index; + uint8_t got_list:1; + uint8_t got_bus:1; + uint8_t got_addr:1; + uint8_t got_iface:1; + uint8_t got_set_config:1; + uint8_t got_set_alt:1; + uint8_t got_set_owner:1; + uint8_t got_set_perm:1; + uint8_t got_suspend:1; + uint8_t got_resume:1; + uint8_t got_reset:1; + uint8_t got_power_off:1; + uint8_t got_power_save:1; + uint8_t got_power_on:1; + uint8_t got_dump_device_quirks:1; + uint8_t got_dump_quirk_names:1; + uint8_t got_dump_device_desc:1; + uint8_t got_dump_curr_config:1; + uint8_t got_dump_all_config:1; + uint8_t got_dump_info:1; + uint8_t got_dump_access:1; + uint8_t got_remove_device_quirk:1; + uint8_t got_add_device_quirk:1; +}; + +struct token { + const char *name; + uint8_t value; + uint8_t narg; +}; + +enum { + T_UNIT, + T_ADDR, + T_IFACE, + T_SET_CONFIG, + T_SET_ALT, + T_SET_OWNER, + T_SET_PERM, + T_ADD_DEVICE_QUIRK, + T_REMOVE_DEVICE_QUIRK, + T_DUMP_QUIRK_NAMES, + T_DUMP_DEVICE_QUIRKS, + T_DUMP_DEVICE_DESC, + T_DUMP_CURR_CONFIG_DESC, + T_DUMP_ALL_CONFIG_DESC, + T_DUMP_ACCESS, + T_DUMP_INFO, + T_SUSPEND, + T_RESUME, + T_POWER_OFF, + T_POWER_SAVE, + T_POWER_ON, + T_RESET, + T_LIST, +}; + +static struct options options; + +static const struct token token[] = { + {"-u", T_UNIT, 1}, + {"-a", T_ADDR, 1}, + {"-i", T_IFACE, 1}, + {"set_config", T_SET_CONFIG, 1}, + {"set_alt", T_SET_ALT, 1}, + {"set_owner", T_SET_OWNER, 1}, + {"set_perm", T_SET_PERM, 1}, + {"add_dev_quirk_vplh", T_ADD_DEVICE_QUIRK, 5}, + {"remove_dev_quirk_vplh", T_REMOVE_DEVICE_QUIRK, 5}, + {"dump_quirk_names", T_DUMP_QUIRK_NAMES, 0}, + {"dump_device_quirks", T_DUMP_DEVICE_QUIRKS, 0}, + {"dump_device_desc", T_DUMP_DEVICE_DESC, 0}, + {"dump_curr_config_desc", T_DUMP_CURR_CONFIG_DESC, 0}, + {"dump_all_config_desc", T_DUMP_ALL_CONFIG_DESC, 0}, + {"dump_access", T_DUMP_ACCESS, 0}, + {"dump_info", T_DUMP_INFO, 0}, + {"suspend", T_SUSPEND, 0}, + {"resume", T_RESUME, 0}, + {"power_off", T_POWER_OFF, 0}, + {"power_save", T_POWER_SAVE, 0}, + {"power_on", T_POWER_ON, 0}, + {"reset", T_RESET, 0}, + {"list", T_LIST, 0}, +}; + +static void +be_dev_remove_quirk(struct libusb20_backend *pbe, + uint16_t vid, uint16_t pid, uint16_t lorev, uint16_t hirev, + const char *str) +{ + struct libusb20_quirk q; + int err; + + memset(&q, 0, sizeof(q)); + + q.vid = vid; + q.pid = pid; + q.bcdDeviceLow = lorev; + q.bcdDeviceHigh = hirev; + strlcpy(q.quirkname, str, sizeof(q.quirkname)); + + err = libusb20_be_remove_dev_quirk(pbe, &q); + if (err) { + printf("Removing quirk '%s' failed, continuing.\n", str); + } + return; +} + +static void +be_dev_add_quirk(struct libusb20_backend *pbe, + uint16_t vid, uint16_t pid, uint16_t lorev, uint16_t hirev, + const char *str) +{ + struct libusb20_quirk q; + int err; + + memset(&q, 0, sizeof(q)); + + q.vid = vid; + q.pid = pid; + q.bcdDeviceLow = lorev; + q.bcdDeviceHigh = hirev; + strlcpy(q.quirkname, str, sizeof(q.quirkname)); + + err = libusb20_be_add_dev_quirk(pbe, &q); + if (err) { + printf("Adding quirk '%s' failed, continuing.\n", str); + } + return; +} + +static uint8_t +get_token(const char *str, uint8_t narg) +{ + uint8_t n; + + for (n = 0; n != (sizeof(token) / sizeof(token[0])); n++) { + if (strcasecmp(str, token[n].name) == 0) { + if (token[n].narg > narg) { + /* to little arguments */ + break; + } + return (token[n].value); + } + } + return (0 - 1); +} + +static uid_t +num_id(const char *name, const char *type) +{ + uid_t val; + char *ep; + + errno = 0; + val = strtoul(name, &ep, 0); + if (errno) { + err(1, "%s", name); + } + if (*ep != '\0') { + errx(1, "%s: illegal %s name", name, type); + } + return (val); +} + +static gid_t +a_gid(const char *s) +{ + struct group *gr; + + if (*s == '\0') { + /* empty group ID */ + return ((gid_t)-1); + } + return ((gr = getgrnam(s)) ? gr->gr_gid : num_id(s, "group")); +} + +static uid_t +a_uid(const char *s) +{ + struct passwd *pw; + + if (*s == '\0') { + /* empty user ID */ + return ((uid_t)-1); + } + return ((pw = getpwnam(s)) ? pw->pw_uid : num_id(s, "user")); +} + +static mode_t +a_mode(const char *s) +{ + uint16_t val; + char *ep; + + errno = 0; + val = strtoul(s, &ep, 8); + if (errno) { + err(1, "%s", s); + } + if (*ep != '\0') { + errx(1, "illegal permissions: %s", s); + } + return val; +} + +static void +usage(void) +{ + printf("" + "usbconfig - configure the USB subsystem" "\n" + "usage: usbconfig -u -a -i [cmds...]" "\n" + "commands:" "\n" + " set_config " "\n" + " set_alt " "\n" + " set_owner " "\n" + " set_perm " "\n" + " add_dev_quirk_vplh " "\n" + " remove_dev_quirk_vplh " "\n" + " dump_quirk_names" "\n" + " dump_device_quirks" "\n" + " dump_device_desc" "\n" + " dump_curr_config_desc" "\n" + " dump_all_config_desc" "\n" + " dump_access" "\n" + " dump_info" "\n" + " suspend" "\n" + " resume" "\n" + " power_off" "\n" + " power_save" "\n" + " power_on" "\n" + " reset" "\n" + " list" "\n" + ); + exit(1); +} + +static void +reset_options(struct options *opt) +{ + memset(opt, 0, sizeof(*opt)); + return; +} + +static void +flush_command(struct libusb20_backend *pbe, struct options *opt) +{ + struct libusb20_device *pdev = NULL; + uint32_t matches = 0; + uint8_t dump_any; + + /* check for invalid option combinations */ + if ((opt->got_suspend + + opt->got_resume + + opt->got_reset + + opt->got_set_config + + opt->got_set_alt + + opt->got_power_save + + opt->got_power_on + + opt->got_power_off) > 1) { + err(1, "cannot only specify one of 'set_config', " + "'set_alt', 'reset', 'suspend', 'resume', " + "'power_save', 'power_on' and 'power_off' " + "at the same time!"); + } + if (opt->got_dump_access) { + opt->got_any--; + dump_be_access(pbe); + } + if (opt->got_dump_quirk_names) { + opt->got_any--; + dump_be_quirk_names(pbe); + } + if (opt->got_dump_device_quirks) { + opt->got_any--; + dump_be_dev_quirks(pbe); + } + if (opt->got_remove_device_quirk) { + opt->got_any--; + be_dev_remove_quirk(pbe, + opt->vid, opt->pid, opt->lo_rev, opt->hi_rev, opt->quirkname); + } + if (opt->got_add_device_quirk) { + opt->got_any--; + be_dev_add_quirk(pbe, + opt->vid, opt->pid, opt->lo_rev, opt->hi_rev, opt->quirkname); + } + if (opt->got_any == 0) { + /* + * do not scan through all the devices if there are no valid + * options + */ + goto done; + } + while ((pdev = libusb20_be_device_foreach(pbe, pdev))) { + + if (opt->got_bus && + (libusb20_dev_get_bus_number(pdev) != opt->bus)) { + continue; + } + if (opt->got_addr && + (libusb20_dev_get_address(pdev) != opt->addr)) { + continue; + } + matches++; + + /* do owner and permissions first */ + + if (opt->got_bus && opt->got_addr && opt->got_iface) { + if (opt->got_set_owner) { + if (libusb20_dev_set_iface_owner(pdev, + opt->iface, opt->uid, opt->gid)) { + err(1, "setting owner and group failed\n"); + } + } + if (opt->got_set_perm) { + if (libusb20_dev_set_iface_perm(pdev, + opt->iface, opt->mode)) { + err(1, "setting mode failed\n"); + } + } + } else if (opt->got_bus && opt->got_addr) { + if (opt->got_set_owner) { + if (libusb20_dev_set_owner(pdev, + opt->uid, opt->gid)) { + err(1, "setting owner and group failed\n"); + } + } + if (opt->got_set_perm) { + if (libusb20_dev_set_perm(pdev, + opt->mode)) { + err(1, "setting mode failed\n"); + } + } + } else if (opt->got_bus) { + if (opt->got_set_owner) { + if (libusb20_bus_set_owner(pbe, opt->bus, + opt->uid, opt->gid)) { + err(1, "setting owner and group failed\n"); + } + } + if (opt->got_set_perm) { + if (libusb20_bus_set_perm(pbe, opt->bus, + opt->mode)) { + err(1, "setting mode failed\n"); + } + } + } else { + if (opt->got_set_owner) { + if (libusb20_be_set_owner(pbe, + opt->uid, opt->gid)) { + err(1, "setting owner and group failed\n"); + } + } + if (opt->got_set_perm) { + if (libusb20_be_set_perm(pbe, + opt->mode)) { + err(1, "setting mode failed\n"); + } + } + } + + if (libusb20_dev_open(pdev, 0)) { + err(1, "could not open device"); + } + if (opt->got_set_config) { + if (libusb20_dev_set_config_index(pdev, + opt->config_index)) { + err(1, "could not set config index"); + } + } + if (opt->got_set_alt) { + if (libusb20_dev_set_alt_index(pdev, opt->iface, opt->alt_index)) { + err(1, "could not set alternate setting"); + } + } + if (opt->got_reset) { + if (libusb20_dev_reset(pdev)) { + err(1, "could not reset device"); + } + } + if (opt->got_suspend) { + if (libusb20_dev_set_power_mode(pdev, LIBUSB20_POWER_SUSPEND)) { + err(1, "could not set suspend"); + } + } + if (opt->got_resume) { + if (libusb20_dev_set_power_mode(pdev, LIBUSB20_POWER_RESUME)) { + err(1, "could not set resume"); + } + } + if (opt->got_power_off) { + if (libusb20_dev_set_power_mode(pdev, LIBUSB20_POWER_OFF)) { + err(1, "could not set power OFF"); + } + } + if (opt->got_power_save) { + if (libusb20_dev_set_power_mode(pdev, LIBUSB20_POWER_SAVE)) { + err(1, "could not set power SAVE"); + } + } + if (opt->got_power_on) { + if (libusb20_dev_set_power_mode(pdev, LIBUSB20_POWER_ON)) { + err(1, "could not set power ON"); + } + } + dump_any = + (opt->got_dump_device_desc || + opt->got_dump_curr_config || + opt->got_dump_all_config || + opt->got_dump_info || + opt->got_dump_access); + + if (opt->got_list || dump_any) { + dump_device_info(pdev); + } + if (opt->got_dump_access) { + printf("\n"); + dump_device_access(pdev, opt->got_iface ? + opt->iface : 0xFF); + } + if (opt->got_dump_device_desc) { + printf("\n"); + dump_device_desc(pdev); + } + if (opt->got_dump_all_config) { + printf("\n"); + dump_config(pdev, 1); + } else if (opt->got_dump_curr_config) { + printf("\n"); + dump_config(pdev, 0); + } + if (dump_any) { + printf("\n"); + } + if (libusb20_dev_close(pdev)) { + err(1, "could not close device"); + } + } + + if (matches == 0) { + printf("No device match\n"); + } +done: + reset_options(opt); + + return; +} + +int +main(int argc, char **argv) +{ + struct libusb20_backend *pbe; + struct options *opt = &options; + char *cp; + int n; + int t; + + if (argc < 1) { + usage(); + } + pbe = libusb20_be_alloc_default(); + if (pbe == NULL) + err(1, "could not access USB backend\n"); + + for (n = 1; n != argc; n++) { + + /* get number of additional options */ + t = (argc - n - 1); + if (t > 255) + t = 255; + switch (get_token(argv[n], t)) { + case T_ADD_DEVICE_QUIRK: + if (opt->got_add_device_quirk) { + flush_command(pbe, opt); + } + opt->vid = num_id(argv[n + 1], "Vendor ID"); + opt->pid = num_id(argv[n + 2], "Product ID"); + opt->lo_rev = num_id(argv[n + 3], "Low Revision"); + opt->hi_rev = num_id(argv[n + 4], "High Revision"); + opt->quirkname = argv[n + 5]; + n += 5; + + opt->got_add_device_quirk = 1; + opt->got_any++; + break; + + case T_REMOVE_DEVICE_QUIRK: + if (opt->got_remove_device_quirk) { + flush_command(pbe, opt); + } + opt->vid = num_id(argv[n + 1], "Vendor ID"); + opt->pid = num_id(argv[n + 2], "Product ID"); + opt->lo_rev = num_id(argv[n + 3], "Low Revision"); + opt->hi_rev = num_id(argv[n + 4], "High Revision"); + opt->quirkname = argv[n + 5]; + n += 5; + opt->got_remove_device_quirk = 1; + opt->got_any++; + break; + + case T_DUMP_QUIRK_NAMES: + opt->got_dump_quirk_names = 1; + opt->got_any++; + break; + + case T_DUMP_DEVICE_QUIRKS: + opt->got_dump_device_quirks = 1; + opt->got_any++; + break; + + case T_UNIT: + if (opt->got_any) { + /* allow multiple commands on the same line */ + flush_command(pbe, opt); + } + opt->bus = num_id(argv[n + 1], "busnum"); + opt->got_bus = 1; + n++; + break; + case T_ADDR: + opt->addr = num_id(argv[n + 1], "addr"); + opt->got_addr = 1; + n++; + break; + case T_IFACE: + opt->iface = num_id(argv[n + 1], "iface"); + opt->got_iface = 1; + n++; + break; + case T_SET_CONFIG: + opt->config_index = num_id(argv[n + 1], "confindex"); + opt->got_set_config = 1; + opt->got_any++; + n++; + break; + case T_SET_ALT: + opt->alt_index = num_id(argv[n + 1], "confindex"); + opt->got_set_alt = 1; + opt->got_any++; + n++; + break; + case T_SET_OWNER: + cp = argv[n + 1]; + cp = strchr(cp, ':'); + if (cp == NULL) { + err(1, "missing colon in '%s'!", argv[n + 1]); + } + *(cp++) = '\0'; + opt->gid = a_gid(cp); + opt->uid = a_uid(argv[n + 1]); + opt->got_set_owner = 1; + opt->got_any++; + n++; + break; + case T_SET_PERM: + opt->mode = a_mode(argv[n + 1]); + opt->got_set_perm = 1; + opt->got_any++; + n++; + break; + case T_DUMP_DEVICE_DESC: + opt->got_dump_device_desc = 1; + opt->got_any++; + break; + case T_DUMP_CURR_CONFIG_DESC: + opt->got_dump_curr_config = 1; + opt->got_any++; + break; + case T_DUMP_ALL_CONFIG_DESC: + opt->got_dump_all_config = 1; + opt->got_any++; + break; + case T_DUMP_INFO: + opt->got_dump_info = 1; + opt->got_any++; + break; + case T_DUMP_ACCESS: + opt->got_dump_access = 1; + opt->got_any++; + break; + case T_SUSPEND: + opt->got_suspend = 1; + opt->got_any++; + break; + case T_RESUME: + opt->got_resume = 1; + opt->got_any++; + break; + case T_POWER_OFF: + opt->got_power_off = 1; + opt->got_any++; + break; + case T_POWER_SAVE: + opt->got_power_save = 1; + opt->got_any++; + break; + case T_POWER_ON: + opt->got_power_on = 1; + opt->got_any++; + break; + case T_RESET: + opt->got_reset = 1; + opt->got_any++; + break; + case T_LIST: + opt->got_list = 1; + opt->got_any++; + break; + default: + usage(); + break; + } + } + if (opt->got_any) { + /* flush out last command */ + flush_command(pbe, opt); + } else { + /* list all the devices */ + opt->got_list = 1; + opt->got_any++; + flush_command(pbe, opt); + } + /* release data */ + libusb20_be_free(pbe); + + return (0); +}