/* Copyright 2003 Tematic Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "usbmodhead.h" #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdbool.h> #include <limits.h> #include <stddef.h> #include "bufman.h" #include "Global/RISCOS.h" #include "Global/Services.h" #include <sys/callout.h> #include <sys/ioctl.h> #include "sys/time.h" #include "dev/usb/usb.h" #include "dev/usb/usbdi.h" #include "dev/usb/usbdi_util.h" #include "dev/usb/usbdivar.h" #include "dev/usb/usbhid.h" #include "swis.h" #include "debuglib/debuglib.h" #include "callx/callx.h" #include "oslib/devicefs.h" #ifndef BOOL #define BOOL #endif #include "toolbox.h" #include "usbmouse.h" #include "usbkboard.h" #include "service.h" /*---------------------------------------------------------------------------*/ /* structure definitions */ /*---------------------------------------------------------------------------*/ /* for interfacing with ioctl */ #define FWRITE 0x0002 #define DeviceFSCallDevice_MonitorTX 12 #define DeviceFSCallDevice_MonitorRX 13 #define DeviceFSCallDevice_USBRequest 0x80000000 #define DeviceFSCallDevice_BufferSpace 0x80000002 #define DeviceFSCallDevice_GetHandles 0x80000003 #define DeviceFSCallDevice_GetLocation 0x80000004 #define DeviceFSCallDevice_ClearStall 0x80000005 #define DeviceFSCallDevice_GetConfig 0x80000001 #define DeviceFSCallDevice_SetConfig 0x80000002 #define DeviceFSCallDevice_GetAltInterface 0x80000003 #define DeviceFSCallDevice_SetAltInterface 0x80000004 #define DeviceFSCallDevice_GetNoAlt 0x80000005 #define DeviceFSCallDevice_GetDeviceDesc 0x80000006 #define DeviceFSCallDevice_GetConfigDesc 0x80000007 #define DeviceFSCallDevice_GetInterfaceDesc 0x80000008 #define DeviceFSCallDevice_GetEndpointDesc 0x80000009 #define DeviceFSCallDevice_GetString 0x8000000a struct ugen_softc; #define UAUDIO_NFRAMES 4 struct isoc_buffer { usbd_xfer_handle xfer; uint16_t sizes[UAUDIO_NFRAMES]; uint16_t offsets[UAUDIO_NFRAMES]; int size; }; struct devstream { /* HID needs to know the ugen */ struct ugen_softc* ugen; int ep; int fs_stream; usbd_pipe_handle pipe; usbd_interface_handle iface; char* buf; int buffer; int buffer_id; usbd_xfer_handle xfer; int report; /* hid devices are all hung off the same endpoint, the chain is searched until the one with the correct report is found */ struct devstream* next_hid; int count; int totalcount; /* transfer finishes when count it reaches totalcount */ int timeout; int size; int bandwidth; /* isochronous bandwidth */ int resiude; struct isoc_buffer * isoc; }; struct ugen_softc { USBBASEDEVICE sc_dev; /* base device */ usbd_device_handle sc_udev; devicefs_device* sc_devfs; struct devstream* str[32]; }; /* used for generically matching keyboards and mice, and throwing them off */ struct iface_softc { USBBASEDEVICE sc_dev; usbd_device_handle sc_udev; usbd_interface_handle sc_iface; }; /*---------------------------------------------------------------------------*/ /* variables definitions */ /*---------------------------------------------------------------------------*/ #define E_NoMem "\x00\x90\x81\x00" "NoMem" #define E_NoDevice "\x01\x90\x81\x00" "NoDevice" #define E_NoInterface "\x02\x90\x81\x00" "NoInterface" #define E_NoEndpoint "\x03\x90\x81\x00" "NoEndpoint" #define E_EndpointUsed "\x04\x90\x81\x00" "EndpointUsed" #define E_BadPipe "\x05\x90\x81\x00" "BadPipe" #define E_BadXfer "\x06\x90\x81\x00" "BadXfer" #define E_NoStream "\x07\x90\x81\x00" "NoStream" #define E_BadRequest "\x08\x90\x81\x00" "BadRequest" #define E_NotRootP "\x09\x90\x81\x00" "NotRootP" #define E_XferFailed "\x20\x90\x81\x00" "XferFailed" #define USBDEV_MESSAGES "Resources:$.Resources.USBDriver.USBDevs" typedef struct messages { MessagesFD fd; int handle; bool isopen; const char* filename; } messages; messages mod_messages = { .filename = Module_MessagesFile }; messages usbdev_messages = { .filename = USBDEV_MESSAGES }; _kernel_oserror* uerror (char* e) { return _swix (MessageTrans_ErrorLookup, _INR(0,2), e, &mod_messages.fd, 0); } _kernel_oserror* messages_update (messages* mess) { dprintf (("", "loading message file: %s\n", mess->filename)); int fh, h; _kernel_oserror* e = NULL; e = _swix (OS_Find, _INR(0,1)|_OUT(0), 0x4f, mess->filename, &fh); if (e) return e; /* we must always close the file */ e = _swix (OS_FSControl, _INR(0,1)|_OUT(1), 21, fh, &h); _swix (OS_Find, _INR(0,1), 0, fh); if (e || h == mess->handle) return e; mess->handle = h; if (mess->isopen) { _swix (MessageTrans_CloseFile, _IN(0), &mess->fd); } e = _swix (MessageTrans_OpenFile, _INR(0,2), &mess->fd, mess->filename, 0); if (e) return e; mess->isopen = true; return e; } /* this is defined in usb.c by the macro USB_DECLARE_DRIVER */ extern struct cfattach usb_ca; extern struct cfattach uhub_uhub_ca; /* for debugging */ #ifdef USB_DEBUG extern int usbdebug, uhubdebug; extern int total_sleep; #endif #ifdef DEBUGLIB char* ccodes []= { "NORMAL_COMPLETION", "IN_PROGRESS", "PENDING_REQUESTS", "NOT_STARTED", "INVAL", "NOMEM", "CANCELLED", "BAD_ADDRESS", "IN_USE", "NO_ADDR", "SET_ADDR_FAILED", "NO_POWER", "TOO_DEEP", "IOERROR", "NOT_CONFIGURED", "TIMEOUT", "SHORT_XFER", "STALLED", "INTERRUPTED"}; #endif extern int cold; void* private_word = 0; //int mouseactive = 0; struct sysvar_callback { struct sysvar_callback* next; char com[]; }* sysvar_head = NULL; static int usbbus_no = 1; static int usbdev_no = 1; struct devicelist allbuses = TAILQ_HEAD_INITIALIZER(allbuses); struct devicelist allusbdevs = TAILQ_HEAD_INITIALIZER(allusbdevs); /*---------------------------------------------------------------------------*/ /* static function declarations */ /*---------------------------------------------------------------------------*/ struct device* attach_hub (struct device* parent, void* aux); int detach_hub (struct device* hub); struct device* attach_device (struct device* parent, struct usb_attach_arg* aux, int n); int detach_device (struct device* dev, int); bool sysvar_attach; static int launch_system_variable (struct usb_attach_arg* aux, int unit); extern int ugenioctl(int devt, int cmd, void* addr, int flag, void* p); extern int usbioctl(int devt, u_long cmd, void* data, int flag, void *p); struct device* get_usbdev (int unit); extern char* usbd_get_string (usbd_device_handle, size_t, char*); extern void microtime (struct timeval* tv); extern void triggercbs(void); extern uint32_t clock (void); // avoid header clash with sys/types.h bool announce_attach; static char* service_call (usbd_device_handle dev, int unit, int link); _kernel_oserror* announce_device (_kernel_swi_regs* r, void* pw, void* sc); extern void* resource_files (void); /*---------------------------------------------------------------------------*/ /* functions declarations */ /*---------------------------------------------------------------------------*/ static _kernel_oserror *init_handler(_kernel_swi_regs *r, void *pw, void* h) { /* issue a service call to request any latent HCDs to report themselves */ _swix (OS_ServiceCall, _INR (0, 1), Service_USBDriver_Starting, Service_USBDriver); return NULL; } _kernel_oserror *module_init(const char *cmd_tail, int podule_base, void *pw) { _kernel_oserror* e; private_word = pw; /* set up debugging */ debug_initialise ("USBDriver", "", 0); debug_set_device(DEBUGIT_OUTPUT); debug_set_unbuffered_files (TRUE); debug_set_stamp_debug (TRUE); callx_init (pw); #ifdef STANDALONE e = _swix (ResourceFS_RegisterFiles, _IN (0), resource_files ()); if (e != NULL) return e; #else /* if standalone then this happens in the service call handler */ e = messages_update (&mod_messages); if (e) goto error0; e = messages_update (&usbdev_messages); if (e) goto error1; #endif #ifdef USB_DEBUG usbdebug = atoi(getenv("usbdebug")); uhubdebug = atoi(getenv("uhubdebug")); #endif usbbus_no = 1; usbdev_no = 1; /* do this in a callback so that clients can call our SWIs */ callx_add_callback (init_handler, 0); _swix (OS_Claim, _INR(0,2), PointerV, pointerv_entry, pw); // /* turn on the hourglass until the mouse is active */ // _swix (Hourglass_On, 0); return 0; #ifndef STANDALONE error1: _swix (MessageTrans_CloseFile, _IN(0), &mod_messages); error0: _swix (ResourceFS_DeregisterFiles, _IN (0), resource_files ()); #endif return e; } /*---------------------------------------------------------------------------*/ struct cfattach ugen_ca = { sizeof (struct ugen_softc), NULL, NULL, detach_device, NULL }; _kernel_oserror *module_final(int fatal, int podule, void *pw) { /* issue a service call to request any running HCDs can object */ _swix (OS_ServiceCall, _INR (0, 1), Service_USBDriver_Dying, Service_USBDriver); _swix (OS_Release, _INR(0,2), PointerV, pointerv_entry, pw); _swix (OS_Release, _INR(0,2), KEYV, keyv_entry, pw); /* tell the devices to remove themselves */ struct device* dev; char var[sizeof "DeviceFS$USBnnn$Options"]; TAILQ_FOREACH(dev, &allusbdevs, dv_list) { config_detach (dev, 0 /* number doesn't matter */); } /* get rid of the system variables */ while (_swix (OS_SetVarVal, _INR(0,4), "USB$Device_*", 0, -1, 0, 0) == NULL) { /* do nothing */ } callx_remove_all_callbacks (); callx_remove_all_callafters (); callx_remove_all_calleverys (); _swix (MessageTrans_CloseFile, _IN(0), &mod_messages); _swix (MessageTrans_CloseFile, _IN(0), &usbdev_messages); #ifdef STANDALONE _swix (ResourceFS_DeregisterFiles, _IN (0), resource_files ()); #endif return NULL; } /*---------------------------------------------------------------------------*/ void module_services(int service_number, _kernel_swi_regs *r, void *pw) { dprintf(("", "Service call reason %d\n", service_number)); switch (service_number) { case Service_ResourceFSStarted: messages_update (&mod_messages); messages_update (&usbdev_messages); break; case Service_ResourceFSStarting: #ifdef STANDALONE (*(void (*) (void*,void*,void*,void*)) r->r[2]) (resource_files (), 0, 0, (void*) r->r[3]); #endif break; case Service_USB: switch (r->r[0]) { case Service_USBDriver_Connected: { struct device* dev; USBServiceAnswer* serv; // to link to existing list USBServiceAnswer* lastserv = (USBServiceAnswer*) r->r[2]; while (lastserv != NULL && lastserv->link != NULL) { lastserv = lastserv->link; } TAILQ_FOREACH(dev, &allusbdevs, dv_list) { struct ugen_softc * udev = (struct ugen_softc*) dev; serv = (USBServiceAnswer*) service_call (udev->sc_udev, dev->dv_unit, 1); serv->link = NULL; if (lastserv == NULL) { r->r[2] = (int) serv; } else { lastserv->link = serv; } lastserv = serv; } } break; case Service_USBDriver_Attach: break; case Service_USBDriver_Detach: break; } } } /*---------------------------------------------------------------------------*/ _kernel_oserror* command_enumerate_devices (void) { struct usb_device_info di; puts("No. Bus Dev Class Description"); for (int i = 1; i < usbdev_no; ++i) { struct device* dev = get_usbdev (i); if (dev != NULL) { usbd_device_handle udev = ((struct ugen_softc*) dev)->sc_udev; usbd_fill_deviceinfo (udev, &di, 1); /* in case the vendor string is null, don't print a leading space */ printf ("%3d %3d %3d %2X/%2X %s%s%s\n", i, di.udi_bus, di.udi_addr, di.udi_class, di.udi_subclass, di.udi_vendor, *di.udi_vendor ? " " : "", di.udi_product); } } return 0; } /*---------------------------------------------------------------------------*/ _kernel_oserror* command_enumerate_buses () { struct usb_device_stats stats; printf ("Transfers (%d buses):\n", usbbus_no-1); // 012345678901234567890123456789012345678901234567890 puts ("Bus Control Isochronous Bulk Interrupt"); for (int i = 1; i < usbbus_no; ++i) { if (get_softc (i << 16) == NULL || usbioctl (i << 16, USB_DEVICESTATS, &stats, 0, 0)) { continue; } printf ("%3d %9lu %9lu %9lu %9lu\n", i, stats.uds_requests[UE_CONTROL], stats.uds_requests[UE_ISOCHRONOUS], stats.uds_requests[UE_BULK], stats.uds_requests[UE_INTERRUPT]); } return 0; } /*---------------------------------------------------------------------------*/ _kernel_oserror* command_discover () { #ifdef USB_DEBUG total_sleep = 0; #endif for (int i = 1; i < usbbus_no; ++i) { if (get_softc (i << 16) != NULL) usbioctl (i << 16, USB_DISCOVER, 0, 0, 0); } #ifdef USB_DEBUG dprintf (("", "total sleep = %d\n", total_sleep)); #endif return NULL; } /*---------------------------------------------------------------------------*/ _kernel_oserror* command_reset (int n) { struct device* dev = get_usbdev (n); if (dev == NULL) { return uerror (E_NoDevice); } /* pretend it's a ugen to get the udev */ usbd_device_handle udev = ((struct ugen_softc*) dev)->sc_udev; struct usbd_port * port = udev->powersrc; usbd_device_handle parent = port->parent; if (parent == NULL) { return uerror (E_NotRootP); } usb_disconnect_port (port, (device_ptr_t) parent->hub); usbd_clear_port_feature(parent, port->portno, UHF_PORT_POWER); usbd_delay_ms(parent, USB_PORT_RESET_DELAY); usbd_set_port_feature(parent, port->portno, UHF_PORT_POWER); return NULL; } /*---------------------------------------------------------------------------*/ _kernel_oserror* command_dev_info (int n) { char string[127]; struct device* dev = get_usbdev (n); if (dev == NULL) { return uerror (E_NoDevice); } /* pretend it's a ugen to get the udev */ usbd_device_handle udev = ((struct ugen_softc*) dev)->sc_udev; usb_device_descriptor_t * ddesc = &udev->ddesc; printf ("USB release : %04X\n", UGETW(ddesc->bcdUSB)); printf ("Device class : %02X\n", ddesc->bDeviceClass); printf ("Device subclass : %02X\n", ddesc->bDeviceSubClass); printf ("Device protocol : %02X\n", ddesc->bDeviceProtocol); printf ("Max packet size : %02X\n", ddesc->bMaxPacketSize); printf ("Vendor ID : %04X\n", UGETW(ddesc->idVendor)); printf ("Product ID : %04X\n", UGETW(ddesc->idProduct)); printf ("Device ID : %04X\n", UGETW(ddesc->bcdDevice)); printf ("Manufacturer : '%s'\n", usbd_get_string (udev, ddesc->iManufacturer, string)? string: ""); printf ("Product : '%s'\n", usbd_get_string (udev, ddesc->iProduct, string)? string: ""); printf ("Serial number : '%s'\n", usbd_get_string (udev, ddesc->iSerialNumber, string)? string: ""); printf ("# of configs : %d\n", ddesc->bNumConfigurations); return NULL; } /*---------------------------------------------------------------------------*/ _kernel_oserror* command_conf_info (int n) { char string[127]; struct device* dev = get_usbdev (n); if (dev == NULL) { return uerror (E_NoDevice); } usbd_device_handle udev = ((struct ugen_softc*) dev)->sc_udev; usb_config_descriptor_t * cdesc = udev->cdesc; printf ("Current config : %d\n\n", udev->config); if (udev->config == 0 || cdesc == NULL) { return NULL; } printf ("# of interfaces : %d\n", cdesc->bNumInterface); printf ("Config value : %d\n", cdesc->bConfigurationValue); printf ("Name : '%s'\n", usbd_get_string (udev, cdesc->iConfiguration, string)? string: ""); printf ("Attributes : "); int f = 0; if (cdesc->bmAttributes & UC_BUS_POWERED) { printf ("Bus powered\n"); f = 1; } if (cdesc->bmAttributes & UC_SELF_POWERED) { printf ("%sSelf powered\n", f?" ": ""); f = 1; } if (cdesc->bmAttributes & UC_REMOTE_WAKEUP) { printf ("%sRemote Wakeup\n", f?" ": ""); f = 1; } if (cdesc->bmAttributes == 0) puts (""); printf ("Maximum power : %dmA\n", cdesc->bMaxPower * UC_POWER_FACTOR); char* ptr = (char*) cdesc, *ptr_end = ptr + UGETW(cdesc->wTotalLength); ptr += cdesc->bLength; while (ptr < ptr_end) { switch (ptr[1]) { case UDESC_INTERFACE: { usb_interface_descriptor_t * d = (usb_interface_descriptor_t *) ptr; printf ("\nInterface %d.%d class %d.%d:%d '%s'\n", d->bInterfaceNumber, d->bAlternateSetting, d->bInterfaceClass, d->bInterfaceSubClass, d->bInterfaceProtocol, usbd_get_string (udev, d->iInterface, string)? string: ""); break; } case UDESC_ENDPOINT: { usb_endpoint_descriptor_t * d = (usb_endpoint_descriptor_t *) ptr; printf ("%2d %s ", UE_GET_ADDR(d->bEndpointAddress), UE_GET_DIR(d->bEndpointAddress) == UE_DIR_IN? "IN ":"OUT"); switch (d->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: printf ("Control "); break; case UE_ISOCHRONOUS: printf ("Isochronous "); switch (UE_GET_ISO_TYPE(d->bmAttributes)) { case UE_ISO_ASYNC: printf ("asynchronous "); break; case UE_ISO_ADAPT: printf ("adaptive "); break; case UE_ISO_SYNC: printf ("synchronous "); break; } break; case UE_BULK: printf ("Bulk "); break; case UE_INTERRUPT: printf ("Interrupt "); break; } printf ("%d bytes %d frames\n", UGETW(d->wMaxPacketSize), d->bInterval); break; } case UDESC_HID: { usb_hid_descriptor_t * d = (usb_hid_descriptor_t *) ptr; ddumpbuf("", d, d->bLength, 0); ddumpbuf("", d, sizeof *d, 0); printf ("HID%X descriptors, Country %X\n", UGETW(d->bcdHID), d->bCountryCode); #ifdef __riscos /* RISC OS can't handle packed structures, so we only cope with one report */ { printf (" Type %X, length %d\n", d->bHIDDescriptorType, UGETW (d->wDescriptorLength)); } #else for (int i = 0; i < d->bNumDescriptors; ++i) { printf (" Type %X, length %d\n", d->descrs[i].bDescriptorType, UGETW (d->descrs[i].wDescriptorLength)); } #endif break; } } ptr += *ptr; } return NULL; } /*---------------------------------------------------------------------------*/ static _kernel_oserror* command_set_config (int device, int config) { struct device* dev = get_usbdev (device); if (dev == NULL) { return uerror (E_NoDevice); } usbd_device_handle udev = ((struct ugen_softc*) dev)->sc_udev; usbd_set_config_no(udev, config, 0); return NULL; } /*---------------------------------------------------------------------------*/ static _kernel_oserror* command_set_interface (int device, int ifcn, int alt) { struct device* dev = get_usbdev (device); if (dev == NULL) { return uerror (E_NoDevice); } usbd_device_handle udev = ((struct ugen_softc*) dev)->sc_udev; usbd_interface_handle ifc; int err = usbd_device2interface_handle (udev, ifcn, &ifc); if (err) { return uerror (E_NoInterface); } usbd_set_interface(ifc, alt); return NULL; } /*---------------------------------------------------------------------------*/ int tsleep (void* ident, int priority, const char* wmesg, int timo, int noblock); _kernel_oserror *module_commands(const char *arg_string, int argc, int cmd_no, void *pw) { switch (cmd_no) { #ifdef USB_DEBUG case CMD_USBDebug: { char* ptr; usbdebug = (int) strtoul (arg_string, &ptr, 0); if (ptr) uhubdebug = (int) strtoul (ptr, &ptr, 0); } break; #endif case CMD_USBDevices: return command_enumerate_devices (); case CMD_USBBuses: return command_enumerate_buses (); case CMD_USBDevInfo: return command_dev_info (atoi (arg_string)); case CMD_USBConfInfo: return command_conf_info (atoi (arg_string)); case CMD_USBSetConfig: { int d, c; char* p; d = (int) strtoul (arg_string, &p, 10); c = (int) strtoul (p, &p, 10); return command_set_config (d, c); } break; case CMD_USBSetInterface: { int b, c, d; char* p; b = (int) strtoul (arg_string, &p, 10); c = (int) strtoul (p, &p, 10); d = (int) strtoul (p, &p, 10); return command_set_interface (b, c, d); } break; #ifdef USB_DEBUG case CMD_USBDiscover: return command_discover (); #endif case CMD_USBReset: return command_reset ((int) strtoul (arg_string, 0, 10)); } return 0; } /*---------------------------------------------------------------------------*/ static device_ptr_t register_bus (device_ptr_t bus) { device_ptr_t softc; /* initialise device structure */ softc = calloc (usb_ca.ca_devsize, 1); if (softc == NULL) return NULL; TAILQ_INSERT_TAIL (&allbuses, softc, dv_list); dprintf (("", "adding bus %p\n", bus)); /* abuse the device structure a bit */ bus->dv_unit = softc->dv_unit = (usbbus_no++); /* set the flag to make it explore immediately */ softc->dv_cfdata = &(struct cfdata) { .cf_flags = 1 };; strncpy (softc->dv_xname, "USBDriver"Module_VersionString, sizeof softc->dv_xname - 1)[sizeof softc->dv_xname - 1] = '\0'; #ifdef USB_DEBUG total_sleep = 0; #endif (*usb_ca.ca_attach)(0, softc, bus); #ifdef USB_DEBUG dprintf (("", "total sleep = %d\n", total_sleep)); #endif return softc; } static void deregister_bus (device_ptr_t bus) { dprintf (("", "removing bus %p\n", bus)); (*usb_ca.ca_detach)(bus, 0); dprintf (("", "finished removing bus %p\n", bus)); TAILQ_REMOVE (&allbuses, (device_ptr_t) bus, dv_list); free (bus); } _kernel_oserror *module_swis(int swi_offset, _kernel_swi_regs *r, void *pw) { switch (swi_offset) { // case USBDriver_Register: // break; // case USBDriver_DeRegister: // break; case USBDriver_RegisterBus - USBDriver_00: r->r[0] = (int) register_bus ((device_ptr_t) r->r[0]); break; case USBDriver_DeRegisterBus - USBDriver_00: deregister_bus ((device_ptr_t) r->r[0]); break; case USBDriver_InsertTransfer - USBDriver_00: r->r[0] = usb_insert_transfer ((usbd_xfer_handle) r->r[0]); break; case USBDriver_TransferComplete - USBDriver_00: usb_transfer_complete ((usbd_xfer_handle) r->r[0]); break; case USBDriver_ScheduleSoftInterrupt - USBDriver_00: if (r->r[0] > usbbus_no) usb_schedsoftintr ((struct usbd_bus*) r->r[0]); else { struct device* bus = get_softc (r->r[0] << 16); if (bus != NULL) /* discustingly hacky */ usb_schedsoftintr (*(void**) (bus + 1)); } break; default: return error_BAD_SWI; } return 0; } /*---------------------------------------------------------------------------*/ struct device* get_softc (int unit) { struct device* dev; TAILQ_FOREACH(dev, &allbuses, dv_list) { if (dev->dv_unit == ((unit >> 16) & 0xff)) return dev; } dprintf (("", "couldn't find unit %x\n", unit)); return NULL; } /*---------------------------------------------------------------------------*/ struct device* get_usbdev (int unit) { struct device* dev; TAILQ_FOREACH(dev, &allusbdevs, dv_list) { if (dev->dv_unit == unit) { return dev; } } dprintf (("", "couldn't find unit %x\n", unit)); return NULL; } /*---------------------------------------------------------------------------*/ struct device* riscos_usb_attach ( struct device* parent, void* aux ) { struct device* ret; /* reset these variables to false upon requesting attachment of a device */ if (((struct usb_attach_arg*) aux)->configno == UHUB_UNK_CONFIGURATION) { sysvar_attach = false; announce_attach = false; } typedef device_ptr_t pf (device_ptr_t, void*); pf* funcs[] = { attach_hub, attach_mouse, attach_keyboard, NULL }; for (pf** f = funcs; *f != NULL; ++f) { if ((ret = (*f) (parent, aux)) != NULL) { // if (!mouseactive) // { // mouseactive = 1; // _swix (Hourglass_Off, 0); // } return ret; } } ret = attach_device (parent, aux, usbdev_no); if (ret != NULL) { ret->dv_unit = usbdev_no++; dprintf (("", "first = %p, last = %p\n", allusbdevs.tqh_first, allusbdevs.tqh_last)); TAILQ_INSERT_TAIL (&allusbdevs, ret, dv_list); /* now execute any * commands queued */ struct sysvar_callback * sc = sysvar_head; _kernel_oserror* e; while (sc) { dprintf (("", "executing: %s\n", sc->com)); // if (_kernel_oscli (sc->com)) // dprintf (("", "error: %s\n", _kernel_last_oserror ()->errmess)); if (NULL != (e = _swix (OS_CLI, _IN(0), sc->com))) dprintf (("", "error: %s\n", e->errmess)); sc = sysvar_head->next; free (sysvar_head); sysvar_head = sc; } } return ret; } /*---------------------------------------------------------------------------*/ /* dummy - we don't do attachement like this */ void* (config_found) (struct device* dev, void* h, int (*f) (void*, const char*)) { (void) f; (void) dev; (void) h; return (void*) 1; } /*---------------------------------------------------------------------------*/ int config_detach (struct device* dev, int n) { dprintf (("", "config detach %p, %d, type %d\n", dev, n, (int) dev->dv_cfdata)); /* catch case of config_detach called from config_found above */ if (dev == (void*) 1) { return 0; } /* only remove if a generic device or hub, others match as generic as well */ switch ((int) (dev->dv_cfdata)) { case 1: case 2: if (dev->dv_list.tqe_prev == NULL) dprintf (("", "not removing %p\n", dev)); else { dprintf (("", "removing %p (prev=%p, next=%p)\n", dev, dev->dv_list.tqe_prev, dev->dv_list.tqe_next)); TAILQ_REMOVE (&allusbdevs, dev, dv_list); /* memory is free'd at the detach point later */ } /* remove any system variables attached to this device and since we don't reuse numbers nuke the devicefs options variable too */ char var[sizeof "DeviceFS$USBnnn$Options"]; /* and "USB$Device_*USBnnn" */ sprintf (var, "USB$Device_*USB%d", dev->dv_unit); while (_swix (OS_SetVarVal, _INR(0,4), var, 0, -1, 0, 0) == NULL) { /* do nothing */ } sprintf (var, "DeviceFS$USB%d$Options", dev->dv_unit); while (_swix (OS_SetVarVal, _INR(0,4), var, 0, -1, 0, 0) == NULL) { /* do nothing */ } } switch ((int) (dev->dv_cfdata)) { case 1: return detach_hub (dev); case 2: return detach_device (dev, n); case 3: return detach_mouse (dev); case 4: return detach_keyboard (dev); } // free (dev); return 0; } /*---------------------------------------------------------------------------*/ struct device* attach_hub (struct device* parent, void* aux) { struct device* softc; struct usb_attach_arg* uaa = aux; /* don't match generic */ if (uaa->usegeneric) return NULL; dprintf (("", "Trying match on usb hub\n")); /* First see if we match */ if ((*uhub_uhub_ca.ca_match) (0, 0, aux) == UMATCH_NONE) { dprintf (("", "Failed to match\n")); return NULL; } /* If so, allocate memory for the device and attach ourselves. */ softc = malloc (uhub_uhub_ca.ca_devsize); if (softc == 0) { dprintf (("", "Couldn't allocate memory for hub device\n")); return NULL; } memset (softc, 0, uhub_uhub_ca.ca_devsize); strncpy (softc->dv_xname, "USBHub"Module_VersionString, sizeof softc->dv_xname - 1)[sizeof softc->dv_xname - 1] = '\0'; softc->dv_cfdata = (void*) 1; // hub (*uhub_uhub_ca.ca_attach) (parent, softc, aux); dprintf (("", "Matched hub\n")); return softc; } /*---------------------------------------------------------------------------*/ int detach_hub (struct device* hub) { (*uhub_uhub_ca.ca_detach) (hub, 0); free (hub); return 0; } /*---------------------------------------------------------------------------*/ struct dev_struct { devicefs_device dev; int null; char name[32]; }; #ifdef OBSOLETE_SERVICE_CALLS /* returns non-zero if not handed */ char* service_call (usbd_device_handle dev, int unit, int link) { char name[3 + 11 + 1]; int nifs = dev->cdesc->bNumInterface; int epn, iface; for (epn = 0, iface = 0; iface < nifs; ++iface) { epn += dev->ifaces[iface].idesc->bNumEndpoints; } sprintf (name, "USB%d", unit); size_t size = (link? sizeof (USBServiceCall*): 0) + sizeof (USBServiceCall) + epn * sizeof (USBDevFSEndpoint) + epn * sizeof (USBDevFSEndpoint*) + nifs * sizeof (usb_interface_descriptor_t) + nifs * sizeof (usb_interface_descriptor_t*) + 20; char* real_serv = malloc (size); USBServiceCall* serv = (USBServiceCall*) (link? real_serv + 4: real_serv); char* srv = (char*) serv; if (serv == NULL) { return 0; } memset (serv, 0, size); USBDevFSEndpoint* ep = (USBDevFSEndpoint*) (srv + sizeof (USBServiceCall)); USBDevFSEndpoint** eps = (USBDevFSEndpoint**) (srv + sizeof (USBServiceCall) + epn * sizeof (USBDevFSEndpoint)); for (int i = 0; i < epn; ++i) eps[i] = ep + i; usb_interface_descriptor_t* idesc = (usb_interface_descriptor_t*) (srv + sizeof (USBServiceCall) + epn * sizeof (USBDevFSEndpoint) + epn * sizeof (USBDevFSEndpoint*)); usb_interface_descriptor_t** idescs = (usb_interface_descriptor_t**) (srv + sizeof (USBServiceCall) + epn * sizeof (USBDevFSEndpoint) + epn * sizeof (USBDevFSEndpoint*) + nifs * sizeof (usb_interface_descriptor_t)); for (int i = 0; i < epn; ++i) idescs[i] = idesc + i; serv->dev.address = dev->address; serv->dev.port_status = dev->powersrc->status; serv->dev.dev = dev->ddesc; /* only the device pointer and the name need filling in here - the rest are zero */ serv->dev.ep_default.dev = &serv->dev; strncpy (serv->dev.ep_default.device_name, name, 19)[19] = '\0'; serv->dev.config = dev->cdesc; serv->dev.eps = eps; strncpy(serv->dev.name, name, 19)[19] = '\0'; serv->ep = ep; serv->epd = NULL; // not filling this in serv->ifc = NULL; // not filling this in serv->neps = epn; serv->nifs = nifs; serv->hostaddr = dev->powersrc->parent? dev->powersrc->parent->address: 0; serv->hostport = dev->powersrc->portno; serv->bus = dev->bus->bdev.dv_unit; serv->dev.ifcs = idescs; for (epn = 0, iface = 0; iface < nifs; ++iface) { int ifcepn = dev->ifaces[iface].idesc->bNumEndpoints; dprintf(("", "iface %d, num endpoints = %d, at %p\n", iface, ifcepn, idescs[iface])); memcpy (idescs[iface], dev->ifaces[iface].idesc, USB_INTERFACE_DESCRIPTOR_SIZE); struct usbd_interface * ifc = &dev->ifaces[iface]; for (int n = 0; n < ifcepn; ++n, ++epn) { ep[epn].dev = &serv->dev; ep[epn].ifc = ifc->index; ep[epn].altifc = ifc->altindex; ep[epn].ep = ifc->endpoints[n].edesc; strncpy(ep[epn].device_name, name, 19)[19] = '\0'; } } dprintf (("", "%s\n", name)); // ddumpbuf ("", serv, size, (int) serv); return real_serv; } #else /* returns non-zero if not handed */ char* service_call (usbd_device_handle dev, int unit, int link) { size_t size = (link? sizeof (USBServiceCall*): 0) + sizeof (USBServiceCall) + UGETW(dev->cdesc->wTotalLength); char* real_serv = malloc (size); USBServiceCall* serv = (USBServiceCall*) (link? real_serv + 4: real_serv); if (serv == NULL) { return 0; } memset (real_serv, 0, size); serv->sclen = size - (link? sizeof (USBServiceCall*): 0); serv->descoff = offsetof (USBServiceCall, ddesc); snprintf (serv->devname, sizeof serv->devname, "USB%d", unit); serv->bus = dev->bus->bdev.dv_unit; serv->devaddr = dev->address; serv->hostaddr = dev->powersrc->parent? dev->powersrc->parent->address: 0; serv->hostport = dev->powersrc->portno; serv->speed = dev->speed; memcpy ((void *)&serv->ddesc, (void *)&dev->ddesc, sizeof serv->ddesc); memcpy ((char*)(serv + 1) - 2, (void *)dev->cdesc, UGETW(dev->cdesc->wTotalLength)); return real_serv; } #endif /*---------------------------------------------------------------------------*/ #define USBALIAS "Alias$@USBDevice_" static int launch_system_variable (struct usb_attach_arg* aux, int unit) { usbd_interface_handle ifc = NULL; int class = aux->iface? aux->iface->idesc->bInterfaceClass: aux->device->ddesc.bDeviceClass; int subclass = aux->iface? aux->iface->idesc->bInterfaceSubClass: aux->device->ddesc.bDeviceSubClass; int protocol = aux->iface? aux->iface->idesc->bInterfaceProtocol: aux->device->ddesc.bDeviceProtocol; int vendor = aux->vendor; int product = aux->product; int config = aux->configno; int interface = aux->ifaceno; int release = aux->release; const char* alias = USBALIAS; int len = 0; char* name = NULL; char str[sizeof "Alias$@USBDevice_LL_SS_TT_VVVV_PPPP_CC_II_RRRR_USBnnn"]; /* always set a usb$device variable */ sprintf (str, "USB$Device_%02X_%02X_%02X_%04X_%04X_%02d_%02d_%04X_USB%d", class, subclass, protocol, vendor, product, config, interface, release, unit); if (aux->ifaceno != UHUB_UNK_INTERFACE) { usbd_device2interface_handle (aux->device, aux->ifaceno, &ifc); } char val[13]; if (ifc) { sprintf (val, "%d %d %d", unit, aux->ifaceno, ifc->altindex); } else { sprintf (val, "%d", unit); } _kernel_setenv (str, val); if (sysvar_attach) return 0; for (int i = 0; i < 6; ++i) { if (aux->ifaceno == UHUB_UNK_INTERFACE) { switch (i) { case 0: sprintf (str, "%s*_*_*_%04X_%04X___%04X_*", alias, vendor, product, release); break; case 1: sprintf (str, "%s*_*_*_%04X_%04X___*_*", alias, vendor, product); break; case 2: sprintf (str, "%s%02X_%02X_%02X_%04X_*___*_*", alias, class, subclass, protocol, vendor); break; case 3: sprintf (str, "%s%02X_%02X_*_%04X_*___*_*", alias, class, subclass, vendor); break; case 4: sprintf (str, "%s%02X_%02X_%02X_*_*___*_*", alias, class, subclass, protocol); break; case 5: sprintf (str, "%s%02X_%02X_*_*_*___*_*", alias, class, subclass); break; } } else { switch (i) { case 0: sprintf (str, "%s*_*_*_%04X_%04X_%02d_%02d_%04X_*", alias, vendor, product, config, interface, release); break; case 1: sprintf (str, "%s*_*_*_%04X_%04X_%02d_%02d_*_*", alias, vendor, product, interface, release); break; case 2: sprintf (str, "%s%02X_%02X_%02X_%04X_*_*_*_*_*", alias, class, subclass, protocol, vendor); break; case 3: sprintf (str, "%s%02X_%02X_*_%04X_*_*_*_*_*", alias, class, subclass, vendor); break; case 4: sprintf (str, "%s%02X_%02X_%02X_*_*_*_*_*_*", alias, class, subclass, protocol); break; case 5: sprintf (str, "%s%02X_%02X_*_*_*_*_*_*_*", alias, class, subclass); break; } } _kernel_swi_regs r = {{ (int) str, 0, -1, 0, 0 }}; _kernel_oserror* e = _kernel_swi (OS_ReadVarVal, &r, &r); len = r.r[2]; name = (char*) r.r[3]; if (len) goto match; } /* no match */ return 0; match: if (aux->ifaceno == UHUB_UNK_INTERFACE) sysvar_attach = true; dprintf (("", "Found match for %s\n", name)); struct sysvar_callback* sc = malloc (sizeof *sc + ~len + 12 /*unit*/ + 12/*ifc*/); sprintf (sc->com, "%.*s %d %d", ~len, name + sizeof "Alias$" - 1, unit, aux->ifaceno); sc->next = sysvar_head; sysvar_head = sc; return 0; } /*---------------------------------------------------------------------------*/ _kernel_oserror* announce_device (_kernel_swi_regs* r, void* pw, void* sc) { /* make sure we only announce once per device */ if (announce_attach) return NULL; announce_attach = true; struct ugen_softc* softc = sc; char* serv = service_call (softc->sc_udev, softc->sc_dev.dv_unit, 0); if (serv == NULL) return NULL; dprintf (("", "Send USBDriver_Attach service call\n")); _swix (OS_ServiceCall, _INR(0,2), Service_USBDriver_Attach, Service_USBDriver, serv); free (serv); return NULL; } /*---------------------------------------------------------------------------*/ struct device* attach_device (struct device* parent, struct usb_attach_arg* aux, int no) { _kernel_oserror* e = NULL; struct ugen_softc * softc; launch_system_variable (aux, no); /* only latch onto generic device */ if (aux->usegeneric == 0) return NULL; /* If so, allocate memory for the device and attach ourselves. */ softc = calloc (sizeof *softc, 1); if (softc == 0) { dprintf (("", "Couldn't allocate memory for device\n")); return NULL; } softc->sc_dev.dv_cfdata = (void*) 2; // device softc->sc_udev = ((struct usb_attach_arg*)aux)->device; struct dev_struct * dev = calloc (sizeof *dev, 1); sprintf (dev->name, "USB%d", no); strncpy (softc->sc_dev.dv_xname, dev->name, sizeof softc->sc_dev.dv_xname - 1) [sizeof softc->sc_dev.dv_xname - 1] = '\0'; dev->dev.name_offset = dev->name - (char*) dev; dev->dev.flags = 3; dev->dev.tx_flags = 0x8; dev->dev.tx_buffer_size = 8192; dev->dev.rx_flags = 0x8; dev->dev.rx_buffer_size = 1024; e = _swix (DeviceFS_Register, _INR (0, 7) | _OUT(0), 6, dev, driver_entry, softc, private_word, "endpoint/Ninterface/Nalternate/Nreport/Ncontrol,isochronous,bulk,interrupt/Susbtimeout/Nsize/N", #ifdef DEVICEFSISBROKEN INT_MAX, // should be -1, but that doesn't seem to work INT_MAX, #else -1, -1, #endif &softc->sc_devfs); if (e != NULL) { dprintf (("", "failed to register: %s\n", e->errmess)); free (dev); free (softc); return NULL; } dprintf (("", "registered driver %p\n", softc->sc_devfs)); callx_add_callback (announce_device, softc); return (struct device*) softc; } /*---------------------------------------------------------------------------*/ int detach_device (struct device* dev, int d) { struct ugen_softc * udev = (struct ugen_softc*) dev; #ifdef OBSOLETE_SERVICE_CALLS /* inform the world that the device has gone */ char* serv = service_call (udev->sc_udev, udev->sc_dev.dv_unit, 0); int nothandled; _swix (OS_ServiceCall, _INR(0,2)|_OUT(1), Service_USBDriver_Detach, Service_USBDriver, serv, ¬handled); free (serv); #endif _swix (DeviceFS_Deregister, _IN(0), udev->sc_devfs); dprintf (("", "deregistered driver %p\n", udev->sc_devfs)); free (udev); return 0; } /*---------------------------------------------------------------------------*/ void uhub_activate (void) {} /*---------------------------------------------------------------------------*/ extern void usb_discover (void*); _kernel_oserror* discover_callback (_kernel_swi_regs* r, void* pw, void* sc) { struct usbd_bus* bus = sc; struct device* dev; TAILQ_FOREACH(dev, &allbuses, dv_list) { /* XXX this is dodgy, because in the case where the OHCIDriver module has removed its memory, 'bus' us no longer a valid pointer, and could either cause an abort or accidentally contain a valid usbctl */ if (dev == (struct device*) bus->usbctl) goto valid; } dprintf (("", "bus has been removed\n")); return NULL; valid: // _swix (Hourglass_On, 0); do { // _swix (Hourglass_LEDs, _INR(0,1), 1, 0); usb_discover (bus->usbctl); // _swix (Hourglass_LEDs, _INR(0,1), 2, 0); } while (--bus->callbacks); #ifdef USB_DEBUG dprintf (("", "finished callbacks, total sleep = %d\n", total_sleep)); #else dprintf (("", "finished callbacks\n")); #endif // _swix (Hourglass_Off, 0); return NULL; } /*---------------------------------------------------------------------------*/ void usb_needs_explore_callback (void* h) { struct usbd_bus* bus = h; #ifdef USB_DEBUG total_sleep = 0; #endif if (bus->callbacks++ == 0) { dprintf (("", "Adding explore callback\n")); callx_add_callback (discover_callback, h); } else dprintf (("", "deferring callback - %d callbacks queued\n", bus->callbacks)); } /*---------------------------------------------------------------------------*/ void bufrem (void* dma, void* priv_id, int size) { if (priv_id == 0) { dprintf (("", "Stream has been closed\n")); return; } _kernel_swi_regs r; r.r[0] = BM_ExamineBlock; r.r[1] = (int) priv_id; r.r[2] = (int) dma; r.r[3] = size; CallBufMan (&r); } /*---------------------------------------------------------------------------*/ void bufins (void* dma, void* x) { usbd_xfer_handle xfer = x; struct devstream* str = xfer->priv; int actlen = xfer->actlen; if (str->fs_stream == 0) { dprintf (("", "Stream has been closed\n")); return; } /* if we have a report setting, then search for the appropriate stream */ if (str->report != 0xdeaddead) { /* buffer insert is always going to be an IN endpoint, so we always need to add 16 */ int index = UE_GET_ADDR(str->ep) + 16; str = str->ugen->str[index]; while (str->report != *(char*) dma) { str = str->next_hid; if (str == NULL) { dprintf (("", "*** run out of chained hids")); return; } } dma = ((char*) dma) + 1; actlen--; } str->count += actlen; // dprintf (("", "inserting %d bytes, total %d\n", // actlen, str->count)); _kernel_swi_regs r; r.r[0] = BM_InsertBlock; r.r[1] = (int) str->buffer_id; r.r[2] = (int) dma; r.r[3] = actlen; CallBufMan (&r); } void usbd_devinfo_vp(usbd_device_handle dev, char* v, char* p, int usedev) { _kernel_oserror* e = NULL; usb_device_descriptor_t *udd = &dev->ddesc; char *vendor = 0, *product = 0; if (dev == NULL) { v[0] = p[0] = '\0'; return; } dprintf (("", "Looking up v = %x, p = %x\n", UGETW(udd->idVendor), UGETW(udd->idProduct))); if (usedev) { vendor = usbd_get_string(dev, udd->iManufacturer, v); product = usbd_get_string(dev, udd->iProduct, p); } if (vendor == NULL) { char str[10]; int len; sprintf (str, "V%04X", UGETW(udd->idVendor)); e = _swix (MessageTrans_Lookup, _INR(0,3)|_OUTR(2,3), &usbdev_messages.fd, str, v, 255, &vendor, &len); dprintf (("", "lookup '%s' returned '%s' e = %s\n", str, vendor? vendor: "(null)", e? e->errmess: "NULL")); if (!e) v[len] = '\0'; } if (product == NULL) { char str[10]; int len; sprintf (str, "P%04X%04X", UGETW(udd->idVendor), UGETW(udd->idProduct)); e = _swix (MessageTrans_Lookup, _INR(0,3)|_OUTR(2,3), &usbdev_messages.fd, str, p, 255, &product, &len); dprintf (("", "lookup '%s' returned '%s' e = %s\n", str, product? product: "(null)", e? e->errmess: "NULL")); if (!e) p[len] = '\0'; } if (vendor == NULL) sprintf(v, "Vendor ID %04X", UGETW(udd->idVendor)); if (product == NULL) sprintf(p, "Product ID %04X", UGETW(udd->idProduct)); } void softintr_schedule (void* p) { *(void**)p = (void*) softintr_entry; } int softintr (_kernel_swi_regs* r, void* pw) { static volatile int reentry = 0; // dprintf (("", "entering soft interrupt %d\n", reentry)); _kernel_irqs_off (); if (reentry++ == 0) while (reentry > 0) { reentry--; // dprintf (("", "reentry now %d\n", reentry)); _kernel_irqs_on (); struct usbd_bus* sc = (struct usbd_bus*) r->r[0]; sc->methods->soft_intr (sc); _kernel_irqs_off (); } _kernel_irqs_on (); // dprintf (("", "leaving soft interrupt %d\n", reentry)); return 0; } /*--------------------------------------------------------------------------*/ /* devicefs interface */ typedef struct device_valid { uint32_t endpoint; uint32_t interface; uint32_t alternate; uint32_t report; uint32_t ep_type; uint32_t timeout; uint32_t size; } device_valid; static void find_interface_and_endpoint ( usbd_device_handle dev, uint32_t endpoint, uint32_t iface, uint32_t type, uint32_t dir, int* iface_return, int* endpoint_return ) { int i; usb_interface_descriptor_t * d; usb_endpoint_descriptor_t * b; if (type == 0xdeaddead) { type = UE_BULK; } dprintf (("", "looking for %x, iface %x, type %x, dir %x\n", endpoint, iface, type, dir)); usb_config_descriptor_t * cdesc = dev->cdesc; char* ptr = (char*) cdesc, *ptr_end = ptr + UGETW(cdesc->wTotalLength); ptr += cdesc->bLength; while (ptr < ptr_end) { switch (ptr[1]) { case UDESC_INTERFACE: d = (usb_interface_descriptor_t*) ptr; i = d->bInterfaceNumber; dprintf (("", "interface %d\n", i)); break; case UDESC_ENDPOINT: b = (usb_endpoint_descriptor_t *) ptr; dprintf (("", "endpoint %x\n", b->bEndpointAddress)); dprintf (("", "attributes %x\n", UE_GET_XFERTYPE (b->bmAttributes))); /* if we've found the requested endpoint, return the interface */ if (UE_GET_ADDR(b->bEndpointAddress) == endpoint && ((dir & 1) << 7) != UE_GET_DIR(b->bEndpointAddress)) { *iface_return = i; *endpoint_return = b->bEndpointAddress; return; } /* if no endpoint was requested, see if the type matches (if given) and the direction and the interface (if given) */ else if ( (iface == i || iface == 0xdeaddead) && (endpoint == 0xdeaddead) && (type == UE_GET_XFERTYPE (b->bmAttributes)) && /* bit is set for TX, whereas USB is set for IN */ (((dir & 1) << 7) != UE_GET_DIR(b->bEndpointAddress)) ) { *iface_return = i; *endpoint_return = b->bEndpointAddress; return; } break; } ptr += ptr[0]; } dprintf (("", "couldn't find an endpoint\n")); *iface_return = (int) 0xdeaddead; *endpoint_return = (int) 0xdeaddead; return; } /*---------------------------------------------------------------------------*/ static _kernel_oserror* device_initialise ( int devicefs_handle, uint32_t flags, device_valid * valid, struct ugen_softc * ugen, struct devstream ** stream_handle ) { int ep; int err; int iface; int index = -1; _kernel_oserror* e = NULL; dprintf (("", "device_initialise: flags = %x, fs_stream = %x\n", flags, devicefs_handle)); struct devstream* str = malloc (sizeof *str); if (str == NULL) { return uerror (E_NoMem); } memset (str, 0, sizeof *str); str->size = valid->size; str->ugen = ugen; /* flags bit one is the transmit bit, if set we're open for TX, i.e. an OUT endpoint */ if (valid->endpoint != 0xdeaddead) { ep = valid->endpoint + ((flags & 1)? 0: 1 << 7); } /* XXX we should validate the report at this point */ str->report = valid->report; /* usb timeout, used for transfers */ str->timeout = valid->timeout; /* force an interface alternate */ if (valid->alternate != 0xdeaddead) usbd_set_interface(str->iface, valid->alternate); /* see if an interface was specified, if not then look for the interface with the endpoint specified */ find_interface_and_endpoint ( ugen->sc_udev, valid->endpoint, valid->interface, valid->ep_type, flags, &iface, &ep ); dprintf (("", "found interface %x, endpoint %x\n", iface, ep)); if (iface == 0xdeaddead) { e = uerror (E_NoEndpoint); goto error; } err = usbd_device2interface_handle (ugen->sc_udev, iface, &str->iface); if (err) { e = uerror (E_NoInterface); goto error; } str->ep = ep; /* remove an internally connected device to this interface */ for (int n = 0; ugen->sc_udev->subdevs[n]; ++n) { struct iface_softc* ifc; /* throw off mice or keyboards */ switch ((int) (ugen->sc_udev->subdevs[n]->dv_cfdata)) { case 3: case 4: ifc = (struct iface_softc*) ugen->sc_udev->subdevs[n]; if (ifc->sc_iface->index == iface) { dprintf (("", "throwing off subdevice %d\n", n)); config_detach (ugen->sc_udev->subdevs[n], 0); /* compact the list */ do { ugen->sc_udev->subdevs[n] = ugen->sc_udev->subdevs[n + 1]; n++; } while (ugen->sc_udev->subdevs[n] != 0); goto detach_done; } break; } } detach_done: index = UE_GET_ADDR(ep) + (UE_GET_DIR(ep)? 16: 0); /* if the stream is already used, it might have been left open, we're opening a HID report descriptor, or it's a mistake */ if (ugen->str[index] != NULL && ugen->str[index]->fs_stream != 0) { if (valid->report == 0xdeaddead) { dprintf (("", "Endpoint in use, fs_stream = %x\n", ugen->str[index]->fs_stream)); e = uerror (E_EndpointUsed); goto error; } /* it's a HID report, so link to the chain, and don't bother opening the pipe - we need to remember to unlink properly when we close */ str->next_hid = ugen->str[index]; ugen->str[index] = str; str->fs_stream = devicefs_handle; *stream_handle = str; return NULL; } else if (ugen->str[index] != NULL) { /* it is a previously used endpoint left open */ str->pipe = ugen->str[index]->pipe; str->xfer = ugen->str[index]->xfer; } if (str->pipe == NULL) err = usbd_open_pipe(str->iface, ep, 0, &str->pipe); if (err) { dprintf (("", "Couldn't open pipe, err = %d", err)); str->pipe = NULL; e = uerror (E_BadPipe); goto error; } if (str->xfer == NULL) { if ((str->pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) { str->isoc = calloc (sizeof *str->isoc, 2); str->isoc[0].xfer = usbd_alloc_xfer (ugen->sc_udev); str->isoc[1].xfer = usbd_alloc_xfer (ugen->sc_udev); str->xfer = str->isoc[0].xfer; } else { str->xfer = usbd_alloc_xfer (ugen->sc_udev); } } if (str->xfer == NULL) { e = uerror (E_BadXfer); goto error; } if ((str->pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT) { str->pipe->repeat = 1; } str->fs_stream = devicefs_handle; *stream_handle = str; ugen->str[index] = str; return NULL; error: if (str->pipe) usbd_close_pipe (str->pipe); if (str->xfer) usbd_free_xfer (str->xfer); if (str->isoc != NULL) { if (str->isoc[1].xfer) { usbd_free_xfer (str->isoc[1].xfer); } free (str->isoc); } free (str); return e; } /*---------------------------------------------------------------------------*/ void start_write (struct devstream* str); void start_read (struct devstream* str); static void write_cb(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct devstream* str = priv; if (status != USBD_NORMAL_COMPLETION) { dprintf (("", "Bad completion code: %d (%s), %d bytes read\n", status, ccodes[status], xfer->actlen)); return; } if (str->fs_stream == 0) { dprintf (("", "Stream has been closed\n")); return; } #ifndef DMA_FROM_BUFFER _kernel_swi_regs r; r.r[0] = BM_NextFilledBlock; r.r[1] = (int) str->buffer_id; r.r[3] = xfer->actlen; CallBufMan(&r); #endif start_write (str); } /*---------------------------------------------------------------------------*/ void start_write (struct devstream* str) { static int reentry = 0; if (str->xfer == NULL) { dprintf (("", "Non-head HID stream\n")); return; } if (str->xfer->status != USBD_NORMAL_COMPLETION) { dprintf (("", "Can't start, status = %d (%s)\n", str->xfer->status, ccodes[str->xfer->status])); return; } if (reentry != 0) { dprintf (("", "!! start_write reentered !!\n")); return; } reentry = 1; int s = _kernel_irqs_disabled (); _kernel_irqs_off (); #ifdef DMA_FROM_BUFFER _kernel_swi_regs r; r.r[0] = BM_NextFilledBlock; r.r[1] = str->buffer_id; r.r[3] = str->xfer->actlen; CallBufMan(&r); #else _kernel_swi_regs r; r.r[0] = BM_UsedSpace; r.r[1] = str->buffer_id; CallBufMan(&r); #endif if (r.r[2] != 0) { #ifdef DMA_FROM_BUFFER usbd_setup_xfer(str->xfer, str->pipe, str, (void*) r.r[2], r.r[3], USBD_NO_COPY, 500, write_cb); #else usbd_setup_xfer( str->xfer, str->pipe, str, (void*) str->buffer_id, r.r[2], 0, str->timeout, write_cb); str->xfer->rqflags |= URQ_RISCOS_BUF; dprintf (("", "transferring %d bytes\n", r.r[2])); #endif str->xfer->status = usbd_transfer (str->xfer); /* this can either return in progress, or normal completion (if the pipe wasn't already running) */ if (str->xfer->status != USBD_IN_PROGRESS && str->xfer->status != USBD_NORMAL_COMPLETION) { dprintf (("", "Failed to insert transfer, status = %d (%s)\n", str->xfer->status, ccodes[str->xfer->status])); } } else { str->xfer->actlen = 0; dprintf (("", "no more data to write\n")); } if (s == 0) _kernel_irqs_on (); reentry = 0; } /*---------------------------------------------------------------------------*/ static void read_cb(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct devstream* str = priv; if (status != USBD_NORMAL_COMPLETION) { dprintf (("", "Bad completion code: %d (%s) %d bytes read\n", status, ccodes[status], xfer->actlen)); return; } if (str->fs_stream == 0) { dprintf (("", "Stream has been closed\n")); return; } /* only start another transfer if we haven't finished the transfer and this is not a interrupt endpoint (the BSD framework restarts repeating transfers) */ if (str->count != str->totalcount && !xfer->pipe->repeat) { dprintf (("", "Starting read from callback\n")); start_read (str); } } /*---------------------------------------------------------------------------*/ void fill_isoc_xfer (struct devstream* str, int maxpacket) { int size; str->size = maxpacket; str->xfer->actlen = 0; usbd_setup_isoc_xfer( str->xfer, str->pipe, str, (u_int16_t*) &str->size, /* sizes */ 1, /* n frames */ USBD_SHORT_XFER_OK, read_cb); str->xfer->buffer = (void*) str->buffer_id; str->xfer->length = maxpacket; } void start_read (struct devstream* str) { static int reentry = 0; if (str->xfer == NULL) { dprintf (("", "Non-head HID stream\n")); return; } if (str->xfer->status != USBD_NORMAL_COMPLETION) { dprintf (("", "Can't start, status = %d (%s)\n", str->xfer->status, ccodes[str->xfer->status])); return; } if (str->count >= str->totalcount && !str->xfer->pipe->repeat) { dprintf (("", "Finished reading %d bytes\n", str->totalcount)); return; } if (reentry != 0) { dprintf (("", "!! start_read reentered !!\n")); return; } int s = _kernel_irqs_disabled (); _kernel_irqs_off (); reentry = 1; #ifdef DMA_FROM_BUFFER // XXX this was copied from write probably wrong for read _kernel_swi_regs r; r.r[0] = BM_NextFilledBlock; r.r[1] = str->buffer_id; r.r[3] = str->xfer->actlen; CallBufMan(&r); #else _kernel_swi_regs r; r.r[0] = BM_FreeSpace; r.r[1] = str->buffer_id; CallBufMan(&r); #endif int actlen = r.r[2]; int maxpacket = UGETW(str->pipe->endpoint->edesc->wMaxPacketSize); if (actlen > maxpacket) { switch (str->pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: case UE_ISOCHRONOUS: /* if an interrupt endpoint, ask for exactly max packet */ actlen = maxpacket; break; case UE_BULK: /* only ask for multiple of maxpacket if bulk */ actlen -= actlen % maxpacket; /* truncate at length requested */ if (str->totalcount && actlen > str->totalcount - str->count) actlen = str->totalcount - str->count; if (actlen <= 0) goto end; break; } #ifdef DMA_FROM_BUFFER /* this almost definitely doesn't work any more */ usbd_setup_xfer( str->xfer, str->pipe, str, (void*) r.r[2], r.r[3], USBD_NO_COPY|USBD_SHORT_XFER_OK, 500, read_cb); #else switch (str->pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE) { case UE_BULK: case UE_INTERRUPT: usbd_setup_xfer( str->xfer, str->pipe, str, (void*) str->buffer_id, actlen, USBD_SHORT_XFER_OK, str->timeout, read_cb); dprintf (("", "starting (bulk/interrupt) transfer of %d bytes\n", actlen)); break; case UE_ISOCHRONOUS: fill_isoc_xfer (str, maxpacket); dprintf (("", "starting (isoc) transfer of %d bytes\n", str->size)); break; } str->xfer->rqflags |= URQ_RISCOS_BUF; #endif str->xfer->status = usbd_transfer (str->xfer); /* this can either return in progress, or normal completion (if the pipe wasn't already running) */ if (str->xfer->status != USBD_IN_PROGRESS && str->xfer->status != USBD_NORMAL_COMPLETION) { dprintf (("", "Failed to insert transfer, status = %d (%s)\n", str->xfer->status, ccodes[str->xfer->status])); } } end: reentry = 0; if (s == 0) _kernel_irqs_on (); } /*---------------------------------------------------------------------------*/ void terminate_stream (struct ugen_softc* ugen, struct devstream * str, int kill) { static int reentry = 0; dprintf (("", "terminate stream %p, ep %x, kill = %d, reentry = %d\n", str, str->ep, kill, reentry)); if (reentry) return; reentry = 1; if (str == NULL) { reentry = 0; return; } int index = UE_GET_ADDR(str->ep) + (UE_GET_DIR(str->ep)? 16: 0); /* don't remove stream twice! */ if (ugen->str[index] == NULL) { reentry = 0; return; } /* in case we were a multiply linked HID reporter, don't close the pipe */ if (ugen->str[index]->next_hid == NULL) { if (kill) { int status; /* only close these when the device is removed */ status = usbd_abort_pipe(str->pipe); dprintf (("", "status: %s\n", ccodes[status])); status = usbd_close_pipe(str->pipe); dprintf (("", "status: %s\n", ccodes[status])); usbd_free_buffer (str->xfer); status = usbd_free_xfer(str->xfer); dprintf (("", "status: %s\n", ccodes[status])); ugen->str[index] = NULL; } else { /* normally just null these entries, so the same endpoint gets used next time and toggling carries on */ str->fs_stream = 0; str->buffer = 0; str->buffer_id = 0; str->report = 0; dprintf (("", "fs_stream now 0\n")); /* return early so we don't free the stream */ reentry = 0; return; } } else { /* otherwise, make sure we don't lose the handles, and keep the chain going. */ dprintf (("", "delinking HID stream, ep=%x\n", str->ep)); struct devstream* s = ugen->str[index]; if (s != str) { while (s->next_hid != str) { s = s->next_hid; if (s == NULL) { dprintf (("", "*** couldn't find HID stream")); reentry = 0; return; } } s->next_hid = str->next_hid; } else { /* special case replacing the head */ s = ugen->str[index] = str->next_hid; } if (str->pipe) { s->pipe = str->pipe; s->xfer = str->xfer; } } free (str); reentry = 0; } /*---------------------------------------------------------------------------*/ _kernel_oserror* create_buffer ( struct devstream* str, uint32_t *flags, size_t *size, uint32_t *handle, size_t *thresh ) { _kernel_oserror* e = NULL; char* p; /* if we are a multiple HID device, this just return */ if (str->xfer == NULL) { dprintf (("", "multiple HID device: str->xfer == NULL\n")); return NULL; } if (str->size != 0xdeaddead) *size = str->size; if ((str->pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) { p = usbd_alloc_buffer (str->isoc[0].xfer, *size / 2); if (p == 0) return uerror (E_NoMem); p = usbd_alloc_buffer (str->isoc[1].xfer, *size / 2); if (p == 0) return uerror (E_NoMem); } else { p = usbd_alloc_buffer (str->xfer, *size); if (p == 0) return uerror (E_NoMem); #ifdef DMA_FROM_BUFFER e = _swix (Buffer_Register, _INR(0,3)|_OUT(0), *flags, p, p + *size, -1, handle); #endif } *thresh = *size - UGETW(str->pipe->endpoint->edesc->wMaxPacketSize); return e; } /*---------------------------------------------------------------------------*/ _kernel_oserror* get_buffer_space ( struct ugen_softc* udev, int fs, int* size, int* free ) { int devfs; _kernel_oserror* e = _swix (OS_FSControl, _INR(0,1)|_OUT(1), 21, fs, &devfs); if (e) return e; for (int i = 0; i < sizeof (udev->str) / sizeof (udev->str[0]); ++i) { if (udev->str[i] && udev->str[i]->fs_stream == devfs) { int f; _kernel_swi_regs r; r.r[0] = BM_FreeSpace; r.r[1] = udev->str[i]->buffer_id; CallBufMan(&r); f = r.r[2]; if (free) *free = f; r.r[0] = BM_UsedSpace; CallBufMan(&r); if (size) *size = *free + r.r[2]; return NULL; } } return uerror (E_NoStream); } /*---------------------------------------------------------------------------*/ _kernel_oserror* get_handles ( struct ugen_softc* udev, int fs, int* buf, int* dvfs ) { int devfs; _kernel_oserror* e = _swix (OS_FSControl, _INR(0,1)|_OUT(1), 21, fs, &devfs); if (e) return e; for (int i = 0; i < sizeof (udev->str) / sizeof (udev->str[0]); ++i) { if (udev->str[i] && udev->str[i]->fs_stream == devfs) { if (dvfs) *dvfs = devfs; if (buf) *buf = udev->str[i]->buffer; return NULL; } } return uerror (E_NoStream); } _kernel_oserror* get_location ( struct ugen_softc* udev, char* location ) { if (location == NULL) return NULL; usbd_device_handle dev = udev->sc_udev; memset (location, 0, 6); while (dev->depth > 0) { dprintf (("", "%d, %d\n", dev->powersrc->portno, dev->depth)); location[dev->depth] = dev->powersrc->portno; dev = dev->powersrc->parent; }; location[0] = dev->bus->bdev.dv_unit; return NULL; } _kernel_oserror* clear_endpoint_stall ( struct ugen_softc* udev, int fs ) { int devfs; _kernel_oserror* e = _swix (OS_FSControl, _INR(0,1)|_OUT(1), 21, fs, &devfs); if (e) return e; for (int i = 0; i < sizeof (udev->str) / sizeof (udev->str[0]); ++i) { if (udev->str[i] && udev->str[i]->fs_stream == devfs) { int err = usbd_clear_endpoint_stall (udev->str[i]->pipe); #if 1 if (err != 0) return uerror (E_BadRequest); #else if (err != 0) { static _kernel_oserror e; char cc[5], *cp; sprintf (cc, "CC%02d", err); _swix (MessageTrans_Lookup, _INR(0,2)|_OUT(2), &mod_messages, cc, 0, &cp); return _swix (MessageTrans_ErrorLookup, _INR(0,4), E_BadRequest, &mod_messages, &e, sizeof e, cp); } #endif udev->str[i]->xfer->status = USBD_NORMAL_COMPLETION; return NULL; } } return uerror (E_NoStream); } /*---------------------------------------------------------------------------*/ _kernel_oserror* driver (_kernel_swi_regs* r, void* pw) { // _kernel_oserror* e = NULL; struct ugen_softc* udev = (struct ugen_softc*) r->r[8]; // int32_t ep; struct devstream * str = (struct devstream*) r->r[2]; (void) pw; // if (r->r[0] != 12) dprintf (("", "devfs driver reason %d, dev %p, str %p\n", // r->r[0], udev, str)); switch ((uint32_t) r->r[0]) { case DeviceFSCallDevice_Initialise: return device_initialise ( r->r[2], r->r[3], (device_valid *) r->r[6], udev, (struct devstream **) (r->r + 2) ); break; case DeviceFSCallDevice_Terminate: if (str == NULL) { for (struct devstream** str = udev->str; str < udev->str + sizeof udev->str / sizeof udev->str[0]; ++str) { terminate_stream (udev, *str, 1); } } else { terminate_stream (udev, str, 1); } break; case DeviceFSCallDevice_TxWakeUp: str->count = 0; dprintf (("", "wakeup write, resetting count to %d\n", str->count)); start_write (str); break; case DeviceFSCallDevice_RxWakeUp: str->count = 0; /* total length has always been passed in R3, although not documented */ str->totalcount = r->r[3]; dprintf (("", "wakeup read, resetting count to %d, totalcount to %d\n", str->count, str->totalcount)); if (!str->pipe->repeat) start_read (str); break; case DeviceFSCallDevice_RxSleep: break; case DeviceFSCallDevice_EnumDir: break; case DeviceFSCallDevice_TxCreateBuffer: case DeviceFSCallDevice_RxCreateBuffer: return create_buffer ( (struct devstream*) r->r[2], (uint32_t*)r->r + 3, (size_t*)r->r + 4, (uint32_t*)r->r + 5, (size_t*)r->r + 6); break; case DeviceFSCallDevice_Halt: break; case DeviceFSCallDevice_Resume: dprintf (("", "resume ep = %x\n", str->ep)); /* if the top bit is set, this is an IN endpoint */ if (UE_GET_DIR(str->ep) == UE_DIR_IN) { if (!str->pipe->repeat) start_read (str); } else start_write (str); break; case DeviceFSCallDevice_EndOfData: /* this was to try and make bulk transfers finish neatly, but doesn't seem to achieve the desired effect */ if (str->xfer->status == USBD_IN_PROGRESS) r->r[3] = 0; break; case DeviceFSCallDevice_StreamCreated: str->buffer = r->r[3]; _swix (Buffer_InternalInfo, _IN(0)|_OUTR(0,2), r->r[3], &str->buffer_id, &BuffManService, &BuffManWS); if ( (str->pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT || (str->pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS ) { start_read (str); // dprintf (("", "inserting interrupt transfer\n")); // usbd_setup_xfer( // str->xfer, // str->pipe, // str, // (void*) str->buffer_id, // UGETW(str->pipe->endpoint->edesc->wMaxPacketSize), // USBD_SHORT_XFER_OK, // str->timeout, // read_cb); // // str->xfer->status = usbd_transfer (str->xfer); } break; case DeviceFSCallDevice_MonitorTX: case DeviceFSCallDevice_MonitorRX: { int status = str->xfer->status; /* the xfer is NULL for case of HID not owning the xfer */ if (str->xfer == NULL|| /* normal completion occurs when a transfer has finished */ status == USBD_NORMAL_COMPLETION || status == USBD_IN_PROGRESS) return NULL; _kernel_swi_regs r; /* empty the buffer so that we return */ r.r[0] = BM_PurgeBuffer; r.r[1] = str->buffer_id; CallBufMan (&r); static _kernel_oserror err; char errtoken[sizeof E_XferFailed] = E_XferFailed, cc[5], *cp; ((_kernel_oserror *)errtoken)->errnum += status; sprintf (cc, "CC%02d", status); _swix (MessageTrans_Lookup, _INR(0,2)|_OUT(2), &mod_messages, cc, 0, &cp); return _swix (MessageTrans_ErrorLookup, _INR(0,4), errtoken, &mod_messages, &err, sizeof err, cp); } break; case DeviceFSCallDevice_USBRequest: { struct req { int a, b; } rq = (struct req) { r->r[3], r->r[4] }; int err = usbd_do_request (udev->sc_udev, (void*) &rq, (char*)r->r[5]); #if 1 if (err != 0) return uerror (E_BadRequest); #else if (err != 0) { static _kernel_oserror e; char cc[5], *cp; sprintf (cc, "CC%02d", err); _swix (MessageTrans_Lookup, _INR(0,2)|_OUT(2), &mod_messages, cc, 0, &cp); return _swix (MessageTrans_ErrorLookup, _INR(0,4), E_BadRequest, &mod_messages, &e, sizeof e, cp); } #endif break; } case DeviceFSCallDevice_BufferSpace: return get_buffer_space (udev, r->r[2], r->r + 3, r->r + 4); break; case DeviceFSCallDevice_GetHandles: return get_handles (udev, r->r[2], r->r + 3, r->r + 4); break; case DeviceFSCallDevice_GetLocation: return get_location (udev, (char*) r->r[3]); break; case DeviceFSCallDevice_ClearStall: return clear_endpoint_stall (udev, r->r[2]); break; } return NULL; } /*--------------------------------------------------------------------------*/