virtio: Add config access helpers
At the moment device-specific config access is tailored for a Linux
guest, that performs any access in 8 bits. But config access can have
any size, and modern virtio drivers must use the size of the accessed
field. Add helpers that generalize config accesses.
Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
Link: https://lore.kernel.org/r/20220607170239.120084-7-jean-philippe.brucker@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
diff --git a/include/kvm/virtio.h b/include/kvm/virtio.h
index 24179ec..cc4ba1d 100644
--- a/include/kvm/virtio.h
+++ b/include/kvm/virtio.h
@@ -236,6 +236,9 @@
struct virt_queue *vq, size_t nr_descs);
void virtio_exit_vq(struct kvm *kvm, struct virtio_device *vdev, void *dev,
int num);
+bool virtio_access_config(struct kvm *kvm, struct virtio_device *vdev, void *dev,
+ unsigned long offset, void *data, size_t size,
+ bool is_write);
void virtio_set_guest_features(struct kvm *kvm, struct virtio_device *vdev,
void *dev, u32 features);
void virtio_notify_status(struct kvm *kvm, struct virtio_device *vdev,
diff --git a/virtio/core.c b/virtio/core.c
index d6f2c68..be0f6f8 100644
--- a/virtio/core.c
+++ b/virtio/core.c
@@ -282,6 +282,44 @@
vdev->ops->notify_status(kvm, dev, ext_status);
}
+bool virtio_access_config(struct kvm *kvm, struct virtio_device *vdev,
+ void *dev, unsigned long offset, void *data,
+ size_t size, bool is_write)
+{
+ void *in, *out, *config;
+ size_t config_size = vdev->ops->get_config_size(kvm, dev);
+
+ if (WARN_ONCE(offset + size > config_size,
+ "Config access offset (%lu) is beyond config size (%zu)\n",
+ offset, config_size))
+ return false;
+
+ config = vdev->ops->get_config(kvm, dev) + offset;
+
+ in = is_write ? data : config;
+ out = is_write ? config : data;
+
+ switch (size) {
+ case 1:
+ *(u8 *)out = *(u8 *)in;
+ break;
+ case 2:
+ *(u16 *)out = *(u16 *)in;
+ break;
+ case 4:
+ *(u32 *)out = *(u32 *)in;
+ break;
+ case 8:
+ *(u64 *)out = *(u64 *)in;
+ break;
+ default:
+ WARN_ONCE(1, "%s: invalid access size\n", __func__);
+ return false;
+ }
+
+ return true;
+}
+
int virtio_init(struct kvm *kvm, void *dev, struct virtio_device *vdev,
struct virtio_ops *ops, enum virtio_trans trans,
int device_id, int subsys_id, int class)
diff --git a/virtio/mmio.c b/virtio/mmio.c
index 77289e2..268a439 100644
--- a/virtio/mmio.c
+++ b/virtio/mmio.c
@@ -98,33 +98,6 @@
return 0;
}
-static void virtio_mmio_device_specific(struct kvm_cpu *vcpu,
- u64 addr, u8 *data, u32 len,
- u8 is_write, struct virtio_device *vdev)
-{
- struct virtio_mmio *vmmio = vdev->virtio;
- u8 *config;
- size_t config_size;
- u32 i;
-
- config = vdev->ops->get_config(vmmio->kvm, vmmio->dev);
- config_size = vdev->ops->get_config_size(vmmio->kvm, vmmio->dev);
-
- /* Prevent invalid accesses which go beyond the config */
- if (config_size < addr + len) {
- WARN_ONCE(1, "Offset (%llu) Length (%u) goes beyond config size (%zu).\n",
- addr, len, config_size);
- return;
- }
-
- for (i = 0; i < len; i++) {
- if (is_write)
- config[addr + i] = *(u8 *)data + i;
- else
- data[i] = config[addr + i];
- }
-}
-
#define vmmio_selected_vq(vdev, vmmio) \
(vdev)->ops->get_vq((vmmio)->kvm, (vmmio)->dev, (vmmio)->hdr.queue_sel)
@@ -263,7 +236,8 @@
if (offset >= VIRTIO_MMIO_CONFIG) {
offset -= VIRTIO_MMIO_CONFIG;
- virtio_mmio_device_specific(vcpu, offset, data, len, is_write, ptr);
+ virtio_access_config(vmmio->kvm, vdev, vmmio->dev, offset, data,
+ len, is_write);
return;
}
diff --git a/virtio/pci.c b/virtio/pci.c
index 20b1622..85018e7 100644
--- a/virtio/pci.c
+++ b/virtio/pci.c
@@ -135,25 +135,8 @@
return true;
} else if (type == VIRTIO_PCI_O_CONFIG) {
- u8 cfg;
- size_t config_size;
-
- config_size = vdev->ops->get_config_size(kvm, vpci->dev);
- if (config_offset + size > config_size) {
- /* Access goes beyond the config size, so return failure. */
- WARN_ONCE(1, "Config access offset (%u) is beyond config size (%zu)\n",
- config_offset, config_size);
- return false;
- }
-
- /* TODO: Handle access lengths beyond one byte */
- if (size != 1) {
- WARN_ONCE(1, "Size (%u) not supported\n", size);
- return false;
- }
- cfg = vdev->ops->get_config(kvm, vpci->dev)[config_offset];
- ioport__write8(data, cfg);
- return true;
+ return virtio_access_config(kvm, vdev, vpci->dev, config_offset,
+ data, size, false);
}
return false;
@@ -290,24 +273,8 @@
return true;
} else if (type == VIRTIO_PCI_O_CONFIG) {
- size_t config_size;
-
- config_size = vdev->ops->get_config_size(kvm, vpci->dev);
- if (config_offset + size > config_size) {
- /* Access goes beyond the config size, so return failure. */
- WARN_ONCE(1, "Config access offset (%u) is beyond config size (%zu)\n",
- config_offset, config_size);
- return false;
- }
-
- /* TODO: Handle access lengths beyond one byte */
- if (size != 1) {
- WARN_ONCE(1, "Size (%u) not supported\n", size);
- return false;
- }
- vdev->ops->get_config(kvm, vpci->dev)[config_offset] = *(u8 *)data;
-
- return true;
+ return virtio_access_config(kvm, vdev, vpci->dev, config_offset,
+ data, size, true);
}
return false;