Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+255
View File
@@ -0,0 +1,255 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIRTIO_ANCHOR
bool
config VIRTIO
tristate
select VIRTIO_ANCHOR
help
This option is selected by any driver which implements the virtio
bus, such as CONFIG_VIRTIO_PCI, CONFIG_VIRTIO_MMIO, CONFIG_RPMSG
or CONFIG_S390_GUEST.
config VIRTIO_PCI_LIB
tristate
help
Modern PCI device implementation. This module implements the
basic probe and control for devices which are based on modern
PCI device with possible vendor specific extensions. Any
module that selects this module must depend on PCI.
config VIRTIO_PCI_LIB_LEGACY
tristate
help
Legacy PCI device (Virtio PCI Card 0.9.x Draft and older device)
implementation.
This module implements the basic probe and control for devices
which are based on legacy PCI device. Any module that selects this
module must depend on PCI.
menuconfig VIRTIO_MENU
bool "Virtio drivers"
default y
if VIRTIO_MENU
config VIRTIO_HARDEN_NOTIFICATION
bool "Harden virtio notification"
depends on BROKEN
help
Enable this to harden the device notifications and suppress
those that happen at a time where notifications are illegal.
Experimental: Note that several drivers still have issues that
may cause crashes or hangs when correct handling of
notifications is enforced; depending on the subset of
drivers and devices you use, this may or may not work.
If unsure, say N.
config VIRTIO_PCI
tristate "PCI driver for virtio devices"
depends on PCI
select VIRTIO_PCI_LIB
select VIRTIO
help
This driver provides support for virtio based paravirtual device
drivers over PCI. This requires that your VMM has appropriate PCI
virtio backends. Most QEMU based VMMs should support these devices
(like KVM or Xen).
If unsure, say M.
config VIRTIO_PCI_ADMIN_LEGACY
bool
depends on VIRTIO_PCI && (X86 || COMPILE_TEST)
default y
config VIRTIO_PCI_LEGACY
bool "Support for legacy virtio draft 0.9.X and older devices"
default y
depends on VIRTIO_PCI
select VIRTIO_PCI_LIB_LEGACY
help
Virtio PCI Card 0.9.X Draft (circa 2014) and older device support.
This option enables building a transitional driver, supporting
both devices conforming to Virtio 1 specification, and legacy devices.
If disabled, you get a slightly smaller, non-transitional driver,
with no legacy compatibility.
So look out into your driveway. Do you have a flying car? If
so, you can happily disable this option and virtio will not
break. Otherwise, leave it set. Unless you're testing what
life will be like in The Future.
If unsure, say Y.
config VIRTIO_VDPA
tristate "vDPA driver for virtio devices"
depends on VDPA
select VIRTIO
help
This driver provides support for virtio based paravirtual
device driver over vDPA bus. For this to be useful, you need
an appropriate vDPA device implementation that operates on a
physical device to allow the datapath of virtio to be
offloaded to hardware.
If unsure, say M.
config VIRTIO_PMEM
tristate "Support for virtio pmem driver"
depends on VIRTIO
depends on LIBNVDIMM
help
This driver provides access to virtio-pmem devices, storage devices
that are mapped into the physical address space - similar to NVDIMMs
- with a virtio-based flushing interface.
If unsure, say Y.
config VIRTIO_BALLOON
tristate "Virtio balloon driver"
depends on VIRTIO
select BALLOON
select PAGE_REPORTING
help
This driver supports increasing and decreasing the amount
of memory within a KVM guest.
If unsure, say M.
config VIRTIO_MEM
tristate "Virtio mem driver"
depends on X86_64 || ARM64 || RISCV || S390
depends on VIRTIO
depends on MEMORY_HOTPLUG
depends on MEMORY_HOTREMOVE
depends on CONTIG_ALLOC
depends on EXCLUSIVE_SYSTEM_RAM
help
This driver provides access to virtio-mem paravirtualized memory
devices, allowing to hotplug and hotunplug memory.
This driver currently supports x86-64, arm64, riscv and s390.
Although it should compile on other architectures that implement
memory hot(un)plug, architecture-specific and/or common
code changes may be required for virtio-mem, kdump and kexec to
work as expected.
If unsure, say M.
config VIRTIO_INPUT
tristate "Virtio input driver"
depends on VIRTIO
depends on INPUT
help
This driver supports virtio input devices such as
keyboards, mice and tablets.
If unsure, say M.
config VIRTIO_MMIO
tristate "Platform bus driver for memory mapped virtio devices"
depends on HAS_IOMEM && HAS_DMA
select VIRTIO
help
This drivers provides support for memory mapped virtio
platform device driver.
If unsure, say N.
config VIRTIO_MMIO_CMDLINE_DEVICES
bool "Memory mapped virtio devices parameter parsing"
depends on VIRTIO_MMIO
help
Allow virtio-mmio devices instantiation via the kernel command line
or module parameters. Be aware that using incorrect parameters (base
address in particular) can crash your system - you have been warned.
See Documentation/admin-guide/kernel-parameters.rst for details.
If unsure, say 'N'.
config VIRTIO_DMA_SHARED_BUFFER
tristate
depends on DMA_SHARED_BUFFER
help
This option adds a flavor of dma buffers that are backed by
virtio resources.
config VIRTIO_DEBUG
bool "Debug facilities"
depends on VIRTIO
help
Enable this to expose debug facilities over debugfs.
This allows to debug features, to see what features the device
advertises and to set filter for features used by driver.
If unsure, say N.
config VIRTIO_RTC
tristate "Virtio RTC driver"
depends on VIRTIO
depends on PTP_1588_CLOCK_OPTIONAL
help
This driver provides current time from a Virtio RTC device. The driver
provides the time through one or more clocks. The Virtio RTC PTP
clocks and/or the Real Time Clock driver for Virtio RTC must be
enabled to expose the clocks to userspace.
To compile this code as a module, choose M here: the module will be
called virtio_rtc.
If unsure, say M.
if VIRTIO_RTC
comment "WARNING: Consider enabling VIRTIO_RTC_PTP and/or VIRTIO_RTC_CLASS."
depends on !VIRTIO_RTC_PTP && !VIRTIO_RTC_CLASS
comment "Enable PTP_1588_CLOCK in order to enable VIRTIO_RTC_PTP."
depends on PTP_1588_CLOCK=n
config VIRTIO_RTC_PTP
bool "Virtio RTC PTP clocks"
default y
depends on PTP_1588_CLOCK
help
This exposes any Virtio RTC clocks as PTP Hardware Clocks (PHCs) to
userspace. The PHC sysfs attribute "clock_name" describes the clock
type.
If unsure, say Y.
config VIRTIO_RTC_ARM
bool "Virtio RTC cross-timestamping using Arm Generic Timer"
default y
depends on VIRTIO_RTC_PTP && ARM_ARCH_TIMER
help
This enables Virtio RTC cross-timestamping using the Arm Generic Timer.
It only has an effect if the Virtio RTC device also supports this. The
cross-timestamp is available through the PTP clock driver precise
cross-timestamp ioctl (PTP_SYS_OFFSET_PRECISE2 aka
PTP_SYS_OFFSET_PRECISE).
If unsure, say Y.
comment "Enable RTC_CLASS in order to enable VIRTIO_RTC_CLASS."
depends on RTC_CLASS=n
config VIRTIO_RTC_CLASS
bool "Real Time Clock driver for Virtio RTC"
default y
depends on RTC_CLASS
help
This exposes the Virtio RTC UTC-like clock as a Linux Real Time Clock.
It only has an effect if the Virtio RTC device has a UTC-like clock
which smears leap seconds to avoid steps. The Real Time Clock is
read-only, and may support setting an alarm.
If unsure, say Y.
endif # VIRTIO_RTC
endif # VIRTIO_MENU
+21
View File
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VIRTIO) += virtio.o virtio_ring.o
obj-$(CONFIG_VIRTIO_ANCHOR) += virtio_anchor.o
obj-$(CONFIG_VIRTIO_PCI_LIB) += virtio_pci_modern_dev.o
obj-$(CONFIG_VIRTIO_PCI_LIB_LEGACY) += virtio_pci_legacy_dev.o
obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o
obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o
virtio_pci-y := virtio_pci_modern.o virtio_pci_common.o
virtio_pci-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o
virtio_pci-$(CONFIG_VIRTIO_PCI_ADMIN_LEGACY) += virtio_pci_admin_legacy_io.o
obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o
obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o
obj-$(CONFIG_VIRTIO_VDPA) += virtio_vdpa.o
obj-$(CONFIG_VIRTIO_MEM) += virtio_mem.o
obj-$(CONFIG_VIRTIO_DMA_SHARED_BUFFER) += virtio_dma_buf.o
obj-$(CONFIG_VIRTIO_DEBUG) += virtio_debug.o
obj-$(CONFIG_VIRTIO_RTC) += virtio_rtc.o
virtio_rtc-y := virtio_rtc_driver.o
virtio_rtc-$(CONFIG_VIRTIO_RTC_PTP) += virtio_rtc_ptp.o
virtio_rtc-$(CONFIG_VIRTIO_RTC_ARM) += virtio_rtc_arm.o
virtio_rtc-$(CONFIG_VIRTIO_RTC_CLASS) += virtio_rtc_class.o
+739
View File
@@ -0,0 +1,739 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/virtio.h>
#include <linux/spinlock.h>
#include <linux/virtio_config.h>
#include <linux/virtio_anchor.h>
#include <linux/module.h>
#include <linux/idr.h>
#include <linux/of.h>
#include <uapi/linux/virtio_ids.h>
/* Unique numbering for virtio devices. */
static DEFINE_IDA(virtio_index_ida);
static ssize_t device_show(struct device *_d,
struct device_attribute *attr, char *buf)
{
struct virtio_device *dev = dev_to_virtio(_d);
return sysfs_emit(buf, "0x%04x\n", dev->id.device);
}
static DEVICE_ATTR_RO(device);
static ssize_t vendor_show(struct device *_d,
struct device_attribute *attr, char *buf)
{
struct virtio_device *dev = dev_to_virtio(_d);
return sysfs_emit(buf, "0x%04x\n", dev->id.vendor);
}
static DEVICE_ATTR_RO(vendor);
static ssize_t status_show(struct device *_d,
struct device_attribute *attr, char *buf)
{
struct virtio_device *dev = dev_to_virtio(_d);
return sysfs_emit(buf, "0x%08x\n", dev->config->get_status(dev));
}
static DEVICE_ATTR_RO(status);
static ssize_t modalias_show(struct device *_d,
struct device_attribute *attr, char *buf)
{
struct virtio_device *dev = dev_to_virtio(_d);
return sysfs_emit(buf, "virtio:d%08Xv%08X\n",
dev->id.device, dev->id.vendor);
}
static DEVICE_ATTR_RO(modalias);
static ssize_t features_show(struct device *_d,
struct device_attribute *attr, char *buf)
{
struct virtio_device *dev = dev_to_virtio(_d);
unsigned int i;
ssize_t len = 0;
/* We actually represent this as a bitstring, as it could be
* arbitrary length in future. */
for (i = 0; i < VIRTIO_FEATURES_BITS; i++)
len += sysfs_emit_at(buf, len, "%c",
__virtio_test_bit(dev, i) ? '1' : '0');
len += sysfs_emit_at(buf, len, "\n");
return len;
}
static DEVICE_ATTR_RO(features);
static struct attribute *virtio_dev_attrs[] = {
&dev_attr_device.attr,
&dev_attr_vendor.attr,
&dev_attr_status.attr,
&dev_attr_modalias.attr,
&dev_attr_features.attr,
NULL,
};
ATTRIBUTE_GROUPS(virtio_dev);
static inline int virtio_id_match(const struct virtio_device *dev,
const struct virtio_device_id *id)
{
if (id->device != dev->id.device && id->device != VIRTIO_DEV_ANY_ID)
return 0;
return id->vendor == VIRTIO_DEV_ANY_ID || id->vendor == dev->id.vendor;
}
/* This looks through all the IDs a driver claims to support. If any of them
* match, we return 1 and the kernel will call virtio_dev_probe(). */
static int virtio_dev_match(struct device *_dv, const struct device_driver *_dr)
{
unsigned int i;
struct virtio_device *dev = dev_to_virtio(_dv);
const struct virtio_device_id *ids;
ids = drv_to_virtio(_dr)->id_table;
for (i = 0; ids[i].device; i++)
if (virtio_id_match(dev, &ids[i]))
return 1;
return 0;
}
static int virtio_uevent(const struct device *_dv, struct kobj_uevent_env *env)
{
const struct virtio_device *dev = dev_to_virtio(_dv);
return add_uevent_var(env, "MODALIAS=virtio:d%08Xv%08X",
dev->id.device, dev->id.vendor);
}
void virtio_check_driver_offered_feature(const struct virtio_device *vdev,
unsigned int fbit)
{
unsigned int i;
struct virtio_driver *drv = drv_to_virtio(vdev->dev.driver);
for (i = 0; i < drv->feature_table_size; i++)
if (drv->feature_table[i] == fbit)
return;
if (drv->feature_table_legacy) {
for (i = 0; i < drv->feature_table_size_legacy; i++)
if (drv->feature_table_legacy[i] == fbit)
return;
}
BUG();
}
EXPORT_SYMBOL_GPL(virtio_check_driver_offered_feature);
static void __virtio_config_changed(struct virtio_device *dev)
{
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
if (!dev->config_core_enabled || dev->config_driver_disabled)
dev->config_change_pending = true;
else if (drv && drv->config_changed) {
drv->config_changed(dev);
dev->config_change_pending = false;
}
}
void virtio_config_changed(struct virtio_device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->config_lock, flags);
__virtio_config_changed(dev);
spin_unlock_irqrestore(&dev->config_lock, flags);
}
EXPORT_SYMBOL_GPL(virtio_config_changed);
/**
* virtio_config_driver_disable - disable config change reporting by drivers
* @dev: the device to disable
*
* This is only allowed to be called by a driver and disabling can't
* be nested.
*/
void virtio_config_driver_disable(struct virtio_device *dev)
{
spin_lock_irq(&dev->config_lock);
dev->config_driver_disabled = true;
spin_unlock_irq(&dev->config_lock);
}
EXPORT_SYMBOL_GPL(virtio_config_driver_disable);
/**
* virtio_config_driver_enable - enable config change reporting by drivers
* @dev: the device to enable
*
* This is only allowed to be called by a driver and enabling can't
* be nested.
*/
void virtio_config_driver_enable(struct virtio_device *dev)
{
spin_lock_irq(&dev->config_lock);
dev->config_driver_disabled = false;
if (dev->config_change_pending)
__virtio_config_changed(dev);
spin_unlock_irq(&dev->config_lock);
}
EXPORT_SYMBOL_GPL(virtio_config_driver_enable);
static void virtio_config_core_disable(struct virtio_device *dev)
{
spin_lock_irq(&dev->config_lock);
dev->config_core_enabled = false;
spin_unlock_irq(&dev->config_lock);
}
static void virtio_config_core_enable(struct virtio_device *dev)
{
spin_lock_irq(&dev->config_lock);
dev->config_core_enabled = true;
if (dev->config_change_pending)
__virtio_config_changed(dev);
spin_unlock_irq(&dev->config_lock);
}
void virtio_add_status(struct virtio_device *dev, unsigned int status)
{
might_sleep();
dev->config->set_status(dev, dev->config->get_status(dev) | status);
}
EXPORT_SYMBOL_GPL(virtio_add_status);
/* Do some validation, then set FEATURES_OK */
static int virtio_features_ok(struct virtio_device *dev)
{
unsigned int status;
might_sleep();
if (virtio_check_mem_acc_cb(dev)) {
if (!virtio_has_feature(dev, VIRTIO_F_VERSION_1)) {
dev_warn(&dev->dev,
"device must provide VIRTIO_F_VERSION_1\n");
return -ENODEV;
}
if (!virtio_has_feature(dev, VIRTIO_F_ACCESS_PLATFORM)) {
dev_warn(&dev->dev,
"device must provide VIRTIO_F_ACCESS_PLATFORM\n");
return -ENODEV;
}
}
if (!virtio_has_feature(dev, VIRTIO_F_VERSION_1))
return 0;
virtio_add_status(dev, VIRTIO_CONFIG_S_FEATURES_OK);
status = dev->config->get_status(dev);
if (!(status & VIRTIO_CONFIG_S_FEATURES_OK)) {
dev_err(&dev->dev, "virtio: device refuses features: %x\n",
status);
return -ENODEV;
}
return 0;
}
/**
* virtio_reset_device - quiesce device for removal
* @dev: the device to reset
*
* Prevents device from sending interrupts and accessing memory.
*
* Generally used for cleanup during driver / device removal.
*
* Once this has been invoked, caller must ensure that
* virtqueue_notify / virtqueue_kick are not in progress.
*
* Note: this guarantees that vq callbacks are not in progress, however caller
* is responsible for preventing access from other contexts, such as a system
* call/workqueue/bh. Invoking virtio_break_device then flushing any such
* contexts is one way to handle that.
* */
void virtio_reset_device(struct virtio_device *dev)
{
#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
/*
* The below virtio_synchronize_cbs() guarantees that any
* interrupt for this line arriving after
* virtio_synchronize_vqs() has completed is guaranteed to see
* vq->broken as true.
*/
virtio_break_device(dev);
virtio_synchronize_cbs(dev);
#endif
dev->config->reset(dev);
}
EXPORT_SYMBOL_GPL(virtio_reset_device);
static int virtio_dev_probe(struct device *_d)
{
int err, i;
struct virtio_device *dev = dev_to_virtio(_d);
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
u64 device_features[VIRTIO_FEATURES_U64S];
u64 driver_features[VIRTIO_FEATURES_U64S];
u64 driver_features_legacy;
/* We have a driver! */
virtio_add_status(dev, VIRTIO_CONFIG_S_DRIVER);
/* Figure out what features the device supports. */
virtio_get_features(dev, device_features);
/* Figure out what features the driver supports. */
virtio_features_zero(driver_features);
for (i = 0; i < drv->feature_table_size; i++) {
unsigned int f = drv->feature_table[i];
if (!WARN_ON_ONCE(f >= VIRTIO_FEATURES_BITS))
virtio_features_set_bit(driver_features, f);
}
/* Some drivers have a separate feature table for virtio v1.0 */
if (drv->feature_table_legacy) {
driver_features_legacy = 0;
for (i = 0; i < drv->feature_table_size_legacy; i++) {
unsigned int f = drv->feature_table_legacy[i];
if (!WARN_ON_ONCE(f >= 64))
driver_features_legacy |= (1ULL << f);
}
} else {
driver_features_legacy = driver_features[0];
}
if (virtio_features_test_bit(device_features, VIRTIO_F_VERSION_1)) {
for (i = 0; i < VIRTIO_FEATURES_U64S; ++i)
dev->features_array[i] = driver_features[i] &
device_features[i];
} else {
virtio_features_from_u64(dev->features_array,
driver_features_legacy &
device_features[0]);
}
/* When debugging, user may filter some features by hand. */
virtio_debug_device_filter_features(dev);
/* Transport features always preserved to pass to finalize_features. */
for (i = VIRTIO_TRANSPORT_F_START; i < VIRTIO_TRANSPORT_F_END; i++)
if (virtio_features_test_bit(device_features, i))
__virtio_set_bit(dev, i);
err = dev->config->finalize_features(dev);
if (err)
goto err;
if (drv->validate) {
u64 features[VIRTIO_FEATURES_U64S];
virtio_features_copy(features, dev->features_array);
err = drv->validate(dev);
if (err)
goto err;
/* Did validation change any features? Then write them again. */
if (!virtio_features_equal(features, dev->features_array)) {
err = dev->config->finalize_features(dev);
if (err)
goto err;
}
}
err = virtio_features_ok(dev);
if (err)
goto err;
err = drv->probe(dev);
if (err)
goto err;
/* If probe didn't do it, mark device DRIVER_OK ourselves. */
if (!(dev->config->get_status(dev) & VIRTIO_CONFIG_S_DRIVER_OK))
virtio_device_ready(dev);
if (drv->scan)
drv->scan(dev);
virtio_config_core_enable(dev);
return 0;
err:
virtio_add_status(dev, VIRTIO_CONFIG_S_FAILED);
return err;
}
static void virtio_dev_remove(struct device *_d)
{
struct virtio_device *dev = dev_to_virtio(_d);
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
virtio_config_core_disable(dev);
drv->remove(dev);
/* Driver should have reset device. */
WARN_ON_ONCE(dev->config->get_status(dev));
/* Acknowledge the device's existence again. */
virtio_add_status(dev, VIRTIO_CONFIG_S_ACKNOWLEDGE);
of_node_put(dev->dev.of_node);
}
/*
* virtio_irq_get_affinity - get IRQ affinity mask for device
* @_d: ptr to dev structure
* @irq_vec: interrupt vector number
*
* Return the CPU affinity mask for @_d and @irq_vec.
*/
static const struct cpumask *virtio_irq_get_affinity(struct device *_d,
unsigned int irq_vec)
{
struct virtio_device *dev = dev_to_virtio(_d);
if (!dev->config->get_vq_affinity)
return NULL;
return dev->config->get_vq_affinity(dev, irq_vec);
}
static void virtio_dev_shutdown(struct device *_d)
{
struct virtio_device *dev = dev_to_virtio(_d);
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
/*
* Stop accesses to or from the device.
* We only need to do it if there's a driver - no accesses otherwise.
*/
if (!drv)
return;
/* If the driver has its own shutdown method, use that. */
if (drv->shutdown) {
drv->shutdown(dev);
return;
}
/*
* Some devices get wedged if you kick them after they are
* reset. Mark all vqs as broken to make sure we don't.
*/
virtio_break_device(dev);
/*
* Guarantee that any callback will see vq->broken as true.
*/
virtio_synchronize_cbs(dev);
/*
* As IOMMUs are reset on shutdown, this will block device access to memory.
* Some devices get wedged if this happens, so reset to make sure it does not.
*/
dev->config->reset(dev);
}
static int virtio_dev_num_vf(struct device *dev)
{
struct virtio_device *vdev = dev_to_virtio(dev);
return dev_num_vf(vdev->dev.parent);
}
static const struct bus_type virtio_bus = {
.name = "virtio",
.match = virtio_dev_match,
.dev_groups = virtio_dev_groups,
.uevent = virtio_uevent,
.probe = virtio_dev_probe,
.remove = virtio_dev_remove,
.irq_get_affinity = virtio_irq_get_affinity,
.shutdown = virtio_dev_shutdown,
.num_vf = virtio_dev_num_vf,
};
int __register_virtio_driver(struct virtio_driver *driver, struct module *owner)
{
/* Catch this early. */
BUG_ON(driver->feature_table_size && !driver->feature_table);
driver->driver.bus = &virtio_bus;
driver->driver.owner = owner;
return driver_register(&driver->driver);
}
EXPORT_SYMBOL_GPL(__register_virtio_driver);
void unregister_virtio_driver(struct virtio_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL_GPL(unregister_virtio_driver);
static int virtio_device_of_init(struct virtio_device *dev)
{
struct device_node *np, *pnode = dev_of_node(dev->dev.parent);
char compat[] = "virtio,deviceXXXXXXXX";
int ret, count;
if (!pnode)
return 0;
count = of_get_available_child_count(pnode);
if (!count)
return 0;
/* There can be only 1 child node */
if (WARN_ON(count > 1))
return -EINVAL;
np = of_get_next_available_child(pnode, NULL);
if (WARN_ON(!np))
return -ENODEV;
ret = snprintf(compat, sizeof(compat), "virtio,device%x", dev->id.device);
BUG_ON(ret >= sizeof(compat));
/*
* On powerpc/pseries virtio devices are PCI devices so PCI
* vendor/device ids play the role of the "compatible" property.
* Simply don't init of_node in this case.
*/
if (!of_device_is_compatible(np, compat)) {
ret = 0;
goto out;
}
dev->dev.of_node = np;
return 0;
out:
of_node_put(np);
return ret;
}
/**
* register_virtio_device - register virtio device
* @dev : virtio device to be registered
*
* On error, the caller must call put_device on &@dev->dev (and not kfree),
* as another code path may have obtained a reference to @dev.
*
* Returns: 0 on success, -error on failure
*/
int register_virtio_device(struct virtio_device *dev)
{
int err;
dev->dev.bus = &virtio_bus;
device_initialize(&dev->dev);
/* Assign a unique device index and hence name. */
err = ida_alloc(&virtio_index_ida, GFP_KERNEL);
if (err < 0)
goto out;
dev->index = err;
err = dev_set_name(&dev->dev, "virtio%u", dev->index);
if (err)
goto out_ida_remove;
err = virtio_device_of_init(dev);
if (err)
goto out_ida_remove;
spin_lock_init(&dev->config_lock);
dev->config_driver_disabled = false;
dev->config_core_enabled = false;
dev->config_change_pending = false;
INIT_LIST_HEAD(&dev->vqs);
spin_lock_init(&dev->vqs_list_lock);
/* We always start by resetting the device, in case a previous
* driver messed it up. This also tests that code path a little. */
virtio_reset_device(dev);
/* Acknowledge that we've seen the device. */
virtio_add_status(dev, VIRTIO_CONFIG_S_ACKNOWLEDGE);
virtio_debug_device_init(dev);
/*
* device_add() causes the bus infrastructure to look for a matching
* driver.
*/
err = device_add(&dev->dev);
if (err)
goto out_of_node_put;
return 0;
out_of_node_put:
of_node_put(dev->dev.of_node);
out_ida_remove:
ida_free(&virtio_index_ida, dev->index);
out:
virtio_add_status(dev, VIRTIO_CONFIG_S_FAILED);
return err;
}
EXPORT_SYMBOL_GPL(register_virtio_device);
bool is_virtio_device(struct device *dev)
{
return dev->bus == &virtio_bus;
}
EXPORT_SYMBOL_GPL(is_virtio_device);
void unregister_virtio_device(struct virtio_device *dev)
{
int index = dev->index; /* save for after device release */
device_unregister(&dev->dev);
virtio_debug_device_exit(dev);
ida_free(&virtio_index_ida, index);
}
EXPORT_SYMBOL_GPL(unregister_virtio_device);
static int virtio_device_restore_priv(struct virtio_device *dev, bool restore)
{
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
int ret;
/* We always start by resetting the device, in case a previous
* driver messed it up. */
virtio_reset_device(dev);
/* Acknowledge that we've seen the device. */
virtio_add_status(dev, VIRTIO_CONFIG_S_ACKNOWLEDGE);
/* Maybe driver failed before freeze.
* Restore the failed status, for debugging. */
if (dev->failed)
virtio_add_status(dev, VIRTIO_CONFIG_S_FAILED);
if (!drv)
return 0;
/* We have a driver! */
virtio_add_status(dev, VIRTIO_CONFIG_S_DRIVER);
ret = dev->config->finalize_features(dev);
if (ret)
goto err;
ret = virtio_features_ok(dev);
if (ret)
goto err;
if (restore) {
if (drv->restore) {
ret = drv->restore(dev);
if (ret)
goto err;
}
} else {
ret = drv->reset_done(dev);
if (ret)
goto err;
}
/* If restore didn't do it, mark device DRIVER_OK ourselves. */
if (!(dev->config->get_status(dev) & VIRTIO_CONFIG_S_DRIVER_OK))
virtio_device_ready(dev);
virtio_config_core_enable(dev);
return 0;
err:
virtio_add_status(dev, VIRTIO_CONFIG_S_FAILED);
return ret;
}
#ifdef CONFIG_PM_SLEEP
int virtio_device_freeze(struct virtio_device *dev)
{
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
int ret;
virtio_config_core_disable(dev);
dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED;
if (drv && drv->freeze) {
ret = drv->freeze(dev);
if (ret) {
virtio_config_core_enable(dev);
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(virtio_device_freeze);
int virtio_device_restore(struct virtio_device *dev)
{
return virtio_device_restore_priv(dev, true);
}
EXPORT_SYMBOL_GPL(virtio_device_restore);
#endif
int virtio_device_reset_prepare(struct virtio_device *dev)
{
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
int ret;
if (!drv || !drv->reset_prepare)
return -EOPNOTSUPP;
virtio_config_core_disable(dev);
dev->failed = dev->config->get_status(dev) & VIRTIO_CONFIG_S_FAILED;
ret = drv->reset_prepare(dev);
if (ret) {
virtio_config_core_enable(dev);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(virtio_device_reset_prepare);
int virtio_device_reset_done(struct virtio_device *dev)
{
struct virtio_driver *drv = drv_to_virtio(dev->dev.driver);
if (!drv || !drv->reset_done)
return -EOPNOTSUPP;
return virtio_device_restore_priv(dev, false);
}
EXPORT_SYMBOL_GPL(virtio_device_reset_done);
static int virtio_init(void)
{
BUILD_BUG_ON(offsetof(struct virtio_device, features) !=
offsetof(struct virtio_device, features_array[0]));
if (bus_register(&virtio_bus) != 0)
panic("virtio bus registration failed");
virtio_debug_init();
return 0;
}
static void __exit virtio_exit(void)
{
virtio_debug_exit();
bus_unregister(&virtio_bus);
ida_destroy(&virtio_index_ida);
}
core_initcall(virtio_init);
module_exit(virtio_exit);
MODULE_DESCRIPTION("Virtio core interface");
MODULE_LICENSE("GPL");
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/virtio.h>
#include <linux/virtio_anchor.h>
bool virtio_require_restricted_mem_acc(struct virtio_device *dev)
{
return true;
}
EXPORT_SYMBOL_GPL(virtio_require_restricted_mem_acc);
static bool virtio_no_restricted_mem_acc(struct virtio_device *dev)
{
return false;
}
bool (*virtio_check_mem_acc_cb)(struct virtio_device *dev) =
virtio_no_restricted_mem_acc;
EXPORT_SYMBOL_GPL(virtio_check_mem_acc_cb);
File diff suppressed because it is too large Load Diff
+117
View File
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/debugfs.h>
static struct dentry *virtio_debugfs_dir;
static int virtio_debug_device_features_show(struct seq_file *s, void *data)
{
u64 device_features[VIRTIO_FEATURES_U64S];
struct virtio_device *dev = s->private;
unsigned int i;
virtio_get_features(dev, device_features);
for (i = 0; i < VIRTIO_FEATURES_BITS; i++) {
if (virtio_features_test_bit(device_features, i))
seq_printf(s, "%u\n", i);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(virtio_debug_device_features);
static int virtio_debug_filter_features_show(struct seq_file *s, void *data)
{
struct virtio_device *dev = s->private;
unsigned int i;
for (i = 0; i < VIRTIO_FEATURES_BITS; i++) {
if (virtio_features_test_bit(dev->debugfs_filter_features, i))
seq_printf(s, "%u\n", i);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(virtio_debug_filter_features);
static int virtio_debug_filter_features_clear(void *data, u64 val)
{
struct virtio_device *dev = data;
if (val == 1)
virtio_features_zero(dev->debugfs_filter_features);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(virtio_debug_filter_features_clear_fops, NULL,
virtio_debug_filter_features_clear, "%llu\n");
static int virtio_debug_filter_feature_add(void *data, u64 val)
{
struct virtio_device *dev = data;
if (val >= VIRTIO_FEATURES_BITS)
return -EINVAL;
virtio_features_set_bit(dev->debugfs_filter_features, val);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(virtio_debug_filter_feature_add_fops, NULL,
virtio_debug_filter_feature_add, "%llu\n");
static int virtio_debug_filter_feature_del(void *data, u64 val)
{
struct virtio_device *dev = data;
if (val >= VIRTIO_FEATURES_BITS)
return -EINVAL;
virtio_features_clear_bit(dev->debugfs_filter_features, val);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(virtio_debug_filter_feature_del_fops, NULL,
virtio_debug_filter_feature_del, "%llu\n");
void virtio_debug_device_init(struct virtio_device *dev)
{
dev->debugfs_dir = debugfs_create_dir(dev_name(&dev->dev),
virtio_debugfs_dir);
debugfs_create_file("device_features", 0400, dev->debugfs_dir, dev,
&virtio_debug_device_features_fops);
debugfs_create_file("filter_features", 0400, dev->debugfs_dir, dev,
&virtio_debug_filter_features_fops);
debugfs_create_file("filter_features_clear", 0200, dev->debugfs_dir, dev,
&virtio_debug_filter_features_clear_fops);
debugfs_create_file("filter_feature_add", 0200, dev->debugfs_dir, dev,
&virtio_debug_filter_feature_add_fops);
debugfs_create_file("filter_feature_del", 0200, dev->debugfs_dir, dev,
&virtio_debug_filter_feature_del_fops);
}
EXPORT_SYMBOL_GPL(virtio_debug_device_init);
void virtio_debug_device_filter_features(struct virtio_device *dev)
{
virtio_features_andnot(dev->features_array, dev->features_array,
dev->debugfs_filter_features);
}
EXPORT_SYMBOL_GPL(virtio_debug_device_filter_features);
void virtio_debug_device_exit(struct virtio_device *dev)
{
debugfs_remove_recursive(dev->debugfs_dir);
}
EXPORT_SYMBOL_GPL(virtio_debug_device_exit);
void virtio_debug_init(void)
{
virtio_debugfs_dir = debugfs_create_dir("virtio", NULL);
}
EXPORT_SYMBOL_GPL(virtio_debug_init);
void virtio_debug_exit(void)
{
debugfs_remove_recursive(virtio_debugfs_dir);
}
EXPORT_SYMBOL_GPL(virtio_debug_exit);
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* dma-bufs for virtio exported objects
*
* Copyright (C) 2020 Google, Inc.
*/
#include <linux/module.h>
#include <linux/virtio_dma_buf.h>
/**
* virtio_dma_buf_export - Creates a new dma-buf for a virtio exported object
* @exp_info: [in] see dma_buf_export(). ops MUST refer to a dma_buf_ops
* struct embedded in a virtio_dma_buf_ops.
*
* This wraps dma_buf_export() to allow virtio drivers to create a dma-buf
* for an virtio exported object that can be queried by other virtio drivers
* for the object's UUID.
*/
struct dma_buf *virtio_dma_buf_export
(const struct dma_buf_export_info *exp_info)
{
const struct virtio_dma_buf_ops *virtio_ops =
container_of(exp_info->ops,
const struct virtio_dma_buf_ops, ops);
if (!exp_info->ops ||
exp_info->ops->attach != &virtio_dma_buf_attach ||
!virtio_ops->get_uuid) {
return ERR_PTR(-EINVAL);
}
return dma_buf_export(exp_info);
}
EXPORT_SYMBOL(virtio_dma_buf_export);
/**
* virtio_dma_buf_attach - mandatory attach callback for virtio dma-bufs
* @dma_buf: [in] buffer to attach
* @attach: [in] attachment structure
*/
int virtio_dma_buf_attach(struct dma_buf *dma_buf,
struct dma_buf_attachment *attach)
{
int ret;
const struct virtio_dma_buf_ops *ops =
container_of(dma_buf->ops,
const struct virtio_dma_buf_ops, ops);
if (ops->device_attach) {
ret = ops->device_attach(dma_buf, attach);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL(virtio_dma_buf_attach);
/**
* is_virtio_dma_buf - returns true if the given dma-buf is a virtio dma-buf
* @dma_buf: buffer to query
*/
bool is_virtio_dma_buf(struct dma_buf *dma_buf)
{
return dma_buf->ops->attach == &virtio_dma_buf_attach;
}
EXPORT_SYMBOL(is_virtio_dma_buf);
/**
* virtio_dma_buf_get_uuid - gets a virtio dma-buf's exported object's uuid
* @dma_buf: [in] buffer to query
* @uuid: [out] the uuid
*
* Returns: 0 on success, negative on failure.
*/
int virtio_dma_buf_get_uuid(struct dma_buf *dma_buf,
uuid_t *uuid)
{
const struct virtio_dma_buf_ops *ops =
container_of(dma_buf->ops,
const struct virtio_dma_buf_ops, ops);
if (!is_virtio_dma_buf(dma_buf))
return -EINVAL;
return ops->get_uuid(dma_buf, uuid);
}
EXPORT_SYMBOL(virtio_dma_buf_get_uuid);
MODULE_DESCRIPTION("dma-bufs for virtio exported objects");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("DMA_BUF");
+421
View File
@@ -0,0 +1,421 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/module.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <uapi/linux/virtio_ids.h>
#include <uapi/linux/virtio_input.h>
#include <linux/input/mt.h>
struct virtio_input {
struct virtio_device *vdev;
struct input_dev *idev;
char name[64];
char serial[64];
char phys[64];
struct virtqueue *evt, *sts;
__dma_from_device_group_begin();
struct virtio_input_event evts[64];
__dma_from_device_group_end();
spinlock_t lock;
bool ready;
};
static void virtinput_queue_evtbuf(struct virtio_input *vi,
struct virtio_input_event *evtbuf)
{
struct scatterlist sg[1];
sg_init_one(sg, evtbuf, sizeof(*evtbuf));
virtqueue_add_inbuf_cache_clean(vi->evt, sg, 1, evtbuf, GFP_ATOMIC);
}
static void virtinput_recv_events(struct virtqueue *vq)
{
struct virtio_input *vi = vq->vdev->priv;
struct virtio_input_event *event;
unsigned long flags;
unsigned int len;
spin_lock_irqsave(&vi->lock, flags);
if (vi->ready) {
while ((event = virtqueue_get_buf(vi->evt, &len)) != NULL) {
spin_unlock_irqrestore(&vi->lock, flags);
input_event(vi->idev,
le16_to_cpu(event->type),
le16_to_cpu(event->code),
le32_to_cpu(event->value));
spin_lock_irqsave(&vi->lock, flags);
virtinput_queue_evtbuf(vi, event);
}
virtqueue_kick(vq);
}
spin_unlock_irqrestore(&vi->lock, flags);
}
/*
* On error we are losing the status update, which isn't critical as
* this is typically used for stuff like keyboard leds.
*/
static int virtinput_send_status(struct virtio_input *vi,
u16 type, u16 code, s32 value)
{
struct virtio_input_event *stsbuf;
struct scatterlist sg[1];
unsigned long flags;
int rc;
/*
* Since 29cc309d8bf1 (HID: hid-multitouch: forward MSC_TIMESTAMP),
* EV_MSC/MSC_TIMESTAMP is added to each before EV_SYN event.
* EV_MSC is configured as INPUT_PASS_TO_ALL.
* In case of touch device:
* BE pass EV_MSC/MSC_TIMESTAMP to FE on receiving event from evdev.
* FE pass EV_MSC/MSC_TIMESTAMP back to BE.
* BE writes EV_MSC/MSC_TIMESTAMP to evdev due to INPUT_PASS_TO_ALL.
* BE receives extra EV_MSC/MSC_TIMESTAMP and pass to FE.
* >>> Each new frame becomes larger and larger.
* Disable EV_MSC/MSC_TIMESTAMP forwarding for MT.
*/
if (vi->idev->mt && type == EV_MSC && code == MSC_TIMESTAMP)
return 0;
stsbuf = kzalloc_obj(*stsbuf, GFP_ATOMIC);
if (!stsbuf)
return -ENOMEM;
stsbuf->type = cpu_to_le16(type);
stsbuf->code = cpu_to_le16(code);
stsbuf->value = cpu_to_le32(value);
sg_init_one(sg, stsbuf, sizeof(*stsbuf));
spin_lock_irqsave(&vi->lock, flags);
if (vi->ready) {
rc = virtqueue_add_outbuf(vi->sts, sg, 1, stsbuf, GFP_ATOMIC);
virtqueue_kick(vi->sts);
} else {
rc = -ENODEV;
}
spin_unlock_irqrestore(&vi->lock, flags);
if (rc != 0)
kfree(stsbuf);
return rc;
}
static void virtinput_recv_status(struct virtqueue *vq)
{
struct virtio_input *vi = vq->vdev->priv;
struct virtio_input_event *stsbuf;
unsigned long flags;
unsigned int len;
spin_lock_irqsave(&vi->lock, flags);
while ((stsbuf = virtqueue_get_buf(vi->sts, &len)) != NULL)
kfree(stsbuf);
spin_unlock_irqrestore(&vi->lock, flags);
}
static int virtinput_status(struct input_dev *idev, unsigned int type,
unsigned int code, int value)
{
struct virtio_input *vi = input_get_drvdata(idev);
return virtinput_send_status(vi, type, code, value);
}
static u8 virtinput_cfg_select(struct virtio_input *vi,
u8 select, u8 subsel)
{
u8 size;
virtio_cwrite_le(vi->vdev, struct virtio_input_config, select, &select);
virtio_cwrite_le(vi->vdev, struct virtio_input_config, subsel, &subsel);
virtio_cread_le(vi->vdev, struct virtio_input_config, size, &size);
return size;
}
static void virtinput_cfg_bits(struct virtio_input *vi, int select, int subsel,
unsigned long *bits, unsigned int bitcount)
{
unsigned int bit;
u8 *virtio_bits;
u8 bytes;
bytes = virtinput_cfg_select(vi, select, subsel);
if (!bytes)
return;
if (bitcount > bytes * 8)
bitcount = bytes * 8;
/*
* Bitmap in virtio config space is a simple stream of bytes,
* with the first byte carrying bits 0-7, second bits 8-15 and
* so on.
*/
virtio_bits = kzalloc(bytes, GFP_KERNEL);
if (!virtio_bits)
return;
virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config,
u.bitmap),
virtio_bits, bytes);
for (bit = 0; bit < bitcount; bit++) {
if (virtio_bits[bit / 8] & (1 << (bit % 8)))
__set_bit(bit, bits);
}
kfree(virtio_bits);
if (select == VIRTIO_INPUT_CFG_EV_BITS)
__set_bit(subsel, vi->idev->evbit);
}
static void virtinput_cfg_abs(struct virtio_input *vi, int abs)
{
u32 mi, ma, re, fu, fl;
virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ABS_INFO, abs);
virtio_cread_le(vi->vdev, struct virtio_input_config, u.abs.min, &mi);
virtio_cread_le(vi->vdev, struct virtio_input_config, u.abs.max, &ma);
virtio_cread_le(vi->vdev, struct virtio_input_config, u.abs.res, &re);
virtio_cread_le(vi->vdev, struct virtio_input_config, u.abs.fuzz, &fu);
virtio_cread_le(vi->vdev, struct virtio_input_config, u.abs.flat, &fl);
input_set_abs_params(vi->idev, abs, mi, ma, fu, fl);
input_abs_set_res(vi->idev, abs, re);
}
static int virtinput_init_vqs(struct virtio_input *vi)
{
struct virtqueue_info vqs_info[] = {
{ "events", virtinput_recv_events },
{ "status", virtinput_recv_status },
};
struct virtqueue *vqs[2];
int err;
err = virtio_find_vqs(vi->vdev, 2, vqs, vqs_info, NULL);
if (err)
return err;
vi->evt = vqs[0];
vi->sts = vqs[1];
return 0;
}
static void virtinput_fill_evt(struct virtio_input *vi)
{
unsigned long flags;
int i, size;
spin_lock_irqsave(&vi->lock, flags);
size = virtqueue_get_vring_size(vi->evt);
if (size > ARRAY_SIZE(vi->evts))
size = ARRAY_SIZE(vi->evts);
for (i = 0; i < size; i++)
virtinput_queue_evtbuf(vi, &vi->evts[i]);
virtqueue_kick(vi->evt);
spin_unlock_irqrestore(&vi->lock, flags);
}
static int virtinput_probe(struct virtio_device *vdev)
{
struct virtio_input *vi;
unsigned long flags;
size_t size;
int abs, err, nslots;
if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1))
return -ENODEV;
vi = kzalloc_obj(*vi);
if (!vi)
return -ENOMEM;
vdev->priv = vi;
vi->vdev = vdev;
spin_lock_init(&vi->lock);
err = virtinput_init_vqs(vi);
if (err)
goto err_init_vq;
vi->idev = input_allocate_device();
if (!vi->idev) {
err = -ENOMEM;
goto err_input_alloc;
}
input_set_drvdata(vi->idev, vi);
size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ID_NAME, 0);
virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config,
u.string),
vi->name, min(size, sizeof(vi->name)));
size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ID_SERIAL, 0);
virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config,
u.string),
vi->serial, min(size, sizeof(vi->serial)));
snprintf(vi->phys, sizeof(vi->phys),
"virtio%d/input0", vdev->index);
vi->idev->name = vi->name;
vi->idev->phys = vi->phys;
vi->idev->uniq = vi->serial;
size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ID_DEVIDS, 0);
if (size >= sizeof(struct virtio_input_devids)) {
virtio_cread_le(vi->vdev, struct virtio_input_config,
u.ids.bustype, &vi->idev->id.bustype);
virtio_cread_le(vi->vdev, struct virtio_input_config,
u.ids.vendor, &vi->idev->id.vendor);
virtio_cread_le(vi->vdev, struct virtio_input_config,
u.ids.product, &vi->idev->id.product);
virtio_cread_le(vi->vdev, struct virtio_input_config,
u.ids.version, &vi->idev->id.version);
} else {
vi->idev->id.bustype = BUS_VIRTUAL;
}
virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_PROP_BITS, 0,
vi->idev->propbit, INPUT_PROP_CNT);
size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_REP);
if (size)
__set_bit(EV_REP, vi->idev->evbit);
vi->idev->dev.parent = &vdev->dev;
vi->idev->event = virtinput_status;
/* device -> kernel */
virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_KEY,
vi->idev->keybit, KEY_CNT);
virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_REL,
vi->idev->relbit, REL_CNT);
virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_ABS,
vi->idev->absbit, ABS_CNT);
virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_MSC,
vi->idev->mscbit, MSC_CNT);
virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_SW,
vi->idev->swbit, SW_CNT);
/* kernel -> device */
virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_LED,
vi->idev->ledbit, LED_CNT);
virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_SND,
vi->idev->sndbit, SND_CNT);
if (test_bit(EV_ABS, vi->idev->evbit)) {
for (abs = 0; abs < ABS_CNT; abs++) {
if (!test_bit(abs, vi->idev->absbit))
continue;
virtinput_cfg_abs(vi, abs);
}
if (test_bit(ABS_MT_SLOT, vi->idev->absbit)) {
nslots = input_abs_get_max(vi->idev, ABS_MT_SLOT) + 1;
err = input_mt_init_slots(vi->idev, nslots, 0);
if (err)
goto err_mt_init_slots;
}
}
virtio_device_ready(vdev);
vi->ready = true;
err = input_register_device(vi->idev);
if (err)
goto err_input_register;
virtinput_fill_evt(vi);
return 0;
err_input_register:
spin_lock_irqsave(&vi->lock, flags);
vi->ready = false;
spin_unlock_irqrestore(&vi->lock, flags);
err_mt_init_slots:
input_free_device(vi->idev);
err_input_alloc:
vdev->config->del_vqs(vdev);
err_init_vq:
kfree(vi);
return err;
}
static void virtinput_remove(struct virtio_device *vdev)
{
struct virtio_input *vi = vdev->priv;
void *buf;
unsigned long flags;
spin_lock_irqsave(&vi->lock, flags);
vi->ready = false;
spin_unlock_irqrestore(&vi->lock, flags);
input_unregister_device(vi->idev);
virtio_reset_device(vdev);
while ((buf = virtqueue_detach_unused_buf(vi->sts)) != NULL)
kfree(buf);
vdev->config->del_vqs(vdev);
kfree(vi);
}
#ifdef CONFIG_PM_SLEEP
static int virtinput_freeze(struct virtio_device *vdev)
{
struct virtio_input *vi = vdev->priv;
unsigned long flags;
void *buf;
spin_lock_irqsave(&vi->lock, flags);
vi->ready = false;
spin_unlock_irqrestore(&vi->lock, flags);
virtio_reset_device(vdev);
while ((buf = virtqueue_detach_unused_buf(vi->sts)) != NULL)
kfree(buf);
vdev->config->del_vqs(vdev);
return 0;
}
static int virtinput_restore(struct virtio_device *vdev)
{
struct virtio_input *vi = vdev->priv;
int err;
err = virtinput_init_vqs(vi);
if (err)
return err;
virtio_device_ready(vdev);
vi->ready = true;
virtinput_fill_evt(vi);
return 0;
}
#endif
static unsigned int features[] = {
/* none */
};
static const struct virtio_device_id id_table[] = {
{ VIRTIO_ID_INPUT, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static struct virtio_driver virtio_input_driver = {
.driver.name = KBUILD_MODNAME,
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.id_table = id_table,
.probe = virtinput_probe,
.remove = virtinput_remove,
#ifdef CONFIG_PM_SLEEP
.freeze = virtinput_freeze,
.restore = virtinput_restore,
#endif
};
module_virtio_driver(virtio_input_driver);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Virtio input device driver");
MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>");
File diff suppressed because it is too large Load Diff
+829
View File
@@ -0,0 +1,829 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Virtio memory mapped device driver
*
* Copyright 2011-2014, ARM Ltd.
*
* This module allows virtio devices to be used over a virtual, memory mapped
* platform device.
*
* The guest device(s) may be instantiated in one of three equivalent ways:
*
* 1. Static platform device in board's code, eg.:
*
* static struct platform_device v2m_virtio_device = {
* .name = "virtio-mmio",
* .id = -1,
* .num_resources = 2,
* .resource = (struct resource []) {
* {
* .start = 0x1001e000,
* .end = 0x1001e0ff,
* .flags = IORESOURCE_MEM,
* }, {
* .start = 42 + 32,
* .end = 42 + 32,
* .flags = IORESOURCE_IRQ,
* },
* }
* };
*
* 2. Device Tree node, eg.:
*
* virtio_block@1e000 {
* compatible = "virtio,mmio";
* reg = <0x1e000 0x100>;
* interrupts = <42>;
* }
*
* 3. Kernel module (or command line) parameter. Can be used more than once -
* one device will be created for each one. Syntax:
*
* [virtio_mmio.]device=<size>@<baseaddr>:<irq>[:<id>]
* where:
* <size> := size (can use standard suffixes like K, M or G)
* <baseaddr> := physical base address
* <irq> := interrupt number (as passed to request_irq())
* <id> := (optional) platform device id
* eg.:
* virtio_mmio.device=0x100@0x100b0000:48 \
* virtio_mmio.device=1K@0x1001e000:74
*
* Based on Virtio PCI driver by Anthony Liguori, copyright IBM Corp. 2007
*/
#define pr_fmt(fmt) "virtio-mmio: " fmt
#include <linux/acpi.h>
#include <linux/dma-mapping.h>
#include <linux/highmem.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <uapi/linux/virtio_mmio.h>
#include <linux/virtio_ring.h>
/* The alignment to use between consumer and producer parts of vring.
* Currently hardcoded to the page size. */
#define VIRTIO_MMIO_VRING_ALIGN PAGE_SIZE
#define to_virtio_mmio_device(_plat_dev) \
container_of(_plat_dev, struct virtio_mmio_device, vdev)
struct virtio_mmio_device {
struct virtio_device vdev;
struct platform_device *pdev;
void __iomem *base;
unsigned long version;
};
/* Configuration interface */
static u64 vm_get_features(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
u64 features;
writel(1, vm_dev->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
features = readl(vm_dev->base + VIRTIO_MMIO_DEVICE_FEATURES);
features <<= 32;
writel(0, vm_dev->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
features |= readl(vm_dev->base + VIRTIO_MMIO_DEVICE_FEATURES);
return features;
}
static int vm_finalize_features(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
/* Give virtio_ring a chance to accept features. */
vring_transport_features(vdev);
/* Make sure there are no mixed devices */
if (vm_dev->version == 2 &&
!__virtio_test_bit(vdev, VIRTIO_F_VERSION_1)) {
dev_err(&vdev->dev, "New virtio-mmio devices (version 2) must provide VIRTIO_F_VERSION_1 feature!\n");
return -EINVAL;
}
writel(1, vm_dev->base + VIRTIO_MMIO_DRIVER_FEATURES_SEL);
writel((u32)(vdev->features >> 32),
vm_dev->base + VIRTIO_MMIO_DRIVER_FEATURES);
writel(0, vm_dev->base + VIRTIO_MMIO_DRIVER_FEATURES_SEL);
writel((u32)vdev->features,
vm_dev->base + VIRTIO_MMIO_DRIVER_FEATURES);
return 0;
}
static void vm_get(struct virtio_device *vdev, unsigned int offset,
void *buf, unsigned int len)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
void __iomem *base = vm_dev->base + VIRTIO_MMIO_CONFIG;
u8 b;
__le16 w;
__le32 l;
if (vm_dev->version == 1) {
u8 *ptr = buf;
int i;
for (i = 0; i < len; i++)
ptr[i] = readb(base + offset + i);
return;
}
switch (len) {
case 1:
b = readb(base + offset);
memcpy(buf, &b, sizeof b);
break;
case 2:
w = cpu_to_le16(readw(base + offset));
memcpy(buf, &w, sizeof w);
break;
case 4:
l = cpu_to_le32(readl(base + offset));
memcpy(buf, &l, sizeof l);
break;
case 8:
l = cpu_to_le32(readl(base + offset));
memcpy(buf, &l, sizeof l);
l = cpu_to_le32(ioread32(base + offset + sizeof l));
memcpy(buf + sizeof l, &l, sizeof l);
break;
default:
BUG();
}
}
static void vm_set(struct virtio_device *vdev, unsigned int offset,
const void *buf, unsigned int len)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
void __iomem *base = vm_dev->base + VIRTIO_MMIO_CONFIG;
u8 b;
__le16 w;
__le32 l;
if (vm_dev->version == 1) {
const u8 *ptr = buf;
int i;
for (i = 0; i < len; i++)
writeb(ptr[i], base + offset + i);
return;
}
switch (len) {
case 1:
memcpy(&b, buf, sizeof b);
writeb(b, base + offset);
break;
case 2:
memcpy(&w, buf, sizeof w);
writew(le16_to_cpu(w), base + offset);
break;
case 4:
memcpy(&l, buf, sizeof l);
writel(le32_to_cpu(l), base + offset);
break;
case 8:
memcpy(&l, buf, sizeof l);
writel(le32_to_cpu(l), base + offset);
memcpy(&l, buf + sizeof l, sizeof l);
writel(le32_to_cpu(l), base + offset + sizeof l);
break;
default:
BUG();
}
}
static u32 vm_generation(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
if (vm_dev->version == 1)
return 0;
else
return readl(vm_dev->base + VIRTIO_MMIO_CONFIG_GENERATION);
}
static u8 vm_get_status(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
return readl(vm_dev->base + VIRTIO_MMIO_STATUS) & 0xff;
}
static void vm_set_status(struct virtio_device *vdev, u8 status)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
/* We should never be setting status to 0. */
BUG_ON(status == 0);
/*
* Per memory-barriers.txt, wmb() is not needed to guarantee
* that the cache coherent memory writes have completed
* before writing to the MMIO region.
*/
writel(status, vm_dev->base + VIRTIO_MMIO_STATUS);
}
static void vm_reset(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
/* 0 status means a reset. */
writel(0, vm_dev->base + VIRTIO_MMIO_STATUS);
}
/* Transport interface */
/* the notify function used when creating a virt queue */
static bool vm_notify(struct virtqueue *vq)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vq->vdev);
/* We write the queue's selector into the notification register to
* signal the other end */
writel(vq->index, vm_dev->base + VIRTIO_MMIO_QUEUE_NOTIFY);
return true;
}
static bool vm_notify_with_data(struct virtqueue *vq)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vq->vdev);
u32 data = vring_notification_data(vq);
writel(data, vm_dev->base + VIRTIO_MMIO_QUEUE_NOTIFY);
return true;
}
/* Notify all virtqueues on an interrupt. */
static irqreturn_t vm_interrupt(int irq, void *opaque)
{
struct virtio_mmio_device *vm_dev = opaque;
struct virtqueue *vq;
unsigned long status;
irqreturn_t ret = IRQ_NONE;
/* Read and acknowledge interrupts */
status = readl(vm_dev->base + VIRTIO_MMIO_INTERRUPT_STATUS);
writel(status, vm_dev->base + VIRTIO_MMIO_INTERRUPT_ACK);
if (unlikely(status & VIRTIO_MMIO_INT_CONFIG)) {
virtio_config_changed(&vm_dev->vdev);
ret = IRQ_HANDLED;
}
if (likely(status & VIRTIO_MMIO_INT_VRING)) {
virtio_device_for_each_vq(&vm_dev->vdev, vq)
ret |= vring_interrupt(irq, vq);
}
return ret;
}
static void vm_del_vq(struct virtqueue *vq)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vq->vdev);
unsigned int index = vq->index;
/* Select and deactivate the queue */
writel(index, vm_dev->base + VIRTIO_MMIO_QUEUE_SEL);
if (vm_dev->version == 1) {
writel(0, vm_dev->base + VIRTIO_MMIO_QUEUE_PFN);
} else {
writel(0, vm_dev->base + VIRTIO_MMIO_QUEUE_READY);
WARN_ON(readl(vm_dev->base + VIRTIO_MMIO_QUEUE_READY));
}
vring_del_virtqueue(vq);
}
static void vm_del_vqs(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
struct virtqueue *vq, *n;
list_for_each_entry_safe(vq, n, &vdev->vqs, list)
vm_del_vq(vq);
free_irq(platform_get_irq(vm_dev->pdev, 0), vm_dev);
}
static void vm_synchronize_cbs(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
synchronize_irq(platform_get_irq(vm_dev->pdev, 0));
}
static struct virtqueue *vm_setup_vq(struct virtio_device *vdev, unsigned int index,
void (*callback)(struct virtqueue *vq),
const char *name, bool ctx)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
bool (*notify)(struct virtqueue *vq);
struct virtqueue *vq;
unsigned int num;
int err;
if (__virtio_test_bit(vdev, VIRTIO_F_NOTIFICATION_DATA))
notify = vm_notify_with_data;
else
notify = vm_notify;
if (!name)
return NULL;
/* Select the queue we're interested in */
writel(index, vm_dev->base + VIRTIO_MMIO_QUEUE_SEL);
/* Queue shouldn't already be set up. */
if (readl(vm_dev->base + (vm_dev->version == 1 ?
VIRTIO_MMIO_QUEUE_PFN : VIRTIO_MMIO_QUEUE_READY))) {
err = -ENOENT;
goto error_available;
}
num = readl(vm_dev->base + VIRTIO_MMIO_QUEUE_NUM_MAX);
if (num == 0) {
err = -ENOENT;
goto error_new_virtqueue;
}
/* Create the vring */
vq = vring_create_virtqueue(index, num, VIRTIO_MMIO_VRING_ALIGN, vdev,
true, true, ctx, notify, callback, name);
if (!vq) {
err = -ENOMEM;
goto error_new_virtqueue;
}
vq->num_max = num;
/* Activate the queue */
writel(virtqueue_get_vring_size(vq), vm_dev->base + VIRTIO_MMIO_QUEUE_NUM);
if (vm_dev->version == 1) {
u64 q_pfn = virtqueue_get_desc_addr(vq) >> PAGE_SHIFT;
/*
* virtio-mmio v1 uses a 32bit QUEUE PFN. If we have something
* that doesn't fit in 32bit, fail the setup rather than
* pretending to be successful.
*/
if (q_pfn >> 32) {
dev_err(&vdev->dev,
"platform bug: legacy virtio-mmio must not be used with RAM above 0x%llxGB\n",
0x1ULL << (32 + PAGE_SHIFT - 30));
err = -E2BIG;
goto error_bad_pfn;
}
writel(PAGE_SIZE, vm_dev->base + VIRTIO_MMIO_QUEUE_ALIGN);
writel(q_pfn, vm_dev->base + VIRTIO_MMIO_QUEUE_PFN);
} else {
u64 addr;
addr = virtqueue_get_desc_addr(vq);
writel((u32)addr, vm_dev->base + VIRTIO_MMIO_QUEUE_DESC_LOW);
writel((u32)(addr >> 32),
vm_dev->base + VIRTIO_MMIO_QUEUE_DESC_HIGH);
addr = virtqueue_get_avail_addr(vq);
writel((u32)addr, vm_dev->base + VIRTIO_MMIO_QUEUE_AVAIL_LOW);
writel((u32)(addr >> 32),
vm_dev->base + VIRTIO_MMIO_QUEUE_AVAIL_HIGH);
addr = virtqueue_get_used_addr(vq);
writel((u32)addr, vm_dev->base + VIRTIO_MMIO_QUEUE_USED_LOW);
writel((u32)(addr >> 32),
vm_dev->base + VIRTIO_MMIO_QUEUE_USED_HIGH);
writel(1, vm_dev->base + VIRTIO_MMIO_QUEUE_READY);
}
return vq;
error_bad_pfn:
vring_del_virtqueue(vq);
error_new_virtqueue:
if (vm_dev->version == 1) {
writel(0, vm_dev->base + VIRTIO_MMIO_QUEUE_PFN);
} else {
writel(0, vm_dev->base + VIRTIO_MMIO_QUEUE_READY);
WARN_ON(readl(vm_dev->base + VIRTIO_MMIO_QUEUE_READY));
}
error_available:
return ERR_PTR(err);
}
static int vm_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
struct virtqueue *vqs[],
struct virtqueue_info vqs_info[],
struct irq_affinity *desc)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
int irq = platform_get_irq(vm_dev->pdev, 0);
int i, err, queue_idx = 0;
if (irq < 0)
return irq;
err = request_irq(irq, vm_interrupt, IRQF_SHARED,
dev_name(&vdev->dev), vm_dev);
if (err)
return err;
if (of_property_read_bool(vm_dev->pdev->dev.of_node, "wakeup-source"))
enable_irq_wake(irq);
for (i = 0; i < nvqs; ++i) {
struct virtqueue_info *vqi = &vqs_info[i];
if (!vqi->name) {
vqs[i] = NULL;
continue;
}
vqs[i] = vm_setup_vq(vdev, queue_idx++, vqi->callback,
vqi->name, vqi->ctx);
if (IS_ERR(vqs[i])) {
vm_del_vqs(vdev);
return PTR_ERR(vqs[i]);
}
}
return 0;
}
static const char *vm_bus_name(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
return vm_dev->pdev->name;
}
static bool vm_get_shm_region(struct virtio_device *vdev,
struct virtio_shm_region *region, u8 id)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
u64 len, addr;
/* Select the region we're interested in */
writel(id, vm_dev->base + VIRTIO_MMIO_SHM_SEL);
/* Read the region size */
len = (u64) readl(vm_dev->base + VIRTIO_MMIO_SHM_LEN_LOW);
len |= (u64) readl(vm_dev->base + VIRTIO_MMIO_SHM_LEN_HIGH) << 32;
region->len = len;
/* Check if region length is -1. If that's the case, the shared memory
* region does not exist and there is no need to proceed further.
*/
if (len == ~(u64)0)
return false;
/* Read the region base address */
addr = (u64) readl(vm_dev->base + VIRTIO_MMIO_SHM_BASE_LOW);
addr |= (u64) readl(vm_dev->base + VIRTIO_MMIO_SHM_BASE_HIGH) << 32;
region->addr = addr;
return true;
}
static const struct virtio_config_ops virtio_mmio_config_ops = {
.get = vm_get,
.set = vm_set,
.generation = vm_generation,
.get_status = vm_get_status,
.set_status = vm_set_status,
.reset = vm_reset,
.find_vqs = vm_find_vqs,
.del_vqs = vm_del_vqs,
.get_features = vm_get_features,
.finalize_features = vm_finalize_features,
.bus_name = vm_bus_name,
.get_shm_region = vm_get_shm_region,
.synchronize_cbs = vm_synchronize_cbs,
};
#ifdef CONFIG_PM_SLEEP
static int virtio_mmio_freeze(struct device *dev)
{
struct virtio_mmio_device *vm_dev = dev_get_drvdata(dev);
return virtio_device_freeze(&vm_dev->vdev);
}
static int virtio_mmio_restore(struct device *dev)
{
struct virtio_mmio_device *vm_dev = dev_get_drvdata(dev);
if (vm_dev->version == 1)
writel(PAGE_SIZE, vm_dev->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
return virtio_device_restore(&vm_dev->vdev);
}
static const struct dev_pm_ops virtio_mmio_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(virtio_mmio_freeze, virtio_mmio_restore)
};
#endif
static void virtio_mmio_release_dev(struct device *_d)
{
struct virtio_device *vdev =
container_of(_d, struct virtio_device, dev);
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
kfree(vm_dev);
}
/* Platform device */
static int virtio_mmio_probe(struct platform_device *pdev)
{
struct virtio_mmio_device *vm_dev;
unsigned long magic;
int rc;
vm_dev = kzalloc_obj(*vm_dev);
if (!vm_dev)
return -ENOMEM;
vm_dev->vdev.dev.parent = &pdev->dev;
vm_dev->vdev.dev.release = virtio_mmio_release_dev;
vm_dev->vdev.config = &virtio_mmio_config_ops;
vm_dev->pdev = pdev;
vm_dev->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(vm_dev->base)) {
rc = PTR_ERR(vm_dev->base);
goto free_vm_dev;
}
/* Check magic value */
magic = readl(vm_dev->base + VIRTIO_MMIO_MAGIC_VALUE);
if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) {
dev_warn(&pdev->dev, "Wrong magic value 0x%08lx!\n", magic);
rc = -ENODEV;
goto free_vm_dev;
}
/* Check device version */
vm_dev->version = readl(vm_dev->base + VIRTIO_MMIO_VERSION);
if (vm_dev->version < 1 || vm_dev->version > 2) {
dev_err(&pdev->dev, "Version %ld not supported!\n",
vm_dev->version);
rc = -ENXIO;
goto free_vm_dev;
}
vm_dev->vdev.id.device = readl(vm_dev->base + VIRTIO_MMIO_DEVICE_ID);
if (vm_dev->vdev.id.device == 0) {
/*
* virtio-mmio device with an ID 0 is a (dummy) placeholder
* with no function. End probing now with no error reported.
*/
rc = -ENODEV;
goto free_vm_dev;
}
vm_dev->vdev.id.vendor = readl(vm_dev->base + VIRTIO_MMIO_VENDOR_ID);
if (vm_dev->version == 1) {
writel(PAGE_SIZE, vm_dev->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
rc = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
/*
* In the legacy case, ensure our coherently-allocated virtio
* ring will be at an address expressable as a 32-bit PFN.
*/
if (!rc)
dma_set_coherent_mask(&pdev->dev,
DMA_BIT_MASK(32 + PAGE_SHIFT));
} else {
rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
}
if (rc)
rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (rc)
dev_warn(&pdev->dev, "Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.\n");
platform_set_drvdata(pdev, vm_dev);
rc = register_virtio_device(&vm_dev->vdev);
if (rc)
put_device(&vm_dev->vdev.dev);
return rc;
free_vm_dev:
kfree(vm_dev);
return rc;
}
static void virtio_mmio_remove(struct platform_device *pdev)
{
struct virtio_mmio_device *vm_dev = platform_get_drvdata(pdev);
unregister_virtio_device(&vm_dev->vdev);
}
/* Devices list parameter */
#if defined(CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES)
static struct device *vm_cmdline_parent;
static int vm_cmdline_parent_registered;
static int vm_cmdline_id;
static int vm_cmdline_set(const char *device,
const struct kernel_param *kp)
{
struct resource resources[2] = {};
char *str;
long long base, size;
unsigned int irq;
int processed, consumed = 0;
struct platform_device *pdev;
/* Consume "size" part of the command line parameter */
size = memparse(device, &str);
/* Get "@<base>:<irq>[:<id>]" chunks */
processed = sscanf(str, "@%lli:%u%n:%d%n",
&base, &irq, &consumed,
&vm_cmdline_id, &consumed);
/*
* sscanf() must process at least 2 chunks; also there
* must be no extra characters after the last chunk, so
* str[consumed] must be '\0'
*/
if (processed < 2 || str[consumed] || irq == 0)
return -EINVAL;
resources[0].flags = IORESOURCE_MEM;
resources[0].start = base;
resources[0].end = base + size - 1;
resources[1].flags = IORESOURCE_IRQ;
resources[1].start = resources[1].end = irq;
if (!vm_cmdline_parent_registered) {
vm_cmdline_parent = __root_device_register("virtio-mmio-cmdline", NULL);
if (IS_ERR(vm_cmdline_parent)) {
pr_err("Failed to register parent device!\n");
return PTR_ERR(vm_cmdline_parent);
}
vm_cmdline_parent_registered = 1;
}
pr_info("Registering device virtio-mmio.%d at 0x%llx-0x%llx, IRQ %d.\n",
vm_cmdline_id,
(unsigned long long)resources[0].start,
(unsigned long long)resources[0].end,
(int)resources[1].start);
pdev = platform_device_register_resndata(vm_cmdline_parent,
"virtio-mmio", vm_cmdline_id++,
resources, ARRAY_SIZE(resources), NULL, 0);
return PTR_ERR_OR_ZERO(pdev);
}
static int vm_cmdline_get_device(struct device *dev, void *data)
{
char *buffer = data;
unsigned int len = strlen(buffer);
struct platform_device *pdev = to_platform_device(dev);
snprintf(buffer + len, PAGE_SIZE - len, "0x%llx@0x%llx:%llu:%d\n",
pdev->resource[0].end - pdev->resource[0].start + 1ULL,
(unsigned long long)pdev->resource[0].start,
(unsigned long long)pdev->resource[1].start,
pdev->id);
return 0;
}
static int vm_cmdline_get(char *buffer, const struct kernel_param *kp)
{
buffer[0] = '\0';
if (vm_cmdline_parent_registered) {
device_for_each_child(vm_cmdline_parent, buffer,
vm_cmdline_get_device);
}
return strlen(buffer) + 1;
}
static const struct kernel_param_ops vm_cmdline_param_ops = {
.set = vm_cmdline_set,
.get = vm_cmdline_get,
};
device_param_cb(device, &vm_cmdline_param_ops, NULL, S_IRUSR);
static int vm_unregister_cmdline_device(struct device *dev,
void *data)
{
platform_device_unregister(to_platform_device(dev));
return 0;
}
static void vm_unregister_cmdline_devices(void)
{
if (vm_cmdline_parent_registered) {
device_for_each_child(vm_cmdline_parent, NULL,
vm_unregister_cmdline_device);
root_device_unregister(vm_cmdline_parent);
vm_cmdline_parent_registered = 0;
}
}
#else
static void vm_unregister_cmdline_devices(void)
{
}
#endif
/* Platform driver */
static const struct of_device_id virtio_mmio_match[] = {
{ .compatible = "virtio,mmio", },
{},
};
MODULE_DEVICE_TABLE(of, virtio_mmio_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id virtio_mmio_acpi_match[] = {
{ "LNRO0005", },
{ }
};
MODULE_DEVICE_TABLE(acpi, virtio_mmio_acpi_match);
#endif
static struct platform_driver virtio_mmio_driver = {
.probe = virtio_mmio_probe,
.remove = virtio_mmio_remove,
.driver = {
.name = "virtio-mmio",
.of_match_table = virtio_mmio_match,
.acpi_match_table = ACPI_PTR(virtio_mmio_acpi_match),
#ifdef CONFIG_PM_SLEEP
.pm = &virtio_mmio_pm_ops,
#endif
},
};
static int __init virtio_mmio_init(void)
{
return platform_driver_register(&virtio_mmio_driver);
}
static void __exit virtio_mmio_exit(void)
{
platform_driver_unregister(&virtio_mmio_driver);
vm_unregister_cmdline_devices();
}
module_init(virtio_mmio_init);
module_exit(virtio_mmio_exit);
MODULE_AUTHOR("Pawel Moll <pawel.moll@arm.com>");
MODULE_DESCRIPTION("Platform bus driver for memory mapped virtio devices");
MODULE_LICENSE("GPL");
@@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved
*/
#include <linux/virtio_pci_admin.h>
#include "virtio_pci_common.h"
/*
* virtio_pci_admin_has_legacy_io - Checks whether the legacy IO
* commands are supported
* @dev: VF pci_dev
*
* Returns true on success.
*/
bool virtio_pci_admin_has_legacy_io(struct pci_dev *pdev)
{
struct virtio_device *virtio_dev = virtio_pci_vf_get_pf_dev(pdev);
struct virtio_pci_device *vp_dev;
if (!virtio_dev)
return false;
if (!virtio_has_feature(virtio_dev, VIRTIO_F_ADMIN_VQ))
return false;
vp_dev = to_vp_device(virtio_dev);
if ((vp_dev->admin_vq.supported_cmds & VIRTIO_LEGACY_ADMIN_CMD_BITMAP) ==
VIRTIO_LEGACY_ADMIN_CMD_BITMAP)
return true;
return false;
}
EXPORT_SYMBOL_GPL(virtio_pci_admin_has_legacy_io);
static int virtio_pci_admin_legacy_io_write(struct pci_dev *pdev, u16 opcode,
u8 offset, u8 size, u8 *buf)
{
struct virtio_device *virtio_dev = virtio_pci_vf_get_pf_dev(pdev);
struct virtio_admin_cmd_legacy_wr_data *data;
struct virtio_admin_cmd cmd = {};
struct scatterlist data_sg;
int vf_id;
int ret;
if (!virtio_dev)
return -ENODEV;
vf_id = pci_iov_vf_id(pdev);
if (vf_id < 0)
return vf_id;
data = kzalloc(sizeof(*data) + size, GFP_KERNEL);
if (!data)
return -ENOMEM;
data->offset = offset;
memcpy(data->registers, buf, size);
sg_init_one(&data_sg, data, sizeof(*data) + size);
cmd.opcode = cpu_to_le16(opcode);
cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SRIOV);
cmd.group_member_id = cpu_to_le64(vf_id + 1);
cmd.data_sg = &data_sg;
ret = vp_modern_admin_cmd_exec(virtio_dev, &cmd);
kfree(data);
return ret;
}
/*
* virtio_pci_admin_legacy_io_write_common - Write legacy common configuration
* of a member device
* @dev: VF pci_dev
* @offset: starting byte offset within the common configuration area to write to
* @size: size of the data to write
* @buf: buffer which holds the data
*
* Note: caller must serialize access for the given device.
* Returns 0 on success, or negative on failure.
*/
int virtio_pci_admin_legacy_common_io_write(struct pci_dev *pdev, u8 offset,
u8 size, u8 *buf)
{
return virtio_pci_admin_legacy_io_write(pdev,
VIRTIO_ADMIN_CMD_LEGACY_COMMON_CFG_WRITE,
offset, size, buf);
}
EXPORT_SYMBOL_GPL(virtio_pci_admin_legacy_common_io_write);
/*
* virtio_pci_admin_legacy_io_write_device - Write legacy device configuration
* of a member device
* @dev: VF pci_dev
* @offset: starting byte offset within the device configuration area to write to
* @size: size of the data to write
* @buf: buffer which holds the data
*
* Note: caller must serialize access for the given device.
* Returns 0 on success, or negative on failure.
*/
int virtio_pci_admin_legacy_device_io_write(struct pci_dev *pdev, u8 offset,
u8 size, u8 *buf)
{
return virtio_pci_admin_legacy_io_write(pdev,
VIRTIO_ADMIN_CMD_LEGACY_DEV_CFG_WRITE,
offset, size, buf);
}
EXPORT_SYMBOL_GPL(virtio_pci_admin_legacy_device_io_write);
static int virtio_pci_admin_legacy_io_read(struct pci_dev *pdev, u16 opcode,
u8 offset, u8 size, u8 *buf)
{
struct virtio_device *virtio_dev = virtio_pci_vf_get_pf_dev(pdev);
struct virtio_admin_cmd_legacy_rd_data *data;
struct scatterlist data_sg, result_sg;
struct virtio_admin_cmd cmd = {};
int vf_id;
int ret;
if (!virtio_dev)
return -ENODEV;
vf_id = pci_iov_vf_id(pdev);
if (vf_id < 0)
return vf_id;
data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
data->offset = offset;
sg_init_one(&data_sg, data, sizeof(*data));
sg_init_one(&result_sg, buf, size);
cmd.opcode = cpu_to_le16(opcode);
cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SRIOV);
cmd.group_member_id = cpu_to_le64(vf_id + 1);
cmd.data_sg = &data_sg;
cmd.result_sg = &result_sg;
ret = vp_modern_admin_cmd_exec(virtio_dev, &cmd);
kfree(data);
return ret;
}
/*
* virtio_pci_admin_legacy_device_io_read - Read legacy device configuration of
* a member device
* @dev: VF pci_dev
* @offset: starting byte offset within the device configuration area to read from
* @size: size of the data to be read
* @buf: buffer to hold the returned data
*
* Note: caller must serialize access for the given device.
* Returns 0 on success, or negative on failure.
*/
int virtio_pci_admin_legacy_device_io_read(struct pci_dev *pdev, u8 offset,
u8 size, u8 *buf)
{
return virtio_pci_admin_legacy_io_read(pdev,
VIRTIO_ADMIN_CMD_LEGACY_DEV_CFG_READ,
offset, size, buf);
}
EXPORT_SYMBOL_GPL(virtio_pci_admin_legacy_device_io_read);
/*
* virtio_pci_admin_legacy_common_io_read - Read legacy common configuration of
* a member device
* @dev: VF pci_dev
* @offset: starting byte offset within the common configuration area to read from
* @size: size of the data to be read
* @buf: buffer to hold the returned data
*
* Note: caller must serialize access for the given device.
* Returns 0 on success, or negative on failure.
*/
int virtio_pci_admin_legacy_common_io_read(struct pci_dev *pdev, u8 offset,
u8 size, u8 *buf)
{
return virtio_pci_admin_legacy_io_read(pdev,
VIRTIO_ADMIN_CMD_LEGACY_COMMON_CFG_READ,
offset, size, buf);
}
EXPORT_SYMBOL_GPL(virtio_pci_admin_legacy_common_io_read);
/*
* virtio_pci_admin_legacy_io_notify_info - Read the queue notification
* information for legacy interface
* @dev: VF pci_dev
* @req_bar_flags: requested bar flags
* @bar: on output the BAR number of the owner or member device
* @bar_offset: on output the offset within bar
*
* Returns 0 on success, or negative on failure.
*/
int virtio_pci_admin_legacy_io_notify_info(struct pci_dev *pdev,
u8 req_bar_flags, u8 *bar,
u64 *bar_offset)
{
struct virtio_device *virtio_dev = virtio_pci_vf_get_pf_dev(pdev);
struct virtio_admin_cmd_notify_info_result *result;
struct virtio_admin_cmd cmd = {};
struct scatterlist result_sg;
int vf_id;
int ret;
if (!virtio_dev)
return -ENODEV;
vf_id = pci_iov_vf_id(pdev);
if (vf_id < 0)
return vf_id;
result = kzalloc_obj(*result);
if (!result)
return -ENOMEM;
sg_init_one(&result_sg, result, sizeof(*result));
cmd.opcode = cpu_to_le16(VIRTIO_ADMIN_CMD_LEGACY_NOTIFY_INFO);
cmd.group_type = cpu_to_le16(VIRTIO_ADMIN_GROUP_TYPE_SRIOV);
cmd.group_member_id = cpu_to_le64(vf_id + 1);
cmd.result_sg = &result_sg;
ret = vp_modern_admin_cmd_exec(virtio_dev, &cmd);
if (!ret) {
struct virtio_admin_cmd_notify_info_data *entry;
int i;
ret = -ENOENT;
for (i = 0; i < VIRTIO_ADMIN_CMD_MAX_NOTIFY_INFO; i++) {
entry = &result->entries[i];
if (entry->flags == VIRTIO_ADMIN_CMD_NOTIFY_INFO_FLAGS_END)
break;
if (entry->flags != req_bar_flags)
continue;
*bar = entry->bar;
*bar_offset = le64_to_cpu(entry->offset);
ret = 0;
break;
}
}
kfree(result);
return ret;
}
EXPORT_SYMBOL_GPL(virtio_pci_admin_legacy_io_notify_info);
@@ -0,0 +1,864 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Virtio PCI driver - common functionality for all device versions
*
* This module allows virtio devices to be used over a virtual PCI device.
* This can be used with QEMU based VMMs like KVM or Xen.
*
* Copyright IBM Corp. 2007
* Copyright Red Hat, Inc. 2014
*
* Authors:
* Anthony Liguori <aliguori@us.ibm.com>
* Rusty Russell <rusty@rustcorp.com.au>
* Michael S. Tsirkin <mst@redhat.com>
*/
#include "virtio_pci_common.h"
static bool force_legacy = false;
#if IS_ENABLED(CONFIG_VIRTIO_PCI_LEGACY)
module_param(force_legacy, bool, 0444);
MODULE_PARM_DESC(force_legacy,
"Force legacy mode for transitional virtio 1 devices");
#endif
bool vp_is_avq(struct virtio_device *vdev, unsigned int index)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
if (!virtio_has_feature(vdev, VIRTIO_F_ADMIN_VQ))
return false;
return index == vp_dev->admin_vq.vq_index;
}
/* wait for pending irq handlers */
void vp_synchronize_vectors(struct virtio_device *vdev)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
int i;
if (vp_dev->intx_enabled)
synchronize_irq(vp_dev->pci_dev->irq);
for (i = 0; i < vp_dev->msix_vectors; ++i)
synchronize_irq(pci_irq_vector(vp_dev->pci_dev, i));
}
/* the notify function used when creating a virt queue */
bool vp_notify(struct virtqueue *vq)
{
/* we write the queue's selector into the notification register to
* signal the other end */
iowrite16(vq->index, (void __iomem *)vq->priv);
return true;
}
/* Notify all slow path virtqueues on an interrupt. */
static void vp_vring_slow_path_interrupt(int irq,
struct virtio_pci_device *vp_dev)
{
struct virtio_pci_vq_info *info;
unsigned long flags;
spin_lock_irqsave(&vp_dev->lock, flags);
list_for_each_entry(info, &vp_dev->slow_virtqueues, node)
vring_interrupt(irq, info->vq);
spin_unlock_irqrestore(&vp_dev->lock, flags);
}
/* Handle a configuration change: Tell driver if it wants to know. */
static irqreturn_t vp_config_changed(int irq, void *opaque)
{
struct virtio_pci_device *vp_dev = opaque;
virtio_config_changed(&vp_dev->vdev);
vp_vring_slow_path_interrupt(irq, vp_dev);
return IRQ_HANDLED;
}
/* Notify all virtqueues on an interrupt. */
static irqreturn_t vp_vring_interrupt(int irq, void *opaque)
{
struct virtio_pci_device *vp_dev = opaque;
struct virtio_pci_vq_info *info;
irqreturn_t ret = IRQ_NONE;
unsigned long flags;
spin_lock_irqsave(&vp_dev->lock, flags);
list_for_each_entry(info, &vp_dev->virtqueues, node) {
if (vring_interrupt(irq, info->vq) == IRQ_HANDLED)
ret = IRQ_HANDLED;
}
spin_unlock_irqrestore(&vp_dev->lock, flags);
return ret;
}
/* A small wrapper to also acknowledge the interrupt when it's handled.
* I really need an EIO hook for the vring so I can ack the interrupt once we
* know that we'll be handling the IRQ but before we invoke the callback since
* the callback may notify the host which results in the host attempting to
* raise an interrupt that we would then mask once we acknowledged the
* interrupt. */
static irqreturn_t vp_interrupt(int irq, void *opaque)
{
struct virtio_pci_device *vp_dev = opaque;
u8 isr;
/* reading the ISR has the effect of also clearing it so it's very
* important to save off the value. */
isr = ioread8(vp_dev->isr);
/* It's definitely not us if the ISR was not high */
if (!isr)
return IRQ_NONE;
/* Configuration change? Tell driver if it wants to know. */
if (isr & VIRTIO_PCI_ISR_CONFIG)
vp_config_changed(irq, opaque);
return vp_vring_interrupt(irq, opaque);
}
static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,
bool per_vq_vectors, struct irq_affinity *desc)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
const char *name = dev_name(&vp_dev->vdev.dev);
unsigned int flags = PCI_IRQ_MSIX;
unsigned int i, v;
int err = -ENOMEM;
vp_dev->msix_vectors = nvectors;
vp_dev->msix_names = kmalloc_objs(*vp_dev->msix_names, nvectors);
if (!vp_dev->msix_names)
goto error;
vp_dev->msix_affinity_masks
= kzalloc_objs(*vp_dev->msix_affinity_masks, nvectors);
if (!vp_dev->msix_affinity_masks)
goto error;
for (i = 0; i < nvectors; ++i)
if (!alloc_cpumask_var(&vp_dev->msix_affinity_masks[i],
GFP_KERNEL))
goto error;
if (!per_vq_vectors)
desc = NULL;
if (desc) {
flags |= PCI_IRQ_AFFINITY;
desc->pre_vectors++; /* virtio config vector */
}
err = pci_alloc_irq_vectors_affinity(vp_dev->pci_dev, nvectors,
nvectors, flags, desc);
if (err < 0)
goto error;
vp_dev->msix_enabled = 1;
/* Set the vector used for configuration */
v = vp_dev->msix_used_vectors;
snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
"%s-config", name);
err = request_irq(pci_irq_vector(vp_dev->pci_dev, v),
vp_config_changed, 0, vp_dev->msix_names[v],
vp_dev);
if (err)
goto error;
++vp_dev->msix_used_vectors;
v = vp_dev->config_vector(vp_dev, v);
/* Verify we had enough resources to assign the vector */
if (v == VIRTIO_MSI_NO_VECTOR) {
err = -EBUSY;
goto error;
}
if (!per_vq_vectors) {
/* Shared vector for all VQs */
v = vp_dev->msix_used_vectors;
snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
"%s-virtqueues", name);
err = request_irq(pci_irq_vector(vp_dev->pci_dev, v),
vp_vring_interrupt, 0, vp_dev->msix_names[v],
vp_dev);
if (err)
goto error;
++vp_dev->msix_used_vectors;
}
return 0;
error:
return err;
}
static bool vp_is_slow_path_vector(u16 msix_vec)
{
return msix_vec == VP_MSIX_CONFIG_VECTOR;
}
static struct virtqueue *vp_setup_vq(struct virtio_device *vdev, unsigned int index,
void (*callback)(struct virtqueue *vq),
const char *name,
bool ctx,
u16 msix_vec,
struct virtio_pci_vq_info **p_info)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtio_pci_vq_info *info = kmalloc_obj(*info);
struct virtqueue *vq;
unsigned long flags;
/* fill out our structure that represents an active queue */
if (!info)
return ERR_PTR(-ENOMEM);
vq = vp_dev->setup_vq(vp_dev, info, index, callback, name, ctx,
msix_vec);
if (IS_ERR(vq))
goto out_info;
info->vq = vq;
if (callback) {
spin_lock_irqsave(&vp_dev->lock, flags);
if (!vp_is_slow_path_vector(msix_vec))
list_add(&info->node, &vp_dev->virtqueues);
else
list_add(&info->node, &vp_dev->slow_virtqueues);
spin_unlock_irqrestore(&vp_dev->lock, flags);
} else {
INIT_LIST_HEAD(&info->node);
}
*p_info = info;
return vq;
out_info:
kfree(info);
return vq;
}
static void vp_del_vq(struct virtqueue *vq, struct virtio_pci_vq_info *info)
{
struct virtio_pci_device *vp_dev = to_vp_device(vq->vdev);
unsigned long flags;
/*
* If it fails during re-enable reset vq. This way we won't rejoin
* info->node to the queue. Prevent unexpected irqs.
*/
if (!vq->reset) {
spin_lock_irqsave(&vp_dev->lock, flags);
list_del(&info->node);
spin_unlock_irqrestore(&vp_dev->lock, flags);
}
vp_dev->del_vq(info);
kfree(info);
}
/* the config->del_vqs() implementation */
void vp_del_vqs(struct virtio_device *vdev)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtio_pci_vq_info *info;
struct virtqueue *vq, *n;
int i;
list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
info = vp_is_avq(vdev, vq->index) ? vp_dev->admin_vq.info :
vp_dev->vqs[vq->index];
if (vp_dev->per_vq_vectors) {
int v = info->msix_vector;
if (v != VIRTIO_MSI_NO_VECTOR &&
!vp_is_slow_path_vector(v)) {
int irq = pci_irq_vector(vp_dev->pci_dev, v);
irq_update_affinity_hint(irq, NULL);
free_irq(irq, vq);
}
}
vp_del_vq(vq, info);
}
vp_dev->per_vq_vectors = false;
if (vp_dev->intx_enabled) {
free_irq(vp_dev->pci_dev->irq, vp_dev);
vp_dev->intx_enabled = 0;
}
for (i = 0; i < vp_dev->msix_used_vectors; ++i)
free_irq(pci_irq_vector(vp_dev->pci_dev, i), vp_dev);
if (vp_dev->msix_affinity_masks) {
for (i = 0; i < vp_dev->msix_vectors; i++)
free_cpumask_var(vp_dev->msix_affinity_masks[i]);
}
if (vp_dev->msix_enabled) {
/* Disable the vector used for configuration */
vp_dev->config_vector(vp_dev, VIRTIO_MSI_NO_VECTOR);
pci_free_irq_vectors(vp_dev->pci_dev);
vp_dev->msix_enabled = 0;
}
vp_dev->msix_vectors = 0;
vp_dev->msix_used_vectors = 0;
kfree(vp_dev->msix_names);
vp_dev->msix_names = NULL;
kfree(vp_dev->msix_affinity_masks);
vp_dev->msix_affinity_masks = NULL;
kfree(vp_dev->vqs);
vp_dev->vqs = NULL;
}
enum vp_vq_vector_policy {
VP_VQ_VECTOR_POLICY_EACH,
VP_VQ_VECTOR_POLICY_SHARED_SLOW,
VP_VQ_VECTOR_POLICY_SHARED,
};
static struct virtqueue *
vp_find_one_vq_msix(struct virtio_device *vdev, int queue_idx,
vq_callback_t *callback, const char *name, bool ctx,
bool slow_path, int *allocated_vectors,
enum vp_vq_vector_policy vector_policy,
struct virtio_pci_vq_info **p_info)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtqueue *vq;
u16 msix_vec;
int err;
if (!callback)
msix_vec = VIRTIO_MSI_NO_VECTOR;
else if (vector_policy == VP_VQ_VECTOR_POLICY_EACH ||
(vector_policy == VP_VQ_VECTOR_POLICY_SHARED_SLOW &&
!slow_path))
msix_vec = (*allocated_vectors)++;
else if (vector_policy != VP_VQ_VECTOR_POLICY_EACH &&
slow_path)
msix_vec = VP_MSIX_CONFIG_VECTOR;
else
msix_vec = VP_MSIX_VQ_VECTOR;
vq = vp_setup_vq(vdev, queue_idx, callback, name, ctx, msix_vec,
p_info);
if (IS_ERR(vq))
return vq;
if (vector_policy == VP_VQ_VECTOR_POLICY_SHARED ||
msix_vec == VIRTIO_MSI_NO_VECTOR ||
vp_is_slow_path_vector(msix_vec))
return vq;
/* allocate per-vq irq if available and necessary */
snprintf(vp_dev->msix_names[msix_vec], sizeof(*vp_dev->msix_names),
"%s-%s", dev_name(&vp_dev->vdev.dev), name);
err = request_irq(pci_irq_vector(vp_dev->pci_dev, msix_vec),
vring_interrupt, 0,
vp_dev->msix_names[msix_vec], vq);
if (err) {
vp_del_vq(vq, *p_info);
return ERR_PTR(err);
}
return vq;
}
static int vp_find_vqs_msix(struct virtio_device *vdev, unsigned int nvqs,
struct virtqueue *vqs[],
struct virtqueue_info vqs_info[],
enum vp_vq_vector_policy vector_policy,
struct irq_affinity *desc)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtio_pci_admin_vq *avq = &vp_dev->admin_vq;
struct virtqueue_info *vqi;
int i, err, nvectors, allocated_vectors, queue_idx = 0;
struct virtqueue *vq;
bool per_vq_vectors;
u16 avq_num = 0;
vp_dev->vqs = kzalloc_objs(*vp_dev->vqs, nvqs);
if (!vp_dev->vqs)
return -ENOMEM;
if (vp_dev->avq_index) {
err = vp_dev->avq_index(vdev, &avq->vq_index, &avq_num);
if (err)
goto error_find;
}
per_vq_vectors = vector_policy != VP_VQ_VECTOR_POLICY_SHARED;
if (per_vq_vectors) {
/* Best option: one for change interrupt, one per vq. */
nvectors = 1;
for (i = 0; i < nvqs; ++i) {
vqi = &vqs_info[i];
if (vqi->name && vqi->callback)
++nvectors;
}
if (avq_num && vector_policy == VP_VQ_VECTOR_POLICY_EACH)
++nvectors;
} else {
/* Second best: one for change, shared for all vqs. */
nvectors = 2;
}
err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors, desc);
if (err)
goto error_find;
vp_dev->per_vq_vectors = per_vq_vectors;
allocated_vectors = vp_dev->msix_used_vectors;
for (i = 0; i < nvqs; ++i) {
vqi = &vqs_info[i];
if (!vqi->name) {
vqs[i] = NULL;
continue;
}
vqs[i] = vp_find_one_vq_msix(vdev, queue_idx, vqi->callback,
vqi->name, vqi->ctx, false,
&allocated_vectors, vector_policy,
&vp_dev->vqs[queue_idx]);
queue_idx++;
if (IS_ERR(vqs[i])) {
err = PTR_ERR(vqs[i]);
goto error_find;
}
}
if (!avq_num)
return 0;
sprintf(avq->name, "avq.%u", avq->vq_index);
vq = vp_find_one_vq_msix(vdev, avq->vq_index, vp_modern_avq_done,
avq->name, false, true, &allocated_vectors,
vector_policy, &vp_dev->admin_vq.info);
if (IS_ERR(vq)) {
err = PTR_ERR(vq);
goto error_find;
}
return 0;
error_find:
vp_del_vqs(vdev);
return err;
}
static int vp_find_vqs_intx(struct virtio_device *vdev, unsigned int nvqs,
struct virtqueue *vqs[],
struct virtqueue_info vqs_info[])
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtio_pci_admin_vq *avq = &vp_dev->admin_vq;
int i, err, queue_idx = 0;
struct virtqueue *vq;
u16 avq_num = 0;
vp_dev->vqs = kzalloc_objs(*vp_dev->vqs, nvqs);
if (!vp_dev->vqs)
return -ENOMEM;
if (vp_dev->avq_index) {
err = vp_dev->avq_index(vdev, &avq->vq_index, &avq_num);
if (err)
goto out_del_vqs;
}
err = request_irq(vp_dev->pci_dev->irq, vp_interrupt, IRQF_SHARED,
dev_name(&vdev->dev), vp_dev);
if (err)
goto out_del_vqs;
vp_dev->intx_enabled = 1;
vp_dev->per_vq_vectors = false;
for (i = 0; i < nvqs; ++i) {
struct virtqueue_info *vqi = &vqs_info[i];
if (!vqi->name) {
vqs[i] = NULL;
continue;
}
vqs[i] = vp_setup_vq(vdev, queue_idx, vqi->callback,
vqi->name, vqi->ctx,
VIRTIO_MSI_NO_VECTOR, &vp_dev->vqs[queue_idx]);
queue_idx++;
if (IS_ERR(vqs[i])) {
err = PTR_ERR(vqs[i]);
goto out_del_vqs;
}
}
if (!avq_num)
return 0;
sprintf(avq->name, "avq.%u", avq->vq_index);
vq = vp_setup_vq(vdev, queue_idx++, vp_modern_avq_done, avq->name,
false, VIRTIO_MSI_NO_VECTOR,
&vp_dev->admin_vq.info);
if (IS_ERR(vq)) {
err = PTR_ERR(vq);
goto out_del_vqs;
}
return 0;
out_del_vqs:
vp_del_vqs(vdev);
return err;
}
/* the config->find_vqs() implementation */
int vp_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
struct virtqueue *vqs[], struct virtqueue_info vqs_info[],
struct irq_affinity *desc)
{
int err;
/* Try MSI-X with one vector per queue. */
err = vp_find_vqs_msix(vdev, nvqs, vqs, vqs_info,
VP_VQ_VECTOR_POLICY_EACH, desc);
if (!err)
return 0;
/* Fallback: MSI-X with one shared vector for config and
* slow path queues, one vector per queue for the rest.
*/
err = vp_find_vqs_msix(vdev, nvqs, vqs, vqs_info,
VP_VQ_VECTOR_POLICY_SHARED_SLOW, desc);
if (!err)
return 0;
/* Fallback: MSI-X with one vector for config, one shared for queues. */
err = vp_find_vqs_msix(vdev, nvqs, vqs, vqs_info,
VP_VQ_VECTOR_POLICY_SHARED, desc);
if (!err)
return 0;
/* Is there an interrupt? If not give up. */
if (!(to_vp_device(vdev)->pci_dev->irq))
return err;
/* Finally fall back to regular interrupts. */
return vp_find_vqs_intx(vdev, nvqs, vqs, vqs_info);
}
const char *vp_bus_name(struct virtio_device *vdev)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
return pci_name(vp_dev->pci_dev);
}
/* Setup the affinity for a virtqueue:
* - force the affinity for per vq vector
* - OR over all affinities for shared MSI
* - ignore the affinity request if we're using INTX
*/
int vp_set_vq_affinity(struct virtqueue *vq, const struct cpumask *cpu_mask)
{
struct virtio_device *vdev = vq->vdev;
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtio_pci_vq_info *info = vp_dev->vqs[vq->index];
struct cpumask *mask;
unsigned int irq;
if (!vq->callback)
return -EINVAL;
if (vp_dev->msix_enabled) {
mask = vp_dev->msix_affinity_masks[info->msix_vector];
irq = pci_irq_vector(vp_dev->pci_dev, info->msix_vector);
if (!cpu_mask)
irq_update_affinity_hint(irq, NULL);
else {
cpumask_copy(mask, cpu_mask);
irq_set_affinity_and_hint(irq, mask);
}
}
return 0;
}
const struct cpumask *vp_get_vq_affinity(struct virtio_device *vdev, int index)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
if (!vp_dev->per_vq_vectors ||
vp_dev->vqs[index]->msix_vector == VIRTIO_MSI_NO_VECTOR ||
vp_is_slow_path_vector(vp_dev->vqs[index]->msix_vector))
return NULL;
return pci_irq_get_affinity(vp_dev->pci_dev,
vp_dev->vqs[index]->msix_vector);
}
#ifdef CONFIG_PM_SLEEP
static int virtio_pci_freeze(struct device *dev)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
int ret;
ret = virtio_device_freeze(&vp_dev->vdev);
if (!ret)
pci_disable_device(pci_dev);
return ret;
}
static int virtio_pci_restore(struct device *dev)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
int ret;
ret = pci_enable_device(pci_dev);
if (ret)
return ret;
pci_set_master(pci_dev);
return virtio_device_restore(&vp_dev->vdev);
}
static bool vp_supports_pm_no_reset(struct device *dev)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
u16 pmcsr;
if (!pci_dev->pm_cap)
return false;
pci_read_config_word(pci_dev, pci_dev->pm_cap + PCI_PM_CTRL, &pmcsr);
if (PCI_POSSIBLE_ERROR(pmcsr)) {
dev_err(dev, "Unable to query pmcsr");
return false;
}
return pmcsr & PCI_PM_CTRL_NO_SOFT_RESET;
}
static int virtio_pci_suspend(struct device *dev)
{
return vp_supports_pm_no_reset(dev) ? 0 : virtio_pci_freeze(dev);
}
static int virtio_pci_resume(struct device *dev)
{
return vp_supports_pm_no_reset(dev) ? 0 : virtio_pci_restore(dev);
}
static const struct dev_pm_ops virtio_pci_pm_ops = {
.suspend = virtio_pci_suspend,
.resume = virtio_pci_resume,
.freeze = virtio_pci_freeze,
.thaw = virtio_pci_restore,
.poweroff = virtio_pci_freeze,
.restore = virtio_pci_restore,
};
#endif
/* Qumranet donated their vendor ID for devices 0x1000 thru 0x10FF. */
static const struct pci_device_id virtio_pci_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_REDHAT_QUMRANET, PCI_ANY_ID) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, virtio_pci_id_table);
static void virtio_pci_release_dev(struct device *_d)
{
struct virtio_device *vdev = dev_to_virtio(_d);
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
/* As struct device is a kobject, it's not safe to
* free the memory (including the reference counter itself)
* until it's release callback. */
kfree(vp_dev);
}
static int virtio_pci_probe(struct pci_dev *pci_dev,
const struct pci_device_id *id)
{
struct virtio_pci_device *vp_dev, *reg_dev = NULL;
int rc;
/* allocate our structure and fill it out */
vp_dev = kzalloc_obj(struct virtio_pci_device);
if (!vp_dev)
return -ENOMEM;
pci_set_drvdata(pci_dev, vp_dev);
vp_dev->vdev.dev.parent = &pci_dev->dev;
vp_dev->vdev.dev.release = virtio_pci_release_dev;
vp_dev->pci_dev = pci_dev;
INIT_LIST_HEAD(&vp_dev->virtqueues);
INIT_LIST_HEAD(&vp_dev->slow_virtqueues);
spin_lock_init(&vp_dev->lock);
/* enable the device */
rc = pci_enable_device(pci_dev);
if (rc)
goto err_enable_device;
if (force_legacy) {
rc = virtio_pci_legacy_probe(vp_dev);
/* Also try modern mode if we can't map BAR0 (no IO space). */
if (rc == -ENODEV || rc == -ENOMEM)
rc = virtio_pci_modern_probe(vp_dev);
if (rc)
goto err_probe;
} else {
rc = virtio_pci_modern_probe(vp_dev);
if (rc == -ENODEV)
rc = virtio_pci_legacy_probe(vp_dev);
if (rc)
goto err_probe;
}
pci_set_master(pci_dev);
rc = register_virtio_device(&vp_dev->vdev);
reg_dev = vp_dev;
if (rc)
goto err_register;
return 0;
err_register:
if (vp_dev->is_legacy)
virtio_pci_legacy_remove(vp_dev);
else
virtio_pci_modern_remove(vp_dev);
err_probe:
pci_disable_device(pci_dev);
err_enable_device:
if (reg_dev)
put_device(&vp_dev->vdev.dev);
else
kfree(vp_dev);
return rc;
}
static void virtio_pci_remove(struct pci_dev *pci_dev)
{
struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
struct device *dev = get_device(&vp_dev->vdev.dev);
/*
* Device is marked broken on surprise removal so that virtio upper
* layers can abort any ongoing operation.
*/
if (!pci_device_is_present(pci_dev))
virtio_break_device(&vp_dev->vdev);
pci_disable_sriov(pci_dev);
unregister_virtio_device(&vp_dev->vdev);
if (vp_dev->is_legacy)
virtio_pci_legacy_remove(vp_dev);
else
virtio_pci_modern_remove(vp_dev);
pci_disable_device(pci_dev);
put_device(dev);
}
static int virtio_pci_sriov_configure(struct pci_dev *pci_dev, int num_vfs)
{
struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
struct virtio_device *vdev = &vp_dev->vdev;
int ret;
if (!(vdev->config->get_status(vdev) & VIRTIO_CONFIG_S_DRIVER_OK))
return -EBUSY;
if (!__virtio_test_bit(vdev, VIRTIO_F_SR_IOV))
return -EINVAL;
if (pci_vfs_assigned(pci_dev))
return -EPERM;
if (num_vfs == 0) {
pci_disable_sriov(pci_dev);
return 0;
}
ret = pci_enable_sriov(pci_dev, num_vfs);
if (ret < 0)
return ret;
return num_vfs;
}
static void virtio_pci_reset_prepare(struct pci_dev *pci_dev)
{
struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
int ret = 0;
ret = virtio_device_reset_prepare(&vp_dev->vdev);
if (ret) {
if (ret != -EOPNOTSUPP)
dev_warn(&pci_dev->dev, "Reset prepare failure: %d",
ret);
return;
}
if (pci_is_enabled(pci_dev))
pci_disable_device(pci_dev);
}
static void virtio_pci_reset_done(struct pci_dev *pci_dev)
{
struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);
int ret;
if (pci_is_enabled(pci_dev))
return;
ret = pci_enable_device(pci_dev);
if (!ret) {
pci_set_master(pci_dev);
ret = virtio_device_reset_done(&vp_dev->vdev);
}
if (ret && ret != -EOPNOTSUPP)
dev_warn(&pci_dev->dev, "Reset done failure: %d", ret);
}
static const struct pci_error_handlers virtio_pci_err_handler = {
.reset_prepare = virtio_pci_reset_prepare,
.reset_done = virtio_pci_reset_done,
};
static struct pci_driver virtio_pci_driver = {
.name = "virtio-pci",
.id_table = virtio_pci_id_table,
.probe = virtio_pci_probe,
.remove = virtio_pci_remove,
#ifdef CONFIG_PM_SLEEP
.driver.pm = &virtio_pci_pm_ops,
#endif
.sriov_configure = virtio_pci_sriov_configure,
.err_handler = &virtio_pci_err_handler,
};
struct virtio_device *virtio_pci_vf_get_pf_dev(struct pci_dev *pdev)
{
struct virtio_pci_device *pf_vp_dev;
pf_vp_dev = pci_iov_get_pf_drvdata(pdev, &virtio_pci_driver);
if (IS_ERR(pf_vp_dev))
return NULL;
return &pf_vp_dev->vdev;
}
module_pci_driver(virtio_pci_driver);
MODULE_AUTHOR("Anthony Liguori <aliguori@us.ibm.com>");
MODULE_DESCRIPTION("virtio-pci");
MODULE_LICENSE("GPL");
MODULE_VERSION("1");
@@ -0,0 +1,201 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _DRIVERS_VIRTIO_VIRTIO_PCI_COMMON_H
#define _DRIVERS_VIRTIO_VIRTIO_PCI_COMMON_H
/*
* Virtio PCI driver - APIs for common functionality for all device versions
*
* This module allows virtio devices to be used over a virtual PCI device.
* This can be used with QEMU based VMMs like KVM or Xen.
*
* Copyright IBM Corp. 2007
* Copyright Red Hat, Inc. 2014
*
* Authors:
* Anthony Liguori <aliguori@us.ibm.com>
* Rusty Russell <rusty@rustcorp.com.au>
* Michael S. Tsirkin <mst@redhat.com>
*/
#include <linux/module.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ring.h>
#include <linux/virtio_pci.h>
#include <linux/virtio_pci_legacy.h>
#include <linux/virtio_pci_modern.h>
#include <linux/highmem.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
struct virtio_pci_vq_info {
/* the actual virtqueue */
struct virtqueue *vq;
/* the list node for the virtqueues or slow_virtqueues list */
struct list_head node;
/* MSI-X vector (or none) */
unsigned int msix_vector;
};
struct virtio_pci_admin_vq {
/* Virtqueue info associated with this admin queue. */
struct virtio_pci_vq_info *info;
/* Protects virtqueue access. */
spinlock_t lock;
u64 supported_cmds;
u64 supported_caps;
u8 max_dev_parts_objects;
struct ida dev_parts_ida;
/* Name of the admin queue: avq.$vq_index. */
char name[10];
u16 vq_index;
};
/* Our device structure */
struct virtio_pci_device {
struct virtio_device vdev;
struct pci_dev *pci_dev;
union {
struct virtio_pci_legacy_device ldev;
struct virtio_pci_modern_device mdev;
};
bool is_legacy;
/* Where to read and clear interrupt */
u8 __iomem *isr;
/* Lists of queues and potentially slow path queues
* so we can dispatch IRQs.
*/
spinlock_t lock;
struct list_head virtqueues;
struct list_head slow_virtqueues;
/* Array of all virtqueues reported in the
* PCI common config num_queues field
*/
struct virtio_pci_vq_info **vqs;
struct virtio_pci_admin_vq admin_vq;
/* MSI-X support */
int msix_enabled;
int intx_enabled;
cpumask_var_t *msix_affinity_masks;
/* Name strings for interrupts. This size should be enough,
* and I'm too lazy to allocate each name separately. */
char (*msix_names)[256];
/* Number of available vectors */
unsigned int msix_vectors;
/* Vectors allocated, excluding per-vq vectors if any */
unsigned int msix_used_vectors;
/* Whether we have vector per vq */
bool per_vq_vectors;
struct virtqueue *(*setup_vq)(struct virtio_pci_device *vp_dev,
struct virtio_pci_vq_info *info,
unsigned int idx,
void (*callback)(struct virtqueue *vq),
const char *name,
bool ctx,
u16 msix_vec);
void (*del_vq)(struct virtio_pci_vq_info *info);
u16 (*config_vector)(struct virtio_pci_device *vp_dev, u16 vector);
int (*avq_index)(struct virtio_device *vdev, u16 *index, u16 *num);
};
/* Constants for MSI-X */
/* Use first vector for configuration changes, second and the rest for
* virtqueues Thus, we need at least 2 vectors for MSI. */
enum {
VP_MSIX_CONFIG_VECTOR = 0,
VP_MSIX_VQ_VECTOR = 1,
};
/* Convert a generic virtio device to our structure */
static struct virtio_pci_device *to_vp_device(struct virtio_device *vdev)
{
return container_of(vdev, struct virtio_pci_device, vdev);
}
/* wait for pending irq handlers */
void vp_synchronize_vectors(struct virtio_device *vdev);
/* the notify function used when creating a virt queue */
bool vp_notify(struct virtqueue *vq);
/* the config->del_vqs() implementation */
void vp_del_vqs(struct virtio_device *vdev);
/* the config->find_vqs() implementation */
int vp_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
struct virtqueue *vqs[], struct virtqueue_info vqs_info[],
struct irq_affinity *desc);
const char *vp_bus_name(struct virtio_device *vdev);
/* Setup the affinity for a virtqueue:
* - force the affinity for per vq vector
* - OR over all affinities for shared MSI
* - ignore the affinity request if we're using INTX
*/
int vp_set_vq_affinity(struct virtqueue *vq, const struct cpumask *cpu_mask);
const struct cpumask *vp_get_vq_affinity(struct virtio_device *vdev, int index);
#if IS_ENABLED(CONFIG_VIRTIO_PCI_LEGACY)
int virtio_pci_legacy_probe(struct virtio_pci_device *);
void virtio_pci_legacy_remove(struct virtio_pci_device *);
#else
static inline int virtio_pci_legacy_probe(struct virtio_pci_device *vp_dev)
{
return -ENODEV;
}
static inline void virtio_pci_legacy_remove(struct virtio_pci_device *vp_dev)
{
}
#endif
int virtio_pci_modern_probe(struct virtio_pci_device *);
void virtio_pci_modern_remove(struct virtio_pci_device *);
struct virtio_device *virtio_pci_vf_get_pf_dev(struct pci_dev *pdev);
#define VIRTIO_LEGACY_ADMIN_CMD_BITMAP \
(BIT_ULL(VIRTIO_ADMIN_CMD_LEGACY_COMMON_CFG_WRITE) | \
BIT_ULL(VIRTIO_ADMIN_CMD_LEGACY_COMMON_CFG_READ) | \
BIT_ULL(VIRTIO_ADMIN_CMD_LEGACY_DEV_CFG_WRITE) | \
BIT_ULL(VIRTIO_ADMIN_CMD_LEGACY_DEV_CFG_READ) | \
BIT_ULL(VIRTIO_ADMIN_CMD_LEGACY_NOTIFY_INFO))
#define VIRTIO_DEV_PARTS_ADMIN_CMD_BITMAP \
(BIT_ULL(VIRTIO_ADMIN_CMD_CAP_ID_LIST_QUERY) | \
BIT_ULL(VIRTIO_ADMIN_CMD_DRIVER_CAP_SET) | \
BIT_ULL(VIRTIO_ADMIN_CMD_DEVICE_CAP_GET) | \
BIT_ULL(VIRTIO_ADMIN_CMD_RESOURCE_OBJ_CREATE) | \
BIT_ULL(VIRTIO_ADMIN_CMD_RESOURCE_OBJ_DESTROY) | \
BIT_ULL(VIRTIO_ADMIN_CMD_DEV_PARTS_METADATA_GET) | \
BIT_ULL(VIRTIO_ADMIN_CMD_DEV_PARTS_GET) | \
BIT_ULL(VIRTIO_ADMIN_CMD_DEV_PARTS_SET) | \
BIT_ULL(VIRTIO_ADMIN_CMD_DEV_MODE_SET))
/* Unlike modern drivers which support hardware virtio devices, legacy drivers
* assume software-based devices: e.g. they don't use proper memory barriers
* on ARM, use big endian on PPC, etc. X86 drivers are mostly ok though, more
* or less by chance. For now, only support legacy IO on X86.
*/
#ifdef CONFIG_VIRTIO_PCI_ADMIN_LEGACY
#define VIRTIO_ADMIN_CMD_BITMAP (VIRTIO_LEGACY_ADMIN_CMD_BITMAP | \
VIRTIO_DEV_PARTS_ADMIN_CMD_BITMAP)
#else
#define VIRTIO_ADMIN_CMD_BITMAP VIRTIO_DEV_PARTS_ADMIN_CMD_BITMAP
#endif
bool vp_is_avq(struct virtio_device *vdev, unsigned int index);
void vp_modern_avq_done(struct virtqueue *vq);
int vp_modern_admin_cmd_exec(struct virtio_device *vdev,
struct virtio_admin_cmd *cmd);
#endif
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Virtio PCI driver - legacy device support
*
* This module allows virtio devices to be used over a virtual PCI device.
* This can be used with QEMU based VMMs like KVM or Xen.
*
* Copyright IBM Corp. 2007
* Copyright Red Hat, Inc. 2014
*
* Authors:
* Anthony Liguori <aliguori@us.ibm.com>
* Rusty Russell <rusty@rustcorp.com.au>
* Michael S. Tsirkin <mst@redhat.com>
*/
#include "linux/virtio_pci_legacy.h"
#include "virtio_pci_common.h"
/* virtio config->get_features() implementation */
static u64 vp_get_features(struct virtio_device *vdev)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
/* When someone needs more than 32 feature bits, we'll need to
* steal a bit to indicate that the rest are somewhere else. */
return vp_legacy_get_features(&vp_dev->ldev);
}
/* virtio config->finalize_features() implementation */
static int vp_finalize_features(struct virtio_device *vdev)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
/* Give virtio_ring a chance to accept features. */
vring_transport_features(vdev);
/* Make sure we don't have any features > 32 bits! */
BUG_ON((u32)vdev->features != vdev->features);
/* We only support 32 feature bits. */
vp_legacy_set_features(&vp_dev->ldev, vdev->features);
return 0;
}
/* virtio config->get() implementation */
static void vp_get(struct virtio_device *vdev, unsigned int offset,
void *buf, unsigned int len)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
void __iomem *ioaddr = vp_dev->ldev.ioaddr +
VIRTIO_PCI_CONFIG_OFF(vp_dev->msix_enabled) +
offset;
u8 *ptr = buf;
int i;
for (i = 0; i < len; i++)
ptr[i] = ioread8(ioaddr + i);
}
/* the config->set() implementation. it's symmetric to the config->get()
* implementation */
static void vp_set(struct virtio_device *vdev, unsigned int offset,
const void *buf, unsigned int len)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
void __iomem *ioaddr = vp_dev->ldev.ioaddr +
VIRTIO_PCI_CONFIG_OFF(vp_dev->msix_enabled) +
offset;
const u8 *ptr = buf;
int i;
for (i = 0; i < len; i++)
iowrite8(ptr[i], ioaddr + i);
}
/* config->{get,set}_status() implementations */
static u8 vp_get_status(struct virtio_device *vdev)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
return vp_legacy_get_status(&vp_dev->ldev);
}
static void vp_set_status(struct virtio_device *vdev, u8 status)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
/* We should never be setting status to 0. */
BUG_ON(status == 0);
vp_legacy_set_status(&vp_dev->ldev, status);
}
static void vp_reset(struct virtio_device *vdev)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
/* 0 status means a reset. */
vp_legacy_set_status(&vp_dev->ldev, 0);
/* Flush out the status write, and flush in device writes,
* including MSi-X interrupts, if any. */
vp_legacy_get_status(&vp_dev->ldev);
/* Flush pending VQ/configuration callbacks. */
vp_synchronize_vectors(vdev);
}
static u16 vp_config_vector(struct virtio_pci_device *vp_dev, u16 vector)
{
return vp_legacy_config_vector(&vp_dev->ldev, vector);
}
static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
struct virtio_pci_vq_info *info,
unsigned int index,
void (*callback)(struct virtqueue *vq),
const char *name,
bool ctx,
u16 msix_vec)
{
struct virtqueue *vq;
u16 num;
int err;
u64 q_pfn;
/* Check if queue is either not available or already active. */
num = vp_legacy_get_queue_size(&vp_dev->ldev, index);
if (!num || vp_legacy_get_queue_enable(&vp_dev->ldev, index))
return ERR_PTR(-ENOENT);
info->msix_vector = msix_vec;
/* create the vring */
vq = vring_create_virtqueue(index, num,
VIRTIO_PCI_VRING_ALIGN, &vp_dev->vdev,
true, false, ctx,
vp_notify, callback, name);
if (!vq)
return ERR_PTR(-ENOMEM);
vq->num_max = num;
q_pfn = virtqueue_get_desc_addr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT;
if (q_pfn >> 32) {
dev_err(&vp_dev->pci_dev->dev,
"platform bug: legacy virtio-pci must not be used with RAM above 0x%llxGB\n",
0x1ULL << (32 + PAGE_SHIFT - 30));
err = -E2BIG;
goto out_del_vq;
}
/* activate the queue */
vp_legacy_set_queue_address(&vp_dev->ldev, index, q_pfn);
vq->priv = (void __force *)vp_dev->ldev.ioaddr + VIRTIO_PCI_QUEUE_NOTIFY;
if (msix_vec != VIRTIO_MSI_NO_VECTOR) {
msix_vec = vp_legacy_queue_vector(&vp_dev->ldev, index, msix_vec);
if (msix_vec == VIRTIO_MSI_NO_VECTOR) {
err = -EBUSY;
goto out_deactivate;
}
}
return vq;
out_deactivate:
vp_legacy_set_queue_address(&vp_dev->ldev, index, 0);
out_del_vq:
vring_del_virtqueue(vq);
return ERR_PTR(err);
}
static void del_vq(struct virtio_pci_vq_info *info)
{
struct virtqueue *vq = info->vq;
struct virtio_pci_device *vp_dev = to_vp_device(vq->vdev);
if (vp_dev->msix_enabled) {
vp_legacy_queue_vector(&vp_dev->ldev, vq->index,
VIRTIO_MSI_NO_VECTOR);
/* Flush the write out to device */
ioread8(vp_dev->ldev.ioaddr + VIRTIO_PCI_ISR);
}
/* Select and deactivate the queue */
vp_legacy_set_queue_address(&vp_dev->ldev, vq->index, 0);
vring_del_virtqueue(vq);
}
static const struct virtio_config_ops virtio_pci_config_ops = {
.get = vp_get,
.set = vp_set,
.get_status = vp_get_status,
.set_status = vp_set_status,
.reset = vp_reset,
.find_vqs = vp_find_vqs,
.del_vqs = vp_del_vqs,
.synchronize_cbs = vp_synchronize_vectors,
.get_features = vp_get_features,
.finalize_features = vp_finalize_features,
.bus_name = vp_bus_name,
.set_vq_affinity = vp_set_vq_affinity,
.get_vq_affinity = vp_get_vq_affinity,
};
/* the PCI probing function */
int virtio_pci_legacy_probe(struct virtio_pci_device *vp_dev)
{
struct virtio_pci_legacy_device *ldev = &vp_dev->ldev;
struct pci_dev *pci_dev = vp_dev->pci_dev;
int rc;
ldev->pci_dev = pci_dev;
rc = vp_legacy_probe(ldev);
if (rc)
return rc;
vp_dev->isr = ldev->isr;
vp_dev->vdev.id = ldev->id;
vp_dev->vdev.config = &virtio_pci_config_ops;
vp_dev->config_vector = vp_config_vector;
vp_dev->setup_vq = setup_vq;
vp_dev->del_vq = del_vq;
vp_dev->is_legacy = true;
return 0;
}
void virtio_pci_legacy_remove(struct virtio_pci_device *vp_dev)
{
struct virtio_pci_legacy_device *ldev = &vp_dev->ldev;
vp_legacy_remove(ldev);
}
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "linux/virtio_pci.h"
#include <linux/virtio_pci_legacy.h>
#include <linux/module.h>
#include <linux/pci.h>
/*
* vp_legacy_probe: probe the legacy virtio pci device, note that the
* caller is required to enable PCI device before calling this function.
* @ldev: the legacy virtio-pci device
*
* Return 0 on succeed otherwise fail
*/
int vp_legacy_probe(struct virtio_pci_legacy_device *ldev)
{
struct pci_dev *pci_dev = ldev->pci_dev;
int rc;
/* We only own devices >= 0x1000 and <= 0x103f: leave the rest. */
if (pci_dev->device < 0x1000 || pci_dev->device > 0x103f)
return -ENODEV;
if (pci_dev->revision != VIRTIO_PCI_ABI_VERSION)
return -ENODEV;
rc = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(64));
if (rc) {
rc = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(32));
} else {
/*
* The virtio ring base address is expressed as a 32-bit PFN,
* with a page size of 1 << VIRTIO_PCI_QUEUE_ADDR_SHIFT.
*/
dma_set_coherent_mask(&pci_dev->dev,
DMA_BIT_MASK(32 + VIRTIO_PCI_QUEUE_ADDR_SHIFT));
}
if (rc)
dev_warn(&pci_dev->dev, "Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.\n");
rc = pci_request_region(pci_dev, 0, "virtio-pci-legacy");
if (rc)
return rc;
ldev->ioaddr = pci_iomap(pci_dev, 0, 0);
if (!ldev->ioaddr) {
rc = -EIO;
goto err_iomap;
}
ldev->isr = ldev->ioaddr + VIRTIO_PCI_ISR;
ldev->id.vendor = pci_dev->subsystem_vendor;
ldev->id.device = pci_dev->subsystem_device;
return 0;
err_iomap:
pci_release_region(pci_dev, 0);
return rc;
}
EXPORT_SYMBOL_GPL(vp_legacy_probe);
/*
* vp_legacy_probe: remove and cleanup the legacy virtio pci device
* @ldev: the legacy virtio-pci device
*/
void vp_legacy_remove(struct virtio_pci_legacy_device *ldev)
{
struct pci_dev *pci_dev = ldev->pci_dev;
pci_iounmap(pci_dev, ldev->ioaddr);
pci_release_region(pci_dev, 0);
}
EXPORT_SYMBOL_GPL(vp_legacy_remove);
/*
* vp_legacy_get_features - get features from device
* @ldev: the legacy virtio-pci device
*
* Returns the features read from the device
*/
u64 vp_legacy_get_features(struct virtio_pci_legacy_device *ldev)
{
return ioread32(ldev->ioaddr + VIRTIO_PCI_HOST_FEATURES);
}
EXPORT_SYMBOL_GPL(vp_legacy_get_features);
/*
* vp_legacy_get_driver_features - get driver features from device
* @ldev: the legacy virtio-pci device
*
* Returns the driver features read from the device
*/
u64 vp_legacy_get_driver_features(struct virtio_pci_legacy_device *ldev)
{
return ioread32(ldev->ioaddr + VIRTIO_PCI_GUEST_FEATURES);
}
EXPORT_SYMBOL_GPL(vp_legacy_get_driver_features);
/*
* vp_legacy_set_features - set features to device
* @ldev: the legacy virtio-pci device
* @features: the features set to device
*/
void vp_legacy_set_features(struct virtio_pci_legacy_device *ldev,
u32 features)
{
iowrite32(features, ldev->ioaddr + VIRTIO_PCI_GUEST_FEATURES);
}
EXPORT_SYMBOL_GPL(vp_legacy_set_features);
/*
* vp_legacy_get_status - get the device status
* @ldev: the legacy virtio-pci device
*
* Returns the status read from device
*/
u8 vp_legacy_get_status(struct virtio_pci_legacy_device *ldev)
{
return ioread8(ldev->ioaddr + VIRTIO_PCI_STATUS);
}
EXPORT_SYMBOL_GPL(vp_legacy_get_status);
/*
* vp_legacy_set_status - set status to device
* @ldev: the legacy virtio-pci device
* @status: the status set to device
*/
void vp_legacy_set_status(struct virtio_pci_legacy_device *ldev,
u8 status)
{
iowrite8(status, ldev->ioaddr + VIRTIO_PCI_STATUS);
}
EXPORT_SYMBOL_GPL(vp_legacy_set_status);
/*
* vp_legacy_queue_vector - set the MSIX vector for a specific virtqueue
* @ldev: the legacy virtio-pci device
* @index: queue index
* @vector: the queue vector
*
* Returns the queue vector read from the device
*/
u16 vp_legacy_queue_vector(struct virtio_pci_legacy_device *ldev,
u16 index, u16 vector)
{
iowrite16(index, ldev->ioaddr + VIRTIO_PCI_QUEUE_SEL);
iowrite16(vector, ldev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR);
/* Flush the write out to device */
return ioread16(ldev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR);
}
EXPORT_SYMBOL_GPL(vp_legacy_queue_vector);
/*
* vp_legacy_config_vector - set the vector for config interrupt
* @ldev: the legacy virtio-pci device
* @vector: the config vector
*
* Returns the config vector read from the device
*/
u16 vp_legacy_config_vector(struct virtio_pci_legacy_device *ldev,
u16 vector)
{
/* Setup the vector used for configuration events */
iowrite16(vector, ldev->ioaddr + VIRTIO_MSI_CONFIG_VECTOR);
/* Verify we had enough resources to assign the vector */
/* Will also flush the write out to device */
return ioread16(ldev->ioaddr + VIRTIO_MSI_CONFIG_VECTOR);
}
EXPORT_SYMBOL_GPL(vp_legacy_config_vector);
/*
* vp_legacy_set_queue_address - set the virtqueue address
* @ldev: the legacy virtio-pci device
* @index: the queue index
* @queue_pfn: pfn of the virtqueue
*/
void vp_legacy_set_queue_address(struct virtio_pci_legacy_device *ldev,
u16 index, u32 queue_pfn)
{
iowrite16(index, ldev->ioaddr + VIRTIO_PCI_QUEUE_SEL);
iowrite32(queue_pfn, ldev->ioaddr + VIRTIO_PCI_QUEUE_PFN);
}
EXPORT_SYMBOL_GPL(vp_legacy_set_queue_address);
/*
* vp_legacy_get_queue_enable - enable a virtqueue
* @ldev: the legacy virtio-pci device
* @index: the queue index
*
* Returns whether a virtqueue is enabled or not
*/
bool vp_legacy_get_queue_enable(struct virtio_pci_legacy_device *ldev,
u16 index)
{
iowrite16(index, ldev->ioaddr + VIRTIO_PCI_QUEUE_SEL);
return ioread32(ldev->ioaddr + VIRTIO_PCI_QUEUE_PFN);
}
EXPORT_SYMBOL_GPL(vp_legacy_get_queue_enable);
/*
* vp_legacy_get_queue_size - get size for a virtqueue
* @ldev: the legacy virtio-pci device
* @index: the queue index
*
* Returns the size of the virtqueue
*/
u16 vp_legacy_get_queue_size(struct virtio_pci_legacy_device *ldev,
u16 index)
{
iowrite16(index, ldev->ioaddr + VIRTIO_PCI_QUEUE_SEL);
return ioread16(ldev->ioaddr + VIRTIO_PCI_QUEUE_NUM);
}
EXPORT_SYMBOL_GPL(vp_legacy_get_queue_size);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Legacy Virtio PCI Device");
MODULE_AUTHOR("Wu Zongyong <wuzongyong@linux.alibaba.com>");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,758 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/virtio_pci_modern.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/delay.h>
/*
* vp_modern_map_capability - map a part of virtio pci capability
* @mdev: the modern virtio-pci device
* @off: offset of the capability
* @minlen: minimal length of the capability
* @align: align requirement
* @start: start from the capability
* @size: map size
* @len: the length that is actually mapped
* @pa: physical address of the capability
*
* Returns the io address of for the part of the capability
*/
static void __iomem *
vp_modern_map_capability(struct virtio_pci_modern_device *mdev, int off,
size_t minlen, u32 align, u32 start, u32 size,
size_t *len, resource_size_t *pa)
{
struct pci_dev *dev = mdev->pci_dev;
u8 bar;
u32 offset, length;
void __iomem *p;
pci_read_config_byte(dev, off + offsetof(struct virtio_pci_cap,
bar),
&bar);
pci_read_config_dword(dev, off + offsetof(struct virtio_pci_cap, offset),
&offset);
pci_read_config_dword(dev, off + offsetof(struct virtio_pci_cap, length),
&length);
/* Check if the BAR may have changed since we requested the region. */
if (bar >= PCI_STD_NUM_BARS || !(mdev->modern_bars & (1 << bar))) {
dev_err(&dev->dev,
"virtio_pci: bar unexpectedly changed to %u\n", bar);
return NULL;
}
if (length <= start) {
dev_err(&dev->dev,
"virtio_pci: bad capability len %u (>%u expected)\n",
length, start);
return NULL;
}
if (length - start < minlen) {
dev_err(&dev->dev,
"virtio_pci: bad capability len %u (>=%zu expected)\n",
length, minlen);
return NULL;
}
length -= start;
if (start + offset < offset) {
dev_err(&dev->dev,
"virtio_pci: map wrap-around %u+%u\n",
start, offset);
return NULL;
}
offset += start;
if (offset & (align - 1)) {
dev_err(&dev->dev,
"virtio_pci: offset %u not aligned to %u\n",
offset, align);
return NULL;
}
if (length > size)
length = size;
if (len)
*len = length;
if (minlen + offset < minlen ||
minlen + offset > pci_resource_len(dev, bar)) {
dev_err(&dev->dev,
"virtio_pci: map virtio %zu@%u "
"out of range on bar %i length %lu\n",
minlen, offset,
bar, (unsigned long)pci_resource_len(dev, bar));
return NULL;
}
p = pci_iomap_range(dev, bar, offset, length);
if (!p)
dev_err(&dev->dev,
"virtio_pci: unable to map virtio %u@%u on bar %i\n",
length, offset, bar);
else if (pa)
*pa = pci_resource_start(dev, bar) + offset;
return p;
}
/**
* virtio_pci_find_capability - walk capabilities to find device info.
* @dev: the pci device
* @cfg_type: the VIRTIO_PCI_CAP_* value we seek
* @ioresource_types: IORESOURCE_MEM and/or IORESOURCE_IO.
* @bars: the bitmask of BARs
*
* Returns offset of the capability, or 0.
*/
static inline int virtio_pci_find_capability(struct pci_dev *dev, u8 cfg_type,
u32 ioresource_types, int *bars)
{
int pos;
for (pos = pci_find_capability(dev, PCI_CAP_ID_VNDR);
pos > 0;
pos = pci_find_next_capability(dev, pos, PCI_CAP_ID_VNDR)) {
u8 type, bar;
pci_read_config_byte(dev, pos + offsetof(struct virtio_pci_cap,
cfg_type),
&type);
pci_read_config_byte(dev, pos + offsetof(struct virtio_pci_cap,
bar),
&bar);
/* Ignore structures with reserved BAR values */
if (bar >= PCI_STD_NUM_BARS)
continue;
if (type == cfg_type) {
if (pci_resource_len(dev, bar) &&
pci_resource_flags(dev, bar) & ioresource_types) {
*bars |= (1 << bar);
return pos;
}
}
}
return 0;
}
/* This is part of the ABI. Don't screw with it. */
static inline void check_offsets(void)
{
/* Note: disk space was harmed in compilation of this function. */
BUILD_BUG_ON(VIRTIO_PCI_CAP_VNDR !=
offsetof(struct virtio_pci_cap, cap_vndr));
BUILD_BUG_ON(VIRTIO_PCI_CAP_NEXT !=
offsetof(struct virtio_pci_cap, cap_next));
BUILD_BUG_ON(VIRTIO_PCI_CAP_LEN !=
offsetof(struct virtio_pci_cap, cap_len));
BUILD_BUG_ON(VIRTIO_PCI_CAP_CFG_TYPE !=
offsetof(struct virtio_pci_cap, cfg_type));
BUILD_BUG_ON(VIRTIO_PCI_CAP_BAR !=
offsetof(struct virtio_pci_cap, bar));
BUILD_BUG_ON(VIRTIO_PCI_CAP_OFFSET !=
offsetof(struct virtio_pci_cap, offset));
BUILD_BUG_ON(VIRTIO_PCI_CAP_LENGTH !=
offsetof(struct virtio_pci_cap, length));
BUILD_BUG_ON(VIRTIO_PCI_NOTIFY_CAP_MULT !=
offsetof(struct virtio_pci_notify_cap,
notify_off_multiplier));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_DFSELECT !=
offsetof(struct virtio_pci_common_cfg,
device_feature_select));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_DF !=
offsetof(struct virtio_pci_common_cfg, device_feature));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_GFSELECT !=
offsetof(struct virtio_pci_common_cfg,
guest_feature_select));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_GF !=
offsetof(struct virtio_pci_common_cfg, guest_feature));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_MSIX !=
offsetof(struct virtio_pci_common_cfg, msix_config));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_NUMQ !=
offsetof(struct virtio_pci_common_cfg, num_queues));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_STATUS !=
offsetof(struct virtio_pci_common_cfg, device_status));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_CFGGENERATION !=
offsetof(struct virtio_pci_common_cfg, config_generation));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_SELECT !=
offsetof(struct virtio_pci_common_cfg, queue_select));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_SIZE !=
offsetof(struct virtio_pci_common_cfg, queue_size));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_MSIX !=
offsetof(struct virtio_pci_common_cfg, queue_msix_vector));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_ENABLE !=
offsetof(struct virtio_pci_common_cfg, queue_enable));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_NOFF !=
offsetof(struct virtio_pci_common_cfg, queue_notify_off));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_DESCLO !=
offsetof(struct virtio_pci_common_cfg, queue_desc_lo));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_DESCHI !=
offsetof(struct virtio_pci_common_cfg, queue_desc_hi));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_AVAILLO !=
offsetof(struct virtio_pci_common_cfg, queue_avail_lo));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_AVAILHI !=
offsetof(struct virtio_pci_common_cfg, queue_avail_hi));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_USEDLO !=
offsetof(struct virtio_pci_common_cfg, queue_used_lo));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_USEDHI !=
offsetof(struct virtio_pci_common_cfg, queue_used_hi));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_NDATA !=
offsetof(struct virtio_pci_modern_common_cfg, queue_notify_data));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_Q_RESET !=
offsetof(struct virtio_pci_modern_common_cfg, queue_reset));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_ADM_Q_IDX !=
offsetof(struct virtio_pci_modern_common_cfg, admin_queue_index));
BUILD_BUG_ON(VIRTIO_PCI_COMMON_ADM_Q_NUM !=
offsetof(struct virtio_pci_modern_common_cfg, admin_queue_num));
}
/*
* vp_modern_probe: probe the modern virtio pci device, note that the
* caller is required to enable PCI device before calling this function.
* @mdev: the modern virtio-pci device
*
* Return 0 on succeed otherwise fail
*/
int vp_modern_probe(struct virtio_pci_modern_device *mdev)
{
struct pci_dev *pci_dev = mdev->pci_dev;
int err, common, isr, notify, device;
u32 notify_length;
u32 notify_offset;
int devid;
check_offsets();
if (mdev->device_id_check) {
devid = mdev->device_id_check(pci_dev);
if (devid < 0)
return devid;
mdev->id.device = devid;
} else {
/* We only own devices >= 0x1000 and <= 0x107f: leave the rest. */
if (pci_dev->device < 0x1000 || pci_dev->device > 0x107f)
return -ENODEV;
if (pci_dev->device < 0x1040) {
/* Transitional devices: use the PCI subsystem device id as
* virtio device id, same as legacy driver always did.
*/
mdev->id.device = pci_dev->subsystem_device;
} else {
/* Modern devices: simply use PCI device id, but start from 0x1040. */
mdev->id.device = pci_dev->device - 0x1040;
}
}
mdev->id.vendor = pci_dev->subsystem_vendor;
/* check for a common config: if not, use legacy mode (bar 0). */
common = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_COMMON_CFG,
IORESOURCE_IO | IORESOURCE_MEM,
&mdev->modern_bars);
if (!common) {
dev_info(&pci_dev->dev,
"virtio_pci: leaving for legacy driver\n");
return -ENODEV;
}
/* If common is there, these should be too... */
isr = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_ISR_CFG,
IORESOURCE_IO | IORESOURCE_MEM,
&mdev->modern_bars);
notify = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_NOTIFY_CFG,
IORESOURCE_IO | IORESOURCE_MEM,
&mdev->modern_bars);
if (!isr || !notify) {
dev_err(&pci_dev->dev,
"virtio_pci: missing capabilities %i/%i/%i\n",
common, isr, notify);
return -EINVAL;
}
err = dma_set_mask_and_coherent(&pci_dev->dev,
mdev->dma_mask ? : DMA_BIT_MASK(64));
if (err)
err = dma_set_mask_and_coherent(&pci_dev->dev,
DMA_BIT_MASK(32));
if (err)
dev_warn(&pci_dev->dev, "Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.\n");
/* Device capability is only mandatory for devices that have
* device-specific configuration.
*/
device = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_DEVICE_CFG,
IORESOURCE_IO | IORESOURCE_MEM,
&mdev->modern_bars);
err = pci_request_selected_regions(pci_dev, mdev->modern_bars,
"virtio-pci-modern");
if (err)
return err;
err = -EINVAL;
mdev->common = vp_modern_map_capability(mdev, common,
sizeof(struct virtio_pci_common_cfg), 4, 0,
offsetofend(struct virtio_pci_modern_common_cfg,
admin_queue_num),
&mdev->common_len, NULL);
if (!mdev->common)
goto err_map_common;
mdev->isr = vp_modern_map_capability(mdev, isr, sizeof(u8), 1,
0, 1,
NULL, NULL);
if (!mdev->isr)
goto err_map_isr;
/* Read notify_off_multiplier from config space. */
pci_read_config_dword(pci_dev,
notify + offsetof(struct virtio_pci_notify_cap,
notify_off_multiplier),
&mdev->notify_offset_multiplier);
/* Read notify length and offset from config space. */
pci_read_config_dword(pci_dev,
notify + offsetof(struct virtio_pci_notify_cap,
cap.length),
&notify_length);
pci_read_config_dword(pci_dev,
notify + offsetof(struct virtio_pci_notify_cap,
cap.offset),
&notify_offset);
/* We don't know how many VQs we'll map, ahead of the time.
* If notify length is small, map it all now.
* Otherwise, map each VQ individually later.
*/
if ((u64)notify_length + (notify_offset % PAGE_SIZE) <= PAGE_SIZE) {
mdev->notify_base = vp_modern_map_capability(mdev, notify,
2, 2,
0, notify_length,
&mdev->notify_len,
&mdev->notify_pa);
if (!mdev->notify_base)
goto err_map_notify;
} else {
mdev->notify_map_cap = notify;
}
/* Again, we don't know how much we should map, but PAGE_SIZE
* is more than enough for all existing devices.
*/
if (device) {
mdev->device = vp_modern_map_capability(mdev, device, 0, 4,
0, PAGE_SIZE,
&mdev->device_len,
NULL);
if (!mdev->device)
goto err_map_device;
}
return 0;
err_map_device:
if (mdev->notify_base)
pci_iounmap(pci_dev, mdev->notify_base);
err_map_notify:
pci_iounmap(pci_dev, mdev->isr);
err_map_isr:
pci_iounmap(pci_dev, mdev->common);
err_map_common:
pci_release_selected_regions(pci_dev, mdev->modern_bars);
return err;
}
EXPORT_SYMBOL_GPL(vp_modern_probe);
/*
* vp_modern_remove: remove and cleanup the modern virtio pci device
* @mdev: the modern virtio-pci device
*/
void vp_modern_remove(struct virtio_pci_modern_device *mdev)
{
struct pci_dev *pci_dev = mdev->pci_dev;
if (mdev->device)
pci_iounmap(pci_dev, mdev->device);
if (mdev->notify_base)
pci_iounmap(pci_dev, mdev->notify_base);
pci_iounmap(pci_dev, mdev->isr);
pci_iounmap(pci_dev, mdev->common);
pci_release_selected_regions(pci_dev, mdev->modern_bars);
}
EXPORT_SYMBOL_GPL(vp_modern_remove);
/*
* vp_modern_get_extended_features - get features from device
* @mdev: the modern virtio-pci device
* @features: the features array to be filled
*
* Fill the specified features array with the features read from the device
*/
void vp_modern_get_extended_features(struct virtio_pci_modern_device *mdev,
u64 *features)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
int i;
virtio_features_zero(features);
for (i = 0; i < VIRTIO_FEATURES_BITS / 32; i++) {
u64 cur;
vp_iowrite32(i, &cfg->device_feature_select);
cur = vp_ioread32(&cfg->device_feature);
features[i >> 1] |= cur << (32 * (i & 1));
}
}
EXPORT_SYMBOL_GPL(vp_modern_get_extended_features);
/*
* vp_modern_get_driver_features - get driver features from device
* @mdev: the modern virtio-pci device
* @features: the features array to be filled
*
* Fill the specified features array with the driver features read from the
* device
*/
void
vp_modern_get_driver_extended_features(struct virtio_pci_modern_device *mdev,
u64 *features)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
int i;
virtio_features_zero(features);
for (i = 0; i < VIRTIO_FEATURES_BITS / 32; i++) {
u64 cur;
vp_iowrite32(i, &cfg->guest_feature_select);
cur = vp_ioread32(&cfg->guest_feature);
features[i >> 1] |= cur << (32 * (i & 1));
}
}
EXPORT_SYMBOL_GPL(vp_modern_get_driver_extended_features);
/*
* vp_modern_set_extended_features - set features to device
* @mdev: the modern virtio-pci device
* @features: the features set to device
*/
void vp_modern_set_extended_features(struct virtio_pci_modern_device *mdev,
const u64 *features)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
int i;
for (i = 0; i < VIRTIO_FEATURES_BITS / 32; i++) {
u32 cur = features[i >> 1] >> (32 * (i & 1));
vp_iowrite32(i, &cfg->guest_feature_select);
vp_iowrite32(cur, &cfg->guest_feature);
}
}
EXPORT_SYMBOL_GPL(vp_modern_set_extended_features);
/*
* vp_modern_generation - get the device genreation
* @mdev: the modern virtio-pci device
*
* Returns the genreation read from device
*/
u32 vp_modern_generation(struct virtio_pci_modern_device *mdev)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
return vp_ioread8(&cfg->config_generation);
}
EXPORT_SYMBOL_GPL(vp_modern_generation);
/*
* vp_modern_get_status - get the device status
* @mdev: the modern virtio-pci device
*
* Returns the status read from device
*/
u8 vp_modern_get_status(struct virtio_pci_modern_device *mdev)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
return vp_ioread8(&cfg->device_status);
}
EXPORT_SYMBOL_GPL(vp_modern_get_status);
/*
* vp_modern_set_status - set status to device
* @mdev: the modern virtio-pci device
* @status: the status set to device
*/
void vp_modern_set_status(struct virtio_pci_modern_device *mdev,
u8 status)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
/*
* Per memory-barriers.txt, wmb() is not needed to guarantee
* that the cache coherent memory writes have completed
* before writing to the MMIO region.
*/
vp_iowrite8(status, &cfg->device_status);
}
EXPORT_SYMBOL_GPL(vp_modern_set_status);
/*
* vp_modern_get_queue_reset - get the queue reset status
* @mdev: the modern virtio-pci device
* @index: queue index
*/
int vp_modern_get_queue_reset(struct virtio_pci_modern_device *mdev, u16 index)
{
struct virtio_pci_modern_common_cfg __iomem *cfg;
cfg = (struct virtio_pci_modern_common_cfg __iomem *)mdev->common;
vp_iowrite16(index, &cfg->cfg.queue_select);
return vp_ioread16(&cfg->queue_reset);
}
EXPORT_SYMBOL_GPL(vp_modern_get_queue_reset);
/*
* vp_modern_set_queue_reset - reset the queue
* @mdev: the modern virtio-pci device
* @index: queue index
*/
void vp_modern_set_queue_reset(struct virtio_pci_modern_device *mdev, u16 index)
{
struct virtio_pci_modern_common_cfg __iomem *cfg;
cfg = (struct virtio_pci_modern_common_cfg __iomem *)mdev->common;
vp_iowrite16(index, &cfg->cfg.queue_select);
vp_iowrite16(1, &cfg->queue_reset);
while (vp_ioread16(&cfg->queue_reset))
msleep(1);
while (vp_ioread16(&cfg->cfg.queue_enable))
msleep(1);
}
EXPORT_SYMBOL_GPL(vp_modern_set_queue_reset);
/*
* vp_modern_queue_vector - set the MSIX vector for a specific virtqueue
* @mdev: the modern virtio-pci device
* @index: queue index
* @vector: the queue vector
*
* Returns the queue vector read from the device
*/
u16 vp_modern_queue_vector(struct virtio_pci_modern_device *mdev,
u16 index, u16 vector)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
vp_iowrite16(index, &cfg->queue_select);
vp_iowrite16(vector, &cfg->queue_msix_vector);
/* Flush the write out to device */
return vp_ioread16(&cfg->queue_msix_vector);
}
EXPORT_SYMBOL_GPL(vp_modern_queue_vector);
/*
* vp_modern_config_vector - set the vector for config interrupt
* @mdev: the modern virtio-pci device
* @vector: the config vector
*
* Returns the config vector read from the device
*/
u16 vp_modern_config_vector(struct virtio_pci_modern_device *mdev,
u16 vector)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
/* Setup the vector used for configuration events */
vp_iowrite16(vector, &cfg->msix_config);
/* Verify we had enough resources to assign the vector */
/* Will also flush the write out to device */
return vp_ioread16(&cfg->msix_config);
}
EXPORT_SYMBOL_GPL(vp_modern_config_vector);
/*
* vp_modern_queue_address - set the virtqueue address
* @mdev: the modern virtio-pci device
* @index: the queue index
* @desc_addr: address of the descriptor area
* @driver_addr: address of the driver area
* @device_addr: address of the device area
*/
void vp_modern_queue_address(struct virtio_pci_modern_device *mdev,
u16 index, u64 desc_addr, u64 driver_addr,
u64 device_addr)
{
struct virtio_pci_common_cfg __iomem *cfg = mdev->common;
vp_iowrite16(index, &cfg->queue_select);
vp_iowrite64_twopart(desc_addr, &cfg->queue_desc_lo,
&cfg->queue_desc_hi);
vp_iowrite64_twopart(driver_addr, &cfg->queue_avail_lo,
&cfg->queue_avail_hi);
vp_iowrite64_twopart(device_addr, &cfg->queue_used_lo,
&cfg->queue_used_hi);
}
EXPORT_SYMBOL_GPL(vp_modern_queue_address);
/*
* vp_modern_set_queue_enable - enable a virtqueue
* @mdev: the modern virtio-pci device
* @index: the queue index
* @enable: whether the virtqueue is enable or not
*/
void vp_modern_set_queue_enable(struct virtio_pci_modern_device *mdev,
u16 index, bool enable)
{
vp_iowrite16(index, &mdev->common->queue_select);
vp_iowrite16(enable, &mdev->common->queue_enable);
}
EXPORT_SYMBOL_GPL(vp_modern_set_queue_enable);
/*
* vp_modern_get_queue_enable - enable a virtqueue
* @mdev: the modern virtio-pci device
* @index: the queue index
*
* Returns whether a virtqueue is enabled or not
*/
bool vp_modern_get_queue_enable(struct virtio_pci_modern_device *mdev,
u16 index)
{
vp_iowrite16(index, &mdev->common->queue_select);
return vp_ioread16(&mdev->common->queue_enable);
}
EXPORT_SYMBOL_GPL(vp_modern_get_queue_enable);
/*
* vp_modern_set_queue_size - set size for a virtqueue
* @mdev: the modern virtio-pci device
* @index: the queue index
* @size: the size of the virtqueue
*/
void vp_modern_set_queue_size(struct virtio_pci_modern_device *mdev,
u16 index, u16 size)
{
vp_iowrite16(index, &mdev->common->queue_select);
vp_iowrite16(size, &mdev->common->queue_size);
}
EXPORT_SYMBOL_GPL(vp_modern_set_queue_size);
/*
* vp_modern_get_queue_size - get size for a virtqueue
* @mdev: the modern virtio-pci device
* @index: the queue index
*
* Returns the size of the virtqueue
*/
u16 vp_modern_get_queue_size(struct virtio_pci_modern_device *mdev,
u16 index)
{
vp_iowrite16(index, &mdev->common->queue_select);
return vp_ioread16(&mdev->common->queue_size);
}
EXPORT_SYMBOL_GPL(vp_modern_get_queue_size);
/*
* vp_modern_get_num_queues - get the number of virtqueues
* @mdev: the modern virtio-pci device
*
* Returns the number of virtqueues
*/
u16 vp_modern_get_num_queues(struct virtio_pci_modern_device *mdev)
{
return vp_ioread16(&mdev->common->num_queues);
}
EXPORT_SYMBOL_GPL(vp_modern_get_num_queues);
/*
* vp_modern_get_queue_notify_off - get notification offset for a virtqueue
* @mdev: the modern virtio-pci device
* @index: the queue index
*
* Returns the notification offset for a virtqueue
*/
static u16 vp_modern_get_queue_notify_off(struct virtio_pci_modern_device *mdev,
u16 index)
{
vp_iowrite16(index, &mdev->common->queue_select);
return vp_ioread16(&mdev->common->queue_notify_off);
}
/*
* vp_modern_map_vq_notify - map notification area for a
* specific virtqueue
* @mdev: the modern virtio-pci device
* @index: the queue index
* @pa: the pointer to the physical address of the nofity area
*
* Returns the address of the notification area
*/
void __iomem *vp_modern_map_vq_notify(struct virtio_pci_modern_device *mdev,
u16 index, resource_size_t *pa)
{
u16 off = vp_modern_get_queue_notify_off(mdev, index);
if (mdev->notify_base) {
/* offset should not wrap */
if ((u64)off * mdev->notify_offset_multiplier + 2
> mdev->notify_len) {
dev_warn(&mdev->pci_dev->dev,
"bad notification offset %u (x %u) "
"for queue %u > %zd",
off, mdev->notify_offset_multiplier,
index, mdev->notify_len);
return NULL;
}
if (pa)
*pa = mdev->notify_pa +
off * mdev->notify_offset_multiplier;
return mdev->notify_base + off * mdev->notify_offset_multiplier;
} else {
return vp_modern_map_capability(mdev,
mdev->notify_map_cap, 2, 2,
off * mdev->notify_offset_multiplier, 2,
NULL, pa);
}
}
EXPORT_SYMBOL_GPL(vp_modern_map_vq_notify);
u16 vp_modern_avq_num(struct virtio_pci_modern_device *mdev)
{
struct virtio_pci_modern_common_cfg __iomem *cfg;
cfg = (struct virtio_pci_modern_common_cfg __iomem *)mdev->common;
return vp_ioread16(&cfg->admin_queue_num);
}
EXPORT_SYMBOL_GPL(vp_modern_avq_num);
u16 vp_modern_avq_index(struct virtio_pci_modern_device *mdev)
{
struct virtio_pci_modern_common_cfg __iomem *cfg;
cfg = (struct virtio_pci_modern_common_cfg __iomem *)mdev->common;
return vp_ioread16(&cfg->admin_queue_index);
}
EXPORT_SYMBOL_GPL(vp_modern_avq_index);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Modern Virtio PCI Device");
MODULE_AUTHOR("Jason Wang <jasowang@redhat.com>");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Provides cross-timestamp params for Arm.
*
* Copyright (C) 2022-2023 OpenSynergy GmbH
* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/clocksource_ids.h>
#include <uapi/linux/virtio_rtc.h>
#include "virtio_rtc_internal.h"
/* see header for doc */
int viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id)
{
*hw_counter = VIRTIO_RTC_COUNTER_ARM_VCT;
*cs_id = CSID_ARM_ARCH_COUNTER;
return 0;
}
@@ -0,0 +1,262 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* virtio_rtc RTC class driver
*
* Copyright (C) 2023 OpenSynergy GmbH
* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/math64.h>
#include <linux/overflow.h>
#include <linux/rtc.h>
#include <linux/time64.h>
#include <uapi/linux/virtio_rtc.h>
#include "virtio_rtc_internal.h"
/**
* struct viortc_class - RTC class wrapper
* @viortc: virtio_rtc device data
* @rtc: RTC device
* @vio_clk_id: virtio_rtc clock id
* @stopped: Whether RTC ops are disallowed. Access protected by rtc_lock().
*/
struct viortc_class {
struct viortc_dev *viortc;
struct rtc_device *rtc;
u16 vio_clk_id;
bool stopped;
};
/**
* viortc_class_get_locked() - get RTC class wrapper, if ops allowed
* @dev: virtio device
*
* Gets the RTC class wrapper from the virtio device, if it is available and
* ops are allowed.
*
* Context: Caller must hold rtc_lock().
* Return: RTC class wrapper if available and ops allowed, ERR_PTR otherwise.
*/
static struct viortc_class *viortc_class_get_locked(struct device *dev)
{
struct viortc_class *viortc_class;
viortc_class = viortc_class_from_dev(dev);
if (IS_ERR(viortc_class))
return viortc_class;
if (viortc_class->stopped)
return ERR_PTR(-EBUSY);
return viortc_class;
}
/**
* viortc_class_read_time() - RTC class op read_time
* @dev: virtio device
* @tm: read time
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
static int viortc_class_read_time(struct device *dev, struct rtc_time *tm)
{
struct viortc_class *viortc_class;
time64_t sec;
int ret;
u64 ns;
viortc_class = viortc_class_get_locked(dev);
if (IS_ERR(viortc_class))
return PTR_ERR(viortc_class);
ret = viortc_read(viortc_class->viortc, viortc_class->vio_clk_id, &ns);
if (ret)
return ret;
sec = div_u64(ns, NSEC_PER_SEC);
rtc_time64_to_tm(sec, tm);
return 0;
}
/**
* viortc_class_read_alarm() - RTC class op read_alarm
* @dev: virtio device
* @alrm: alarm read out
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
static int viortc_class_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct viortc_class *viortc_class;
time64_t alarm_time_sec;
u64 alarm_time_ns;
bool enabled;
int ret;
viortc_class = viortc_class_get_locked(dev);
if (IS_ERR(viortc_class))
return PTR_ERR(viortc_class);
ret = viortc_read_alarm(viortc_class->viortc, viortc_class->vio_clk_id,
&alarm_time_ns, &enabled);
if (ret)
return ret;
alarm_time_sec = div_u64(alarm_time_ns, NSEC_PER_SEC);
rtc_time64_to_tm(alarm_time_sec, &alrm->time);
alrm->enabled = enabled;
return 0;
}
/**
* viortc_class_set_alarm() - RTC class op set_alarm
* @dev: virtio device
* @alrm: alarm to set
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
static int viortc_class_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct viortc_class *viortc_class;
time64_t alarm_time_sec;
u64 alarm_time_ns;
viortc_class = viortc_class_get_locked(dev);
if (IS_ERR(viortc_class))
return PTR_ERR(viortc_class);
alarm_time_sec = rtc_tm_to_time64(&alrm->time);
if (alarm_time_sec < 0)
return -EINVAL;
if (check_mul_overflow((u64)alarm_time_sec, (u64)NSEC_PER_SEC,
&alarm_time_ns))
return -EINVAL;
return viortc_set_alarm(viortc_class->viortc, viortc_class->vio_clk_id,
alarm_time_ns, alrm->enabled);
}
/**
* viortc_class_alarm_irq_enable() - RTC class op alarm_irq_enable
* @dev: virtio device
* @enabled: enable or disable alarm IRQ
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
static int viortc_class_alarm_irq_enable(struct device *dev,
unsigned int enabled)
{
struct viortc_class *viortc_class;
viortc_class = viortc_class_get_locked(dev);
if (IS_ERR(viortc_class))
return PTR_ERR(viortc_class);
return viortc_set_alarm_enabled(viortc_class->viortc,
viortc_class->vio_clk_id, enabled);
}
static const struct rtc_class_ops viortc_class_ops = {
.read_time = viortc_class_read_time,
.read_alarm = viortc_class_read_alarm,
.set_alarm = viortc_class_set_alarm,
.alarm_irq_enable = viortc_class_alarm_irq_enable,
};
/**
* viortc_class_alarm() - propagate alarm notification as alarm interrupt
* @viortc_class: RTC class wrapper
* @vio_clk_id: virtio_rtc clock id
*
* Context: Any context.
*/
void viortc_class_alarm(struct viortc_class *viortc_class, u16 vio_clk_id)
{
if (vio_clk_id != viortc_class->vio_clk_id) {
dev_warn_ratelimited(&viortc_class->rtc->dev,
"ignoring alarm for clock id %d, expected id %d\n",
vio_clk_id, viortc_class->vio_clk_id);
return;
}
rtc_update_irq(viortc_class->rtc, 1, RTC_AF | RTC_IRQF);
}
/**
* viortc_class_stop() - disallow RTC class ops
* @viortc_class: RTC class wrapper
*
* Context: Process context. Caller must NOT hold rtc_lock().
*/
void viortc_class_stop(struct viortc_class *viortc_class)
{
rtc_lock(viortc_class->rtc);
viortc_class->stopped = true;
rtc_unlock(viortc_class->rtc);
}
/**
* viortc_class_register() - register RTC class device
* @viortc_class: RTC class wrapper
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
int viortc_class_register(struct viortc_class *viortc_class)
{
return devm_rtc_register_device(viortc_class->rtc);
}
/**
* viortc_class_init() - init RTC class wrapper and device
* @viortc: device data
* @vio_clk_id: virtio_rtc clock id
* @have_alarm: have alarm feature
* @parent_dev: virtio device
*
* Context: Process context.
* Return: RTC class wrapper on success, ERR_PTR otherwise.
*/
struct viortc_class *viortc_class_init(struct viortc_dev *viortc,
u16 vio_clk_id, bool have_alarm,
struct device *parent_dev)
{
struct viortc_class *viortc_class;
struct rtc_device *rtc;
viortc_class =
devm_kzalloc(parent_dev, sizeof(*viortc_class), GFP_KERNEL);
if (!viortc_class)
return ERR_PTR(-ENOMEM);
rtc = devm_rtc_allocate_device(parent_dev);
if (IS_ERR(rtc))
return ERR_CAST(rtc);
viortc_class->viortc = viortc;
viortc_class->rtc = rtc;
viortc_class->vio_clk_id = vio_clk_id;
if (!have_alarm)
clear_bit(RTC_FEATURE_ALARM, rtc->features);
clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features);
rtc->ops = &viortc_class_ops;
rtc->range_max = div_u64(U64_MAX, NSEC_PER_SEC);
return viortc_class;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* virtio_rtc internal interfaces
*
* Copyright (C) 2022-2023 OpenSynergy GmbH
* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _VIRTIO_RTC_INTERNAL_H_
#define _VIRTIO_RTC_INTERNAL_H_
#include <linux/device.h>
#include <linux/err.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/types.h>
/* driver core IFs */
struct viortc_dev;
int viortc_read(struct viortc_dev *viortc, u16 vio_clk_id, u64 *reading);
int viortc_read_cross(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter,
u64 *reading, u64 *cycles);
int viortc_cross_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 hw_counter,
bool *supported);
int viortc_read_alarm(struct viortc_dev *viortc, u16 vio_clk_id,
u64 *alarm_time, bool *enabled);
int viortc_set_alarm(struct viortc_dev *viortc, u16 vio_clk_id, u64 alarm_time,
bool alarm_enable);
int viortc_set_alarm_enabled(struct viortc_dev *viortc, u16 vio_clk_id,
bool alarm_enable);
struct viortc_class;
struct viortc_class *viortc_class_from_dev(struct device *dev);
/* PTP IFs */
struct viortc_ptp_clock;
#if IS_ENABLED(CONFIG_VIRTIO_RTC_PTP)
struct viortc_ptp_clock *viortc_ptp_register(struct viortc_dev *viortc,
struct device *parent_dev,
u16 vio_clk_id,
const char *ptp_clock_name);
int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp,
struct device *parent_dev);
#else
static inline struct viortc_ptp_clock *
viortc_ptp_register(struct viortc_dev *viortc, struct device *parent_dev,
u16 vio_clk_id, const char *ptp_clock_name)
{
return NULL;
}
static inline int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp,
struct device *parent_dev)
{
return -ENODEV;
}
#endif
/* HW counter IFs */
/**
* viortc_hw_xtstamp_params() - get HW-specific xtstamp params
* @hw_counter: virtio_rtc HW counter type
* @cs_id: clocksource id corresponding to hw_counter
*
* Gets the HW-specific xtstamp params. Returns an error if the driver cannot
* support xtstamp.
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
int viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id);
/* RTC class IFs */
#if IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS)
void viortc_class_alarm(struct viortc_class *viortc_class, u16 vio_clk_id);
void viortc_class_stop(struct viortc_class *viortc_class);
int viortc_class_register(struct viortc_class *viortc_class);
struct viortc_class *viortc_class_init(struct viortc_dev *viortc,
u16 vio_clk_id, bool have_alarm,
struct device *parent_dev);
#else /* CONFIG_VIRTIO_RTC_CLASS */
static inline void viortc_class_alarm(struct viortc_class *viortc_class,
u16 vio_clk_id)
{
}
static inline void viortc_class_stop(struct viortc_class *viortc_class)
{
}
static inline int viortc_class_register(struct viortc_class *viortc_class)
{
return -ENODEV;
}
static inline struct viortc_class *viortc_class_init(struct viortc_dev *viortc,
u16 vio_clk_id,
bool have_alarm,
struct device *parent_dev)
{
return ERR_PTR(-ENODEV);
}
#endif /* CONFIG_VIRTIO_RTC_CLASS */
#endif /* _VIRTIO_RTC_INTERNAL_H_ */
+347
View File
@@ -0,0 +1,347 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Expose virtio_rtc clocks as PTP clocks.
*
* Copyright (C) 2022-2023 OpenSynergy GmbH
* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*
* Derived from ptp_kvm_common.c, virtual PTP 1588 clock for use with KVM
* guests.
*
* Copyright (C) 2017 Red Hat Inc.
*/
#include <linux/device.h>
#include <linux/err.h>
#include <linux/ptp_clock_kernel.h>
#include <uapi/linux/virtio_rtc.h>
#include "virtio_rtc_internal.h"
/**
* struct viortc_ptp_clock - PTP clock abstraction
* @ptp_clock: PTP clock handle for unregistering
* @viortc: virtio_rtc device data
* @ptp_info: PTP clock description
* @vio_clk_id: virtio_rtc clock id
* @have_cross: device supports crosststamp with available HW counter
*/
struct viortc_ptp_clock {
struct ptp_clock *ptp_clock;
struct viortc_dev *viortc;
struct ptp_clock_info ptp_info;
u16 vio_clk_id;
bool have_cross;
};
/**
* struct viortc_ptp_cross_ctx - context for get_device_system_crosststamp()
* @device_time: device clock reading
* @system_counterval: HW counter value at device_time
*
* Provides the already obtained crosststamp to get_device_system_crosststamp().
*/
struct viortc_ptp_cross_ctx {
ktime_t device_time;
struct system_counterval_t system_counterval;
};
/* Weak function in case get_device_system_crosststamp() is not supported */
int __weak viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id)
{
return -EOPNOTSUPP;
}
/**
* viortc_ptp_get_time_fn() - callback for get_device_system_crosststamp()
* @device_time: device clock reading
* @system_counterval: HW counter value at device_time
* @ctx: context with already obtained crosststamp
*
* Return: zero (success).
*/
static int viortc_ptp_get_time_fn(ktime_t *device_time,
struct system_counterval_t *system_counterval,
void *ctx)
{
struct viortc_ptp_cross_ctx *vio_ctx = ctx;
*device_time = vio_ctx->device_time;
*system_counterval = vio_ctx->system_counterval;
return 0;
}
/**
* viortc_ptp_do_xtstamp() - get crosststamp from device
* @vio_ptp: virtio_rtc PTP clock
* @hw_counter: virtio_rtc HW counter type
* @cs_id: clocksource id corresponding to hw_counter
* @ctx: context for get_device_system_crosststamp()
*
* Reads HW-specific crosststamp from device.
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
static int viortc_ptp_do_xtstamp(struct viortc_ptp_clock *vio_ptp,
u8 hw_counter, enum clocksource_ids cs_id,
struct viortc_ptp_cross_ctx *ctx)
{
u64 max_ns, ns;
int ret;
ctx->system_counterval.cs_id = cs_id;
ret = viortc_read_cross(vio_ptp->viortc, vio_ptp->vio_clk_id,
hw_counter, &ns,
&ctx->system_counterval.cycles);
if (ret)
return ret;
max_ns = (u64)ktime_to_ns(KTIME_MAX);
if (ns > max_ns)
return -EINVAL;
ctx->device_time = ns_to_ktime(ns);
return 0;
}
/*
* PTP clock operations
*/
/**
* viortc_ptp_getcrosststamp() - PTP clock getcrosststamp op
* @ptp: PTP clock info
* @xtstamp: crosststamp
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
static int viortc_ptp_getcrosststamp(struct ptp_clock_info *ptp,
struct system_device_crosststamp *xtstamp)
{
struct viortc_ptp_clock *vio_ptp =
container_of(ptp, struct viortc_ptp_clock, ptp_info);
struct system_time_snapshot history_begin;
struct viortc_ptp_cross_ctx ctx;
enum clocksource_ids cs_id;
u8 hw_counter;
int ret;
if (!vio_ptp->have_cross)
return -EOPNOTSUPP;
ret = viortc_hw_xtstamp_params(&hw_counter, &cs_id);
if (ret)
return ret;
ktime_get_snapshot_id(xtstamp->clock_id, &history_begin);
if (history_begin.cs_id != cs_id)
return -EOPNOTSUPP;
/*
* Getting the timestamp can take many milliseconds with a slow Virtio
* device. This is too long for viortc_ptp_get_time_fn() passed to
* get_device_system_crosststamp(), which has to usually return before
* the timekeeper seqcount increases (every tick or so).
*
* So, get the actual cross-timestamp first.
*/
ret = viortc_ptp_do_xtstamp(vio_ptp, hw_counter, cs_id, &ctx);
if (ret)
return ret;
ret = get_device_system_crosststamp(viortc_ptp_get_time_fn, &ctx,
&history_begin, xtstamp);
if (ret)
pr_debug("%s: get_device_system_crosststamp() returned %d\n",
__func__, ret);
return ret;
}
/* viortc_ptp_adjfine() - unsupported PTP clock adjfine op */
static int viortc_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
return -EOPNOTSUPP;
}
/* viortc_ptp_adjtime() - unsupported PTP clock adjtime op */
static int viortc_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
return -EOPNOTSUPP;
}
/* viortc_ptp_settime64() - unsupported PTP clock settime64 op */
static int viortc_ptp_settime64(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
return -EOPNOTSUPP;
}
/*
* viortc_ptp_gettimex64() - PTP clock gettimex64 op
*
* Context: Process context.
*/
static int viortc_ptp_gettimex64(struct ptp_clock_info *ptp,
struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct viortc_ptp_clock *vio_ptp =
container_of(ptp, struct viortc_ptp_clock, ptp_info);
int ret;
u64 ns;
ptp_read_system_prets(sts);
ret = viortc_read(vio_ptp->viortc, vio_ptp->vio_clk_id, &ns);
ptp_read_system_postts(sts);
if (ret)
return ret;
if (ns > (u64)S64_MAX)
return -EINVAL;
*ts = ns_to_timespec64((s64)ns);
return 0;
}
/* viortc_ptp_enable() - unsupported PTP clock enable op */
static int viortc_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
return -EOPNOTSUPP;
}
/*
* viortc_ptp_info_template - ptp_clock_info template
*
* The .name member will be set for individual virtio_rtc PTP clocks.
*
* The .getcrosststamp member will be cleared for PTP clocks not supporting
* crosststamp.
*/
static const struct ptp_clock_info viortc_ptp_info_template = {
.owner = THIS_MODULE,
/* .name is set according to clock type */
.adjfine = viortc_ptp_adjfine,
.adjtime = viortc_ptp_adjtime,
.gettimex64 = viortc_ptp_gettimex64,
.settime64 = viortc_ptp_settime64,
.enable = viortc_ptp_enable,
.getcrosststamp = viortc_ptp_getcrosststamp,
};
/**
* viortc_ptp_unregister() - PTP clock unregistering wrapper
* @vio_ptp: virtio_rtc PTP clock
* @parent_dev: parent device of PTP clock
*
* Return: Zero on success, negative error code otherwise.
*/
int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp,
struct device *parent_dev)
{
int ret = ptp_clock_unregister(vio_ptp->ptp_clock);
if (!ret)
devm_kfree(parent_dev, vio_ptp);
return ret;
}
/**
* viortc_ptp_get_cross_cap() - get xtstamp support info from device
* @viortc: virtio_rtc device data
* @vio_ptp: virtio_rtc PTP clock abstraction
*
* Context: Process context.
* Return: Zero on success, negative error code otherwise.
*/
static int viortc_ptp_get_cross_cap(struct viortc_dev *viortc,
struct viortc_ptp_clock *vio_ptp)
{
enum clocksource_ids cs_id;
bool xtstamp_supported;
u8 hw_counter;
int ret;
ret = viortc_hw_xtstamp_params(&hw_counter, &cs_id);
if (ret) {
vio_ptp->have_cross = false;
return 0;
}
ret = viortc_cross_cap(viortc, vio_ptp->vio_clk_id, hw_counter,
&xtstamp_supported);
if (ret)
return ret;
vio_ptp->have_cross = xtstamp_supported;
return 0;
}
/**
* viortc_ptp_register() - prepare and register PTP clock
* @viortc: virtio_rtc device data
* @parent_dev: parent device for PTP clock
* @vio_clk_id: id of virtio_rtc clock which backs PTP clock
* @ptp_clock_name: PTP clock name
*
* Context: Process context.
* Return: Pointer on success, ERR_PTR() otherwise; NULL if PTP clock support
* not available.
*/
struct viortc_ptp_clock *viortc_ptp_register(struct viortc_dev *viortc,
struct device *parent_dev,
u16 vio_clk_id,
const char *ptp_clock_name)
{
struct viortc_ptp_clock *vio_ptp;
struct ptp_clock *ptp_clock;
ssize_t len;
int ret;
vio_ptp = devm_kzalloc(parent_dev, sizeof(*vio_ptp), GFP_KERNEL);
if (!vio_ptp)
return ERR_PTR(-ENOMEM);
vio_ptp->viortc = viortc;
vio_ptp->vio_clk_id = vio_clk_id;
vio_ptp->ptp_info = viortc_ptp_info_template;
len = strscpy(vio_ptp->ptp_info.name, ptp_clock_name,
sizeof(vio_ptp->ptp_info.name));
if (len < 0) {
ret = len;
goto err_free_dev;
}
ret = viortc_ptp_get_cross_cap(viortc, vio_ptp);
if (ret)
goto err_free_dev;
if (!vio_ptp->have_cross)
vio_ptp->ptp_info.getcrosststamp = NULL;
ptp_clock = ptp_clock_register(&vio_ptp->ptp_info, parent_dev);
if (IS_ERR(ptp_clock))
goto err_on_register;
vio_ptp->ptp_clock = ptp_clock;
return vio_ptp;
err_on_register:
ret = PTR_ERR(ptp_clock);
err_free_dev:
devm_kfree(parent_dev, vio_ptp);
return ERR_PTR(ret);
}
+518
View File
@@ -0,0 +1,518 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VIRTIO based driver for vDPA device
*
* Copyright (c) 2020, Red Hat. All rights reserved.
* Author: Jason Wang <jasowang@redhat.com>
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/uuid.h>
#include <linux/group_cpus.h>
#include <linux/virtio.h>
#include <linux/vdpa.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ring.h>
#define MOD_VERSION "0.1"
#define MOD_AUTHOR "Jason Wang <jasowang@redhat.com>"
#define MOD_DESC "vDPA bus driver for virtio devices"
#define MOD_LICENSE "GPL v2"
struct virtio_vdpa_device {
struct virtio_device vdev;
struct vdpa_device *vdpa;
u64 features;
};
static inline struct virtio_vdpa_device *
to_virtio_vdpa_device(struct virtio_device *dev)
{
return container_of(dev, struct virtio_vdpa_device, vdev);
}
static struct vdpa_device *vd_get_vdpa(struct virtio_device *vdev)
{
return to_virtio_vdpa_device(vdev)->vdpa;
}
static void virtio_vdpa_get(struct virtio_device *vdev, unsigned int offset,
void *buf, unsigned int len)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
vdpa_get_config(vdpa, offset, buf, len);
}
static void virtio_vdpa_set(struct virtio_device *vdev, unsigned int offset,
const void *buf, unsigned int len)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
vdpa_set_config(vdpa, offset, buf, len);
}
static u32 virtio_vdpa_generation(struct virtio_device *vdev)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
const struct vdpa_config_ops *ops = vdpa->config;
if (ops->get_generation)
return ops->get_generation(vdpa);
return 0;
}
static u8 virtio_vdpa_get_status(struct virtio_device *vdev)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
const struct vdpa_config_ops *ops = vdpa->config;
return ops->get_status(vdpa);
}
static void virtio_vdpa_set_status(struct virtio_device *vdev, u8 status)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
vdpa_set_status(vdpa, status);
}
static void virtio_vdpa_reset(struct virtio_device *vdev)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
vdpa_reset(vdpa, 0);
}
static bool virtio_vdpa_notify(struct virtqueue *vq)
{
struct vdpa_device *vdpa = vd_get_vdpa(vq->vdev);
const struct vdpa_config_ops *ops = vdpa->config;
ops->kick_vq(vdpa, vq->index);
return true;
}
static bool virtio_vdpa_notify_with_data(struct virtqueue *vq)
{
struct vdpa_device *vdpa = vd_get_vdpa(vq->vdev);
const struct vdpa_config_ops *ops = vdpa->config;
u32 data = vring_notification_data(vq);
ops->kick_vq_with_data(vdpa, data);
return true;
}
static irqreturn_t virtio_vdpa_config_cb(void *private)
{
struct virtio_vdpa_device *vd_dev = private;
virtio_config_changed(&vd_dev->vdev);
return IRQ_HANDLED;
}
static irqreturn_t virtio_vdpa_virtqueue_cb(void *private)
{
struct virtqueue *vq = private;
return vring_interrupt(0, vq);
}
static struct virtqueue *
virtio_vdpa_setup_vq(struct virtio_device *vdev, unsigned int index,
void (*callback)(struct virtqueue *vq),
const char *name, bool ctx)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
const struct vdpa_config_ops *ops = vdpa->config;
bool (*notify)(struct virtqueue *vq) = virtio_vdpa_notify;
struct vdpa_callback cb;
struct virtqueue *vq;
u64 desc_addr, driver_addr, device_addr;
union virtio_map map = {0};
/* Assume split virtqueue, switch to packed if necessary */
struct vdpa_vq_state state = {0};
u32 align, max_num, min_num = 1;
bool may_reduce_num = true;
int err;
if (!name)
return NULL;
if (index >= vdpa->nvqs)
return ERR_PTR(-ENOENT);
/* We cannot accept VIRTIO_F_NOTIFICATION_DATA without kick_vq_with_data */
if (__virtio_test_bit(vdev, VIRTIO_F_NOTIFICATION_DATA)) {
if (ops->kick_vq_with_data)
notify = virtio_vdpa_notify_with_data;
else
__virtio_clear_bit(vdev, VIRTIO_F_NOTIFICATION_DATA);
}
/* Queue shouldn't already be set up. */
if (ops->get_vq_ready(vdpa, index))
return ERR_PTR(-ENOENT);
if (ops->get_vq_size)
max_num = ops->get_vq_size(vdpa, index);
else
max_num = ops->get_vq_num_max(vdpa);
if (max_num == 0) {
err = -ENOENT;
goto error_new_virtqueue;
}
if (ops->get_vq_num_min)
min_num = ops->get_vq_num_min(vdpa);
may_reduce_num = (max_num != min_num);
/* Create the vring */
align = ops->get_vq_align(vdpa);
if (ops->get_vq_map)
map = ops->get_vq_map(vdpa, index);
else
map = vdpa_get_map(vdpa);
vq = vring_create_virtqueue_map(index, max_num, align, vdev,
true, may_reduce_num, ctx,
notify, callback, name, map);
if (!vq) {
err = -ENOMEM;
goto error_new_virtqueue;
}
if (index == 0)
vdev->vmap = map;
vq->num_max = max_num;
/* Setup virtqueue callback */
cb.callback = callback ? virtio_vdpa_virtqueue_cb : NULL;
cb.private = vq;
cb.trigger = NULL;
ops->set_vq_cb(vdpa, index, &cb);
ops->set_vq_num(vdpa, index, virtqueue_get_vring_size(vq));
desc_addr = virtqueue_get_desc_addr(vq);
driver_addr = virtqueue_get_avail_addr(vq);
device_addr = virtqueue_get_used_addr(vq);
if (ops->set_vq_address(vdpa, index,
desc_addr, driver_addr,
device_addr)) {
err = -EINVAL;
goto err_vq;
}
/* reset virtqueue state index */
if (virtio_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
struct vdpa_vq_state_packed *s = &state.packed;
s->last_avail_counter = 1;
s->last_avail_idx = 0;
s->last_used_counter = 1;
s->last_used_idx = 0;
}
err = ops->set_vq_state(vdpa, index, &state);
if (err)
goto err_vq;
ops->set_vq_ready(vdpa, index, 1);
return vq;
err_vq:
vring_del_virtqueue(vq);
error_new_virtqueue:
ops->set_vq_ready(vdpa, index, 0);
/* VDPA driver should make sure vq is stopeed here */
WARN_ON(ops->get_vq_ready(vdpa, index));
return ERR_PTR(err);
}
static void virtio_vdpa_del_vq(struct virtqueue *vq)
{
struct virtio_vdpa_device *vd_dev = to_virtio_vdpa_device(vq->vdev);
struct vdpa_device *vdpa = vd_dev->vdpa;
const struct vdpa_config_ops *ops = vdpa->config;
unsigned int index = vq->index;
/* Select and deactivate the queue (best effort) */
ops->set_vq_ready(vdpa, index, 0);
vring_del_virtqueue(vq);
}
static void virtio_vdpa_del_vqs(struct virtio_device *vdev)
{
struct virtqueue *vq, *n;
list_for_each_entry_safe(vq, n, &vdev->vqs, list)
virtio_vdpa_del_vq(vq);
}
static void default_calc_sets(struct irq_affinity *affd, unsigned int affvecs)
{
affd->nr_sets = 1;
affd->set_size[0] = affvecs;
}
static struct cpumask *
create_affinity_masks(unsigned int nvecs, struct irq_affinity *affd)
{
unsigned int affvecs = 0, curvec, usedvecs, i;
struct cpumask *masks = NULL;
if (nvecs > affd->pre_vectors + affd->post_vectors)
affvecs = nvecs - affd->pre_vectors - affd->post_vectors;
if (!affd->calc_sets)
affd->calc_sets = default_calc_sets;
affd->calc_sets(affd, affvecs);
if (!affvecs)
return NULL;
masks = kzalloc_objs(*masks, nvecs);
if (!masks)
return NULL;
/* Fill out vectors at the beginning that don't need affinity */
for (curvec = 0; curvec < affd->pre_vectors; curvec++)
cpumask_setall(&masks[curvec]);
for (i = 0, usedvecs = 0; i < affd->nr_sets; i++) {
unsigned int this_vecs = affd->set_size[i];
unsigned int nr_masks;
int j;
struct cpumask *result = group_cpus_evenly(this_vecs, &nr_masks);
if (!result) {
kfree(masks);
return NULL;
}
for (j = 0; j < nr_masks; j++)
cpumask_copy(&masks[curvec + j], &result[j]);
kfree(result);
curvec += nr_masks;
usedvecs += nr_masks;
}
/* Fill out vectors at the end that don't need affinity */
if (usedvecs >= affvecs)
curvec = affd->pre_vectors + affvecs;
else
curvec = affd->pre_vectors + usedvecs;
for (; curvec < nvecs; curvec++)
cpumask_setall(&masks[curvec]);
return masks;
}
static int virtio_vdpa_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
struct virtqueue *vqs[],
struct virtqueue_info vqs_info[],
struct irq_affinity *desc)
{
struct virtio_vdpa_device *vd_dev = to_virtio_vdpa_device(vdev);
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
const struct vdpa_config_ops *ops = vdpa->config;
struct cpumask *masks;
struct vdpa_callback cb;
bool has_affinity = desc && ops->set_vq_affinity;
int i, err, queue_idx = 0;
if (has_affinity) {
masks = create_affinity_masks(nvqs, desc);
if (!masks)
return -ENOMEM;
}
for (i = 0; i < nvqs; ++i) {
struct virtqueue_info *vqi = &vqs_info[i];
if (!vqi->name) {
vqs[i] = NULL;
continue;
}
vqs[i] = virtio_vdpa_setup_vq(vdev, queue_idx++, vqi->callback,
vqi->name, vqi->ctx);
if (IS_ERR(vqs[i])) {
err = PTR_ERR(vqs[i]);
goto err_setup_vq;
}
if (has_affinity)
ops->set_vq_affinity(vdpa, i, &masks[i]);
}
cb.callback = virtio_vdpa_config_cb;
cb.private = vd_dev;
ops->set_config_cb(vdpa, &cb);
if (has_affinity)
kfree(masks);
return 0;
err_setup_vq:
virtio_vdpa_del_vqs(vdev);
if (has_affinity)
kfree(masks);
return err;
}
static u64 virtio_vdpa_get_features(struct virtio_device *vdev)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
const struct vdpa_config_ops *ops = vdpa->config;
return ops->get_device_features(vdpa);
}
static int virtio_vdpa_finalize_features(struct virtio_device *vdev)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
/* Give virtio_ring a chance to accept features. */
vring_transport_features(vdev);
return vdpa_set_features(vdpa, vdev->features);
}
static const char *virtio_vdpa_bus_name(struct virtio_device *vdev)
{
struct virtio_vdpa_device *vd_dev = to_virtio_vdpa_device(vdev);
struct vdpa_device *vdpa = vd_dev->vdpa;
return dev_name(&vdpa->dev);
}
static int virtio_vdpa_set_vq_affinity(struct virtqueue *vq,
const struct cpumask *cpu_mask)
{
struct virtio_vdpa_device *vd_dev = to_virtio_vdpa_device(vq->vdev);
struct vdpa_device *vdpa = vd_dev->vdpa;
const struct vdpa_config_ops *ops = vdpa->config;
unsigned int index = vq->index;
if (ops->set_vq_affinity)
return ops->set_vq_affinity(vdpa, index, cpu_mask);
return 0;
}
static const struct cpumask *
virtio_vdpa_get_vq_affinity(struct virtio_device *vdev, int index)
{
struct vdpa_device *vdpa = vd_get_vdpa(vdev);
const struct vdpa_config_ops *ops = vdpa->config;
if (ops->get_vq_affinity)
return ops->get_vq_affinity(vdpa, index);
return NULL;
}
static const struct virtio_config_ops virtio_vdpa_config_ops = {
.get = virtio_vdpa_get,
.set = virtio_vdpa_set,
.generation = virtio_vdpa_generation,
.get_status = virtio_vdpa_get_status,
.set_status = virtio_vdpa_set_status,
.reset = virtio_vdpa_reset,
.find_vqs = virtio_vdpa_find_vqs,
.del_vqs = virtio_vdpa_del_vqs,
.get_features = virtio_vdpa_get_features,
.finalize_features = virtio_vdpa_finalize_features,
.bus_name = virtio_vdpa_bus_name,
.set_vq_affinity = virtio_vdpa_set_vq_affinity,
.get_vq_affinity = virtio_vdpa_get_vq_affinity,
};
static void virtio_vdpa_release_dev(struct device *_d)
{
struct virtio_device *vdev =
container_of(_d, struct virtio_device, dev);
struct virtio_vdpa_device *vd_dev =
container_of(vdev, struct virtio_vdpa_device, vdev);
kfree(vd_dev);
}
static int virtio_vdpa_probe(struct vdpa_device *vdpa)
{
const struct vdpa_config_ops *ops = vdpa->config;
struct virtio_vdpa_device *vd_dev, *reg_dev = NULL;
int ret = -EINVAL;
vd_dev = kzalloc_obj(*vd_dev);
if (!vd_dev)
return -ENOMEM;
vd_dev->vdev.dev.parent = vdpa->map ? &vdpa->dev :
vdpa_get_map(vdpa).dma_dev;
vd_dev->vdev.dev.release = virtio_vdpa_release_dev;
vd_dev->vdev.config = &virtio_vdpa_config_ops;
vd_dev->vdev.map = vdpa->map;
vd_dev->vdpa = vdpa;
vd_dev->vdev.id.device = ops->get_device_id(vdpa);
if (vd_dev->vdev.id.device == 0)
goto err;
vd_dev->vdev.id.vendor = ops->get_vendor_id(vdpa);
ret = register_virtio_device(&vd_dev->vdev);
reg_dev = vd_dev;
if (ret)
goto err;
vdpa_set_drvdata(vdpa, vd_dev);
return 0;
err:
if (reg_dev)
put_device(&vd_dev->vdev.dev);
else
kfree(vd_dev);
return ret;
}
static void virtio_vdpa_remove(struct vdpa_device *vdpa)
{
struct virtio_vdpa_device *vd_dev = vdpa_get_drvdata(vdpa);
unregister_virtio_device(&vd_dev->vdev);
}
static struct vdpa_driver virtio_vdpa_driver = {
.driver = {
.name = "virtio_vdpa",
},
.probe = virtio_vdpa_probe,
.remove = virtio_vdpa_remove,
};
module_vdpa_driver(virtio_vdpa_driver);
MODULE_VERSION(MOD_VERSION);
MODULE_LICENSE(MOD_LICENSE);
MODULE_AUTHOR(MOD_AUTHOR);
MODULE_DESCRIPTION(MOD_DESC);