mirror of https://gitee.com/openkylin/qemu.git
Block pull request
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJUDYm7AAoJEJykq7OBq3PIPmAH+gKAVT+GALKYyTSD5Lo3fM3Z vvq0fDwzrdup9A9p6vEumfUjn5L9TWSNJfspmVSL8qASG7nvJbynDCGd8yrARjFF q2rPCUhdiyErel1crXzewTRf7Hl+jPq89OeUGaXKknTXvIUTRc5BROMTS+jLPYPf tBNJ2+WKDjsp370yvuYNf12rikPHQrOqGtYvu4+Y+eaEAlZMeSOom/VWGz3PRWjL +ppTec1dalq5LVeLwU05TPT6b7MA+daNZ4aORlzyMl0rXzzg2W8RzpzbKlO1sBg2 qaLJl0ZRc520JTiqVA36uo2rXBkdilNLvSnsuxI/VUZ8g9tvst9Wt42eGWOrsb4= =69HF -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/stefanha/tags/block-pull-request' into staging Block pull request # gpg: Signature made Mon 08 Sep 2014 11:49:31 BST using RSA key ID 81AB73C8 # gpg: Good signature from "Stefan Hajnoczi <stefanha@redhat.com>" # gpg: aka "Stefan Hajnoczi <stefanha@gmail.com>" * remotes/stefanha/tags/block-pull-request: (24 commits) ide: Add resize callback to ide/core IDE: Fill the IDENTIFY request consistently vmdk: fix buf leak in vmdk_parse_extents() vmdk: fix vmdk_parse_extents() extent_file leaks ide: Add wwn support to IDE-ATAPI drive qtest/ide: Uninitialize PC allocator libqos: add a simple first-fit memory allocator MAINTAINERS: update sheepdog maintainer qemu-nbd: fix indentation and coding style qemu-nbd: add option to set detect-zeroes mode rename parse_enum_option to qapi_enum_parse and make it public block/archipelago: Use QEMU atomic builtins qemu-img: fix rebase src_cache option documentation qemu-img: clarify src_cache option documentation libqos: Added EVENT_IDX support libqos: Added MSI-X support libqos: Added test case for configuration changes in virtio-blk test libqos: Added indirect descriptor support to virtio implementation libqos: Added basic virtqueue support to virtio implementation tests: Add virtio device initialization ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
1bc0e40581
|
@ -980,7 +980,7 @@ S: Supported
|
||||||
F: block/rbd.c
|
F: block/rbd.c
|
||||||
|
|
||||||
Sheepdog
|
Sheepdog
|
||||||
M: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
|
M: Hitoshi Mitake <mitake.hitoshi@lab.ntt.co.jp>
|
||||||
M: Liu Yuan <namei.unix@gmail.com>
|
M: Liu Yuan <namei.unix@gmail.com>
|
||||||
L: sheepdog@lists.wpkg.org
|
L: sheepdog@lists.wpkg.org
|
||||||
S: Supported
|
S: Supported
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
#include "qapi/qmp/qint.h"
|
#include "qapi/qmp/qint.h"
|
||||||
#include "qapi/qmp/qstring.h"
|
#include "qapi/qmp/qstring.h"
|
||||||
#include "qapi/qmp/qjson.h"
|
#include "qapi/qmp/qjson.h"
|
||||||
|
#include "qemu/atomic.h"
|
||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <xseg/xseg.h>
|
#include <xseg/xseg.h>
|
||||||
|
@ -214,7 +215,7 @@ static void xseg_request_handler(void *state)
|
||||||
|
|
||||||
xseg_put_request(s->xseg, req, s->srcport);
|
xseg_put_request(s->xseg, req, s->srcport);
|
||||||
|
|
||||||
if ((__sync_add_and_fetch(&segreq->ref, -1)) == 0) {
|
if (atomic_fetch_dec(&segreq->ref) == 1) {
|
||||||
if (!segreq->failed) {
|
if (!segreq->failed) {
|
||||||
reqdata->aio_cb->ret = segreq->count;
|
reqdata->aio_cb->ret = segreq->count;
|
||||||
archipelago_finish_aiocb(reqdata);
|
archipelago_finish_aiocb(reqdata);
|
||||||
|
@ -233,7 +234,7 @@ static void xseg_request_handler(void *state)
|
||||||
segreq->count += req->serviced;
|
segreq->count += req->serviced;
|
||||||
xseg_put_request(s->xseg, req, s->srcport);
|
xseg_put_request(s->xseg, req, s->srcport);
|
||||||
|
|
||||||
if ((__sync_add_and_fetch(&segreq->ref, -1)) == 0) {
|
if (atomic_fetch_dec(&segreq->ref) == 1) {
|
||||||
if (!segreq->failed) {
|
if (!segreq->failed) {
|
||||||
reqdata->aio_cb->ret = segreq->count;
|
reqdata->aio_cb->ret = segreq->count;
|
||||||
archipelago_finish_aiocb(reqdata);
|
archipelago_finish_aiocb(reqdata);
|
||||||
|
@ -824,78 +825,47 @@ static int archipelago_aio_segmented_rw(BDRVArchipelagoState *s,
|
||||||
ArchipelagoAIOCB *aio_cb,
|
ArchipelagoAIOCB *aio_cb,
|
||||||
int op)
|
int op)
|
||||||
{
|
{
|
||||||
int i, ret, segments_nr, last_segment_size;
|
int ret, segments_nr;
|
||||||
|
size_t pos = 0;
|
||||||
ArchipelagoSegmentedRequest *segreq;
|
ArchipelagoSegmentedRequest *segreq;
|
||||||
|
|
||||||
segreq = g_new(ArchipelagoSegmentedRequest, 1);
|
segreq = g_new0(ArchipelagoSegmentedRequest, 1);
|
||||||
|
|
||||||
if (op == ARCHIP_OP_FLUSH) {
|
if (op == ARCHIP_OP_FLUSH) {
|
||||||
segments_nr = 1;
|
segments_nr = 1;
|
||||||
segreq->ref = segments_nr;
|
} else {
|
||||||
segreq->total = count;
|
|
||||||
segreq->count = 0;
|
|
||||||
segreq->failed = 0;
|
|
||||||
ret = archipelago_submit_request(s, 0, count, offset, aio_cb,
|
|
||||||
segreq, ARCHIP_OP_FLUSH);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto err_exit;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
segments_nr = (int)(count / MAX_REQUEST_SIZE) + \
|
segments_nr = (int)(count / MAX_REQUEST_SIZE) + \
|
||||||
((count % MAX_REQUEST_SIZE) ? 1 : 0);
|
((count % MAX_REQUEST_SIZE) ? 1 : 0);
|
||||||
last_segment_size = (int)(count % MAX_REQUEST_SIZE);
|
}
|
||||||
|
|
||||||
segreq->ref = segments_nr;
|
|
||||||
segreq->total = count;
|
segreq->total = count;
|
||||||
segreq->count = 0;
|
atomic_mb_set(&segreq->ref, segments_nr);
|
||||||
segreq->failed = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < segments_nr - 1; i++) {
|
while (segments_nr > 1) {
|
||||||
ret = archipelago_submit_request(s, i * MAX_REQUEST_SIZE,
|
ret = archipelago_submit_request(s, pos,
|
||||||
MAX_REQUEST_SIZE,
|
MAX_REQUEST_SIZE,
|
||||||
offset + i * MAX_REQUEST_SIZE,
|
offset + pos,
|
||||||
aio_cb, segreq, op);
|
aio_cb, segreq, op);
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto err_exit;
|
goto err_exit;
|
||||||
}
|
}
|
||||||
|
count -= MAX_REQUEST_SIZE;
|
||||||
|
pos += MAX_REQUEST_SIZE;
|
||||||
|
segments_nr--;
|
||||||
}
|
}
|
||||||
|
ret = archipelago_submit_request(s, pos, count, offset + pos,
|
||||||
if ((segments_nr > 1) && last_segment_size) {
|
|
||||||
ret = archipelago_submit_request(s, i * MAX_REQUEST_SIZE,
|
|
||||||
last_segment_size,
|
|
||||||
offset + i * MAX_REQUEST_SIZE,
|
|
||||||
aio_cb, segreq, op);
|
aio_cb, segreq, op);
|
||||||
} else if ((segments_nr > 1) && !last_segment_size) {
|
|
||||||
ret = archipelago_submit_request(s, i * MAX_REQUEST_SIZE,
|
|
||||||
MAX_REQUEST_SIZE,
|
|
||||||
offset + i * MAX_REQUEST_SIZE,
|
|
||||||
aio_cb, segreq, op);
|
|
||||||
} else if (segments_nr == 1) {
|
|
||||||
ret = archipelago_submit_request(s, 0, count, offset, aio_cb,
|
|
||||||
segreq, op);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto err_exit;
|
goto err_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_exit:
|
err_exit:
|
||||||
__sync_add_and_fetch(&segreq->failed, 1);
|
segreq->failed = 1;
|
||||||
if (segments_nr == 1) {
|
if (atomic_fetch_sub(&segreq->ref, segments_nr) == segments_nr) {
|
||||||
if (__sync_add_and_fetch(&segreq->ref, -1) == 0) {
|
|
||||||
g_free(segreq);
|
g_free(segreq);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if ((__sync_add_and_fetch(&segreq->ref, -segments_nr + i)) == 0) {
|
|
||||||
g_free(segreq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -834,6 +834,7 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs,
|
||||||
ret = vmdk_add_extent(bs, extent_file, true, sectors,
|
ret = vmdk_add_extent(bs, extent_file, true, sectors,
|
||||||
0, 0, 0, 0, 0, &extent, errp);
|
0, 0, 0, 0, 0, &extent, errp);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
bdrv_unref(extent_file);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
extent->flat_start_offset = flat_offset << 9;
|
extent->flat_start_offset = flat_offset << 9;
|
||||||
|
@ -845,14 +846,15 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs,
|
||||||
} else {
|
} else {
|
||||||
ret = vmdk_open_sparse(bs, extent_file, bs->open_flags, buf, errp);
|
ret = vmdk_open_sparse(bs, extent_file, bs->open_flags, buf, errp);
|
||||||
}
|
}
|
||||||
if (ret) {
|
|
||||||
g_free(buf);
|
g_free(buf);
|
||||||
|
if (ret) {
|
||||||
bdrv_unref(extent_file);
|
bdrv_unref(extent_file);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
extent = &s->extents[s->num_extents - 1];
|
extent = &s->extents[s->num_extents - 1];
|
||||||
} else {
|
} else {
|
||||||
error_setg(errp, "Unsupported extent type '%s'", type);
|
error_setg(errp, "Unsupported extent type '%s'", type);
|
||||||
|
bdrv_unref(extent_file);
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
extent->type = g_strdup(type);
|
extent->type = g_strdup(type);
|
||||||
|
|
22
blockdev.c
22
blockdev.c
|
@ -39,6 +39,7 @@
|
||||||
#include "qapi/qmp/types.h"
|
#include "qapi/qmp/types.h"
|
||||||
#include "qapi-visit.h"
|
#include "qapi-visit.h"
|
||||||
#include "qapi/qmp-output-visitor.h"
|
#include "qapi/qmp-output-visitor.h"
|
||||||
|
#include "qapi/util.h"
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qmp-commands.h"
|
#include "qmp-commands.h"
|
||||||
|
@ -274,25 +275,6 @@ static int parse_block_error_action(const char *buf, bool is_read, Error **errp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int parse_enum_option(const char *lookup[], const char *buf,
|
|
||||||
int max, int def, Error **errp)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (!buf) {
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < max; i++) {
|
|
||||||
if (!strcmp(buf, lookup[i])) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error_setg(errp, "invalid parameter value: %s", buf);
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool check_throttle_config(ThrottleConfig *cfg, Error **errp)
|
static bool check_throttle_config(ThrottleConfig *cfg, Error **errp)
|
||||||
{
|
{
|
||||||
if (throttle_conflicting(cfg)) {
|
if (throttle_conflicting(cfg)) {
|
||||||
|
@ -456,7 +438,7 @@ static DriveInfo *blockdev_init(const char *file, QDict *bs_opts,
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_zeroes =
|
detect_zeroes =
|
||||||
parse_enum_option(BlockdevDetectZeroesOptions_lookup,
|
qapi_enum_parse(BlockdevDetectZeroesOptions_lookup,
|
||||||
qemu_opt_get(opts, "detect-zeroes"),
|
qemu_opt_get(opts, "detect-zeroes"),
|
||||||
BLOCKDEV_DETECT_ZEROES_OPTIONS_MAX,
|
BLOCKDEV_DETECT_ZEROES_OPTIONS_MAX,
|
||||||
BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF,
|
BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF,
|
||||||
|
|
|
@ -94,10 +94,13 @@ struct pflash_t {
|
||||||
void *storage;
|
void *storage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int pflash_post_load(void *opaque, int version_id);
|
||||||
|
|
||||||
static const VMStateDescription vmstate_pflash = {
|
static const VMStateDescription vmstate_pflash = {
|
||||||
.name = "pflash_cfi01",
|
.name = "pflash_cfi01",
|
||||||
.version_id = 1,
|
.version_id = 1,
|
||||||
.minimum_version_id = 1,
|
.minimum_version_id = 1,
|
||||||
|
.post_load = pflash_post_load,
|
||||||
.fields = (VMStateField[]) {
|
.fields = (VMStateField[]) {
|
||||||
VMSTATE_UINT8(wcycle, pflash_t),
|
VMSTATE_UINT8(wcycle, pflash_t),
|
||||||
VMSTATE_UINT8(cmd, pflash_t),
|
VMSTATE_UINT8(cmd, pflash_t),
|
||||||
|
@ -209,11 +212,11 @@ static uint32_t pflash_devid_query(pflash_t *pfl, hwaddr offset)
|
||||||
switch (boff & 0xFF) {
|
switch (boff & 0xFF) {
|
||||||
case 0:
|
case 0:
|
||||||
resp = pfl->ident0;
|
resp = pfl->ident0;
|
||||||
DPRINTF("%s: Manufacturer Code %04x\n", __func__, ret);
|
DPRINTF("%s: Manufacturer Code %04x\n", __func__, resp);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
resp = pfl->ident1;
|
resp = pfl->ident1;
|
||||||
DPRINTF("%s: Device ID Code %04x\n", __func__, ret);
|
DPRINTF("%s: Device ID Code %04x\n", __func__, resp);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DPRINTF("%s: Read Device Information offset=%x\n", __func__,
|
DPRINTF("%s: Read Device Information offset=%x\n", __func__,
|
||||||
|
@ -982,3 +985,14 @@ MemoryRegion *pflash_cfi01_get_memory(pflash_t *fl)
|
||||||
{
|
{
|
||||||
return &fl->mem;
|
return &fl->mem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int pflash_post_load(void *opaque, int version_id)
|
||||||
|
{
|
||||||
|
pflash_t *pfl = opaque;
|
||||||
|
|
||||||
|
if (!pfl->ro) {
|
||||||
|
DPRINTF("%s: updating bdrv for %s\n", __func__, pfl->name);
|
||||||
|
pflash_update(pfl, 0, pfl->sector_len * pfl->nb_blocs);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
109
hw/ide/core.c
109
hw/ide/core.c
|
@ -75,19 +75,29 @@ static void put_le16(uint16_t *p, unsigned int v)
|
||||||
*p = cpu_to_le16(v);
|
*p = cpu_to_le16(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ide_identify_size(IDEState *s)
|
||||||
|
{
|
||||||
|
uint16_t *p = (uint16_t *)s->identify_data;
|
||||||
|
put_le16(p + 60, s->nb_sectors);
|
||||||
|
put_le16(p + 61, s->nb_sectors >> 16);
|
||||||
|
put_le16(p + 100, s->nb_sectors);
|
||||||
|
put_le16(p + 101, s->nb_sectors >> 16);
|
||||||
|
put_le16(p + 102, s->nb_sectors >> 32);
|
||||||
|
put_le16(p + 103, s->nb_sectors >> 48);
|
||||||
|
}
|
||||||
|
|
||||||
static void ide_identify(IDEState *s)
|
static void ide_identify(IDEState *s)
|
||||||
{
|
{
|
||||||
uint16_t *p;
|
uint16_t *p;
|
||||||
unsigned int oldsize;
|
unsigned int oldsize;
|
||||||
IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master;
|
IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master;
|
||||||
|
|
||||||
|
p = (uint16_t *)s->identify_data;
|
||||||
if (s->identify_set) {
|
if (s->identify_set) {
|
||||||
memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data));
|
goto fill_buffer;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
memset(p, 0, sizeof(s->identify_data));
|
||||||
|
|
||||||
memset(s->io_buffer, 0, 512);
|
|
||||||
p = (uint16_t *)s->io_buffer;
|
|
||||||
put_le16(p + 0, 0x0040);
|
put_le16(p + 0, 0x0040);
|
||||||
put_le16(p + 1, s->cylinders);
|
put_le16(p + 1, s->cylinders);
|
||||||
put_le16(p + 3, s->heads);
|
put_le16(p + 3, s->heads);
|
||||||
|
@ -116,8 +126,8 @@ static void ide_identify(IDEState *s)
|
||||||
put_le16(p + 58, oldsize >> 16);
|
put_le16(p + 58, oldsize >> 16);
|
||||||
if (s->mult_sectors)
|
if (s->mult_sectors)
|
||||||
put_le16(p + 59, 0x100 | s->mult_sectors);
|
put_le16(p + 59, 0x100 | s->mult_sectors);
|
||||||
put_le16(p + 60, s->nb_sectors);
|
/* *(p + 60) := nb_sectors -- see ide_identify_size */
|
||||||
put_le16(p + 61, s->nb_sectors >> 16);
|
/* *(p + 61) := nb_sectors >> 16 -- see ide_identify_size */
|
||||||
put_le16(p + 62, 0x07); /* single word dma0-2 supported */
|
put_le16(p + 62, 0x07); /* single word dma0-2 supported */
|
||||||
put_le16(p + 63, 0x07); /* mdma0-2 supported */
|
put_le16(p + 63, 0x07); /* mdma0-2 supported */
|
||||||
put_le16(p + 64, 0x03); /* pio3-4 supported */
|
put_le16(p + 64, 0x03); /* pio3-4 supported */
|
||||||
|
@ -162,10 +172,10 @@ static void ide_identify(IDEState *s)
|
||||||
}
|
}
|
||||||
put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */
|
put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */
|
||||||
put_le16(p + 93, 1 | (1 << 14) | 0x2000);
|
put_le16(p + 93, 1 | (1 << 14) | 0x2000);
|
||||||
put_le16(p + 100, s->nb_sectors);
|
/* *(p + 100) := nb_sectors -- see ide_identify_size */
|
||||||
put_le16(p + 101, s->nb_sectors >> 16);
|
/* *(p + 101) := nb_sectors >> 16 -- see ide_identify_size */
|
||||||
put_le16(p + 102, s->nb_sectors >> 32);
|
/* *(p + 102) := nb_sectors >> 32 -- see ide_identify_size */
|
||||||
put_le16(p + 103, s->nb_sectors >> 48);
|
/* *(p + 103) := nb_sectors >> 48 -- see ide_identify_size */
|
||||||
|
|
||||||
if (dev && dev->conf.physical_block_size)
|
if (dev && dev->conf.physical_block_size)
|
||||||
put_le16(p + 106, 0x6000 | get_physical_block_exp(&dev->conf));
|
put_le16(p + 106, 0x6000 | get_physical_block_exp(&dev->conf));
|
||||||
|
@ -180,21 +190,23 @@ static void ide_identify(IDEState *s)
|
||||||
put_le16(p + 169, 1); /* TRIM support */
|
put_le16(p + 169, 1); /* TRIM support */
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(s->identify_data, p, sizeof(s->identify_data));
|
ide_identify_size(s);
|
||||||
s->identify_set = 1;
|
s->identify_set = 1;
|
||||||
|
|
||||||
|
fill_buffer:
|
||||||
|
memcpy(s->io_buffer, p, sizeof(s->identify_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ide_atapi_identify(IDEState *s)
|
static void ide_atapi_identify(IDEState *s)
|
||||||
{
|
{
|
||||||
uint16_t *p;
|
uint16_t *p;
|
||||||
|
|
||||||
|
p = (uint16_t *)s->identify_data;
|
||||||
if (s->identify_set) {
|
if (s->identify_set) {
|
||||||
memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data));
|
goto fill_buffer;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
memset(p, 0, sizeof(s->identify_data));
|
||||||
|
|
||||||
memset(s->io_buffer, 0, 512);
|
|
||||||
p = (uint16_t *)s->io_buffer;
|
|
||||||
/* Removable CDROM, 50us response, 12 byte packets */
|
/* Removable CDROM, 50us response, 12 byte packets */
|
||||||
put_le16(p + 0, (2 << 14) | (5 << 8) | (1 << 7) | (2 << 5) | (0 << 0));
|
put_le16(p + 0, (2 << 14) | (5 << 8) | (1 << 7) | (2 << 5) | (0 << 0));
|
||||||
padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
|
padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
|
||||||
|
@ -230,11 +242,36 @@ static void ide_atapi_identify(IDEState *s)
|
||||||
}
|
}
|
||||||
|
|
||||||
put_le16(p + 80, 0x1e); /* support up to ATA/ATAPI-4 */
|
put_le16(p + 80, 0x1e); /* support up to ATA/ATAPI-4 */
|
||||||
|
if (s->wwn) {
|
||||||
|
put_le16(p + 84, (1 << 8)); /* supports WWN for words 108-111 */
|
||||||
|
put_le16(p + 87, (1 << 8)); /* WWN enabled */
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_DMA_CDROM
|
#ifdef USE_DMA_CDROM
|
||||||
put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */
|
put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */
|
||||||
#endif
|
#endif
|
||||||
memcpy(s->identify_data, p, sizeof(s->identify_data));
|
|
||||||
|
if (s->wwn) {
|
||||||
|
/* LE 16-bit words 111-108 contain 64-bit World Wide Name */
|
||||||
|
put_le16(p + 108, s->wwn >> 48);
|
||||||
|
put_le16(p + 109, s->wwn >> 32);
|
||||||
|
put_le16(p + 110, s->wwn >> 16);
|
||||||
|
put_le16(p + 111, s->wwn);
|
||||||
|
}
|
||||||
|
|
||||||
s->identify_set = 1;
|
s->identify_set = 1;
|
||||||
|
|
||||||
|
fill_buffer:
|
||||||
|
memcpy(s->io_buffer, p, sizeof(s->identify_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ide_cfata_identify_size(IDEState *s)
|
||||||
|
{
|
||||||
|
uint16_t *p = (uint16_t *)s->identify_data;
|
||||||
|
put_le16(p + 7, s->nb_sectors >> 16); /* Sectors per card */
|
||||||
|
put_le16(p + 8, s->nb_sectors); /* Sectors per card */
|
||||||
|
put_le16(p + 60, s->nb_sectors); /* Total LBA sectors */
|
||||||
|
put_le16(p + 61, s->nb_sectors >> 16); /* Total LBA sectors */
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ide_cfata_identify(IDEState *s)
|
static void ide_cfata_identify(IDEState *s)
|
||||||
|
@ -243,9 +280,9 @@ static void ide_cfata_identify(IDEState *s)
|
||||||
uint32_t cur_sec;
|
uint32_t cur_sec;
|
||||||
|
|
||||||
p = (uint16_t *)s->identify_data;
|
p = (uint16_t *)s->identify_data;
|
||||||
if (s->identify_set)
|
if (s->identify_set) {
|
||||||
goto fill_buffer;
|
goto fill_buffer;
|
||||||
|
}
|
||||||
memset(p, 0, sizeof(s->identify_data));
|
memset(p, 0, sizeof(s->identify_data));
|
||||||
|
|
||||||
cur_sec = s->cylinders * s->heads * s->sectors;
|
cur_sec = s->cylinders * s->heads * s->sectors;
|
||||||
|
@ -254,8 +291,8 @@ static void ide_cfata_identify(IDEState *s)
|
||||||
put_le16(p + 1, s->cylinders); /* Default cylinders */
|
put_le16(p + 1, s->cylinders); /* Default cylinders */
|
||||||
put_le16(p + 3, s->heads); /* Default heads */
|
put_le16(p + 3, s->heads); /* Default heads */
|
||||||
put_le16(p + 6, s->sectors); /* Default sectors per track */
|
put_le16(p + 6, s->sectors); /* Default sectors per track */
|
||||||
put_le16(p + 7, s->nb_sectors >> 16); /* Sectors per card */
|
/* *(p + 7) := nb_sectors >> 16 -- see ide_cfata_identify_size */
|
||||||
put_le16(p + 8, s->nb_sectors); /* Sectors per card */
|
/* *(p + 8) := nb_sectors -- see ide_cfata_identify_size */
|
||||||
padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
|
padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
|
||||||
put_le16(p + 22, 0x0004); /* ECC bytes */
|
put_le16(p + 22, 0x0004); /* ECC bytes */
|
||||||
padstr((char *) (p + 23), s->version, 8); /* Firmware Revision */
|
padstr((char *) (p + 23), s->version, 8); /* Firmware Revision */
|
||||||
|
@ -276,8 +313,8 @@ static void ide_cfata_identify(IDEState *s)
|
||||||
put_le16(p + 58, cur_sec >> 16); /* Current capacity */
|
put_le16(p + 58, cur_sec >> 16); /* Current capacity */
|
||||||
if (s->mult_sectors) /* Multiple sector setting */
|
if (s->mult_sectors) /* Multiple sector setting */
|
||||||
put_le16(p + 59, 0x100 | s->mult_sectors);
|
put_le16(p + 59, 0x100 | s->mult_sectors);
|
||||||
put_le16(p + 60, s->nb_sectors); /* Total LBA sectors */
|
/* *(p + 60) := nb_sectors -- see ide_cfata_identify_size */
|
||||||
put_le16(p + 61, s->nb_sectors >> 16); /* Total LBA sectors */
|
/* *(p + 61) := nb_sectors >> 16 -- see ide_cfata_identify_size */
|
||||||
put_le16(p + 63, 0x0203); /* Multiword DMA capability */
|
put_le16(p + 63, 0x0203); /* Multiword DMA capability */
|
||||||
put_le16(p + 64, 0x0001); /* Flow Control PIO support */
|
put_le16(p + 64, 0x0001); /* Flow Control PIO support */
|
||||||
put_le16(p + 65, 0x0096); /* Min. Multiword DMA cycle */
|
put_le16(p + 65, 0x0096); /* Min. Multiword DMA cycle */
|
||||||
|
@ -297,6 +334,7 @@ static void ide_cfata_identify(IDEState *s)
|
||||||
put_le16(p + 160, 0x8100); /* Power requirement */
|
put_le16(p + 160, 0x8100); /* Power requirement */
|
||||||
put_le16(p + 161, 0x8001); /* CF command set */
|
put_le16(p + 161, 0x8001); /* CF command set */
|
||||||
|
|
||||||
|
ide_cfata_identify_size(s);
|
||||||
s->identify_set = 1;
|
s->identify_set = 1;
|
||||||
|
|
||||||
fill_buffer:
|
fill_buffer:
|
||||||
|
@ -2115,6 +2153,28 @@ static bool ide_cd_is_medium_locked(void *opaque)
|
||||||
return ((IDEState *)opaque)->tray_locked;
|
return ((IDEState *)opaque)->tray_locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ide_resize_cb(void *opaque)
|
||||||
|
{
|
||||||
|
IDEState *s = opaque;
|
||||||
|
uint64_t nb_sectors;
|
||||||
|
|
||||||
|
if (!s->identify_set) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bdrv_get_geometry(s->bs, &nb_sectors);
|
||||||
|
s->nb_sectors = nb_sectors;
|
||||||
|
|
||||||
|
/* Update the identify data buffer. */
|
||||||
|
if (s->drive_kind == IDE_CFATA) {
|
||||||
|
ide_cfata_identify_size(s);
|
||||||
|
} else {
|
||||||
|
/* IDE_CD uses a different set of callbacks entirely. */
|
||||||
|
assert(s->drive_kind != IDE_CD);
|
||||||
|
ide_identify_size(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static const BlockDevOps ide_cd_block_ops = {
|
static const BlockDevOps ide_cd_block_ops = {
|
||||||
.change_media_cb = ide_cd_change_cb,
|
.change_media_cb = ide_cd_change_cb,
|
||||||
.eject_request_cb = ide_cd_eject_request_cb,
|
.eject_request_cb = ide_cd_eject_request_cb,
|
||||||
|
@ -2122,6 +2182,10 @@ static const BlockDevOps ide_cd_block_ops = {
|
||||||
.is_medium_locked = ide_cd_is_medium_locked,
|
.is_medium_locked = ide_cd_is_medium_locked,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const BlockDevOps ide_hd_block_ops = {
|
||||||
|
.resize_cb = ide_resize_cb,
|
||||||
|
};
|
||||||
|
|
||||||
int ide_init_drive(IDEState *s, BlockDriverState *bs, IDEDriveKind kind,
|
int ide_init_drive(IDEState *s, BlockDriverState *bs, IDEDriveKind kind,
|
||||||
const char *version, const char *serial, const char *model,
|
const char *version, const char *serial, const char *model,
|
||||||
uint64_t wwn,
|
uint64_t wwn,
|
||||||
|
@ -2158,6 +2222,7 @@ int ide_init_drive(IDEState *s, BlockDriverState *bs, IDEDriveKind kind,
|
||||||
error_report("Can't use a read-only drive");
|
error_report("Can't use a read-only drive");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
bdrv_set_dev_ops(bs, &ide_hd_block_ops, s);
|
||||||
}
|
}
|
||||||
if (serial) {
|
if (serial) {
|
||||||
pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), serial);
|
pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), serial);
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* QAPI util functions
|
||||||
|
*
|
||||||
|
* Copyright Fujitsu, Inc. 2014
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
||||||
|
* See the COPYING.LIB file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef QAPI_UTIL_H
|
||||||
|
#define QAPI_UTIL_H
|
||||||
|
|
||||||
|
int qapi_enum_parse(const char *lookup[], const char *buf,
|
||||||
|
int max, int def, Error **errp);
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,6 +1,6 @@
|
||||||
util-obj-y = qapi-visit-core.o qapi-dealloc-visitor.o qmp-input-visitor.o
|
util-obj-y = qapi-visit-core.o qapi-dealloc-visitor.o qmp-input-visitor.o
|
||||||
util-obj-y += qmp-output-visitor.o qmp-registry.o qmp-dispatch.o
|
util-obj-y += qmp-output-visitor.o qmp-registry.o qmp-dispatch.o
|
||||||
util-obj-y += string-input-visitor.o string-output-visitor.o
|
util-obj-y += string-input-visitor.o string-output-visitor.o
|
||||||
|
|
||||||
util-obj-y += opts-visitor.o
|
util-obj-y += opts-visitor.o
|
||||||
util-obj-y += qmp-event.o
|
util-obj-y += qmp-event.o
|
||||||
|
util-obj-y += qapi-util.o
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* QAPI util functions
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Hu Tao <hutao@cn.fujitsu.com>
|
||||||
|
* Peter Lieven <pl@kamp.de>
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
||||||
|
* See the COPYING.LIB file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "qapi/error.h"
|
||||||
|
#include "qapi/util.h"
|
||||||
|
|
||||||
|
int qapi_enum_parse(const char *lookup[], const char *buf,
|
||||||
|
int max, int def, Error **errp)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < max; i++) {
|
||||||
|
if (!strcmp(buf, lookup[i])) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_setg(errp, "invalid parameter value: %s", buf);
|
||||||
|
return def;
|
||||||
|
}
|
|
@ -95,7 +95,8 @@ static void QEMU_NORETURN help(void)
|
||||||
" 'cache' is the cache mode used to write the output disk image, the valid\n"
|
" 'cache' is the cache mode used to write the output disk image, the valid\n"
|
||||||
" options are: 'none', 'writeback' (default, except for convert), 'writethrough',\n"
|
" options are: 'none', 'writeback' (default, except for convert), 'writethrough',\n"
|
||||||
" 'directsync' and 'unsafe' (default for convert)\n"
|
" 'directsync' and 'unsafe' (default for convert)\n"
|
||||||
" 'src_cache' in contrast is the cache mode used to read input disk images\n"
|
" 'src_cache' is the cache mode used to read input disk images, the valid\n"
|
||||||
|
" options are the same as for the 'cache' option\n"
|
||||||
" 'size' is the disk image size in bytes. Optional suffixes\n"
|
" 'size' is the disk image size in bytes. Optional suffixes\n"
|
||||||
" 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G' (gigabyte, 1024M),\n"
|
" 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G' (gigabyte, 1024M),\n"
|
||||||
" 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E' (exabyte, 1024P) are\n"
|
" 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E' (exabyte, 1024P) are\n"
|
||||||
|
|
|
@ -73,8 +73,9 @@ specifies the cache mode that should be used with the (destination) file. See
|
||||||
the documentation of the emulator's @code{-drive cache=...} option for allowed
|
the documentation of the emulator's @code{-drive cache=...} option for allowed
|
||||||
values.
|
values.
|
||||||
@item -T @var{src_cache}
|
@item -T @var{src_cache}
|
||||||
in contrast specifies the cache mode that should be used with the source
|
specifies the cache mode that should be used with the source file(s). See
|
||||||
file(s).
|
the documentation of the emulator's @code{-drive cache=...} option for allowed
|
||||||
|
values.
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
Parameters to snapshot subcommand:
|
Parameters to snapshot subcommand:
|
||||||
|
@ -340,7 +341,7 @@ string), then the image is rebased onto no backing file (i.e. it will exist
|
||||||
independently of any backing file).
|
independently of any backing file).
|
||||||
|
|
||||||
@var{cache} specifies the cache mode to be used for @var{filename}, whereas
|
@var{cache} specifies the cache mode to be used for @var{filename}, whereas
|
||||||
@var{src_cache} specifies the cache mode for reading the new backing file.
|
@var{src_cache} specifies the cache mode for reading backing files.
|
||||||
|
|
||||||
There are two different modes in which @code{rebase} can operate:
|
There are two different modes in which @code{rebase} can operate:
|
||||||
@table @option
|
@table @option
|
||||||
|
|
34
qemu-nbd.c
34
qemu-nbd.c
|
@ -18,11 +18,13 @@
|
||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/block.h"
|
#include "block/block.h"
|
||||||
|
#include "block/block_int.h"
|
||||||
#include "block/nbd.h"
|
#include "block/nbd.h"
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
#include "qemu/sockets.h"
|
#include "qemu/sockets.h"
|
||||||
#include "qemu/error-report.h"
|
#include "qemu/error-report.h"
|
||||||
#include "block/snapshot.h"
|
#include "block/snapshot.h"
|
||||||
|
#include "qapi/util.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -41,6 +43,7 @@
|
||||||
#define QEMU_NBD_OPT_CACHE 1
|
#define QEMU_NBD_OPT_CACHE 1
|
||||||
#define QEMU_NBD_OPT_AIO 2
|
#define QEMU_NBD_OPT_AIO 2
|
||||||
#define QEMU_NBD_OPT_DISCARD 3
|
#define QEMU_NBD_OPT_DISCARD 3
|
||||||
|
#define QEMU_NBD_OPT_DETECT_ZEROES 4
|
||||||
|
|
||||||
static NBDExport *exp;
|
static NBDExport *exp;
|
||||||
static int verbose;
|
static int verbose;
|
||||||
|
@ -96,6 +99,8 @@ static void usage(const char *name)
|
||||||
#ifdef CONFIG_LINUX_AIO
|
#ifdef CONFIG_LINUX_AIO
|
||||||
" --aio=MODE set AIO mode (native or threads)\n"
|
" --aio=MODE set AIO mode (native or threads)\n"
|
||||||
#endif
|
#endif
|
||||||
|
" --discard=MODE set discard mode (ignore, unmap)\n"
|
||||||
|
" --detect-zeroes=MODE set detect-zeroes mode (off, on, discard)\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Report bugs to <qemu-devel@nongnu.org>\n"
|
"Report bugs to <qemu-devel@nongnu.org>\n"
|
||||||
, name, NBD_DEFAULT_PORT, "DEVICE");
|
, name, NBD_DEFAULT_PORT, "DEVICE");
|
||||||
|
@ -410,6 +415,7 @@ int main(int argc, char **argv)
|
||||||
{ "aio", 1, NULL, QEMU_NBD_OPT_AIO },
|
{ "aio", 1, NULL, QEMU_NBD_OPT_AIO },
|
||||||
#endif
|
#endif
|
||||||
{ "discard", 1, NULL, QEMU_NBD_OPT_DISCARD },
|
{ "discard", 1, NULL, QEMU_NBD_OPT_DISCARD },
|
||||||
|
{ "detect-zeroes", 1, NULL, QEMU_NBD_OPT_DETECT_ZEROES },
|
||||||
{ "shared", 1, NULL, 'e' },
|
{ "shared", 1, NULL, 'e' },
|
||||||
{ "format", 1, NULL, 'f' },
|
{ "format", 1, NULL, 'f' },
|
||||||
{ "persistent", 0, NULL, 't' },
|
{ "persistent", 0, NULL, 't' },
|
||||||
|
@ -432,6 +438,7 @@ int main(int argc, char **argv)
|
||||||
pthread_t client_thread;
|
pthread_t client_thread;
|
||||||
const char *fmt = NULL;
|
const char *fmt = NULL;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
|
||||||
|
|
||||||
/* The client thread uses SIGTERM to interrupt the server. A signal
|
/* The client thread uses SIGTERM to interrupt the server. A signal
|
||||||
* handler ensures that "qemu-nbd -v -c" exits with a nice status code.
|
* handler ensures that "qemu-nbd -v -c" exits with a nice status code.
|
||||||
|
@ -483,6 +490,23 @@ int main(int argc, char **argv)
|
||||||
errx(EXIT_FAILURE, "Invalid discard mode `%s'", optarg);
|
errx(EXIT_FAILURE, "Invalid discard mode `%s'", optarg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case QEMU_NBD_OPT_DETECT_ZEROES:
|
||||||
|
detect_zeroes =
|
||||||
|
qapi_enum_parse(BlockdevDetectZeroesOptions_lookup,
|
||||||
|
optarg,
|
||||||
|
BLOCKDEV_DETECT_ZEROES_OPTIONS_MAX,
|
||||||
|
BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF,
|
||||||
|
&local_err);
|
||||||
|
if (local_err) {
|
||||||
|
errx(EXIT_FAILURE, "Failed to parse detect_zeroes mode: %s",
|
||||||
|
error_get_pretty(local_err));
|
||||||
|
}
|
||||||
|
if (detect_zeroes == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP &&
|
||||||
|
!(flags & BDRV_O_UNMAP)) {
|
||||||
|
errx(EXIT_FAILURE, "setting detect-zeroes to unmap is not allowed "
|
||||||
|
"without setting discard operation to unmap");
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
bindto = optarg;
|
bindto = optarg;
|
||||||
break;
|
break;
|
||||||
|
@ -522,15 +546,18 @@ int main(int argc, char **argv)
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
partition = strtol(optarg, &end, 0);
|
partition = strtol(optarg, &end, 0);
|
||||||
if (*end)
|
if (*end) {
|
||||||
errx(EXIT_FAILURE, "Invalid partition `%s'", optarg);
|
errx(EXIT_FAILURE, "Invalid partition `%s'", optarg);
|
||||||
if (partition < 1 || partition > 8)
|
}
|
||||||
|
if (partition < 1 || partition > 8) {
|
||||||
errx(EXIT_FAILURE, "Invalid partition %d", partition);
|
errx(EXIT_FAILURE, "Invalid partition %d", partition);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'k':
|
case 'k':
|
||||||
sockpath = optarg;
|
sockpath = optarg;
|
||||||
if (sockpath[0] != '/')
|
if (sockpath[0] != '/') {
|
||||||
errx(EXIT_FAILURE, "socket path must be absolute\n");
|
errx(EXIT_FAILURE, "socket path must be absolute\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
disconnect = true;
|
disconnect = true;
|
||||||
|
@ -686,6 +713,7 @@ int main(int argc, char **argv)
|
||||||
error_get_pretty(local_err));
|
error_get_pretty(local_err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bs->detect_zeroes = detect_zeroes;
|
||||||
fd_size = bdrv_getlength(bs);
|
fd_size = bdrv_getlength(bs);
|
||||||
|
|
||||||
if (partition != -1) {
|
if (partition != -1) {
|
||||||
|
|
|
@ -299,6 +299,7 @@ libqos-obj-y += tests/libqos/i2c.o
|
||||||
libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o
|
libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o
|
||||||
libqos-pc-obj-y += tests/libqos/malloc-pc.o
|
libqos-pc-obj-y += tests/libqos/malloc-pc.o
|
||||||
libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o
|
libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o
|
||||||
|
libqos-virtio-obj-y = $(libqos-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio.o tests/libqos/virtio-pci.o
|
||||||
|
|
||||||
tests/rtc-test$(EXESUF): tests/rtc-test.o
|
tests/rtc-test$(EXESUF): tests/rtc-test.o
|
||||||
tests/m48t59-test$(EXESUF): tests/m48t59-test.o
|
tests/m48t59-test$(EXESUF): tests/m48t59-test.o
|
||||||
|
@ -320,7 +321,7 @@ tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o
|
||||||
tests/ne2000-test$(EXESUF): tests/ne2000-test.o
|
tests/ne2000-test$(EXESUF): tests/ne2000-test.o
|
||||||
tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o
|
tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o
|
||||||
tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o
|
tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o
|
||||||
tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o
|
tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y)
|
||||||
tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o
|
tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o
|
||||||
tests/virtio-rng-test$(EXESUF): tests/virtio-rng-test.o
|
tests/virtio-rng-test$(EXESUF): tests/virtio-rng-test.o
|
||||||
tests/virtio-scsi-test$(EXESUF): tests/virtio-scsi-test.o
|
tests/virtio-scsi-test$(EXESUF): tests/virtio-scsi-test.o
|
||||||
|
|
|
@ -126,6 +126,8 @@ static void ide_test_start(const char *cmdline_fmt, ...)
|
||||||
|
|
||||||
static void ide_test_quit(void)
|
static void ide_test_quit(void)
|
||||||
{
|
{
|
||||||
|
pc_alloc_uninit(guest_malloc);
|
||||||
|
guest_malloc = NULL;
|
||||||
qtest_end();
|
qtest_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,45 +17,294 @@
|
||||||
#include "hw/nvram/fw_cfg.h"
|
#include "hw/nvram/fw_cfg.h"
|
||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
|
#include "qemu/queue.h"
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#define PAGE_SIZE (4096)
|
#define PAGE_SIZE (4096)
|
||||||
|
|
||||||
|
#define MLIST_ENTNAME entries
|
||||||
|
typedef QTAILQ_HEAD(MemList, MemBlock) MemList;
|
||||||
|
typedef struct MemBlock {
|
||||||
|
QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t addr;
|
||||||
|
} MemBlock;
|
||||||
|
|
||||||
typedef struct PCAlloc
|
typedef struct PCAlloc
|
||||||
{
|
{
|
||||||
QGuestAllocator alloc;
|
QGuestAllocator alloc;
|
||||||
|
PCAllocOpts opts;
|
||||||
uint64_t start;
|
uint64_t start;
|
||||||
uint64_t end;
|
uint64_t end;
|
||||||
|
|
||||||
|
MemList used;
|
||||||
|
MemList free;
|
||||||
} PCAlloc;
|
} PCAlloc;
|
||||||
|
|
||||||
|
static MemBlock *mlist_new(uint64_t addr, uint64_t size)
|
||||||
|
{
|
||||||
|
MemBlock *block;
|
||||||
|
|
||||||
|
if (!size) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
block = g_malloc0(sizeof(MemBlock));
|
||||||
|
|
||||||
|
block->addr = addr;
|
||||||
|
block->size = size;
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mlist_delete(MemList *list, MemBlock *node)
|
||||||
|
{
|
||||||
|
g_assert(list && node);
|
||||||
|
QTAILQ_REMOVE(list, node, MLIST_ENTNAME);
|
||||||
|
g_free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemBlock *mlist_find_key(MemList *head, uint64_t addr)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
||||||
|
if (node->addr == addr) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemBlock *mlist_find_space(MemList *head, uint64_t size)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
||||||
|
if (node->size >= size) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
g_assert(head && insr);
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
||||||
|
if (insr->addr < node->addr) {
|
||||||
|
QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME);
|
||||||
|
return insr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME);
|
||||||
|
return insr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t mlist_boundary(MemBlock *node)
|
||||||
|
{
|
||||||
|
return node->size + node->addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right)
|
||||||
|
{
|
||||||
|
g_assert(head && left && right);
|
||||||
|
|
||||||
|
left->size += right->size;
|
||||||
|
mlist_delete(head, right);
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mlist_coalesce(MemList *head, MemBlock *node)
|
||||||
|
{
|
||||||
|
g_assert(node);
|
||||||
|
MemBlock *left;
|
||||||
|
MemBlock *right;
|
||||||
|
char merge;
|
||||||
|
|
||||||
|
do {
|
||||||
|
merge = 0;
|
||||||
|
left = QTAILQ_PREV(node, MemList, MLIST_ENTNAME);
|
||||||
|
right = QTAILQ_NEXT(node, MLIST_ENTNAME);
|
||||||
|
|
||||||
|
/* clowns to the left of me */
|
||||||
|
if (left && mlist_boundary(left) == node->addr) {
|
||||||
|
node = mlist_join(head, left, node);
|
||||||
|
merge = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* jokers to the right */
|
||||||
|
if (right && mlist_boundary(node) == right->addr) {
|
||||||
|
node = mlist_join(head, node, right);
|
||||||
|
merge = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (merge);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t pc_mlist_fulfill(PCAlloc *s, MemBlock *freenode, uint64_t size)
|
||||||
|
{
|
||||||
|
uint64_t addr;
|
||||||
|
MemBlock *usednode;
|
||||||
|
|
||||||
|
g_assert(freenode);
|
||||||
|
g_assert_cmpint(freenode->size, >=, size);
|
||||||
|
|
||||||
|
addr = freenode->addr;
|
||||||
|
if (freenode->size == size) {
|
||||||
|
/* re-use this freenode as our used node */
|
||||||
|
QTAILQ_REMOVE(&s->free, freenode, MLIST_ENTNAME);
|
||||||
|
usednode = freenode;
|
||||||
|
} else {
|
||||||
|
/* adjust the free node and create a new used node */
|
||||||
|
freenode->addr += size;
|
||||||
|
freenode->size -= size;
|
||||||
|
usednode = mlist_new(addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
mlist_sort_insert(&s->used, usednode);
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To assert the correctness of the list.
|
||||||
|
* Used only if PC_ALLOC_PARANOID is set. */
|
||||||
|
static void pc_mlist_check(PCAlloc *s)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
uint64_t addr = s->start > 0 ? s->start - 1 : 0;
|
||||||
|
uint64_t next = s->start;
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(node, &s->free, MLIST_ENTNAME) {
|
||||||
|
g_assert_cmpint(node->addr, >, addr);
|
||||||
|
g_assert_cmpint(node->addr, >=, next);
|
||||||
|
addr = node->addr;
|
||||||
|
next = node->addr + node->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = s->start > 0 ? s->start - 1 : 0;
|
||||||
|
next = s->start;
|
||||||
|
QTAILQ_FOREACH(node, &s->used, MLIST_ENTNAME) {
|
||||||
|
g_assert_cmpint(node->addr, >, addr);
|
||||||
|
g_assert_cmpint(node->addr, >=, next);
|
||||||
|
addr = node->addr;
|
||||||
|
next = node->addr + node->size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t pc_mlist_alloc(PCAlloc *s, uint64_t size)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
|
||||||
|
node = mlist_find_space(&s->free, size);
|
||||||
|
if (!node) {
|
||||||
|
fprintf(stderr, "Out of guest memory.\n");
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
return pc_mlist_fulfill(s, node, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pc_mlist_free(PCAlloc *s, uint64_t addr)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
|
||||||
|
if (addr == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = mlist_find_key(&s->used, addr);
|
||||||
|
if (!node) {
|
||||||
|
fprintf(stderr, "Error: no record found for an allocation at "
|
||||||
|
"0x%016" PRIx64 ".\n",
|
||||||
|
addr);
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rip it out of the used list and re-insert back into the free list. */
|
||||||
|
QTAILQ_REMOVE(&s->used, node, MLIST_ENTNAME);
|
||||||
|
mlist_sort_insert(&s->free, node);
|
||||||
|
mlist_coalesce(&s->free, node);
|
||||||
|
}
|
||||||
|
|
||||||
static uint64_t pc_alloc(QGuestAllocator *allocator, size_t size)
|
static uint64_t pc_alloc(QGuestAllocator *allocator, size_t size)
|
||||||
{
|
{
|
||||||
PCAlloc *s = container_of(allocator, PCAlloc, alloc);
|
PCAlloc *s = container_of(allocator, PCAlloc, alloc);
|
||||||
uint64_t addr;
|
uint64_t rsize = size;
|
||||||
|
uint64_t naddr;
|
||||||
|
|
||||||
|
rsize += (PAGE_SIZE - 1);
|
||||||
|
rsize &= -PAGE_SIZE;
|
||||||
|
g_assert_cmpint((s->start + rsize), <=, s->end);
|
||||||
|
g_assert_cmpint(rsize, >=, size);
|
||||||
|
|
||||||
size += (PAGE_SIZE - 1);
|
naddr = pc_mlist_alloc(s, rsize);
|
||||||
size &= -PAGE_SIZE;
|
if (s->opts & PC_ALLOC_PARANOID) {
|
||||||
|
pc_mlist_check(s);
|
||||||
|
}
|
||||||
|
|
||||||
g_assert_cmpint((s->start + size), <=, s->end);
|
return naddr;
|
||||||
|
|
||||||
addr = s->start;
|
|
||||||
s->start += size;
|
|
||||||
|
|
||||||
return addr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pc_free(QGuestAllocator *allocator, uint64_t addr)
|
static void pc_free(QGuestAllocator *allocator, uint64_t addr)
|
||||||
{
|
{
|
||||||
|
PCAlloc *s = container_of(allocator, PCAlloc, alloc);
|
||||||
|
|
||||||
|
pc_mlist_free(s, addr);
|
||||||
|
if (s->opts & PC_ALLOC_PARANOID) {
|
||||||
|
pc_mlist_check(s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QGuestAllocator *pc_alloc_init(void)
|
/*
|
||||||
|
* Mostly for valgrind happiness, but it does offer
|
||||||
|
* a chokepoint for debugging guest memory leaks, too.
|
||||||
|
*/
|
||||||
|
void pc_alloc_uninit(QGuestAllocator *allocator)
|
||||||
|
{
|
||||||
|
PCAlloc *s = container_of(allocator, PCAlloc, alloc);
|
||||||
|
MemBlock *node;
|
||||||
|
MemBlock *tmp;
|
||||||
|
PCAllocOpts mask;
|
||||||
|
|
||||||
|
/* Check for guest leaks, and destroy the list. */
|
||||||
|
QTAILQ_FOREACH_SAFE(node, &s->used, MLIST_ENTNAME, tmp) {
|
||||||
|
if (s->opts & (PC_ALLOC_LEAK_WARN | PC_ALLOC_LEAK_ASSERT)) {
|
||||||
|
fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; "
|
||||||
|
"size 0x%016" PRIx64 ".\n",
|
||||||
|
node->addr, node->size);
|
||||||
|
}
|
||||||
|
if (s->opts & (PC_ALLOC_LEAK_ASSERT)) {
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
g_free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we have previously asserted that there are no leaks, then there
|
||||||
|
* should be only one node here with a specific address and size. */
|
||||||
|
mask = PC_ALLOC_LEAK_ASSERT | PC_ALLOC_PARANOID;
|
||||||
|
QTAILQ_FOREACH_SAFE(node, &s->free, MLIST_ENTNAME, tmp) {
|
||||||
|
if ((s->opts & mask) == mask) {
|
||||||
|
if ((node->addr != s->start) ||
|
||||||
|
(node->size != s->end - s->start)) {
|
||||||
|
fprintf(stderr, "Free list is corrupted.\n");
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags)
|
||||||
{
|
{
|
||||||
PCAlloc *s = g_malloc0(sizeof(*s));
|
PCAlloc *s = g_malloc0(sizeof(*s));
|
||||||
uint64_t ram_size;
|
uint64_t ram_size;
|
||||||
QFWCFG *fw_cfg = pc_fw_cfg_init();
|
QFWCFG *fw_cfg = pc_fw_cfg_init();
|
||||||
|
MemBlock *node;
|
||||||
|
|
||||||
|
s->opts = flags;
|
||||||
s->alloc.alloc = pc_alloc;
|
s->alloc.alloc = pc_alloc;
|
||||||
s->alloc.free = pc_free;
|
s->alloc.free = pc_free;
|
||||||
|
|
||||||
|
@ -70,5 +319,16 @@ QGuestAllocator *pc_alloc_init(void)
|
||||||
/* clean-up */
|
/* clean-up */
|
||||||
g_free(fw_cfg);
|
g_free(fw_cfg);
|
||||||
|
|
||||||
|
QTAILQ_INIT(&s->used);
|
||||||
|
QTAILQ_INIT(&s->free);
|
||||||
|
|
||||||
|
node = mlist_new(s->start, s->end - s->start);
|
||||||
|
QTAILQ_INSERT_HEAD(&s->free, node, MLIST_ENTNAME);
|
||||||
|
|
||||||
return &s->alloc;
|
return &s->alloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline QGuestAllocator *pc_alloc_init(void)
|
||||||
|
{
|
||||||
|
return pc_alloc_init_flags(PC_ALLOC_NO_FLAGS);
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,15 @@
|
||||||
|
|
||||||
#include "libqos/malloc.h"
|
#include "libqos/malloc.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PC_ALLOC_NO_FLAGS = 0x00,
|
||||||
|
PC_ALLOC_LEAK_WARN = 0x01,
|
||||||
|
PC_ALLOC_LEAK_ASSERT = 0x02,
|
||||||
|
PC_ALLOC_PARANOID = 0x04
|
||||||
|
} PCAllocOpts;
|
||||||
|
|
||||||
QGuestAllocator *pc_alloc_init(void);
|
QGuestAllocator *pc_alloc_init(void);
|
||||||
|
QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags);
|
||||||
|
void pc_alloc_uninit(QGuestAllocator *allocator);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
#include "hw/pci/pci_regs.h"
|
#include "hw/pci/pci_regs.h"
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
|
void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
|
||||||
void (*func)(QPCIDevice *dev, int devfn, void *data),
|
void (*func)(QPCIDevice *dev, int devfn, void *data),
|
||||||
void *data)
|
void *data)
|
||||||
|
@ -75,6 +73,115 @@ void qpci_device_enable(QPCIDevice *dev)
|
||||||
qpci_config_writew(dev, PCI_COMMAND, cmd);
|
qpci_config_writew(dev, PCI_COMMAND, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id)
|
||||||
|
{
|
||||||
|
uint8_t cap;
|
||||||
|
uint8_t addr = qpci_config_readb(dev, PCI_CAPABILITY_LIST);
|
||||||
|
|
||||||
|
do {
|
||||||
|
cap = qpci_config_readb(dev, addr);
|
||||||
|
if (cap != id) {
|
||||||
|
addr = qpci_config_readb(dev, addr + PCI_CAP_LIST_NEXT);
|
||||||
|
}
|
||||||
|
} while (cap != id && addr != 0);
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qpci_msix_enable(QPCIDevice *dev)
|
||||||
|
{
|
||||||
|
uint8_t addr;
|
||||||
|
uint16_t val;
|
||||||
|
uint32_t table;
|
||||||
|
uint8_t bir_table;
|
||||||
|
uint8_t bir_pba;
|
||||||
|
void *offset;
|
||||||
|
|
||||||
|
addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX);
|
||||||
|
g_assert_cmphex(addr, !=, 0);
|
||||||
|
|
||||||
|
val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS);
|
||||||
|
qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, val | PCI_MSIX_FLAGS_ENABLE);
|
||||||
|
|
||||||
|
table = qpci_config_readl(dev, addr + PCI_MSIX_TABLE);
|
||||||
|
bir_table = table & PCI_MSIX_FLAGS_BIRMASK;
|
||||||
|
offset = qpci_iomap(dev, bir_table, NULL);
|
||||||
|
dev->msix_table = offset + (table & ~PCI_MSIX_FLAGS_BIRMASK);
|
||||||
|
|
||||||
|
table = qpci_config_readl(dev, addr + PCI_MSIX_PBA);
|
||||||
|
bir_pba = table & PCI_MSIX_FLAGS_BIRMASK;
|
||||||
|
if (bir_pba != bir_table) {
|
||||||
|
offset = qpci_iomap(dev, bir_pba, NULL);
|
||||||
|
}
|
||||||
|
dev->msix_pba = offset + (table & ~PCI_MSIX_FLAGS_BIRMASK);
|
||||||
|
|
||||||
|
g_assert(dev->msix_table != NULL);
|
||||||
|
g_assert(dev->msix_pba != NULL);
|
||||||
|
dev->msix_enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qpci_msix_disable(QPCIDevice *dev)
|
||||||
|
{
|
||||||
|
uint8_t addr;
|
||||||
|
uint16_t val;
|
||||||
|
|
||||||
|
g_assert(dev->msix_enabled);
|
||||||
|
addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX);
|
||||||
|
g_assert_cmphex(addr, !=, 0);
|
||||||
|
val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS);
|
||||||
|
qpci_config_writew(dev, addr + PCI_MSIX_FLAGS,
|
||||||
|
val & ~PCI_MSIX_FLAGS_ENABLE);
|
||||||
|
|
||||||
|
qpci_iounmap(dev, dev->msix_table);
|
||||||
|
qpci_iounmap(dev, dev->msix_pba);
|
||||||
|
dev->msix_enabled = 0;
|
||||||
|
dev->msix_table = NULL;
|
||||||
|
dev->msix_pba = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry)
|
||||||
|
{
|
||||||
|
uint32_t pba_entry;
|
||||||
|
uint8_t bit_n = entry % 32;
|
||||||
|
void *addr = dev->msix_pba + (entry / 32) * PCI_MSIX_ENTRY_SIZE / 4;
|
||||||
|
|
||||||
|
g_assert(dev->msix_enabled);
|
||||||
|
pba_entry = qpci_io_readl(dev, addr);
|
||||||
|
qpci_io_writel(dev, addr, pba_entry & ~(1 << bit_n));
|
||||||
|
return (pba_entry & (1 << bit_n)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry)
|
||||||
|
{
|
||||||
|
uint8_t addr;
|
||||||
|
uint16_t val;
|
||||||
|
void *vector_addr = dev->msix_table + (entry * PCI_MSIX_ENTRY_SIZE);
|
||||||
|
|
||||||
|
g_assert(dev->msix_enabled);
|
||||||
|
addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX);
|
||||||
|
g_assert_cmphex(addr, !=, 0);
|
||||||
|
val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS);
|
||||||
|
|
||||||
|
if (val & PCI_MSIX_FLAGS_MASKALL) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return (qpci_io_readl(dev, vector_addr + PCI_MSIX_ENTRY_VECTOR_CTRL)
|
||||||
|
& PCI_MSIX_ENTRY_CTRL_MASKBIT) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t qpci_msix_table_size(QPCIDevice *dev)
|
||||||
|
{
|
||||||
|
uint8_t addr;
|
||||||
|
uint16_t control;
|
||||||
|
|
||||||
|
addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX);
|
||||||
|
g_assert_cmphex(addr, !=, 0);
|
||||||
|
|
||||||
|
control = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS);
|
||||||
|
return (control & PCI_MSIX_FLAGS_QSIZE) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset)
|
uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset)
|
||||||
{
|
{
|
||||||
return dev->bus->config_readb(dev->bus, dev->devfn, offset);
|
return dev->bus->config_readb(dev->bus, dev->devfn, offset);
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#define LIBQOS_PCI_H
|
#define LIBQOS_PCI_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "libqtest.h"
|
||||||
|
|
||||||
#define QPCI_DEVFN(dev, fn) (((dev) << 3) | (fn))
|
#define QPCI_DEVFN(dev, fn) (((dev) << 3) | (fn))
|
||||||
|
|
||||||
|
@ -49,6 +50,9 @@ struct QPCIDevice
|
||||||
{
|
{
|
||||||
QPCIBus *bus;
|
QPCIBus *bus;
|
||||||
int devfn;
|
int devfn;
|
||||||
|
bool msix_enabled;
|
||||||
|
void *msix_table;
|
||||||
|
void *msix_pba;
|
||||||
};
|
};
|
||||||
|
|
||||||
void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
|
void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
|
||||||
|
@ -57,6 +61,12 @@ void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
|
||||||
QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn);
|
QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn);
|
||||||
|
|
||||||
void qpci_device_enable(QPCIDevice *dev);
|
void qpci_device_enable(QPCIDevice *dev);
|
||||||
|
uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id);
|
||||||
|
void qpci_msix_enable(QPCIDevice *dev);
|
||||||
|
void qpci_msix_disable(QPCIDevice *dev);
|
||||||
|
bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry);
|
||||||
|
bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry);
|
||||||
|
uint16_t qpci_msix_table_size(QPCIDevice *dev);
|
||||||
|
|
||||||
uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset);
|
uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset);
|
||||||
uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset);
|
uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset);
|
||||||
|
|
|
@ -0,0 +1,343 @@
|
||||||
|
/*
|
||||||
|
* libqos virtio PCI driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Marc Marí
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "libqos/virtio.h"
|
||||||
|
#include "libqos/virtio-pci.h"
|
||||||
|
#include "libqos/pci.h"
|
||||||
|
#include "libqos/pci-pc.h"
|
||||||
|
#include "libqos/malloc.h"
|
||||||
|
#include "libqos/malloc-pc.h"
|
||||||
|
|
||||||
|
#include "hw/pci/pci_regs.h"
|
||||||
|
|
||||||
|
typedef struct QVirtioPCIForeachData {
|
||||||
|
void (*func)(QVirtioDevice *d, void *data);
|
||||||
|
uint16_t device_type;
|
||||||
|
void *user_data;
|
||||||
|
} QVirtioPCIForeachData;
|
||||||
|
|
||||||
|
static QVirtioPCIDevice *qpcidevice_to_qvirtiodevice(QPCIDevice *pdev)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *vpcidev;
|
||||||
|
vpcidev = g_malloc0(sizeof(*vpcidev));
|
||||||
|
|
||||||
|
if (pdev) {
|
||||||
|
vpcidev->pdev = pdev;
|
||||||
|
vpcidev->vdev.device_type =
|
||||||
|
qpci_config_readw(vpcidev->pdev, PCI_SUBSYSTEM_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
vpcidev->config_msix_entry = -1;
|
||||||
|
|
||||||
|
return vpcidev;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qvirtio_pci_foreach_callback(
|
||||||
|
QPCIDevice *dev, int devfn, void *data)
|
||||||
|
{
|
||||||
|
QVirtioPCIForeachData *d = data;
|
||||||
|
QVirtioPCIDevice *vpcidev = qpcidevice_to_qvirtiodevice(dev);
|
||||||
|
|
||||||
|
if (vpcidev->vdev.device_type == d->device_type) {
|
||||||
|
d->func(&vpcidev->vdev, d->user_data);
|
||||||
|
} else {
|
||||||
|
g_free(vpcidev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qvirtio_pci_assign_device(QVirtioDevice *d, void *data)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice **vpcidev = data;
|
||||||
|
*vpcidev = (QVirtioPCIDevice *)d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t qvirtio_pci_config_readb(QVirtioDevice *d, void *addr)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
return qpci_io_readb(dev->pdev, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t qvirtio_pci_config_readw(QVirtioDevice *d, void *addr)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
return qpci_io_readw(dev->pdev, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t qvirtio_pci_config_readl(QVirtioDevice *d, void *addr)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
return qpci_io_readl(dev->pdev, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, void *addr)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
int i;
|
||||||
|
uint64_t u64 = 0;
|
||||||
|
|
||||||
|
if (qtest_big_endian()) {
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
u64 |= (uint64_t)qpci_io_readb(dev->pdev, addr + i) << (7 - i) * 8;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
u64 |= (uint64_t)qpci_io_readb(dev->pdev, addr + i) << i * 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t qvirtio_pci_get_features(QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
return qpci_io_readl(dev->pdev, dev->addr + QVIRTIO_DEVICE_FEATURES);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qvirtio_pci_set_features(QVirtioDevice *d, uint32_t features)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
qpci_io_writel(dev->pdev, dev->addr + QVIRTIO_GUEST_FEATURES, features);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t qvirtio_pci_get_guest_features(QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
return qpci_io_readl(dev->pdev, dev->addr + QVIRTIO_GUEST_FEATURES);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t qvirtio_pci_get_status(QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_DEVICE_STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qvirtio_pci_set_status(QVirtioDevice *d, uint8_t status)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
qpci_io_writeb(dev->pdev, dev->addr + QVIRTIO_DEVICE_STATUS, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool qvirtio_pci_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
QVirtQueuePCI *vqpci = (QVirtQueuePCI *)vq;
|
||||||
|
uint32_t data;
|
||||||
|
|
||||||
|
if (dev->pdev->msix_enabled) {
|
||||||
|
g_assert_cmpint(vqpci->msix_entry, !=, -1);
|
||||||
|
if (qpci_msix_masked(dev->pdev, vqpci->msix_entry)) {
|
||||||
|
/* No ISR checking should be done if masked, but read anyway */
|
||||||
|
return qpci_msix_pending(dev->pdev, vqpci->msix_entry);
|
||||||
|
} else {
|
||||||
|
data = readl(vqpci->msix_addr);
|
||||||
|
writel(vqpci->msix_addr, 0);
|
||||||
|
return data == vqpci->msix_data;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_ISR_STATUS) & 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
uint32_t data;
|
||||||
|
|
||||||
|
if (dev->pdev->msix_enabled) {
|
||||||
|
g_assert_cmpint(dev->config_msix_entry, !=, -1);
|
||||||
|
if (qpci_msix_masked(dev->pdev, dev->config_msix_entry)) {
|
||||||
|
/* No ISR checking should be done if masked, but read anyway */
|
||||||
|
return qpci_msix_pending(dev->pdev, dev->config_msix_entry);
|
||||||
|
} else {
|
||||||
|
data = readl(dev->config_msix_addr);
|
||||||
|
writel(dev->config_msix_addr, 0);
|
||||||
|
return data == dev->config_msix_data;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_ISR_STATUS) & 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
qpci_io_writeb(dev->pdev, dev->addr + QVIRTIO_QUEUE_SELECT, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t qvirtio_pci_get_queue_size(QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
return qpci_io_readw(dev->pdev, dev->addr + QVIRTIO_QUEUE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qvirtio_pci_set_queue_address(QVirtioDevice *d, uint32_t pfn)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
qpci_io_writel(dev->pdev, dev->addr + QVIRTIO_QUEUE_ADDRESS, pfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QVirtQueue *qvirtio_pci_virtqueue_setup(QVirtioDevice *d,
|
||||||
|
QGuestAllocator *alloc, uint16_t index)
|
||||||
|
{
|
||||||
|
uint32_t feat;
|
||||||
|
uint64_t addr;
|
||||||
|
QVirtQueuePCI *vqpci;
|
||||||
|
|
||||||
|
vqpci = g_malloc0(sizeof(*vqpci));
|
||||||
|
feat = qvirtio_pci_get_guest_features(d);
|
||||||
|
|
||||||
|
qvirtio_pci_queue_select(d, index);
|
||||||
|
vqpci->vq.index = index;
|
||||||
|
vqpci->vq.size = qvirtio_pci_get_queue_size(d);
|
||||||
|
vqpci->vq.free_head = 0;
|
||||||
|
vqpci->vq.num_free = vqpci->vq.size;
|
||||||
|
vqpci->vq.align = QVIRTIO_PCI_ALIGN;
|
||||||
|
vqpci->vq.indirect = (feat & QVIRTIO_F_RING_INDIRECT_DESC) != 0;
|
||||||
|
vqpci->vq.event = (feat & QVIRTIO_F_RING_EVENT_IDX) != 0;
|
||||||
|
|
||||||
|
vqpci->msix_entry = -1;
|
||||||
|
vqpci->msix_addr = 0;
|
||||||
|
vqpci->msix_data = 0x12345678;
|
||||||
|
|
||||||
|
/* Check different than 0 */
|
||||||
|
g_assert_cmpint(vqpci->vq.size, !=, 0);
|
||||||
|
|
||||||
|
/* Check power of 2 */
|
||||||
|
g_assert_cmpint(vqpci->vq.size & (vqpci->vq.size - 1), ==, 0);
|
||||||
|
|
||||||
|
addr = guest_alloc(alloc, qvring_size(vqpci->vq.size, QVIRTIO_PCI_ALIGN));
|
||||||
|
qvring_init(alloc, &vqpci->vq, addr);
|
||||||
|
qvirtio_pci_set_queue_address(d, vqpci->vq.desc / QVIRTIO_PCI_ALIGN);
|
||||||
|
|
||||||
|
return &vqpci->vq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qvirtio_pci_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
|
||||||
|
qpci_io_writew(dev->pdev, dev->addr + QVIRTIO_QUEUE_NOTIFY, vq->index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVirtioBus qvirtio_pci = {
|
||||||
|
.config_readb = qvirtio_pci_config_readb,
|
||||||
|
.config_readw = qvirtio_pci_config_readw,
|
||||||
|
.config_readl = qvirtio_pci_config_readl,
|
||||||
|
.config_readq = qvirtio_pci_config_readq,
|
||||||
|
.get_features = qvirtio_pci_get_features,
|
||||||
|
.set_features = qvirtio_pci_set_features,
|
||||||
|
.get_guest_features = qvirtio_pci_get_guest_features,
|
||||||
|
.get_status = qvirtio_pci_get_status,
|
||||||
|
.set_status = qvirtio_pci_set_status,
|
||||||
|
.get_queue_isr_status = qvirtio_pci_get_queue_isr_status,
|
||||||
|
.get_config_isr_status = qvirtio_pci_get_config_isr_status,
|
||||||
|
.queue_select = qvirtio_pci_queue_select,
|
||||||
|
.get_queue_size = qvirtio_pci_get_queue_size,
|
||||||
|
.set_queue_address = qvirtio_pci_set_queue_address,
|
||||||
|
.virtqueue_setup = qvirtio_pci_virtqueue_setup,
|
||||||
|
.virtqueue_kick = qvirtio_pci_virtqueue_kick,
|
||||||
|
};
|
||||||
|
|
||||||
|
void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type,
|
||||||
|
void (*func)(QVirtioDevice *d, void *data), void *data)
|
||||||
|
{
|
||||||
|
QVirtioPCIForeachData d = { .func = func,
|
||||||
|
.device_type = device_type,
|
||||||
|
.user_data = data };
|
||||||
|
|
||||||
|
qpci_device_foreach(bus, QVIRTIO_VENDOR_ID, -1,
|
||||||
|
qvirtio_pci_foreach_callback, &d);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev = NULL;
|
||||||
|
qvirtio_pci_foreach(bus, device_type, qvirtio_pci_assign_device, &dev);
|
||||||
|
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtio_pci_device_enable(QVirtioPCIDevice *d)
|
||||||
|
{
|
||||||
|
qpci_device_enable(d->pdev);
|
||||||
|
d->addr = qpci_iomap(d->pdev, 0, NULL);
|
||||||
|
g_assert(d->addr != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtio_pci_device_disable(QVirtioPCIDevice *d)
|
||||||
|
{
|
||||||
|
qpci_iounmap(d->pdev, d->addr);
|
||||||
|
d->addr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci,
|
||||||
|
QGuestAllocator *alloc, uint16_t entry)
|
||||||
|
{
|
||||||
|
uint16_t vector;
|
||||||
|
uint32_t control;
|
||||||
|
void *addr;
|
||||||
|
|
||||||
|
g_assert(d->pdev->msix_enabled);
|
||||||
|
addr = d->pdev->msix_table + (entry * 16);
|
||||||
|
|
||||||
|
g_assert_cmpint(entry, >=, 0);
|
||||||
|
g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev));
|
||||||
|
vqpci->msix_entry = entry;
|
||||||
|
|
||||||
|
vqpci->msix_addr = guest_alloc(alloc, 4);
|
||||||
|
qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_LOWER_ADDR,
|
||||||
|
vqpci->msix_addr & ~0UL);
|
||||||
|
qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_UPPER_ADDR,
|
||||||
|
(vqpci->msix_addr >> 32) & ~0UL);
|
||||||
|
qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_DATA, vqpci->msix_data);
|
||||||
|
|
||||||
|
control = qpci_io_readl(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL);
|
||||||
|
qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL,
|
||||||
|
control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT);
|
||||||
|
|
||||||
|
qvirtio_pci_queue_select(&d->vdev, vqpci->vq.index);
|
||||||
|
qpci_io_writew(d->pdev, d->addr + QVIRTIO_MSIX_QUEUE_VECTOR, entry);
|
||||||
|
vector = qpci_io_readw(d->pdev, d->addr + QVIRTIO_MSIX_QUEUE_VECTOR);
|
||||||
|
g_assert_cmphex(vector, !=, QVIRTIO_MSI_NO_VECTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d,
|
||||||
|
QGuestAllocator *alloc, uint16_t entry)
|
||||||
|
{
|
||||||
|
uint16_t vector;
|
||||||
|
uint32_t control;
|
||||||
|
void *addr;
|
||||||
|
|
||||||
|
g_assert(d->pdev->msix_enabled);
|
||||||
|
addr = d->pdev->msix_table + (entry * 16);
|
||||||
|
|
||||||
|
g_assert_cmpint(entry, >=, 0);
|
||||||
|
g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev));
|
||||||
|
d->config_msix_entry = entry;
|
||||||
|
|
||||||
|
d->config_msix_data = 0x12345678;
|
||||||
|
d->config_msix_addr = guest_alloc(alloc, 4);
|
||||||
|
|
||||||
|
qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_LOWER_ADDR,
|
||||||
|
d->config_msix_addr & ~0UL);
|
||||||
|
qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_UPPER_ADDR,
|
||||||
|
(d->config_msix_addr >> 32) & ~0UL);
|
||||||
|
qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_DATA, d->config_msix_data);
|
||||||
|
|
||||||
|
control = qpci_io_readl(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL);
|
||||||
|
qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL,
|
||||||
|
control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT);
|
||||||
|
|
||||||
|
qpci_io_writew(d->pdev, d->addr + QVIRTIO_MSIX_CONF_VECTOR, entry);
|
||||||
|
vector = qpci_io_readw(d->pdev, d->addr + QVIRTIO_MSIX_CONF_VECTOR);
|
||||||
|
g_assert_cmphex(vector, !=, QVIRTIO_MSI_NO_VECTOR);
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* libqos virtio PCI definitions
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Marc Marí
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIBQOS_VIRTIO_PCI_H
|
||||||
|
#define LIBQOS_VIRTIO_PCI_H
|
||||||
|
|
||||||
|
#include "libqos/virtio.h"
|
||||||
|
#include "libqos/pci.h"
|
||||||
|
|
||||||
|
#define QVIRTIO_DEVICE_FEATURES 0x00
|
||||||
|
#define QVIRTIO_GUEST_FEATURES 0x04
|
||||||
|
#define QVIRTIO_QUEUE_ADDRESS 0x08
|
||||||
|
#define QVIRTIO_QUEUE_SIZE 0x0C
|
||||||
|
#define QVIRTIO_QUEUE_SELECT 0x0E
|
||||||
|
#define QVIRTIO_QUEUE_NOTIFY 0x10
|
||||||
|
#define QVIRTIO_DEVICE_STATUS 0x12
|
||||||
|
#define QVIRTIO_ISR_STATUS 0x13
|
||||||
|
#define QVIRTIO_MSIX_CONF_VECTOR 0x14
|
||||||
|
#define QVIRTIO_MSIX_QUEUE_VECTOR 0x16
|
||||||
|
#define QVIRTIO_DEVICE_SPECIFIC_MSIX 0x18
|
||||||
|
#define QVIRTIO_DEVICE_SPECIFIC_NO_MSIX 0x14
|
||||||
|
|
||||||
|
#define QVIRTIO_PCI_ALIGN 4096
|
||||||
|
|
||||||
|
#define QVIRTIO_MSI_NO_VECTOR 0xFFFF
|
||||||
|
|
||||||
|
typedef struct QVirtioPCIDevice {
|
||||||
|
QVirtioDevice vdev;
|
||||||
|
QPCIDevice *pdev;
|
||||||
|
void *addr;
|
||||||
|
uint16_t config_msix_entry;
|
||||||
|
uint64_t config_msix_addr;
|
||||||
|
uint32_t config_msix_data;
|
||||||
|
} QVirtioPCIDevice;
|
||||||
|
|
||||||
|
typedef struct QVirtQueuePCI {
|
||||||
|
QVirtQueue vq;
|
||||||
|
uint16_t msix_entry;
|
||||||
|
uint64_t msix_addr;
|
||||||
|
uint32_t msix_data;
|
||||||
|
} QVirtQueuePCI;
|
||||||
|
|
||||||
|
extern const QVirtioBus qvirtio_pci;
|
||||||
|
|
||||||
|
void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type,
|
||||||
|
void (*func)(QVirtioDevice *d, void *data), void *data);
|
||||||
|
QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type);
|
||||||
|
void qvirtio_pci_device_enable(QVirtioPCIDevice *d);
|
||||||
|
void qvirtio_pci_device_disable(QVirtioPCIDevice *d);
|
||||||
|
|
||||||
|
void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d,
|
||||||
|
QGuestAllocator *alloc, uint16_t entry);
|
||||||
|
void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci,
|
||||||
|
QGuestAllocator *alloc, uint16_t entry);
|
||||||
|
#endif
|
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
* libqos virtio driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Marc Marí
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "libqos/virtio.h"
|
||||||
|
|
||||||
|
uint8_t qvirtio_config_readb(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
void *addr)
|
||||||
|
{
|
||||||
|
return bus->config_readb(d, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t qvirtio_config_readw(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
void *addr)
|
||||||
|
{
|
||||||
|
return bus->config_readw(d, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t qvirtio_config_readl(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
void *addr)
|
||||||
|
{
|
||||||
|
return bus->config_readl(d, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
void *addr)
|
||||||
|
{
|
||||||
|
return bus->config_readq(d, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t qvirtio_get_features(const QVirtioBus *bus, QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
return bus->get_features(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtio_set_features(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
uint32_t features)
|
||||||
|
{
|
||||||
|
bus->set_features(d, features);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
QGuestAllocator *alloc, uint16_t index)
|
||||||
|
{
|
||||||
|
return bus->virtqueue_setup(d, alloc, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
bus->set_status(d, QVIRTIO_RESET);
|
||||||
|
g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
bus->set_status(d, bus->get_status(d) | QVIRTIO_ACKNOWLEDGE);
|
||||||
|
g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_ACKNOWLEDGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
bus->set_status(d, bus->get_status(d) | QVIRTIO_DRIVER);
|
||||||
|
g_assert_cmphex(bus->get_status(d), ==,
|
||||||
|
QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d)
|
||||||
|
{
|
||||||
|
bus->set_status(d, bus->get_status(d) | QVIRTIO_DRIVER_OK);
|
||||||
|
g_assert_cmphex(bus->get_status(d), ==,
|
||||||
|
QVIRTIO_DRIVER_OK | QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool qvirtio_wait_queue_isr(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
QVirtQueue *vq, uint64_t timeout)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
clock_step(10);
|
||||||
|
if (bus->get_queue_isr_status(d, vq)) {
|
||||||
|
break; /* It has ended */
|
||||||
|
}
|
||||||
|
} while (--timeout);
|
||||||
|
|
||||||
|
return timeout != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool qvirtio_wait_config_isr(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
uint64_t timeout)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
clock_step(10);
|
||||||
|
if (bus->get_config_isr_status(d)) {
|
||||||
|
break; /* It has ended */
|
||||||
|
}
|
||||||
|
} while (--timeout);
|
||||||
|
|
||||||
|
return timeout != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
vq->desc = addr;
|
||||||
|
vq->avail = vq->desc + vq->size*sizeof(QVRingDesc);
|
||||||
|
vq->used = (uint64_t)((vq->avail + sizeof(uint16_t) * (3 + vq->size)
|
||||||
|
+ vq->align - 1) & ~(vq->align - 1));
|
||||||
|
|
||||||
|
for (i = 0; i < vq->size - 1; i++) {
|
||||||
|
/* vq->desc[i].addr */
|
||||||
|
writew(vq->desc + (16 * i), 0);
|
||||||
|
/* vq->desc[i].next */
|
||||||
|
writew(vq->desc + (16 * i) + 14, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vq->avail->flags */
|
||||||
|
writew(vq->avail, 0);
|
||||||
|
/* vq->avail->idx */
|
||||||
|
writew(vq->avail + 2, 0);
|
||||||
|
/* vq->avail->used_event */
|
||||||
|
writew(vq->avail + 4 + (2 * vq->size), 0);
|
||||||
|
|
||||||
|
/* vq->used->flags */
|
||||||
|
writew(vq->used, 0);
|
||||||
|
/* vq->used->avail_event */
|
||||||
|
writew(vq->used+2+(sizeof(struct QVRingUsedElem)*vq->size), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVRingIndirectDesc *qvring_indirect_desc_setup(QVirtioDevice *d,
|
||||||
|
QGuestAllocator *alloc, uint16_t elem)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
QVRingIndirectDesc *indirect = g_malloc(sizeof(*indirect));
|
||||||
|
|
||||||
|
indirect->index = 0;
|
||||||
|
indirect->elem = elem;
|
||||||
|
indirect->desc = guest_alloc(alloc, sizeof(QVRingDesc)*elem);
|
||||||
|
|
||||||
|
for (i = 0; i < elem - 1; ++i) {
|
||||||
|
/* indirect->desc[i].addr */
|
||||||
|
writeq(indirect->desc + (16 * i), 0);
|
||||||
|
/* indirect->desc[i].flags */
|
||||||
|
writew(indirect->desc + (16 * i) + 12, QVRING_DESC_F_NEXT);
|
||||||
|
/* indirect->desc[i].next */
|
||||||
|
writew(indirect->desc + (16 * i) + 14, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return indirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvring_indirect_desc_add(QVRingIndirectDesc *indirect, uint64_t data,
|
||||||
|
uint32_t len, bool write)
|
||||||
|
{
|
||||||
|
uint16_t flags;
|
||||||
|
|
||||||
|
g_assert_cmpint(indirect->index, <, indirect->elem);
|
||||||
|
|
||||||
|
flags = readw(indirect->desc + (16 * indirect->index) + 12);
|
||||||
|
|
||||||
|
if (write) {
|
||||||
|
flags |= QVRING_DESC_F_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* indirect->desc[indirect->index].addr */
|
||||||
|
writeq(indirect->desc + (16 * indirect->index), data);
|
||||||
|
/* indirect->desc[indirect->index].len */
|
||||||
|
writel(indirect->desc + (16 * indirect->index) + 8, len);
|
||||||
|
/* indirect->desc[indirect->index].flags */
|
||||||
|
writew(indirect->desc + (16 * indirect->index) + 12, flags);
|
||||||
|
|
||||||
|
indirect->index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write,
|
||||||
|
bool next)
|
||||||
|
{
|
||||||
|
uint16_t flags = 0;
|
||||||
|
vq->num_free--;
|
||||||
|
|
||||||
|
if (write) {
|
||||||
|
flags |= QVRING_DESC_F_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
flags |= QVRING_DESC_F_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vq->desc[vq->free_head].addr */
|
||||||
|
writeq(vq->desc + (16 * vq->free_head), data);
|
||||||
|
/* vq->desc[vq->free_head].len */
|
||||||
|
writel(vq->desc + (16 * vq->free_head) + 8, len);
|
||||||
|
/* vq->desc[vq->free_head].flags */
|
||||||
|
writew(vq->desc + (16 * vq->free_head) + 12, flags);
|
||||||
|
|
||||||
|
return vq->free_head++; /* Return and increase, in this order */
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t qvirtqueue_add_indirect(QVirtQueue *vq, QVRingIndirectDesc *indirect)
|
||||||
|
{
|
||||||
|
g_assert(vq->indirect);
|
||||||
|
g_assert_cmpint(vq->size, >=, indirect->elem);
|
||||||
|
g_assert_cmpint(indirect->index, ==, indirect->elem);
|
||||||
|
|
||||||
|
vq->num_free--;
|
||||||
|
|
||||||
|
/* vq->desc[vq->free_head].addr */
|
||||||
|
writeq(vq->desc + (16 * vq->free_head), indirect->desc);
|
||||||
|
/* vq->desc[vq->free_head].len */
|
||||||
|
writel(vq->desc + (16 * vq->free_head) + 8,
|
||||||
|
sizeof(QVRingDesc) * indirect->elem);
|
||||||
|
/* vq->desc[vq->free_head].flags */
|
||||||
|
writew(vq->desc + (16 * vq->free_head) + 12, QVRING_DESC_F_INDIRECT);
|
||||||
|
|
||||||
|
return vq->free_head++; /* Return and increase, in this order */
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq,
|
||||||
|
uint32_t free_head)
|
||||||
|
{
|
||||||
|
/* vq->avail->idx */
|
||||||
|
uint16_t idx = readl(vq->avail + 2);
|
||||||
|
/* vq->used->flags */
|
||||||
|
uint16_t flags;
|
||||||
|
/* vq->used->avail_event */
|
||||||
|
uint16_t avail_event;
|
||||||
|
|
||||||
|
/* vq->avail->ring[idx % vq->size] */
|
||||||
|
writel(vq->avail + 4 + (2 * (idx % vq->size)), free_head);
|
||||||
|
/* vq->avail->idx */
|
||||||
|
writel(vq->avail + 2, idx + 1);
|
||||||
|
|
||||||
|
/* Must read after idx is updated */
|
||||||
|
flags = readw(vq->avail);
|
||||||
|
avail_event = readw(vq->used + 4 +
|
||||||
|
(sizeof(struct QVRingUsedElem) * vq->size));
|
||||||
|
|
||||||
|
/* < 1 because we add elements to avail queue one by one */
|
||||||
|
if ((flags & QVRING_USED_F_NO_NOTIFY) == 0 &&
|
||||||
|
(!vq->event || (uint16_t)(idx-avail_event) < 1)) {
|
||||||
|
bus->virtqueue_kick(d, vq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx)
|
||||||
|
{
|
||||||
|
g_assert(vq->event);
|
||||||
|
|
||||||
|
/* vq->avail->used_event */
|
||||||
|
writew(vq->avail + 4 + (2 * vq->size), idx);
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* libqos virtio definitions
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Marc Marí
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIBQOS_VIRTIO_H
|
||||||
|
#define LIBQOS_VIRTIO_H
|
||||||
|
|
||||||
|
#include "libqos/malloc.h"
|
||||||
|
|
||||||
|
#define QVIRTIO_VENDOR_ID 0x1AF4
|
||||||
|
|
||||||
|
#define QVIRTIO_RESET 0x0
|
||||||
|
#define QVIRTIO_ACKNOWLEDGE 0x1
|
||||||
|
#define QVIRTIO_DRIVER 0x2
|
||||||
|
#define QVIRTIO_DRIVER_OK 0x4
|
||||||
|
|
||||||
|
#define QVIRTIO_NET_DEVICE_ID 0x1
|
||||||
|
#define QVIRTIO_BLK_DEVICE_ID 0x2
|
||||||
|
|
||||||
|
#define QVIRTIO_F_NOTIFY_ON_EMPTY 0x01000000
|
||||||
|
#define QVIRTIO_F_ANY_LAYOUT 0x08000000
|
||||||
|
#define QVIRTIO_F_RING_INDIRECT_DESC 0x10000000
|
||||||
|
#define QVIRTIO_F_RING_EVENT_IDX 0x20000000
|
||||||
|
#define QVIRTIO_F_BAD_FEATURE 0x40000000
|
||||||
|
|
||||||
|
#define QVRING_DESC_F_NEXT 0x1
|
||||||
|
#define QVRING_DESC_F_WRITE 0x2
|
||||||
|
#define QVRING_DESC_F_INDIRECT 0x4
|
||||||
|
|
||||||
|
#define QVIRTIO_F_NOTIFY_ON_EMPTY 0x01000000
|
||||||
|
#define QVIRTIO_F_ANY_LAYOUT 0x08000000
|
||||||
|
#define QVIRTIO_F_RING_INDIRECT_DESC 0x10000000
|
||||||
|
#define QVIRTIO_F_RING_EVENT_IDX 0x20000000
|
||||||
|
#define QVIRTIO_F_BAD_FEATURE 0x40000000
|
||||||
|
|
||||||
|
#define QVRING_AVAIL_F_NO_INTERRUPT 1
|
||||||
|
|
||||||
|
#define QVRING_USED_F_NO_NOTIFY 1
|
||||||
|
|
||||||
|
typedef struct QVirtioDevice {
|
||||||
|
/* Device type */
|
||||||
|
uint16_t device_type;
|
||||||
|
} QVirtioDevice;
|
||||||
|
|
||||||
|
typedef struct QVRingDesc {
|
||||||
|
uint64_t addr;
|
||||||
|
uint32_t len;
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t next;
|
||||||
|
} QVRingDesc;
|
||||||
|
|
||||||
|
typedef struct QVRingAvail {
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t idx;
|
||||||
|
uint16_t ring[0]; /* This is an array of uint16_t */
|
||||||
|
uint16_t used_event;
|
||||||
|
} QVRingAvail;
|
||||||
|
|
||||||
|
typedef struct QVRingUsedElem {
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t len;
|
||||||
|
} QVRingUsedElem;
|
||||||
|
|
||||||
|
typedef struct QVRingUsed {
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t idx;
|
||||||
|
QVRingUsedElem ring[0]; /* This is an array of QVRingUsedElem structs */
|
||||||
|
uint16_t avail_event;
|
||||||
|
} QVRingUsed;
|
||||||
|
|
||||||
|
typedef struct QVirtQueue {
|
||||||
|
uint64_t desc; /* This points to an array of QVRingDesc */
|
||||||
|
uint64_t avail; /* This points to a QVRingAvail */
|
||||||
|
uint64_t used; /* This points to a QVRingDesc */
|
||||||
|
uint16_t index;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t free_head;
|
||||||
|
uint32_t num_free;
|
||||||
|
uint32_t align;
|
||||||
|
bool indirect;
|
||||||
|
bool event;
|
||||||
|
} QVirtQueue;
|
||||||
|
|
||||||
|
typedef struct QVRingIndirectDesc {
|
||||||
|
uint64_t desc; /* This points to an array fo QVRingDesc */
|
||||||
|
uint16_t index;
|
||||||
|
uint16_t elem;
|
||||||
|
} QVRingIndirectDesc;
|
||||||
|
|
||||||
|
typedef struct QVirtioBus {
|
||||||
|
uint8_t (*config_readb)(QVirtioDevice *d, void *addr);
|
||||||
|
uint16_t (*config_readw)(QVirtioDevice *d, void *addr);
|
||||||
|
uint32_t (*config_readl)(QVirtioDevice *d, void *addr);
|
||||||
|
uint64_t (*config_readq)(QVirtioDevice *d, void *addr);
|
||||||
|
|
||||||
|
/* Get features of the device */
|
||||||
|
uint32_t (*get_features)(QVirtioDevice *d);
|
||||||
|
|
||||||
|
/* Set features of the device */
|
||||||
|
void (*set_features)(QVirtioDevice *d, uint32_t features);
|
||||||
|
|
||||||
|
/* Get features of the guest */
|
||||||
|
uint32_t (*get_guest_features)(QVirtioDevice *d);
|
||||||
|
|
||||||
|
/* Get status of the device */
|
||||||
|
uint8_t (*get_status)(QVirtioDevice *d);
|
||||||
|
|
||||||
|
/* Set status of the device */
|
||||||
|
void (*set_status)(QVirtioDevice *d, uint8_t status);
|
||||||
|
|
||||||
|
/* Get the queue ISR status of the device */
|
||||||
|
bool (*get_queue_isr_status)(QVirtioDevice *d, QVirtQueue *vq);
|
||||||
|
|
||||||
|
/* Get the configuration ISR status of the device */
|
||||||
|
bool (*get_config_isr_status)(QVirtioDevice *d);
|
||||||
|
|
||||||
|
/* Select a queue to work on */
|
||||||
|
void (*queue_select)(QVirtioDevice *d, uint16_t index);
|
||||||
|
|
||||||
|
/* Get the size of the selected queue */
|
||||||
|
uint16_t (*get_queue_size)(QVirtioDevice *d);
|
||||||
|
|
||||||
|
/* Set the address of the selected queue */
|
||||||
|
void (*set_queue_address)(QVirtioDevice *d, uint32_t pfn);
|
||||||
|
|
||||||
|
/* Setup the virtqueue specified by index */
|
||||||
|
QVirtQueue *(*virtqueue_setup)(QVirtioDevice *d, QGuestAllocator *alloc,
|
||||||
|
uint16_t index);
|
||||||
|
|
||||||
|
/* Notify changes in virtqueue */
|
||||||
|
void (*virtqueue_kick)(QVirtioDevice *d, QVirtQueue *vq);
|
||||||
|
} QVirtioBus;
|
||||||
|
|
||||||
|
static inline uint32_t qvring_size(uint32_t num, uint32_t align)
|
||||||
|
{
|
||||||
|
return ((sizeof(struct QVRingDesc) * num + sizeof(uint16_t) * (3 + num)
|
||||||
|
+ align - 1) & ~(align - 1))
|
||||||
|
+ sizeof(uint16_t) * 3 + sizeof(struct QVRingUsedElem) * num;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t qvirtio_config_readb(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
void *addr);
|
||||||
|
uint16_t qvirtio_config_readw(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
void *addr);
|
||||||
|
uint32_t qvirtio_config_readl(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
void *addr);
|
||||||
|
uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
void *addr);
|
||||||
|
uint32_t qvirtio_get_features(const QVirtioBus *bus, QVirtioDevice *d);
|
||||||
|
void qvirtio_set_features(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
uint32_t features);
|
||||||
|
|
||||||
|
void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d);
|
||||||
|
void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d);
|
||||||
|
void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d);
|
||||||
|
void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d);
|
||||||
|
|
||||||
|
bool qvirtio_wait_queue_isr(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
QVirtQueue *vq, uint64_t timeout);
|
||||||
|
bool qvirtio_wait_config_isr(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
uint64_t timeout);
|
||||||
|
QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d,
|
||||||
|
QGuestAllocator *alloc, uint16_t index);
|
||||||
|
|
||||||
|
void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr);
|
||||||
|
QVRingIndirectDesc *qvring_indirect_desc_setup(QVirtioDevice *d,
|
||||||
|
QGuestAllocator *alloc, uint16_t elem);
|
||||||
|
void qvring_indirect_desc_add(QVRingIndirectDesc *indirect, uint64_t data,
|
||||||
|
uint32_t len, bool write);
|
||||||
|
uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write,
|
||||||
|
bool next);
|
||||||
|
uint32_t qvirtqueue_add_indirect(QVirtQueue *vq, QVRingIndirectDesc *indirect);
|
||||||
|
void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq,
|
||||||
|
uint32_t free_head);
|
||||||
|
|
||||||
|
void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx);
|
||||||
|
#endif
|
|
@ -696,3 +696,51 @@ void qmp_discard_response(const char *fmt, ...)
|
||||||
qtest_qmpv_discard_response(global_qtest, fmt, ap);
|
qtest_qmpv_discard_response(global_qtest, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool qtest_big_endian(void)
|
||||||
|
{
|
||||||
|
const char *arch = qtest_get_arch();
|
||||||
|
int i;
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
const char *arch;
|
||||||
|
bool big_endian;
|
||||||
|
} endianness[] = {
|
||||||
|
{ "aarch64", false },
|
||||||
|
{ "alpha", false },
|
||||||
|
{ "arm", false },
|
||||||
|
{ "cris", false },
|
||||||
|
{ "i386", false },
|
||||||
|
{ "lm32", true },
|
||||||
|
{ "m68k", true },
|
||||||
|
{ "microblaze", true },
|
||||||
|
{ "microblazeel", false },
|
||||||
|
{ "mips", true },
|
||||||
|
{ "mips64", true },
|
||||||
|
{ "mips64el", false },
|
||||||
|
{ "mipsel", false },
|
||||||
|
{ "moxie", true },
|
||||||
|
{ "or32", true },
|
||||||
|
{ "ppc", true },
|
||||||
|
{ "ppc64", true },
|
||||||
|
{ "ppcemb", true },
|
||||||
|
{ "s390x", true },
|
||||||
|
{ "sh4", false },
|
||||||
|
{ "sh4eb", true },
|
||||||
|
{ "sparc", true },
|
||||||
|
{ "sparc64", true },
|
||||||
|
{ "unicore32", false },
|
||||||
|
{ "x86_64", false },
|
||||||
|
{ "xtensa", false },
|
||||||
|
{ "xtensaeb", true },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (i = 0; endianness[i].arch; i++) {
|
||||||
|
if (strcmp(endianness[i].arch, arch) == 0) {
|
||||||
|
return endianness[i].big_endian;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -682,4 +682,11 @@ static inline int64_t clock_set(int64_t val)
|
||||||
return qtest_clock_set(global_qtest, val);
|
return qtest_clock_set(global_qtest, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* qtest_big_endian:
|
||||||
|
*
|
||||||
|
* Returns: True if the architecture under test has a big endian configuration.
|
||||||
|
*/
|
||||||
|
bool qtest_big_endian(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* QTest testcase for VirtIO Block Device
|
* QTest testcase for VirtIO Block Device
|
||||||
*
|
*
|
||||||
* Copyright (c) 2014 SUSE LINUX Products GmbH
|
* Copyright (c) 2014 SUSE LINUX Products GmbH
|
||||||
|
* Copyright (c) 2014 Marc Marí
|
||||||
*
|
*
|
||||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
|
@ -9,12 +10,634 @@
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "libqtest.h"
|
#include "libqtest.h"
|
||||||
#include "qemu/osdep.h"
|
#include "libqos/virtio.h"
|
||||||
|
#include "libqos/virtio-pci.h"
|
||||||
|
#include "libqos/pci-pc.h"
|
||||||
|
#include "libqos/malloc.h"
|
||||||
|
#include "libqos/malloc-pc.h"
|
||||||
|
#include "qemu/bswap.h"
|
||||||
|
|
||||||
/* Tests only initialization so far. TODO: Replace with functional tests */
|
#define QVIRTIO_BLK_F_BARRIER 0x00000001
|
||||||
static void pci_nop(void)
|
#define QVIRTIO_BLK_F_SIZE_MAX 0x00000002
|
||||||
|
#define QVIRTIO_BLK_F_SEG_MAX 0x00000004
|
||||||
|
#define QVIRTIO_BLK_F_GEOMETRY 0x00000010
|
||||||
|
#define QVIRTIO_BLK_F_RO 0x00000020
|
||||||
|
#define QVIRTIO_BLK_F_BLK_SIZE 0x00000040
|
||||||
|
#define QVIRTIO_BLK_F_SCSI 0x00000080
|
||||||
|
#define QVIRTIO_BLK_F_WCE 0x00000200
|
||||||
|
#define QVIRTIO_BLK_F_TOPOLOGY 0x00000400
|
||||||
|
#define QVIRTIO_BLK_F_CONFIG_WCE 0x00000800
|
||||||
|
|
||||||
|
#define QVIRTIO_BLK_T_IN 0
|
||||||
|
#define QVIRTIO_BLK_T_OUT 1
|
||||||
|
#define QVIRTIO_BLK_T_SCSI_CMD 2
|
||||||
|
#define QVIRTIO_BLK_T_SCSI_CMD_OUT 3
|
||||||
|
#define QVIRTIO_BLK_T_FLUSH 4
|
||||||
|
#define QVIRTIO_BLK_T_FLUSH_OUT 5
|
||||||
|
#define QVIRTIO_BLK_T_GET_ID 8
|
||||||
|
|
||||||
|
#define TEST_IMAGE_SIZE (64 * 1024 * 1024)
|
||||||
|
#define QVIRTIO_BLK_TIMEOUT 100
|
||||||
|
#define PCI_SLOT 0x04
|
||||||
|
#define PCI_FN 0x00
|
||||||
|
|
||||||
|
typedef struct QVirtioBlkReq {
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t ioprio;
|
||||||
|
uint64_t sector;
|
||||||
|
char *data;
|
||||||
|
uint8_t status;
|
||||||
|
} QVirtioBlkReq;
|
||||||
|
|
||||||
|
static QPCIBus *test_start(void)
|
||||||
{
|
{
|
||||||
|
char cmdline[100];
|
||||||
|
char tmp_path[] = "/tmp/qtest.XXXXXX";
|
||||||
|
int fd, ret;
|
||||||
|
|
||||||
|
/* Create a temporary raw image */
|
||||||
|
fd = mkstemp(tmp_path);
|
||||||
|
g_assert_cmpint(fd, >=, 0);
|
||||||
|
ret = ftruncate(fd, TEST_IMAGE_SIZE);
|
||||||
|
g_assert_cmpint(ret, ==, 0);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
snprintf(cmdline, 100, "-drive if=none,id=drive0,file=%s "
|
||||||
|
"-device virtio-blk-pci,drive=drive0,addr=%x.%x",
|
||||||
|
tmp_path, PCI_SLOT, PCI_FN);
|
||||||
|
qtest_start(cmdline);
|
||||||
|
unlink(tmp_path);
|
||||||
|
|
||||||
|
return qpci_init_pc();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_end(void)
|
||||||
|
{
|
||||||
|
qtest_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static QVirtioPCIDevice *virtio_blk_init(QPCIBus *bus)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev;
|
||||||
|
|
||||||
|
dev = qvirtio_pci_device_find(bus, QVIRTIO_BLK_DEVICE_ID);
|
||||||
|
g_assert(dev != NULL);
|
||||||
|
g_assert_cmphex(dev->vdev.device_type, ==, QVIRTIO_BLK_DEVICE_ID);
|
||||||
|
g_assert_cmphex(dev->pdev->devfn, ==, ((PCI_SLOT << 3) | PCI_FN));
|
||||||
|
|
||||||
|
qvirtio_pci_device_enable(dev);
|
||||||
|
qvirtio_reset(&qvirtio_pci, &dev->vdev);
|
||||||
|
qvirtio_set_acknowledge(&qvirtio_pci, &dev->vdev);
|
||||||
|
qvirtio_set_driver(&qvirtio_pci, &dev->vdev);
|
||||||
|
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void virtio_blk_fix_request(QVirtioBlkReq *req)
|
||||||
|
{
|
||||||
|
#ifdef HOST_WORDS_BIGENDIAN
|
||||||
|
bool host_endian = true;
|
||||||
|
#else
|
||||||
|
bool host_endian = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (qtest_big_endian() != host_endian) {
|
||||||
|
req->type = bswap32(req->type);
|
||||||
|
req->ioprio = bswap32(req->ioprio);
|
||||||
|
req->sector = bswap64(req->sector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t virtio_blk_request(QGuestAllocator *alloc, QVirtioBlkReq *req,
|
||||||
|
uint64_t data_size)
|
||||||
|
{
|
||||||
|
uint64_t addr;
|
||||||
|
uint8_t status = 0xFF;
|
||||||
|
|
||||||
|
g_assert_cmpuint(data_size % 512, ==, 0);
|
||||||
|
addr = guest_alloc(alloc, sizeof(*req) + data_size);
|
||||||
|
|
||||||
|
virtio_blk_fix_request(req);
|
||||||
|
|
||||||
|
memwrite(addr, req, 16);
|
||||||
|
memwrite(addr + 16, req->data, data_size);
|
||||||
|
memwrite(addr + 16 + data_size, &status, sizeof(status));
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pci_basic(void)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev;
|
||||||
|
QPCIBus *bus;
|
||||||
|
QVirtQueuePCI *vqpci;
|
||||||
|
QGuestAllocator *alloc;
|
||||||
|
QVirtioBlkReq req;
|
||||||
|
void *addr;
|
||||||
|
uint64_t req_addr;
|
||||||
|
uint64_t capacity;
|
||||||
|
uint32_t features;
|
||||||
|
uint32_t free_head;
|
||||||
|
uint8_t status;
|
||||||
|
char *data;
|
||||||
|
|
||||||
|
bus = test_start();
|
||||||
|
|
||||||
|
dev = virtio_blk_init(bus);
|
||||||
|
|
||||||
|
/* MSI-X is not enabled */
|
||||||
|
addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX;
|
||||||
|
|
||||||
|
capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr);
|
||||||
|
g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
|
||||||
|
|
||||||
|
features = qvirtio_get_features(&qvirtio_pci, &dev->vdev);
|
||||||
|
features = features & ~(QVIRTIO_F_BAD_FEATURE |
|
||||||
|
QVIRTIO_F_RING_INDIRECT_DESC | QVIRTIO_F_RING_EVENT_IDX |
|
||||||
|
QVIRTIO_BLK_F_SCSI);
|
||||||
|
qvirtio_set_features(&qvirtio_pci, &dev->vdev, features);
|
||||||
|
|
||||||
|
alloc = pc_alloc_init();
|
||||||
|
vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev,
|
||||||
|
alloc, 0);
|
||||||
|
|
||||||
|
qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev);
|
||||||
|
|
||||||
|
/* Write and read with 2 descriptor layout */
|
||||||
|
/* Write request */
|
||||||
|
req.type = QVIRTIO_BLK_T_OUT;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 0;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
strcpy(req.data, "TEST");
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false);
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* Read request */
|
||||||
|
req.type = QVIRTIO_BLK_T_IN;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 0;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false);
|
||||||
|
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
data = g_malloc0(512);
|
||||||
|
memread(req_addr + 16, data, 512);
|
||||||
|
g_assert_cmpstr(data, ==, "TEST");
|
||||||
|
g_free(data);
|
||||||
|
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* Write and read with 3 descriptor layout */
|
||||||
|
/* Write request */
|
||||||
|
req.type = QVIRTIO_BLK_T_OUT;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 1;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
strcpy(req.data, "TEST");
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 16, 512, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false);
|
||||||
|
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* Read request */
|
||||||
|
req.type = QVIRTIO_BLK_T_IN;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 1;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 16, 512, true, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false);
|
||||||
|
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
data = g_malloc0(512);
|
||||||
|
memread(req_addr + 16, data, 512);
|
||||||
|
g_assert_cmpstr(data, ==, "TEST");
|
||||||
|
g_free(data);
|
||||||
|
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* End test */
|
||||||
|
guest_free(alloc, vqpci->vq.desc);
|
||||||
|
qvirtio_pci_device_disable(dev);
|
||||||
|
g_free(dev);
|
||||||
|
test_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pci_indirect(void)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev;
|
||||||
|
QPCIBus *bus;
|
||||||
|
QVirtQueuePCI *vqpci;
|
||||||
|
QGuestAllocator *alloc;
|
||||||
|
QVirtioBlkReq req;
|
||||||
|
QVRingIndirectDesc *indirect;
|
||||||
|
void *addr;
|
||||||
|
uint64_t req_addr;
|
||||||
|
uint64_t capacity;
|
||||||
|
uint32_t features;
|
||||||
|
uint32_t free_head;
|
||||||
|
uint8_t status;
|
||||||
|
char *data;
|
||||||
|
|
||||||
|
bus = test_start();
|
||||||
|
|
||||||
|
dev = virtio_blk_init(bus);
|
||||||
|
|
||||||
|
/* MSI-X is not enabled */
|
||||||
|
addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX;
|
||||||
|
|
||||||
|
capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr);
|
||||||
|
g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
|
||||||
|
|
||||||
|
features = qvirtio_get_features(&qvirtio_pci, &dev->vdev);
|
||||||
|
g_assert_cmphex(features & QVIRTIO_F_RING_INDIRECT_DESC, !=, 0);
|
||||||
|
features = features & ~(QVIRTIO_F_BAD_FEATURE | QVIRTIO_F_RING_EVENT_IDX |
|
||||||
|
QVIRTIO_BLK_F_SCSI);
|
||||||
|
qvirtio_set_features(&qvirtio_pci, &dev->vdev, features);
|
||||||
|
|
||||||
|
alloc = pc_alloc_init();
|
||||||
|
vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev,
|
||||||
|
alloc, 0);
|
||||||
|
qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev);
|
||||||
|
|
||||||
|
/* Write request */
|
||||||
|
req.type = QVIRTIO_BLK_T_OUT;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 0;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
strcpy(req.data, "TEST");
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
indirect = qvring_indirect_desc_setup(&dev->vdev, alloc, 2);
|
||||||
|
qvring_indirect_desc_add(indirect, req_addr, 528, false);
|
||||||
|
qvring_indirect_desc_add(indirect, req_addr + 528, 1, true);
|
||||||
|
free_head = qvirtqueue_add_indirect(&vqpci->vq, indirect);
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
g_free(indirect);
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* Read request */
|
||||||
|
req.type = QVIRTIO_BLK_T_IN;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 0;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
strcpy(req.data, "TEST");
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
indirect = qvring_indirect_desc_setup(&dev->vdev, alloc, 2);
|
||||||
|
qvring_indirect_desc_add(indirect, req_addr, 16, false);
|
||||||
|
qvring_indirect_desc_add(indirect, req_addr + 16, 513, true);
|
||||||
|
free_head = qvirtqueue_add_indirect(&vqpci->vq, indirect);
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
data = g_malloc0(512);
|
||||||
|
memread(req_addr + 16, data, 512);
|
||||||
|
g_assert_cmpstr(data, ==, "TEST");
|
||||||
|
g_free(data);
|
||||||
|
|
||||||
|
g_free(indirect);
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* End test */
|
||||||
|
guest_free(alloc, vqpci->vq.desc);
|
||||||
|
qvirtio_pci_device_disable(dev);
|
||||||
|
g_free(dev);
|
||||||
|
test_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pci_config(void)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev;
|
||||||
|
QPCIBus *bus;
|
||||||
|
int n_size = TEST_IMAGE_SIZE / 2;
|
||||||
|
void *addr;
|
||||||
|
uint64_t capacity;
|
||||||
|
|
||||||
|
bus = test_start();
|
||||||
|
|
||||||
|
dev = virtio_blk_init(bus);
|
||||||
|
|
||||||
|
/* MSI-X is not enabled */
|
||||||
|
addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX;
|
||||||
|
|
||||||
|
capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr);
|
||||||
|
g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
|
||||||
|
|
||||||
|
qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev);
|
||||||
|
|
||||||
|
qmp("{ 'execute': 'block_resize', 'arguments': { 'device': 'drive0', "
|
||||||
|
" 'size': %d } }", n_size);
|
||||||
|
g_assert(qvirtio_wait_config_isr(&qvirtio_pci, &dev->vdev,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
|
||||||
|
capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr);
|
||||||
|
g_assert_cmpint(capacity, ==, n_size / 512);
|
||||||
|
|
||||||
|
qvirtio_pci_device_disable(dev);
|
||||||
|
g_free(dev);
|
||||||
|
test_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pci_msix(void)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev;
|
||||||
|
QPCIBus *bus;
|
||||||
|
QVirtQueuePCI *vqpci;
|
||||||
|
QGuestAllocator *alloc;
|
||||||
|
QVirtioBlkReq req;
|
||||||
|
int n_size = TEST_IMAGE_SIZE / 2;
|
||||||
|
void *addr;
|
||||||
|
uint64_t req_addr;
|
||||||
|
uint64_t capacity;
|
||||||
|
uint32_t features;
|
||||||
|
uint32_t free_head;
|
||||||
|
uint8_t status;
|
||||||
|
char *data;
|
||||||
|
|
||||||
|
bus = test_start();
|
||||||
|
alloc = pc_alloc_init();
|
||||||
|
|
||||||
|
dev = virtio_blk_init(bus);
|
||||||
|
qpci_msix_enable(dev->pdev);
|
||||||
|
|
||||||
|
qvirtio_pci_set_msix_configuration_vector(dev, alloc, 0);
|
||||||
|
|
||||||
|
/* MSI-X is enabled */
|
||||||
|
addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_MSIX;
|
||||||
|
|
||||||
|
capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr);
|
||||||
|
g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
|
||||||
|
|
||||||
|
features = qvirtio_get_features(&qvirtio_pci, &dev->vdev);
|
||||||
|
features = features & ~(QVIRTIO_F_BAD_FEATURE |
|
||||||
|
QVIRTIO_F_RING_INDIRECT_DESC |
|
||||||
|
QVIRTIO_F_RING_EVENT_IDX | QVIRTIO_BLK_F_SCSI);
|
||||||
|
qvirtio_set_features(&qvirtio_pci, &dev->vdev, features);
|
||||||
|
|
||||||
|
vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev,
|
||||||
|
alloc, 0);
|
||||||
|
qvirtqueue_pci_msix_setup(dev, vqpci, alloc, 1);
|
||||||
|
|
||||||
|
qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev);
|
||||||
|
|
||||||
|
qmp("{ 'execute': 'block_resize', 'arguments': { 'device': 'drive0', "
|
||||||
|
" 'size': %d } }", n_size);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_config_isr(&qvirtio_pci, &dev->vdev,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
|
||||||
|
capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr);
|
||||||
|
g_assert_cmpint(capacity, ==, n_size / 512);
|
||||||
|
|
||||||
|
/* Write request */
|
||||||
|
req.type = QVIRTIO_BLK_T_OUT;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 0;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
strcpy(req.data, "TEST");
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false);
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* Read request */
|
||||||
|
req.type = QVIRTIO_BLK_T_IN;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 0;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false);
|
||||||
|
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
data = g_malloc0(512);
|
||||||
|
memread(req_addr + 16, data, 512);
|
||||||
|
g_assert_cmpstr(data, ==, "TEST");
|
||||||
|
g_free(data);
|
||||||
|
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* End test */
|
||||||
|
guest_free(alloc, (uint64_t)vqpci->vq.desc);
|
||||||
|
qpci_msix_disable(dev->pdev);
|
||||||
|
qvirtio_pci_device_disable(dev);
|
||||||
|
g_free(dev);
|
||||||
|
test_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pci_idx(void)
|
||||||
|
{
|
||||||
|
QVirtioPCIDevice *dev;
|
||||||
|
QPCIBus *bus;
|
||||||
|
QVirtQueuePCI *vqpci;
|
||||||
|
QGuestAllocator *alloc;
|
||||||
|
QVirtioBlkReq req;
|
||||||
|
void *addr;
|
||||||
|
uint64_t req_addr;
|
||||||
|
uint64_t capacity;
|
||||||
|
uint32_t features;
|
||||||
|
uint32_t free_head;
|
||||||
|
uint8_t status;
|
||||||
|
char *data;
|
||||||
|
|
||||||
|
bus = test_start();
|
||||||
|
alloc = pc_alloc_init();
|
||||||
|
|
||||||
|
dev = virtio_blk_init(bus);
|
||||||
|
qpci_msix_enable(dev->pdev);
|
||||||
|
|
||||||
|
qvirtio_pci_set_msix_configuration_vector(dev, alloc, 0);
|
||||||
|
|
||||||
|
/* MSI-X is enabled */
|
||||||
|
addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_MSIX;
|
||||||
|
|
||||||
|
capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr);
|
||||||
|
g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
|
||||||
|
|
||||||
|
features = qvirtio_get_features(&qvirtio_pci, &dev->vdev);
|
||||||
|
features = features & ~(QVIRTIO_F_BAD_FEATURE |
|
||||||
|
QVIRTIO_F_RING_INDIRECT_DESC |
|
||||||
|
QVIRTIO_F_NOTIFY_ON_EMPTY | QVIRTIO_BLK_F_SCSI);
|
||||||
|
qvirtio_set_features(&qvirtio_pci, &dev->vdev, features);
|
||||||
|
|
||||||
|
vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev,
|
||||||
|
alloc, 0);
|
||||||
|
qvirtqueue_pci_msix_setup(dev, vqpci, alloc, 1);
|
||||||
|
|
||||||
|
qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev);
|
||||||
|
|
||||||
|
/* Write request */
|
||||||
|
req.type = QVIRTIO_BLK_T_OUT;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 0;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
strcpy(req.data, "TEST");
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false);
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
|
||||||
|
/* Write request */
|
||||||
|
req.type = QVIRTIO_BLK_T_OUT;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 1;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
strcpy(req.data, "TEST");
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
/* Notify after processing the third request */
|
||||||
|
qvirtqueue_set_used_event(&vqpci->vq, 2);
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false);
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
/* No notification expected */
|
||||||
|
g_assert(!qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* Read request */
|
||||||
|
req.type = QVIRTIO_BLK_T_IN;
|
||||||
|
req.ioprio = 1;
|
||||||
|
req.sector = 1;
|
||||||
|
req.data = g_malloc0(512);
|
||||||
|
|
||||||
|
req_addr = virtio_blk_request(alloc, &req, 512);
|
||||||
|
|
||||||
|
g_free(req.data);
|
||||||
|
|
||||||
|
free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true);
|
||||||
|
qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false);
|
||||||
|
|
||||||
|
qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head);
|
||||||
|
|
||||||
|
|
||||||
|
g_assert(qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq,
|
||||||
|
QVIRTIO_BLK_TIMEOUT));
|
||||||
|
|
||||||
|
status = readb(req_addr + 528);
|
||||||
|
g_assert_cmpint(status, ==, 0);
|
||||||
|
|
||||||
|
data = g_malloc0(512);
|
||||||
|
memread(req_addr + 16, data, 512);
|
||||||
|
g_assert_cmpstr(data, ==, "TEST");
|
||||||
|
g_free(data);
|
||||||
|
|
||||||
|
guest_free(alloc, req_addr);
|
||||||
|
|
||||||
|
/* End test */
|
||||||
|
guest_free(alloc, vqpci->vq.desc);
|
||||||
|
qpci_msix_disable(dev->pdev);
|
||||||
|
qvirtio_pci_device_disable(dev);
|
||||||
|
g_free(dev);
|
||||||
|
test_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
@ -22,13 +645,14 @@ int main(int argc, char **argv)
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
g_test_init(&argc, &argv, NULL);
|
g_test_init(&argc, &argv, NULL);
|
||||||
qtest_add_func("/virtio/blk/pci/nop", pci_nop);
|
|
||||||
|
|
||||||
qtest_start("-drive id=drv0,if=none,file=/dev/null "
|
g_test_add_func("/virtio/blk/pci/basic", pci_basic);
|
||||||
"-device virtio-blk-pci,drive=drv0");
|
g_test_add_func("/virtio/blk/pci/indirect", pci_indirect);
|
||||||
|
g_test_add_func("/virtio/blk/pci/config", pci_config);
|
||||||
|
g_test_add_func("/virtio/blk/pci/msix", pci_msix);
|
||||||
|
g_test_add_func("/virtio/blk/pci/idx", pci_idx);
|
||||||
|
|
||||||
ret = g_test_run();
|
ret = g_test_run();
|
||||||
|
|
||||||
qtest_end();
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue