tegrakernel/kernel/nvidia/drivers/virt/tegra/tegra_hv_wdt_handler.c

572 lines
14 KiB
C
Raw Permalink Normal View History

2022-02-16 09:13:02 -06:00
/*
* Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/errno.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/ioctl.h>
#include <linux/tegra-ivc.h>
#include <linux/tegra-ivc-instance.h>
#include <soc/tegra/chip-id.h>
#include <uapi/linux/nvhv_wdt_handler_ioctl.h>
#define DRV_NAME "tegra_hv_wdt_handler"
/* Time to sleep each time in ms, after checking channel ready */
#define DEFAULT_CH_WRITE_SLEEP_TIME_MS 100
/* Amount of time to wait for channel to become available for writing
* before giving up. Some time channel may be full, if monitor is not able
* to read the previous command, ideally this should not happened, since
* monitor reads command in interrupt immediately its sent. This can only
* fail if Monitor is dead or has not been given low priority than Guest.
* Which is a integration bug.
*/
#define DEFAULT_CH_WRITE_LOOP_CNT 20
/* Message from monitor with the guest id for which WDT expired */
struct tegra_hv_wdt_h_mon_msg {
unsigned int vmid;
};
struct tegra_hv_wdt_h {
uint32_t ivc_ch;
struct tegra_hv_ivc_cookie *ivck;
struct hv_wdt_h_state_array state_array;
struct platform_device *pdev;
struct class *class;
struct cdev cdev;
dev_t char_devt;
bool char_is_open;
wait_queue_head_t wq;
struct mutex mutex_lock;
};
static const char * const guest_state_string[] = {
"GUEST_STATE_INIT",
"GUEST_STATE_TIMER_EXPIRED",
"GUEST_STATE_WAIT_FOR_ACK",
};
static int hv_wdt_h_get_exp_guest_cnt(struct tegra_hv_wdt_h *hv)
{
int i;
int count = 0;
mutex_lock(&hv->mutex_lock);
for (i = 0; i < MAX_GUESTS_NUM; i++) {
if (hv->state_array.guest_state[i] == GUEST_STATE_TIMER_EXPIRED)
count++;
}
mutex_unlock(&hv->mutex_lock);
return count;
}
static long hv_wdt_h_get_guest_state(struct tegra_hv_wdt_h *hv,
bool non_blocking,
struct hv_wdt_h_state_array __user *state_array)
{
int i, ret = 0;
/* If any of the VM is expired dont wait */
if (!hv_wdt_h_get_exp_guest_cnt(hv)) {
if (non_blocking)
return -EAGAIN;
ret = wait_event_interruptible(hv->wq,
hv_wdt_h_get_exp_guest_cnt(hv));
if (ret < 0)
return ret;
}
/* Give VM data to Application */
mutex_lock(&hv->mutex_lock);
ret = copy_to_user(state_array, &hv->state_array,
sizeof(*state_array));
if (ret != 0) {
dev_err(&hv->pdev->dev, "Failed to copy user data\n");
goto out;
}
/* Mark all the expired states as sent to user */
for (i = 0; i < MAX_GUESTS_NUM; i++) {
if (hv->state_array.guest_state[i] == GUEST_STATE_TIMER_EXPIRED)
hv->state_array.guest_state[i] =
GUEST_STATE_WAIT_FOR_ACK;
}
out:
mutex_unlock(&hv->mutex_lock);
return ret;
}
static long hv_wdt_h_send_guest_cmd(struct tegra_hv_wdt_h *hv,
struct tegra_hv_wdt_h_cmd_array *cmds)
{
int i, ret = 0, write_possible_loop_cnt = 0;
ret = tegra_hv_ivc_channel_notified(hv->ivck);
if (ret != 0) {
dev_err(&hv->pdev->dev, "IVC channel not ready %d\n",
hv->ivc_ch);
return ret;
}
if (cmds->num_vmids > MAX_GUESTS_NUM) {
dev_err(&hv->pdev->dev, "Number of VMIDs greater than Max\n");
return -EINVAL;
}
while (tegra_hv_ivc_can_write(hv->ivck) == 0) {
msleep(DEFAULT_CH_WRITE_SLEEP_TIME_MS);
write_possible_loop_cnt++;
if (write_possible_loop_cnt >= DEFAULT_CH_WRITE_LOOP_CNT) {
dev_err(&hv->pdev->dev, "Failed to write data through IVC\n");
return -EAGAIN;
}
}
ret = tegra_hv_ivc_write(hv->ivck, cmds, sizeof(*cmds));
if (ret != sizeof(*cmds)) {
dev_err(&hv->pdev->dev, "Wrong write size from ivc write, required = %lu got = %d\n",
sizeof(*cmds), ret);
return -EINVAL;
}
mutex_lock(&hv->mutex_lock);
for (i = 0; i < cmds->num_vmids; i++) {
/* VMID number is greater than number of guests supported */
if (cmds->commands[i].vmid > MAX_GUESTS_NUM)
continue;
if (hv->state_array.guest_state[cmds->commands[i].vmid] ==
GUEST_STATE_WAIT_FOR_ACK)
hv->state_array.guest_state[cmds->commands[i].vmid] =
GUEST_STATE_INIT;
else {
dev_info(&hv->pdev->dev, "State not updated for %d guest\n",
cmds->commands[i].vmid);
}
}
mutex_unlock(&hv->mutex_lock);
return 0;
}
static unsigned int hv_wdt_h_poll(struct file *filp,
struct poll_table_struct *table)
{
struct tegra_hv_wdt_h *hv = filp->private_data;
unsigned long req_events = poll_requested_events(table);
unsigned int read_mask = POLLIN | POLLRDNORM;
unsigned int mask = 0;
if (req_events & read_mask)
poll_wait(filp, &hv->wq, table);
/* If any of the VMs has expired return mask */
if (hv_wdt_h_get_exp_guest_cnt(hv))
mask |= read_mask;
return mask;
}
static int hv_wdt_h_open(struct inode *inode, struct file *filp)
{
struct cdev *cdev = inode->i_cdev;
struct tegra_hv_wdt_h *hv =
container_of(cdev, struct tegra_hv_wdt_h, cdev);
int ret = 0;
/* We allow only single file handle to open */
mutex_lock(&hv->mutex_lock);
if (hv->char_is_open) {
ret = -EBUSY;
goto out;
}
hv->char_is_open = true;
filp->private_data = hv;
nonseekable_open(inode, filp);
out:
mutex_unlock(&hv->mutex_lock);
return ret;
}
static int hv_wdt_h_release(struct inode *inode, struct file *filp)
{
struct tegra_hv_wdt_h *hv = filp->private_data;
mutex_lock(&hv->mutex_lock);
hv->char_is_open = false;
filp->private_data = NULL;
mutex_unlock(&hv->mutex_lock);
return 0;
}
static long hv_wdt_h_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
struct tegra_hv_wdt_h *hv = file->private_data;
int ret = 0;
void __user *argp = (void __user *)arg;
struct hv_wdt_h_state_array __user *state_array;
struct tegra_hv_wdt_h_cmd_array cmds;
switch (cmd) {
case TEGRA_HV_WDT_H_GET_STATE:
state_array = argp;
ret = hv_wdt_h_get_guest_state(hv, (file->f_flags & O_NONBLOCK),
state_array);
break;
case TEGRA_HV_WDT_H_CMD:
if (copy_from_user(&cmds, argp, sizeof(cmds))) {
ret = -EFAULT;
goto out;
}
ret = hv_wdt_h_send_guest_cmd(hv, &cmds);
break;
default:
ret = -EINVAL;
break;
}
out:
return ret;
}
static const struct file_operations hv_wdt_h_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.poll = hv_wdt_h_poll,
.open = hv_wdt_h_open,
.release = hv_wdt_h_release,
.unlocked_ioctl = hv_wdt_h_ioctl,
};
static irqreturn_t hv_wdt_h_ivc_irq_thread(int irq, void *data)
{
struct tegra_hv_wdt_h_mon_msg monitor_msg;
struct tegra_hv_wdt_h *hv = (struct tegra_hv_wdt_h *)data;
int len = 0;
len = tegra_hv_ivc_read(hv->ivck, &monitor_msg, sizeof(monitor_msg));
if (len != sizeof(monitor_msg)) {
dev_err(&hv->pdev->dev, "Incorrect message size from IVC\n");
return IRQ_HANDLED;
}
mutex_lock(&hv->mutex_lock);
hv->state_array.guest_state[monitor_msg.vmid] =
GUEST_STATE_TIMER_EXPIRED;
mutex_unlock(&hv->mutex_lock);
wake_up_interruptible_all(&hv->wq);
return IRQ_HANDLED;
}
static irqreturn_t hv_wdt_h_ivc_irq(int irq, void *data)
{
struct tegra_hv_wdt_h *hv = (struct tegra_hv_wdt_h *)data;
if (tegra_hv_ivc_channel_notified(hv->ivck) != 0)
return IRQ_HANDLED;
if (tegra_hv_ivc_can_read(hv->ivck))
return IRQ_WAKE_THREAD;
return IRQ_HANDLED;
}
static void hv_wdt_h_cleanup(struct platform_device *pdev,
struct tegra_hv_wdt_h *hv)
{
/* Channel reset is required to make sure that other side is aware that
* peer is going down.
*/
tegra_hv_ivc_channel_reset(hv->ivck);
tegra_hv_ivc_unreserve(hv->ivck);
}
static int hv_wdt_h_init(struct platform_device *pdev,
struct tegra_hv_wdt_h *hv)
{
struct device_node *dn;
uint32_t id;
int err = 0;
init_waitqueue_head(&hv->wq);
mutex_init(&hv->mutex_lock);
hv->pdev = pdev;
dn = pdev->dev.of_node;
if (of_property_read_u32_index(dn, "ivc", 1, &id) != 0) {
dev_err(&pdev->dev, "failed to find ivc property\n");
return -EINVAL;
}
hv->ivc_ch = id;
hv->ivck = tegra_hv_ivc_reserve(dn, hv->ivc_ch, NULL);
if (IS_ERR_OR_NULL(hv->ivck)) {
dev_err(&pdev->dev, "Failed to reserve ivc channel %d\n",
hv->ivc_ch);
return -EINVAL;
}
tegra_hv_ivc_channel_reset(hv->ivck);
err = devm_request_threaded_irq(&pdev->dev, hv->ivck->irq,
hv_wdt_h_ivc_irq,
hv_wdt_h_ivc_irq_thread,
0, "tegra_hv_vm_irq", hv);
if (err) {
hv_wdt_h_cleanup(pdev, hv);
return err;
}
return 0;
}
static ssize_t guest_cmd_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct tegra_hv_wdt_h *hv = dev_get_drvdata(dev);
char local_buf[128] = {0};
char *b = local_buf;
char *guest_token;
char *cmd_token;
int guest_id, cmd, err;
struct tegra_hv_wdt_h_cmd_array cmd_array;
memcpy(local_buf, buf, min_t(size_t, count, sizeof(local_buf) - 1));
guest_token = strsep(&b, " ");
if (!guest_token)
goto wrong_cmd;
cmd_token = strsep(&b, " ");
if (!cmd_token)
goto wrong_cmd;
err = kstrtouint(guest_token, 10, &guest_id);
if (err) {
dev_err(dev, "Not a number for guest\n");
return -EINVAL;
} else if (guest_id > (MAX_GUESTS_NUM - 1)) {
dev_err(dev, "Guest number: %d > %d max supported guests\n",
guest_id, MAX_GUESTS_NUM - 1);
return -EINVAL;
}
if (!(strcmp(cmd_token, "MESSAGE_HANDLED_SYNC")))
cmd = MESSAGE_HANDLED_SYNC;
else if (!(strcmp(cmd_token, "MESSAGE_HANDLED_ASYNC")))
cmd = MESSAGE_HANDLED_ASYNC;
else
goto wrong_cmd;
cmd_array.num_vmids = 1;
cmd_array.commands[0].command = cmd;
cmd_array.commands[0].vmid = guest_id;
err = hv_wdt_h_send_guest_cmd(hv, &cmd_array);
if (err) {
dev_err(dev, "Error in sending command %d\n", err);
return err;
}
return count;
wrong_cmd:
dev_err(dev, "cmd not supported, cmd: echo -n \"guest_id MESSAGE_HANDLED_SYNC | MESSAGE_HANDLED_ASYNC\"\n");
return -EINVAL;
}
static ssize_t guest_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tegra_hv_wdt_h *hv = dev_get_drvdata(dev);
int i, count = 0, bytes;
const char *st_str;
mutex_lock(&hv->mutex_lock);
for (i = 0; i < MAX_GUESTS_NUM; i++) {
st_str = guest_state_string[hv->state_array.guest_state[i]];
bytes = snprintf(buf + count, PAGE_SIZE - count,
"GUEST%d\tstate:\t %s\n",
i, st_str);
count += bytes;
}
/* Mark all the expired states as sent to user */
for (i = 0; i < MAX_GUESTS_NUM; i++) {
if (hv->state_array.guest_state[i] == GUEST_STATE_TIMER_EXPIRED)
hv->state_array.guest_state[i] =
GUEST_STATE_WAIT_FOR_ACK;
}
mutex_unlock(&hv->mutex_lock);
return count;
}
static DEVICE_ATTR_RO(guest_state);
static DEVICE_ATTR_WO(guest_cmd);
static struct attribute *hv_wdt_ctl_attrs[] = {
&dev_attr_guest_state.attr,
&dev_attr_guest_cmd.attr,
NULL
};
ATTRIBUTE_GROUPS(hv_wdt_ctl);
static const struct attribute_group hv_wdt_ctl_attr_group = {
.attrs = hv_wdt_ctl_attrs,
};
static struct class wdt_handler_class = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.dev_groups = hv_wdt_ctl_groups,
};
static void hv_wdt_h_chrdev_cleanup(struct platform_device *pdev,
struct tegra_hv_wdt_h *hv)
{
device_destroy(hv->class, hv->char_devt);
cdev_del(&hv->cdev);
unregister_chrdev_region(MAJOR(hv->char_devt), 1);
class_unregister(&wdt_handler_class);
platform_set_drvdata(pdev, NULL);
}
static int hv_wdt_h_chrdev_init(struct platform_device *pdev,
struct tegra_hv_wdt_h *hv)
{
int err;
struct device *chr_dev;
platform_set_drvdata(pdev, hv);
class_register(&wdt_handler_class);
hv->class = &wdt_handler_class;
err = alloc_chrdev_region(&hv->char_devt, 0, 1,
DRV_NAME);
if (err < 0) {
dev_err(&pdev->dev, "%s: Failed to alloc chrdev region, %d\n",
__func__, err);
goto error_unregister_class;
}
cdev_init(&hv->cdev, &hv_wdt_h_ops);
hv->cdev.owner = THIS_MODULE;
err = cdev_add(&hv->cdev, hv->char_devt, 1);
if (err) {
dev_err(&pdev->dev, "%s: Failed to add cdev, %d\n",
__func__, err);
goto error_unregister_chrdev_region;
}
chr_dev = device_create(hv->class, &hv->pdev->dev, hv->char_devt,
hv, DRV_NAME);
if (IS_ERR(chr_dev)) {
dev_err(&pdev->dev, "%s: Failed to create device, %ld\n",
__func__, PTR_ERR(chr_dev));
err = PTR_ERR(chr_dev);
goto error_cdev_del;
}
return 0;
error_cdev_del:
cdev_del(&hv->cdev);
error_unregister_chrdev_region:
unregister_chrdev_region(MAJOR(hv->char_devt), 1);
error_unregister_class:
class_unregister(&wdt_handler_class);
return err;
}
static int hv_wdt_h_probe(struct platform_device *pdev)
{
struct tegra_hv_wdt_h *hv;
int err = 0;
if (!is_tegra_hypervisor_mode()) {
dev_info(&pdev->dev, "hypervisor is not present\n");
return -ENODEV;
}
hv = devm_kzalloc(&pdev->dev, sizeof(*hv), GFP_KERNEL);
if (!hv)
return -ENOMEM;
err = hv_wdt_h_init(pdev, hv);
if (err != 0) {
dev_err(&pdev->dev, "Failed to enable guest wdt handler\n");
return err;
}
err = hv_wdt_h_chrdev_init(pdev, hv);
if (err != 0) {
dev_err(&pdev->dev, "Fail to init character driver\n");
hv_wdt_h_cleanup(pdev, hv);
return err;
}
return 0;
}
static int hv_wdt_h_remove(struct platform_device *pdev)
{
struct tegra_hv_wdt_h *hv = platform_get_drvdata(pdev);
hv_wdt_h_chrdev_cleanup(pdev, hv);
hv_wdt_h_cleanup(pdev, hv);
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct of_device_id tegra_wdt_h_match[] = {
{ .compatible = "nvidia,tegra-hv-wdt-handler", },
{}
};
static struct platform_driver tegra_wdt_h_driver = {
.probe = hv_wdt_h_probe,
.remove = hv_wdt_h_remove,
.driver = {
.owner = THIS_MODULE,
.name = DRV_NAME,
.of_match_table = of_match_ptr(tegra_wdt_h_match),
},
};
module_platform_driver(tegra_wdt_h_driver);
MODULE_AUTHOR("Hardik Tushar Shah <hardikts@nvidia.com>");
MODULE_DESCRIPTION("Tegra Hypervisor VM WDT Expiry handler");
MODULE_LICENSE("GPL");