Commit 682df581 authored by Stefan Hajnoczi's avatar Stefan Hajnoczi
Browse files

Merge remote-tracking branch 'jsnow/tags/ide-pull-request' into staging



# gpg: Signature made Mon 14 Nov 2016 04:16:48 PM GMT
# gpg:                using RSA key 0x7DEF8106AAFC390E
# gpg: Good signature from "John Snow (John Huston) <jsnow@redhat.com>"
# Primary key fingerprint: FAEB 9711 A12C F475 812F  18F2 88A9 064D 1835 61EB
#      Subkey fingerprint: F9B7 ABDB BCAC DF95 BE76  CBD0 7DEF 8106 AAFC 390E

* jsnow/tags/ide-pull-request:
  ahci-test: add QMP tray test for ATAPI
  libqos/ahci: Add get_sense and test_ready
  libqos/ahci: Add ATAPI tray macros
  libqos/ahci: Support expected errors
  libqtest: add qmp_eventwait_ref
  block-backend: Always notify on blk_eject
  ahci-test: test atapi read_cd with bcl, nb_sectors = 0
  ahci-test: Create smaller test ISO images
  atapi: classify read_cd as conditionally returning data

Message-id: 1479140746-22142-1-git-send-email-jsnow@redhat.com
Signed-off-by: default avatarStefan Hajnoczi <stefanha@redhat.com>
parents a77beb0f 22381d41
Loading
Loading
Loading
Loading
+7 −6
Original line number Diff line number Diff line
@@ -1393,13 +1393,14 @@ void blk_eject(BlockBackend *blk, bool eject_flag)

    if (bs) {
        bdrv_eject(bs, eject_flag);
    }

    /* Whether or not we ejected on the backend,
     * the frontend experienced a tray event. */
    id = blk_get_attached_dev_id(blk);
    qapi_event_send_device_tray_moved(blk_name(blk), id,
                                      eject_flag, &error_abort);
    g_free(id);

    }
}

int blk_get_flags(BlockBackend *blk)
+40 −11
Original line number Diff line number Diff line
@@ -637,6 +637,23 @@ static unsigned int event_status_media(IDEState *s,
    return 8; /* We wrote to 4 extra bytes from the header */
}

/*
 * Before transferring data or otherwise signalling acceptance of a command
 * marked CONDDATA, we must check the validity of the byte_count_limit.
 */
static bool validate_bcl(IDEState *s)
{
    /* TODO: Check IDENTIFY data word 125 for defacult BCL (currently 0) */
    if (s->atapi_dma || atapi_byte_count_limit(s)) {
        return true;
    }

    /* TODO: Move abort back into core.c and introduce proper error flow between
     *       ATAPI layer and IDE core layer */
    ide_abort_command(s);
    return false;
}

