Commit 264f53b4 authored by Michael S. Tsirkin's avatar Michael S. Tsirkin Committed by Greg Kroah-Hartman

Revert "mei: virtio: virtualization frontend driver"

This reverts commit d162219c.

The device uses a VIRTIO device ID out of a not-for-production range.
Releasing Linux using an ID out of this range will make it conflict with
development setups. An official request to reserve an ID for an MEI
device is yet to be submitted to the virtio TC, thus there's no chance
it will be reserved and fixed in time before the next release.

Once requested it usually takes 2-3 weeks to land in the spec, which
means the device can be supported with the official ID in the next Linux
version if contributors act quickly.
Signed-off-by: default avatarMichael S. Tsirkin <mst@redhat.com>
Cc: Tomas Winkler <tomas.winkler@intel.com>
Cc: Alexander Usyskin <alexander.usyskin@intel.com>
Cc: Wang Yu <yu1.wang@intel.com>
Cc: Liu Shuo <shuo.a.liu@intel.com>
Link: https://lore.kernel.org/r/20201205193625.469773-1-mst@redhat.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 1a16af33
......@@ -46,14 +46,4 @@ config INTEL_MEI_TXE
Supported SoCs:
Intel Bay Trail
config INTEL_MEI_VIRTIO
tristate "Intel MEI interface emulation with virtio framework"
select INTEL_MEI
depends on X86 && PCI && VIRTIO_PCI
help
This module implements mei hw emulation over virtio transport.
The module will be called mei_virtio.
Enable this if your virtual machine supports virtual mei
device over virtio.
source "drivers/misc/mei/hdcp/Kconfig"
......@@ -22,9 +22,6 @@ obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o
mei-txe-objs := pci-txe.o
mei-txe-objs += hw-txe.o
obj-$(CONFIG_INTEL_MEI_VIRTIO) += mei-virtio.o
mei-virtio-objs := hw-virtio.o
mei-$(CONFIG_EVENT_TRACING) += mei-trace.o
CFLAGS_mei-trace.o = -I$(src)
......
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Management Engine Interface (Intel MEI) Linux driver
* Copyright (c) 2018-2020, Intel Corporation.
*/
#include <linux/err.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/scatterlist.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ids.h>
#include <linux/atomic.h>
#include "mei_dev.h"
#include "hbm.h"
#include "client.h"
#define MEI_VIRTIO_RPM_TIMEOUT 500
/* ACRN virtio device types */
#ifndef VIRTIO_ID_MEI
#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */
#endif
/**
* struct mei_virtio_cfg - settings passed from the virtio backend
* @buf_depth: read buffer depth in slots (4bytes)
* @hw_ready: hw is ready for operation
* @host_reset: synchronize reset with virtio backend
* @reserved: reserved for alignment
* @fw_status: FW status
*/
struct mei_virtio_cfg {
u32 buf_depth;
u8 hw_ready;
u8 host_reset;
u8 reserved[2];
u32 fw_status[MEI_FW_STATUS_MAX];
} __packed;
struct mei_virtio_hw {
struct mei_device mdev;
char name[32];
struct virtqueue *in;
struct virtqueue *out;
bool host_ready;
struct work_struct intr_handler;
u32 *recv_buf;
u8 recv_rdy;
size_t recv_sz;
u32 recv_idx;
u32 recv_len;
/* send buffer */
atomic_t hbuf_ready;
const void *send_hdr;
const void *send_buf;
struct mei_virtio_cfg cfg;
};
#define to_virtio_hw(_dev) container_of(_dev, struct mei_virtio_hw, mdev)
/**
* mei_virtio_fw_status() - read status register of mei
* @dev: mei device
* @fw_status: fw status register values
*
* Return: always 0
*/
static int mei_virtio_fw_status(struct mei_device *dev,
struct mei_fw_status *fw_status)
{
struct virtio_device *vdev = dev_to_virtio(dev->dev);
fw_status->count = MEI_FW_STATUS_MAX;
virtio_cread_bytes(vdev, offsetof(struct mei_virtio_cfg, fw_status),
fw_status->status, sizeof(fw_status->status));
return 0;
}
/**
* mei_virtio_pg_state() - translate internal pg state
* to the mei power gating state
* There is no power management in ACRN mode always return OFF
* @dev: mei device
*
* Return:
* * MEI_PG_OFF - if aliveness is on (always)
* * MEI_PG_ON - (never)
*/
static inline enum mei_pg_state mei_virtio_pg_state(struct mei_device *dev)
{
return MEI_PG_OFF;
}
/**
* mei_virtio_hw_config() - configure hw dependent settings
*
* @dev: mei device
*
* Return: always 0
*/
static int mei_virtio_hw_config(struct mei_device *dev)
{
return 0;
}
/**
* mei_virtio_hbuf_empty_slots() - counts write empty slots.
* @dev: the device structure
*
* Return: always return frontend buf size if buffer is ready, 0 otherwise
*/
static int mei_virtio_hbuf_empty_slots(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
return (atomic_read(&hw->hbuf_ready) == 1) ? hw->cfg.buf_depth : 0;
}
/**
* mei_virtio_hbuf_is_ready() - checks if write buffer is ready
* @dev: the device structure
*
* Return: true if hbuf is ready
*/
static bool mei_virtio_hbuf_is_ready(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
return atomic_read(&hw->hbuf_ready) == 1;
}
/**
* mei_virtio_hbuf_max_depth() - returns depth of FE write buffer.
* @dev: the device structure
*
* Return: size of frontend write buffer in bytes
*/
static u32 mei_virtio_hbuf_depth(const struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
return hw->cfg.buf_depth;
}
/**
* mei_virtio_intr_clear() - clear and stop interrupts
* @dev: the device structure
*/
static void mei_virtio_intr_clear(struct mei_device *dev)
{
/*
* In our virtio solution, there are two types of interrupts,
* vq interrupt and config change interrupt.
* 1) start/reset rely on virtio config changed interrupt;
* 2) send/recv rely on virtio virtqueue interrupts.
* They are all virtual interrupts. So, we don't have corresponding
* operation to do here.
*/
}
/**
* mei_virtio_intr_enable() - enables mei BE virtqueues callbacks
* @dev: the device structure
*/
static void mei_virtio_intr_enable(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
struct virtio_device *vdev = dev_to_virtio(dev->dev);
virtio_config_enable(vdev);
virtqueue_enable_cb(hw->in);
virtqueue_enable_cb(hw->out);
}
/**
* mei_virtio_intr_disable() - disables mei BE virtqueues callbacks
*
* @dev: the device structure
*/
static void mei_virtio_intr_disable(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
struct virtio_device *vdev = dev_to_virtio(dev->dev);
virtio_config_disable(vdev);
virtqueue_disable_cb(hw->in);
virtqueue_disable_cb(hw->out);
}
/**
* mei_virtio_synchronize_irq() - wait for pending IRQ handlers for all
* virtqueue
* @dev: the device structure
*/
static void mei_virtio_synchronize_irq(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
/*
* Now, all IRQ handlers are converted to workqueue.
* Change synchronize irq to flush this work.
*/
flush_work(&hw->intr_handler);
}
static void mei_virtio_free_outbufs(struct mei_virtio_hw *hw)
{
kfree(hw->send_hdr);
kfree(hw->send_buf);
hw->send_hdr = NULL;
hw->send_buf = NULL;
}
/**
* mei_virtio_write_message() - writes a message to mei virtio back-end service.
* @dev: the device structure
* @hdr: mei header of message
* @hdr_len: header length
* @data: message payload will be written
* @data_len: message payload length
*
* Return:
* * 0: on success
* * -EIO: if write has failed
* * -ENOMEM: on memory allocation failure
*/
static int mei_virtio_write_message(struct mei_device *dev,
const void *hdr, size_t hdr_len,
const void *data, size_t data_len)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
struct scatterlist sg[2];
const void *hbuf, *dbuf;
int ret;
if (WARN_ON(!atomic_add_unless(&hw->hbuf_ready, -1, 0)))
return -EIO;
hbuf = kmemdup(hdr, hdr_len, GFP_KERNEL);
hw->send_hdr = hbuf;
dbuf = kmemdup(data, data_len, GFP_KERNEL);
hw->send_buf = dbuf;
if (!hbuf || !dbuf) {
ret = -ENOMEM;
goto fail;
}
sg_init_table(sg, 2);
sg_set_buf(&sg[0], hbuf, hdr_len);
sg_set_buf(&sg[1], dbuf, data_len);
ret = virtqueue_add_outbuf(hw->out, sg, 2, hw, GFP_KERNEL);
if (ret) {
dev_err(dev->dev, "failed to add outbuf\n");
goto fail;
}
virtqueue_kick(hw->out);
return 0;
fail:
mei_virtio_free_outbufs(hw);
return ret;
}
/**
* mei_virtio_count_full_read_slots() - counts read full slots.
* @dev: the device structure
*
* Return: -EOVERFLOW if overflow, otherwise filled slots count
*/
static int mei_virtio_count_full_read_slots(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
if (hw->recv_idx > hw->recv_len)
return -EOVERFLOW;
return hw->recv_len - hw->recv_idx;
}
/**
* mei_virtio_read_hdr() - Reads 32bit dword from mei virtio receive buffer
*
* @dev: the device structure
*
* Return: 32bit dword of receive buffer (u32)
*/
static inline u32 mei_virtio_read_hdr(const struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
WARN_ON(hw->cfg.buf_depth < hw->recv_idx + 1);
return hw->recv_buf[hw->recv_idx++];
}
static int mei_virtio_read(struct mei_device *dev, unsigned char *buffer,
unsigned long len)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
u32 slots = mei_data2slots(len);
if (WARN_ON(hw->cfg.buf_depth < hw->recv_idx + slots))
return -EOVERFLOW;
/*
* Assumption: There is only one MEI message in recv_buf each time.
* Backend service need follow this rule too.
*/
memcpy(buffer, hw->recv_buf + hw->recv_idx, len);
hw->recv_idx += slots;
return 0;
}
static bool mei_virtio_pg_is_enabled(struct mei_device *dev)
{
return false;
}
static bool mei_virtio_pg_in_transition(struct mei_device *dev)
{
return false;
}
static void mei_virtio_add_recv_buf(struct mei_virtio_hw *hw)
{
struct scatterlist sg;
if (hw->recv_rdy) /* not needed */
return;
/* refill the recv_buf to IN virtqueue to get next message */
sg_init_one(&sg, hw->recv_buf, mei_slots2data(hw->cfg.buf_depth));
hw->recv_len = 0;
hw->recv_idx = 0;
hw->recv_rdy = 1;
virtqueue_add_inbuf(hw->in, &sg, 1, hw->recv_buf, GFP_KERNEL);
virtqueue_kick(hw->in);
}
/**
* mei_virtio_hw_is_ready() - check whether the BE(hw) has turned ready
* @dev: mei device
* Return: bool
*/
static bool mei_virtio_hw_is_ready(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
struct virtio_device *vdev = dev_to_virtio(dev->dev);
virtio_cread(vdev, struct mei_virtio_cfg,
hw_ready, &hw->cfg.hw_ready);
dev_dbg(dev->dev, "hw ready %d\n", hw->cfg.hw_ready);
return hw->cfg.hw_ready;
}
/**
* mei_virtio_hw_reset - resets virtio hw.
*
* @dev: the device structure
* @intr_enable: virtio use data/config callbacks
*
* Return: 0 on success an error code otherwise
*/
static int mei_virtio_hw_reset(struct mei_device *dev, bool intr_enable)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
struct virtio_device *vdev = dev_to_virtio(dev->dev);
dev_dbg(dev->dev, "hw reset\n");
dev->recvd_hw_ready = false;
hw->host_ready = false;
atomic_set(&hw->hbuf_ready, 0);
hw->recv_len = 0;
hw->recv_idx = 0;
hw->cfg.host_reset = 1;
virtio_cwrite(vdev, struct mei_virtio_cfg,
host_reset, &hw->cfg.host_reset);
mei_virtio_hw_is_ready(dev);
if (intr_enable)
mei_virtio_intr_enable(dev);
return 0;
}
/**
* mei_virtio_hw_reset_release() - release device from the reset
* @dev: the device structure
*/
static void mei_virtio_hw_reset_release(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
struct virtio_device *vdev = dev_to_virtio(dev->dev);
dev_dbg(dev->dev, "hw reset release\n");
hw->cfg.host_reset = 0;
virtio_cwrite(vdev, struct mei_virtio_cfg,
host_reset, &hw->cfg.host_reset);
}
/**
* mei_virtio_hw_ready_wait() - wait until the virtio(hw) has turned ready
* or timeout is reached
* @dev: mei device
*
* Return: 0 on success, error otherwise
*/
static int mei_virtio_hw_ready_wait(struct mei_device *dev)
{
mutex_unlock(&dev->device_lock);
wait_event_timeout(dev->wait_hw_ready,
dev->recvd_hw_ready,
mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT));
mutex_lock(&dev->device_lock);
if (!dev->recvd_hw_ready) {
dev_err(dev->dev, "wait hw ready failed\n");
return -ETIMEDOUT;
}
dev->recvd_hw_ready = false;
return 0;
}
/**
* mei_virtio_hw_start() - hw start routine
* @dev: mei device
*
* Return: 0 on success, error otherwise
*/
static int mei_virtio_hw_start(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
int ret;
dev_dbg(dev->dev, "hw start\n");
mei_virtio_hw_reset_release(dev);
ret = mei_virtio_hw_ready_wait(dev);
if (ret)
return ret;
mei_virtio_add_recv_buf(hw);
atomic_set(&hw->hbuf_ready, 1);
dev_dbg(dev->dev, "hw is ready\n");
hw->host_ready = true;
return 0;
}
/**
* mei_virtio_host_is_ready() - check whether the FE has turned ready
* @dev: mei device
*
* Return: bool
*/
static bool mei_virtio_host_is_ready(struct mei_device *dev)
{
struct mei_virtio_hw *hw = to_virtio_hw(dev);
dev_dbg(dev->dev, "host ready %d\n", hw->host_ready);
return hw->host_ready;
}
/**
* mei_virtio_data_in() - The callback of recv virtqueue of virtio mei
* @vq: receiving virtqueue
*/
static void mei_virtio_data_in(struct virtqueue *vq)
{
struct mei_virtio_hw *hw = vq->vdev->priv;
/* disable interrupts (enabled again from in the interrupt worker) */
virtqueue_disable_cb(hw->in);
schedule_work(&hw->intr_handler);
}
/**
* mei_virtio_data_out() - The callback of send virtqueue of virtio mei
* @vq: transmitting virtqueue
*/
static void mei_virtio_data_out(struct virtqueue *vq)
{
struct mei_virtio_hw *hw = vq->vdev->priv;
schedule_work(&hw->intr_handler);
}
static void mei_virtio_intr_handler(struct work_struct *work)
{
struct mei_virtio_hw *hw =
container_of(work, struct mei_virtio_hw, intr_handler);
struct mei_device *dev = &hw->mdev;
LIST_HEAD(complete_list);
s32 slots;
int rets = 0;
void *data;
unsigned int len;
mutex_lock(&dev->device_lock);
if (dev->dev_state == MEI_DEV_DISABLED) {
dev_warn(dev->dev, "Interrupt in disabled state.\n");
mei_virtio_intr_disable(dev);
goto end;
}
/* check if ME wants a reset */
if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) {
dev_warn(dev->dev, "BE service not ready: resetting.\n");
schedule_work(&dev->reset_work);
goto end;
}
/* check if we need to start the dev */
if (!mei_host_is_ready(dev)) {
if (mei_hw_is_ready(dev)) {
dev_dbg(dev->dev, "we need to start the dev.\n");
dev->recvd_hw_ready = true;
wake_up(&dev->wait_hw_ready);
} else {
dev_warn(dev->dev, "Spurious Interrupt\n");
}
goto end;
}
/* read */
if (hw->recv_rdy) {
data = virtqueue_get_buf(hw->in, &len);
if (!data || !len) {
dev_dbg(dev->dev, "No data %d", len);
} else {
dev_dbg(dev->dev, "data_in %d\n", len);
WARN_ON(data != hw->recv_buf);
hw->recv_len = mei_data2slots(len);
hw->recv_rdy = 0;
}
}
/* write */
if (!atomic_read(&hw->hbuf_ready)) {
if (!virtqueue_get_buf(hw->out, &len)) {
dev_warn(dev->dev, "Failed to getbuf\n");
} else {
mei_virtio_free_outbufs(hw);
atomic_inc(&hw->hbuf_ready);
}
}
/* check slots available for reading */
slots = mei_count_full_read_slots(dev);
while (slots > 0) {
dev_dbg(dev->dev, "slots to read = %08x\n", slots);
rets = mei_irq_read_handler(dev, &complete_list, &slots);
if (rets &&
(dev->dev_state != MEI_DEV_RESETTING &&
dev->dev_state != MEI_DEV_POWER_DOWN)) {
dev_err(dev->dev, "mei_irq_read_handler ret = %d.\n",
rets);
schedule_work(&dev->reset_work);
goto end;
}
}
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
mei_irq_write_handler(dev, &complete_list);
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
mei_irq_compl_handler(dev, &complete_list);
mei_virtio_add_recv_buf(hw);
end:
if (dev->dev_state != MEI_DEV_DISABLED) {
if (!virtqueue_enable_cb(hw->in))
schedule_work(&hw->intr_handler);
}
mutex_unlock(&dev->device_lock);
}
static void mei_virtio_config_changed(struct virtio_device *vdev)
{
struct mei_virtio_hw *hw = vdev->priv;
struct mei_device *dev = &hw->mdev;
virtio_cread(vdev, struct mei_virtio_cfg,
hw_ready, &hw->cfg.hw_ready);
if (dev->dev_state == MEI_DEV_DISABLED) {
dev_dbg(dev->dev, "disabled state don't start\n");
return;
}
/* Run intr handler once to handle reset notify */
schedule_work(&hw->intr_handler);
}
static void mei_virtio_remove_vqs(struct virtio_device *vdev)
{
struct mei_virtio_hw *hw = vdev->priv;
virtqueue_detach_unused_buf(hw->in);
hw->recv_len = 0;
hw->recv_idx = 0;
hw->recv_rdy = 0;
virtqueue_detach_unused_buf(hw->out);
mei_virtio_free_outbufs(hw);
vdev->config->del_vqs(vdev);
}
/*
* There are two virtqueues, one is for send and another is for recv.
*/
static int mei_virtio_init_vqs(struct mei_virtio_hw *hw,
struct virtio_device *vdev)
{
struct virtqueue *vqs[2];
vq_callback_t *cbs[] = {
mei_virtio_data_in,
mei_virtio_data_out,
};
static const char * const names[] = {
"in",
"out",
};
int ret;
ret = virtio_find_vqs(vdev, 2, vqs, cbs, names, NULL);
if (ret)
return ret;
hw->in = vqs[0];
hw->out = vqs[1];
return 0;
}
static const struct mei_hw_ops mei_virtio_ops = {
.fw_status = mei_virtio_fw_status,
.pg_state = mei_virtio_pg_state,
.host_is_ready = mei_virtio_host_is_ready,
.hw_is_ready = mei_virtio_hw_is_ready,
.hw_reset = mei_virtio_hw_reset,
.hw_config = mei_virtio_hw_config,
.hw_start = mei_virtio_hw_start,
.pg_in_transition = mei_virtio_pg_in_transition,
.pg_is_enabled = mei_virtio_pg_is_enabled,
.intr_clear = mei_virtio_intr_clear,
.intr_enable = mei_virtio_intr_enable,
.intr_disable = mei_virtio_intr_disable,
.synchronize_irq = mei_virtio_synchronize_irq,
.hbuf_free_slots = mei_virtio_hbuf_empty_slots,
.hbuf_is_ready = mei_virtio_hbuf_is_ready,
.hbuf_depth = mei_virtio_hbuf_depth,
.write = mei_virtio_write_message,
.rdbuf_full_slots = mei_virtio_count_full_read_slots,
.read_hdr = mei_virtio_read_hdr,
.read = mei_virtio_read,
};
static int mei_virtio_probe(struct virtio_device *vdev)
{
struct mei_virtio_hw *hw;
int ret;
hw = devm_kzalloc(&vdev->dev, sizeof(*hw), GFP_KERNEL);
if (!hw)
return -ENOMEM;
vdev->priv = hw;
INIT_WORK(&hw->intr_handler, mei_virtio_intr_handler);
ret = mei_virtio_init_vqs(hw, vdev);
if (ret)
goto vqs_failed;
virtio_cread(vdev, struct mei_virtio_cfg,
buf_depth, &hw->cfg.buf_depth);
hw->recv_buf = kzalloc(mei_slots2data(hw->cfg.buf_depth), GFP_KERNEL);
if (!hw->recv_buf) {
ret = -ENOMEM;
goto hbuf_failed;
}
atomic_set(&hw->hbuf_ready, 0);
virtio_device_ready(vdev);
mei_device_init(&hw->mdev, &vdev->dev, &mei_virtio_ops);
pm_runtime_get_noresume(&vdev->dev);
pm_runtime_set_active(&vdev->dev);
pm_runtime_enable(&vdev->dev);
ret = mei_start(&hw->mdev);
if (ret)
goto mei_start_failed;
pm_runtime_set_autosuspend_delay(&vdev->dev, MEI_VIRTIO_RPM_TIMEOUT);
pm_runtime_use_autosuspend(&vdev->dev);
ret = mei_register(&hw->mdev, &vdev->dev);
if (ret)
goto mei_failed;
pm_runtime_put(&vdev->dev);
return 0;
mei_failed:
mei_stop(&hw->mdev);
mei_start_failed:
mei_cancel_work(&hw->mdev);
mei_disable_interrupts(&hw->mdev);
kfree(hw->recv_buf);
hbuf_failed:
vdev->config->del_vqs(vdev);
vqs_failed:
return ret;
}
static int __maybe_unused mei_virtio_pm_runtime_idle(struct device *device)
{
struct virtio_device *vdev = dev_to_virtio(device);
struct mei_virtio_hw *hw = vdev->priv;
dev_dbg(&vdev->dev, "rpm: mei_virtio : runtime_idle\n");
if (!hw)
return -ENODEV;
if (mei_write_is_idle(&hw->mdev))
pm_runtime_autosuspend(device);
return -EBUSY;
}
static int __maybe_unused mei_virtio_pm_runtime_suspend(struct device *device)
{
return 0;
}
static int __maybe_unused mei_virtio_pm_runtime_resume(struct device *device)
{
return 0;
}
static int __maybe_unused mei_virtio_freeze(struct virtio_device *vdev)
{
struct mei_virtio_hw *hw = vdev->priv;
dev_dbg(&vdev->dev, "freeze\n");
if (!hw)
return -ENODEV;
mei_stop(&hw->mdev);
mei_disable_interrupts(&hw->mdev);
cancel_work_sync(&hw->intr_handler);
vdev->config->reset(vdev);
mei_virtio_remove_vqs(vdev);
return 0;
}
static int __maybe_unused mei_virtio_restore(struct virtio_device *vdev)
{
struct mei_virtio_hw *hw = vdev->priv;
int ret;
dev_dbg(&vdev->dev, "restore\n");
if (!hw)
return -ENODEV;
ret = mei_virtio_init_vqs(hw, vdev);
if (ret)
return ret;
virtio_device_ready(vdev);
ret = mei_restart(&hw->mdev);
if (ret)
return ret;
/* Start timer if stopped in suspend */
schedule_delayed_work(&hw->mdev.timer_work, HZ);
return 0;
}
static const struct dev_pm_ops mei_virtio_pm_ops = {
SET_RUNTIME_PM_OPS(mei_virtio_pm_runtime_suspend,
mei_virtio_pm_runtime_resume,
mei_virtio_pm_runtime_idle)
};
static void mei_virtio_remove(struct virtio_device *vdev)
{
struct mei_virtio_hw *hw = vdev->priv;
mei_stop(&hw->mdev);
mei_disable_interrupts(&hw->mdev);
cancel_work_sync(&hw->intr_handler);
mei_deregister(&hw->mdev);
vdev->config->reset(vdev);
mei_virtio_remove_vqs(vdev);
kfree(hw->recv_buf);
pm_runtime_disable(&vdev->dev);
}
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_MEI, VIRTIO_DEV_ANY_ID },
{ }
};
static struct virtio_driver mei_virtio_driver = {
.id_table = id_table,
.probe = mei_virtio_probe,
.remove = mei_virtio_remove,
.config_changed = mei_virtio_config_changed,
.driver = {
.name = KBUILD_MODNAME,
.owner = THIS_MODULE,
.pm = &mei_virtio_pm_ops,
},
#ifdef CONFIG_PM_SLEEP
.freeze = mei_virtio_freeze,
.restore = mei_virtio_restore,
#endif
};
module_virtio_driver(mei_virtio_driver);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio MEI frontend driver");
MODULE_LICENSE("GPL v2");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment