| // 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"); |