|
|
1.1 ! root 1: /* ! 2: * QEMU AHCI Emulation ! 3: * ! 4: * Copyright (c) 2010 [email protected] ! 5: * Copyright (c) 2010 Roland Elek <[email protected]> ! 6: * Copyright (c) 2010 Sebastian Herbszt <[email protected]> ! 7: * Copyright (c) 2010 Alexander Graf <[email protected]> ! 8: * ! 9: * This library is free software; you can redistribute it and/or ! 10: * modify it under the terms of the GNU Lesser General Public ! 11: * License as published by the Free Software Foundation; either ! 12: * version 2 of the License, or (at your option) any later version. ! 13: * ! 14: * This library is distributed in the hope that it will be useful, ! 15: * but WITHOUT ANY WARRANTY; without even the implied warranty of ! 16: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ! 17: * Lesser General Public License for more details. ! 18: * ! 19: * You should have received a copy of the GNU Lesser General Public ! 20: * License along with this library; if not, see <http://www.gnu.org/licenses/>. ! 21: * ! 22: */ ! 23: ! 24: #include <hw/hw.h> ! 25: #include <hw/msi.h> ! 26: #include <hw/pc.h> ! 27: #include <hw/pci.h> ! 28: ! 29: #include "monitor.h" ! 30: #include "dma.h" ! 31: #include "cpu-common.h" ! 32: #include "internal.h" ! 33: #include <hw/ide/pci.h> ! 34: #include <hw/ide/ahci.h> ! 35: ! 36: /* #define DEBUG_AHCI */ ! 37: ! 38: #ifdef DEBUG_AHCI ! 39: #define DPRINTF(port, fmt, ...) \ ! 40: do { fprintf(stderr, "ahci: %s: [%d] ", __FUNCTION__, port); \ ! 41: fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) ! 42: #else ! 43: #define DPRINTF(port, fmt, ...) do {} while(0) ! 44: #endif ! 45: ! 46: static void check_cmd(AHCIState *s, int port); ! 47: static int handle_cmd(AHCIState *s,int port,int slot); ! 48: static void ahci_reset_port(AHCIState *s, int port); ! 49: static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis); ! 50: static void ahci_init_d2h(AHCIDevice *ad); ! 51: ! 52: static uint32_t ahci_port_read(AHCIState *s, int port, int offset) ! 53: { ! 54: uint32_t val; ! 55: AHCIPortRegs *pr; ! 56: pr = &s->dev[port].port_regs; ! 57: ! 58: switch (offset) { ! 59: case PORT_LST_ADDR: ! 60: val = pr->lst_addr; ! 61: break; ! 62: case PORT_LST_ADDR_HI: ! 63: val = pr->lst_addr_hi; ! 64: break; ! 65: case PORT_FIS_ADDR: ! 66: val = pr->fis_addr; ! 67: break; ! 68: case PORT_FIS_ADDR_HI: ! 69: val = pr->fis_addr_hi; ! 70: break; ! 71: case PORT_IRQ_STAT: ! 72: val = pr->irq_stat; ! 73: break; ! 74: case PORT_IRQ_MASK: ! 75: val = pr->irq_mask; ! 76: break; ! 77: case PORT_CMD: ! 78: val = pr->cmd; ! 79: break; ! 80: case PORT_TFDATA: ! 81: val = ((uint16_t)s->dev[port].port.ifs[0].error << 8) | ! 82: s->dev[port].port.ifs[0].status; ! 83: break; ! 84: case PORT_SIG: ! 85: val = pr->sig; ! 86: break; ! 87: case PORT_SCR_STAT: ! 88: if (s->dev[port].port.ifs[0].bs) { ! 89: val = SATA_SCR_SSTATUS_DET_DEV_PRESENT_PHY_UP | ! 90: SATA_SCR_SSTATUS_SPD_GEN1 | SATA_SCR_SSTATUS_IPM_ACTIVE; ! 91: } else { ! 92: val = SATA_SCR_SSTATUS_DET_NODEV; ! 93: } ! 94: break; ! 95: case PORT_SCR_CTL: ! 96: val = pr->scr_ctl; ! 97: break; ! 98: case PORT_SCR_ERR: ! 99: val = pr->scr_err; ! 100: break; ! 101: case PORT_SCR_ACT: ! 102: pr->scr_act &= ~s->dev[port].finished; ! 103: s->dev[port].finished = 0; ! 104: val = pr->scr_act; ! 105: break; ! 106: case PORT_CMD_ISSUE: ! 107: val = pr->cmd_issue; ! 108: break; ! 109: case PORT_RESERVED: ! 110: default: ! 111: val = 0; ! 112: } ! 113: DPRINTF(port, "offset: 0x%x val: 0x%x\n", offset, val); ! 114: return val; ! 115: ! 116: } ! 117: ! 118: static void ahci_irq_raise(AHCIState *s, AHCIDevice *dev) ! 119: { ! 120: struct AHCIPCIState *d = container_of(s, AHCIPCIState, ahci); ! 121: ! 122: DPRINTF(0, "raise irq\n"); ! 123: ! 124: if (msi_enabled(&d->card)) { ! 125: msi_notify(&d->card, 0); ! 126: } else { ! 127: qemu_irq_raise(s->irq); ! 128: } ! 129: } ! 130: ! 131: static void ahci_irq_lower(AHCIState *s, AHCIDevice *dev) ! 132: { ! 133: struct AHCIPCIState *d = container_of(s, AHCIPCIState, ahci); ! 134: ! 135: DPRINTF(0, "lower irq\n"); ! 136: ! 137: if (!msi_enabled(&d->card)) { ! 138: qemu_irq_lower(s->irq); ! 139: } ! 140: } ! 141: ! 142: static void ahci_check_irq(AHCIState *s) ! 143: { ! 144: int i; ! 145: ! 146: DPRINTF(-1, "check irq %#x\n", s->control_regs.irqstatus); ! 147: ! 148: for (i = 0; i < s->ports; i++) { ! 149: AHCIPortRegs *pr = &s->dev[i].port_regs; ! 150: if (pr->irq_stat & pr->irq_mask) { ! 151: s->control_regs.irqstatus |= (1 << i); ! 152: } ! 153: } ! 154: ! 155: if (s->control_regs.irqstatus && ! 156: (s->control_regs.ghc & HOST_CTL_IRQ_EN)) { ! 157: ahci_irq_raise(s, NULL); ! 158: } else { ! 159: ahci_irq_lower(s, NULL); ! 160: } ! 161: } ! 162: ! 163: static void ahci_trigger_irq(AHCIState *s, AHCIDevice *d, ! 164: int irq_type) ! 165: { ! 166: DPRINTF(d->port_no, "trigger irq %#x -> %x\n", ! 167: irq_type, d->port_regs.irq_mask & irq_type); ! 168: ! 169: d->port_regs.irq_stat |= irq_type; ! 170: ahci_check_irq(s); ! 171: } ! 172: ! 173: static void map_page(uint8_t **ptr, uint64_t addr, uint32_t wanted) ! 174: { ! 175: target_phys_addr_t len = wanted; ! 176: ! 177: if (*ptr) { ! 178: cpu_physical_memory_unmap(*ptr, len, 1, len); ! 179: } ! 180: ! 181: *ptr = cpu_physical_memory_map(addr, &len, 1); ! 182: if (len < wanted) { ! 183: cpu_physical_memory_unmap(*ptr, len, 1, len); ! 184: *ptr = NULL; ! 185: } ! 186: } ! 187: ! 188: static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val) ! 189: { ! 190: AHCIPortRegs *pr = &s->dev[port].port_regs; ! 191: ! 192: DPRINTF(port, "offset: 0x%x val: 0x%x\n", offset, val); ! 193: switch (offset) { ! 194: case PORT_LST_ADDR: ! 195: pr->lst_addr = val; ! 196: map_page(&s->dev[port].lst, ! 197: ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024); ! 198: s->dev[port].cur_cmd = NULL; ! 199: break; ! 200: case PORT_LST_ADDR_HI: ! 201: pr->lst_addr_hi = val; ! 202: map_page(&s->dev[port].lst, ! 203: ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024); ! 204: s->dev[port].cur_cmd = NULL; ! 205: break; ! 206: case PORT_FIS_ADDR: ! 207: pr->fis_addr = val; ! 208: map_page(&s->dev[port].res_fis, ! 209: ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256); ! 210: break; ! 211: case PORT_FIS_ADDR_HI: ! 212: pr->fis_addr_hi = val; ! 213: map_page(&s->dev[port].res_fis, ! 214: ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256); ! 215: break; ! 216: case PORT_IRQ_STAT: ! 217: pr->irq_stat &= ~val; ! 218: break; ! 219: case PORT_IRQ_MASK: ! 220: pr->irq_mask = val & 0xfdc000ff; ! 221: ahci_check_irq(s); ! 222: break; ! 223: case PORT_CMD: ! 224: pr->cmd = val & ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON); ! 225: ! 226: if (pr->cmd & PORT_CMD_START) { ! 227: pr->cmd |= PORT_CMD_LIST_ON; ! 228: } ! 229: ! 230: if (pr->cmd & PORT_CMD_FIS_RX) { ! 231: pr->cmd |= PORT_CMD_FIS_ON; ! 232: } ! 233: ! 234: /* XXX usually the FIS would be pending on the bus here and ! 235: issuing deferred until the OS enables FIS receival. ! 236: Instead, we only submit it once - which works in most ! 237: cases, but is a hack. */ ! 238: if ((pr->cmd & PORT_CMD_FIS_ON) && ! 239: !s->dev[port].init_d2h_sent) { ! 240: ahci_init_d2h(&s->dev[port]); ! 241: s->dev[port].init_d2h_sent = 1; ! 242: } ! 243: ! 244: check_cmd(s, port); ! 245: break; ! 246: case PORT_TFDATA: ! 247: s->dev[port].port.ifs[0].error = (val >> 8) & 0xff; ! 248: s->dev[port].port.ifs[0].status = val & 0xff; ! 249: break; ! 250: case PORT_SIG: ! 251: pr->sig = val; ! 252: break; ! 253: case PORT_SCR_STAT: ! 254: pr->scr_stat = val; ! 255: break; ! 256: case PORT_SCR_CTL: ! 257: if (((pr->scr_ctl & AHCI_SCR_SCTL_DET) == 1) && ! 258: ((val & AHCI_SCR_SCTL_DET) == 0)) { ! 259: ahci_reset_port(s, port); ! 260: } ! 261: pr->scr_ctl = val; ! 262: break; ! 263: case PORT_SCR_ERR: ! 264: pr->scr_err &= ~val; ! 265: break; ! 266: case PORT_SCR_ACT: ! 267: /* RW1 */ ! 268: pr->scr_act |= val; ! 269: break; ! 270: case PORT_CMD_ISSUE: ! 271: pr->cmd_issue |= val; ! 272: check_cmd(s, port); ! 273: break; ! 274: default: ! 275: break; ! 276: } ! 277: } ! 278: ! 279: static uint32_t ahci_mem_readl(void *ptr, target_phys_addr_t addr) ! 280: { ! 281: AHCIState *s = ptr; ! 282: uint32_t val = 0; ! 283: ! 284: addr = addr & 0xfff; ! 285: if (addr < AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR) { ! 286: switch (addr) { ! 287: case HOST_CAP: ! 288: val = s->control_regs.cap; ! 289: break; ! 290: case HOST_CTL: ! 291: val = s->control_regs.ghc; ! 292: break; ! 293: case HOST_IRQ_STAT: ! 294: val = s->control_regs.irqstatus; ! 295: break; ! 296: case HOST_PORTS_IMPL: ! 297: val = s->control_regs.impl; ! 298: break; ! 299: case HOST_VERSION: ! 300: val = s->control_regs.version; ! 301: break; ! 302: } ! 303: ! 304: DPRINTF(-1, "(addr 0x%08X), val 0x%08X\n", (unsigned) addr, val); ! 305: } else if ((addr >= AHCI_PORT_REGS_START_ADDR) && ! 306: (addr < (AHCI_PORT_REGS_START_ADDR + ! 307: (s->ports * AHCI_PORT_ADDR_OFFSET_LEN)))) { ! 308: val = ahci_port_read(s, (addr - AHCI_PORT_REGS_START_ADDR) >> 7, ! 309: addr & AHCI_PORT_ADDR_OFFSET_MASK); ! 310: } ! 311: ! 312: return val; ! 313: } ! 314: ! 315: ! 316: ! 317: static void ahci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) ! 318: { ! 319: AHCIState *s = ptr; ! 320: addr = addr & 0xfff; ! 321: ! 322: /* Only aligned reads are allowed on AHCI */ ! 323: if (addr & 3) { ! 324: fprintf(stderr, "ahci: Mis-aligned write to addr 0x" ! 325: TARGET_FMT_plx "\n", addr); ! 326: return; ! 327: } ! 328: ! 329: if (addr < AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR) { ! 330: DPRINTF(-1, "(addr 0x%08X), val 0x%08X\n", (unsigned) addr, val); ! 331: ! 332: switch (addr) { ! 333: case HOST_CAP: /* R/WO, RO */ ! 334: /* FIXME handle R/WO */ ! 335: break; ! 336: case HOST_CTL: /* R/W */ ! 337: if (val & HOST_CTL_RESET) { ! 338: DPRINTF(-1, "HBA Reset\n"); ! 339: ahci_reset(container_of(s, AHCIPCIState, ahci)); ! 340: } else { ! 341: s->control_regs.ghc = (val & 0x3) | HOST_CTL_AHCI_EN; ! 342: ahci_check_irq(s); ! 343: } ! 344: break; ! 345: case HOST_IRQ_STAT: /* R/WC, RO */ ! 346: s->control_regs.irqstatus &= ~val; ! 347: ahci_check_irq(s); ! 348: break; ! 349: case HOST_PORTS_IMPL: /* R/WO, RO */ ! 350: /* FIXME handle R/WO */ ! 351: break; ! 352: case HOST_VERSION: /* RO */ ! 353: /* FIXME report write? */ ! 354: break; ! 355: default: ! 356: DPRINTF(-1, "write to unknown register 0x%x\n", (unsigned)addr); ! 357: } ! 358: } else if ((addr >= AHCI_PORT_REGS_START_ADDR) && ! 359: (addr < (AHCI_PORT_REGS_START_ADDR + ! 360: (s->ports * AHCI_PORT_ADDR_OFFSET_LEN)))) { ! 361: ahci_port_write(s, (addr - AHCI_PORT_REGS_START_ADDR) >> 7, ! 362: addr & AHCI_PORT_ADDR_OFFSET_MASK, val); ! 363: } ! 364: ! 365: } ! 366: ! 367: static CPUReadMemoryFunc * const ahci_readfn[3]={ ! 368: ahci_mem_readl, ! 369: ahci_mem_readl, ! 370: ahci_mem_readl ! 371: }; ! 372: ! 373: static CPUWriteMemoryFunc * const ahci_writefn[3]={ ! 374: ahci_mem_writel, ! 375: ahci_mem_writel, ! 376: ahci_mem_writel ! 377: }; ! 378: ! 379: static void ahci_reg_init(AHCIState *s) ! 380: { ! 381: int i; ! 382: ! 383: s->control_regs.cap = (s->ports - 1) | ! 384: (AHCI_NUM_COMMAND_SLOTS << 8) | ! 385: (AHCI_SUPPORTED_SPEED_GEN1 << AHCI_SUPPORTED_SPEED) | ! 386: HOST_CAP_NCQ | HOST_CAP_AHCI; ! 387: ! 388: s->control_regs.impl = (1 << s->ports) - 1; ! 389: ! 390: s->control_regs.version = AHCI_VERSION_1_0; ! 391: ! 392: for (i = 0; i < s->ports; i++) { ! 393: s->dev[i].port_state = STATE_RUN; ! 394: } ! 395: } ! 396: ! 397: static uint32_t read_from_sglist(uint8_t *buffer, uint32_t len, ! 398: QEMUSGList *sglist) ! 399: { ! 400: uint32_t i = 0; ! 401: uint32_t total = 0, once; ! 402: ScatterGatherEntry *cur_prd; ! 403: uint32_t sgcount; ! 404: ! 405: cur_prd = sglist->sg; ! 406: sgcount = sglist->nsg; ! 407: for (i = 0; len && sgcount; i++) { ! 408: once = MIN(cur_prd->len, len); ! 409: cpu_physical_memory_read(cur_prd->base, buffer, once); ! 410: cur_prd++; ! 411: sgcount--; ! 412: len -= once; ! 413: buffer += once; ! 414: total += once; ! 415: } ! 416: ! 417: return total; ! 418: } ! 419: ! 420: static uint32_t write_to_sglist(uint8_t *buffer, uint32_t len, ! 421: QEMUSGList *sglist) ! 422: { ! 423: uint32_t i = 0; ! 424: uint32_t total = 0, once; ! 425: ScatterGatherEntry *cur_prd; ! 426: uint32_t sgcount; ! 427: ! 428: DPRINTF(-1, "total: 0x%x bytes\n", len); ! 429: ! 430: cur_prd = sglist->sg; ! 431: sgcount = sglist->nsg; ! 432: for (i = 0; len && sgcount; i++) { ! 433: once = MIN(cur_prd->len, len); ! 434: DPRINTF(-1, "write 0x%x bytes to 0x%lx\n", once, (long)cur_prd->base); ! 435: cpu_physical_memory_write(cur_prd->base, buffer, once); ! 436: cur_prd++; ! 437: sgcount--; ! 438: len -= once; ! 439: buffer += once; ! 440: total += once; ! 441: } ! 442: ! 443: return total; ! 444: } ! 445: ! 446: static void check_cmd(AHCIState *s, int port) ! 447: { ! 448: AHCIPortRegs *pr = &s->dev[port].port_regs; ! 449: int slot; ! 450: ! 451: if ((pr->cmd & PORT_CMD_START) && pr->cmd_issue) { ! 452: for (slot = 0; (slot < 32) && pr->cmd_issue; slot++) { ! 453: if ((pr->cmd_issue & (1 << slot)) && ! 454: !handle_cmd(s, port, slot)) { ! 455: pr->cmd_issue &= ~(1 << slot); ! 456: } ! 457: } ! 458: } ! 459: } ! 460: ! 461: static void ahci_check_cmd_bh(void *opaque) ! 462: { ! 463: AHCIDevice *ad = opaque; ! 464: ! 465: qemu_bh_delete(ad->check_bh); ! 466: ad->check_bh = NULL; ! 467: ! 468: if ((ad->busy_slot != -1) && ! 469: !(ad->port.ifs[0].status & (BUSY_STAT|DRQ_STAT))) { ! 470: /* no longer busy */ ! 471: ad->port_regs.cmd_issue &= ~(1 << ad->busy_slot); ! 472: ad->busy_slot = -1; ! 473: } ! 474: ! 475: check_cmd(ad->hba, ad->port_no); ! 476: } ! 477: ! 478: static void ahci_init_d2h(AHCIDevice *ad) ! 479: { ! 480: uint8_t init_fis[0x20]; ! 481: IDEState *ide_state = &ad->port.ifs[0]; ! 482: ! 483: memset(init_fis, 0, sizeof(init_fis)); ! 484: ! 485: init_fis[4] = 1; ! 486: init_fis[12] = 1; ! 487: ! 488: if (ide_state->drive_kind == IDE_CD) { ! 489: init_fis[5] = ide_state->lcyl; ! 490: init_fis[6] = ide_state->hcyl; ! 491: } ! 492: ! 493: ahci_write_fis_d2h(ad, init_fis); ! 494: } ! 495: ! 496: static void ahci_reset_port(AHCIState *s, int port) ! 497: { ! 498: AHCIDevice *d = &s->dev[port]; ! 499: AHCIPortRegs *pr = &d->port_regs; ! 500: IDEState *ide_state = &d->port.ifs[0]; ! 501: int i; ! 502: ! 503: DPRINTF(port, "reset port\n"); ! 504: ! 505: ide_bus_reset(&d->port); ! 506: ide_state->ncq_queues = AHCI_MAX_CMDS; ! 507: ! 508: pr->irq_stat = 0; ! 509: pr->irq_mask = 0; ! 510: pr->scr_stat = 0; ! 511: pr->scr_ctl = 0; ! 512: pr->scr_err = 0; ! 513: pr->scr_act = 0; ! 514: d->busy_slot = -1; ! 515: d->init_d2h_sent = 0; ! 516: ! 517: ide_state = &s->dev[port].port.ifs[0]; ! 518: if (!ide_state->bs) { ! 519: return; ! 520: } ! 521: ! 522: /* reset ncq queue */ ! 523: for (i = 0; i < AHCI_MAX_CMDS; i++) { ! 524: NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[i]; ! 525: if (!ncq_tfs->used) { ! 526: continue; ! 527: } ! 528: ! 529: if (ncq_tfs->aiocb) { ! 530: bdrv_aio_cancel(ncq_tfs->aiocb); ! 531: ncq_tfs->aiocb = NULL; ! 532: } ! 533: ! 534: qemu_sglist_destroy(&ncq_tfs->sglist); ! 535: ncq_tfs->used = 0; ! 536: } ! 537: ! 538: s->dev[port].port_state = STATE_RUN; ! 539: if (!ide_state->bs) { ! 540: s->dev[port].port_regs.sig = 0; ! 541: ide_state->status = SEEK_STAT | WRERR_STAT; ! 542: } else if (ide_state->drive_kind == IDE_CD) { ! 543: s->dev[port].port_regs.sig = SATA_SIGNATURE_CDROM; ! 544: ide_state->lcyl = 0x14; ! 545: ide_state->hcyl = 0xeb; ! 546: DPRINTF(port, "set lcyl = %d\n", ide_state->lcyl); ! 547: ide_state->status = SEEK_STAT | WRERR_STAT | READY_STAT; ! 548: } else { ! 549: s->dev[port].port_regs.sig = SATA_SIGNATURE_DISK; ! 550: ide_state->status = SEEK_STAT | WRERR_STAT; ! 551: } ! 552: ! 553: ide_state->error = 1; ! 554: ahci_init_d2h(d); ! 555: } ! 556: ! 557: static void debug_print_fis(uint8_t *fis, int cmd_len) ! 558: { ! 559: #ifdef DEBUG_AHCI ! 560: int i; ! 561: ! 562: fprintf(stderr, "fis:"); ! 563: for (i = 0; i < cmd_len; i++) { ! 564: if ((i & 0xf) == 0) { ! 565: fprintf(stderr, "\n%02x:",i); ! 566: } ! 567: fprintf(stderr, "%02x ",fis[i]); ! 568: } ! 569: fprintf(stderr, "\n"); ! 570: #endif ! 571: } ! 572: ! 573: static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished) ! 574: { ! 575: AHCIPortRegs *pr = &s->dev[port].port_regs; ! 576: IDEState *ide_state; ! 577: uint8_t *sdb_fis; ! 578: ! 579: if (!s->dev[port].res_fis || ! 580: !(pr->cmd & PORT_CMD_FIS_RX)) { ! 581: return; ! 582: } ! 583: ! 584: sdb_fis = &s->dev[port].res_fis[RES_FIS_SDBFIS]; ! 585: ide_state = &s->dev[port].port.ifs[0]; ! 586: ! 587: /* clear memory */ ! 588: *(uint32_t*)sdb_fis = 0; ! 589: ! 590: /* write values */ ! 591: sdb_fis[0] = ide_state->error; ! 592: sdb_fis[2] = ide_state->status & 0x77; ! 593: s->dev[port].finished |= finished; ! 594: *(uint32_t*)(sdb_fis + 4) = cpu_to_le32(s->dev[port].finished); ! 595: ! 596: ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_STAT_SDBS); ! 597: } ! 598: ! 599: static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis) ! 600: { ! 601: AHCIPortRegs *pr = &ad->port_regs; ! 602: uint8_t *d2h_fis; ! 603: int i; ! 604: target_phys_addr_t cmd_len = 0x80; ! 605: int cmd_mapped = 0; ! 606: ! 607: if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) { ! 608: return; ! 609: } ! 610: ! 611: if (!cmd_fis) { ! 612: /* map cmd_fis */ ! 613: uint64_t tbl_addr = le64_to_cpu(ad->cur_cmd->tbl_addr); ! 614: cmd_fis = cpu_physical_memory_map(tbl_addr, &cmd_len, 0); ! 615: cmd_mapped = 1; ! 616: } ! 617: ! 618: d2h_fis = &ad->res_fis[RES_FIS_RFIS]; ! 619: ! 620: d2h_fis[0] = 0x34; ! 621: d2h_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0); ! 622: d2h_fis[2] = ad->port.ifs[0].status; ! 623: d2h_fis[3] = ad->port.ifs[0].error; ! 624: ! 625: d2h_fis[4] = cmd_fis[4]; ! 626: d2h_fis[5] = cmd_fis[5]; ! 627: d2h_fis[6] = cmd_fis[6]; ! 628: d2h_fis[7] = cmd_fis[7]; ! 629: d2h_fis[8] = cmd_fis[8]; ! 630: d2h_fis[9] = cmd_fis[9]; ! 631: d2h_fis[10] = cmd_fis[10]; ! 632: d2h_fis[11] = cmd_fis[11]; ! 633: d2h_fis[12] = cmd_fis[12]; ! 634: d2h_fis[13] = cmd_fis[13]; ! 635: for (i = 14; i < 0x20; i++) { ! 636: d2h_fis[i] = 0; ! 637: } ! 638: ! 639: if (d2h_fis[2] & ERR_STAT) { ! 640: ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_TFES); ! 641: } ! 642: ! 643: ahci_trigger_irq(ad->hba, ad, PORT_IRQ_D2H_REG_FIS); ! 644: ! 645: if (cmd_mapped) { ! 646: cpu_physical_memory_unmap(cmd_fis, cmd_len, 0, cmd_len); ! 647: } ! 648: } ! 649: ! 650: static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist) ! 651: { ! 652: AHCICmdHdr *cmd = ad->cur_cmd; ! 653: uint32_t opts = le32_to_cpu(cmd->opts); ! 654: uint64_t prdt_addr = le64_to_cpu(cmd->tbl_addr) + 0x80; ! 655: int sglist_alloc_hint = opts >> AHCI_CMD_HDR_PRDT_LEN; ! 656: target_phys_addr_t prdt_len = (sglist_alloc_hint * sizeof(AHCI_SG)); ! 657: target_phys_addr_t real_prdt_len = prdt_len; ! 658: uint8_t *prdt; ! 659: int i; ! 660: int r = 0; ! 661: ! 662: if (!sglist_alloc_hint) { ! 663: DPRINTF(ad->port_no, "no sg list given by guest: 0x%08x\n", opts); ! 664: return -1; ! 665: } ! 666: ! 667: /* map PRDT */ ! 668: if (!(prdt = cpu_physical_memory_map(prdt_addr, &prdt_len, 0))){ ! 669: DPRINTF(ad->port_no, "map failed\n"); ! 670: return -1; ! 671: } ! 672: ! 673: if (prdt_len < real_prdt_len) { ! 674: DPRINTF(ad->port_no, "mapped less than expected\n"); ! 675: r = -1; ! 676: goto out; ! 677: } ! 678: ! 679: /* Get entries in the PRDT, init a qemu sglist accordingly */ ! 680: if (sglist_alloc_hint > 0) { ! 681: AHCI_SG *tbl = (AHCI_SG *)prdt; ! 682: ! 683: qemu_sglist_init(sglist, sglist_alloc_hint); ! 684: for (i = 0; i < sglist_alloc_hint; i++) { ! 685: /* flags_size is zero-based */ ! 686: qemu_sglist_add(sglist, le64_to_cpu(tbl[i].addr), ! 687: le32_to_cpu(tbl[i].flags_size) + 1); ! 688: } ! 689: } ! 690: ! 691: out: ! 692: cpu_physical_memory_unmap(prdt, prdt_len, 0, prdt_len); ! 693: return r; ! 694: } ! 695: ! 696: static void ncq_cb(void *opaque, int ret) ! 697: { ! 698: NCQTransferState *ncq_tfs = (NCQTransferState *)opaque; ! 699: IDEState *ide_state = &ncq_tfs->drive->port.ifs[0]; ! 700: ! 701: /* Clear bit for this tag in SActive */ ! 702: ncq_tfs->drive->port_regs.scr_act &= ~(1 << ncq_tfs->tag); ! 703: ! 704: if (ret < 0) { ! 705: /* error */ ! 706: ide_state->error = ABRT_ERR; ! 707: ide_state->status = READY_STAT | ERR_STAT; ! 708: ncq_tfs->drive->port_regs.scr_err |= (1 << ncq_tfs->tag); ! 709: } else { ! 710: ide_state->status = READY_STAT | SEEK_STAT; ! 711: } ! 712: ! 713: ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs->drive->port_no, ! 714: (1 << ncq_tfs->tag)); ! 715: ! 716: DPRINTF(ncq_tfs->drive->port_no, "NCQ transfer tag %d finished\n", ! 717: ncq_tfs->tag); ! 718: ! 719: qemu_sglist_destroy(&ncq_tfs->sglist); ! 720: ncq_tfs->used = 0; ! 721: } ! 722: ! 723: static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis, ! 724: int slot) ! 725: { ! 726: NCQFrame *ncq_fis = (NCQFrame*)cmd_fis; ! 727: uint8_t tag = ncq_fis->tag >> 3; ! 728: NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[tag]; ! 729: ! 730: if (ncq_tfs->used) { ! 731: /* error - already in use */ ! 732: fprintf(stderr, "%s: tag %d already used\n", __FUNCTION__, tag); ! 733: return; ! 734: } ! 735: ! 736: ncq_tfs->used = 1; ! 737: ncq_tfs->drive = &s->dev[port]; ! 738: ncq_tfs->slot = slot; ! 739: ncq_tfs->lba = ((uint64_t)ncq_fis->lba5 << 40) | ! 740: ((uint64_t)ncq_fis->lba4 << 32) | ! 741: ((uint64_t)ncq_fis->lba3 << 24) | ! 742: ((uint64_t)ncq_fis->lba2 << 16) | ! 743: ((uint64_t)ncq_fis->lba1 << 8) | ! 744: (uint64_t)ncq_fis->lba0; ! 745: ! 746: /* Note: We calculate the sector count, but don't currently rely on it. ! 747: * The total size of the DMA buffer tells us the transfer size instead. */ ! 748: ncq_tfs->sector_count = ((uint16_t)ncq_fis->sector_count_high << 8) | ! 749: ncq_fis->sector_count_low; ! 750: ! 751: DPRINTF(port, "NCQ transfer LBA from %ld to %ld, drive max %ld\n", ! 752: ncq_tfs->lba, ncq_tfs->lba + ncq_tfs->sector_count - 2, ! 753: s->dev[port].port.ifs[0].nb_sectors - 1); ! 754: ! 755: ahci_populate_sglist(&s->dev[port], &ncq_tfs->sglist); ! 756: ncq_tfs->tag = tag; ! 757: ! 758: switch(ncq_fis->command) { ! 759: case READ_FPDMA_QUEUED: ! 760: DPRINTF(port, "NCQ reading %d sectors from LBA %ld, tag %d\n", ! 761: ncq_tfs->sector_count-1, ncq_tfs->lba, ncq_tfs->tag); ! 762: ncq_tfs->is_read = 1; ! 763: ! 764: DPRINTF(port, "tag %d aio read %ld\n", ncq_tfs->tag, ncq_tfs->lba); ! 765: ncq_tfs->aiocb = dma_bdrv_read(ncq_tfs->drive->port.ifs[0].bs, ! 766: &ncq_tfs->sglist, ncq_tfs->lba, ! 767: ncq_cb, ncq_tfs); ! 768: break; ! 769: case WRITE_FPDMA_QUEUED: ! 770: DPRINTF(port, "NCQ writing %d sectors to LBA %ld, tag %d\n", ! 771: ncq_tfs->sector_count-1, ncq_tfs->lba, ncq_tfs->tag); ! 772: ncq_tfs->is_read = 0; ! 773: ! 774: DPRINTF(port, "tag %d aio write %ld\n", ncq_tfs->tag, ncq_tfs->lba); ! 775: ncq_tfs->aiocb = dma_bdrv_write(ncq_tfs->drive->port.ifs[0].bs, ! 776: &ncq_tfs->sglist, ncq_tfs->lba, ! 777: ncq_cb, ncq_tfs); ! 778: break; ! 779: default: ! 780: DPRINTF(port, "error: tried to process non-NCQ command as NCQ\n"); ! 781: qemu_sglist_destroy(&ncq_tfs->sglist); ! 782: break; ! 783: } ! 784: } ! 785: ! 786: static int handle_cmd(AHCIState *s, int port, int slot) ! 787: { ! 788: IDEState *ide_state; ! 789: uint32_t opts; ! 790: uint64_t tbl_addr; ! 791: AHCICmdHdr *cmd; ! 792: uint8_t *cmd_fis; ! 793: target_phys_addr_t cmd_len; ! 794: ! 795: if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) { ! 796: /* Engine currently busy, try again later */ ! 797: DPRINTF(port, "engine busy\n"); ! 798: return -1; ! 799: } ! 800: ! 801: cmd = &((AHCICmdHdr *)s->dev[port].lst)[slot]; ! 802: ! 803: if (!s->dev[port].lst) { ! 804: DPRINTF(port, "error: lst not given but cmd handled"); ! 805: return -1; ! 806: } ! 807: ! 808: /* remember current slot handle for later */ ! 809: s->dev[port].cur_cmd = cmd; ! 810: ! 811: opts = le32_to_cpu(cmd->opts); ! 812: tbl_addr = le64_to_cpu(cmd->tbl_addr); ! 813: ! 814: cmd_len = 0x80; ! 815: cmd_fis = cpu_physical_memory_map(tbl_addr, &cmd_len, 1); ! 816: ! 817: if (!cmd_fis) { ! 818: DPRINTF(port, "error: guest passed us an invalid cmd fis\n"); ! 819: return -1; ! 820: } ! 821: ! 822: /* The device we are working for */ ! 823: ide_state = &s->dev[port].port.ifs[0]; ! 824: ! 825: if (!ide_state->bs) { ! 826: DPRINTF(port, "error: guest accessed unused port"); ! 827: goto out; ! 828: } ! 829: ! 830: debug_print_fis(cmd_fis, 0x90); ! 831: //debug_print_fis(cmd_fis, (opts & AHCI_CMD_HDR_CMD_FIS_LEN) * 4); ! 832: ! 833: switch (cmd_fis[0]) { ! 834: case SATA_FIS_TYPE_REGISTER_H2D: ! 835: break; ! 836: default: ! 837: DPRINTF(port, "unknown command cmd_fis[0]=%02x cmd_fis[1]=%02x " ! 838: "cmd_fis[2]=%02x\n", cmd_fis[0], cmd_fis[1], ! 839: cmd_fis[2]); ! 840: goto out; ! 841: break; ! 842: } ! 843: ! 844: switch (cmd_fis[1]) { ! 845: case SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER: ! 846: break; ! 847: case 0: ! 848: break; ! 849: default: ! 850: DPRINTF(port, "unknown command cmd_fis[0]=%02x cmd_fis[1]=%02x " ! 851: "cmd_fis[2]=%02x\n", cmd_fis[0], cmd_fis[1], ! 852: cmd_fis[2]); ! 853: goto out; ! 854: break; ! 855: } ! 856: ! 857: switch (s->dev[port].port_state) { ! 858: case STATE_RUN: ! 859: if (cmd_fis[15] & ATA_SRST) { ! 860: s->dev[port].port_state = STATE_RESET; ! 861: } ! 862: break; ! 863: case STATE_RESET: ! 864: if (!(cmd_fis[15] & ATA_SRST)) { ! 865: ahci_reset_port(s, port); ! 866: } ! 867: break; ! 868: } ! 869: ! 870: if (cmd_fis[1] == SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER) { ! 871: ! 872: /* Check for NCQ command */ ! 873: if ((cmd_fis[2] == READ_FPDMA_QUEUED) || ! 874: (cmd_fis[2] == WRITE_FPDMA_QUEUED)) { ! 875: process_ncq_command(s, port, cmd_fis, slot); ! 876: goto out; ! 877: } ! 878: ! 879: /* Decompose the FIS */ ! 880: ide_state->nsector = (int64_t)((cmd_fis[13] << 8) | cmd_fis[12]); ! 881: ide_state->feature = cmd_fis[3]; ! 882: if (!ide_state->nsector) { ! 883: ide_state->nsector = 256; ! 884: } ! 885: ! 886: if (ide_state->drive_kind != IDE_CD) { ! 887: ide_set_sector(ide_state, (cmd_fis[6] << 16) | (cmd_fis[5] << 8) | ! 888: cmd_fis[4]); ! 889: } ! 890: ! 891: /* Copy the ACMD field (ATAPI packet, if any) from the AHCI command ! 892: * table to ide_state->io_buffer ! 893: */ ! 894: if (opts & AHCI_CMD_ATAPI) { ! 895: memcpy(ide_state->io_buffer, &cmd_fis[AHCI_COMMAND_TABLE_ACMD], 0x10); ! 896: ide_state->lcyl = 0x14; ! 897: ide_state->hcyl = 0xeb; ! 898: debug_print_fis(ide_state->io_buffer, 0x10); ! 899: ide_state->feature = IDE_FEATURE_DMA; ! 900: s->dev[port].done_atapi_packet = 0; ! 901: /* XXX send PIO setup FIS */ ! 902: } ! 903: ! 904: ide_state->error = 0; ! 905: ! 906: /* Reset transferred byte counter */ ! 907: cmd->status = 0; ! 908: ! 909: /* We're ready to process the command in FIS byte 2. */ ! 910: ide_exec_cmd(&s->dev[port].port, cmd_fis[2]); ! 911: ! 912: if (s->dev[port].port.ifs[0].status & READY_STAT) { ! 913: ahci_write_fis_d2h(&s->dev[port], cmd_fis); ! 914: } ! 915: } ! 916: ! 917: out: ! 918: cpu_physical_memory_unmap(cmd_fis, cmd_len, 1, cmd_len); ! 919: ! 920: if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) { ! 921: /* async command, complete later */ ! 922: s->dev[port].busy_slot = slot; ! 923: return -1; ! 924: } ! 925: ! 926: /* done handling the command */ ! 927: return 0; ! 928: } ! 929: ! 930: /* DMA dev <-> ram */ ! 931: static int ahci_start_transfer(IDEDMA *dma) ! 932: { ! 933: AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); ! 934: IDEState *s = &ad->port.ifs[0]; ! 935: uint32_t size = (uint32_t)(s->data_end - s->data_ptr); ! 936: /* write == ram -> device */ ! 937: uint32_t opts = le32_to_cpu(ad->cur_cmd->opts); ! 938: int is_write = opts & AHCI_CMD_WRITE; ! 939: int is_atapi = opts & AHCI_CMD_ATAPI; ! 940: int has_sglist = 0; ! 941: ! 942: if (is_atapi && !ad->done_atapi_packet) { ! 943: /* already prepopulated iobuffer */ ! 944: ad->done_atapi_packet = 1; ! 945: goto out; ! 946: } ! 947: ! 948: if (!ahci_populate_sglist(ad, &s->sg)) { ! 949: has_sglist = 1; ! 950: } ! 951: ! 952: DPRINTF(ad->port_no, "%sing %d bytes on %s w/%s sglist\n", ! 953: is_write ? "writ" : "read", size, is_atapi ? "atapi" : "ata", ! 954: has_sglist ? "" : "o"); ! 955: ! 956: if (is_write && has_sglist && (s->data_ptr < s->data_end)) { ! 957: read_from_sglist(s->data_ptr, size, &s->sg); ! 958: } ! 959: ! 960: if (!is_write && has_sglist && (s->data_ptr < s->data_end)) { ! 961: write_to_sglist(s->data_ptr, size, &s->sg); ! 962: } ! 963: ! 964: /* update number of transferred bytes */ ! 965: ad->cur_cmd->status = cpu_to_le32(le32_to_cpu(ad->cur_cmd->status) + size); ! 966: ! 967: out: ! 968: /* declare that we processed everything */ ! 969: s->data_ptr = s->data_end; ! 970: ! 971: if (has_sglist) { ! 972: qemu_sglist_destroy(&s->sg); ! 973: } ! 974: ! 975: s->end_transfer_func(s); ! 976: ! 977: if (!(s->status & DRQ_STAT)) { ! 978: /* done with DMA */ ! 979: ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS); ! 980: } ! 981: ! 982: return 0; ! 983: } ! 984: ! 985: static void ahci_start_dma(IDEDMA *dma, IDEState *s, ! 986: BlockDriverCompletionFunc *dma_cb) ! 987: { ! 988: AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); ! 989: ! 990: DPRINTF(ad->port_no, "\n"); ! 991: ad->dma_cb = dma_cb; ! 992: ad->dma_status |= BM_STATUS_DMAING; ! 993: dma_cb(s, 0); ! 994: } ! 995: ! 996: static int ahci_dma_prepare_buf(IDEDMA *dma, int is_write) ! 997: { ! 998: AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); ! 999: IDEState *s = &ad->port.ifs[0]; ! 1000: int i; ! 1001: ! 1002: ahci_populate_sglist(ad, &s->sg); ! 1003: ! 1004: s->io_buffer_size = 0; ! 1005: for (i = 0; i < s->sg.nsg; i++) { ! 1006: s->io_buffer_size += s->sg.sg[i].len; ! 1007: } ! 1008: ! 1009: DPRINTF(ad->port_no, "len=%#x\n", s->io_buffer_size); ! 1010: return s->io_buffer_size != 0; ! 1011: } ! 1012: ! 1013: static int ahci_dma_rw_buf(IDEDMA *dma, int is_write) ! 1014: { ! 1015: AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); ! 1016: IDEState *s = &ad->port.ifs[0]; ! 1017: uint8_t *p = s->io_buffer + s->io_buffer_index; ! 1018: int l = s->io_buffer_size - s->io_buffer_index; ! 1019: ! 1020: if (ahci_populate_sglist(ad, &s->sg)) { ! 1021: return 0; ! 1022: } ! 1023: ! 1024: if (is_write) { ! 1025: write_to_sglist(p, l, &s->sg); ! 1026: } else { ! 1027: read_from_sglist(p, l, &s->sg); ! 1028: } ! 1029: ! 1030: /* update number of transferred bytes */ ! 1031: ad->cur_cmd->status = cpu_to_le32(le32_to_cpu(ad->cur_cmd->status) + l); ! 1032: s->io_buffer_index += l; ! 1033: ! 1034: DPRINTF(ad->port_no, "len=%#x\n", l); ! 1035: ! 1036: return 1; ! 1037: } ! 1038: ! 1039: static int ahci_dma_set_unit(IDEDMA *dma, int unit) ! 1040: { ! 1041: /* only a single unit per link */ ! 1042: return 0; ! 1043: } ! 1044: ! 1045: static int ahci_dma_add_status(IDEDMA *dma, int status) ! 1046: { ! 1047: AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); ! 1048: ad->dma_status |= status; ! 1049: DPRINTF(ad->port_no, "set status: %x\n", status); ! 1050: ! 1051: if (status & BM_STATUS_INT) { ! 1052: ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS); ! 1053: } ! 1054: ! 1055: return 0; ! 1056: } ! 1057: ! 1058: static int ahci_dma_set_inactive(IDEDMA *dma) ! 1059: { ! 1060: AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); ! 1061: ! 1062: DPRINTF(ad->port_no, "dma done\n"); ! 1063: ! 1064: /* update d2h status */ ! 1065: ahci_write_fis_d2h(ad, NULL); ! 1066: ! 1067: ad->dma_cb = NULL; ! 1068: ! 1069: /* maybe we still have something to process, check later */ ! 1070: ad->check_bh = qemu_bh_new(ahci_check_cmd_bh, ad); ! 1071: qemu_bh_schedule(ad->check_bh); ! 1072: ! 1073: return 0; ! 1074: } ! 1075: ! 1076: static void ahci_irq_set(void *opaque, int n, int level) ! 1077: { ! 1078: } ! 1079: ! 1080: static void ahci_dma_restart_cb(void *opaque, int running, int reason) ! 1081: { ! 1082: } ! 1083: ! 1084: static int ahci_dma_reset(IDEDMA *dma) ! 1085: { ! 1086: return 0; ! 1087: } ! 1088: ! 1089: static const IDEDMAOps ahci_dma_ops = { ! 1090: .start_dma = ahci_start_dma, ! 1091: .start_transfer = ahci_start_transfer, ! 1092: .prepare_buf = ahci_dma_prepare_buf, ! 1093: .rw_buf = ahci_dma_rw_buf, ! 1094: .set_unit = ahci_dma_set_unit, ! 1095: .add_status = ahci_dma_add_status, ! 1096: .set_inactive = ahci_dma_set_inactive, ! 1097: .restart_cb = ahci_dma_restart_cb, ! 1098: .reset = ahci_dma_reset, ! 1099: }; ! 1100: ! 1101: void ahci_init(AHCIState *s, DeviceState *qdev, int ports) ! 1102: { ! 1103: qemu_irq *irqs; ! 1104: int i; ! 1105: ! 1106: s->ports = ports; ! 1107: s->dev = qemu_mallocz(sizeof(AHCIDevice) * ports); ! 1108: ahci_reg_init(s); ! 1109: s->mem = cpu_register_io_memory(ahci_readfn, ahci_writefn, s, ! 1110: DEVICE_LITTLE_ENDIAN); ! 1111: irqs = qemu_allocate_irqs(ahci_irq_set, s, s->ports); ! 1112: ! 1113: for (i = 0; i < s->ports; i++) { ! 1114: AHCIDevice *ad = &s->dev[i]; ! 1115: ! 1116: ide_bus_new(&ad->port, qdev, i); ! 1117: ide_init2(&ad->port, irqs[i]); ! 1118: ! 1119: ad->hba = s; ! 1120: ad->port_no = i; ! 1121: ad->port.dma = &ad->dma; ! 1122: ad->port.dma->ops = &ahci_dma_ops; ! 1123: ad->port_regs.cmd = PORT_CMD_SPIN_UP | PORT_CMD_POWER_ON; ! 1124: } ! 1125: } ! 1126: ! 1127: void ahci_uninit(AHCIState *s) ! 1128: { ! 1129: qemu_free(s->dev); ! 1130: } ! 1131: ! 1132: void ahci_pci_map(PCIDevice *pci_dev, int region_num, ! 1133: pcibus_t addr, pcibus_t size, int type) ! 1134: { ! 1135: struct AHCIPCIState *d = (struct AHCIPCIState *)pci_dev; ! 1136: AHCIState *s = &d->ahci; ! 1137: ! 1138: cpu_register_physical_memory(addr, size, s->mem); ! 1139: } ! 1140: ! 1141: void ahci_reset(void *opaque) ! 1142: { ! 1143: struct AHCIPCIState *d = opaque; ! 1144: int i; ! 1145: ! 1146: d->ahci.control_regs.irqstatus = 0; ! 1147: d->ahci.control_regs.ghc = 0; ! 1148: ! 1149: for (i = 0; i < d->ahci.ports; i++) { ! 1150: ahci_reset_port(&d->ahci, i); ! 1151: } ! 1152: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.