mirror of
https://github.com/xemu-project/xemu.git
synced 2024-12-05 02:06:40 +00:00
d590474922
If the FIS or DMA engines are already started, do not allow them to be "restarted." As a side-effect of this change, the migration post-load routine must be modified to cope. If the engines are listed as "on" in the migrated registers, they must be cleared to allow the startup routine to see the transition from "off" to "on". As a second side-effect, the extra argument to ahci_cond_engine_start is removed in favor of consistent behavior. Signed-off-by: John Snow <jsnow@redhat.com> Message-id: 1454103689-13042-5-git-send-email-jsnow@redhat.com
1833 lines
55 KiB
C
1833 lines
55 KiB
C
/*
|
|
* QEMU AHCI Emulation
|
|
*
|
|
* Copyright (c) 2010 qiaochong@loongson.cn
|
|
* Copyright (c) 2010 Roland Elek <elek.roland@gmail.com>
|
|
* Copyright (c) 2010 Sebastian Herbszt <herbszt@gmx.de>
|
|
* Copyright (c) 2010 Alexander Graf <agraf@suse.de>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include <hw/hw.h>
|
|
#include <hw/pci/msi.h>
|
|
#include <hw/i386/pc.h>
|
|
#include <hw/pci/pci.h>
|
|
|
|
#include "qemu/error-report.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "sysemu/dma.h"
|
|
#include "internal.h"
|
|
#include <hw/ide/pci.h>
|
|
#include <hw/ide/ahci.h>
|
|
|
|
#define DEBUG_AHCI 0
|
|
|
|
#define DPRINTF(port, fmt, ...) \
|
|
do { \
|
|
if (DEBUG_AHCI) { \
|
|
fprintf(stderr, "ahci: %s: [%d] ", __func__, port); \
|
|
fprintf(stderr, fmt, ## __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
static void check_cmd(AHCIState *s, int port);
|
|
static int handle_cmd(AHCIState *s, int port, uint8_t slot);
|
|
static void ahci_reset_port(AHCIState *s, int port);
|
|
static bool ahci_write_fis_d2h(AHCIDevice *ad);
|
|
static void ahci_init_d2h(AHCIDevice *ad);
|
|
static int ahci_dma_prepare_buf(IDEDMA *dma, int32_t limit);
|
|
static bool ahci_map_clb_address(AHCIDevice *ad);
|
|
static bool ahci_map_fis_address(AHCIDevice *ad);
|
|
static void ahci_unmap_clb_address(AHCIDevice *ad);
|
|
static void ahci_unmap_fis_address(AHCIDevice *ad);
|
|
|
|
|
|
static uint32_t ahci_port_read(AHCIState *s, int port, int offset)
|
|
{
|
|
uint32_t val;
|
|
AHCIPortRegs *pr;
|
|
pr = &s->dev[port].port_regs;
|
|
|
|
switch (offset) {
|
|
case PORT_LST_ADDR:
|
|
val = pr->lst_addr;
|
|
break;
|
|
case PORT_LST_ADDR_HI:
|
|
val = pr->lst_addr_hi;
|
|
break;
|
|
case PORT_FIS_ADDR:
|
|
val = pr->fis_addr;
|
|
break;
|
|
case PORT_FIS_ADDR_HI:
|
|
val = pr->fis_addr_hi;
|
|
break;
|
|
case PORT_IRQ_STAT:
|
|
val = pr->irq_stat;
|
|
break;
|
|
case PORT_IRQ_MASK:
|
|
val = pr->irq_mask;
|
|
break;
|
|
case PORT_CMD:
|
|
val = pr->cmd;
|
|
break;
|
|
case PORT_TFDATA:
|
|
val = pr->tfdata;
|
|
break;
|
|
case PORT_SIG:
|
|
val = pr->sig;
|
|
break;
|
|
case PORT_SCR_STAT:
|
|
if (s->dev[port].port.ifs[0].blk) {
|
|
val = SATA_SCR_SSTATUS_DET_DEV_PRESENT_PHY_UP |
|
|
SATA_SCR_SSTATUS_SPD_GEN1 | SATA_SCR_SSTATUS_IPM_ACTIVE;
|
|
} else {
|
|
val = SATA_SCR_SSTATUS_DET_NODEV;
|
|
}
|
|
break;
|
|
case PORT_SCR_CTL:
|
|
val = pr->scr_ctl;
|
|
break;
|
|
case PORT_SCR_ERR:
|
|
val = pr->scr_err;
|
|
break;
|
|
case PORT_SCR_ACT:
|
|
val = pr->scr_act;
|
|
break;
|
|
case PORT_CMD_ISSUE:
|
|
val = pr->cmd_issue;
|
|
break;
|
|
case PORT_RESERVED:
|
|
default:
|
|
val = 0;
|
|
}
|
|
DPRINTF(port, "offset: 0x%x val: 0x%x\n", offset, val);
|
|
return val;
|
|
|
|
}
|
|
|
|
static void ahci_irq_raise(AHCIState *s, AHCIDevice *dev)
|
|
{
|
|
DeviceState *dev_state = s->container;
|
|
PCIDevice *pci_dev = (PCIDevice *) object_dynamic_cast(OBJECT(dev_state),
|
|
TYPE_PCI_DEVICE);
|
|
|
|
DPRINTF(0, "raise irq\n");
|
|
|
|
if (pci_dev && msi_enabled(pci_dev)) {
|
|
msi_notify(pci_dev, 0);
|
|
} else {
|
|
qemu_irq_raise(s->irq);
|
|
}
|
|
}
|
|
|
|
static void ahci_irq_lower(AHCIState *s, AHCIDevice *dev)
|
|
{
|
|
DeviceState *dev_state = s->container;
|
|
PCIDevice *pci_dev = (PCIDevice *) object_dynamic_cast(OBJECT(dev_state),
|
|
TYPE_PCI_DEVICE);
|
|
|
|
DPRINTF(0, "lower irq\n");
|
|
|
|
if (!pci_dev || !msi_enabled(pci_dev)) {
|
|
qemu_irq_lower(s->irq);
|
|
}
|
|
}
|
|
|
|
static void ahci_check_irq(AHCIState *s)
|
|
{
|
|
int i;
|
|
|
|
DPRINTF(-1, "check irq %#x\n", s->control_regs.irqstatus);
|
|
|
|
s->control_regs.irqstatus = 0;
|
|
for (i = 0; i < s->ports; i++) {
|
|
AHCIPortRegs *pr = &s->dev[i].port_regs;
|
|
if (pr->irq_stat & pr->irq_mask) {
|
|
s->control_regs.irqstatus |= (1 << i);
|
|
}
|
|
}
|
|
|
|
if (s->control_regs.irqstatus &&
|
|
(s->control_regs.ghc & HOST_CTL_IRQ_EN)) {
|
|
ahci_irq_raise(s, NULL);
|
|
} else {
|
|
ahci_irq_lower(s, NULL);
|
|
}
|
|
}
|
|
|
|
static void ahci_trigger_irq(AHCIState *s, AHCIDevice *d,
|
|
int irq_type)
|
|
{
|
|
DPRINTF(d->port_no, "trigger irq %#x -> %x\n",
|
|
irq_type, d->port_regs.irq_mask & irq_type);
|
|
|
|
d->port_regs.irq_stat |= irq_type;
|
|
ahci_check_irq(s);
|
|
}
|
|
|
|
static void map_page(AddressSpace *as, uint8_t **ptr, uint64_t addr,
|
|
uint32_t wanted)
|
|
{
|
|
hwaddr len = wanted;
|
|
|
|
if (*ptr) {
|
|
dma_memory_unmap(as, *ptr, len, DMA_DIRECTION_FROM_DEVICE, len);
|
|
}
|
|
|
|
*ptr = dma_memory_map(as, addr, &len, DMA_DIRECTION_FROM_DEVICE);
|
|
if (len < wanted) {
|
|
dma_memory_unmap(as, *ptr, len, DMA_DIRECTION_FROM_DEVICE, len);
|
|
*ptr = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check the cmd register to see if we should start or stop
|
|
* the DMA or FIS RX engines.
|
|
*
|
|
* @ad: Device to dis/engage.
|
|
*
|
|
* @return 0 on success, -1 on error.
|
|
*/
|
|
static int ahci_cond_start_engines(AHCIDevice *ad)
|
|
{
|
|
AHCIPortRegs *pr = &ad->port_regs;
|
|
bool cmd_start = pr->cmd & PORT_CMD_START;
|
|
bool cmd_on = pr->cmd & PORT_CMD_LIST_ON;
|
|
bool fis_start = pr->cmd & PORT_CMD_FIS_RX;
|
|
bool fis_on = pr->cmd & PORT_CMD_FIS_ON;
|
|
|
|
if (cmd_start && !cmd_on) {
|
|
if (!ahci_map_clb_address(ad)) {
|
|
pr->cmd &= ~PORT_CMD_START;
|
|
error_report("AHCI: Failed to start DMA engine: "
|
|
"bad command list buffer address");
|
|
return -1;
|
|
}
|
|
} else if (!cmd_start && cmd_on) {
|
|
ahci_unmap_clb_address(ad);
|
|
}
|
|
|
|
if (fis_start && !fis_on) {
|
|
if (!ahci_map_fis_address(ad)) {
|
|
pr->cmd &= ~PORT_CMD_FIS_RX;
|
|
error_report("AHCI: Failed to start FIS receive engine: "
|
|
"bad FIS receive buffer address");
|
|
return -1;
|
|
}
|
|
} else if (!fis_start && fis_on) {
|
|
ahci_unmap_fis_address(ad);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
|
|
{
|
|
AHCIPortRegs *pr = &s->dev[port].port_regs;
|
|
|
|
DPRINTF(port, "offset: 0x%x val: 0x%x\n", offset, val);
|
|
switch (offset) {
|
|
case PORT_LST_ADDR:
|
|
pr->lst_addr = val;
|
|
break;
|
|
case PORT_LST_ADDR_HI:
|
|
pr->lst_addr_hi = val;
|
|
break;
|
|
case PORT_FIS_ADDR:
|
|
pr->fis_addr = val;
|
|
break;
|
|
case PORT_FIS_ADDR_HI:
|
|
pr->fis_addr_hi = val;
|
|
break;
|
|
case PORT_IRQ_STAT:
|
|
pr->irq_stat &= ~val;
|
|
ahci_check_irq(s);
|
|
break;
|
|
case PORT_IRQ_MASK:
|
|
pr->irq_mask = val & 0xfdc000ff;
|
|
ahci_check_irq(s);
|
|
break;
|
|
case PORT_CMD:
|
|
/* Block any Read-only fields from being set;
|
|
* including LIST_ON and FIS_ON.
|
|
* The spec requires to set ICC bits to zero after the ICC change
|
|
* is done. We don't support ICC state changes, therefore always
|
|
* force the ICC bits to zero.
|
|
*/
|
|
pr->cmd = (pr->cmd & PORT_CMD_RO_MASK) |
|
|
(val & ~(PORT_CMD_RO_MASK|PORT_CMD_ICC_MASK));
|
|
|
|
/* Check FIS RX and CLB engines */
|
|
ahci_cond_start_engines(&s->dev[port]);
|
|
|
|
/* XXX usually the FIS would be pending on the bus here and
|
|
issuing deferred until the OS enables FIS receival.
|
|
Instead, we only submit it once - which works in most
|
|
cases, but is a hack. */
|
|
if ((pr->cmd & PORT_CMD_FIS_ON) &&
|
|
!s->dev[port].init_d2h_sent) {
|
|
ahci_init_d2h(&s->dev[port]);
|
|
}
|
|
|
|
check_cmd(s, port);
|
|
break;
|
|
case PORT_TFDATA:
|
|
/* Read Only. */
|
|
break;
|
|
case PORT_SIG:
|
|
/* Read Only */
|
|
break;
|
|
case PORT_SCR_STAT:
|
|
/* Read Only */
|
|
break;
|
|
case PORT_SCR_CTL:
|
|
if (((pr->scr_ctl & AHCI_SCR_SCTL_DET) == 1) &&
|
|
((val & AHCI_SCR_SCTL_DET) == 0)) {
|
|
ahci_reset_port(s, port);
|
|
}
|
|
pr->scr_ctl = val;
|
|
break;
|
|
case PORT_SCR_ERR:
|
|
pr->scr_err &= ~val;
|
|
break;
|
|
case PORT_SCR_ACT:
|
|
/* RW1 */
|
|
pr->scr_act |= val;
|
|
break;
|
|
case PORT_CMD_ISSUE:
|
|
pr->cmd_issue |= val;
|
|
check_cmd(s, port);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint64_t ahci_mem_read_32(void *opaque, hwaddr addr)
|
|
{
|
|
AHCIState *s = opaque;
|
|
uint32_t val = 0;
|
|
|
|
if (addr < AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR) {
|
|
switch (addr) {
|
|
case HOST_CAP:
|
|
val = s->control_regs.cap;
|
|
break;
|
|
case HOST_CTL:
|
|
val = s->control_regs.ghc;
|
|
break;
|
|
case HOST_IRQ_STAT:
|
|
val = s->control_regs.irqstatus;
|
|
break;
|
|
case HOST_PORTS_IMPL:
|
|
val = s->control_regs.impl;
|
|
break;
|
|
case HOST_VERSION:
|
|
val = s->control_regs.version;
|
|
break;
|
|
}
|
|
|
|
DPRINTF(-1, "(addr 0x%08X), val 0x%08X\n", (unsigned) addr, val);
|
|
} else if ((addr >= AHCI_PORT_REGS_START_ADDR) &&
|
|
(addr < (AHCI_PORT_REGS_START_ADDR +
|
|
(s->ports * AHCI_PORT_ADDR_OFFSET_LEN)))) {
|
|
val = ahci_port_read(s, (addr - AHCI_PORT_REGS_START_ADDR) >> 7,
|
|
addr & AHCI_PORT_ADDR_OFFSET_MASK);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
/**
|
|
* AHCI 1.3 section 3 ("HBA Memory Registers")
|
|
* Support unaligned 8/16/32 bit reads, and 64 bit aligned reads.
|
|
* Caller is responsible for masking unwanted higher order bytes.
|
|
*/
|
|
static uint64_t ahci_mem_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
hwaddr aligned = addr & ~0x3;
|
|
int ofst = addr - aligned;
|
|
uint64_t lo = ahci_mem_read_32(opaque, aligned);
|
|
uint64_t hi;
|
|
uint64_t val;
|
|
|
|
/* if < 8 byte read does not cross 4 byte boundary */
|
|
if (ofst + size <= 4) {
|
|
val = lo >> (ofst * 8);
|
|
} else {
|
|
g_assert_cmpint(size, >, 1);
|
|
|
|
/* If the 64bit read is unaligned, we will produce undefined
|
|
* results. AHCI does not support unaligned 64bit reads. */
|
|
hi = ahci_mem_read_32(opaque, aligned + 4);
|
|
val = (hi << 32 | lo) >> (ofst * 8);
|
|
}
|
|
|
|
DPRINTF(-1, "addr=0x%" HWADDR_PRIx " val=0x%" PRIx64 ", size=%d\n",
|
|
addr, val, size);
|
|
return val;
|
|
}
|
|
|
|
|
|
static void ahci_mem_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
AHCIState *s = opaque;
|
|
|
|
DPRINTF(-1, "addr=0x%" HWADDR_PRIx " val=0x%" PRIx64 ", size=%d\n",
|
|
addr, val, size);
|
|
|
|
/* Only aligned reads are allowed on AHCI */
|
|
if (addr & 3) {
|
|
fprintf(stderr, "ahci: Mis-aligned write to addr 0x"
|
|
TARGET_FMT_plx "\n", addr);
|
|
return;
|
|
}
|
|
|
|
if (addr < AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR) {
|
|
DPRINTF(-1, "(addr 0x%08X), val 0x%08"PRIX64"\n", (unsigned) addr, val);
|
|
|
|
switch (addr) {
|
|
case HOST_CAP: /* R/WO, RO */
|
|
/* FIXME handle R/WO */
|
|
break;
|
|
case HOST_CTL: /* R/W */
|
|
if (val & HOST_CTL_RESET) {
|
|
DPRINTF(-1, "HBA Reset\n");
|
|
ahci_reset(s);
|
|
} else {
|
|
s->control_regs.ghc = (val & 0x3) | HOST_CTL_AHCI_EN;
|
|
ahci_check_irq(s);
|
|
}
|
|
break;
|
|
case HOST_IRQ_STAT: /* R/WC, RO */
|
|
s->control_regs.irqstatus &= ~val;
|
|
ahci_check_irq(s);
|
|
break;
|
|
case HOST_PORTS_IMPL: /* R/WO, RO */
|
|
/* FIXME handle R/WO */
|
|
break;
|
|
case HOST_VERSION: /* RO */
|
|
/* FIXME report write? */
|
|
break;
|
|
default:
|
|
DPRINTF(-1, "write to unknown register 0x%x\n", (unsigned)addr);
|
|
}
|
|
} else if ((addr >= AHCI_PORT_REGS_START_ADDR) &&
|
|
(addr < (AHCI_PORT_REGS_START_ADDR +
|
|
(s->ports * AHCI_PORT_ADDR_OFFSET_LEN)))) {
|
|
ahci_port_write(s, (addr - AHCI_PORT_REGS_START_ADDR) >> 7,
|
|
addr & AHCI_PORT_ADDR_OFFSET_MASK, val);
|
|
}
|
|
|
|
}
|
|
|
|
static const MemoryRegionOps ahci_mem_ops = {
|
|
.read = ahci_mem_read,
|
|
.write = ahci_mem_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static uint64_t ahci_idp_read(void *opaque, hwaddr addr,
|
|
unsigned size)
|
|
{
|
|
AHCIState *s = opaque;
|
|
|
|
if (addr == s->idp_offset) {
|
|
/* index register */
|
|
return s->idp_index;
|
|
} else if (addr == s->idp_offset + 4) {
|
|
/* data register - do memory read at location selected by index */
|
|
return ahci_mem_read(opaque, s->idp_index, size);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void ahci_idp_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
AHCIState *s = opaque;
|
|
|
|
if (addr == s->idp_offset) {
|
|
/* index register - mask off reserved bits */
|
|
s->idp_index = (uint32_t)val & ((AHCI_MEM_BAR_SIZE - 1) & ~3);
|
|
} else if (addr == s->idp_offset + 4) {
|
|
/* data register - do memory write at location selected by index */
|
|
ahci_mem_write(opaque, s->idp_index, val, size);
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps ahci_idp_ops = {
|
|
.read = ahci_idp_read,
|
|
.write = ahci_idp_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
|
|
static void ahci_reg_init(AHCIState *s)
|
|
{
|
|
int i;
|
|
|
|
s->control_regs.cap = (s->ports - 1) |
|
|
(AHCI_NUM_COMMAND_SLOTS << 8) |
|
|
(AHCI_SUPPORTED_SPEED_GEN1 << AHCI_SUPPORTED_SPEED) |
|
|
HOST_CAP_NCQ | HOST_CAP_AHCI;
|
|
|
|
s->control_regs.impl = (1 << s->ports) - 1;
|
|
|
|
s->control_regs.version = AHCI_VERSION_1_0;
|
|
|
|
for (i = 0; i < s->ports; i++) {
|
|
s->dev[i].port_state = STATE_RUN;
|
|
}
|
|
}
|
|
|
|
static void check_cmd(AHCIState *s, int port)
|
|
{
|
|
AHCIPortRegs *pr = &s->dev[port].port_regs;
|
|
uint8_t slot;
|
|
|
|
if ((pr->cmd & PORT_CMD_START) && pr->cmd_issue) {
|
|
for (slot = 0; (slot < 32) && pr->cmd_issue; slot++) {
|
|
if ((pr->cmd_issue & (1U << slot)) &&
|
|
!handle_cmd(s, port, slot)) {
|
|
pr->cmd_issue &= ~(1U << slot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ahci_check_cmd_bh(void *opaque)
|
|
{
|
|
AHCIDevice *ad = opaque;
|
|
|
|
qemu_bh_delete(ad->check_bh);
|
|
ad->check_bh = NULL;
|
|
|
|
if ((ad->busy_slot != -1) &&
|
|
!(ad->port.ifs[0].status & (BUSY_STAT|DRQ_STAT))) {
|
|
/* no longer busy */
|
|
ad->port_regs.cmd_issue &= ~(1 << ad->busy_slot);
|
|
ad->busy_slot = -1;
|
|
}
|
|
|
|
check_cmd(ad->hba, ad->port_no);
|
|
}
|
|
|
|
static void ahci_init_d2h(AHCIDevice *ad)
|
|
{
|
|
IDEState *ide_state = &ad->port.ifs[0];
|
|
AHCIPortRegs *pr = &ad->port_regs;
|
|
|
|
if (ad->init_d2h_sent) {
|
|
return;
|
|
}
|
|
|
|
if (ahci_write_fis_d2h(ad)) {
|
|
ad->init_d2h_sent = true;
|
|
/* We're emulating receiving the first Reg H2D Fis from the device;
|
|
* Update the SIG register, but otherwise proceed as normal. */
|
|
pr->sig = ((uint32_t)ide_state->hcyl << 24) |
|
|
(ide_state->lcyl << 16) |
|
|
(ide_state->sector << 8) |
|
|
(ide_state->nsector & 0xFF);
|
|
}
|
|
}
|
|
|
|
static void ahci_set_signature(AHCIDevice *ad, uint32_t sig)
|
|
{
|
|
IDEState *s = &ad->port.ifs[0];
|
|
s->hcyl = sig >> 24 & 0xFF;
|
|
s->lcyl = sig >> 16 & 0xFF;
|
|
s->sector = sig >> 8 & 0xFF;
|
|
s->nsector = sig & 0xFF;
|
|
|
|
DPRINTF(ad->port_no, "set hcyl:lcyl:sect:nsect = 0x%08x\n", sig);
|
|
}
|
|
|
|
static void ahci_reset_port(AHCIState *s, int port)
|
|
{
|
|
AHCIDevice *d = &s->dev[port];
|
|
AHCIPortRegs *pr = &d->port_regs;
|
|
IDEState *ide_state = &d->port.ifs[0];
|
|
int i;
|
|
|
|
DPRINTF(port, "reset port\n");
|
|
|
|
ide_bus_reset(&d->port);
|
|
ide_state->ncq_queues = AHCI_MAX_CMDS;
|
|
|
|
pr->scr_stat = 0;
|
|
pr->scr_err = 0;
|
|
pr->scr_act = 0;
|
|
pr->tfdata = 0x7F;
|
|
pr->sig = 0xFFFFFFFF;
|
|
d->busy_slot = -1;
|
|
d->init_d2h_sent = false;
|
|
|
|
ide_state = &s->dev[port].port.ifs[0];
|
|
if (!ide_state->blk) {
|
|
return;
|
|
}
|
|
|
|
/* reset ncq queue */
|
|
for (i = 0; i < AHCI_MAX_CMDS; i++) {
|
|
NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[i];
|
|
ncq_tfs->halt = false;
|
|
if (!ncq_tfs->used) {
|
|
continue;
|
|
}
|
|
|
|
if (ncq_tfs->aiocb) {
|
|
blk_aio_cancel(ncq_tfs->aiocb);
|
|
ncq_tfs->aiocb = NULL;
|
|
}
|
|
|
|
/* Maybe we just finished the request thanks to blk_aio_cancel() */
|
|
if (!ncq_tfs->used) {
|
|
continue;
|
|
}
|
|
|
|
qemu_sglist_destroy(&ncq_tfs->sglist);
|
|
ncq_tfs->used = 0;
|
|
}
|
|
|
|
s->dev[port].port_state = STATE_RUN;
|
|
if (ide_state->drive_kind == IDE_CD) {
|
|
ahci_set_signature(d, SATA_SIGNATURE_CDROM);\
|
|
ide_state->status = SEEK_STAT | WRERR_STAT | READY_STAT;
|
|
} else {
|
|
ahci_set_signature(d, SATA_SIGNATURE_DISK);
|
|
ide_state->status = SEEK_STAT | WRERR_STAT;
|
|
}
|
|
|
|
ide_state->error = 1;
|
|
ahci_init_d2h(d);
|
|
}
|
|
|
|
static void debug_print_fis(uint8_t *fis, int cmd_len)
|
|
{
|
|
#if DEBUG_AHCI
|
|
int i;
|
|
|
|
fprintf(stderr, "fis:");
|
|
for (i = 0; i < cmd_len; i++) {
|
|
if ((i & 0xf) == 0) {
|
|
fprintf(stderr, "\n%02x:",i);
|
|
}
|
|
fprintf(stderr, "%02x ",fis[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
}
|
|
|
|
static bool ahci_map_fis_address(AHCIDevice *ad)
|
|
{
|
|
AHCIPortRegs *pr = &ad->port_regs;
|
|
map_page(ad->hba->as, &ad->res_fis,
|
|
((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256);
|
|
if (ad->res_fis != NULL) {
|
|
pr->cmd |= PORT_CMD_FIS_ON;
|
|
return true;
|
|
}
|
|
|
|
pr->cmd &= ~PORT_CMD_FIS_ON;
|
|
return false;
|
|
}
|
|
|
|
static void ahci_unmap_fis_address(AHCIDevice *ad)
|
|
{
|
|
if (ad->res_fis == NULL) {
|
|
DPRINTF(ad->port_no, "Attempt to unmap NULL FIS address\n");
|
|
return;
|
|
}
|
|
ad->port_regs.cmd &= ~PORT_CMD_FIS_ON;
|
|
dma_memory_unmap(ad->hba->as, ad->res_fis, 256,
|
|
DMA_DIRECTION_FROM_DEVICE, 256);
|
|
ad->res_fis = NULL;
|
|
}
|
|
|
|
static bool ahci_map_clb_address(AHCIDevice *ad)
|
|
{
|
|
AHCIPortRegs *pr = &ad->port_regs;
|
|
ad->cur_cmd = NULL;
|
|
map_page(ad->hba->as, &ad->lst,
|
|
((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024);
|
|
if (ad->lst != NULL) {
|
|
pr->cmd |= PORT_CMD_LIST_ON;
|
|
return true;
|
|
}
|
|
|
|
pr->cmd &= ~PORT_CMD_LIST_ON;
|
|
return false;
|
|
}
|
|
|
|
static void ahci_unmap_clb_address(AHCIDevice *ad)
|
|
{
|
|
if (ad->lst == NULL) {
|
|
DPRINTF(ad->port_no, "Attempt to unmap NULL CLB address\n");
|
|
return;
|
|
}
|
|
ad->port_regs.cmd &= ~PORT_CMD_LIST_ON;
|
|
dma_memory_unmap(ad->hba->as, ad->lst, 1024,
|
|
DMA_DIRECTION_FROM_DEVICE, 1024);
|
|
ad->lst = NULL;
|
|
}
|
|
|
|
static void ahci_write_fis_sdb(AHCIState *s, NCQTransferState *ncq_tfs)
|
|
{
|
|
AHCIDevice *ad = ncq_tfs->drive;
|
|
AHCIPortRegs *pr = &ad->port_regs;
|
|
IDEState *ide_state;
|
|
SDBFIS *sdb_fis;
|
|
|
|
if (!ad->res_fis ||
|
|
!(pr->cmd & PORT_CMD_FIS_RX)) {
|
|
return;
|
|
}
|
|
|
|
sdb_fis = (SDBFIS *)&ad->res_fis[RES_FIS_SDBFIS];
|
|
ide_state = &ad->port.ifs[0];
|
|
|
|
sdb_fis->type = SATA_FIS_TYPE_SDB;
|
|
/* Interrupt pending & Notification bit */
|
|
sdb_fis->flags = 0x40; /* Interrupt bit, always 1 for NCQ */
|
|
sdb_fis->status = ide_state->status & 0x77;
|
|
sdb_fis->error = ide_state->error;
|
|
/* update SAct field in SDB_FIS */
|
|
sdb_fis->payload = cpu_to_le32(ad->finished);
|
|
|
|
/* Update shadow registers (except BSY 0x80 and DRQ 0x08) */
|
|
pr->tfdata = (ad->port.ifs[0].error << 8) |
|
|
(ad->port.ifs[0].status & 0x77) |
|
|
(pr->tfdata & 0x88);
|
|
pr->scr_act &= ~ad->finished;
|
|
ad->finished = 0;
|
|
|
|
/* Trigger IRQ if interrupt bit is set (which currently, it always is) */
|
|
if (sdb_fis->flags & 0x40) {
|
|
ahci_trigger_irq(s, ad, PORT_IRQ_SDB_FIS);
|
|
}
|
|
}
|
|
|
|
static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len)
|
|
{
|
|
AHCIPortRegs *pr = &ad->port_regs;
|
|
uint8_t *pio_fis;
|
|
IDEState *s = &ad->port.ifs[0];
|
|
|
|
if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) {
|
|
return;
|
|
}
|
|
|
|
pio_fis = &ad->res_fis[RES_FIS_PSFIS];
|
|
|
|
pio_fis[0] = SATA_FIS_TYPE_PIO_SETUP;
|
|
pio_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0);
|
|
pio_fis[2] = s->status;
|
|
pio_fis[3] = s->error;
|
|
|
|
pio_fis[4] = s->sector;
|
|
pio_fis[5] = s->lcyl;
|
|
pio_fis[6] = s->hcyl;
|
|
pio_fis[7] = s->select;
|
|
pio_fis[8] = s->hob_sector;
|
|
pio_fis[9] = s->hob_lcyl;
|
|
pio_fis[10] = s->hob_hcyl;
|
|
pio_fis[11] = 0;
|
|
pio_fis[12] = s->nsector & 0xFF;
|
|
pio_fis[13] = (s->nsector >> 8) & 0xFF;
|
|
pio_fis[14] = 0;
|
|
pio_fis[15] = s->status;
|
|
pio_fis[16] = len & 255;
|
|
pio_fis[17] = len >> 8;
|
|
pio_fis[18] = 0;
|
|
pio_fis[19] = 0;
|
|
|
|
/* Update shadow registers: */
|
|
pr->tfdata = (ad->port.ifs[0].error << 8) |
|
|
ad->port.ifs[0].status;
|
|
|
|
if (pio_fis[2] & ERR_STAT) {
|
|
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
|
|
}
|
|
|
|
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_PIOS_FIS);
|
|
}
|
|
|
|
static bool ahci_write_fis_d2h(AHCIDevice *ad)
|
|
{
|
|
AHCIPortRegs *pr = &ad->port_regs;
|
|
uint8_t *d2h_fis;
|
|
int i;
|
|
IDEState *s = &ad->port.ifs[0];
|
|
|
|
if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) {
|
|
return false;
|
|
}
|
|
|
|
d2h_fis = &ad->res_fis[RES_FIS_RFIS];
|
|
|
|
d2h_fis[0] = SATA_FIS_TYPE_REGISTER_D2H;
|
|
d2h_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0);
|
|
d2h_fis[2] = s->status;
|
|
d2h_fis[3] = s->error;
|
|
|
|
d2h_fis[4] = s->sector;
|
|
d2h_fis[5] = s->lcyl;
|
|
d2h_fis[6] = s->hcyl;
|
|
d2h_fis[7] = s->select;
|
|
d2h_fis[8] = s->hob_sector;
|
|
d2h_fis[9] = s->hob_lcyl;
|
|
d2h_fis[10] = s->hob_hcyl;
|
|
d2h_fis[11] = 0;
|
|
d2h_fis[12] = s->nsector & 0xFF;
|
|
d2h_fis[13] = (s->nsector >> 8) & 0xFF;
|
|
for (i = 14; i < 20; i++) {
|
|
d2h_fis[i] = 0;
|
|
}
|
|
|
|
/* Update shadow registers: */
|
|
pr->tfdata = (ad->port.ifs[0].error << 8) |
|
|
ad->port.ifs[0].status;
|
|
|
|
if (d2h_fis[2] & ERR_STAT) {
|
|
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
|
|
}
|
|
|
|
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_D2H_REG_FIS);
|
|
return true;
|
|
}
|
|
|
|
static int prdt_tbl_entry_size(const AHCI_SG *tbl)
|
|
{
|
|
/* flags_size is zero-based */
|
|
return (le32_to_cpu(tbl->flags_size) & AHCI_PRDT_SIZE_MASK) + 1;
|
|
}
|
|
|
|
/**
|
|
* Fetch entries in a guest-provided PRDT and convert it into a QEMU SGlist.
|
|
* @ad: The AHCIDevice for whom we are building the SGList.
|
|
* @sglist: The SGList target to add PRD entries to.
|
|
* @cmd: The AHCI Command Header that describes where the PRDT is.
|
|
* @limit: The remaining size of the S/ATA transaction, in bytes.
|
|
* @offset: The number of bytes already transferred, in bytes.
|
|
*
|
|
* The AHCI PRDT can describe up to 256GiB. S/ATA only support transactions of
|
|
* up to 32MiB as of ATA8-ACS3 rev 1b, assuming a 512 byte sector size. We stop
|
|
* building the sglist from the PRDT as soon as we hit @limit bytes,
|
|
* which is <= INT32_MAX/2GiB.
|
|
*/
|
|
static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist,
|
|
AHCICmdHdr *cmd, int64_t limit, uint64_t offset)
|
|
{
|
|
uint16_t opts = le16_to_cpu(cmd->opts);
|
|
uint16_t prdtl = le16_to_cpu(cmd->prdtl);
|
|
uint64_t cfis_addr = le64_to_cpu(cmd->tbl_addr);
|
|
uint64_t prdt_addr = cfis_addr + 0x80;
|
|
dma_addr_t prdt_len = (prdtl * sizeof(AHCI_SG));
|
|
dma_addr_t real_prdt_len = prdt_len;
|
|
uint8_t *prdt;
|
|
int i;
|
|
int r = 0;
|
|
uint64_t sum = 0;
|
|
int off_idx = -1;
|
|
int64_t off_pos = -1;
|
|
int tbl_entry_size;
|
|
IDEBus *bus = &ad->port;
|
|
BusState *qbus = BUS(bus);
|
|
|
|
if (!prdtl) {
|
|
DPRINTF(ad->port_no, "no sg list given by guest: 0x%08x\n", opts);
|
|
return -1;
|
|
}
|
|
|
|
/* map PRDT */
|
|
if (!(prdt = dma_memory_map(ad->hba->as, prdt_addr, &prdt_len,
|
|
DMA_DIRECTION_TO_DEVICE))){
|
|
DPRINTF(ad->port_no, "map failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (prdt_len < real_prdt_len) {
|
|
DPRINTF(ad->port_no, "mapped less than expected\n");
|
|
r = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Get entries in the PRDT, init a qemu sglist accordingly */
|
|
if (prdtl > 0) {
|
|
AHCI_SG *tbl = (AHCI_SG *)prdt;
|
|
sum = 0;
|
|
for (i = 0; i < prdtl; i++) {
|
|
tbl_entry_size = prdt_tbl_entry_size(&tbl[i]);
|
|
if (offset < (sum + tbl_entry_size)) {
|
|
off_idx = i;
|
|
off_pos = offset - sum;
|
|
break;
|
|
}
|
|
sum += tbl_entry_size;
|
|
}
|
|
if ((off_idx == -1) || (off_pos < 0) || (off_pos > tbl_entry_size)) {
|
|
DPRINTF(ad->port_no, "%s: Incorrect offset! "
|
|
"off_idx: %d, off_pos: %"PRId64"\n",
|
|
__func__, off_idx, off_pos);
|
|
r = -1;
|
|
goto out;
|
|
}
|
|
|
|
qemu_sglist_init(sglist, qbus->parent, (prdtl - off_idx),
|
|
ad->hba->as);
|
|
qemu_sglist_add(sglist, le64_to_cpu(tbl[off_idx].addr) + off_pos,
|
|
MIN(prdt_tbl_entry_size(&tbl[off_idx]) - off_pos,
|
|
limit));
|
|
|
|
for (i = off_idx + 1; i < prdtl && sglist->size < limit; i++) {
|
|
qemu_sglist_add(sglist, le64_to_cpu(tbl[i].addr),
|
|
MIN(prdt_tbl_entry_size(&tbl[i]),
|
|
limit - sglist->size));
|
|
}
|
|
}
|
|
|
|
out:
|
|
dma_memory_unmap(ad->hba->as, prdt, prdt_len,
|
|
DMA_DIRECTION_TO_DEVICE, prdt_len);
|
|
return r;
|
|
}
|
|
|
|
static void ncq_err(NCQTransferState *ncq_tfs)
|
|
{
|
|
IDEState *ide_state = &ncq_tfs->drive->port.ifs[0];
|
|
|
|
ide_state->error = ABRT_ERR;
|
|
ide_state->status = READY_STAT | ERR_STAT;
|
|
ncq_tfs->drive->port_regs.scr_err |= (1 << ncq_tfs->tag);
|
|
ncq_tfs->used = 0;
|
|
}
|
|
|
|
static void ncq_finish(NCQTransferState *ncq_tfs)
|
|
{
|
|
/* If we didn't error out, set our finished bit. Errored commands
|
|
* do not get a bit set for the SDB FIS ACT register, nor do they
|
|
* clear the outstanding bit in scr_act (PxSACT). */
|
|
if (!(ncq_tfs->drive->port_regs.scr_err & (1 << ncq_tfs->tag))) {
|
|
ncq_tfs->drive->finished |= (1 << ncq_tfs->tag);
|
|
}
|
|
|
|
ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs);
|
|
|
|
DPRINTF(ncq_tfs->drive->port_no, "NCQ transfer tag %d finished\n",
|
|
ncq_tfs->tag);
|
|
|
|
block_acct_done(blk_get_stats(ncq_tfs->drive->port.ifs[0].blk),
|
|
&ncq_tfs->acct);
|
|
qemu_sglist_destroy(&ncq_tfs->sglist);
|
|
ncq_tfs->used = 0;
|
|
}
|
|
|
|
static void ncq_cb(void *opaque, int ret)
|
|
{
|
|
NCQTransferState *ncq_tfs = (NCQTransferState *)opaque;
|
|
IDEState *ide_state = &ncq_tfs->drive->port.ifs[0];
|
|
|
|
if (ret == -ECANCELED) {
|
|
return;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
bool is_read = ncq_tfs->cmd == READ_FPDMA_QUEUED;
|
|
BlockErrorAction action = blk_get_error_action(ide_state->blk,
|
|
is_read, -ret);
|
|
if (action == BLOCK_ERROR_ACTION_STOP) {
|
|
ncq_tfs->halt = true;
|
|
ide_state->bus->error_status = IDE_RETRY_HBA;
|
|
} else if (action == BLOCK_ERROR_ACTION_REPORT) {
|
|
ncq_err(ncq_tfs);
|
|
}
|
|
blk_error_action(ide_state->blk, action, is_read, -ret);
|
|
} else {
|
|
ide_state->status = READY_STAT | SEEK_STAT;
|
|
}
|
|
|
|
if (!ncq_tfs->halt) {
|
|
ncq_finish(ncq_tfs);
|
|
}
|
|
}
|
|
|
|
static int is_ncq(uint8_t ata_cmd)
|
|
{
|
|
/* Based on SATA 3.2 section 13.6.3.2 */
|
|
switch (ata_cmd) {
|
|
case READ_FPDMA_QUEUED:
|
|
case WRITE_FPDMA_QUEUED:
|
|
case NCQ_NON_DATA:
|
|
case RECEIVE_FPDMA_QUEUED:
|
|
case SEND_FPDMA_QUEUED:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void execute_ncq_command(NCQTransferState *ncq_tfs)
|
|
{
|
|
AHCIDevice *ad = ncq_tfs->drive;
|
|
IDEState *ide_state = &ad->port.ifs[0];
|
|
int port = ad->port_no;
|
|
|
|
g_assert(is_ncq(ncq_tfs->cmd));
|
|
ncq_tfs->halt = false;
|
|
|
|
switch (ncq_tfs->cmd) {
|
|
case READ_FPDMA_QUEUED:
|
|
DPRINTF(port, "NCQ reading %d sectors from LBA %"PRId64", tag %d\n",
|
|
ncq_tfs->sector_count, ncq_tfs->lba, ncq_tfs->tag);
|
|
|
|
DPRINTF(port, "tag %d aio read %"PRId64"\n",
|
|
ncq_tfs->tag, ncq_tfs->lba);
|
|
|
|
dma_acct_start(ide_state->blk, &ncq_tfs->acct,
|
|
&ncq_tfs->sglist, BLOCK_ACCT_READ);
|
|
ncq_tfs->aiocb = dma_blk_read(ide_state->blk, &ncq_tfs->sglist,
|
|
ncq_tfs->lba, ncq_cb, ncq_tfs);
|
|
break;
|
|
case WRITE_FPDMA_QUEUED:
|
|
DPRINTF(port, "NCQ writing %d sectors to LBA %"PRId64", tag %d\n",
|
|
ncq_tfs->sector_count, ncq_tfs->lba, ncq_tfs->tag);
|
|
|
|
DPRINTF(port, "tag %d aio write %"PRId64"\n",
|
|
ncq_tfs->tag, ncq_tfs->lba);
|
|
|
|
dma_acct_start(ide_state->blk, &ncq_tfs->acct,
|
|
&ncq_tfs->sglist, BLOCK_ACCT_WRITE);
|
|
ncq_tfs->aiocb = dma_blk_write(ide_state->blk, &ncq_tfs->sglist,
|
|
ncq_tfs->lba, ncq_cb, ncq_tfs);
|
|
break;
|
|
default:
|
|
DPRINTF(port, "error: unsupported NCQ command (0x%02x) received\n",
|
|
ncq_tfs->cmd);
|
|
qemu_sglist_destroy(&ncq_tfs->sglist);
|
|
ncq_err(ncq_tfs);
|
|
}
|
|
}
|
|
|
|
|
|
static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis,
|
|
uint8_t slot)
|
|
{
|
|
AHCIDevice *ad = &s->dev[port];
|
|
IDEState *ide_state = &ad->port.ifs[0];
|
|
NCQFrame *ncq_fis = (NCQFrame*)cmd_fis;
|
|
uint8_t tag = ncq_fis->tag >> 3;
|
|
NCQTransferState *ncq_tfs = &ad->ncq_tfs[tag];
|
|
size_t size;
|
|
|
|
g_assert(is_ncq(ncq_fis->command));
|
|
if (ncq_tfs->used) {
|
|
/* error - already in use */
|
|
fprintf(stderr, "%s: tag %d already used\n", __FUNCTION__, tag);
|
|
return;
|
|
}
|
|
|
|
ncq_tfs->used = 1;
|
|
ncq_tfs->drive = ad;
|
|
ncq_tfs->slot = slot;
|
|
ncq_tfs->cmdh = &((AHCICmdHdr *)ad->lst)[slot];
|
|
ncq_tfs->cmd = ncq_fis->command;
|
|
ncq_tfs->lba = ((uint64_t)ncq_fis->lba5 << 40) |
|
|
((uint64_t)ncq_fis->lba4 << 32) |
|
|
((uint64_t)ncq_fis->lba3 << 24) |
|
|
((uint64_t)ncq_fis->lba2 << 16) |
|
|
((uint64_t)ncq_fis->lba1 << 8) |
|
|
(uint64_t)ncq_fis->lba0;
|
|
ncq_tfs->tag = tag;
|
|
|
|
/* Sanity-check the NCQ packet */
|
|
if (tag != slot) {
|
|
DPRINTF(port, "Warn: NCQ slot (%d) did not match the given tag (%d)\n",
|
|
slot, tag);
|
|
}
|
|
|
|
if (ncq_fis->aux0 || ncq_fis->aux1 || ncq_fis->aux2 || ncq_fis->aux3) {
|
|
DPRINTF(port, "Warn: Attempt to use NCQ auxiliary fields.\n");
|
|
}
|
|
if (ncq_fis->prio || ncq_fis->icc) {
|
|
DPRINTF(port, "Warn: Unsupported attempt to use PRIO/ICC fields\n");
|
|
}
|
|
if (ncq_fis->fua & NCQ_FIS_FUA_MASK) {
|
|
DPRINTF(port, "Warn: Unsupported attempt to use Force Unit Access\n");
|
|
}
|
|
if (ncq_fis->tag & NCQ_FIS_RARC_MASK) {
|
|
DPRINTF(port, "Warn: Unsupported attempt to use Rebuild Assist\n");
|
|
}
|
|
|
|
ncq_tfs->sector_count = ((ncq_fis->sector_count_high << 8) |
|
|
ncq_fis->sector_count_low);
|
|
if (!ncq_tfs->sector_count) {
|
|
ncq_tfs->sector_count = 0x10000;
|
|
}
|
|
size = ncq_tfs->sector_count * 512;
|
|
ahci_populate_sglist(ad, &ncq_tfs->sglist, ncq_tfs->cmdh, size, 0);
|
|
|
|
if (ncq_tfs->sglist.size < size) {
|
|
error_report("ahci: PRDT length for NCQ command (0x%zx) "
|
|
"is smaller than the requested size (0x%zx)",
|
|
ncq_tfs->sglist.size, size);
|
|
qemu_sglist_destroy(&ncq_tfs->sglist);
|
|
ncq_err(ncq_tfs);
|
|
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_OVERFLOW);
|
|
return;
|
|
} else if (ncq_tfs->sglist.size != size) {
|
|
DPRINTF(port, "Warn: PRDTL (0x%zx)"
|
|
" does not match requested size (0x%zx)",
|
|
ncq_tfs->sglist.size, size);
|
|
}
|
|
|
|
DPRINTF(port, "NCQ transfer LBA from %"PRId64" to %"PRId64", "
|
|
"drive max %"PRId64"\n",
|
|
ncq_tfs->lba, ncq_tfs->lba + ncq_tfs->sector_count - 1,
|
|
ide_state->nb_sectors - 1);
|
|
|
|
execute_ncq_command(ncq_tfs);
|
|
}
|
|
|
|
static AHCICmdHdr *get_cmd_header(AHCIState *s, uint8_t port, uint8_t slot)
|
|
{
|
|
if (port >= s->ports || slot >= AHCI_MAX_CMDS) {
|
|
return NULL;
|
|
}
|
|
|
|
return s->dev[port].lst ? &((AHCICmdHdr *)s->dev[port].lst)[slot] : NULL;
|
|
}
|
|
|
|
static void handle_reg_h2d_fis(AHCIState *s, int port,
|
|
uint8_t slot, uint8_t *cmd_fis)
|
|
{
|
|
IDEState *ide_state = &s->dev[port].port.ifs[0];
|
|
AHCICmdHdr *cmd = get_cmd_header(s, port, slot);
|
|
uint16_t opts = le16_to_cpu(cmd->opts);
|
|
|
|
if (cmd_fis[1] & 0x0F) {
|
|
DPRINTF(port, "Port Multiplier not supported."
|
|
" cmd_fis[0]=%02x cmd_fis[1]=%02x cmd_fis[2]=%02x\n",
|
|
cmd_fis[0], cmd_fis[1], cmd_fis[2]);
|
|
return;
|
|
}
|
|
|
|
if (cmd_fis[1] & 0x70) {
|
|
DPRINTF(port, "Reserved flags set in H2D Register FIS."
|
|
" cmd_fis[0]=%02x cmd_fis[1]=%02x cmd_fis[2]=%02x\n",
|
|
cmd_fis[0], cmd_fis[1], cmd_fis[2]);
|
|
return;
|
|
}
|
|
|
|
if (!(cmd_fis[1] & SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER)) {
|
|
switch (s->dev[port].port_state) {
|
|
case STATE_RUN:
|
|
if (cmd_fis[15] & ATA_SRST) {
|
|
s->dev[port].port_state = STATE_RESET;
|
|
}
|
|
break;
|
|
case STATE_RESET:
|
|
if (!(cmd_fis[15] & ATA_SRST)) {
|
|
ahci_reset_port(s, port);
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Check for NCQ command */
|
|
if (is_ncq(cmd_fis[2])) {
|
|
process_ncq_command(s, port, cmd_fis, slot);
|
|
return;
|
|
}
|
|
|
|
/* Decompose the FIS:
|
|
* AHCI does not interpret FIS packets, it only forwards them.
|
|
* SATA 1.0 describes how to decode LBA28 and CHS FIS packets.
|
|
* Later specifications, e.g, SATA 3.2, describe LBA48 FIS packets.
|
|
*
|
|
* ATA4 describes sector number for LBA28/CHS commands.
|
|
* ATA6 describes sector number for LBA48 commands.
|
|
* ATA8 deprecates CHS fully, describing only LBA28/48.
|
|
*
|
|
* We dutifully convert the FIS into IDE registers, and allow the
|
|
* core layer to interpret them as needed. */
|
|
ide_state->feature = cmd_fis[3];
|
|
ide_state->sector = cmd_fis[4]; /* LBA 7:0 */
|
|
ide_state->lcyl = cmd_fis[5]; /* LBA 15:8 */
|
|
ide_state->hcyl = cmd_fis[6]; /* LBA 23:16 */
|
|
ide_state->select = cmd_fis[7]; /* LBA 27:24 (LBA28) */
|
|
ide_state->hob_sector = cmd_fis[8]; /* LBA 31:24 */
|
|
ide_state->hob_lcyl = cmd_fis[9]; /* LBA 39:32 */
|
|
ide_state->hob_hcyl = cmd_fis[10]; /* LBA 47:40 */
|
|
ide_state->hob_feature = cmd_fis[11];
|
|
ide_state->nsector = (int64_t)((cmd_fis[13] << 8) | cmd_fis[12]);
|
|
/* 14, 16, 17, 18, 19: Reserved (SATA 1.0) */
|
|
/* 15: Only valid when UPDATE_COMMAND not set. */
|
|
|
|
/* Copy the ACMD field (ATAPI packet, if any) from the AHCI command
|
|
* table to ide_state->io_buffer */
|
|
if (opts & AHCI_CMD_ATAPI) {
|
|
memcpy(ide_state->io_buffer, &cmd_fis[AHCI_COMMAND_TABLE_ACMD], 0x10);
|
|
debug_print_fis(ide_state->io_buffer, 0x10);
|
|
s->dev[port].done_atapi_packet = false;
|
|
/* XXX send PIO setup FIS */
|
|
}
|
|
|
|
ide_state->error = 0;
|
|
|
|
/* Reset transferred byte counter */
|
|
cmd->status = 0;
|
|
|
|
/* We're ready to process the command in FIS byte 2. */
|
|
ide_exec_cmd(&s->dev[port].port, cmd_fis[2]);
|
|
}
|
|
|
|
static int handle_cmd(AHCIState *s, int port, uint8_t slot)
|
|
{
|
|
IDEState *ide_state;
|
|
uint64_t tbl_addr;
|
|
AHCICmdHdr *cmd;
|
|
uint8_t *cmd_fis;
|
|
dma_addr_t cmd_len;
|
|
|
|
if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) {
|
|
/* Engine currently busy, try again later */
|
|
DPRINTF(port, "engine busy\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!s->dev[port].lst) {
|
|
DPRINTF(port, "error: lst not given but cmd handled");
|
|
return -1;
|
|
}
|
|
cmd = get_cmd_header(s, port, slot);
|
|
/* remember current slot handle for later */
|
|
s->dev[port].cur_cmd = cmd;
|
|
|
|
/* The device we are working for */
|
|
ide_state = &s->dev[port].port.ifs[0];
|
|
if (!ide_state->blk) {
|
|
DPRINTF(port, "error: guest accessed unused port");
|
|
return -1;
|
|
}
|
|
|
|
tbl_addr = le64_to_cpu(cmd->tbl_addr);
|
|
cmd_len = 0x80;
|
|
cmd_fis = dma_memory_map(s->as, tbl_addr, &cmd_len,
|
|
DMA_DIRECTION_FROM_DEVICE);
|
|
if (!cmd_fis) {
|
|
DPRINTF(port, "error: guest passed us an invalid cmd fis\n");
|
|
return -1;
|
|
} else if (cmd_len != 0x80) {
|
|
ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_HBUS_ERR);
|
|
DPRINTF(port, "error: dma_memory_map failed: "
|
|
"(len(%02"PRIx64") != 0x80)\n",
|
|
cmd_len);
|
|
goto out;
|
|
}
|
|
debug_print_fis(cmd_fis, 0x80);
|
|
|
|
switch (cmd_fis[0]) {
|
|
case SATA_FIS_TYPE_REGISTER_H2D:
|
|
handle_reg_h2d_fis(s, port, slot, cmd_fis);
|
|
break;
|
|
default:
|
|
DPRINTF(port, "unknown command cmd_fis[0]=%02x cmd_fis[1]=%02x "
|
|
"cmd_fis[2]=%02x\n", cmd_fis[0], cmd_fis[1],
|
|
cmd_fis[2]);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
dma_memory_unmap(s->as, cmd_fis, cmd_len, DMA_DIRECTION_FROM_DEVICE,
|
|
cmd_len);
|
|
|
|
if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) {
|
|
/* async command, complete later */
|
|
s->dev[port].busy_slot = slot;
|
|
return -1;
|
|
}
|
|
|
|
/* done handling the command */
|
|
return 0;
|
|
}
|
|
|
|
/* DMA dev <-> ram */
|
|
static void ahci_start_transfer(IDEDMA *dma)
|
|
{
|
|
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
|
IDEState *s = &ad->port.ifs[0];
|
|
uint32_t size = (uint32_t)(s->data_end - s->data_ptr);
|
|
/* write == ram -> device */
|
|
uint16_t opts = le16_to_cpu(ad->cur_cmd->opts);
|
|
int is_write = opts & AHCI_CMD_WRITE;
|
|
int is_atapi = opts & AHCI_CMD_ATAPI;
|
|
int has_sglist = 0;
|
|
|
|
if (is_atapi && !ad->done_atapi_packet) {
|
|
/* already prepopulated iobuffer */
|
|
ad->done_atapi_packet = true;
|
|
size = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (ahci_dma_prepare_buf(dma, size)) {
|
|
has_sglist = 1;
|
|
}
|
|
|
|
DPRINTF(ad->port_no, "%sing %d bytes on %s w/%s sglist\n",
|
|
is_write ? "writ" : "read", size, is_atapi ? "atapi" : "ata",
|
|
has_sglist ? "" : "o");
|
|
|
|
if (has_sglist && size) {
|
|
if (is_write) {
|
|
dma_buf_write(s->data_ptr, size, &s->sg);
|
|
} else {
|
|
dma_buf_read(s->data_ptr, size, &s->sg);
|
|
}
|
|
}
|
|
|
|
out:
|
|
/* declare that we processed everything */
|
|
s->data_ptr = s->data_end;
|
|
|
|
/* Update number of transferred bytes, destroy sglist */
|
|
dma_buf_commit(s, size);
|
|
|
|
s->end_transfer_func(s);
|
|
|
|
if (!(s->status & DRQ_STAT)) {
|
|
/* done with PIO send/receive */
|
|
ahci_write_fis_pio(ad, le32_to_cpu(ad->cur_cmd->status));
|
|
}
|
|
}
|
|
|
|
static void ahci_start_dma(IDEDMA *dma, IDEState *s,
|
|
BlockCompletionFunc *dma_cb)
|
|
{
|
|
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
|
DPRINTF(ad->port_no, "\n");
|
|
s->io_buffer_offset = 0;
|
|
dma_cb(s, 0);
|
|
}
|
|
|
|
static void ahci_restart_dma(IDEDMA *dma)
|
|
{
|
|
/* Nothing to do, ahci_start_dma already resets s->io_buffer_offset. */
|
|
}
|
|
|
|
/**
|
|
* IDE/PIO restarts are handled by the core layer, but NCQ commands
|
|
* need an extra kick from the AHCI HBA.
|
|
*/
|
|
static void ahci_restart(IDEDMA *dma)
|
|
{
|
|
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
|
int i;
|
|
|
|
for (i = 0; i < AHCI_MAX_CMDS; i++) {
|
|
NCQTransferState *ncq_tfs = &ad->ncq_tfs[i];
|
|
if (ncq_tfs->halt) {
|
|
execute_ncq_command(ncq_tfs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called in DMA and PIO R/W chains to read the PRDT.
|
|
* Not shared with NCQ pathways.
|
|
*/
|
|
static int32_t ahci_dma_prepare_buf(IDEDMA *dma, int32_t limit)
|
|
{
|
|
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
|
IDEState *s = &ad->port.ifs[0];
|
|
|
|
if (ahci_populate_sglist(ad, &s->sg, ad->cur_cmd,
|
|
limit, s->io_buffer_offset) == -1) {
|
|
DPRINTF(ad->port_no, "ahci_dma_prepare_buf failed.\n");
|
|
return -1;
|
|
}
|
|
s->io_buffer_size = s->sg.size;
|
|
|
|
DPRINTF(ad->port_no, "len=%#x\n", s->io_buffer_size);
|
|
return s->io_buffer_size;
|
|
}
|
|
|
|
/**
|
|
* Updates the command header with a bytes-read value.
|
|
* Called via dma_buf_commit, for both DMA and PIO paths.
|
|
* sglist destruction is handled within dma_buf_commit.
|
|
*/
|
|
static void ahci_commit_buf(IDEDMA *dma, uint32_t tx_bytes)
|
|
{
|
|
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
|
|
|
tx_bytes += le32_to_cpu(ad->cur_cmd->status);
|
|
ad->cur_cmd->status = cpu_to_le32(tx_bytes);
|
|
}
|
|
|
|
static int ahci_dma_rw_buf(IDEDMA *dma, int is_write)
|
|
{
|
|
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
|
IDEState *s = &ad->port.ifs[0];
|
|
uint8_t *p = s->io_buffer + s->io_buffer_index;
|
|
int l = s->io_buffer_size - s->io_buffer_index;
|
|
|
|
if (ahci_populate_sglist(ad, &s->sg, ad->cur_cmd, l, s->io_buffer_offset)) {
|
|
return 0;
|
|
}
|
|
|
|
if (is_write) {
|
|
dma_buf_read(p, l, &s->sg);
|
|
} else {
|
|
dma_buf_write(p, l, &s->sg);
|
|
}
|
|
|
|
/* free sglist, update byte count */
|
|
dma_buf_commit(s, l);
|
|
|
|
s->io_buffer_index += l;
|
|
|
|
DPRINTF(ad->port_no, "len=%#x\n", l);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void ahci_cmd_done(IDEDMA *dma)
|
|
{
|
|
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
|
|
|
DPRINTF(ad->port_no, "cmd done\n");
|
|
|
|
/* update d2h status */
|
|
ahci_write_fis_d2h(ad);
|
|
|
|
if (!ad->check_bh) {
|
|
/* maybe we still have something to process, check later */
|
|
ad->check_bh = qemu_bh_new(ahci_check_cmd_bh, ad);
|
|
qemu_bh_schedule(ad->check_bh);
|
|
}
|
|
}
|
|
|
|
static void ahci_irq_set(void *opaque, int n, int level)
|
|
{
|
|
}
|
|
|
|
static const IDEDMAOps ahci_dma_ops = {
|
|
.start_dma = ahci_start_dma,
|
|
.restart = ahci_restart,
|
|
.restart_dma = ahci_restart_dma,
|
|
.start_transfer = ahci_start_transfer,
|
|
.prepare_buf = ahci_dma_prepare_buf,
|
|
.commit_buf = ahci_commit_buf,
|
|
.rw_buf = ahci_dma_rw_buf,
|
|
.cmd_done = ahci_cmd_done,
|
|
};
|
|
|
|
void ahci_init(AHCIState *s, DeviceState *qdev)
|
|
{
|
|
s->container = qdev;
|
|
/* XXX BAR size should be 1k, but that breaks, so bump it to 4k for now */
|
|
memory_region_init_io(&s->mem, OBJECT(qdev), &ahci_mem_ops, s,
|
|
"ahci", AHCI_MEM_BAR_SIZE);
|
|
memory_region_init_io(&s->idp, OBJECT(qdev), &ahci_idp_ops, s,
|
|
"ahci-idp", 32);
|
|
}
|
|
|
|
void ahci_realize(AHCIState *s, DeviceState *qdev, AddressSpace *as, int ports)
|
|
{
|
|
qemu_irq *irqs;
|
|
int i;
|
|
|
|
s->as = as;
|
|
s->ports = ports;
|
|
s->dev = g_new0(AHCIDevice, ports);
|
|
ahci_reg_init(s);
|
|
irqs = qemu_allocate_irqs(ahci_irq_set, s, s->ports);
|
|
for (i = 0; i < s->ports; i++) {
|
|
AHCIDevice *ad = &s->dev[i];
|
|
|
|
ide_bus_new(&ad->port, sizeof(ad->port), qdev, i, 1);
|
|
ide_init2(&ad->port, irqs[i]);
|
|
|
|
ad->hba = s;
|
|
ad->port_no = i;
|
|
ad->port.dma = &ad->dma;
|
|
ad->port.dma->ops = &ahci_dma_ops;
|
|
ide_register_restart_cb(&ad->port);
|
|
}
|
|
}
|
|
|
|
void ahci_uninit(AHCIState *s)
|
|
{
|
|
g_free(s->dev);
|
|
}
|
|
|
|
void ahci_reset(AHCIState *s)
|
|
{
|
|
AHCIPortRegs *pr;
|
|
int i;
|
|
|
|
s->control_regs.irqstatus = 0;
|
|
/* AHCI Enable (AE)
|
|
* The implementation of this bit is dependent upon the value of the
|
|
* CAP.SAM bit. If CAP.SAM is '0', then GHC.AE shall be read-write and
|
|
* shall have a reset value of '0'. If CAP.SAM is '1', then AE shall be
|
|
* read-only and shall have a reset value of '1'.
|
|
*
|
|
* We set HOST_CAP_AHCI so we must enable AHCI at reset.
|
|
*/
|
|
s->control_regs.ghc = HOST_CTL_AHCI_EN;
|
|
|
|
for (i = 0; i < s->ports; i++) {
|
|
pr = &s->dev[i].port_regs;
|
|
pr->irq_stat = 0;
|
|
pr->irq_mask = 0;
|
|
pr->scr_ctl = 0;
|
|
pr->cmd = PORT_CMD_SPIN_UP | PORT_CMD_POWER_ON;
|
|
ahci_reset_port(s, i);
|
|
}
|
|
}
|
|
|
|
static const VMStateDescription vmstate_ncq_tfs = {
|
|
.name = "ncq state",
|
|
.version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32(sector_count, NCQTransferState),
|
|
VMSTATE_UINT64(lba, NCQTransferState),
|
|
VMSTATE_UINT8(tag, NCQTransferState),
|
|
VMSTATE_UINT8(cmd, NCQTransferState),
|
|
VMSTATE_UINT8(slot, NCQTransferState),
|
|
VMSTATE_BOOL(used, NCQTransferState),
|
|
VMSTATE_BOOL(halt, NCQTransferState),
|
|
VMSTATE_END_OF_LIST()
|
|
},
|
|
};
|
|
|
|
static const VMStateDescription vmstate_ahci_device = {
|
|
.name = "ahci port",
|
|
.version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_IDE_BUS(port, AHCIDevice),
|
|
VMSTATE_IDE_DRIVE(port.ifs[0], AHCIDevice),
|
|
VMSTATE_UINT32(port_state, AHCIDevice),
|
|
VMSTATE_UINT32(finished, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.lst_addr, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.lst_addr_hi, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.fis_addr, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.fis_addr_hi, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.irq_stat, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.irq_mask, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.cmd, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.tfdata, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.sig, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.scr_stat, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.scr_ctl, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.scr_err, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.scr_act, AHCIDevice),
|
|
VMSTATE_UINT32(port_regs.cmd_issue, AHCIDevice),
|
|
VMSTATE_BOOL(done_atapi_packet, AHCIDevice),
|
|
VMSTATE_INT32(busy_slot, AHCIDevice),
|
|
VMSTATE_BOOL(init_d2h_sent, AHCIDevice),
|
|
VMSTATE_STRUCT_ARRAY(ncq_tfs, AHCIDevice, AHCI_MAX_CMDS,
|
|
1, vmstate_ncq_tfs, NCQTransferState),
|
|
VMSTATE_END_OF_LIST()
|
|
},
|
|
};
|
|
|
|
static int ahci_state_post_load(void *opaque, int version_id)
|
|
{
|
|
int i, j;
|
|
struct AHCIDevice *ad;
|
|
NCQTransferState *ncq_tfs;
|
|
AHCIPortRegs *pr;
|
|
AHCIState *s = opaque;
|
|
|
|
for (i = 0; i < s->ports; i++) {
|
|
ad = &s->dev[i];
|
|
pr = &ad->port_regs;
|
|
|
|
if (!(pr->cmd & PORT_CMD_START) && (pr->cmd & PORT_CMD_LIST_ON)) {
|
|
error_report("AHCI: DMA engine should be off, but status bit "
|
|
"indicates it is still running.");
|
|
return -1;
|
|
}
|
|
if (!(pr->cmd & PORT_CMD_FIS_RX) && (pr->cmd & PORT_CMD_FIS_ON)) {
|
|
error_report("AHCI: FIS RX engine should be off, but status bit "
|
|
"indicates it is still running.");
|
|
return -1;
|
|
}
|
|
|
|
/* After a migrate, the DMA/FIS engines are "off" and
|
|
* need to be conditionally restarted */
|
|
pr->cmd &= ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON);
|
|
if (ahci_cond_start_engines(ad) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
for (j = 0; j < AHCI_MAX_CMDS; j++) {
|
|
ncq_tfs = &ad->ncq_tfs[j];
|
|
ncq_tfs->drive = ad;
|
|
|
|
if (ncq_tfs->used != ncq_tfs->halt) {
|
|
return -1;
|
|
}
|
|
if (!ncq_tfs->halt) {
|
|
continue;
|
|
}
|
|
if (!is_ncq(ncq_tfs->cmd)) {
|
|
return -1;
|
|
}
|
|
if (ncq_tfs->slot != ncq_tfs->tag) {
|
|
return -1;
|
|
}
|
|
/* If ncq_tfs->halt is justly set, the engine should be engaged,
|
|
* and the command list buffer should be mapped. */
|
|
ncq_tfs->cmdh = get_cmd_header(s, i, ncq_tfs->slot);
|
|
if (!ncq_tfs->cmdh) {
|
|
return -1;
|
|
}
|
|
ahci_populate_sglist(ncq_tfs->drive, &ncq_tfs->sglist,
|
|
ncq_tfs->cmdh, ncq_tfs->sector_count * 512,
|
|
0);
|
|
if (ncq_tfs->sector_count != ncq_tfs->sglist.size >> 9) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* If an error is present, ad->busy_slot will be valid and not -1.
|
|
* In this case, an operation is waiting to resume and will re-check
|
|
* for additional AHCI commands to execute upon completion.
|
|
*
|
|
* In the case where no error was present, busy_slot will be -1,
|
|
* and we should check to see if there are additional commands waiting.
|
|
*/
|
|
if (ad->busy_slot == -1) {
|
|
check_cmd(s, i);
|
|
} else {
|
|
/* We are in the middle of a command, and may need to access
|
|
* the command header in guest memory again. */
|
|
if (ad->busy_slot < 0 || ad->busy_slot >= AHCI_MAX_CMDS) {
|
|
return -1;
|
|
}
|
|
ad->cur_cmd = get_cmd_header(s, i, ad->busy_slot);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const VMStateDescription vmstate_ahci = {
|
|
.name = "ahci",
|
|
.version_id = 1,
|
|
.post_load = ahci_state_post_load,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_STRUCT_VARRAY_POINTER_INT32(dev, AHCIState, ports,
|
|
vmstate_ahci_device, AHCIDevice),
|
|
VMSTATE_UINT32(control_regs.cap, AHCIState),
|
|
VMSTATE_UINT32(control_regs.ghc, AHCIState),
|
|
VMSTATE_UINT32(control_regs.irqstatus, AHCIState),
|
|
VMSTATE_UINT32(control_regs.impl, AHCIState),
|
|
VMSTATE_UINT32(control_regs.version, AHCIState),
|
|
VMSTATE_UINT32(idp_index, AHCIState),
|
|
VMSTATE_INT32_EQUAL(ports, AHCIState),
|
|
VMSTATE_END_OF_LIST()
|
|
},
|
|
};
|
|
|
|
static const VMStateDescription vmstate_sysbus_ahci = {
|
|
.name = "sysbus-ahci",
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_AHCI(ahci, SysbusAHCIState),
|
|
VMSTATE_END_OF_LIST()
|
|
},
|
|
};
|
|
|
|
static void sysbus_ahci_reset(DeviceState *dev)
|
|
{
|
|
SysbusAHCIState *s = SYSBUS_AHCI(dev);
|
|
|
|
ahci_reset(&s->ahci);
|
|
}
|
|
|
|
static void sysbus_ahci_init(Object *obj)
|
|
{
|
|
SysbusAHCIState *s = SYSBUS_AHCI(obj);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
|
|
|
ahci_init(&s->ahci, DEVICE(obj));
|
|
|
|
sysbus_init_mmio(sbd, &s->ahci.mem);
|
|
sysbus_init_irq(sbd, &s->ahci.irq);
|
|
}
|
|
|
|
static void sysbus_ahci_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
SysbusAHCIState *s = SYSBUS_AHCI(dev);
|
|
|
|
ahci_realize(&s->ahci, dev, &address_space_memory, s->num_ports);
|
|
}
|
|
|
|
static Property sysbus_ahci_properties[] = {
|
|
DEFINE_PROP_UINT32("num-ports", SysbusAHCIState, num_ports, 1),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void sysbus_ahci_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = sysbus_ahci_realize;
|
|
dc->vmsd = &vmstate_sysbus_ahci;
|
|
dc->props = sysbus_ahci_properties;
|
|
dc->reset = sysbus_ahci_reset;
|
|
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
|
|
}
|
|
|
|
static const TypeInfo sysbus_ahci_info = {
|
|
.name = TYPE_SYSBUS_AHCI,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(SysbusAHCIState),
|
|
.instance_init = sysbus_ahci_init,
|
|
.class_init = sysbus_ahci_class_init,
|
|
};
|
|
|
|
#define ALLWINNER_AHCI_BISTAFR ((0xa0 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_BISTCR ((0xa4 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_BISTFCTR ((0xa8 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_BISTSR ((0xac - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_BISTDECR ((0xb0 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_DIAGNR0 ((0xb4 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_DIAGNR1 ((0xb8 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_OOBR ((0xbc - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_PHYCS0R ((0xc0 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_PHYCS1R ((0xc4 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_PHYCS2R ((0xc8 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_TIMER1MS ((0xe0 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_GPARAM1R ((0xe8 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_GPARAM2R ((0xec - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_PPARAMR ((0xf0 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_TESTR ((0xf4 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_VERSIONR ((0xf8 - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_IDR ((0xfc - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
#define ALLWINNER_AHCI_RWCR ((0xfc - ALLWINNER_AHCI_MMIO_OFF) / 4)
|
|
|
|
static uint64_t allwinner_ahci_mem_read(void *opaque, hwaddr addr,
|
|
unsigned size)
|
|
{
|
|
AllwinnerAHCIState *a = opaque;
|
|
uint64_t val = a->regs[addr/4];
|
|
|
|
switch (addr / 4) {
|
|
case ALLWINNER_AHCI_PHYCS0R:
|
|
val |= 0x2 << 28;
|
|
break;
|
|
case ALLWINNER_AHCI_PHYCS2R:
|
|
val &= ~(0x1 << 24);
|
|
break;
|
|
}
|
|
DPRINTF(-1, "addr=0x%" HWADDR_PRIx " val=0x%" PRIx64 ", size=%d\n",
|
|
addr, val, size);
|
|
return val;
|
|
}
|
|
|
|
static void allwinner_ahci_mem_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
AllwinnerAHCIState *a = opaque;
|
|
|
|
DPRINTF(-1, "addr=0x%" HWADDR_PRIx " val=0x%" PRIx64 ", size=%d\n",
|
|
addr, val, size);
|
|
a->regs[addr/4] = val;
|
|
}
|
|
|
|
static const MemoryRegionOps allwinner_ahci_mem_ops = {
|
|
.read = allwinner_ahci_mem_read,
|
|
.write = allwinner_ahci_mem_write,
|
|
.valid.min_access_size = 4,
|
|
.valid.max_access_size = 4,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static void allwinner_ahci_init(Object *obj)
|
|
{
|
|
SysbusAHCIState *s = SYSBUS_AHCI(obj);
|
|
AllwinnerAHCIState *a = ALLWINNER_AHCI(obj);
|
|
|
|
memory_region_init_io(&a->mmio, OBJECT(obj), &allwinner_ahci_mem_ops, a,
|
|
"allwinner-ahci", ALLWINNER_AHCI_MMIO_SIZE);
|
|
memory_region_add_subregion(&s->ahci.mem, ALLWINNER_AHCI_MMIO_OFF,
|
|
&a->mmio);
|
|
}
|
|
|
|
static const VMStateDescription vmstate_allwinner_ahci = {
|
|
.name = "allwinner-ahci",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32_ARRAY(regs, AllwinnerAHCIState,
|
|
ALLWINNER_AHCI_MMIO_SIZE/4),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void allwinner_ahci_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->vmsd = &vmstate_allwinner_ahci;
|
|
}
|
|
|
|
static const TypeInfo allwinner_ahci_info = {
|
|
.name = TYPE_ALLWINNER_AHCI,
|
|
.parent = TYPE_SYSBUS_AHCI,
|
|
.instance_size = sizeof(AllwinnerAHCIState),
|
|
.instance_init = allwinner_ahci_init,
|
|
.class_init = allwinner_ahci_class_init,
|
|
};
|
|
|
|
static void sysbus_ahci_register_types(void)
|
|
{
|
|
type_register_static(&sysbus_ahci_info);
|
|
type_register_static(&allwinner_ahci_info);
|
|
}
|
|
|
|
type_init(sysbus_ahci_register_types)
|
|
|
|
void ahci_ide_create_devs(PCIDevice *dev, DriveInfo **hd)
|
|
{
|
|
AHCIPCIState *d = ICH_AHCI(dev);
|
|
AHCIState *ahci = &d->ahci;
|
|
int i;
|
|
|
|
for (i = 0; i < ahci->ports; i++) {
|
|
if (hd[i] == NULL) {
|
|
continue;
|
|
}
|
|
ide_create_drive(&ahci->dev[i].port, 0, hd[i]);
|
|
}
|
|
|
|
}
|