Changeset df6ded8 in mainline for uspace/lib/usbhost/src/hcd.c
- Timestamp:
- 2018-02-28T16:37:50Z (6 years ago)
- Branches:
- lfn, master, serial, ticket/834-toolchain-update, topic/msim-upgrade, topic/simplify-dev-export
- Children:
- 1b20da0
- Parents:
- f5e5f73 (diff), b2dca8de (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)
links above to see all the changes relative to each parent. - git-author:
- Jakub Jermar <jakub@…> (2018-02-28 16:06:42)
- git-committer:
- Jakub Jermar <jakub@…> (2018-02-28 16:37:50)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/lib/usbhost/src/hcd.c
rf5e5f73 rdf6ded8 1 1 /* 2 2 * Copyright (c) 2011 Jan Vesely 3 * Copyright (c) 2018 Ondrej Hlavaty 3 4 * All rights reserved. 4 5 * … … 32 33 /** @file 33 34 * 34 */ 35 36 #include <usb/debug.h> 37 #include <usb/request.h> 35 * Host controller driver framework. Encapsulates DDF device of HC to an 36 * hc_device_t, which is passed to driver implementing hc_driver_t. 37 */ 38 38 39 39 #include <assert.h> 40 40 #include <async.h> 41 #include <ddf/interrupt.h> 41 42 #include <errno.h> 43 #include <macros.h> 44 #include <str_error.h> 45 #include <usb/debug.h> 46 #include <usb/descriptor.h> 47 #include <usb/request.h> 42 48 #include <usb_iface.h> 43 49 50 #include "bus.h" 51 #include "ddf_helpers.h" 52 #include "endpoint.h" 53 #include "usb_transfer_batch.h" 54 44 55 #include "hcd.h" 45 56 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); 57 int hc_dev_add(ddf_dev_t *); 58 int hc_dev_remove(ddf_dev_t *); 59 int hc_dev_gone(ddf_dev_t *); 60 int hc_fun_online(ddf_fun_t *); 61 int hc_fun_offline(ddf_fun_t *); 62 63 static driver_ops_t hc_driver_ops = { 64 .dev_add = hc_dev_add, 65 .dev_remove = hc_dev_remove, 66 .dev_gone = hc_dev_gone, 67 .fun_online = hc_fun_online, 68 .fun_offline = hc_fun_offline, 69 }; 70 71 static const hc_driver_t *hc_driver; 72 73 /** 74 * The main HC driver routine. 75 */ 76 int hc_driver_main(const hc_driver_t *driver) 77 { 78 driver_t ddf_driver = { 79 .name = driver->name, 80 .driver_ops = &hc_driver_ops, 81 }; 82 83 /* Remember ops to call. */ 84 hc_driver = driver; 85 86 return ddf_driver_main(&ddf_driver); 87 } 88 89 /** 90 * IRQ handling callback. Call the bus operation. 91 * 92 * Currently, there is a bus ops lookup to find the interrupt handler. So far, 93 * the mechanism is too flexible, as it allows different instances of HC to 94 * have different IRQ handlers, disallowing us to optimize the lookup here. 95 * TODO: Make the bus mechanism less flexible in irq handling and remove the 96 * lookup. 97 */ 98 static void irq_handler(ipc_call_t *call, ddf_dev_t *dev) 99 { 100 assert(dev); 101 hc_device_t *hcd = dev_to_hcd(dev); 102 103 const uint32_t status = IPC_GET_ARG1(*call); 104 hcd->bus->ops->interrupt(hcd->bus, status); 105 } 106 107 /** 108 * Worker for the HW interrupt replacement fibril. 109 */ 110 static errno_t interrupt_polling(void *arg) 111 { 112 bus_t *bus = arg; 113 assert(bus); 114 115 if (!bus->ops->interrupt || !bus->ops->status) 116 return ENOTSUP; 117 118 uint32_t status = 0; 119 while (bus->ops->status(bus, &status) == EOK) { 120 bus->ops->interrupt(bus, status); 121 status = 0; 122 /* We should wait 1 frame - 1ms here, but this polling is a 123 * lame crutch anyway so don't hog the system. 10ms is still 124 * good enough for emergency mode */ 125 async_usleep(10000); 126 } 127 return EOK; 128 } 129 130 /** 131 * Clean the IRQ code bottom-half. 132 */ 133 static inline void irq_code_clean(irq_code_t *code) 134 { 135 if (code) { 136 free(code->ranges); 137 free(code->cmds); 138 code->ranges = NULL; 139 code->cmds = NULL; 140 code->rangecount = 0; 141 code->cmdcount = 0; 142 } 143 } 144 145 /** 146 * Register an interrupt handler. If there is a callback to setup the bottom half, 147 * invoke it and register it. Register for notifications. 148 * 149 * If this method fails, a polling fibril is started instead. 150 * 151 * @param[in] hcd Host controller device. 152 * @param[in] hw_res Resources to be used. 153 * 154 * @return IRQ capability handle on success. 155 * @return Negative error code. 156 */ 157 static errno_t hcd_ddf_setup_interrupts(hc_device_t *hcd, 158 const hw_res_list_parsed_t *hw_res) 159 { 55 160 assert(hcd); 56 if (hcd->ops.ep_add_hook) 57 return hcd->ops.ep_add_hook(hcd, ep); 161 irq_code_t irq_code = { 0 }; 162 163 if (!hc_driver->irq_code_gen) 164 return ENOTSUP; 165 166 int irq; 167 errno_t ret; 168 ret = hc_driver->irq_code_gen(&irq_code, hcd, hw_res, &irq); 169 if (ret != EOK) { 170 usb_log_error("Failed to generate IRQ code: %s.", 171 str_error(irq)); 172 return irq; 173 } 174 175 /* Register handler to avoid interrupt lockup */ 176 int irq_cap; 177 ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler, 178 &irq_code, &irq_cap); 179 irq_code_clean(&irq_code); 180 if (ret != EOK) { 181 usb_log_error("Failed to register interrupt handler: %s.", 182 str_error(irq_cap)); 183 return irq_cap; 184 } 185 186 /* Enable interrupts */ 187 ret = hcd_ddf_enable_interrupt(hcd, irq); 188 if (ret != EOK) { 189 usb_log_error("Failed to enable interrupts: %s.", 190 str_error(ret)); 191 unregister_interrupt_handler(hcd->ddf_dev, irq_cap); 192 return ret; 193 } 194 return irq_cap; 195 } 196 197 /** 198 * Initialize HC in memory of the driver. 199 * 200 * This function does all the preparatory work for hc and rh drivers: 201 * - gets device's hw resources 202 * - attempts to enable interrupts 203 * - registers interrupt handler 204 * - calls driver specific initialization 205 * - registers root hub 206 * 207 * @param device DDF instance of the device to use 208 * @return Error code 209 */ 210 errno_t hc_dev_add(ddf_dev_t *device) 211 { 212 errno_t ret = EOK; 213 assert(device); 214 215 if (!hc_driver->hc_add) { 216 usb_log_error("Driver '%s' does not support adding devices.", 217 hc_driver->name); 218 return ENOTSUP; 219 } 220 221 ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size); 222 if (ret != EOK) { 223 usb_log_error("Failed to setup HC device."); 224 return ret; 225 } 226 227 hc_device_t *hcd = dev_to_hcd(device); 228 229 hw_res_list_parsed_t hw_res; 230 ret = hcd_ddf_get_registers(hcd, &hw_res); 231 if (ret != EOK) { 232 usb_log_error("Failed to get register memory addresses " 233 "for `%s': %s.", ddf_dev_get_name(device), 234 str_error(ret)); 235 goto err_hcd; 236 } 237 238 ret = hc_driver->hc_add(hcd, &hw_res); 239 if (ret != EOK) { 240 usb_log_error("Failed to init HCD."); 241 goto err_hw_res; 242 } 243 244 assert(hcd->bus); 245 246 /* Setup interrupts */ 247 hcd->irq_cap = hcd_ddf_setup_interrupts(hcd, &hw_res); 248 if (hcd->irq_cap >= 0) { 249 usb_log_debug("Hw interrupts enabled."); 250 } 251 252 /* Claim the device from BIOS */ 253 if (hc_driver->claim) 254 ret = hc_driver->claim(hcd); 255 if (ret != EOK) { 256 usb_log_error("Failed to claim `%s' for `%s': %s", 257 ddf_dev_get_name(device), hc_driver->name, str_error(ret)); 258 goto err_irq; 259 } 260 261 /* Start hw */ 262 if (hc_driver->start) 263 ret = hc_driver->start(hcd); 264 if (ret != EOK) { 265 usb_log_error("Failed to start HCD: %s.", str_error(ret)); 266 goto err_irq; 267 } 268 269 const bus_ops_t *ops = hcd->bus->ops; 270 271 /* Need working irq replacement to setup root hub */ 272 if (hcd->irq_cap < 0 && ops->status) { 273 hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus); 274 if (!hcd->polling_fibril) { 275 usb_log_error("Failed to create polling fibril"); 276 ret = ENOMEM; 277 goto err_started; 278 } 279 fibril_add_ready(hcd->polling_fibril); 280 usb_log_warning("Failed to enable interrupts: %s." 281 " Falling back to polling.", str_error(hcd->irq_cap)); 282 } 283 284 /* 285 * Creating root hub registers a new USB device so HC 286 * needs to be ready at this time. 287 */ 288 if (hc_driver->setup_root_hub) 289 ret = hc_driver->setup_root_hub(hcd); 290 if (ret != EOK) { 291 usb_log_error("Failed to setup HC root hub: %s.", 292 str_error(ret)); 293 goto err_polling; 294 } 295 296 usb_log_info("Controlling new `%s' device `%s'.", 297 hc_driver->name, ddf_dev_get_name(device)); 58 298 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 299 300 err_polling: 301 // TODO: Stop the polling fibril (refactor the interrupt_polling func) 302 // 303 err_started: 304 if (hc_driver->stop) 305 hc_driver->stop(hcd); 306 err_irq: 307 unregister_interrupt_handler(device, hcd->irq_cap); 308 if (hc_driver->hc_remove) 309 hc_driver->hc_remove(hcd); 310 err_hw_res: 311 hw_res_list_parsed_clean(&hw_res); 312 err_hcd: 313 hcd_ddf_clean_hc(hcd); 241 314 return ret; 242 315 } 243 316 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; 317 errno_t hc_dev_remove(ddf_dev_t *dev) 318 { 319 errno_t err; 320 hc_device_t *hcd = dev_to_hcd(dev); 321 322 if (hc_driver->stop) 323 if ((err = hc_driver->stop(hcd))) 324 return err; 325 326 unregister_interrupt_handler(dev, hcd->irq_cap); 327 328 if (hc_driver->hc_remove) 329 if ((err = hc_driver->hc_remove(hcd))) 330 return err; 331 332 hcd_ddf_clean_hc(hcd); 333 334 // TODO probably not complete 335 336 return EOK; 337 } 338 339 errno_t hc_dev_gone(ddf_dev_t *dev) 340 { 341 errno_t err = ENOTSUP; 342 hc_device_t *hcd = dev_to_hcd(dev); 343 344 if (hc_driver->hc_gone) 345 err = hc_driver->hc_gone(hcd); 346 347 hcd_ddf_clean_hc(hcd); 348 349 return err; 350 } 351 352 errno_t hc_fun_online(ddf_fun_t *fun) 353 { 354 assert(fun); 355 356 device_t *dev = ddf_fun_data_get(fun); 357 assert(dev); 358 359 usb_log_info("Device(%d): Requested to be brought online.", dev->address); 360 return bus_device_online(dev); 361 } 362 363 int hc_fun_offline(ddf_fun_t *fun) 364 { 365 assert(fun); 366 367 device_t *dev = ddf_fun_data_get(fun); 368 assert(dev); 369 370 usb_log_info("Device(%d): Requested to be taken offline.", dev->address); 371 return bus_device_offline(dev); 288 372 } 289 373
Note:
See TracChangeset
for help on using the changeset viewer.