static void cmd_get_event_status_notification(IDEState *s,
                                              uint8_t *buf)
{
@@ -1028,12 +1045,19 @@ static void cmd_read_cd(IDEState *s, uint8_t* buf)
        return;
    }

    transfer_request = buf[9];
    switch(transfer_request & 0xf8) {
    case 0x00:
    transfer_request = buf[9] & 0xf8;
    if (transfer_request == 0x00) {
        /* nothing */
        ide_atapi_cmd_ok(s);
        break;
        return;
    }

    /* Check validity of BCL before transferring data */
    if (!validate_bcl(s)) {
        return;
    }

    switch (transfer_request) {
    case 0x10:
        /* normal read */
        ide_atapi_cmd_read(s, lba, nb_sectors, 2048);
@@ -1266,6 +1290,14 @@ enum {
     * See ATA8-ACS3 "7.21.5 Byte Count Limit"
     */
    NONDATA = 0x04,

    /*
     * CONDDATA implies a command that transfers data only conditionally based
     * on the presence of suboptions. It should be exempt from the BCL check at
     * command validation time, but it needs to be checked at the command
     * handler level instead.
     */
    CONDDATA = 0x08,
};

static const struct AtapiCmd {
@@ -1289,7 +1321,7 @@ static const struct AtapiCmd {
    [ 0xad ] = { cmd_read_dvd_structure,            CHECK_READY },
    [ 0xbb ] = { cmd_set_speed,                     NONDATA },
    [ 0xbd ] = { cmd_mechanism_status,              0 },
    [ 0xbe ] = { cmd_read_cd,                       CHECK_READY },
    [ 0xbe ] = { cmd_read_cd,                       CHECK_READY | CONDDATA },
    /* [1] handler detects and reports not ready condition itself */
};

@@ -1348,15 +1380,12 @@ void ide_atapi_cmd(IDEState *s)
        return;
    }

    /* Nondata commands permit the byte_count_limit to be 0.
    /* Commands that don't transfer DATA permit the byte_count_limit to be 0.
     * If this is a data-transferring PIO command and BCL is 0,
     * we abort at the /ATA/ level, not the ATAPI level.
     * See ATA8 ACS3 section 7.17.6.49 and 7.21.5 */
    if (cmd->handler && !(cmd->flags & NONDATA)) {
        /* TODO: Check IDENTIFY data word 125 for default BCL (currently 0) */
        if (!(atapi_byte_count_limit(s) || s->atapi_dma)) {
            /* TODO: Move abort back into core.c and make static inline again */
            ide_abort_command(s);
    if (cmd->handler && !(cmd->flags & (NONDATA | CONDDATA))) {
        if (!validate_bcl(s)) {
            return;
        }
    }
+129 −8
Original line number Diff line number Diff line
@@ -1473,8 +1473,13 @@ static int ahci_cb_cmp_buff(AHCIQState *ahci, AHCICommand *cmd,
                            const AHCIOpts *opts)
{
    unsigned char *tx = opts->opaque;
    unsigned char *rx = g_malloc0(opts->size);
    unsigned char *rx;

    if (!opts->size) {
        return 0;
    }

    rx = g_malloc0(opts->size);
    bufread(opts->buffer, rx, opts->size);
    g_assert_cmphex(memcmp(tx, rx, opts->size), ==, 0);
    g_free(rx);
@@ -1482,7 +1487,8 @@ static int ahci_cb_cmp_buff(AHCIQState *ahci, AHCICommand *cmd,
    return 0;
}

static void ahci_test_cdrom(int nsectors, bool dma)
static void ahci_test_cdrom(int nsectors, bool dma, uint8_t cmd,
                            bool override_bcl, uint16_t bcl)
{
    AHCIQState *ahci;
    unsigned char *tx;
@@ -1493,10 +1499,13 @@ static void ahci_test_cdrom(int nsectors, bool dma)
        .atapi = true,
        .atapi_dma = dma,
        .post_cb = ahci_cb_cmp_buff,
        .set_bcl = override_bcl,
        .bcl = bcl,
    };
    uint64_t iso_size = ATAPI_SECTOR_SIZE * (nsectors + 1);

    /* Prepare ISO and fill 'tx' buffer */
    fd = prepare_iso(1024 * 1024, &tx, &iso);
    fd = prepare_iso(iso_size, &tx, &iso);
    opts.opaque = tx;

    /* Standard startup wonkery, but use ide-cd and our special iso file */
@@ -1505,7 +1514,7 @@ static void ahci_test_cdrom(int nsectors, bool dma)
                                "-device ide-cd,drive=drive0 ", iso);

    /* Build & Send AHCI command */
    ahci_exec(ahci, ahci_port_select(ahci), CMD_ATAPI_READ_10, &opts);
    ahci_exec(ahci, ahci_port_select(ahci), cmd, &opts);

    /* Cleanup */
    g_free(tx);
@@ -1513,24 +1522,133 @@ static void ahci_test_cdrom(int nsectors, bool dma)
    remove_iso(fd, iso);
}

static void ahci_test_cdrom_read10(int nsectors, bool dma)
{
    ahci_test_cdrom(nsectors, dma, CMD_ATAPI_READ_10, false, 0);
}

static void test_cdrom_dma(void)
{
    ahci_test_cdrom(1, true);
    ahci_test_cdrom_read10(1, true);
}

static void test_cdrom_dma_multi(void)
{
    ahci_test_cdrom(3, true);
    ahci_test_cdrom_read10(3, true);
}

static void test_cdrom_pio(void)
{
    ahci_test_cdrom(1, false);
    ahci_test_cdrom_read10(1, false);
}

static void test_cdrom_pio_multi(void)
{
    ahci_test_cdrom(3, false);
    ahci_test_cdrom_read10(3, false);
}

/* Regression test: Test that a READ_CD command with a BCL of 0 but a size of 0
 * completes as a NOP instead of erroring out. */
static void test_atapi_bcl(void)
{
    ahci_test_cdrom(0, false, CMD_ATAPI_READ_CD, true, 0);
}


static void atapi_wait_tray(bool open)
{
    QDict *rsp = qmp_eventwait_ref("DEVICE_TRAY_MOVED");
    QDict *data = qdict_get_qdict(rsp, "data");
    if (open) {
        g_assert(qdict_get_bool(data, "tray-open"));
    } else {
        g_assert(!qdict_get_bool(data, "tray-open"));
    }
    QDECREF(rsp);
}

static void test_atapi_tray(void)
{
    AHCIQState *ahci;
    unsigned char *tx;
    char *iso;
    int fd;
    uint8_t port, sense, asc;
    uint64_t iso_size = ATAPI_SECTOR_SIZE;
    QDict *rsp;

    fd = prepare_iso(iso_size, &tx, &iso);
    ahci = ahci_boot_and_enable("-drive if=none,id=drive0,file=%s,format=raw "
                                "-M q35 "
                                "-device ide-cd,drive=drive0 ", iso);
    port = ahci_port_select(ahci);

    ahci_atapi_eject(ahci, port);
    atapi_wait_tray(true);

    ahci_atapi_load(ahci, port);
    atapi_wait_tray(false);

    /* Remove media */
    qmp_async("{'execute': 'blockdev-open-tray', "
               "'arguments': {'device': 'drive0'}}");
    atapi_wait_tray(true);
    rsp = qmp_receive();
    QDECREF(rsp);

    qmp_discard_response("{'execute': 'x-blockdev-remove-medium', "
                         "'arguments': {'device': 'drive0'}}");

    /* Test the tray without a medium */
    ahci_atapi_load(ahci, port);
    atapi_wait_tray(false);

    ahci_atapi_eject(ahci, port);
    atapi_wait_tray(true);

    /* Re-insert media */
    qmp_discard_response("{'execute': 'blockdev-add', "
                          "'arguments': {'node-name': 'node0', "
                                        "'driver': 'raw', "
                                        "'file': { 'driver': 'file', "
                                                  "'filename': %s }}}", iso);
    qmp_discard_response("{'execute': 'x-blockdev-insert-medium',"
                          "'arguments': { 'device': 'drive0', "
                                         "'node-name': 'node0' }}");

    /* Again, the event shows up first */
    qmp_async("{'execute': 'blockdev-close-tray', "
               "'arguments': {'device': 'drive0'}}");
    atapi_wait_tray(false);
    rsp = qmp_receive();
    QDECREF(rsp);

    /* Now, to convince ATAPI we understand the media has changed... */
    ahci_atapi_test_ready(ahci, port, false, SENSE_NOT_READY);
    ahci_atapi_get_sense(ahci, port, &sense, &asc);
    g_assert_cmpuint(sense, ==, SENSE_NOT_READY);
    g_assert_cmpuint(asc, ==, ASC_MEDIUM_NOT_PRESENT);

    ahci_atapi_test_ready(ahci, port, false, SENSE_UNIT_ATTENTION);
    ahci_atapi_get_sense(ahci, port, &sense, &asc);
    g_assert_cmpuint(sense, ==, SENSE_UNIT_ATTENTION);
    g_assert_cmpuint(asc, ==, ASC_MEDIUM_MAY_HAVE_CHANGED);

    ahci_atapi_test_ready(ahci, port, true, SENSE_NO_SENSE);
    ahci_atapi_get_sense(ahci, port, &sense, &asc);
    g_assert_cmpuint(sense, ==, SENSE_NO_SENSE);

    /* Final tray test. */
    ahci_atapi_eject(ahci, port);
    atapi_wait_tray(true);

    ahci_atapi_load(ahci, port);
    atapi_wait_tray(false);

    /* Cleanup */
    g_free(tx);
    ahci_shutdown(ahci);
    remove_iso(fd, iso);
}

/******************************************************************************/
@@ -1822,6 +1940,9 @@ int main(int argc, char **argv)
    qtest_add_func("/ahci/cdrom/pio/single", test_cdrom_pio);
    qtest_add_func("/ahci/cdrom/pio/multi", test_cdrom_pio_multi);

    qtest_add_func("/ahci/cdrom/pio/bcl", test_atapi_bcl);
    qtest_add_func("/ahci/cdrom/eject", test_atapi_tray);

    ret = g_test_run();

    /* Cleanup */
+113 −11
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ struct AHCICommand {
    uint8_t name;
    uint8_t port;
    uint8_t slot;
    uint8_t errors;
    uint32_t interrupts;
    uint64_t xbytes;
    uint32_t prd_size;
@@ -402,12 +403,14 @@ void ahci_port_clear(AHCIQState *ahci, uint8_t port)
/**
 * Check a port for errors.
 */
void ahci_port_check_error(AHCIQState *ahci, uint8_t port)
void ahci_port_check_error(AHCIQState *ahci, uint8_t port,
                           uint32_t imask, uint8_t emask)
{
    uint32_t reg;

    /* The upper 9 bits of the IS register all indicate errors. */
    reg = ahci_px_rreg(ahci, port, AHCI_PX_IS);
    reg &= ~imask;
    reg >>= 23;
    g_assert_cmphex(reg, ==, 0);

@@ -417,8 +420,13 @@ void ahci_port_check_error(AHCIQState *ahci, uint8_t port)

    /* The TFD also has two error sections. */
    reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD);
    if (!emask) {
        ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_ERR);
    ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR);
    } else {
        ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_ERR);
    }
    ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR & (~emask << 8));
    ASSERT_BIT_SET(reg, AHCI_PX_TFD_ERR & (emask << 8));
}

