int usb_generic_hid_init(usb_hid_dev_t *hid_dev, void **data) { if (hid_dev == NULL) { return EINVAL; } /* Create the exposed function. */ usb_log_debug("Creating DDF function %s...\n", HID_GENERIC_FUN_NAME); ddf_fun_t *fun = ddf_fun_create(hid_dev->usb_dev->ddf_dev, fun_exposed, HID_GENERIC_FUN_NAME); if (fun == NULL) { usb_log_error("Could not create DDF function node.\n"); return ENOMEM; } /* This is nasty, both device and this function have the same * driver data, thus destruction causes to double free */ fun->driver_data = hid_dev; fun->ops = &usb_generic_hid_ops; int rc = ddf_fun_bind(fun); if (rc != EOK) { usb_log_error("Could not bind DDF function: %s.\n", str_error(rc)); fun->driver_data = NULL; ddf_fun_destroy(fun); return rc; } usb_log_debug("HID function created. Handle: %" PRIun "\n", fun->handle); *data = fun; return EOK; }
/** * Process root hub request. * * @param instance Root hub instance * @param request Structure containing both request and response information * @return Error code */ void rh_request(rh_t *instance, usb_transfer_batch_t *request) { assert(instance); assert(request); switch (request->ep->transfer_type) { case USB_TRANSFER_CONTROL: usb_log_debug("Root hub got CONTROL packet\n"); control_request(instance, request); break; case USB_TRANSFER_INTERRUPT: usb_log_debug("Root hub got INTERRUPT packet\n"); fibril_mutex_lock(&instance->guard); assert(instance->unfinished_interrupt_transfer == NULL); const uint16_t mask = create_interrupt_mask(instance); if (mask == 0) { usb_log_debug("No changes(%hx)...\n", mask); instance->unfinished_interrupt_transfer = request; } else { usb_log_debug("Processing changes...\n"); interrupt_request( request, mask, instance->interrupt_mask_size); } fibril_mutex_unlock(&instance->guard); break; default: usb_log_error("Root hub got unsupported request.\n"); TRANSFER_END(request, ENOTSUP); } }
static int usb_generic_hid_get_event(ddf_fun_t *fun, uint8_t *buffer, size_t size, size_t *act_size, int *event_nr, unsigned int flags) { usb_log_debug2("Generic HID: Get event.\n"); if (fun == NULL || fun->driver_data == NULL || buffer == NULL || act_size == NULL || event_nr == NULL) { usb_log_debug("No function"); return EINVAL; } const usb_hid_dev_t *hid_dev = (usb_hid_dev_t *)fun->driver_data; if (hid_dev->input_report_size > size) { usb_log_debug("input_report_size > size (%zu, %zu)\n", hid_dev->input_report_size, size); return EINVAL; // TODO: other error code } /*! @todo This should probably be somehow atomic. */ memcpy(buffer, hid_dev->input_report, hid_dev->input_report_size); *act_size = hid_dev->input_report_size; *event_nr = usb_hid_report_number(hid_dev); usb_log_debug2("OK\n"); return EOK; }
/** Initialize UHCI hc memory structures. * * @param[in] instance UHCI structure to use. * @return Error code * @note Should be called only once on any structure. * * Structures: * - transfer lists (queue heads need to be accessible by the hw) * - frame list page (needs to be one UHCI hw accessible 4K page) */ int hc_init_mem_structures(hc_t *instance) { assert(instance); /* Init USB frame list page */ instance->frame_list = get_page(); if (!instance->frame_list) { return ENOMEM; } usb_log_debug("Initialized frame list at %p.\n", instance->frame_list); /* Init transfer lists */ int ret = hc_init_transfer_lists(instance); if (ret != EOK) { usb_log_error("Failed to initialize transfer lists.\n"); return_page(instance->frame_list); return ENOMEM; } usb_log_debug("Initialized transfer lists.\n"); /* Set all frames to point to the first queue head */ const uint32_t queue = LINK_POINTER_QH( addr_to_phys(instance->transfers_interrupt.queue_head)); for (unsigned i = 0; i < UHCI_FRAME_LIST_COUNT; ++i) { instance->frame_list[i] = queue; } return EOK; }
/** * Callback for passing a new device to the driver. * * @note Currently, only boot-protocol keyboards are supported by this driver. * * @param dev Structure representing the new device. * @return Error code. */ static int usb_hid_device_add(usb_device_t *dev) { usb_log_debug("%s\n", __FUNCTION__); if (dev == NULL) { usb_log_error("Wrong parameter given for add_device().\n"); return EINVAL; } if (usb_device_get_iface_number(dev) < 0) { usb_log_error("Failed to add HID device: endpoints not found." "\n"); return ENOTSUP; } usb_hid_dev_t *hid_dev = usb_device_data_alloc(dev, sizeof(usb_hid_dev_t)); if (hid_dev == NULL) { usb_log_error("Failed to create USB/HID device structure.\n"); return ENOMEM; } int rc = usb_hid_init(hid_dev, dev); if (rc != EOK) { usb_log_error("Failed to initialize USB/HID device.\n"); usb_hid_deinit(hid_dev); return rc; } usb_log_debug("USB/HID device structure initialized.\n"); /* Start automated polling function. * This will create a separate fibril that will query the device * for the data continuously. */ rc = usb_device_auto_poll_desc(dev, /* Index of the polling pipe. */ hid_dev->poll_pipe_mapping->description, /* Callback when data arrives. */ usb_hid_polling_callback, /* How much data to request. */ hid_dev->poll_pipe_mapping->pipe.max_packet_size, /* Delay */ -1, /* Callback when the polling ends. */ usb_hid_polling_ended_callback, /* Custom argument. */ hid_dev); if (rc != EOK) { usb_log_error("Failed to start polling fibril for `%s'.\n", usb_device_get_name(dev)); usb_hid_deinit(hid_dev); return rc; } hid_dev->running = true; usb_log_info("HID device `%s' ready.\n", usb_device_get_name(dev)); return EOK; }
/** Callback for polling hub for changes. * * @param dev Device where the change occured. * @param change_bitmap Bitmap of changed ports. * @param change_bitmap_size Size of the bitmap in bytes. * @param arg Custom argument, points to @c usb_hub_dev_t. * @return Whether to continue polling. */ bool hub_port_changes_callback(usb_device_t *dev, uint8_t *change_bitmap, size_t change_bitmap_size, void *arg) { usb_log_debug("hub_port_changes_callback\n"); usb_hub_dev_t *hub = arg; assert(hub); /* It is an error condition if we didn't receive enough data */ if (change_bitmap_size == 0) { return false; } /* Lowest bit indicates global change */ const bool change = change_bitmap[0] & 1; if (change) { usb_hub_global_interrupt(hub); } /* N + 1 bit indicates change on port N */ for (size_t port = 0; port < hub->port_count + 1; port++) { const size_t bit = port + 1; const bool change = (change_bitmap[bit / 8] >> (bit % 8)) & 1; if (change) { usb_hub_port_process_interrupt(&hub->ports[port], hub); } } return true; }
static size_t usb_generic_get_report_descriptor_length(ddf_fun_t *fun) { usb_log_debug("Generic HID: Get report descriptor length.\n"); if (fun == NULL || fun->driver_data == NULL) { usb_log_debug("No function"); return EINVAL; } const usb_hid_dev_t *hid_dev = fun->driver_data; usb_log_debug2("hid_dev->report_desc_size = %zu\n", hid_dev->report_desc_size); return hid_dev->report_desc_size; }
static inline void interrupt_request( usb_transfer_batch_t *request, uint16_t mask, size_t size) { assert(request); usb_log_debug("Sending interrupt vector(%zu) %hhx:%hhx.\n", size, ((uint8_t*)&mask)[0], ((uint8_t*)&mask)[1]); usb_transfer_batch_finish_error(request, &mask, size, EOK); usb_transfer_batch_destroy(request); }
/** * Default handler for IPC methods not handled by DDF. * * Currently recognizes only one method (IPC_M_CONNECT_TO_ME), in which case it * assumes the caller is the console and thus it stores IPC session to it for * later use by the driver to notify about key events. * * @param fun Device function handling the call. * @param icallid Call id. * @param icall Call data. */ static void default_connection_handler(ddf_fun_t *fun, ipc_callid_t icallid, ipc_call_t *icall) { usb_log_debug(NAME " default_connection_handler()\n"); usb_multimedia_t *multim_dev = ddf_fun_data_get(fun); async_sess_t *sess = async_callback_receive_start(EXCHANGE_SERIALIZE, icall); if (sess != NULL) { if (multim_dev->console_sess == NULL) { multim_dev->console_sess = sess; usb_log_debug(NAME " Saved session to console: %p\n", sess); async_answer_0(icallid, EOK); } else async_answer_0(icallid, ELIMIT); } else async_answer_0(icallid, EINVAL); }
bool usb_multimedia_polling_callback(struct usb_hid_dev *hid_dev, void *data) { // TODO: checks ddf_fun_t *fun = data; if (hid_dev == NULL) { return false; } usb_multimedia_t *multim_dev = ddf_fun_data_get(fun); usb_hid_report_path_t *path = usb_hid_report_path(); if (path == NULL) return true; /* This might be a temporary failure. */ int ret = usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_CONSUMER, 0); if (ret != EOK) { usb_hid_report_path_free(path); return true; /* This might be a temporary failure. */ } usb_hid_report_path_set_report_id(path, hid_dev->report_id); usb_hid_report_field_t *field = usb_hid_report_get_sibling( &hid_dev->report, NULL, path, USB_HID_PATH_COMPARE_END | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY, USB_HID_REPORT_TYPE_INPUT); //FIXME Is this iterating OK if done multiple times? //FIXME The parsing is not OK. (what's wrong?) while (field != NULL) { if (field->value != 0) { usb_log_debug(NAME " KEY VALUE(%X) USAGE(%X)\n", field->value, field->usage); const unsigned key = usb_multimedia_map_usage(field->usage); const char *key_str = usbhid_multimedia_usage_to_str(field->usage); usb_log_info("Pressed key: %s\n", key_str); usb_multimedia_push_ev(multim_dev, KEY_PRESS, key); } field = usb_hid_report_get_sibling( &hid_dev->report, field, path, USB_HID_PATH_COMPARE_END | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY, USB_HID_REPORT_TYPE_INPUT); } usb_hid_report_path_free(path); return true; }
/** * Set configuration of and USB device * * Check whether there is at least one configuration and sets the first one. * This function should be run prior to running any hub-specific action. * @param usb_device usb device representation * @return error code */ static int usb_set_first_configuration(usb_device_t *usb_device) { assert(usb_device); /* Get number of possible configurations from device descriptor */ const size_t configuration_count = usb_device->descriptors.device.configuration_count; usb_log_debug("Hub has %zu configurations.\n", configuration_count); if (configuration_count < 1) { usb_log_error("There are no configurations available\n"); return EINVAL; } if (usb_device->descriptors.configuration_size < sizeof(usb_standard_configuration_descriptor_t)) { usb_log_error("Configuration descriptor is not big enough" " to fit standard configuration descriptor.\n"); return EOVERFLOW; } // TODO: Make sure that the cast is correct usb_standard_configuration_descriptor_t *config_descriptor = (usb_standard_configuration_descriptor_t *) usb_device->descriptors.configuration; /* Set configuration. Use the configuration that was in * usb_device->descriptors.configuration i.e. The first one. */ const int opResult = usb_request_set_configuration( &usb_device->ctrl_pipe, config_descriptor->configuration_number); if (opResult != EOK) { usb_log_error("Failed to set hub configuration: %s.\n", str_error(opResult)); } else { usb_log_debug("\tUsed configuration %d\n", config_descriptor->configuration_number); } return opResult; }
/** * Process interrupt on a hub device. * * If there is no pending interrupt transfer, nothing happens. * @param instance */ void rh_interrupt(rh_t *instance) { assert(instance); fibril_mutex_lock(&instance->guard); if (instance->unfinished_interrupt_transfer) { usb_log_debug("Finalizing interrupt transfer\n"); const uint16_t mask = create_interrupt_mask(instance); interrupt_request(instance->unfinished_interrupt_transfer, mask, instance->interrupt_mask_size); instance->unfinished_interrupt_transfer = NULL; } fibril_mutex_unlock(&instance->guard); }
/** * 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; }
/** Initialize UHCI hc driver structure * * @param[in] instance Memory place to initialize. * @param[in] regs Address of I/O control registers. * @param[in] reg_size Size of I/O control registers. * @param[in] interrupts True if hw interrupts should be used. * @return Error code. * @note Should be called only once on any structure. * * Initializes memory structures, starts up hw, and launches debugger and * interrupt fibrils. */ int hc_init(hc_t *instance, void *regs, size_t reg_size, bool interrupts) { assert(reg_size >= sizeof(uhci_regs_t)); int ret; #define CHECK_RET_RETURN(ret, message...) \ if (ret != EOK) { \ usb_log_error(message); \ return ret; \ } else (void) 0 instance->hw_interrupts = interrupts; instance->hw_failures = 0; /* allow access to hc control registers */ uhci_regs_t *io; ret = pio_enable(regs, reg_size, (void **)&io); CHECK_RET_RETURN(ret, "Failed to gain access to registers at %p: %s.\n", io, str_error(ret)); instance->registers = io; usb_log_debug( "Device registers at %p (%zuB) accessible.\n", io, reg_size); ret = hc_init_mem_structures(instance); CHECK_RET_RETURN(ret, "Failed to initialize UHCI memory structures: %s.\n", str_error(ret)); #undef CHECK_RET_RETURN hcd_init(&instance->generic, USB_SPEED_FULL, BANDWIDTH_AVAILABLE_USB11, bandwidth_count_usb11); instance->generic.private_data = instance; instance->generic.schedule = hc_schedule; instance->generic.ep_add_hook = NULL; hc_init_hw(instance); if (!interrupts) { instance->interrupt_emulator = fibril_create(hc_interrupt_emulator, instance); fibril_add_ready(instance->interrupt_emulator); } (void)hc_debug_checker; return EOK; }
/** Polling function, emulates interrupts. * * @param[in] arg UHCI hc structure to use. * @return EOK (should never return) */ int hc_interrupt_emulator(void* arg) { usb_log_debug("Started interrupt emulator.\n"); hc_t *instance = arg; assert(instance); while (1) { /* Read and clear status register */ uint16_t status = pio_read_16(&instance->registers->usbsts); pio_write_16(&instance->registers->usbsts, status); if (status != 0) usb_log_debug2("UHCI status: %x.\n", status); hc_interrupt(instance, status); async_usleep(UHCI_INT_EMULATOR_TIMEOUT); } return EOK; }
/** Add endpoint to the list and queue. * * @param[in] instance List to use. * @param[in] endpoint Endpoint to add. * * The endpoint is added to the end of the list and queue. */ void endpoint_list_add_ep(endpoint_list_t *instance, ohci_endpoint_t *ep) { assert(instance); assert(ep); usb_log_debug2("Queue %s: Adding endpoint(%p).\n", instance->name, ep); fibril_mutex_lock(&instance->guard); ed_t *last_ed = NULL; /* Add to the hardware queue. */ if (list_empty(&instance->endpoint_list)) { /* There are no active EDs */ last_ed = instance->list_head; } else { /* There are active EDs, get the last one */ ohci_endpoint_t *last = list_get_instance( list_last(&instance->endpoint_list), ohci_endpoint_t, link); last_ed = last->ed; } /* Keep link */ ep->ed->next = last_ed->next; /* Make sure ED is written to the memory */ write_barrier(); /* Add ed to the hw queue */ ed_append_ed(last_ed, ep->ed); /* Make sure ED is updated */ write_barrier(); /* Add to the sw list */ list_append(&ep->link, &instance->endpoint_list); ohci_endpoint_t *first = list_get_instance( list_first(&instance->endpoint_list), ohci_endpoint_t, link); usb_log_debug("HCD EP(%p) added to list %s, first is %p(%p).\n", ep, instance->name, first, first->ed); if (last_ed == instance->list_head) { usb_log_debug2("%s head ED(%p-0x%0" PRIx32 "): %x:%x:%x:%x.\n", instance->name, last_ed, instance->list_head_pa, last_ed->status, last_ed->td_tail, last_ed->td_head, last_ed->next); } fibril_mutex_unlock(&instance->guard); }
/** Initialize a new ddf driver instance of UHCI root hub. * * @param[in] device DDF instance of the device to initialize. * @return Error code. */ static int uhci_rh_dev_add(ddf_dev_t *device) { if (!device) return EINVAL; usb_log_debug2("uhci_rh_dev_add(handle=%" PRIun ")\n", device->handle); uintptr_t io_regs = 0; size_t io_size = 0; uhci_root_hub_t *rh = NULL; int ret = EOK; #define CHECK_RET_FREE_RH_RETURN(ret, message...) \ if (ret != EOK) { \ usb_log_error(message); \ if (rh) \ free(rh); \ return ret; \ } else (void)0 ret = hc_get_my_registers(device, &io_regs, &io_size); CHECK_RET_FREE_RH_RETURN(ret, "Failed to get registers from HC: %s.\n", str_error(ret)); usb_log_debug("I/O regs at %p (size %zuB).\n", (void *) io_regs, io_size); rh = malloc(sizeof(uhci_root_hub_t)); ret = (rh == NULL) ? ENOMEM : EOK; CHECK_RET_FREE_RH_RETURN(ret, "Failed to allocate rh driver instance.\n"); ret = uhci_root_hub_init(rh, (void*)io_regs, io_size, device); CHECK_RET_FREE_RH_RETURN(ret, "Failed(%d) to initialize rh driver instance: %s.\n", ret, str_error(ret)); device->driver_data = rh; usb_log_info("Controlling root hub '%s' (%" PRIun ").\n", device->name, device->handle); return EOK; }
static int usb_generic_get_report_descriptor(ddf_fun_t *fun, uint8_t *desc, size_t size, size_t *actual_size) { usb_log_debug2("Generic HID: Get report descriptor.\n"); if (fun == NULL || fun->driver_data == NULL) { usb_log_debug("No function"); return EINVAL; } const usb_hid_dev_t *hid_dev = fun->driver_data; if (hid_dev->report_desc_size > size) { return EINVAL; } memcpy(desc, hid_dev->report_desc, hid_dev->report_desc_size); *actual_size = hid_dev->report_desc_size; return EOK; }
/** * 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; }
/** Remove endpoint from the list and queue. * * @param[in] instance List to use. * @param[in] endpoint Endpoint to remove. */ void endpoint_list_remove_ep(endpoint_list_t *instance, ohci_endpoint_t *ep) { assert(instance); assert(instance->list_head); assert(ep); assert(ep->ed); fibril_mutex_lock(&instance->guard); usb_log_debug2("Queue %s: removing endpoint(%p).\n", instance->name, ep); const char *qpos = NULL; ed_t *prev_ed; /* Remove from the hardware queue */ if (list_first(&instance->endpoint_list) == &ep->link) { /* I'm the first one here */ prev_ed = instance->list_head; qpos = "FIRST"; } else { ohci_endpoint_t *prev = list_get_instance(ep->link.prev, ohci_endpoint_t, link); prev_ed = prev->ed; qpos = "NOT FIRST"; } assert(ed_next(prev_ed) == addr_to_phys(ep->ed)); prev_ed->next = ep->ed->next; /* Make sure ED is updated */ write_barrier(); usb_log_debug("HCD EP(%p) removed (%s) from %s, next %x.\n", ep, qpos, instance->name, ep->ed->next); /* Remove from the endpoint list */ list_remove(&ep->link); fibril_mutex_unlock(&instance->guard); }
/** Polling fibril. * * @param arg Pointer to polling_data_t. * @return Always EOK. */ static int polling_fibril(void *arg) { assert(arg); const polling_data_t *data = arg; /* Helper to reduce typing. */ const usb_device_auto_polling_t *params = &data->auto_polling; usb_pipe_t *pipe = &data->dev->pipes[data->pipe_index].pipe; if (params->debug > 0) { const usb_endpoint_mapping_t *mapping = &data->dev->pipes[data->pipe_index]; usb_log_debug("Poll%p: started polling of `%s' - " \ "interface %d (%s,%d,%d), %zuB/%zu.\n", data, data->dev->ddf_dev->name, (int) mapping->interface->interface_number, usb_str_class(mapping->interface->interface_class), (int) mapping->interface->interface_subclass, (int) mapping->interface->interface_protocol, data->request_size, pipe->max_packet_size); } usb_pipe_start_long_transfer(pipe); size_t failed_attempts = 0; while (failed_attempts <= params->max_failures) { size_t actual_size; const int rc = usb_pipe_read(pipe, data->buffer, data->request_size, &actual_size); if (params->debug > 1) { if (rc == EOK) { usb_log_debug( "Poll%p: received: '%s' (%zuB).\n", data, usb_debug_str_buffer(data->buffer, actual_size, 16), actual_size); } else { usb_log_debug( "Poll%p: polling failed: %s.\n", data, str_error(rc)); } } /* If the pipe stalled, we can try to reset the stall. */ if ((rc == ESTALL) && (params->auto_clear_halt)) { /* * We ignore error here as this is usually a futile * attempt anyway. */ usb_request_clear_endpoint_halt( &data->dev->ctrl_pipe, pipe->endpoint_no); } if (rc != EOK) { ++failed_attempts; const bool cont = (params->on_error == NULL) ? true : params->on_error(data->dev, rc, params->arg); if (!cont) { failed_attempts = params->max_failures; } continue; } /* We have the data, execute the callback now. */ assert(params->on_data); const bool carry_on = params->on_data( data->dev, data->buffer, actual_size, params->arg); if (!carry_on) { /* This is user requested abort, erases failures. */ failed_attempts = 0; break; } /* Reset as something might be only a temporary problem. */ failed_attempts = 0; /* Take a rest before next request. */ async_usleep(params->delay); } usb_pipe_end_long_transfer(pipe); const bool failed = failed_attempts > 0; if (params->on_polling_end != NULL) { params->on_polling_end(data->dev, failed, params->arg); } if (params->debug > 0) { if (failed) { usb_log_error("Polling of device `%s' terminated: " "recurring failures.\n", data->dev->ddf_dev->name); } else { usb_log_debug("Polling of device `%s' terminated: " "driver request.\n", data->dev->ddf_dev->name); } } /* Free the allocated memory. */ free(data->buffer); free(data); return EOK; }
/** Debug function, checks consistency of memory structures. * * @param[in] arg UHCI structure to use. * @return EOK (should never return) */ int hc_debug_checker(void *arg) { hc_t *instance = arg; assert(instance); #define QH(queue) \ instance->transfers_##queue.queue_head while (1) { const uint16_t cmd = pio_read_16(&instance->registers->usbcmd); const uint16_t sts = pio_read_16(&instance->registers->usbsts); const uint16_t intr = pio_read_16(&instance->registers->usbintr); if (((cmd & UHCI_CMD_RUN_STOP) != 1) || (sts != 0)) { usb_log_debug2("Command: %X Status: %X Intr: %x\n", cmd, sts, intr); } const uintptr_t frame_list = pio_read_32(&instance->registers->flbaseadd) & ~0xfff; if (frame_list != addr_to_phys(instance->frame_list)) { usb_log_debug("Framelist address: %p vs. %p.\n", (void *) frame_list, (void *) addr_to_phys(instance->frame_list)); } int frnum = pio_read_16(&instance->registers->frnum) & 0x3ff; uintptr_t expected_pa = instance->frame_list[frnum] & LINK_POINTER_ADDRESS_MASK; uintptr_t real_pa = addr_to_phys(QH(interrupt)); if (expected_pa != real_pa) { usb_log_debug("Interrupt QH: %p (frame %d) vs. %p.\n", (void *) expected_pa, frnum, (void *) real_pa); } expected_pa = QH(interrupt)->next & LINK_POINTER_ADDRESS_MASK; real_pa = addr_to_phys(QH(control_slow)); if (expected_pa != real_pa) { usb_log_debug("Control Slow QH: %p vs. %p.\n", (void *) expected_pa, (void *) real_pa); } expected_pa = QH(control_slow)->next & LINK_POINTER_ADDRESS_MASK; real_pa = addr_to_phys(QH(control_full)); if (expected_pa != real_pa) { usb_log_debug("Control Full QH: %p vs. %p.\n", (void *) expected_pa, (void *) real_pa); } expected_pa = QH(control_full)->next & LINK_POINTER_ADDRESS_MASK; real_pa = addr_to_phys(QH(bulk_full)); if (expected_pa != real_pa ) { usb_log_debug("Bulk QH: %p vs. %p.\n", (void *) expected_pa, (void *) real_pa); } async_usleep(UHCI_DEBUGER_TIMEOUT); } return EOK; #undef QH }
/** 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); }
/** * Initialize hub device driver structure. * * Creates hub representation and fibril that periodically checks hub's status. * Hub representation is passed to the fibril. * @param usb_dev generic usb device information * @return error code */ int usb_hub_device_add(usb_device_t *usb_dev) { assert(usb_dev); /* Create driver soft-state structure */ usb_hub_dev_t *hub_dev = usb_device_data_alloc(usb_dev, sizeof(usb_hub_dev_t)); if (hub_dev == NULL) { usb_log_error("Failed to create hub driver structure.\n"); return ENOMEM; } hub_dev->usb_device = usb_dev; hub_dev->pending_ops_count = 0; hub_dev->running = false; fibril_mutex_initialize(&hub_dev->pending_ops_mutex); fibril_condvar_initialize(&hub_dev->pending_ops_cv); int opResult = usb_pipe_start_long_transfer(&usb_dev->ctrl_pipe); if (opResult != EOK) { usb_log_error("Failed to start long ctrl pipe transfer: %s\n", str_error(opResult)); return opResult; } /* Set hub's first configuration. (There should be only one) */ opResult = usb_set_first_configuration(usb_dev); if (opResult != EOK) { usb_pipe_end_long_transfer(&usb_dev->ctrl_pipe); usb_log_error("Could not set hub configuration: %s\n", str_error(opResult)); return opResult; } /* Get port count and create attached_devices. */ opResult = usb_hub_process_hub_specific_info(hub_dev); if (opResult != EOK) { usb_pipe_end_long_transfer(&usb_dev->ctrl_pipe); usb_log_error("Could process hub specific info, %s\n", str_error(opResult)); return opResult; } /* Create hub control function. */ usb_log_debug("Creating DDF function '" HUB_FNC_NAME "'.\n"); hub_dev->hub_fun = ddf_fun_create(hub_dev->usb_device->ddf_dev, fun_exposed, HUB_FNC_NAME); if (hub_dev->hub_fun == NULL) { usb_pipe_end_long_transfer(&usb_dev->ctrl_pipe); usb_log_error("Failed to create hub function.\n"); return ENOMEM; } /* Bind hub control function. */ opResult = ddf_fun_bind(hub_dev->hub_fun); if (opResult != EOK) { usb_pipe_end_long_transfer(&usb_dev->ctrl_pipe); usb_log_error("Failed to bind hub function: %s.\n", str_error(opResult)); ddf_fun_destroy(hub_dev->hub_fun); return opResult; } /* Start hub operation. */ opResult = usb_device_auto_poll(hub_dev->usb_device, 0, hub_port_changes_callback, ((hub_dev->port_count + 1 + 8) / 8), usb_hub_polling_terminated_callback, hub_dev); if (opResult != EOK) { usb_pipe_end_long_transfer(&usb_dev->ctrl_pipe); /* Function is already bound */ ddf_fun_unbind(hub_dev->hub_fun); ddf_fun_destroy(hub_dev->hub_fun); usb_log_error("Failed to create polling fibril: %s.\n", str_error(opResult)); return opResult; } hub_dev->running = true; usb_log_info("Controlling hub '%s' (%zu ports).\n", hub_dev->usb_device->ddf_dev->name, hub_dev->port_count); usb_pipe_end_long_transfer(&usb_dev->ctrl_pipe); return EOK; }
/** * Process hub interrupts. * * The change can be either in the over-current condition or local-power change. * @param hub_dev hub instance */ static void usb_hub_global_interrupt(const usb_hub_dev_t *hub_dev) { assert(hub_dev); assert(hub_dev->usb_device); usb_log_debug("Global interrupt on a hub\n"); usb_pipe_t *control_pipe = &hub_dev->usb_device->ctrl_pipe; usb_hub_status_t status; size_t rcvd_size; /* NOTE: We can't use standard USB GET_STATUS request, because * hubs reply is 4byte instead of 2 */ const int opResult = usb_pipe_control_read(control_pipe, &get_hub_status_request, sizeof(get_hub_status_request), &status, sizeof(usb_hub_status_t), &rcvd_size); if (opResult != EOK) { usb_log_error("Could not get hub status: %s\n", str_error(opResult)); return; } if (rcvd_size != sizeof(usb_hub_status_t)) { usb_log_error("Received status has incorrect size\n"); return; } /* Handle status changes */ if (status & USB_HUB_STATUS_C_OVER_CURRENT) { usb_hub_over_current(hub_dev, status); /* Ack change in hub OC flag */ const int ret = usb_request_clear_feature( &hub_dev->usb_device->ctrl_pipe, USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_FEATURE_C_HUB_OVER_CURRENT, 0); if (ret != EOK) { usb_log_error("Failed to clear hub over-current " "change flag: %s.\n", str_error(opResult)); } } if (status & USB_HUB_STATUS_C_LOCAL_POWER) { /* NOTE: Handling this is more complicated. * If the transition is from bus power to local power, all * is good and we may signal the parent hub that we don't * need the power. * If the transition is from local power to bus power * the hub should turn off all the ports and devices need * to be reinitialized taking into account the limited power * that is now available. * There is no support for power distribution in HelenOS, * (or other OSes/hub devices that I've seen) so this is not * implemented. * Just ACK the change. */ const int ret = usb_request_clear_feature( control_pipe, USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_FEATURE_C_HUB_LOCAL_POWER, 0); if (opResult != EOK) { usb_log_error("Failed to clear hub power change " "flag: %s.\n", str_error(ret)); } } }
/** * 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); } }
/** * Load hub-specific information into hub_dev structure and process if needed * * Read port count and initialize structures holding per port information. * If there are any non-removable devices, start initializing them. * This function is hub-specific and should be run only after the hub is * configured using usb_set_first_configuration function. * @param hub_dev hub representation * @return error code */ static int usb_hub_process_hub_specific_info(usb_hub_dev_t *hub_dev) { assert(hub_dev); /* Get hub descriptor. */ usb_log_debug("Retrieving descriptor\n"); usb_pipe_t *control_pipe = &hub_dev->usb_device->ctrl_pipe; usb_hub_descriptor_header_t descriptor; size_t received_size; int opResult = usb_request_get_descriptor(control_pipe, USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_DEVICE, USB_DESCTYPE_HUB, 0, 0, &descriptor, sizeof(usb_hub_descriptor_header_t), &received_size); if (opResult != EOK) { usb_log_error("Failed to receive hub descriptor: %s.\n", str_error(opResult)); return opResult; } usb_log_debug("Setting port count to %d.\n", descriptor.port_count); hub_dev->port_count = descriptor.port_count; hub_dev->ports = calloc(hub_dev->port_count, sizeof(usb_hub_port_t)); if (!hub_dev->ports) { return ENOMEM; } for (size_t port = 0; port < hub_dev->port_count; ++port) { usb_hub_port_init( &hub_dev->ports[port], port + 1, control_pipe); } hub_dev->power_switched = !(descriptor.characteristics & HUB_CHAR_NO_POWER_SWITCH_FLAG); hub_dev->per_port_power = descriptor.characteristics & HUB_CHAR_POWER_PER_PORT_FLAG; if (!hub_dev->power_switched) { usb_log_info( "Power switching not supported, ports always powered.\n"); return EOK; } usb_log_info("Hub port power switching enabled.\n"); for (size_t port = 0; port < hub_dev->port_count; ++port) { usb_log_debug("Powering port %zu.\n", port); const int ret = usb_hub_port_set_feature( &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER); if (ret != EOK) { usb_log_error("Cannot power on port %zu: %s.\n", hub_dev->ports[port].port_number, str_error(ret)); } else { if (!hub_dev->per_port_power) { usb_log_debug("Ganged power switching, " "one port is enough.\n"); break; } } } return EOK; }
static int usb_generic_hid_client_connected(ddf_fun_t *fun) { usb_log_debug("Generic HID: Client connected.\n"); return EOK; }