685 lines
17 KiB
C
685 lines
17 KiB
C
/*
|
|
* (C) Copyright 2003
|
|
* Gerry Hamel, geh@ti.com, Texas Instruments
|
|
*
|
|
* Based on
|
|
* linux/drivers/usbd/usbd.c.c - USB Device Core Layer
|
|
*
|
|
* Copyright (c) 2000, 2001, 2002 Lineo
|
|
* Copyright (c) 2001 Hewlett Packard
|
|
*
|
|
* By:
|
|
* Stuart Lynne <sl@lineo.com>,
|
|
* Tom Rushworth <tbr@lineo.com>,
|
|
* Bruce Balden <balden@lineo.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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 for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
#include <malloc.h>
|
|
#include "usbdcore.h"
|
|
|
|
#define MAX_INTERFACES 2
|
|
|
|
|
|
int maxstrings = 20;
|
|
|
|
/* Global variables ************************************************************************** */
|
|
|
|
struct usb_string_descriptor **usb_strings;
|
|
|
|
int usb_devices;
|
|
|
|
extern struct usb_function_driver ep0_driver;
|
|
|
|
int registered_functions;
|
|
int registered_devices;
|
|
|
|
char *usbd_device_events[] = {
|
|
"DEVICE_UNKNOWN",
|
|
"DEVICE_INIT",
|
|
"DEVICE_CREATE",
|
|
"DEVICE_HUB_CONFIGURED",
|
|
"DEVICE_RESET",
|
|
"DEVICE_ADDRESS_ASSIGNED",
|
|
"DEVICE_CONFIGURED",
|
|
"DEVICE_SET_INTERFACE",
|
|
"DEVICE_SET_FEATURE",
|
|
"DEVICE_CLEAR_FEATURE",
|
|
"DEVICE_DE_CONFIGURED",
|
|
"DEVICE_BUS_INACTIVE",
|
|
"DEVICE_BUS_ACTIVITY",
|
|
"DEVICE_POWER_INTERRUPTION",
|
|
"DEVICE_HUB_RESET",
|
|
"DEVICE_DESTROY",
|
|
"DEVICE_FUNCTION_PRIVATE",
|
|
};
|
|
|
|
char *usbd_device_states[] = {
|
|
"STATE_INIT",
|
|
"STATE_CREATED",
|
|
"STATE_ATTACHED",
|
|
"STATE_POWERED",
|
|
"STATE_DEFAULT",
|
|
"STATE_ADDRESSED",
|
|
"STATE_CONFIGURED",
|
|
"STATE_UNKNOWN",
|
|
};
|
|
|
|
char *usbd_device_requests[] = {
|
|
"GET STATUS", /* 0 */
|
|
"CLEAR FEATURE", /* 1 */
|
|
"RESERVED", /* 2 */
|
|
"SET FEATURE", /* 3 */
|
|
"RESERVED", /* 4 */
|
|
"SET ADDRESS", /* 5 */
|
|
"GET DESCRIPTOR", /* 6 */
|
|
"SET DESCRIPTOR", /* 7 */
|
|
"GET CONFIGURATION", /* 8 */
|
|
"SET CONFIGURATION", /* 9 */
|
|
"GET INTERFACE", /* 10 */
|
|
"SET INTERFACE", /* 11 */
|
|
"SYNC FRAME", /* 12 */
|
|
};
|
|
|
|
char *usbd_device_descriptors[] = {
|
|
"UNKNOWN", /* 0 */
|
|
"DEVICE", /* 1 */
|
|
"CONFIG", /* 2 */
|
|
"STRING", /* 3 */
|
|
"INTERFACE", /* 4 */
|
|
"ENDPOINT", /* 5 */
|
|
"DEVICE QUALIFIER", /* 6 */
|
|
"OTHER SPEED", /* 7 */
|
|
"INTERFACE POWER", /* 8 */
|
|
};
|
|
|
|
char *usbd_device_status[] = {
|
|
"USBD_OPENING",
|
|
"USBD_OK",
|
|
"USBD_SUSPENDED",
|
|
"USBD_CLOSING",
|
|
};
|
|
|
|
|
|
/* Descriptor support functions ************************************************************** */
|
|
|
|
|
|
/**
|
|
* usbd_get_string - find and return a string descriptor
|
|
* @index: string index to return
|
|
*
|
|
* Find an indexed string and return a pointer to a it.
|
|
*/
|
|
struct usb_string_descriptor *usbd_get_string (__u8 index)
|
|
{
|
|
if (index >= maxstrings) {
|
|
return NULL;
|
|
}
|
|
return usb_strings[index];
|
|
}
|
|
|
|
|
|
/* Access to device descriptor functions ***************************************************** */
|
|
|
|
|
|
/* *
|
|
* usbd_device_configuration_instance - find a configuration instance for this device
|
|
* @device:
|
|
* @configuration: index to configuration, 0 - N-1
|
|
*
|
|
* Get specifed device configuration. Index should be bConfigurationValue-1.
|
|
*/
|
|
static struct usb_configuration_instance *usbd_device_configuration_instance (struct usb_device_instance *device,
|
|
unsigned int port, unsigned int configuration)
|
|
{
|
|
/* XXX */
|
|
configuration = configuration ? configuration - 1 : 0;
|
|
|
|
if (configuration >= device->configurations) {
|
|
return NULL;
|
|
}
|
|
return device->configuration_instance_array + configuration;
|
|
}
|
|
|
|
|
|
/* *
|
|
* usbd_device_interface_instance
|
|
* @device:
|
|
* @configuration: index to configuration, 0 - N-1
|
|
* @interface: index to interface
|
|
*
|
|
* Return the specified interface descriptor for the specified device.
|
|
*/
|
|
struct usb_interface_instance *usbd_device_interface_instance (struct usb_device_instance *device, int port, int configuration, int interface)
|
|
{
|
|
struct usb_configuration_instance *configuration_instance;
|
|
|
|
if ((configuration_instance = usbd_device_configuration_instance (device, port, configuration)) == NULL) {
|
|
return NULL;
|
|
}
|
|
if (interface >= configuration_instance->interfaces) {
|
|
return NULL;
|
|
}
|
|
return configuration_instance->interface_instance_array + interface;
|
|
}
|
|
|
|
/* *
|
|
* usbd_device_alternate_descriptor_list
|
|
* @device:
|
|
* @configuration: index to configuration, 0 - N-1
|
|
* @interface: index to interface
|
|
* @alternate: alternate setting
|
|
*
|
|
* Return the specified alternate descriptor for the specified device.
|
|
*/
|
|
struct usb_alternate_instance *usbd_device_alternate_instance (struct usb_device_instance *device, int port, int configuration, int interface, int alternate)
|
|
{
|
|
struct usb_interface_instance *interface_instance;
|
|
|
|
if ((interface_instance = usbd_device_interface_instance (device, port, configuration, interface)) == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (alternate >= interface_instance->alternates) {
|
|
return NULL;
|
|
}
|
|
|
|
return interface_instance->alternates_instance_array + alternate;
|
|
}
|
|
|
|
|
|
/* *
|
|
* usbd_device_device_descriptor
|
|
* @device: which device
|
|
* @configuration: index to configuration, 0 - N-1
|
|
* @port: which port
|
|
*
|
|
* Return the specified configuration descriptor for the specified device.
|
|
*/
|
|
struct usb_device_descriptor *usbd_device_device_descriptor (struct usb_device_instance *device, int port)
|
|
{
|
|
return (device->device_descriptor);
|
|
}
|
|
|
|
|
|
/**
|
|
* usbd_device_configuration_descriptor
|
|
* @device: which device
|
|
* @port: which port
|
|
* @configuration: index to configuration, 0 - N-1
|
|
*
|
|
* Return the specified configuration descriptor for the specified device.
|
|
*/
|
|
struct usb_configuration_descriptor *usbd_device_configuration_descriptor (struct
|
|
usb_device_instance
|
|
*device, int port, int configuration)
|
|
{
|
|
struct usb_configuration_instance *configuration_instance;
|
|
if (!(configuration_instance = usbd_device_configuration_instance (device, port, configuration))) {
|
|
return NULL;
|
|
}
|
|
return (configuration_instance->configuration_descriptor);
|
|
}
|
|
|
|
|
|
/**
|
|
* usbd_device_interface_descriptor
|
|
* @device: which device
|
|
* @port: which port
|
|
* @configuration: index to configuration, 0 - N-1
|
|
* @interface: index to interface
|
|
* @alternate: alternate setting
|
|
*
|
|
* Return the specified interface descriptor for the specified device.
|
|
*/
|
|
struct usb_interface_descriptor *usbd_device_interface_descriptor (struct usb_device_instance
|
|
*device, int port, int configuration, int interface, int alternate)
|
|
{
|
|
struct usb_interface_instance *interface_instance;
|
|
if (!(interface_instance = usbd_device_interface_instance (device, port, configuration, interface))) {
|
|
return NULL;
|
|
}
|
|
if ((alternate < 0) || (alternate >= interface_instance->alternates)) {
|
|
return NULL;
|
|
}
|
|
return (interface_instance->alternates_instance_array[alternate].interface_descriptor);
|
|
}
|
|
|
|
/**
|
|
* usbd_device_endpoint_descriptor_index
|
|
* @device: which device
|
|
* @port: which port
|
|
* @configuration: index to configuration, 0 - N-1
|
|
* @interface: index to interface
|
|
* @alternate: index setting
|
|
* @index: which index
|
|
*
|
|
* Return the specified endpoint descriptor for the specified device.
|
|
*/
|
|
struct usb_endpoint_descriptor *usbd_device_endpoint_descriptor_index (struct usb_device_instance
|
|
*device, int port, int configuration, int interface, int alternate, int index)
|
|
{
|
|
struct usb_alternate_instance *alternate_instance;
|
|
|
|
if (!(alternate_instance = usbd_device_alternate_instance (device, port, configuration, interface, alternate))) {
|
|
return NULL;
|
|
}
|
|
if (index >= alternate_instance->endpoints) {
|
|
return NULL;
|
|
}
|
|
return *(alternate_instance->endpoints_descriptor_array + index);
|
|
}
|
|
|
|
|
|
/**
|
|
* usbd_device_endpoint_transfersize
|
|
* @device: which device
|
|
* @port: which port
|
|
* @configuration: index to configuration, 0 - N-1
|
|
* @interface: index to interface
|
|
* @index: which index
|
|
*
|
|
* Return the specified endpoint transfer size;
|
|
*/
|
|
int usbd_device_endpoint_transfersize (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int index)
|
|
{
|
|
struct usb_alternate_instance *alternate_instance;
|
|
|
|
if (!(alternate_instance = usbd_device_alternate_instance (device, port, configuration, interface, alternate))) {
|
|
return 0;
|
|
}
|
|
if (index >= alternate_instance->endpoints) {
|
|
return 0;
|
|
}
|
|
return *(alternate_instance->endpoint_transfersize_array + index);
|
|
}
|
|
|
|
|
|
/**
|
|
* usbd_device_endpoint_descriptor
|
|
* @device: which device
|
|
* @port: which port
|
|
* @configuration: index to configuration, 0 - N-1
|
|
* @interface: index to interface
|
|
* @alternate: alternate setting
|
|
* @endpoint: which endpoint
|
|
*
|
|
* Return the specified endpoint descriptor for the specified device.
|
|
*/
|
|
struct usb_endpoint_descriptor *usbd_device_endpoint_descriptor (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int endpoint)
|
|
{
|
|
struct usb_endpoint_descriptor *endpoint_descriptor;
|
|
int i;
|
|
|
|
for (i = 0; !(endpoint_descriptor = usbd_device_endpoint_descriptor_index (device, port, configuration, interface, alternate, i)); i++) {
|
|
if (endpoint_descriptor->bEndpointAddress == endpoint) {
|
|
return endpoint_descriptor;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* usbd_endpoint_halted
|
|
* @device: point to struct usb_device_instance
|
|
* @endpoint: endpoint to check
|
|
*
|
|
* Return non-zero if endpoint is halted.
|
|
*/
|
|
int usbd_endpoint_halted (struct usb_device_instance *device, int endpoint)
|
|
{
|
|
return (device->status == USB_STATUS_HALT);
|
|
}
|
|
|
|
|
|
/**
|
|
* usbd_rcv_complete - complete a receive
|
|
* @endpoint:
|
|
* @len:
|
|
* @urb_bad:
|
|
*
|
|
* Called from rcv interrupt to complete.
|
|
*/
|
|
void usbd_rcv_complete(struct usb_endpoint_instance *endpoint, int len, int urb_bad)
|
|
{
|
|
if (endpoint) {
|
|
struct urb *rcv_urb;
|
|
|
|
/*usbdbg("len: %d urb: %p\n", len, endpoint->rcv_urb); */
|
|
|
|
/* if we had an urb then update actual_length, dispatch if neccessary */
|
|
if ((rcv_urb = endpoint->rcv_urb)) {
|
|
|
|
/*usbdbg("actual: %d buffer: %d\n", */
|
|
/*rcv_urb->actual_length, rcv_urb->buffer_length); */
|
|
|
|
/* check the urb is ok, are we adding data less than the packetsize */
|
|
if (!urb_bad && (len <= endpoint->rcv_packetSize)) {
|
|
/*usbdbg("updating actual_length by %d\n",len); */
|
|
|
|
/* increment the received data size */
|
|
rcv_urb->actual_length += len;
|
|
|
|
} else {
|
|
usberr(" RECV_ERROR actual: %d buffer: %d urb_bad: %d\n",
|
|
rcv_urb->actual_length, rcv_urb->buffer_length, urb_bad);
|
|
|
|
rcv_urb->actual_length = 0;
|
|
rcv_urb->status = RECV_ERROR;
|
|
}
|
|
} else {
|
|
usberr("no rcv_urb!");
|
|
}
|
|
} else {
|
|
usberr("no endpoint!");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* usbd_tx_complete - complete a transmit
|
|
* @endpoint:
|
|
* @resetart:
|
|
*
|
|
* Called from tx interrupt to complete.
|
|
*/
|
|
void usbd_tx_complete (struct usb_endpoint_instance *endpoint)
|
|
{
|
|
if (endpoint) {
|
|
struct urb *tx_urb;
|
|
|
|
/* if we have a tx_urb advance or reset, finish if complete */
|
|
if ((tx_urb = endpoint->tx_urb)) {
|
|
int sent = endpoint->last;
|
|
endpoint->sent += sent;
|
|
endpoint->last -= sent;
|
|
|
|
if( (endpoint->tx_urb->actual_length - endpoint->sent) <= 0 ) {
|
|
tx_urb->actual_length = 0;
|
|
endpoint->sent = 0;
|
|
endpoint->last = 0;
|
|
|
|
/* Remove from active, save for re-use */
|
|
urb_detach(tx_urb);
|
|
urb_append(&endpoint->done, tx_urb);
|
|
/*usbdbg("done->next %p, tx_urb %p, done %p", */
|
|
/* endpoint->done.next, tx_urb, &endpoint->done); */
|
|
|
|
endpoint->tx_urb = first_urb_detached(&endpoint->tx);
|
|
if( endpoint->tx_urb ) {
|
|
endpoint->tx_queue--;
|
|
usbdbg("got urb from tx list");
|
|
}
|
|
if( !endpoint->tx_urb ) {
|
|
/*usbdbg("taking urb from done list"); */
|
|
endpoint->tx_urb = first_urb_detached(&endpoint->done);
|
|
}
|
|
if( !endpoint->tx_urb ) {
|
|
usbdbg("allocating new urb for tx_urb");
|
|
endpoint->tx_urb = usbd_alloc_urb(tx_urb->device, endpoint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* URB linked list functions ***************************************************** */
|
|
|
|
/*
|
|
* Initialize an urb_link to be a single element list.
|
|
* If the urb_link is being used as a distinguished list head
|
|
* the list is empty when the head is the only link in the list.
|
|
*/
|
|
void urb_link_init (urb_link * ul)
|
|
{
|
|
if (ul) {
|
|
ul->prev = ul->next = ul;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Detach an urb_link from a list, and set it
|
|
* up as a single element list, so no dangling
|
|
* pointers can be followed, and so it can be
|
|
* joined to another list if so desired.
|
|
*/
|
|
void urb_detach (struct urb *urb)
|
|
{
|
|
if (urb) {
|
|
urb_link *ul = &urb->link;
|
|
ul->next->prev = ul->prev;
|
|
ul->prev->next = ul->next;
|
|
urb_link_init (ul);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return the first urb_link in a list with a distinguished
|
|
* head "hd", or NULL if the list is empty. This will also
|
|
* work as a predicate, returning NULL if empty, and non-NULL
|
|
* otherwise.
|
|
*/
|
|
urb_link *first_urb_link (urb_link * hd)
|
|
{
|
|
urb_link *nx;
|
|
if (NULL != hd && NULL != (nx = hd->next) && nx != hd) {
|
|
/* There is at least one element in the list */
|
|
/* (besides the distinguished head). */
|
|
return (nx);
|
|
}
|
|
/* The list is empty */
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Return the first urb in a list with a distinguished
|
|
* head "hd", or NULL if the list is empty.
|
|
*/
|
|
struct urb *first_urb (urb_link * hd)
|
|
{
|
|
urb_link *nx;
|
|
if (NULL == (nx = first_urb_link (hd))) {
|
|
/* The list is empty */
|
|
return (NULL);
|
|
}
|
|
return (p2surround (struct urb, link, nx));
|
|
}
|
|
|
|
/*
|
|
* Detach and return the first urb in a list with a distinguished
|
|
* head "hd", or NULL if the list is empty.
|
|
*
|
|
*/
|
|
struct urb *first_urb_detached (urb_link * hd)
|
|
{
|
|
struct urb *urb;
|
|
if ((urb = first_urb (hd))) {
|
|
urb_detach (urb);
|
|
}
|
|
return urb;
|
|
}
|
|
|
|
|
|
/*
|
|
* Append an urb_link (or a whole list of
|
|
* urb_links) to the tail of another list
|
|
* of urb_links.
|
|
*/
|
|
void urb_append (urb_link * hd, struct urb *urb)
|
|
{
|
|
if (hd && urb) {
|
|
urb_link *new = &urb->link;
|
|
|
|
/* This allows the new urb to be a list of urbs, */
|
|
/* with new pointing at the first, but the link */
|
|
/* must be initialized. */
|
|
/* Order is important here... */
|
|
urb_link *pul = hd->prev;
|
|
new->prev->next = hd;
|
|
hd->prev = new->prev;
|
|
new->prev = pul;
|
|
pul->next = new;
|
|
}
|
|
}
|
|
|
|
/* URB create/destroy functions ***************************************************** */
|
|
|
|
/**
|
|
* usbd_alloc_urb - allocate an URB appropriate for specified endpoint
|
|
* @device: device instance
|
|
* @endpoint: endpoint
|
|
*
|
|
* Allocate an urb structure. The usb device urb structure is used to
|
|
* contain all data associated with a transfer, including a setup packet for
|
|
* control transfers.
|
|
*
|
|
* NOTE: endpoint_address MUST contain a direction flag.
|
|
*/
|
|
struct urb *usbd_alloc_urb (struct usb_device_instance *device, struct usb_endpoint_instance *endpoint)
|
|
{
|
|
struct urb *urb;
|
|
|
|
if( !(urb = (struct urb*)malloc(sizeof(struct urb))) ) {
|
|
usberr(" F A T A L: malloc(%u) FAILED!!!!", sizeof(struct urb));
|
|
return NULL;
|
|
}
|
|
|
|
/* Fill in known fields */
|
|
memset(urb, 0, sizeof(struct urb));
|
|
urb->endpoint = endpoint;
|
|
urb->device = device;
|
|
urb->buffer = (u8*)urb->buffer_data;
|
|
urb->buffer_length = sizeof(urb->buffer_data);
|
|
|
|
urb_link_init (&urb->link);
|
|
|
|
return urb;
|
|
}
|
|
|
|
/**
|
|
* usbd_dealloc_urb - deallocate an URB and associated buffer
|
|
* @urb: pointer to an urb structure
|
|
*
|
|
* Deallocate an urb structure and associated data.
|
|
*/
|
|
void usbd_dealloc_urb (struct urb *urb)
|
|
{
|
|
if (urb) {
|
|
free (urb);
|
|
}
|
|
}
|
|
|
|
/* Event signaling functions ***************************************************** */
|
|
|
|
/**
|
|
* usbd_device_event - called to respond to various usb events
|
|
* @device: pointer to struct device
|
|
* @event: event to respond to
|
|
*
|
|
* Used by a Bus driver to indicate an event.
|
|
*/
|
|
void usbd_device_event_irq (struct usb_device_instance *device, usb_device_event_t event, int data)
|
|
{
|
|
usb_device_state_t state;
|
|
|
|
if (!device || !device->bus) {
|
|
usberr("(%p,%d) NULL device or device->bus", device, event);
|
|
return;
|
|
}
|
|
|
|
state = device->device_state;
|
|
|
|
usbinfo("%s", usbd_device_events[event]);
|
|
|
|
switch (event) {
|
|
case DEVICE_UNKNOWN:
|
|
break;
|
|
case DEVICE_INIT:
|
|
device->device_state = STATE_INIT;
|
|
break;
|
|
|
|
case DEVICE_CREATE:
|
|
device->device_state = STATE_ATTACHED;
|
|
break;
|
|
|
|
case DEVICE_HUB_CONFIGURED:
|
|
device->device_state = STATE_POWERED;
|
|
break;
|
|
|
|
case DEVICE_RESET:
|
|
device->device_state = STATE_DEFAULT;
|
|
device->address = 0;
|
|
break;
|
|
|
|
case DEVICE_ADDRESS_ASSIGNED:
|
|
device->device_state = STATE_ADDRESSED;
|
|
break;
|
|
|
|
case DEVICE_CONFIGURED:
|
|
device->device_state = STATE_CONFIGURED;
|
|
break;
|
|
|
|
case DEVICE_DE_CONFIGURED:
|
|
device->device_state = STATE_ADDRESSED;
|
|
break;
|
|
|
|
case DEVICE_BUS_INACTIVE:
|
|
if (device->status != USBD_CLOSING) {
|
|
device->status = USBD_SUSPENDED;
|
|
}
|
|
break;
|
|
case DEVICE_BUS_ACTIVITY:
|
|
if (device->status != USBD_CLOSING) {
|
|
device->status = USBD_OK;
|
|
}
|
|
break;
|
|
|
|
case DEVICE_SET_INTERFACE:
|
|
break;
|
|
case DEVICE_SET_FEATURE:
|
|
break;
|
|
case DEVICE_CLEAR_FEATURE:
|
|
break;
|
|
|
|
case DEVICE_POWER_INTERRUPTION:
|
|
device->device_state = STATE_POWERED;
|
|
break;
|
|
case DEVICE_HUB_RESET:
|
|
device->device_state = STATE_ATTACHED;
|
|
break;
|
|
case DEVICE_DESTROY:
|
|
device->device_state = STATE_UNKNOWN;
|
|
break;
|
|
|
|
case DEVICE_FUNCTION_PRIVATE:
|
|
break;
|
|
|
|
default:
|
|
usbdbg("event %d - not handled",event);
|
|
break;
|
|
}
|
|
/*usbdbg("%s event: %d oldstate: %d newstate: %d status: %d address: %d",
|
|
device->name, event, state,
|
|
device->device_state, device->status, device->address); */
|
|
|
|
/* tell the bus interface driver */
|
|
if( device->event ) {
|
|
/* usbdbg("calling device->event"); */
|
|
device->event(device, event, data);
|
|
}
|
|
}
|