void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port,
@@ -633,7 +641,8 @@ void ahci_exec(AHCIQState *ahci, uint8_t port,

    /* Command creation */
    if (opts->atapi) {
        cmd = ahci_atapi_command_create(op);
        uint16_t bcl = opts->set_bcl ? opts->bcl : ATAPI_SECTOR_SIZE;
        cmd = ahci_atapi_command_create(op, bcl);
        if (opts->atapi_dma) {
            ahci_command_enable_atapi_dma(cmd);
        }
@@ -864,19 +873,82 @@ AHCICommand *ahci_command_create(uint8_t command_name)
    return cmd;
}

AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd)
AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd, uint16_t bcl)
{
    AHCICommand *cmd = ahci_command_create(CMD_PACKET);
    cmd->atapi_cmd = g_malloc0(16);
    cmd->atapi_cmd[0] = scsi_cmd;
    /* ATAPI needs a PIO transfer chunk size set inside of the LBA registers.
     * The block/sector size is a natural default. */
    cmd->fis.lba_lo[1] = ATAPI_SECTOR_SIZE >> 8 & 0xFF;
    cmd->fis.lba_lo[2] = ATAPI_SECTOR_SIZE & 0xFF;

    stw_le_p(&cmd->fis.lba_lo[1], bcl);
    return cmd;
}

