Changes in uspace/lib/usbhost/src/hcd.c [b7fd2a0:5a6cc679] in mainline
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/lib/usbhost/src/hcd.c
rb7fd2a0 r5a6cc679 32 32 /** @file 33 33 * 34 */ 35 36 #include <usb/debug.h> 37 #include <usb/request.h> 34 * Host controller driver framework. Encapsulates DDF device of HC to an 35 * hc_device_t, which is passed to driver implementing hc_driver_t. 36 */ 38 37 39 38 #include <assert.h> 40 39 #include <async.h> 40 #include <ddf/interrupt.h> 41 41 #include <errno.h> 42 #include <macros.h> 43 #include <str_error.h> 44 #include <usb/debug.h> 45 #include <usb/descriptor.h> 46 #include <usb/request.h> 42 47 #include <usb_iface.h> 43 48 49 #include "bus.h" 50 #include "ddf_helpers.h" 51 #include "endpoint.h" 52 #include "usb_transfer_batch.h" 53 44 54 #include "hcd.h" 45 55 46 /** Calls ep_add_hook upon endpoint registration. 47 * @param ep Endpoint to be registered. 48 * @param arg hcd_t in disguise. 49 * @return Error code. 50 */ 51 static errno_t register_helper(endpoint_t *ep, void *arg) 52 { 53 hcd_t *hcd = arg; 54 assert(ep); 56 int hc_dev_add(ddf_dev_t *); 57 int hc_dev_remove(ddf_dev_t *); 58 int hc_dev_gone(ddf_dev_t *); 59 int hc_fun_online(ddf_fun_t *); 60 int hc_fun_offline(ddf_fun_t *); 61 62 static driver_ops_t hc_driver_ops = { 63 .dev_add = hc_dev_add, 64 .dev_remove = hc_dev_remove, 65 .dev_gone = hc_dev_gone, 66 .fun_online = hc_fun_online, 67 .fun_offline = hc_fun_offline, 68 }; 69 70 static const hc_driver_t *hc_driver; 71 72 /** 73 * The main HC driver routine. 74 */ 75 int hc_driver_main(const hc_driver_t *driver) 76 { 77 driver_t ddf_driver = { 78 .name = driver->name, 79 .driver_ops = &hc_driver_ops, 80 }; 81 82 /* Remember ops to call. */ 83 hc_driver = driver; 84 85 return ddf_driver_main(&ddf_driver); 86 } 87 88 /** 89 * IRQ handling callback. Call the bus operation. 90 * 91 * Currently, there is a bus ops lookup to find the interrupt handler. So far, 92 * the mechanism is too flexible, as it allows different instances of HC to 93 * have different IRQ handlers, disallowing us to optimize the lookup here. 94 * TODO: Make the bus mechanism less flexible in irq handling and remove the 95 * lookup. 96 */ 97 static void irq_handler(ipc_call_t *call, ddf_dev_t *dev) 98 { 99 assert(dev); 100 hc_device_t *hcd = dev_to_hcd(dev); 101 102 const uint32_t status = IPC_GET_ARG1(*call); 103 hcd->bus->ops->interrupt(hcd->bus, status); 104 } 105 106 /** 107 * Worker for the HW interrupt replacement fibril. 108 */ 109 static errno_t interrupt_polling(void *arg) 110 { 111 bus_t *bus = arg; 112 assert(bus); 113 114 if (!bus->ops->interrupt || !bus->ops->status) 115 return ENOTSUP; 116 117 uint32_t status = 0; 118 while (bus->ops->status(bus, &status) == EOK) { 119 bus->ops->interrupt(bus, status); 120 status = 0; 121 /* We should wait 1 frame - 1ms here, but this polling is a 122 * lame crutch anyway so don't hog the system. 10ms is still 123 * good enough for emergency mode */ 124 async_usleep(10000); 125 } 126 return EOK; 127 } 128 129 /** 130 * Clean the IRQ code bottom-half. 131 */ 132 static inline void irq_code_clean(irq_code_t *code) 133 { 134 if (code) { 135 free(code->ranges); 136 free(code->cmds); 137 code->ranges = NULL; 138 code->cmds = NULL; 139 code->rangecount = 0; 140 code->cmdcount = 0; 141 } 142 } 143 144 /** 145 * Register an interrupt handler. If there is a callback to setup the bottom half, 146 * invoke it and register it. Register for notifications. 147 * 148 * If this method fails, a polling fibril is started instead. 149 * 150 * @param[in] hcd Host controller device. 151 * @param[in] hw_res Resources to be used. 152 * 153 * @return IRQ capability handle on success. 154 * @return Negative error code. 155 */ 156 static errno_t hcd_ddf_setup_interrupts(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res) 157 { 55 158 assert(hcd); 56 if (hcd->ops.ep_add_hook) 57 return hcd->ops.ep_add_hook(hcd, ep); 159 irq_code_t irq_code = {0}; 160 161 if (!hc_driver->irq_code_gen) 162 return ENOTSUP; 163 164 int irq; 165 errno_t ret; 166 ret = hc_driver->irq_code_gen(&irq_code, hcd, hw_res, &irq); 167 if (ret != EOK) { 168 usb_log_error("Failed to generate IRQ code: %s.", 169 str_error(irq)); 170 return irq; 171 } 172 173 /* Register handler to avoid interrupt lockup */ 174 int irq_cap; 175 ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler, &irq_code, &irq_cap); 176 irq_code_clean(&irq_code); 177 if (ret != EOK) { 178 usb_log_error("Failed to register interrupt handler: %s.", 179 str_error(irq_cap)); 180 return irq_cap; 181 } 182 183 /* Enable interrupts */ 184 ret = hcd_ddf_enable_interrupt(hcd, irq); 185 if (ret != EOK) { 186 usb_log_error("Failed to enable interrupts: %s.", 187 str_error(ret)); 188 unregister_interrupt_handler(hcd->ddf_dev, irq_cap); 189 return ret; 190 } 191 return irq_cap; 192 } 193 194 /** 195 * Initialize HC in memory of the driver. 196 * 197 * This function does all the preparatory work for hc and rh drivers: 198 * - gets device's hw resources 199 * - attempts to enable interrupts 200 * - registers interrupt handler 201 * - calls driver specific initialization 202 * - registers root hub 203 * 204 * @param device DDF instance of the device to use 205 * @return Error code 206 */ 207 errno_t hc_dev_add(ddf_dev_t *device) 208 { 209 errno_t ret = EOK; 210 assert(device); 211 212 if (!hc_driver->hc_add) { 213 usb_log_error("Driver '%s' does not support adding devices.", hc_driver->name); 214 return ENOTSUP; 215 } 216 217 ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size); 218 if (ret != EOK) { 219 usb_log_error("Failed to setup HC device."); 220 return ret; 221 } 222 223 hc_device_t *hcd = dev_to_hcd(device); 224 225 hw_res_list_parsed_t hw_res; 226 ret = hcd_ddf_get_registers(hcd, &hw_res); 227 if (ret != EOK) { 228 usb_log_error("Failed to get register memory addresses " 229 "for `%s': %s.", ddf_dev_get_name(device), 230 str_error(ret)); 231 goto err_hcd; 232 } 233 234 ret = hc_driver->hc_add(hcd, &hw_res); 235 if (ret != EOK) { 236 usb_log_error("Failed to init HCD."); 237 goto err_hw_res; 238 } 239 240 assert(hcd->bus); 241 242 /* Setup interrupts */ 243 hcd->irq_cap = hcd_ddf_setup_interrupts(hcd, &hw_res); 244 if (hcd->irq_cap >= 0) { 245 usb_log_debug("Hw interrupts enabled."); 246 } 247 248 /* Claim the device from BIOS */ 249 if (hc_driver->claim) 250 ret = hc_driver->claim(hcd); 251 if (ret != EOK) { 252 usb_log_error("Failed to claim `%s' for `%s': %s", 253 ddf_dev_get_name(device), hc_driver->name, str_error(ret)); 254 goto err_irq; 255 } 256 257 /* Start hw */ 258 if (hc_driver->start) 259 ret = hc_driver->start(hcd); 260 if (ret != EOK) { 261 usb_log_error("Failed to start HCD: %s.", str_error(ret)); 262 goto err_irq; 263 } 264 265 const bus_ops_t *ops = hcd->bus->ops; 266 267 /* Need working irq replacement to setup root hub */ 268 if (hcd->irq_cap < 0 && ops->status) { 269 hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus); 270 if (!hcd->polling_fibril) { 271 usb_log_error("Failed to create polling fibril"); 272 ret = ENOMEM; 273 goto err_started; 274 } 275 fibril_add_ready(hcd->polling_fibril); 276 usb_log_warning("Failed to enable interrupts: %s." 277 " Falling back to polling.", str_error(hcd->irq_cap)); 278 } 279 280 /* 281 * Creating root hub registers a new USB device so HC 282 * needs to be ready at this time. 283 */ 284 if (hc_driver->setup_root_hub) 285 ret = hc_driver->setup_root_hub(hcd); 286 if (ret != EOK) { 287 usb_log_error("Failed to setup HC root hub: %s.", 288 str_error(ret)); 289 goto err_polling; 290 } 291 292 usb_log_info("Controlling new `%s' device `%s'.", 293 hc_driver->name, ddf_dev_get_name(device)); 58 294 return EOK; 59 } 60 61 /** Calls ep_remove_hook upon endpoint removal. 62 * @param ep Endpoint to be unregistered. 63 * @param arg hcd_t in disguise. 64 */ 65 static void unregister_helper(endpoint_t *ep, void *arg) 66 { 67 hcd_t *hcd = arg; 68 assert(ep); 69 assert(hcd); 70 if (hcd->ops.ep_remove_hook) 71 hcd->ops.ep_remove_hook(hcd, ep); 72 } 73 74 /** Calls ep_remove_hook upon endpoint removal. Prints warning. 75 * * @param ep Endpoint to be unregistered. 76 * * @param arg hcd_t in disguise. 77 * */ 78 static void unregister_helper_warn(endpoint_t *ep, void *arg) 79 { 80 assert(ep); 81 usb_log_warning("Endpoint %d:%d %s was left behind, removing.\n", 82 ep->address, ep->endpoint, usb_str_direction(ep->direction)); 83 unregister_helper(ep, arg); 84 } 85 86 87 /** Initialize hcd_t structure. 88 * Initializes device and endpoint managers. Sets data and hook pointer to NULL. 89 * 90 * @param hcd hcd_t structure to initialize, non-null. 91 * @param max_speed Maximum supported USB speed (full, high). 92 * @param bandwidth Available bandwidth, passed to endpoint manager. 93 * @param bw_count Bandwidth compute function, passed to endpoint manager. 94 */ 95 void hcd_init(hcd_t *hcd, usb_speed_t max_speed, size_t bandwidth, 96 bw_count_func_t bw_count) 97 { 98 assert(hcd); 99 usb_bus_init(&hcd->bus, bandwidth, bw_count, max_speed); 100 101 hcd_set_implementation(hcd, NULL, NULL); 102 } 103 104 errno_t hcd_request_address(hcd_t *hcd, usb_speed_t speed, usb_address_t *address) 105 { 106 assert(hcd); 107 return usb_bus_request_address(&hcd->bus, address, false, speed); 108 } 109 110 errno_t hcd_release_address(hcd_t *hcd, usb_address_t address) 111 { 112 assert(hcd); 113 return usb_bus_remove_address(&hcd->bus, address, 114 unregister_helper_warn, hcd); 115 } 116 117 errno_t hcd_reserve_default_address(hcd_t *hcd, usb_speed_t speed) 118 { 119 assert(hcd); 120 usb_address_t address = 0; 121 return usb_bus_request_address(&hcd->bus, &address, true, speed); 122 } 123 124 errno_t hcd_add_ep(hcd_t *hcd, usb_target_t target, usb_direction_t dir, 125 usb_transfer_type_t type, size_t max_packet_size, unsigned packets, 126 size_t size, usb_address_t tt_address, unsigned tt_port) 127 { 128 assert(hcd); 129 return usb_bus_add_ep(&hcd->bus, target.address, 130 target.endpoint, dir, type, max_packet_size, packets, size, 131 register_helper, hcd, tt_address, tt_port); 132 } 133 134 errno_t hcd_remove_ep(hcd_t *hcd, usb_target_t target, usb_direction_t dir) 135 { 136 assert(hcd); 137 return usb_bus_remove_ep(&hcd->bus, target.address, 138 target.endpoint, dir, unregister_helper, hcd); 139 } 140 141 142 typedef struct { 143 void *original_data; 144 usbhc_iface_transfer_out_callback_t original_callback; 145 usb_target_t target; 146 hcd_t *hcd; 147 } toggle_t; 148 149 static void toggle_reset_callback(errno_t retval, void *arg) 150 { 151 assert(arg); 152 toggle_t *toggle = arg; 153 if (retval == EOK) { 154 usb_log_debug2("Reseting toggle on %d:%d.\n", 155 toggle->target.address, toggle->target.endpoint); 156 usb_bus_reset_toggle(&toggle->hcd->bus, 157 toggle->target, toggle->target.endpoint == 0); 158 } 159 160 toggle->original_callback(retval, toggle->original_data); 161 } 162 163 /** Prepare generic usb_transfer_batch and schedule it. 164 * @param hcd Host controller driver. 165 * @param fun DDF fun 166 * @param target address and endpoint number. 167 * @param setup_data Data to use in setup stage (Control communication type) 168 * @param in Callback for device to host communication. 169 * @param out Callback for host to device communication. 170 * @param arg Callback parameter. 171 * @param name Communication identifier (for nicer output). 172 * @return Error code. 173 */ 174 errno_t hcd_send_batch( 175 hcd_t *hcd, usb_target_t target, usb_direction_t direction, 176 void *data, size_t size, uint64_t setup_data, 177 usbhc_iface_transfer_in_callback_t in, 178 usbhc_iface_transfer_out_callback_t out, void *arg, const char* name) 179 { 180 assert(hcd); 181 182 endpoint_t *ep = usb_bus_find_ep(&hcd->bus, 183 target.address, target.endpoint, direction); 184 if (ep == NULL) { 185 usb_log_error("Endpoint(%d:%d) not registered for %s.\n", 186 target.address, target.endpoint, name); 187 return ENOENT; 188 } 189 190 usb_log_debug2("%s %d:%d %zu(%zu).\n", 191 name, target.address, target.endpoint, size, ep->max_packet_size); 192 193 const size_t bw = bandwidth_count_usb11( 194 ep->speed, ep->transfer_type, size, ep->max_packet_size); 195 /* Check if we have enough bandwidth reserved */ 196 if (ep->bandwidth < bw) { 197 usb_log_error("Endpoint(%d:%d) %s needs %zu bw " 198 "but only %zu is reserved.\n", 199 ep->address, ep->endpoint, name, bw, ep->bandwidth); 200 return ENOSPC; 201 } 202 if (!hcd->ops.schedule) { 203 usb_log_error("HCD does not implement scheduler.\n"); 204 return ENOTSUP; 205 } 206 207 /* Check for commands that reset toggle bit */ 208 if (ep->transfer_type == USB_TRANSFER_CONTROL) { 209 const int reset_toggle = usb_request_needs_toggle_reset( 210 (usb_device_request_setup_packet_t *) &setup_data); 211 if (reset_toggle >= 0) { 212 assert(out); 213 toggle_t *toggle = malloc(sizeof(toggle_t)); 214 if (!toggle) 215 return ENOMEM; 216 toggle->target.address = target.address; 217 toggle->target.endpoint = reset_toggle; 218 toggle->original_callback = out; 219 toggle->original_data = arg; 220 toggle->hcd = hcd; 221 222 arg = toggle; 223 out = toggle_reset_callback; 224 } 225 } 226 227 usb_transfer_batch_t *batch = usb_transfer_batch_create( 228 ep, data, size, setup_data, in, out, arg); 229 if (!batch) { 230 usb_log_error("Failed to create transfer batch.\n"); 231 return ENOMEM; 232 } 233 234 const errno_t ret = hcd->ops.schedule(hcd, batch); 235 if (ret != EOK) 236 usb_transfer_batch_destroy(batch); 237 238 /* Drop our own reference to ep. */ 239 endpoint_del_ref(ep); 240 295 296 err_polling: 297 // TODO: Stop the polling fibril (refactor the interrupt_polling func) 298 // 299 err_started: 300 if (hc_driver->stop) 301 hc_driver->stop(hcd); 302 err_irq: 303 unregister_interrupt_handler(device, hcd->irq_cap); 304 if (hc_driver->hc_remove) 305 hc_driver->hc_remove(hcd); 306 err_hw_res: 307 hw_res_list_parsed_clean(&hw_res); 308 err_hcd: 309 hcd_ddf_clean_hc(hcd); 241 310 return ret; 242 311 } 243 312 244 typedef struct { 245 volatile unsigned done; 246 errno_t ret; 247 size_t size; 248 } sync_data_t; 249 250 static void transfer_in_cb(errno_t ret, size_t size, void* data) 251 { 252 sync_data_t *d = data; 253 assert(d); 254 d->ret = ret; 255 d->done = 1; 256 d->size = size; 257 } 258 259 static void transfer_out_cb(errno_t ret, void* data) 260 { 261 sync_data_t *d = data; 262 assert(data); 263 d->ret = ret; 264 d->done = 1; 265 } 266 267 /** this is really ugly version of sync usb communication */ 268 errno_t hcd_send_batch_sync( 269 hcd_t *hcd, usb_target_t target, usb_direction_t dir, 270 void *data, size_t size, uint64_t setup_data, const char* name, size_t *out_size) 271 { 272 assert(hcd); 273 sync_data_t sd = { .done = 0, .ret = EBUSY, .size = size }; 274 275 const errno_t ret = hcd_send_batch(hcd, target, dir, data, size, setup_data, 276 dir == USB_DIRECTION_IN ? transfer_in_cb : NULL, 277 dir == USB_DIRECTION_OUT ? transfer_out_cb : NULL, &sd, name); 278 if (ret != EOK) 279 return ret; 280 281 while (!sd.done) { 282 async_usleep(1000); 283 } 284 285 if (sd.ret == EOK) 286 *out_size = sd.size; 287 return sd.ret; 313 errno_t hc_dev_remove(ddf_dev_t *dev) 314 { 315 errno_t err; 316 hc_device_t *hcd = dev_to_hcd(dev); 317 318 if (hc_driver->stop) 319 if ((err = hc_driver->stop(hcd))) 320 return err; 321 322 unregister_interrupt_handler(dev, hcd->irq_cap); 323 324 if (hc_driver->hc_remove) 325 if ((err = hc_driver->hc_remove(hcd))) 326 return err; 327 328 hcd_ddf_clean_hc(hcd); 329 330 // TODO probably not complete 331 332 return EOK; 333 } 334 335 errno_t hc_dev_gone(ddf_dev_t *dev) 336 { 337 errno_t err = ENOTSUP; 338 hc_device_t *hcd = dev_to_hcd(dev); 339 340 if (hc_driver->hc_gone) 341 err = hc_driver->hc_gone(hcd); 342 343 hcd_ddf_clean_hc(hcd); 344 345 return err; 346 } 347 348 errno_t hc_fun_online(ddf_fun_t *fun) 349 { 350 assert(fun); 351 352 device_t *dev = ddf_fun_data_get(fun); 353 assert(dev); 354 355 usb_log_info("Device(%d): Requested to be brought online.", dev->address); 356 return bus_device_online(dev); 357 } 358 359 int hc_fun_offline(ddf_fun_t *fun) 360 { 361 assert(fun); 362 363 device_t *dev = ddf_fun_data_get(fun); 364 assert(dev); 365 366 usb_log_info("Device(%d): Requested to be taken offline.", dev->address); 367 return bus_device_offline(dev); 288 368 } 289 369
Note:
See TracChangeset
for help on using the changeset viewer.