/** * 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; }
/** Prepare generic usb_transfer_batch and schedule it. * @param hcd Host controller driver. * @param fun DDF fun * @param target address and endpoint number. * @param setup_data Data to use in setup stage (Control communication type) * @param in Callback for device to host communication. * @param out Callback for host to device communication. * @param arg Callback parameter. * @param name Communication identifier (for nicer output). * @return Error code. */ int hcd_send_batch( hcd_t *hcd, usb_target_t target, usb_direction_t direction, void *data, size_t size, uint64_t setup_data, usbhc_iface_transfer_in_callback_t in, usbhc_iface_transfer_out_callback_t out, void *arg, const char* name) { assert(hcd); endpoint_t *ep = usb_bus_find_ep(&hcd->bus, target.address, target.endpoint, direction); if (ep == NULL) { usb_log_error("Endpoint(%d:%d) not registered for %s.\n", target.address, target.endpoint, name); return ENOENT; } usb_log_debug2("%s %d:%d %zu(%zu).\n", name, target.address, target.endpoint, size, ep->max_packet_size); const size_t bw = bandwidth_count_usb11( ep->speed, ep->transfer_type, size, ep->max_packet_size); /* Check if we have enough bandwidth reserved */ if (ep->bandwidth < bw) { usb_log_error("Endpoint(%d:%d) %s needs %zu bw " "but only %zu is reserved.\n", ep->address, ep->endpoint, name, bw, ep->bandwidth); return ENOSPC; } if (!hcd->ops.schedule) { usb_log_error("HCD does not implement scheduler.\n"); return ENOTSUP; } /* Check for commands that reset toggle bit */ if (ep->transfer_type == USB_TRANSFER_CONTROL) { const int reset_toggle = usb_request_needs_toggle_reset( (usb_device_request_setup_packet_t *) &setup_data); if (reset_toggle >= 0) { assert(out); toggle_t *toggle = malloc(sizeof(toggle_t)); if (!toggle) return ENOMEM; toggle->target.address = target.address; toggle->target.endpoint = reset_toggle; toggle->original_callback = out; toggle->original_data = arg; toggle->hcd = hcd; arg = toggle; out = toggle_reset_callback; } } usb_transfer_batch_t *batch = usb_transfer_batch_create( ep, data, size, setup_data, in, out, arg); if (!batch) { usb_log_error("Failed to create transfer batch.\n"); return ENOMEM; } const int ret = hcd->ops.schedule(hcd, batch); if (ret != EOK) usb_transfer_batch_destroy(batch); /* Drop our own reference to ep. */ endpoint_del_ref(ep); return ret; }