void ahci_atapi_test_ready(AHCIQState *ahci, uint8_t port,
                           bool ready, uint8_t expected_sense)
{
    AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_TEST_UNIT_READY, 0);
    ahci_command_set_size(cmd, 0);
    if (!ready) {
        cmd->interrupts |= AHCI_PX_IS_TFES;
        cmd->errors |= expected_sense << 4;
    }
    ahci_command_commit(ahci, cmd, port);
    ahci_command_issue(ahci, cmd);
    ahci_command_verify(ahci, cmd);
    ahci_command_free(cmd);
}

static int copy_buffer(AHCIQState *ahci, AHCICommand *cmd,
                        const AHCIOpts *opts)
{
    unsigned char *rx = opts->opaque;
    bufread(opts->buffer, rx, opts->size);
    return 0;
}

void ahci_atapi_get_sense(AHCIQState *ahci, uint8_t port,
                          uint8_t *sense, uint8_t *asc)
{
    unsigned char *rx;
    AHCIOpts opts = {
        .size = 18,
        .atapi = true,
        .post_cb = copy_buffer,
    };
    rx = g_malloc(18);
    opts.opaque = rx;

    ahci_exec(ahci, port, CMD_ATAPI_REQUEST_SENSE, &opts);

    *sense = rx[2];
    *asc = rx[12];

    g_free(rx);
}

