Char/Misc driver fixes for 5.10-rc7
Here are some small driver fixes, and one "large" revert, for 5.10-rc7. They include: - revert mei patch from 5.10-rc1 that was using a reserved userspace value. It will be resubmitted once the proper id has been assigned by the virtio people. - habanalabs fixes found by the fall-through audit from Gustavo - speakup driver fixes for reported issues - fpga config build fix for reported issue. All of these except the revert have been in linux-next with no reported issues. The revert is "clean" and just removes a previously-added driver, so no real issue there. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCX8zr3g8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+yk4LwCgz1KKObSoSy8TdbSneDlAZuJlhS0AniwKDQ8g EobiywN+/Q3KP5jGF4Km =8rD3 -----END PGP SIGNATURE----- Merge tag 'char-misc-5.10-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc Pull char/misc driver fixes from Greg KH: "Here are some small driver fixes, and one "large" revert, for 5.10-rc7. They include: - revert mei patch from 5.10-rc1 that was using a reserved userspace value. It will be resubmitted once the proper id has been assigned by the virtio people. - habanalabs fixes found by the fall-through audit from Gustavo - speakup driver fixes for reported issues - fpga config build fix for reported issue. All of these except the revert have been in linux-next with no reported issues. The revert is "clean" and just removes a previously-added driver, so no real issue there" * tag 'char-misc-5.10-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: Revert "mei: virtio: virtualization frontend driver" fpga: Specify HAS_IOMEM dependency for FPGA_DFL habanalabs: put devices before driver removal habanalabs: free host huge va_range if not used speakup: Reject setting the speakup line discipline outside of speakupzero-sugar-mainline-defconfig
commit
ab91292cb3
|
@ -47,27 +47,20 @@ static int spk_ttyio_ldisc_open(struct tty_struct *tty)
|
||||||
{
|
{
|
||||||
struct spk_ldisc_data *ldisc_data;
|
struct spk_ldisc_data *ldisc_data;
|
||||||
|
|
||||||
|
if (tty != speakup_tty)
|
||||||
|
/* Somebody tried to use this line discipline outside speakup */
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
if (!tty->ops->write)
|
if (!tty->ops->write)
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
mutex_lock(&speakup_tty_mutex);
|
|
||||||
if (speakup_tty) {
|
|
||||||
mutex_unlock(&speakup_tty_mutex);
|
|
||||||
return -EBUSY;
|
|
||||||
}
|
|
||||||
speakup_tty = tty;
|
|
||||||
|
|
||||||
ldisc_data = kmalloc(sizeof(*ldisc_data), GFP_KERNEL);
|
ldisc_data = kmalloc(sizeof(*ldisc_data), GFP_KERNEL);
|
||||||
if (!ldisc_data) {
|
if (!ldisc_data)
|
||||||
speakup_tty = NULL;
|
|
||||||
mutex_unlock(&speakup_tty_mutex);
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
|
||||||
|
|
||||||
init_completion(&ldisc_data->completion);
|
init_completion(&ldisc_data->completion);
|
||||||
ldisc_data->buf_free = true;
|
ldisc_data->buf_free = true;
|
||||||
speakup_tty->disc_data = ldisc_data;
|
tty->disc_data = ldisc_data;
|
||||||
mutex_unlock(&speakup_tty_mutex);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -191,9 +184,25 @@ static int spk_ttyio_initialise_ldisc(struct spk_synth *synth)
|
||||||
|
|
||||||
tty_unlock(tty);
|
tty_unlock(tty);
|
||||||
|
|
||||||
|
mutex_lock(&speakup_tty_mutex);
|
||||||
|
speakup_tty = tty;
|
||||||
ret = tty_set_ldisc(tty, N_SPEAKUP);
|
ret = tty_set_ldisc(tty, N_SPEAKUP);
|
||||||
if (ret)
|
if (ret)
|
||||||
pr_err("speakup: Failed to set N_SPEAKUP on tty\n");
|
speakup_tty = NULL;
|
||||||
|
mutex_unlock(&speakup_tty_mutex);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
/* Success */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
pr_err("speakup: Failed to set N_SPEAKUP on tty\n");
|
||||||
|
|
||||||
|
tty_lock(tty);
|
||||||
|
if (tty->ops->close)
|
||||||
|
tty->ops->close(tty, NULL);
|
||||||
|
tty_unlock(tty);
|
||||||
|
|
||||||
|
tty_kclose(tty);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,7 @@ config FPGA_DFL
|
||||||
tristate "FPGA Device Feature List (DFL) support"
|
tristate "FPGA Device Feature List (DFL) support"
|
||||||
select FPGA_BRIDGE
|
select FPGA_BRIDGE
|
||||||
select FPGA_REGION
|
select FPGA_REGION
|
||||||
|
depends on HAS_IOMEM
|
||||||
help
|
help
|
||||||
Device Feature List (DFL) defines a feature list structure that
|
Device Feature List (DFL) defines a feature list structure that
|
||||||
creates a linked list of feature headers within the MMIO space
|
creates a linked list of feature headers within the MMIO space
|
||||||
|
|
|
@ -231,16 +231,16 @@ delete_cdev_device:
|
||||||
|
|
||||||
static void device_cdev_sysfs_del(struct hl_device *hdev)
|
static void device_cdev_sysfs_del(struct hl_device *hdev)
|
||||||
{
|
{
|
||||||
/* device_release() won't be called so must free devices explicitly */
|
if (!hdev->cdev_sysfs_created)
|
||||||
if (!hdev->cdev_sysfs_created) {
|
goto put_devices;
|
||||||
kfree(hdev->dev_ctrl);
|
|
||||||
kfree(hdev->dev);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hl_sysfs_fini(hdev);
|
hl_sysfs_fini(hdev);
|
||||||
cdev_device_del(&hdev->cdev_ctrl, hdev->dev_ctrl);
|
cdev_device_del(&hdev->cdev_ctrl, hdev->dev_ctrl);
|
||||||
cdev_device_del(&hdev->cdev, hdev->dev);
|
cdev_device_del(&hdev->cdev, hdev->dev);
|
||||||
|
|
||||||
|
put_devices:
|
||||||
|
put_device(hdev->dev);
|
||||||
|
put_device(hdev->dev_ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1371,9 +1371,9 @@ sw_fini:
|
||||||
early_fini:
|
early_fini:
|
||||||
device_early_fini(hdev);
|
device_early_fini(hdev);
|
||||||
free_dev_ctrl:
|
free_dev_ctrl:
|
||||||
kfree(hdev->dev_ctrl);
|
put_device(hdev->dev_ctrl);
|
||||||
free_dev:
|
free_dev:
|
||||||
kfree(hdev->dev);
|
put_device(hdev->dev);
|
||||||
out_disabled:
|
out_disabled:
|
||||||
hdev->disabled = true;
|
hdev->disabled = true;
|
||||||
if (add_cdev_sysfs_on_err)
|
if (add_cdev_sysfs_on_err)
|
||||||
|
|
|
@ -1626,6 +1626,7 @@ static int vm_ctx_init_with_ranges(struct hl_ctx *ctx,
|
||||||
goto host_hpage_range_err;
|
goto host_hpage_range_err;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
kfree(ctx->host_huge_va_range);
|
||||||
ctx->host_huge_va_range = ctx->host_va_range;
|
ctx->host_huge_va_range = ctx->host_va_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,14 +46,4 @@ config INTEL_MEI_TXE
|
||||||
Supported SoCs:
|
Supported SoCs:
|
||||||
Intel Bay Trail
|
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"
|
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 := pci-txe.o
|
||||||
mei-txe-objs += hw-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
|
mei-$(CONFIG_EVENT_TRACING) += mei-trace.o
|
||||||
CFLAGS_mei-trace.o = -I$(src)
|
CFLAGS_mei-trace.o = -I$(src)
|
||||||
|
|
||||||
|
|
|
@ -1,874 +0,0 @@
|
||||||
// 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");
|
|
Loading…
Reference in New Issue