This adds the SAS1068 device, a SAS disk controller used in VMware that is oldish but widely supported and has decent performance. Unlike megasas, it presents itself as a SAS controller and not as a RAID controller. The device corresponds to the mptsas kernel driver in Linux. A few small things in the device setup are based on Don Slutz's old patch, but the device emulation was written from scratch based on Don's SeaBIOS patch and on the FreeBSD and Linux drivers. It is 2400 lines shorter than Don's patch (and roughly the same size as MegaSAS---also because it doesn't support the similar SPI controller), implements SCSI task management functions (with asynchronous cancellation), supports big-endian hosts, has complete support for migration and follows the QEMU coding standards much more closely. To write the driver, I first split Don's patch in two parts, with the configuration bits in one file and the rest in a separate file. I first left mptconfig.c in place and rewrote the rest, then deleted mptconfig.c as well. The configuration pages are still based mostly on VirtualBox's, though not exactly the same. However, the implementation is completely different. The contents of the pages themselves should not be copyrightable. Signed-off-by: Don Slutz <Don@CloudSwitch.com> Message-Id: <1347382813-5662-1-git-send-email-Don@CloudSwitch.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
905 lines
26 KiB
C
905 lines
26 KiB
C
/*
|
|
* QEMU LSI SAS1068 Host Bus Adapter emulation - configuration pages
|
|
*
|
|
* Copyright (c) 2016 Red Hat, Inc.
|
|
*
|
|
* Author: Paolo Bonzini
|
|
*
|
|
* 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.
|
|
*/
|
|
#include "qemu/osdep.h"
|
|
#include "hw/hw.h"
|
|
#include "hw/pci/pci.h"
|
|
#include "hw/scsi/scsi.h"
|
|
|
|
#include "mptsas.h"
|
|
#include "mpi.h"
|
|
#include "trace.h"
|
|
|
|
/* Generic functions for marshaling and unmarshaling. */
|
|
|
|
#define repl1(x) x
|
|
#define repl2(x) x x
|
|
#define repl3(x) x x x
|
|
#define repl4(x) x x x x
|
|
#define repl5(x) x x x x x
|
|
#define repl6(x) x x x x x x
|
|
#define repl7(x) x x x x x x x
|
|
#define repl8(x) x x x x x x x x
|
|
|
|
#define repl(n, x) glue(repl, n)(x)
|
|
|
|
typedef union PackValue {
|
|
uint64_t ll;
|
|
char *str;
|
|
} PackValue;
|
|
|
|
static size_t vfill(uint8_t *data, size_t size, const char *fmt, va_list ap)
|
|
{
|
|
size_t ofs;
|
|
PackValue val;
|
|
const char *p;
|
|
|
|
ofs = 0;
|
|
p = fmt;
|
|
while (*p) {
|
|
memset(&val, 0, sizeof(val));
|
|
switch (*p) {
|
|
case '*':
|
|
p++;
|
|
break;
|
|
case 'b':
|
|
case 'w':
|
|
case 'l':
|
|
val.ll = va_arg(ap, int);
|
|
break;
|
|
case 'q':
|
|
val.ll = va_arg(ap, int64_t);
|
|
break;
|
|
case 's':
|
|
val.str = va_arg(ap, void *);
|
|
break;
|
|
}
|
|
switch (*p++) {
|
|
case 'b':
|
|
if (data) {
|
|
stb_p(data + ofs, val.ll);
|
|
}
|
|
ofs++;
|
|
break;
|
|
case 'w':
|
|
if (data) {
|
|
stw_le_p(data + ofs, val.ll);
|
|
}
|
|
ofs += 2;
|
|
break;
|
|
case 'l':
|
|
if (data) {
|
|
stl_le_p(data + ofs, val.ll);
|
|
}
|
|
ofs += 4;
|
|
break;
|
|
case 'q':
|
|
if (data) {
|
|
stq_le_p(data + ofs, val.ll);
|
|
}
|
|
ofs += 8;
|
|
break;
|
|
case 's':
|
|
{
|
|
int cnt = atoi(p);
|
|
if (data) {
|
|
if (val.str) {
|
|
strncpy((void *)data + ofs, val.str, cnt);
|
|
} else {
|
|
memset((void *)data + ofs, 0, cnt);
|
|
}
|
|
}
|
|
ofs += cnt;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ofs;
|
|
}
|
|
|
|
static size_t vpack(uint8_t **p_data, const char *fmt, va_list ap1)
|
|
{
|
|
size_t size = 0;
|
|
uint8_t *data = NULL;
|
|
|
|
if (p_data) {
|
|
va_list ap2;
|
|
|
|
va_copy(ap2, ap1);
|
|
size = vfill(NULL, 0, fmt, ap2);
|
|
*p_data = data = g_malloc(size);
|
|
}
|
|
return vfill(data, size, fmt, ap1);
|
|
}
|
|
|
|
static size_t fill(uint8_t *data, size_t size, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
size_t ret;
|
|
|
|
va_start(ap, fmt);
|
|
ret = vfill(data, size, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Functions to build the page header and fill in the length, always used
|
|
* through the macros.
|
|
*/
|
|
|
|
#define MPTSAS_CONFIG_PACK(number, type, version, fmt, ...) \
|
|
mptsas_config_pack(data, "b*bbb" fmt, version, number, type, \
|
|
## __VA_ARGS__)
|
|
|
|
static size_t mptsas_config_pack(uint8_t **data, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
size_t ret;
|
|
|
|
va_start(ap, fmt);
|
|
ret = vpack(data, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (data) {
|
|
assert(ret < 256 && (ret % 4) == 0);
|
|
stb_p(*data + 1, ret / 4);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#define MPTSAS_CONFIG_PACK_EXT(number, type, version, fmt, ...) \
|
|
mptsas_config_pack_ext(data, "b*bbb*wb*b" fmt, version, number, \
|
|
MPI_CONFIG_PAGETYPE_EXTENDED, type, ## __VA_ARGS__)
|
|
|
|
static size_t mptsas_config_pack_ext(uint8_t **data, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
size_t ret;
|
|
|
|
va_start(ap, fmt);
|
|
ret = vpack(data, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (data) {
|
|
assert(ret < 65536 && (ret % 4) == 0);
|
|
stw_le_p(*data + 4, ret / 4);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Manufacturing pages */
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_0(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(0, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"s16s8s16s16s16",
|
|
"QEMU MPT Fusion",
|
|
"2.5",
|
|
"QEMU MPT Fusion",
|
|
"QEMU",
|
|
"0000111122223333");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_1(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
/* VPD - all zeros */
|
|
return MPTSAS_CONFIG_PACK(1, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"s256");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_2(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
PCIDeviceClass *pcic = PCI_DEVICE_GET_CLASS(s);
|
|
return MPTSAS_CONFIG_PACK(2, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"wb*b*l",
|
|
pcic->device_id, pcic->revision);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_3(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
PCIDeviceClass *pcic = PCI_DEVICE_GET_CLASS(s);
|
|
return MPTSAS_CONFIG_PACK(3, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"wb*b*l",
|
|
pcic->device_id, pcic->revision);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_4(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
/* All zeros */
|
|
return MPTSAS_CONFIG_PACK(4, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x05,
|
|
"*l*b*b*b*b*b*b*w*s56*l*l*l*l*l*l"
|
|
"*b*b*w*b*b*w*l*l");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_5(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(5, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x02,
|
|
"q*b*b*w*l*l", s->sas_addr);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_6(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(6, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"*l");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_7(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(7, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"*l*l*l*s16*b*b*w", MPTSAS_NUM_PORTS);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_8(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(8, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"*l");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_9(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(9, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"*l");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_manufacturing_10(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(10, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
|
|
"*l");
|
|
}
|
|
|
|
/* I/O unit pages */
|
|
|
|
static
|
|
size_t mptsas_config_io_unit_0(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(s);
|
|
uint64_t unique_value = 0x53504D554D4551LL; /* "QEMUMPTx" */
|
|
|
|
unique_value |= (uint64_t)pci->devfn << 56;
|
|
return MPTSAS_CONFIG_PACK(0, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x00,
|
|
"q", unique_value);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_io_unit_1(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(1, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x02, "l",
|
|
0x41 /* single function, RAID disabled */ );
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_io_unit_2(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(s);
|
|
uint8_t devfn = pci->devfn;
|
|
return MPTSAS_CONFIG_PACK(2, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x02,
|
|
"llbbw*b*b*w*b*b*w*b*b*w*l",
|
|
0, 0x100, 0 /* pci bus? */, devfn, 0);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_io_unit_3(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(3, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x01,
|
|
"*b*b*w*l");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_io_unit_4(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(4, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x00, "*l*l*q");
|
|
}
|
|
|
|
/* I/O controller pages */
|
|
|
|
static
|
|
size_t mptsas_config_ioc_0(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
PCIDeviceClass *pcic = PCI_DEVICE_GET_CLASS(s);
|
|
|
|
return MPTSAS_CONFIG_PACK(0, MPI_CONFIG_PAGETYPE_IOC, 0x01,
|
|
"*l*lwwb*b*b*blww",
|
|
pcic->vendor_id, pcic->device_id, pcic->revision,
|
|
pcic->subsystem_vendor_id,
|
|
pcic->subsystem_id);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_ioc_1(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(1, MPI_CONFIG_PAGETYPE_IOC, 0x03,
|
|
"*l*l*b*b*b*b");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_ioc_2(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(2, MPI_CONFIG_PAGETYPE_IOC, 0x04,
|
|
"*l*b*b*b*b");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_ioc_3(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(3, MPI_CONFIG_PAGETYPE_IOC, 0x00,
|
|
"*b*b*w");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_ioc_4(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(4, MPI_CONFIG_PAGETYPE_IOC, 0x00,
|
|
"*b*b*w");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_ioc_5(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(5, MPI_CONFIG_PAGETYPE_IOC, 0x00,
|
|
"*l*b*b*w");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_ioc_6(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK(6, MPI_CONFIG_PAGETYPE_IOC, 0x01,
|
|
"*l*b*b*b*b*b*b*b*b*b*b*w*l*l*l*l*b*b*w"
|
|
"*w*w*w*w*l*l*l");
|
|
}
|
|
|
|
/* SAS I/O unit pages (extended) */
|
|
|
|
#define MPTSAS_CONFIG_SAS_IO_UNIT_0_SIZE 16
|
|
|
|
#define MPI_SAS_IOUNIT0_RATE_FAILED_SPEED_NEGOTIATION 0x02
|
|
#define MPI_SAS_IOUNIT0_RATE_1_5 0x08
|
|
#define MPI_SAS_IOUNIT0_RATE_3_0 0x09
|
|
|
|
#define MPI_SAS_DEVICE_INFO_NO_DEVICE 0x00000000
|
|
#define MPI_SAS_DEVICE_INFO_END_DEVICE 0x00000001
|
|
#define MPI_SAS_DEVICE_INFO_SSP_TARGET 0x00000400
|
|
|
|
#define MPI_SAS_DEVICE0_ASTATUS_NO_ERRORS 0x00
|
|
|
|
#define MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT 0x0001
|
|
#define MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED 0x0002
|
|
#define MPI_SAS_DEVICE0_FLAGS_MAPPING_PERSISTENT 0x0004
|
|
|
|
|
|
|
|
static SCSIDevice *mptsas_phy_get_device(MPTSASState *s, int i,
|
|
int *phy_handle, int *dev_handle)
|
|
{
|
|
SCSIDevice *d = scsi_device_find(&s->bus, 0, i, 0);
|
|
|
|
if (phy_handle) {
|
|
*phy_handle = i + 1;
|
|
}
|
|
if (dev_handle) {
|
|
*dev_handle = d ? i + 1 + MPTSAS_NUM_PORTS : 0;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_sas_io_unit_0(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
size_t size = MPTSAS_CONFIG_PACK_EXT(0, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT, 0x04,
|
|
"*w*wb*b*w"
|
|
repl(MPTSAS_NUM_PORTS, "*s16"),
|
|
MPTSAS_NUM_PORTS);
|
|
|
|
if (data) {
|
|
size_t ofs = size - MPTSAS_NUM_PORTS * MPTSAS_CONFIG_SAS_IO_UNIT_0_SIZE;
|
|
int i;
|
|
|
|
for (i = 0; i < MPTSAS_NUM_PORTS; i++) {
|
|
int phy_handle, dev_handle;
|
|
SCSIDevice *dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
|
|
|
|
fill(*data + ofs, MPTSAS_CONFIG_SAS_IO_UNIT_0_SIZE,
|
|
"bbbblwwl", i, 0, 0,
|
|
(dev
|
|
? MPI_SAS_IOUNIT0_RATE_3_0
|
|
: MPI_SAS_IOUNIT0_RATE_FAILED_SPEED_NEGOTIATION),
|
|
(dev
|
|
? MPI_SAS_DEVICE_INFO_END_DEVICE | MPI_SAS_DEVICE_INFO_SSP_TARGET
|
|
: MPI_SAS_DEVICE_INFO_NO_DEVICE),
|
|
dev_handle,
|
|
dev_handle,
|
|
0);
|
|
ofs += MPTSAS_CONFIG_SAS_IO_UNIT_0_SIZE;
|
|
}
|
|
assert(ofs == size);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
#define MPTSAS_CONFIG_SAS_IO_UNIT_1_SIZE 12
|
|
|
|
static
|
|
size_t mptsas_config_sas_io_unit_1(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
size_t size = MPTSAS_CONFIG_PACK_EXT(1, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT, 0x07,
|
|
"*w*w*w*wb*b*b*b"
|
|
repl(MPTSAS_NUM_PORTS, "*s12"),
|
|
MPTSAS_NUM_PORTS);
|
|
|
|
if (data) {
|
|
size_t ofs = size - MPTSAS_NUM_PORTS * MPTSAS_CONFIG_SAS_IO_UNIT_1_SIZE;
|
|
int i;
|
|
|
|
for (i = 0; i < MPTSAS_NUM_PORTS; i++) {
|
|
SCSIDevice *dev = mptsas_phy_get_device(s, i, NULL, NULL);
|
|
fill(*data + ofs, MPTSAS_CONFIG_SAS_IO_UNIT_1_SIZE,
|
|
"bbbblww", i, 0, 0,
|
|
(MPI_SAS_IOUNIT0_RATE_3_0 << 4) | MPI_SAS_IOUNIT0_RATE_1_5,
|
|
(dev
|
|
? MPI_SAS_DEVICE_INFO_END_DEVICE | MPI_SAS_DEVICE_INFO_SSP_TARGET
|
|
: MPI_SAS_DEVICE_INFO_NO_DEVICE),
|
|
0, 0);
|
|
ofs += MPTSAS_CONFIG_SAS_IO_UNIT_1_SIZE;
|
|
}
|
|
assert(ofs == size);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_sas_io_unit_2(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK_EXT(2, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT, 0x06,
|
|
"*b*b*w*w*w*b*b*w");
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_sas_io_unit_3(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
return MPTSAS_CONFIG_PACK_EXT(3, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT, 0x06,
|
|
"*l*l*l*l*l*l*l*l*l");
|
|
}
|
|
|
|
/* SAS PHY pages (extended) */
|
|
|
|
static int mptsas_phy_addr_get(MPTSASState *s, int address)
|
|
{
|
|
int i;
|
|
if ((address >> MPI_SAS_PHY_PGAD_FORM_SHIFT) == 0) {
|
|
i = address & 255;
|
|
} else if ((address >> MPI_SAS_PHY_PGAD_FORM_SHIFT) == 1) {
|
|
i = address & 65535;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (i >= MPTSAS_NUM_PORTS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_phy_0(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
int phy_handle = -1;
|
|
int dev_handle = -1;
|
|
int i = mptsas_phy_addr_get(s, address);
|
|
SCSIDevice *dev;
|
|
|
|
if (i < 0) {
|
|
trace_mptsas_config_sas_phy(s, address, i, phy_handle, dev_handle, 0);
|
|
return i;
|
|
}
|
|
|
|
dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
|
|
trace_mptsas_config_sas_phy(s, address, i, phy_handle, dev_handle, 0);
|
|
|
|
return MPTSAS_CONFIG_PACK_EXT(0, MPI_CONFIG_EXTPAGETYPE_SAS_PHY, 0x01,
|
|
"w*wqwb*blbb*b*b*l",
|
|
dev_handle, s->sas_addr, dev_handle, i,
|
|
(dev
|
|
? MPI_SAS_DEVICE_INFO_END_DEVICE /* | MPI_SAS_DEVICE_INFO_SSP_TARGET?? */
|
|
: MPI_SAS_DEVICE_INFO_NO_DEVICE),
|
|
(MPI_SAS_IOUNIT0_RATE_3_0 << 4) | MPI_SAS_IOUNIT0_RATE_1_5,
|
|
(MPI_SAS_IOUNIT0_RATE_3_0 << 4) | MPI_SAS_IOUNIT0_RATE_1_5);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_phy_1(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
int phy_handle = -1;
|
|
int dev_handle = -1;
|
|
int i = mptsas_phy_addr_get(s, address);
|
|
|
|
if (i < 0) {
|
|
trace_mptsas_config_sas_phy(s, address, i, phy_handle, dev_handle, 1);
|
|
return i;
|
|
}
|
|
|
|
(void) mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
|
|
trace_mptsas_config_sas_phy(s, address, i, phy_handle, dev_handle, 1);
|
|
|
|
return MPTSAS_CONFIG_PACK_EXT(1, MPI_CONFIG_EXTPAGETYPE_SAS_PHY, 0x01,
|
|
"*l*l*l*l*l");
|
|
}
|
|
|
|
/* SAS device pages (extended) */
|
|
|
|
static int mptsas_device_addr_get(MPTSASState *s, int address)
|
|
{
|
|
uint32_t handle, i;
|
|
uint32_t form = address >> MPI_SAS_PHY_PGAD_FORM_SHIFT;
|
|
if (form == MPI_SAS_DEVICE_PGAD_FORM_GET_NEXT_HANDLE) {
|
|
handle = address & MPI_SAS_DEVICE_PGAD_GNH_HANDLE_MASK;
|
|
do {
|
|
if (handle == 65535) {
|
|
handle = MPTSAS_NUM_PORTS + 1;
|
|
} else {
|
|
++handle;
|
|
}
|
|
i = handle - 1 - MPTSAS_NUM_PORTS;
|
|
} while (i < MPTSAS_NUM_PORTS && !scsi_device_find(&s->bus, 0, i, 0));
|
|
|
|
} else if (form == MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID) {
|
|
if (address & MPI_SAS_DEVICE_PGAD_BT_BUS_MASK) {
|
|
return -EINVAL;
|
|
}
|
|
i = address & MPI_SAS_DEVICE_PGAD_BT_TID_MASK;
|
|
|
|
} else if (form == MPI_SAS_DEVICE_PGAD_FORM_HANDLE) {
|
|
handle = address & MPI_SAS_DEVICE_PGAD_H_HANDLE_MASK;
|
|
i = handle - 1 - MPTSAS_NUM_PORTS;
|
|
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (i >= MPTSAS_NUM_PORTS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_sas_device_0(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
int phy_handle = -1;
|
|
int dev_handle = -1;
|
|
int i = mptsas_device_addr_get(s, address);
|
|
SCSIDevice *dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
|
|
|
|
trace_mptsas_config_sas_device(s, address, i, phy_handle, dev_handle, 0);
|
|
if (!dev) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
return MPTSAS_CONFIG_PACK_EXT(0, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE, 0x05,
|
|
"*w*wqwbbwbblwb*b",
|
|
dev->wwn, phy_handle, i,
|
|
MPI_SAS_DEVICE0_ASTATUS_NO_ERRORS,
|
|
dev_handle, i, 0,
|
|
MPI_SAS_DEVICE_INFO_END_DEVICE | MPI_SAS_DEVICE_INFO_SSP_TARGET,
|
|
(MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT |
|
|
MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED |
|
|
MPI_SAS_DEVICE0_FLAGS_MAPPING_PERSISTENT), i);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_sas_device_1(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
int phy_handle = -1;
|
|
int dev_handle = -1;
|
|
int i = mptsas_device_addr_get(s, address);
|
|
SCSIDevice *dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
|
|
|
|
trace_mptsas_config_sas_device(s, address, i, phy_handle, dev_handle, 1);
|
|
if (!dev) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
return MPTSAS_CONFIG_PACK_EXT(1, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE, 0x00,
|
|
"*lq*lwbb*s20",
|
|
dev->wwn, dev_handle, i, 0);
|
|
}
|
|
|
|
static
|
|
size_t mptsas_config_sas_device_2(MPTSASState *s, uint8_t **data, int address)
|
|
{
|
|
int phy_handle = -1;
|
|
int dev_handle = -1;
|
|
int i = mptsas_device_addr_get(s, address);
|
|
SCSIDevice *dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
|
|
|
|
trace_mptsas_config_sas_device(s, address, i, phy_handle, dev_handle, 2);
|
|
if (!dev) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
return MPTSAS_CONFIG_PACK_EXT(2, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE, 0x01,
|
|
"ql", dev->wwn, 0);
|
|
}
|
|
|
|
typedef struct MPTSASConfigPage {
|
|
uint8_t number;
|
|
uint8_t type;
|
|
size_t (*mpt_config_build)(MPTSASState *s, uint8_t **data, int address);
|
|
} MPTSASConfigPage;
|
|
|
|
static const MPTSASConfigPage mptsas_config_pages[] = {
|
|
{
|
|
0, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_0,
|
|
}, {
|
|
1, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_1,
|
|
}, {
|
|
2, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_2,
|
|
}, {
|
|
3, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_3,
|
|
}, {
|
|
4, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_4,
|
|
}, {
|
|
5, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_5,
|
|
}, {
|
|
6, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_6,
|
|
}, {
|
|
7, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_7,
|
|
}, {
|
|
8, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_8,
|
|
}, {
|
|
9, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_9,
|
|
}, {
|
|
10, MPI_CONFIG_PAGETYPE_MANUFACTURING,
|
|
mptsas_config_manufacturing_10,
|
|
}, {
|
|
0, MPI_CONFIG_PAGETYPE_IO_UNIT,
|
|
mptsas_config_io_unit_0,
|
|
}, {
|
|
1, MPI_CONFIG_PAGETYPE_IO_UNIT,
|
|
mptsas_config_io_unit_1,
|
|
}, {
|
|
2, MPI_CONFIG_PAGETYPE_IO_UNIT,
|
|
mptsas_config_io_unit_2,
|
|
}, {
|
|
3, MPI_CONFIG_PAGETYPE_IO_UNIT,
|
|
mptsas_config_io_unit_3,
|
|
}, {
|
|
4, MPI_CONFIG_PAGETYPE_IO_UNIT,
|
|
mptsas_config_io_unit_4,
|
|
}, {
|
|
0, MPI_CONFIG_PAGETYPE_IOC,
|
|
mptsas_config_ioc_0,
|
|
}, {
|
|
1, MPI_CONFIG_PAGETYPE_IOC,
|
|
mptsas_config_ioc_1,
|
|
}, {
|
|
2, MPI_CONFIG_PAGETYPE_IOC,
|
|
mptsas_config_ioc_2,
|
|
}, {
|
|
3, MPI_CONFIG_PAGETYPE_IOC,
|
|
mptsas_config_ioc_3,
|
|
}, {
|
|
4, MPI_CONFIG_PAGETYPE_IOC,
|
|
mptsas_config_ioc_4,
|
|
}, {
|
|
5, MPI_CONFIG_PAGETYPE_IOC,
|
|
mptsas_config_ioc_5,
|
|
}, {
|
|
6, MPI_CONFIG_PAGETYPE_IOC,
|
|
mptsas_config_ioc_6,
|
|
}, {
|
|
0, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
|
|
mptsas_config_sas_io_unit_0,
|
|
}, {
|
|
1, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
|
|
mptsas_config_sas_io_unit_1,
|
|
}, {
|
|
2, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
|
|
mptsas_config_sas_io_unit_2,
|
|
}, {
|
|
3, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
|
|
mptsas_config_sas_io_unit_3,
|
|
}, {
|
|
0, MPI_CONFIG_EXTPAGETYPE_SAS_PHY,
|
|
mptsas_config_phy_0,
|
|
}, {
|
|
1, MPI_CONFIG_EXTPAGETYPE_SAS_PHY,
|
|
mptsas_config_phy_1,
|
|
}, {
|
|
0, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE,
|
|
mptsas_config_sas_device_0,
|
|
}, {
|
|
1, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE,
|
|
mptsas_config_sas_device_1,
|
|
}, {
|
|
2, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE,
|
|
mptsas_config_sas_device_2,
|
|
}
|
|
};
|
|
|
|
static const MPTSASConfigPage *mptsas_find_config_page(int type, int number)
|
|
{
|
|
const MPTSASConfigPage *page;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mptsas_config_pages); i++) {
|
|
page = &mptsas_config_pages[i];
|
|
if (page->type == type && page->number == number) {
|
|
return page;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void mptsas_process_config(MPTSASState *s, MPIMsgConfig *req)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(s);
|
|
|
|
MPIMsgConfigReply reply;
|
|
const MPTSASConfigPage *page;
|
|
size_t length;
|
|
uint8_t type;
|
|
uint8_t *data = NULL;
|
|
uint32_t flags_and_length;
|
|
uint32_t dmalen;
|
|
uint64_t pa;
|
|
|
|
mptsas_fix_config_endianness(req);
|
|
|
|
QEMU_BUILD_BUG_ON(sizeof(s->doorbell_msg) < sizeof(*req));
|
|
QEMU_BUILD_BUG_ON(sizeof(s->doorbell_reply) < sizeof(reply));
|
|
|
|
/* Copy common bits from the request into the reply. */
|
|
memset(&reply, 0, sizeof(reply));
|
|
reply.Action = req->Action;
|
|
reply.Function = req->Function;
|
|
reply.MsgContext = req->MsgContext;
|
|
reply.MsgLength = sizeof(reply) / 4;
|
|
reply.PageType = req->PageType;
|
|
reply.PageNumber = req->PageNumber;
|
|
reply.PageLength = req->PageLength;
|
|
reply.PageVersion = req->PageVersion;
|
|
|
|
type = req->PageType & MPI_CONFIG_PAGETYPE_MASK;
|
|
if (type == MPI_CONFIG_PAGETYPE_EXTENDED) {
|
|
type = req->ExtPageType;
|
|
if (type <= MPI_CONFIG_PAGETYPE_MASK) {
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_TYPE;
|
|
goto out;
|
|
}
|
|
|
|
reply.ExtPageType = req->ExtPageType;
|
|
}
|
|
|
|
page = mptsas_find_config_page(type, req->PageNumber);
|
|
|
|
switch(req->Action) {
|
|
case MPI_CONFIG_ACTION_PAGE_DEFAULT:
|
|
case MPI_CONFIG_ACTION_PAGE_HEADER:
|
|
case MPI_CONFIG_ACTION_PAGE_READ_NVRAM:
|
|
case MPI_CONFIG_ACTION_PAGE_READ_CURRENT:
|
|
case MPI_CONFIG_ACTION_PAGE_READ_DEFAULT:
|
|
case MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT:
|
|
case MPI_CONFIG_ACTION_PAGE_WRITE_NVRAM:
|
|
break;
|
|
|
|
default:
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_ACTION;
|
|
goto out;
|
|
}
|
|
|
|
if (!page) {
|
|
page = mptsas_find_config_page(type, 1);
|
|
if (page) {
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
|
|
} else {
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_TYPE;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (req->Action == MPI_CONFIG_ACTION_PAGE_DEFAULT ||
|
|
req->Action == MPI_CONFIG_ACTION_PAGE_HEADER) {
|
|
length = page->mpt_config_build(s, NULL, req->PageAddress);
|
|
if ((ssize_t)length < 0) {
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
|
|
goto out;
|
|
} else {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (req->Action == MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT ||
|
|
req->Action == MPI_CONFIG_ACTION_PAGE_WRITE_NVRAM) {
|
|
length = page->mpt_config_build(s, NULL, req->PageAddress);
|
|
if ((ssize_t)length < 0) {
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
|
|
} else {
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_CANT_COMMIT;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
flags_and_length = req->PageBufferSGE.FlagsLength;
|
|
dmalen = flags_and_length & MPI_SGE_LENGTH_MASK;
|
|
if (dmalen == 0) {
|
|
length = page->mpt_config_build(s, NULL, req->PageAddress);
|
|
if ((ssize_t)length < 0) {
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
|
|
goto out;
|
|
} else {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (flags_and_length & MPI_SGE_FLAGS_64_BIT_ADDRESSING) {
|
|
pa = req->PageBufferSGE.u.Address64;
|
|
} else {
|
|
pa = req->PageBufferSGE.u.Address32;
|
|
}
|
|
|
|
/* Only read actions left. */
|
|
length = page->mpt_config_build(s, &data, req->PageAddress);
|
|
if ((ssize_t)length < 0) {
|
|
reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
|
|
goto out;
|
|
} else {
|
|
assert(data[2] == page->number);
|
|
pci_dma_write(pci, pa, data, MIN(length, dmalen));
|
|
goto done;
|
|
}
|
|
|
|
abort();
|
|
|
|
done:
|
|
if (type > MPI_CONFIG_PAGETYPE_MASK) {
|
|
reply.ExtPageLength = length / 4;
|
|
reply.ExtPageType = req->ExtPageType;
|
|
} else {
|
|
reply.PageLength = length / 4;
|
|
}
|
|
|
|
out:
|
|
mptsas_fix_config_reply_endianness(&reply);
|
|
mptsas_reply(s, (MPIDefaultReply *)&reply);
|
|
g_free(data);
|
|
}
|