void ahci_atapi_eject(AHCIQState *ahci, uint8_t port)
{
    AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_START_STOP_UNIT, 0);
    ahci_command_set_size(cmd, 0);

    cmd->atapi_cmd[4] = 0x02; /* loej = true */
    ahci_command_commit(ahci, cmd, port);
    ahci_command_issue(ahci, cmd);
    ahci_command_verify(ahci, cmd);
    ahci_command_free(cmd);
}

void ahci_atapi_load(AHCIQState *ahci, uint8_t port)
{
    AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_START_STOP_UNIT, 0);
    ahci_command_set_size(cmd, 0);

    cmd->atapi_cmd[4] = 0x03; /* loej,start = true */
    ahci_command_commit(ahci, cmd, port);
    ahci_command_issue(ahci, cmd);
    ahci_command_verify(ahci, cmd);
    ahci_command_free(cmd);
}

void ahci_command_free(AHCICommand *cmd)
{
    g_free(cmd->atapi_cmd);
@@ -901,12 +973,22 @@ static void ahci_atapi_command_set_offset(AHCICommand *cmd, uint64_t lba)

    switch (cbd[0]) {
    case CMD_ATAPI_READ_10:
    case CMD_ATAPI_READ_CD:
        g_assert_cmpuint(lba, <=, UINT32_MAX);
        stl_be_p(&cbd[2], lba);
        break;
    case CMD_ATAPI_REQUEST_SENSE:
    case CMD_ATAPI_TEST_UNIT_READY:
    case CMD_ATAPI_START_STOP_UNIT:
        g_assert_cmpuint(lba, ==, 0x00);
        break;
    default:
        /* SCSI doesn't have uniform packet formats,
         * so you have to add support for it manually. Sorry! */
        fprintf(stderr, "The Libqos AHCI driver does not support the "
                "set_offset operation for ATAPI command 0x%02x, "
                "please add support.\n",
                cbd[0]);
        g_assert_not_reached();
    }
}
@@ -951,6 +1033,7 @@ static void ahci_atapi_set_size(AHCICommand *cmd, uint64_t xbytes)
{
    unsigned char *cbd = cmd->atapi_cmd;
    uint64_t nsectors = xbytes / 2048;
    uint32_t tmp;
    g_assert(cbd);

    switch (cbd[0]) {
@@ -958,9 +1041,28 @@ static void ahci_atapi_set_size(AHCICommand *cmd, uint64_t xbytes)
        g_assert_cmpuint(nsectors, <=, UINT16_MAX);
        stw_be_p(&cbd[7], nsectors);
        break;
    case CMD_ATAPI_READ_CD:
        /* 24bit BE store */
        g_assert_cmpuint(nsectors, <, 1ULL << 24);
        tmp = nsectors;
        cbd[6] = (tmp & 0xFF0000) >> 16;
        cbd[7] = (tmp & 0xFF00) >> 8;
        cbd[8] = (tmp & 0xFF);
        break;
    case CMD_ATAPI_REQUEST_SENSE:
        g_assert_cmpuint(xbytes, <=, UINT8_MAX);
        cbd[4] = (uint8_t)xbytes;
        break;
    case CMD_ATAPI_TEST_UNIT_READY:
    case CMD_ATAPI_START_STOP_UNIT:
        g_assert_cmpuint(xbytes, ==, 0);
        break;
    default:
        /* SCSI doesn't have uniform packet formats,
         * so you have to add support for it manually. Sorry! */
        fprintf(stderr, "The Libqos AHCI driver does not support the set_size "
                "operation for ATAPI command 0x%02x, please add support.\n",
                cbd[0]);
        g_assert_not_reached();
    }
}
@@ -1105,7 +1207,7 @@ void ahci_command_verify(AHCIQState *ahci, AHCICommand *cmd)
    uint8_t slot = cmd->slot;
    uint8_t port = cmd->port;

    ahci_port_check_error(ahci, port);
    ahci_port_check_error(ahci, port, cmd->interrupts, cmd->errors);
    ahci_port_check_interrupts(ahci, port, cmd->interrupts);
    ahci_port_check_nonbusy(ahci, port, slot);
    ahci_port_check_cmd_sanity(ahci, cmd);
