/** * Process hub over current change * * This means either to power off the hub or power it on. * @param hub_dev hub instance * @param status hub status bitmask * @return error code */ static void usb_hub_over_current(const usb_hub_dev_t *hub_dev, usb_hub_status_t status) { if (status & USB_HUB_STATUS_OVER_CURRENT) { /* Hub should remove power from all ports if it detects OC */ usb_log_warning("Detected hub over-current condition, " "all ports should be powered off."); return; } /* Ports are always powered. */ if (!hub_dev->power_switched) return; /* Over-current condition is gone, it is safe to turn the ports on. */ for (size_t port = 0; port < hub_dev->port_count; ++port) { const int ret = usb_hub_port_set_feature( &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER); if (ret != EOK) { usb_log_warning("HUB OVER-CURRENT GONE: Cannot power on" " port %zu: %s\n", hub_dev->ports[port].port_number, str_error(ret)); } else { if (!hub_dev->per_port_power) return; } } }
/** * Send Get Idle request to the HID device. * * @param[in] hid_dev HID device to send the request to. * @param[out] duration Duration value (multiplicate by 4 to get real duration * in miliseconds). * * @retval EOK if successful. * @retval EINVAL if no HID device is given. * @return Other value inherited from one of functions * usb_pipe_start_session(), usb_pipe_end_session(), * usb_control_request_set(). */ int usbhid_req_get_idle(usb_pipe_t *ctrl_pipe, int iface_no, uint8_t *duration) { if (ctrl_pipe == NULL) { usb_log_warning("usbhid_req_set_report(): no pipe given.\n"); return EINVAL; } if (iface_no < 0) { usb_log_warning("usbhid_req_set_report(): no interface given." "\n"); return EINVAL; } /* * No need for checking other parameters, as they are checked in * the called function (usb_control_request_set()). */ int rc; usb_log_debug("Sending Get Idle request to the device (" "iface: %d).\n", iface_no); uint16_t value = 0; uint8_t buffer[1]; size_t actual_size = 0; rc = usb_control_request_get(ctrl_pipe, USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_INTERFACE, USB_HIDREQ_GET_IDLE, value, iface_no, buffer, 1, &actual_size); if (rc != EOK) { usb_log_warning("Error sending Get Idle request to the device: " "%s.\n", str_error(rc)); return rc; } if (actual_size != 1) { usb_log_warning("Wrong data size: %zu, expected: 1.\n", actual_size); return ELIMIT; } *duration = buffer[0]; return EOK; }
/** Change address of connected device. * This function automatically updates the backing connection to point to * the new address. It also unregisterrs the old endpoint and registers * a new one. * This creates whole bunch of problems: * 1. All pipes using this wire are broken because they are not * registered for new address * 2. All other pipes for this device are using wrong address, * possibly targeting completely different device * * @param pipe Control endpoint pipe (session must be already started). * @param new_address New USB address to be set (in native endianness). * @return Error code. */ static int usb_request_set_address(usb_pipe_t *pipe, usb_address_t new_address) { if ((new_address < 0) || (new_address >= USB11_ADDRESS_MAX)) { return EINVAL; } assert(pipe); assert(pipe->wire != NULL); const uint16_t addr = uint16_host2usb((uint16_t)new_address); int rc = usb_control_request_set(pipe, USB_REQUEST_TYPE_STANDARD, USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_SET_ADDRESS, addr, 0, NULL, 0); if (rc != EOK) { return rc; } /* TODO: prevent others from accessing the wire now. */ if (usb_pipe_unregister(pipe) != EOK) { usb_log_warning( "Failed to unregister the old pipe on address change.\n"); } /* Address changed. We can release the old one, thus * allowing other to us it. */ usb_hc_release_address(pipe->wire->hc_connection, pipe->wire->address); /* The address is already changed so set it in the wire */ pipe->wire->address = new_address; rc = usb_pipe_register(pipe, 0); if (rc != EOK) return EADDRNOTAVAIL; return EOK; }
/** Calls ep_remove_hook upon endpoint removal. Prints warning. * * @param ep Endpoint to be unregistered. * * @param arg hcd_t in disguise. * */ static void unregister_helper_warn(endpoint_t *ep, void *arg) { assert(ep); usb_log_warning("Endpoint %d:%d %s was left behind, removing.\n", ep->address, ep->endpoint, usb_str_direction(ep->direction)); unregister_helper(ep, arg); }
/** * Send Get Report request to the HID device. * * @param[in] hid_dev HID device to send the request to. * @param[in] type Type of the report. * @param[in][out] buffer Buffer for the report data. * @param[in] buf_size Size of the buffer (in bytes). * @param[out] actual_size Actual size of report received from the device * (in bytes). * * @retval EOK if successful. * @retval EINVAL if no HID device is given. * @return Other value inherited from function usb_control_request_set(). */ int usbhid_req_get_report(usb_pipe_t *ctrl_pipe, int iface_no, usb_hid_report_type_t type, uint8_t *buffer, size_t buf_size, size_t *actual_size) { if (ctrl_pipe == NULL) { usb_log_warning("usbhid_req_set_report(): no pipe given.\n"); return EINVAL; } if (iface_no < 0) { usb_log_warning("usbhid_req_set_report(): no interface given." "\n"); return EINVAL; } /* * No need for checking other parameters, as they are checked in * the called function (usb_control_request_set()). */ int rc; uint16_t value = 0; value |= (type << 8); usb_log_debug("Sending Get Report request to the device.\n"); rc = usb_control_request_get(ctrl_pipe, USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_INTERFACE, USB_HIDREQ_GET_REPORT, value, iface_no, buffer, buf_size, actual_size); if (rc != EOK) { usb_log_warning("Error sending Get Report request to the device: " "%s.\n", str_error(rc)); return rc; } return EOK; }
/** * Send Set Idle request to the HID device. * * @param hid_dev HID device to send the request to. * @param duration Duration value (is multiplicated by 4 by the device to * get real duration in miliseconds). * * @retval EOK if successful. * @retval EINVAL if no HID device is given. * @return Other value inherited from function usb_control_request_set(). */ int usbhid_req_set_idle(usb_pipe_t *ctrl_pipe, int iface_no, uint8_t duration) { if (ctrl_pipe == NULL) { usb_log_warning("usbhid_req_set_report(): no pipe given.\n"); return EINVAL; } if (iface_no < 0) { usb_log_warning("usbhid_req_set_report(): no interface given." "\n"); return EINVAL; } /* * No need for checking other parameters, as they are checked in * the called function (usb_control_request_set()). */ int rc; usb_log_debug("Sending Set Idle request to the device (" "duration: %u, iface: %d).\n", duration, iface_no); uint16_t value = duration << 8; rc = usb_control_request_set(ctrl_pipe, USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_INTERFACE, USB_HIDREQ_SET_IDLE, value, iface_no, NULL, 0); if (rc != EOK) { usb_log_warning("Error sending Set Idle request to the device: " "%s.\n", str_error(rc)); return rc; } return EOK; }
/** * Send Set Protocol request to the HID device. * * @param hid_dev HID device to send the request to. * @param protocol Protocol to set. * * @retval EOK if successful. * @retval EINVAL if no HID device is given. * @return Other value inherited from function usb_control_request_set(). */ int usbhid_req_set_protocol(usb_pipe_t *ctrl_pipe, int iface_no, usb_hid_protocol_t protocol) { if (ctrl_pipe == NULL) { usb_log_warning("usbhid_req_set_report(): no pipe given.\n"); return EINVAL; } if (iface_no < 0) { usb_log_warning("usbhid_req_set_report(): no interface given." "\n"); return EINVAL; } /* * No need for checking other parameters, as they are checked in * the called function (usb_control_request_set()). */ int rc; usb_log_debug("Sending Set Protocol request to the device (" "protocol: %d, iface: %d).\n", protocol, iface_no); rc = usb_control_request_set(ctrl_pipe, USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_INTERFACE, USB_HIDREQ_SET_PROTOCOL, protocol, iface_no, NULL, 0); if (rc != EOK) { usb_log_warning("Error sending Set Protocol request to the " "device: %s.\n", str_error(rc)); return rc; } return EOK; }
/** Initialize UHCI hc hw resources. * * @param[in] instance UHCI structure to use. * For magic values see UHCI Design Guide */ void hc_init_hw(const hc_t *instance) { assert(instance); uhci_regs_t *registers = instance->registers; /* Reset everything, who knows what touched it before us */ pio_write_16(®isters->usbcmd, UHCI_CMD_GLOBAL_RESET); async_usleep(50000); /* 50ms according to USB spec(root hub reset) */ pio_write_16(®isters->usbcmd, 0); /* Reset hc, all states and counters. Hope that hw is not broken */ pio_write_16(®isters->usbcmd, UHCI_CMD_HCRESET); do { async_usleep(10); } while ((pio_read_16(®isters->usbcmd) & UHCI_CMD_HCRESET) != 0); /* Set frame to exactly 1ms */ pio_write_8(®isters->sofmod, 64); /* Set frame list pointer */ const uint32_t pa = addr_to_phys(instance->frame_list); pio_write_32(®isters->flbaseadd, pa); if (instance->hw_interrupts) { /* Enable all interrupts, but resume interrupt */ pio_write_16(&instance->registers->usbintr, UHCI_INTR_ALLOW_INTERRUPTS); } const uint16_t cmd = pio_read_16(®isters->usbcmd); if (cmd != 0) usb_log_warning("Previous command value: %x.\n", cmd); /* Start the hc with large(64B) packet FSBR */ pio_write_16(®isters->usbcmd, UHCI_CMD_RUN_STOP | UHCI_CMD_MAX_PACKET | UHCI_CMD_CONFIGURE); }
/** Root Hub driver structure initialization. * * Reads info registers and prepares descriptors. Sets power mode. */ void rh_init(rh_t *instance, ohci_regs_t *regs) { assert(instance); assert(regs); instance->registers = regs; instance->port_count = OHCI_RD(regs->rh_desc_a) & RHDA_NDS_MASK; usb_log_debug2("rh_desc_a: %x.\n", OHCI_RD(regs->rh_desc_a)); if (instance->port_count > 15) { usb_log_warning("OHCI specification does not allow more than 15" " ports. Max 15 ports will be used"); instance->port_count = 15; } /* Don't forget the hub status bit and round up */ instance->interrupt_mask_size = 1 + (instance->port_count / 8); instance->unfinished_interrupt_transfer = NULL; #if defined OHCI_POWER_SWITCH_no usb_log_debug("OHCI rh: Set power mode to no power switching.\n"); /* Set port power mode to no power-switching. (always on) */ OHCI_SET(regs->rh_desc_a, RHDA_NPS_FLAG); /* Set to no over-current reporting */ OHCI_SET(regs->rh_desc_a, RHDA_NOCP_FLAG); #elif defined OHCI_POWER_SWITCH_ganged usb_log_debug("OHCI rh: Set power mode to ganged power switching.\n"); /* Set port power mode to ganged power-switching. */ OHCI_CLR(regs->rh_desc_a, RHDA_NPS_FLAG); OHCI_CLR(regs->rh_desc_a, RHDA_PSM_FLAG); /* Turn off power (hub driver will turn this back on)*/ OHCI_WR(regs->rh_status, RHS_CLEAR_GLOBAL_POWER); /* Set to global over-current */ OHCI_CLR(regs->rh_desc_a, RHDA_NOCP_FLAG); OHCI_CLR(regs->rh_desc_a, RHDA_OCPM_FLAG); #else usb_log_debug("OHCI rh: Set power mode to per-port power switching.\n"); /* Set port power mode to per port power-switching. */ OHCI_CLR(regs->rh_desc_a, RHDA_NPS_FLAG); OHCI_SET(regs->rh_desc_a, RHDA_PSM_FLAG); /* Control all ports by global switch and turn them off */ OHCI_CLR(regs->rh_desc_b, RHDB_PCC_MASK << RHDB_PCC_SHIFT); OHCI_WR(regs->rh_status, RHS_CLEAR_GLOBAL_POWER); /* Return control to per port state */ OHCI_SET(regs->rh_desc_b, RHDB_PCC_MASK << RHDB_PCC_SHIFT); /* Set per port over-current */ OHCI_CLR(regs->rh_desc_a, RHDA_NOCP_FLAG); OHCI_SET(regs->rh_desc_a, RHDA_OCPM_FLAG); #endif fibril_mutex_initialize(&instance->guard); rh_init_descriptors(instance); usb_log_info("Root hub (%zu ports) initialized.\n", instance->port_count); }
/** Wrapper for registering attached device to the hub. * * The @p enable_port function is expected to enable signaling on given * port. * The argument can have arbitrary meaning and it is not touched at all * by this function (it is passed as is to the @p enable_port function). * * If the @p enable_port fails (i.e. does not return EOK), the device * addition is canceled. * The return value is then returned (it is good idea to use different * error codes than those listed as return codes by this function itself). * * The @p connection representing connection with host controller does not * need to be started. * This function duplicates the connection to allow simultaneous calls of * this function (i.e. from different fibrils). * * @param[in] parent Parent device (i.e. the hub device). * @param[in] connection Connection to host controller. Must be non-null. * @param[in] dev_speed New device speed. * @param[in] enable_port Function for enabling signaling through the port the * device is attached to. * @param[in] arg Any data argument to @p enable_port. * @param[out] assigned_address USB address of the device. * @param[in] dev_ops Child device ops. Will use default if not provided. * @param[in] new_dev_data Arbitrary pointer to be stored in the child * as @c driver_data. Will allocate and assign usb_hub_attached_device_t * structure if NULL. * @param[out] new_fun Storage where pointer to allocated child function * will be written. Must be non-null. * @return Error code. * @retval EINVAL Either connection or new_fun is a NULL pointer. * @retval ENOENT Connection to HC not opened. * @retval EADDRNOTAVAIL Failed retrieving free address from host controller. * @retval EBUSY Failed reserving default USB address. * @retval ENOTCONN Problem connecting to the host controller via USB pipe. * @retval ESTALL Problem communication with device (either SET_ADDRESS * request or requests for descriptors when creating match ids). */ int usb_hc_new_device_wrapper(ddf_dev_t *parent, usb_hc_connection_t *hc_conn, usb_speed_t dev_speed, int (*enable_port)(void *arg), void *arg, usb_address_t *assigned_address, ddf_dev_ops_t *dev_ops, void *new_dev_data, ddf_fun_t **new_fun) { if ((new_fun == NULL) || (hc_conn == NULL)) return EINVAL; int rc; struct timeval start_time; rc = gettimeofday(&start_time, NULL); if (rc != EOK) { return rc; } /* We are gona do a lot of communication better open it in advance. */ rc = usb_hc_connection_open(hc_conn); if (rc != EOK) { return rc; } /* Request a new address. */ usb_address_t dev_addr = usb_hc_request_address(hc_conn, 0, false, dev_speed); if (dev_addr < 0) { rc = EADDRNOTAVAIL; goto close_connection; } /* Initialize connection to device. */ usb_device_connection_t dev_conn; rc = usb_device_connection_initialize( &dev_conn, hc_conn, USB_ADDRESS_DEFAULT); if (rc != EOK) { rc = ENOTCONN; goto leave_release_free_address; } /* Initialize control pipe on default address. Don't register yet. */ usb_pipe_t ctrl_pipe; rc = usb_pipe_initialize_default_control(&ctrl_pipe, &dev_conn); if (rc != EOK) { rc = ENOTCONN; goto leave_release_free_address; } /* * The default address request might fail. * That means that someone else is already using that address. * We will simply wait and try again. * (Someone else already wants to add a new device.) */ do { rc = usb_hc_request_address(hc_conn, USB_ADDRESS_DEFAULT, true, dev_speed); if (rc == ENOENT) { /* Do not overheat the CPU ;-). */ async_usleep(DEFAULT_ADDRESS_ATTEMPT_DELAY_USEC); } } while (rc == ENOENT); if (rc < 0) { goto leave_release_free_address; } /* Register control pipe on default address. 0 means no interval. */ rc = usb_pipe_register(&ctrl_pipe, 0); if (rc != EOK) { rc = ENOTCONN; goto leave_release_default_address; } struct timeval end_time; rc = gettimeofday(&end_time, NULL); if (rc != EOK) { goto leave_release_default_address; } /* According to the USB spec part 9.1.2 host allows 100ms time for * the insertion process to complete. According to 7.1.7.1 this is the * time between attach detected and port reset. However, the setup done * above might use much of this time so we should only wait to fill * up the 100ms quota*/ const suseconds_t elapsed = tv_sub(&end_time, &start_time); if (elapsed < 100000) { async_usleep(100000 - elapsed); } /* Endpoint is registered. We can enable the port and change address. */ rc = enable_port(arg); if (rc != EOK) { goto leave_release_default_address; } /* USB spec 7.1.7.1: The USB System Software guarantees a minimum of * 10ms for reset recovery. Device response to any bus transactions * addressed to the default device address during the reset recovery * time is undefined. */ async_usleep(10000); /* Get max_packet_size value. */ rc = usb_pipe_probe_default_control(&ctrl_pipe); if (rc != EOK) { rc = ESTALL; goto leave_release_default_address; } rc = usb_request_set_address(&ctrl_pipe, dev_addr); if (rc != EOK) { rc = ESTALL; goto leave_release_default_address; } /* Register the device with devman. */ /* FIXME: create device_register that will get opened ctrl pipe. */ ddf_fun_t *child_fun; rc = usb_device_register_child_in_devman(&ctrl_pipe, parent, dev_ops, new_dev_data, &child_fun); if (rc != EOK) { goto leave_release_free_address; } const usb_hub_attached_device_t new_device = { .address = dev_addr, .fun = child_fun, }; /* Inform the host controller about the handle. */ rc = usb_hub_register_device(hc_conn, &new_device); if (rc != EOK) { /* We know nothing about that data. */ if (new_dev_data) child_fun->driver_data = NULL; /* The child function is already created. */ ddf_fun_destroy(child_fun); rc = EDESTADDRREQ; goto leave_release_free_address; } if (assigned_address != NULL) { *assigned_address = dev_addr; } *new_fun = child_fun; rc = EOK; goto close_connection; /* * Error handling (like nested exceptions) starts here. * Completely ignoring errors here. */ leave_release_default_address: if (usb_hc_release_address(hc_conn, USB_ADDRESS_DEFAULT) != EOK) usb_log_warning("%s: Failed to release defaut address.\n", __FUNCTION__); leave_release_free_address: /* This might be either 0:0 or dev_addr:0 */ if (usb_pipe_unregister(&ctrl_pipe) != EOK) usb_log_warning("%s: Failed to unregister default pipe.\n", __FUNCTION__); if (usb_hc_release_address(hc_conn, dev_addr) != EOK) usb_log_warning("%s: Failed to release address: %d.\n", __FUNCTION__, dev_addr); close_connection: if (usb_hc_connection_close(hc_conn) != EOK) usb_log_warning("%s: Failed to close hc connection.\n", __FUNCTION__); return rc; }
/** * Processes key events. * * @note This function was copied from AT keyboard driver and modified to suit * USB keyboard. * * @note Lock keys are not sent to the console, as they are completely handled * in the driver. It may, however, be required later that the driver * sends also these keys to application (otherwise it cannot use those * keys at all). * * @param hid_dev * @param multim_dev * @param type Type of the event (press / release). Recognized values: * KEY_PRESS, KEY_RELEASE * @param key Key code of the key according to HID Usage Tables. */ static void usb_multimedia_push_ev( usb_multimedia_t *multim_dev, int type, unsigned int key) { assert(multim_dev != NULL); const kbd_event_t ev = { .type = type, .key = key, .mods = 0, .c = 0, }; usb_log_debug2(NAME " Sending key %d to the console\n", ev.key); if (multim_dev->console_sess == NULL) { usb_log_warning( "Connection to console not ready, key discarded.\n"); return; } async_exch_t *exch = async_exchange_begin(multim_dev->console_sess); if (exch != NULL) { async_msg_4(exch, KBDEV_EVENT, ev.type, ev.key, ev.mods, ev.c); async_exchange_end(exch); } else { usb_log_warning("Failed to send multimedia key.\n"); } } int usb_multimedia_init(struct usb_hid_dev *hid_dev, void **data) { if (hid_dev == NULL || hid_dev->usb_dev == NULL) { return EINVAL; } usb_log_debug(NAME " Initializing HID/multimedia structure...\n"); /* Create the exposed function. */ ddf_fun_t *fun = ddf_fun_create( hid_dev->usb_dev->ddf_dev, fun_exposed, NAME); if (fun == NULL) { usb_log_error("Could not create DDF function node.\n"); return ENOMEM; } ddf_fun_set_ops(fun, &multimedia_ops); usb_multimedia_t *multim_dev = ddf_fun_data_alloc(fun, sizeof(usb_multimedia_t)); if (multim_dev == NULL) { ddf_fun_destroy(fun); return ENOMEM; } multim_dev->console_sess = NULL; //todo Autorepeat? int rc = ddf_fun_bind(fun); if (rc != EOK) { usb_log_error("Could not bind DDF function: %s.\n", str_error(rc)); ddf_fun_destroy(fun); return rc; } usb_log_debug(NAME " function created (handle: %" PRIun ").\n", ddf_fun_get_handle(fun)); rc = ddf_fun_add_to_category(fun, "keyboard"); if (rc != EOK) { usb_log_error( "Could not add DDF function to category 'keyboard': %s.\n", str_error(rc)); if (ddf_fun_unbind(fun) != EOK) { usb_log_error("Failed to unbind %s, won't destroy.\n", ddf_fun_get_name(fun)); } else { ddf_fun_destroy(fun); } return rc; } /* Save the KBD device structure into the HID device structure. */ *data = fun; usb_log_debug(NAME " HID/multimedia structure initialized.\n"); return EOK; } void usb_multimedia_deinit(struct usb_hid_dev *hid_dev, void *data) { ddf_fun_t *fun = data; usb_multimedia_t *multim_dev = ddf_fun_data_get(fun); /* Hangup session to the console */ if (multim_dev->console_sess) async_hangup(multim_dev->console_sess); if (ddf_fun_unbind(fun) != EOK) { usb_log_error("Failed to unbind %s, won't destroy.\n", ddf_fun_get_name(fun)); } else { usb_log_debug2("%s unbound.\n", ddf_fun_get_name(fun)); /* This frees multim_dev too as it was stored in * fun->data */ ddf_fun_destroy(fun); } }