| [c9c0e41] | 1 | /*
|
|---|
| 2 | * Copyright (c) 2017 Jaroslav Jindrak
|
|---|
| 3 | * All rights reserved.
|
|---|
| 4 | *
|
|---|
| 5 | * Redistribution and use in source and binary forms, with or without
|
|---|
| 6 | * modification, are permitted provided that the following conditions
|
|---|
| 7 | * are met:
|
|---|
| 8 | *
|
|---|
| 9 | * - Redistributions of source code must retain the above copyright
|
|---|
| 10 | * notice, this list of conditions and the following disclaimer.
|
|---|
| 11 | * - Redistributions in binary form must reproduce the above copyright
|
|---|
| 12 | * notice, this list of conditions and the following disclaimer in the
|
|---|
| 13 | * documentation and/or other materials provided with the distribution.
|
|---|
| 14 | * - The name of the author may not be used to endorse or promote products
|
|---|
| 15 | * derived from this software without specific prior written permission.
|
|---|
| 16 | *
|
|---|
| 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|---|
| 18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|---|
| 19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|---|
| 20 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|---|
| 21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|---|
| 22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|---|
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|---|
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|---|
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|---|
| 26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|---|
| 27 | */
|
|---|
| 28 |
|
|---|
| 29 | /** @addtogroup drvusbxhci
|
|---|
| 30 | * @{
|
|---|
| 31 | */
|
|---|
| 32 | /** @file
|
|---|
| 33 | * @brief Command sending functions.
|
|---|
| 34 | */
|
|---|
| 35 |
|
|---|
| 36 | #include <errno.h>
|
|---|
| 37 | #include <str_error.h>
|
|---|
| 38 | #include <usb/debug.h>
|
|---|
| 39 | #include <usb/host/utils/malloc32.h>
|
|---|
| 40 | #include "commands.h"
|
|---|
| 41 | #include "debug.h"
|
|---|
| 42 | #include "hc.h"
|
|---|
| [8db42f7] | 43 | #include "hw_struct/context.h"
|
|---|
| [c9c0e41] | 44 | #include "hw_struct/trb.h"
|
|---|
| 45 |
|
|---|
| [1b78a7c1] | 46 | #define TRB_SET_TCS(trb, tcs) (trb).control |= host2xhci(32, ((tcs &0x1) << 9))
|
|---|
| 47 | #define TRB_SET_TYPE(trb, type) (trb).control |= host2xhci(32, (type) << 10)
|
|---|
| [b724494] | 48 | #define TRB_SET_DC(trb, dc) (trb).control |= host2xhci(32, (dc) << 9)
|
|---|
| [1b78a7c1] | 49 | #define TRB_SET_EP(trb, ep) (trb).control |= host2xhci(32, ((ep) & 0x5) << 16)
|
|---|
| [0cabd10] | 50 | #define TRB_SET_STREAM(trb, st) (trb).control |= host2xhci(32, ((st) & 0xFFFF) << 16)
|
|---|
| [1b78a7c1] | 51 | #define TRB_SET_SUSP(trb, susp) (trb).control |= host2xhci(32, ((susp) & 0x1) << 23)
|
|---|
| 52 | #define TRB_SET_SLOT(trb, slot) (trb).control |= host2xhci(32, (slot) << 24)
|
|---|
| [60af4cdb] | 53 | #define TRB_SET_DEV_SPEED(trb, speed) (trb).control |= host2xhci(32, (speed & 0xF) << 16)
|
|---|
| [1b78a7c1] | 54 |
|
|---|
| [0cabd10] | 55 | /**
|
|---|
| 56 | * TODO: Not sure about SCT and DCS (see section 6.4.3.9).
|
|---|
| 57 | */
|
|---|
| 58 | #define TRB_SET_DEQUEUE_PTR(trb, dptr) (trb).parameter |= host2xhci(64, (dptr))
|
|---|
| [548c123] | 59 | #define TRB_SET_ICTX(trb, phys) (trb).parameter |= host2xhci(64, phys_addr & (~0xF))
|
|---|
| [1b78a7c1] | 60 |
|
|---|
| 61 | #define TRB_GET_CODE(trb) XHCI_DWORD_EXTRACT((trb).status, 31, 24)
|
|---|
| 62 | #define TRB_GET_SLOT(trb) XHCI_DWORD_EXTRACT((trb).control, 31, 24)
|
|---|
| 63 | #define TRB_GET_PHYS(trb) (XHCI_QWORD_EXTRACT((trb).parameter, 63, 4) << 4)
|
|---|
| 64 |
|
|---|
| [c3d926f3] | 65 | /* Control functions */
|
|---|
| 66 |
|
|---|
| [110d795] | 67 | int xhci_init_commands(xhci_hc_t *hc)
|
|---|
| 68 | {
|
|---|
| 69 | assert(hc);
|
|---|
| 70 |
|
|---|
| 71 | list_initialize(&hc->commands);
|
|---|
| [74b852b] | 72 |
|
|---|
| 73 | fibril_mutex_initialize(&hc->commands_mtx);
|
|---|
| 74 |
|
|---|
| [110d795] | 75 | return EOK;
|
|---|
| 76 | }
|
|---|
| 77 |
|
|---|
| [c46c356] | 78 | void xhci_fini_commands(xhci_hc_t *hc)
|
|---|
| 79 | {
|
|---|
| 80 | // Note: Untested.
|
|---|
| 81 | assert(hc);
|
|---|
| 82 | }
|
|---|
| 83 |
|
|---|
| [c3d926f3] | 84 | void xhci_cmd_init(xhci_cmd_t *cmd, xhci_cmd_type_t type)
|
|---|
| [110d795] | 85 | {
|
|---|
| [c3d926f3] | 86 | memset(cmd, 0, sizeof(*cmd));
|
|---|
| [110d795] | 87 |
|
|---|
| [c3d926f3] | 88 | link_initialize(&cmd->_header.link);
|
|---|
| [110d795] | 89 |
|
|---|
| [c3d926f3] | 90 | fibril_mutex_initialize(&cmd->_header.completed_mtx);
|
|---|
| 91 | fibril_condvar_initialize(&cmd->_header.completed_cv);
|
|---|
| [04df063] | 92 |
|
|---|
| [c3d926f3] | 93 | cmd->_header.cmd = type;
|
|---|
| 94 | cmd->_header.timeout = XHCI_DEFAULT_TIMEOUT;
|
|---|
| [4688350b] | 95 | }
|
|---|
| 96 |
|
|---|
| [c3d926f3] | 97 | void xhci_cmd_fini(xhci_cmd_t *cmd)
|
|---|
| [4688350b] | 98 | {
|
|---|
| [c3d926f3] | 99 | list_remove(&cmd->_header.link);
|
|---|
| [110d795] | 100 |
|
|---|
| [c3d926f3] | 101 | if (cmd->input_ctx) {
|
|---|
| 102 | free32(cmd->input_ctx);
|
|---|
| 103 | };
|
|---|
| [110d795] | 104 |
|
|---|
| [c3d926f3] | 105 | if (cmd->bandwidth_ctx) {
|
|---|
| 106 | free32(cmd->bandwidth_ctx);
|
|---|
| 107 | }
|
|---|
| [9304b66] | 108 |
|
|---|
| [c3d926f3] | 109 | if (cmd->_header.async) {
|
|---|
| 110 | free(cmd);
|
|---|
| 111 | }
|
|---|
| [110d795] | 112 | }
|
|---|
| 113 |
|
|---|
| [2fa43d1] | 114 | static inline xhci_cmd_t *get_command(xhci_hc_t *hc, uint64_t phys)
|
|---|
| [110d795] | 115 | {
|
|---|
| [74b852b] | 116 | fibril_mutex_lock(&hc->commands_mtx);
|
|---|
| 117 |
|
|---|
| [110d795] | 118 | link_t *cmd_link = list_first(&hc->commands);
|
|---|
| 119 |
|
|---|
| [2fa43d1] | 120 | while (cmd_link != NULL) {
|
|---|
| [c3d926f3] | 121 | xhci_cmd_t *cmd = list_get_instance(cmd_link, xhci_cmd_t, _header.link);
|
|---|
| [2fa43d1] | 122 |
|
|---|
| [c3d926f3] | 123 | if (cmd->_header.trb_phys == phys)
|
|---|
| [2fa43d1] | 124 | break;
|
|---|
| 125 |
|
|---|
| 126 | cmd_link = list_next(cmd_link, &hc->commands);
|
|---|
| 127 | }
|
|---|
| 128 |
|
|---|
| [110d795] | 129 | if (cmd_link != NULL) {
|
|---|
| 130 | list_remove(cmd_link);
|
|---|
| [74b852b] | 131 | fibril_mutex_unlock(&hc->commands_mtx);
|
|---|
| [9f5b613] | 132 |
|
|---|
| [c3d926f3] | 133 | return list_get_instance(cmd_link, xhci_cmd_t, _header.link);
|
|---|
| [110d795] | 134 | }
|
|---|
| 135 |
|
|---|
| [74b852b] | 136 | fibril_mutex_unlock(&hc->commands_mtx);
|
|---|
| [110d795] | 137 | return NULL;
|
|---|
| 138 | }
|
|---|
| 139 |
|
|---|
| [548c123] | 140 | static inline int enqueue_command(xhci_hc_t *hc, xhci_cmd_t *cmd, unsigned doorbell, unsigned target)
|
|---|
| [481af21e] | 141 | {
|
|---|
| [c058a388] | 142 | assert(hc);
|
|---|
| [548c123] | 143 | assert(cmd);
|
|---|
| 144 |
|
|---|
| [74b852b] | 145 | fibril_mutex_lock(&hc->commands_mtx);
|
|---|
| [c3d926f3] | 146 | list_append(&cmd->_header.link, &hc->commands);
|
|---|
| [74b852b] | 147 | fibril_mutex_unlock(&hc->commands_mtx);
|
|---|
| [c058a388] | 148 |
|
|---|
| [c3d926f3] | 149 | xhci_trb_ring_enqueue(&hc->command_ring, &cmd->_header.trb, &cmd->_header.trb_phys);
|
|---|
| [a0be5d0] | 150 | hc_ring_doorbell(hc, doorbell, target);
|
|---|
| [481af21e] | 151 |
|
|---|
| [548c123] | 152 | usb_log_debug2("HC(%p): Sent command:", hc);
|
|---|
| [c3d926f3] | 153 | xhci_dump_trb(&cmd->_header.trb);
|
|---|
| [481af21e] | 154 |
|
|---|
| 155 | return EOK;
|
|---|
| 156 | }
|
|---|
| 157 |
|
|---|
| [3dc519f] | 158 | void xhci_stop_command_ring(xhci_hc_t *hc)
|
|---|
| 159 | {
|
|---|
| 160 | assert(hc);
|
|---|
| 161 |
|
|---|
| 162 | XHCI_REG_SET(hc->op_regs, XHCI_OP_CS, 1);
|
|---|
| 163 |
|
|---|
| 164 | /**
|
|---|
| 165 | * Note: There is a bug in qemu that checks CS only when CRCR_HI
|
|---|
| 166 | * is written, this (and the read/write in abort) ensures
|
|---|
| 167 | * the command rings stops.
|
|---|
| 168 | */
|
|---|
| 169 | XHCI_REG_WR(hc->op_regs, XHCI_OP_CRCR_HI, XHCI_REG_RD(hc->op_regs, XHCI_OP_CRCR_HI));
|
|---|
| 170 | }
|
|---|
| 171 |
|
|---|
| 172 | void xhci_abort_command_ring(xhci_hc_t *hc)
|
|---|
| 173 | {
|
|---|
| 174 | assert(hc);
|
|---|
| 175 |
|
|---|
| 176 | XHCI_REG_WR(hc->op_regs, XHCI_OP_CA, 1);
|
|---|
| 177 | XHCI_REG_WR(hc->op_regs, XHCI_OP_CRCR_HI, XHCI_REG_RD(hc->op_regs, XHCI_OP_CRCR_HI));
|
|---|
| 178 | }
|
|---|
| 179 |
|
|---|
| 180 | void xhci_start_command_ring(xhci_hc_t *hc)
|
|---|
| 181 | {
|
|---|
| 182 | assert(hc);
|
|---|
| 183 |
|
|---|
| 184 | XHCI_REG_WR(hc->op_regs, XHCI_OP_CRR, 1);
|
|---|
| [a0be5d0] | 185 | hc_ring_doorbell(hc, 0, 0);
|
|---|
| [3dc519f] | 186 | }
|
|---|
| 187 |
|
|---|
| [4fa5342] | 188 | static const char *trb_codes [] = {
|
|---|
| 189 | #define TRBC(t) [XHCI_TRBC_##t] = #t
|
|---|
| 190 | TRBC(INVALID),
|
|---|
| 191 | TRBC(SUCCESS),
|
|---|
| 192 | TRBC(DATA_BUFFER_ERROR),
|
|---|
| 193 | TRBC(BABBLE_DETECTED_ERROR),
|
|---|
| 194 | TRBC(USB_TRANSACTION_ERROR),
|
|---|
| 195 | TRBC(TRB_ERROR),
|
|---|
| 196 | TRBC(STALL_ERROR),
|
|---|
| 197 | TRBC(RESOURCE_ERROR),
|
|---|
| 198 | TRBC(BANDWIDTH_ERROR),
|
|---|
| 199 | TRBC(NO_SLOTS_ERROR),
|
|---|
| 200 | TRBC(INVALID_STREAM_ERROR),
|
|---|
| 201 | TRBC(SLOT_NOT_ENABLED_ERROR),
|
|---|
| 202 | TRBC(EP_NOT_ENABLED_ERROR),
|
|---|
| 203 | TRBC(SHORT_PACKET),
|
|---|
| 204 | TRBC(RING_UNDERRUN),
|
|---|
| 205 | TRBC(RING_OVERRUN),
|
|---|
| 206 | TRBC(VF_EVENT_RING_FULL),
|
|---|
| 207 | TRBC(PARAMETER_ERROR),
|
|---|
| 208 | TRBC(BANDWIDTH_OVERRUN_ERROR),
|
|---|
| 209 | TRBC(CONTEXT_STATE_ERROR),
|
|---|
| 210 | TRBC(NO_PING_RESPONSE_ERROR),
|
|---|
| 211 | TRBC(EVENT_RING_FULL_ERROR),
|
|---|
| 212 | TRBC(INCOMPATIBLE_DEVICE_ERROR),
|
|---|
| 213 | TRBC(MISSED_SERVICE_ERROR),
|
|---|
| 214 | TRBC(COMMAND_RING_STOPPED),
|
|---|
| 215 | TRBC(COMMAND_ABORTED),
|
|---|
| 216 | TRBC(STOPPED),
|
|---|
| 217 | TRBC(STOPPED_LENGTH_INVALID),
|
|---|
| 218 | TRBC(STOPPED_SHORT_PACKET),
|
|---|
| 219 | TRBC(MAX_EXIT_LATENCY_TOO_LARGE_ERROR),
|
|---|
| 220 | [30] = "<reserved>",
|
|---|
| 221 | TRBC(ISOCH_BUFFER_OVERRUN),
|
|---|
| 222 | TRBC(EVENT_LOST_ERROR),
|
|---|
| 223 | TRBC(UNDEFINED_ERROR),
|
|---|
| 224 | TRBC(INVALID_STREAM_ID_ERROR),
|
|---|
| 225 | TRBC(SECONDARY_BANDWIDTH_ERROR),
|
|---|
| 226 | TRBC(SPLIT_TRANSACTION_ERROR),
|
|---|
| 227 | [XHCI_TRBC_MAX] = NULL
|
|---|
| 228 | #undef TRBC
|
|---|
| 229 | };
|
|---|
| 230 |
|
|---|
| 231 | static void report_error(int code)
|
|---|
| 232 | {
|
|---|
| 233 | if (code < XHCI_TRBC_MAX && trb_codes[code] != NULL)
|
|---|
| 234 | usb_log_error("Command resulted in error: %s.", trb_codes[code]);
|
|---|
| 235 | else
|
|---|
| 236 | usb_log_error("Command resulted in reserved or vendor specific error.");
|
|---|
| 237 | }
|
|---|
| 238 |
|
|---|
| [c3d926f3] | 239 | int xhci_handle_command_completion(xhci_hc_t *hc, xhci_trb_t *trb)
|
|---|
| [c9c0e41] | 240 | {
|
|---|
| [c3d926f3] | 241 | // TODO: Update dequeue ptrs.
|
|---|
| [c058a388] | 242 | assert(hc);
|
|---|
| [c3d926f3] | 243 | assert(trb);
|
|---|
| 244 |
|
|---|
| 245 | usb_log_debug2("HC(%p) Command completed.", hc);
|
|---|
| 246 |
|
|---|
| 247 | int code;
|
|---|
| 248 | uint64_t phys;
|
|---|
| 249 | xhci_cmd_t *command;
|
|---|
| 250 |
|
|---|
| 251 | code = TRB_GET_CODE(*trb);
|
|---|
| 252 | phys = TRB_GET_PHYS(*trb);;
|
|---|
| 253 | command = get_command(hc, phys);
|
|---|
| 254 | if (command == NULL) {
|
|---|
| 255 | // TODO: STOP & ABORT may not have command structs in the list!
|
|---|
| 256 | usb_log_warning("No command struct for this completion event found.");
|
|---|
| 257 |
|
|---|
| 258 | if (code != XHCI_TRBC_SUCCESS)
|
|---|
| 259 | report_error(code);
|
|---|
| 260 |
|
|---|
| 261 | return EOK;
|
|---|
| 262 | }
|
|---|
| [c058a388] | 263 |
|
|---|
| [3cbc138] | 264 | /* Semantics of NO_OP_CMD is that success is marked as a TRB error. */
|
|---|
| 265 | if (command->_header.cmd == XHCI_CMD_NO_OP && code == XHCI_TRBC_TRB_ERROR)
|
|---|
| 266 | code = XHCI_TRBC_SUCCESS;
|
|---|
| 267 |
|
|---|
| [c3d926f3] | 268 | command->status = code;
|
|---|
| 269 | command->slot_id = TRB_GET_SLOT(*trb);
|
|---|
| [c9c0e41] | 270 |
|
|---|
| [c3d926f3] | 271 | usb_log_debug2("Completed command trb: %s", xhci_trb_str_type(TRB_TYPE(command->_header.trb)));
|
|---|
| [3cbc138] | 272 |
|
|---|
| 273 | if (code != XHCI_TRBC_SUCCESS) {
|
|---|
| 274 | report_error(code);
|
|---|
| 275 | xhci_dump_trb(&command->_header.trb);
|
|---|
| [c3d926f3] | 276 | }
|
|---|
| 277 |
|
|---|
| 278 | switch (TRB_TYPE(command->_header.trb)) {
|
|---|
| 279 | case XHCI_TRB_TYPE_NO_OP_CMD:
|
|---|
| 280 | break;
|
|---|
| 281 | case XHCI_TRB_TYPE_ENABLE_SLOT_CMD:
|
|---|
| 282 | break;
|
|---|
| 283 | case XHCI_TRB_TYPE_DISABLE_SLOT_CMD:
|
|---|
| 284 | break;
|
|---|
| 285 | case XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD:
|
|---|
| 286 | break;
|
|---|
| 287 | case XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD:
|
|---|
| 288 | break;
|
|---|
| 289 | case XHCI_TRB_TYPE_EVALUATE_CONTEXT_CMD:
|
|---|
| 290 | break;
|
|---|
| 291 | case XHCI_TRB_TYPE_RESET_ENDPOINT_CMD:
|
|---|
| 292 | break;
|
|---|
| 293 | case XHCI_TRB_TYPE_STOP_ENDPOINT_CMD:
|
|---|
| 294 | // Note: If the endpoint was in the middle of a transfer, then the xHC
|
|---|
| 295 | // will add a Transfer TRB before the Event TRB, research that and
|
|---|
| 296 | // handle it appropriately!
|
|---|
| 297 | break;
|
|---|
| 298 | case XHCI_TRB_TYPE_RESET_DEVICE_CMD:
|
|---|
| 299 | break;
|
|---|
| 300 | default:
|
|---|
| 301 | usb_log_debug2("Unsupported command trb: %s", xhci_trb_str_type(TRB_TYPE(command->_header.trb)));
|
|---|
| 302 |
|
|---|
| 303 | command->_header.completed = true;
|
|---|
| 304 | return ENAK;
|
|---|
| 305 | }
|
|---|
| 306 |
|
|---|
| 307 | fibril_mutex_lock(&command->_header.completed_mtx);
|
|---|
| 308 | command->_header.completed = true;
|
|---|
| 309 | fibril_condvar_broadcast(&command->_header.completed_cv);
|
|---|
| 310 | fibril_mutex_unlock(&command->_header.completed_mtx);
|
|---|
| 311 |
|
|---|
| 312 | if (command->_header.async) {
|
|---|
| 313 | /* Free the command and other DS upon completion. */
|
|---|
| 314 | xhci_cmd_fini(command);
|
|---|
| 315 | }
|
|---|
| 316 |
|
|---|
| 317 | return EOK;
|
|---|
| 318 | }
|
|---|
| 319 |
|
|---|
| 320 | /* Command-issuing functions */
|
|---|
| 321 |
|
|---|
| 322 | static int no_op_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| 323 | {
|
|---|
| 324 | assert(hc);
|
|---|
| 325 |
|
|---|
| 326 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| 327 |
|
|---|
| 328 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_NO_OP_CMD);
|
|---|
| [110d795] | 329 |
|
|---|
| [548c123] | 330 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [c9c0e41] | 331 | }
|
|---|
| 332 |
|
|---|
| [c3d926f3] | 333 | static int enable_slot_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [c9c0e41] | 334 | {
|
|---|
| [c058a388] | 335 | assert(hc);
|
|---|
| 336 |
|
|---|
| [c3d926f3] | 337 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [c9c0e41] | 338 |
|
|---|
| [c3d926f3] | 339 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_ENABLE_SLOT_CMD);
|
|---|
| 340 | cmd->_header.trb.control |= host2xhci(32, XHCI_REG_RD(hc->xecp, XHCI_EC_SP_SLOT_TYPE) << 16);
|
|---|
| [110d795] | 341 |
|
|---|
| [548c123] | 342 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [5ac5eb1] | 343 | }
|
|---|
| 344 |
|
|---|
| [c3d926f3] | 345 | static int disable_slot_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [5ac5eb1] | 346 | {
|
|---|
| [c058a388] | 347 | assert(hc);
|
|---|
| [110d795] | 348 | assert(cmd);
|
|---|
| [c058a388] | 349 |
|
|---|
| [c3d926f3] | 350 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [5ac5eb1] | 351 |
|
|---|
| [c3d926f3] | 352 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_DISABLE_SLOT_CMD);
|
|---|
| 353 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| [110d795] | 354 |
|
|---|
| [548c123] | 355 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [c9c0e41] | 356 | }
|
|---|
| 357 |
|
|---|
| [c3d926f3] | 358 | static int address_device_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [8db42f7] | 359 | {
|
|---|
| [c058a388] | 360 | assert(hc);
|
|---|
| [110d795] | 361 | assert(cmd);
|
|---|
| [c3d926f3] | 362 | assert(cmd->input_ctx);
|
|---|
| [c058a388] | 363 |
|
|---|
| [8db42f7] | 364 | /**
|
|---|
| 365 | * TODO: Requirements for this command:
|
|---|
| 366 | * dcbaa[slot_id] is properly sized and initialized
|
|---|
| 367 | * ictx has valids slot context and endpoint 0, all
|
|---|
| 368 | * other should be ignored at this point (see section 4.6.5).
|
|---|
| 369 | */
|
|---|
| [04df063] | 370 |
|
|---|
| [c3d926f3] | 371 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [8db42f7] | 372 |
|
|---|
| [c3d926f3] | 373 | uint64_t phys_addr = (uint64_t) addr_to_phys(cmd->input_ctx);
|
|---|
| 374 | TRB_SET_ICTX(cmd->_header.trb, phys_addr);
|
|---|
| [8db42f7] | 375 |
|
|---|
| 376 | /**
|
|---|
| 377 | * Note: According to section 6.4.3.4, we can set the 9th bit
|
|---|
| 378 | * of the control field of the trb (BSR) to 1 and then the xHC
|
|---|
| 379 | * will not issue the SET_ADDRESS request to the USB device.
|
|---|
| 380 | * This can be used to provide compatibility with legacy USB devices
|
|---|
| 381 | * that require their device descriptor to be read before such request.
|
|---|
| 382 | */
|
|---|
| [c3d926f3] | 383 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD);
|
|---|
| 384 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| [8db42f7] | 385 |
|
|---|
| [548c123] | 386 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [8db42f7] | 387 | }
|
|---|
| 388 |
|
|---|
| [c3d926f3] | 389 | static int configure_endpoint_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [665bf3c] | 390 | {
|
|---|
| [c058a388] | 391 | assert(hc);
|
|---|
| [110d795] | 392 | assert(cmd);
|
|---|
| [c058a388] | 393 |
|
|---|
| [c3d926f3] | 394 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [665bf3c] | 395 |
|
|---|
| [b724494] | 396 | if (!cmd->deconfigure) {
|
|---|
| 397 | /* If the DC flag is on, input context is not evaluated. */
|
|---|
| [c3d926f3] | 398 | assert(cmd->input_ctx);
|
|---|
| [b724494] | 399 |
|
|---|
| [c3d926f3] | 400 | uint64_t phys_addr = (uint64_t) addr_to_phys(cmd->input_ctx);
|
|---|
| 401 | TRB_SET_ICTX(cmd->_header.trb, phys_addr);
|
|---|
| [b724494] | 402 | }
|
|---|
| [110d795] | 403 |
|
|---|
| [c3d926f3] | 404 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD);
|
|---|
| 405 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| 406 | TRB_SET_DC(cmd->_header.trb, cmd->deconfigure);
|
|---|
| [665bf3c] | 407 |
|
|---|
| [548c123] | 408 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [665bf3c] | 409 | }
|
|---|
| 410 |
|
|---|
| [c3d926f3] | 411 | static int evaluate_context_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [c9ce62ae] | 412 | {
|
|---|
| [c058a388] | 413 | assert(hc);
|
|---|
| [110d795] | 414 | assert(cmd);
|
|---|
| [c3d926f3] | 415 | assert(cmd->input_ctx);
|
|---|
| [c058a388] | 416 |
|
|---|
| [c9ce62ae] | 417 | /**
|
|---|
| 418 | * Note: All Drop Context flags of the input context shall be 0,
|
|---|
| 419 | * all Add Context flags shall be initialize to indicate IDs
|
|---|
| 420 | * of the contexts affected by the command.
|
|---|
| 421 | * Refer to sections 6.2.2.3 and 6.3.3.3 for further info.
|
|---|
| 422 | */
|
|---|
| [c3d926f3] | 423 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [c9ce62ae] | 424 |
|
|---|
| [c3d926f3] | 425 | uint64_t phys_addr = (uint64_t) addr_to_phys(cmd->input_ctx);
|
|---|
| 426 | TRB_SET_ICTX(cmd->_header.trb, phys_addr);
|
|---|
| [c9ce62ae] | 427 |
|
|---|
| [c3d926f3] | 428 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_EVALUATE_CONTEXT_CMD);
|
|---|
| 429 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| [110d795] | 430 |
|
|---|
| [548c123] | 431 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [c9ce62ae] | 432 | }
|
|---|
| 433 |
|
|---|
| [c3d926f3] | 434 | static int reset_endpoint_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [05aeee0e] | 435 | {
|
|---|
| [c058a388] | 436 | assert(hc);
|
|---|
| [110d795] | 437 | assert(cmd);
|
|---|
| [c058a388] | 438 |
|
|---|
| [05aeee0e] | 439 | /**
|
|---|
| 440 | * Note: TCS can have values 0 or 1. If it is set to 0, see sectuon 4.5.8 for
|
|---|
| 441 | * information about this flag.
|
|---|
| 442 | */
|
|---|
| [c3d926f3] | 443 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [05aeee0e] | 444 |
|
|---|
| [c3d926f3] | 445 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_RESET_ENDPOINT_CMD);
|
|---|
| 446 | TRB_SET_TCS(cmd->_header.trb, cmd->tcs);
|
|---|
| 447 | TRB_SET_EP(cmd->_header.trb, cmd->endpoint_id);
|
|---|
| 448 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| [c9bec1c] | 449 |
|
|---|
| [548c123] | 450 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [05aeee0e] | 451 | }
|
|---|
| 452 |
|
|---|
| [c3d926f3] | 453 | static int stop_endpoint_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [05aeee0e] | 454 | {
|
|---|
| [c058a388] | 455 | assert(hc);
|
|---|
| [110d795] | 456 | assert(cmd);
|
|---|
| [c058a388] | 457 |
|
|---|
| [c3d926f3] | 458 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [110d795] | 459 |
|
|---|
| [c3d926f3] | 460 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_STOP_ENDPOINT_CMD);
|
|---|
| 461 | TRB_SET_EP(cmd->_header.trb, cmd->endpoint_id);
|
|---|
| 462 | TRB_SET_SUSP(cmd->_header.trb, cmd->susp);
|
|---|
| 463 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| [05aeee0e] | 464 |
|
|---|
| [548c123] | 465 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [c058a388] | 466 | }
|
|---|
| [05aeee0e] | 467 |
|
|---|
| [c3d926f3] | 468 | static int set_tr_dequeue_pointer_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [0cabd10] | 469 | {
|
|---|
| 470 | assert(hc);
|
|---|
| 471 | assert(cmd);
|
|---|
| 472 |
|
|---|
| [c3d926f3] | 473 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [0cabd10] | 474 |
|
|---|
| [c3d926f3] | 475 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD);
|
|---|
| 476 | TRB_SET_EP(cmd->_header.trb, cmd->endpoint_id);
|
|---|
| 477 | TRB_SET_STREAM(cmd->_header.trb, cmd->stream_id);
|
|---|
| 478 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| 479 | TRB_SET_DEQUEUE_PTR(cmd->_header.trb, cmd->dequeue_ptr);
|
|---|
| [0cabd10] | 480 |
|
|---|
| 481 | /**
|
|---|
| 482 | * TODO: Set DCS (see section 4.6.10).
|
|---|
| 483 | */
|
|---|
| 484 |
|
|---|
| [548c123] | 485 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [0cabd10] | 486 | }
|
|---|
| 487 |
|
|---|
| [c3d926f3] | 488 | static int reset_device_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [c058a388] | 489 | {
|
|---|
| 490 | assert(hc);
|
|---|
| [110d795] | 491 | assert(cmd);
|
|---|
| [c058a388] | 492 |
|
|---|
| [c3d926f3] | 493 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [c058a388] | 494 |
|
|---|
| [c3d926f3] | 495 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_RESET_DEVICE_CMD);
|
|---|
| 496 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| [c9bec1c] | 497 |
|
|---|
| [548c123] | 498 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| [05aeee0e] | 499 | }
|
|---|
| 500 |
|
|---|
| [c3d926f3] | 501 | static int get_port_bandwidth_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| [60af4cdb] | 502 | {
|
|---|
| 503 | assert(hc);
|
|---|
| 504 | assert(cmd);
|
|---|
| 505 |
|
|---|
| [c3d926f3] | 506 | xhci_trb_clean(&cmd->_header.trb);
|
|---|
| [60af4cdb] | 507 |
|
|---|
| [c3d926f3] | 508 | uint64_t phys_addr = (uint64_t) addr_to_phys(cmd->bandwidth_ctx);
|
|---|
| 509 | TRB_SET_ICTX(cmd->_header.trb, phys_addr);
|
|---|
| [60af4cdb] | 510 |
|
|---|
| [c3d926f3] | 511 | TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_GET_PORT_BANDWIDTH_CMD);
|
|---|
| 512 | TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
|
|---|
| 513 | TRB_SET_DEV_SPEED(cmd->_header.trb, cmd->device_speed);
|
|---|
| [60af4cdb] | 514 |
|
|---|
| 515 | return enqueue_command(hc, cmd, 0, 0);
|
|---|
| 516 | }
|
|---|
| 517 |
|
|---|
| [c3d926f3] | 518 | /* The table of command-issuing functions. */
|
|---|
| 519 |
|
|---|
| 520 | typedef int (*cmd_handler) (xhci_hc_t *hc, xhci_cmd_t *cmd);
|
|---|
| 521 |
|
|---|
| 522 | static cmd_handler cmd_handlers [] = {
|
|---|
| 523 | [XHCI_CMD_ENABLE_SLOT] = enable_slot_cmd,
|
|---|
| 524 | [XHCI_CMD_DISABLE_SLOT] = disable_slot_cmd,
|
|---|
| 525 | [XHCI_CMD_ADDRESS_DEVICE] = address_device_cmd,
|
|---|
| 526 | [XHCI_CMD_CONFIGURE_ENDPOINT] = configure_endpoint_cmd,
|
|---|
| 527 | [XHCI_CMD_EVALUATE_CONTEXT] = evaluate_context_cmd,
|
|---|
| 528 | [XHCI_CMD_RESET_ENDPOINT] = reset_endpoint_cmd,
|
|---|
| 529 | [XHCI_CMD_STOP_ENDPOINT] = stop_endpoint_cmd,
|
|---|
| 530 | [XHCI_CMD_SET_TR_DEQUEUE_POINTER] = set_tr_dequeue_pointer_cmd,
|
|---|
| 531 | [XHCI_CMD_RESET_DEVICE] = reset_device_cmd,
|
|---|
| 532 | // TODO: Force event (optional normative, for VMM, section 4.6.12).
|
|---|
| 533 | [XHCI_CMD_FORCE_EVENT] = NULL,
|
|---|
| 534 | // TODO: Negotiate bandwidth (optional normative, section 4.6.13).
|
|---|
| 535 | [XHCI_CMD_NEGOTIATE_BANDWIDTH] = NULL,
|
|---|
| 536 | // TODO: Set latency tolerance value (optional normative, section 4.6.14).
|
|---|
| 537 | [XHCI_CMD_SET_LATENCY_TOLERANCE_VALUE] = NULL,
|
|---|
| 538 | // TODO: Get port bandwidth (mandatory, but needs root hub implementation, section 4.6.15).
|
|---|
| 539 | [XHCI_CMD_GET_PORT_BANDWIDTH] = get_port_bandwidth_cmd,
|
|---|
| 540 | // TODO: Force header (mandatory, but needs root hub implementation, section 4.6.16).
|
|---|
| 541 | [XHCI_CMD_FORCE_HEADER] = NULL,
|
|---|
| 542 | [XHCI_CMD_NO_OP] = no_op_cmd
|
|---|
| 543 | };
|
|---|
| 544 |
|
|---|
| 545 | static int wait_for_cmd_completion(xhci_cmd_t *cmd)
|
|---|
| [f9e7fe8] | 546 | {
|
|---|
| [c3d926f3] | 547 | int rv = EOK;
|
|---|
| [c058a388] | 548 |
|
|---|
| [c3d926f3] | 549 | fibril_mutex_lock(&cmd->_header.completed_mtx);
|
|---|
| 550 | while (!cmd->_header.completed) {
|
|---|
| 551 | usb_log_debug2("Waiting for event completion: going to sleep.");
|
|---|
| 552 | rv = fibril_condvar_wait_timeout(&cmd->_header.completed_cv, &cmd->_header.completed_mtx, cmd->_header.timeout);
|
|---|
| [f9e7fe8] | 553 |
|
|---|
| [c3d926f3] | 554 | usb_log_debug2("Waiting for event completion: woken: %s", str_error(rv));
|
|---|
| 555 | if (rv == ETIMEOUT) {
|
|---|
| 556 | break;
|
|---|
| 557 | }
|
|---|
| 558 | }
|
|---|
| 559 | fibril_mutex_unlock(&cmd->_header.completed_mtx);
|
|---|
| [f711f06] | 560 |
|
|---|
| [c3d926f3] | 561 | return rv;
|
|---|
| 562 | }
|
|---|
| [2fa43d1] | 563 |
|
|---|
| [c3d926f3] | 564 | /** Issue command and block the current fibril until it is completed or timeout
|
|---|
| 565 | * expires. Nothing is deallocated. Caller should always execute `xhci_cmd_fini`.
|
|---|
| 566 | */
|
|---|
| 567 | int xhci_cmd_sync(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| 568 | {
|
|---|
| 569 | assert(hc);
|
|---|
| 570 | assert(cmd);
|
|---|
| [2fa43d1] | 571 |
|
|---|
| [c3d926f3] | 572 | int err;
|
|---|
| 573 |
|
|---|
| 574 | if (!cmd_handlers[cmd->_header.cmd]) {
|
|---|
| 575 | /* Handler not implemented. */
|
|---|
| 576 | return ENOTSUP;
|
|---|
| [2fa43d1] | 577 | }
|
|---|
| [110d795] | 578 |
|
|---|
| [c3d926f3] | 579 | if ((err = cmd_handlers[cmd->_header.cmd](hc, cmd))) {
|
|---|
| 580 | /* Command could not be issued. */
|
|---|
| 581 | return err;
|
|---|
| 582 | }
|
|---|
| [110d795] | 583 |
|
|---|
| [c3d926f3] | 584 | if ((err = wait_for_cmd_completion(cmd))) {
|
|---|
| 585 | /* Timeout expired or command failed. */
|
|---|
| 586 | return err;
|
|---|
| [665bf3c] | 587 | }
|
|---|
| [c362127] | 588 |
|
|---|
| [3cbc138] | 589 | return cmd->status == XHCI_TRBC_SUCCESS ? EOK : EINVAL;
|
|---|
| [c3d926f3] | 590 | }
|
|---|
| [110d795] | 591 |
|
|---|
| [c3d926f3] | 592 | /** Does the same thing as `xhci_cmd_sync` and executes `xhci_cmd_fini`. This
|
|---|
| 593 | * is a useful shorthand for issuing commands without out parameters.
|
|---|
| 594 | */
|
|---|
| 595 | int xhci_cmd_sync_fini(xhci_hc_t *hc, xhci_cmd_t *cmd)
|
|---|
| 596 | {
|
|---|
| 597 | const int err = xhci_cmd_sync(hc, cmd);
|
|---|
| 598 | xhci_cmd_fini(cmd);
|
|---|
| 599 |
|
|---|
| 600 | return err;
|
|---|
| 601 | }
|
|---|
| 602 |
|
|---|
| 603 | /** Does the same thing as `xhci_cmd_sync_fini` without blocking the current
|
|---|
| 604 | * fibril. The command is copied to stack memory and `fini` is called upon its completion.
|
|---|
| 605 | */
|
|---|
| 606 | int xhci_cmd_async_fini(xhci_hc_t *hc, xhci_cmd_t *stack_cmd)
|
|---|
| 607 | {
|
|---|
| 608 | assert(hc);
|
|---|
| 609 | assert(stack_cmd);
|
|---|
| 610 |
|
|---|
| 611 | /* Save the command for later. */
|
|---|
| 612 | xhci_cmd_t *heap_cmd = (xhci_cmd_t *) malloc(sizeof(xhci_cmd_t));
|
|---|
| 613 | if (!heap_cmd) {
|
|---|
| 614 | return ENOMEM;
|
|---|
| 615 | }
|
|---|
| 616 |
|
|---|
| 617 | /* TODO: Is this good for the mutex and the condvar? */
|
|---|
| 618 | memcpy(heap_cmd, stack_cmd, sizeof(xhci_cmd_t));
|
|---|
| 619 | heap_cmd->_header.async = true;
|
|---|
| 620 |
|
|---|
| 621 | /* Issue the command. */
|
|---|
| 622 | int err;
|
|---|
| 623 |
|
|---|
| 624 | if (!cmd_handlers[heap_cmd->_header.cmd]) {
|
|---|
| 625 | /* Handler not implemented. */
|
|---|
| 626 | err = ENOTSUP;
|
|---|
| 627 | goto err_heap_cmd;
|
|---|
| [f711f06] | 628 | }
|
|---|
| [110d795] | 629 |
|
|---|
| [c3d926f3] | 630 | if ((err = cmd_handlers[heap_cmd->_header.cmd](hc, heap_cmd))) {
|
|---|
| 631 | /* Command could not be issued. */
|
|---|
| 632 | goto err_heap_cmd;
|
|---|
| 633 | }
|
|---|
| [4688350b] | 634 |
|
|---|
| [110d795] | 635 | return EOK;
|
|---|
| [c9c0e41] | 636 |
|
|---|
| [c3d926f3] | 637 | err_heap_cmd:
|
|---|
| 638 | free(heap_cmd);
|
|---|
| 639 | return err;
|
|---|
| 640 | }
|
|---|
| [c9c0e41] | 641 |
|
|---|
| 642 | /**
|
|---|
| 643 | * @}
|
|---|
| 644 | */
|
|---|