+33 −9
Original line number Diff line number Diff line
@@ -287,7 +287,22 @@ enum {

/* ATAPI Commands */
enum {
    CMD_ATAPI_TEST_UNIT_READY = 0x00,
    CMD_ATAPI_REQUEST_SENSE   = 0x03,
    CMD_ATAPI_START_STOP_UNIT = 0x1b,
    CMD_ATAPI_READ_10         = 0x28,
    CMD_ATAPI_READ_CD         = 0xbe,
};

enum {
    SENSE_NO_SENSE       = 0x00,
    SENSE_NOT_READY      = 0x02,
    SENSE_UNIT_ATTENTION = 0x06,
};

enum {
    ASC_MEDIUM_MAY_HAVE_CHANGED = 0x28,
    ASC_MEDIUM_NOT_PRESENT      = 0x3a,
};

/* AHCI Command Header Flags & Masks*/
@@ -462,12 +477,14 @@ typedef struct AHCICommand AHCICommand;

/* Options to ahci_exec */
typedef struct AHCIOpts {
    size_t size;
    unsigned prd_size;
    uint64_t lba;
    uint64_t buffer;
    bool atapi;
    bool atapi_dma;
    size_t size;        /* Size of transfer */
    unsigned prd_size;  /* Size per-each PRD */
    bool set_bcl;       /* Override the default BCL of ATAPI_SECTOR_SIZE */
    unsigned bcl;       /* Byte Count Limit, for ATAPI PIO */
    uint64_t lba;       /* Starting LBA offset */
    uint64_t buffer;    /* Pointer to source or destination guest buffer */
    bool atapi;         /* ATAPI command? */
    bool atapi_dma;     /* Use DMA for ATAPI? */
    bool error;
    int (*pre_cb)(AHCIQState*, AHCICommand*, const struct AHCIOpts *);
    int (*mid_cb)(AHCIQState*, AHCICommand*, const struct AHCIOpts *);
@@ -573,7 +590,8 @@ void ahci_set_command_header(AHCIQState *ahci, uint8_t port,
void ahci_destroy_command(AHCIQState *ahci, uint8_t port, uint8_t slot);

/* AHCI sanity check routines */
void ahci_port_check_error(AHCIQState *ahci, uint8_t port);
void ahci_port_check_error(AHCIQState *ahci, uint8_t port,
                           uint32_t imask, uint8_t emask);
void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port,
                                uint32_t intr_mask);
void ahci_port_check_nonbusy(AHCIQState *ahci, uint8_t port, uint8_t slot);
@@ -596,10 +614,16 @@ void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd,
             void *buffer, size_t bufsize, uint64_t sector);
void ahci_exec(AHCIQState *ahci, uint8_t port,
               uint8_t op, const AHCIOpts *opts);
void ahci_atapi_test_ready(AHCIQState *ahci, uint8_t port, bool ready,
                           uint8_t expected_sense);
void ahci_atapi_get_sense(AHCIQState *ahci, uint8_t port,
                          uint8_t *sense, uint8_t *asc);
void ahci_atapi_eject(AHCIQState *ahci, uint8_t port);
void ahci_atapi_load(AHCIQState *ahci, uint8_t port);

/* Command: Fine-grained lifecycle */
AHCICommand *ahci_command_create(uint8_t command_name);
AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd);
AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd, uint16_t bcl);
void ahci_command_commit(AHCIQState *ahci, AHCICommand *cmd, uint8_t port);
void ahci_command_issue(AHCIQState *ahci, AHCICommand *cmd);
void ahci_command_issue_async(AHCIQState *ahci, AHCICommand *cmd);
Loading