mirror of
https://github.com/torvalds/linux.git
synced 2026-06-06 21:45:45 +02:00
virtio/virtio_mem: qcom: Introduce virtio_mem platform driver
Modify the existing virtio_mem driver to a platform device. Future patches will add support for communication over the mem-buf driver msgq interface, rather than over a virtqueue. Change-Id: I99a4ed93224a6d5d0197fca31d4f7d53eb417cb8 Signed-off-by: Patrick Daly <quic_pdaly@quicinc.com>
This commit is contained in:
parent
aa9a1b50ef
commit
14575325bc
|
|
@ -3,10 +3,13 @@
|
|||
* Virtio-mem device driver.
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2020
|
||||
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*
|
||||
* Author(s): David Hildenbrand <david@redhat.com>
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/virtio.h>
|
||||
#include <linux/virtio_mem.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
|
@ -103,7 +106,7 @@ enum virtio_mem_bbm_bb_state {
|
|||
};
|
||||
|
||||
struct virtio_mem {
|
||||
struct virtio_device *vdev;
|
||||
struct platform_device *vdev;
|
||||
|
||||
/* We might first have to unplug all memory when starting up. */
|
||||
bool unplug_all_required;
|
||||
|
|
@ -113,9 +116,6 @@ struct virtio_mem {
|
|||
atomic_t wq_active;
|
||||
atomic_t config_changed;
|
||||
|
||||
/* Virtqueue for guest->host requests. */
|
||||
struct virtqueue *vq;
|
||||
|
||||
/* Wait for a host response to a guest request. */
|
||||
wait_queue_head_t host_resp;
|
||||
|
||||
|
|
@ -156,6 +156,10 @@ struct virtio_mem {
|
|||
atomic64_t offline_size;
|
||||
uint64_t offline_threshold;
|
||||
|
||||
/* Held when updating new_requested_size */
|
||||
spinlock_t config_lock;
|
||||
uint64_t new_requested_size;
|
||||
|
||||
/* If set, the driver is in SBM, otherwise in BBM. */
|
||||
bool in_sbm;
|
||||
|
||||
|
|
@ -1318,134 +1322,30 @@ static void virtio_mem_online_page_cb(struct page *page, unsigned int order)
|
|||
generic_online_page(page, order);
|
||||
}
|
||||
|
||||
static uint64_t virtio_mem_send_request(struct virtio_mem *vm,
|
||||
const struct virtio_mem_req *req)
|
||||
{
|
||||
struct scatterlist *sgs[2], sg_req, sg_resp;
|
||||
unsigned int len;
|
||||
int rc;
|
||||
|
||||
/* don't use the request residing on the stack (vaddr) */
|
||||
vm->req = *req;
|
||||
|
||||
/* out: buffer for request */
|
||||
sg_init_one(&sg_req, &vm->req, sizeof(vm->req));
|
||||
sgs[0] = &sg_req;
|
||||
|
||||
/* in: buffer for response */
|
||||
sg_init_one(&sg_resp, &vm->resp, sizeof(vm->resp));
|
||||
sgs[1] = &sg_resp;
|
||||
|
||||
rc = virtqueue_add_sgs(vm->vq, sgs, 1, 1, vm, GFP_KERNEL);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
virtqueue_kick(vm->vq);
|
||||
|
||||
/* wait for a response */
|
||||
wait_event(vm->host_resp, virtqueue_get_buf(vm->vq, &len));
|
||||
|
||||
return virtio16_to_cpu(vm->vdev, vm->resp.type);
|
||||
}
|
||||
|
||||
static int virtio_mem_send_plug_request(struct virtio_mem *vm, uint64_t addr,
|
||||
uint64_t size)
|
||||
{
|
||||
const uint64_t nb_vm_blocks = size / vm->device_block_size;
|
||||
const struct virtio_mem_req req = {
|
||||
.type = cpu_to_virtio16(vm->vdev, VIRTIO_MEM_REQ_PLUG),
|
||||
.u.plug.addr = cpu_to_virtio64(vm->vdev, addr),
|
||||
.u.plug.nb_blocks = cpu_to_virtio16(vm->vdev, nb_vm_blocks),
|
||||
};
|
||||
int rc = -ENOMEM;
|
||||
|
||||
if (atomic_read(&vm->config_changed))
|
||||
return -EAGAIN;
|
||||
|
||||
dev_dbg(&vm->vdev->dev, "plugging memory: 0x%llx - 0x%llx\n", addr,
|
||||
addr + size - 1);
|
||||
|
||||
switch (virtio_mem_send_request(vm, &req)) {
|
||||
case VIRTIO_MEM_RESP_ACK:
|
||||
vm->plugged_size += size;
|
||||
return 0;
|
||||
case VIRTIO_MEM_RESP_NACK:
|
||||
rc = -EAGAIN;
|
||||
break;
|
||||
case VIRTIO_MEM_RESP_BUSY:
|
||||
rc = -ETXTBSY;
|
||||
break;
|
||||
case VIRTIO_MEM_RESP_ERROR:
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dev_dbg(&vm->vdev->dev, "plugging memory failed: %d\n", rc);
|
||||
return rc;
|
||||
WARN_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int virtio_mem_send_unplug_request(struct virtio_mem *vm, uint64_t addr,
|
||||
uint64_t size)
|
||||
{
|
||||
const uint64_t nb_vm_blocks = size / vm->device_block_size;
|
||||
const struct virtio_mem_req req = {
|
||||
.type = cpu_to_virtio16(vm->vdev, VIRTIO_MEM_REQ_UNPLUG),
|
||||
.u.unplug.addr = cpu_to_virtio64(vm->vdev, addr),
|
||||
.u.unplug.nb_blocks = cpu_to_virtio16(vm->vdev, nb_vm_blocks),
|
||||
};
|
||||
int rc = -ENOMEM;
|
||||
|
||||
if (atomic_read(&vm->config_changed))
|
||||
return -EAGAIN;
|
||||
|
||||
dev_dbg(&vm->vdev->dev, "unplugging memory: 0x%llx - 0x%llx\n", addr,
|
||||
addr + size - 1);
|
||||
|
||||
switch (virtio_mem_send_request(vm, &req)) {
|
||||
case VIRTIO_MEM_RESP_ACK:
|
||||
vm->plugged_size -= size;
|
||||
return 0;
|
||||
case VIRTIO_MEM_RESP_BUSY:
|
||||
rc = -ETXTBSY;
|
||||
break;
|
||||
case VIRTIO_MEM_RESP_ERROR:
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dev_dbg(&vm->vdev->dev, "unplugging memory failed: %d\n", rc);
|
||||
return rc;
|
||||
WARN_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int virtio_mem_send_unplug_all_request(struct virtio_mem *vm)
|
||||
{
|
||||
const struct virtio_mem_req req = {
|
||||
.type = cpu_to_virtio16(vm->vdev, VIRTIO_MEM_REQ_UNPLUG_ALL),
|
||||
};
|
||||
int rc = -ENOMEM;
|
||||
|
||||
dev_dbg(&vm->vdev->dev, "unplugging all memory");
|
||||
|
||||
switch (virtio_mem_send_request(vm, &req)) {
|
||||
case VIRTIO_MEM_RESP_ACK:
|
||||
vm->unplug_all_required = false;
|
||||
vm->plugged_size = 0;
|
||||
/* usable region might have shrunk */
|
||||
atomic_set(&vm->config_changed, 1);
|
||||
return 0;
|
||||
case VIRTIO_MEM_RESP_BUSY:
|
||||
rc = -ETXTBSY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dev_dbg(&vm->vdev->dev, "unplugging all memory failed: %d\n", rc);
|
||||
return rc;
|
||||
WARN_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -2296,18 +2196,15 @@ static int virtio_mem_unplug_pending_mb(struct virtio_mem *vm)
|
|||
static void virtio_mem_refresh_config(struct virtio_mem *vm)
|
||||
{
|
||||
const struct range pluggable_range = mhp_get_pluggable_range(true);
|
||||
uint64_t new_plugged_size, usable_region_size, end_addr;
|
||||
|
||||
/* the plugged_size is just a reflection of what _we_ did previously */
|
||||
virtio_cread_le(vm->vdev, struct virtio_mem_config, plugged_size,
|
||||
&new_plugged_size);
|
||||
if (WARN_ON_ONCE(new_plugged_size != vm->plugged_size))
|
||||
vm->plugged_size = new_plugged_size;
|
||||
uint64_t end_addr;
|
||||
|
||||
/* calculate the last usable memory block id */
|
||||
virtio_cread_le(vm->vdev, struct virtio_mem_config,
|
||||
usable_region_size, &usable_region_size);
|
||||
end_addr = min(vm->addr + usable_region_size - 1,
|
||||
/*
|
||||
* Although the end address never changes with virtio-mem platform device
|
||||
* this is the only place with the previous code flow where last_usable_mb_id
|
||||
* is set. So, keep it here for now to minimize diff.
|
||||
*/
|
||||
end_addr = min(vm->addr + vm->region_size - 1,
|
||||
pluggable_range.end);
|
||||
|
||||
if (vm->in_sbm) {
|
||||
|
|
@ -2328,9 +2225,7 @@ static void virtio_mem_refresh_config(struct virtio_mem *vm)
|
|||
*/
|
||||
|
||||
/* see if there is a request to change the size */
|
||||
virtio_cread_le(vm->vdev, struct virtio_mem_config, requested_size,
|
||||
&vm->requested_size);
|
||||
|
||||
vm->requested_size = READ_ONCE(vm->new_requested_size);
|
||||
dev_info(&vm->vdev->dev, "plugged size: 0x%llx", vm->plugged_size);
|
||||
dev_info(&vm->vdev->dev, "requested size: 0x%llx", vm->requested_size);
|
||||
}
|
||||
|
|
@ -2431,26 +2326,6 @@ static enum hrtimer_restart virtio_mem_timer_expired(struct hrtimer *timer)
|
|||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static void virtio_mem_handle_response(struct virtqueue *vq)
|
||||
{
|
||||
struct virtio_mem *vm = vq->vdev->priv;
|
||||
|
||||
wake_up(&vm->host_resp);
|
||||
}
|
||||
|
||||
static int virtio_mem_init_vq(struct virtio_mem *vm)
|
||||
{
|
||||
struct virtqueue *vq;
|
||||
|
||||
vq = virtio_find_single_vq(vm->vdev, virtio_mem_handle_response,
|
||||
"guest-request");
|
||||
if (IS_ERR(vq))
|
||||
return PTR_ERR(vq);
|
||||
vm->vq = vq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virtio_mem_init_hotplug(struct virtio_mem *vm)
|
||||
{
|
||||
const struct range pluggable_range = mhp_get_pluggable_range(true);
|
||||
|
|
@ -2520,6 +2395,22 @@ static int virtio_mem_init_hotplug(struct virtio_mem *vm)
|
|||
vm->offline_threshold);
|
||||
}
|
||||
|
||||
/*
|
||||
* virtio_mem_sbm_plug_sb() & virtio_mem_bbm_plug_bb() call
|
||||
* virtio_mem_send_plug_request() with count * sb_size and
|
||||
* bb_size respectively. Check whether vm->device_block_size
|
||||
* fits evenly.
|
||||
*/
|
||||
if (vm->in_sbm && vm->sbm.sb_size % vm->device_block_size) {
|
||||
dev_err(&vm->vdev->dev, "Device block size %llx doesn't fit in %llx\n",
|
||||
vm->device_block_size, vm->sbm.sb_size);
|
||||
return -EINVAL;
|
||||
} else if (!vm->in_sbm && vm->bbm.bb_size % vm->device_block_size) {
|
||||
dev_err(&vm->vdev->dev, "Device block size %llx doesn't fit in %llx\n",
|
||||
vm->device_block_size, vm->bbm.bb_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_info(&vm->vdev->dev, "memory block size: 0x%lx",
|
||||
memory_block_size_bytes());
|
||||
if (vm->in_sbm)
|
||||
|
|
@ -2656,23 +2547,29 @@ static int virtio_mem_init_kdump(struct virtio_mem *vm)
|
|||
static int virtio_mem_init(struct virtio_mem *vm)
|
||||
{
|
||||
uint16_t node_id;
|
||||
|
||||
if (!vm->vdev->config->get) {
|
||||
dev_err(&vm->vdev->dev, "config access disabled\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
int ret;
|
||||
struct resource res;
|
||||
u32 device_block_size;
|
||||
|
||||
/* Fetch all properties that can't change. */
|
||||
virtio_cread_le(vm->vdev, struct virtio_mem_config, plugged_size,
|
||||
&vm->plugged_size);
|
||||
virtio_cread_le(vm->vdev, struct virtio_mem_config, block_size,
|
||||
&vm->device_block_size);
|
||||
virtio_cread_le(vm->vdev, struct virtio_mem_config, node_id,
|
||||
&node_id);
|
||||
vm->plugged_size = 0;
|
||||
ret = of_property_read_u32(vm->vdev->dev.of_node, "qcom,block-size",
|
||||
&device_block_size);
|
||||
if (ret) {
|
||||
dev_err(&vm->vdev->dev, "Failed to parse qcom,block-size property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
vm->device_block_size = device_block_size;
|
||||
|
||||
node_id = NUMA_NO_NODE;
|
||||
vm->nid = virtio_mem_translate_node_id(vm, node_id);
|
||||
virtio_cread_le(vm->vdev, struct virtio_mem_config, addr, &vm->addr);
|
||||
virtio_cread_le(vm->vdev, struct virtio_mem_config, region_size,
|
||||
&vm->region_size);
|
||||
ret = of_address_to_resource(vm->vdev->dev.of_node, 0, &res);
|
||||
if (ret) {
|
||||
dev_err(&vm->vdev->dev, "Failed to parse reg property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
vm->addr = res.start;
|
||||
vm->region_size = resource_size(&res);
|
||||
|
||||
/* Determine the nid for the device based on the lowest address. */
|
||||
if (vm->nid == NUMA_NO_NODE)
|
||||
|
|
@ -2750,7 +2647,7 @@ static bool virtio_mem_has_memory_added(struct virtio_mem *vm)
|
|||
virtio_mem_range_has_system_ram) == 1;
|
||||
}
|
||||
|
||||
static int virtio_mem_probe(struct virtio_device *vdev)
|
||||
static int virtio_mem_probe(struct platform_device *vdev)
|
||||
{
|
||||
struct virtio_mem *vm;
|
||||
int rc;
|
||||
|
|
@ -2758,14 +2655,16 @@ static int virtio_mem_probe(struct virtio_device *vdev)
|
|||
BUILD_BUG_ON(sizeof(struct virtio_mem_req) != 24);
|
||||
BUILD_BUG_ON(sizeof(struct virtio_mem_resp) != 10);
|
||||
|
||||
vdev->priv = vm = kzalloc(sizeof(*vm), GFP_KERNEL);
|
||||
vm = kzalloc(sizeof(*vm), GFP_KERNEL);
|
||||
if (!vm)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(vdev, vm);
|
||||
|
||||
init_waitqueue_head(&vm->host_resp);
|
||||
vm->vdev = vdev;
|
||||
INIT_WORK(&vm->wq, virtio_mem_run_wq);
|
||||
mutex_init(&vm->hotplug_mutex);
|
||||
spin_lock_init(&vm->config_lock);
|
||||
INIT_LIST_HEAD(&vm->next);
|
||||
spin_lock_init(&vm->removal_lock);
|
||||
hrtimer_init(&vm->retry_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
|
|
@ -2773,17 +2672,10 @@ static int virtio_mem_probe(struct virtio_device *vdev)
|
|||
vm->retry_timer_ms = VIRTIO_MEM_RETRY_TIMER_MIN_MS;
|
||||
vm->in_kdump = is_kdump_kernel();
|
||||
|
||||
/* register the virtqueue */
|
||||
rc = virtio_mem_init_vq(vm);
|
||||
if (rc)
|
||||
goto out_free_vm;
|
||||
|
||||
/* initialize the device by querying the config */
|
||||
rc = virtio_mem_init(vm);
|
||||
if (rc)
|
||||
goto out_del_vq;
|
||||
|
||||
virtio_device_ready(vdev);
|
||||
goto out_free_vm;
|
||||
|
||||
/* trigger a config update to start processing the requested_size */
|
||||
if (!vm->in_kdump) {
|
||||
|
|
@ -2792,11 +2684,10 @@ static int virtio_mem_probe(struct virtio_device *vdev)
|
|||
}
|
||||
|
||||
return 0;
|
||||
out_del_vq:
|
||||
vdev->config->del_vqs(vdev);
|
||||
|
||||
out_free_vm:
|
||||
kfree(vm);
|
||||
vdev->priv = NULL;
|
||||
platform_set_drvdata(vdev, NULL);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
@ -2873,26 +2764,24 @@ static void virtio_mem_deinit_kdump(struct virtio_mem *vm)
|
|||
#endif /* CONFIG_PROC_VMCORE */
|
||||
}
|
||||
|
||||
static void virtio_mem_remove(struct virtio_device *vdev)
|
||||
static int virtio_mem_remove(struct platform_device *vdev)
|
||||
{
|
||||
struct virtio_mem *vm = vdev->priv;
|
||||
struct virtio_mem *vm = platform_get_drvdata(vdev);
|
||||
|
||||
if (vm->in_kdump)
|
||||
virtio_mem_deinit_kdump(vm);
|
||||
else
|
||||
virtio_mem_deinit_hotplug(vm);
|
||||
|
||||
/* reset the device and cleanup the queues */
|
||||
virtio_reset_device(vdev);
|
||||
vdev->config->del_vqs(vdev);
|
||||
|
||||
kfree(vm);
|
||||
vdev->priv = NULL;
|
||||
platform_set_drvdata(vdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virtio_mem_config_changed(struct virtio_device *vdev)
|
||||
static void __maybe_unused virtio_mem_config_changed(struct platform_device *vdev)
|
||||
{
|
||||
struct virtio_mem *vm = vdev->priv;
|
||||
struct virtio_mem *vm = platform_get_drvdata(vdev);
|
||||
|
||||
if (unlikely(vm->in_kdump))
|
||||
return;
|
||||
|
|
@ -2901,52 +2790,22 @@ static void virtio_mem_config_changed(struct virtio_device *vdev)
|
|||
virtio_mem_retry(vm);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int virtio_mem_freeze(struct virtio_device *vdev)
|
||||
{
|
||||
/*
|
||||
* When restarting the VM, all memory is usually unplugged. Don't
|
||||
* allow to suspend/hibernate.
|
||||
*/
|
||||
dev_err(&vdev->dev, "save/restore not supported.\n");
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
static int virtio_mem_restore(struct virtio_device *vdev)
|
||||
{
|
||||
return -EPERM;
|
||||
}
|
||||
#endif
|
||||
|
||||
static unsigned int virtio_mem_features[] = {
|
||||
#if defined(CONFIG_NUMA) && defined(CONFIG_ACPI_NUMA)
|
||||
VIRTIO_MEM_F_ACPI_PXM,
|
||||
#endif
|
||||
VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE,
|
||||
static const struct of_device_id virtio_mem_id_table[] = {
|
||||
{ .compatible = "qcom,virtio-mem" },
|
||||
{ },
|
||||
};
|
||||
|
||||
static const struct virtio_device_id virtio_mem_id_table[] = {
|
||||
{ VIRTIO_ID_MEM, VIRTIO_DEV_ANY_ID },
|
||||
{ 0 },
|
||||
static struct platform_driver virtio_mem_driver = {
|
||||
.driver = {
|
||||
.name = "virtio_mem",
|
||||
.of_match_table = virtio_mem_id_table,
|
||||
},
|
||||
.probe = virtio_mem_probe,
|
||||
.remove = virtio_mem_remove,
|
||||
};
|
||||
|
||||
static struct virtio_driver virtio_mem_driver = {
|
||||
.feature_table = virtio_mem_features,
|
||||
.feature_table_size = ARRAY_SIZE(virtio_mem_features),
|
||||
.driver.name = KBUILD_MODNAME,
|
||||
.driver.owner = THIS_MODULE,
|
||||
.id_table = virtio_mem_id_table,
|
||||
.probe = virtio_mem_probe,
|
||||
.remove = virtio_mem_remove,
|
||||
.config_changed = virtio_mem_config_changed,
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
.freeze = virtio_mem_freeze,
|
||||
.restore = virtio_mem_restore,
|
||||
#endif
|
||||
};
|
||||
|
||||
module_virtio_driver(virtio_mem_driver);
|
||||
MODULE_DEVICE_TABLE(virtio, virtio_mem_id_table);
|
||||
module_platform_driver(virtio_mem_driver);
|
||||
MODULE_DEVICE_TABLE(of, virtio_mem_id_table);
|
||||
MODULE_AUTHOR("David Hildenbrand <david@redhat.com>");
|
||||
MODULE_DESCRIPTION("Virtio-mem driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user