blob: e0fe09467a805fc418eaadf547196d201a4cc881 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018-2019 Intel Corporation
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/peci.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
/*
* A peci_dev represents an peci_adapter ... an PECI or SMBus master, not a
* slave (peci_client) with which messages will be exchanged. It's coupled
* with a character special file which is accessed by user mode drivers.
*
* The list of peci_dev structures is parallel to the peci_adapter lists
* maintained by the driver model, and is updated using bus notifications.
*/
struct peci_dev {
struct list_head list;
struct peci_adapter *adapter;
struct device *dev;
struct cdev cdev;
};
#define PECI_MINORS MINORMASK
static dev_t peci_devt;
static LIST_HEAD(peci_dev_list);
static DEFINE_SPINLOCK(peci_dev_list_lock);
static struct peci_dev *peci_dev_get_by_minor(uint index)
{
struct peci_dev *peci_dev;
spin_lock(&peci_dev_list_lock);
list_for_each_entry(peci_dev, &peci_dev_list, list) {
if (peci_dev->adapter->nr == index)
goto found;
}
peci_dev = NULL;
found:
spin_unlock(&peci_dev_list_lock);
return peci_dev;
}
static struct peci_dev *peci_dev_alloc(struct peci_adapter *adapter)
{
struct peci_dev *peci_dev;
if (adapter->nr >= PECI_MINORS) {
dev_err(&adapter->dev, "Out of device minors (%d)\n",
adapter->nr);
return ERR_PTR(-ENODEV);
}
peci_dev = kzalloc(sizeof(*peci_dev), GFP_KERNEL);
if (!peci_dev)
return ERR_PTR(-ENOMEM);
peci_dev->adapter = adapter;
spin_lock(&peci_dev_list_lock);
list_add_tail(&peci_dev->list, &peci_dev_list);
spin_unlock(&peci_dev_list_lock);
return peci_dev;
}
static void peci_dev_put(struct peci_dev *peci_dev)
{
spin_lock(&peci_dev_list_lock);
list_del(&peci_dev->list);
spin_unlock(&peci_dev_list_lock);
kfree(peci_dev);
}
static ssize_t name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct peci_dev *peci_dev = peci_dev_get_by_minor(MINOR(dev->devt));
if (!peci_dev)
return -ENODEV;
return sprintf(buf, "%s\n", peci_dev->adapter->name);
}
static DEVICE_ATTR_RO(name);
static struct attribute *peci_dev_attrs[] = {
&dev_attr_name.attr,
NULL,
};
ATTRIBUTE_GROUPS(peci_dev);
static long peci_dev_ioctl(struct file *file, uint iocmd, ulong arg)
{
struct peci_dev *peci_dev = file->private_data;
void __user *umsg = (void __user *)arg;
struct peci_xfer_msg *xmsg = NULL;
struct peci_xfer_msg uxmsg;
enum peci_cmd cmd;
u8 *msg = NULL;
uint msg_len;
int ret;
cmd = _IOC_NR(iocmd);
msg_len = _IOC_SIZE(iocmd);
switch (cmd) {
case PECI_CMD_XFER:
if (msg_len != sizeof(struct peci_xfer_msg)) {
ret = -EFAULT;
break;
}
if (copy_from_user(&uxmsg, umsg, msg_len)) {
ret = -EFAULT;
break;
}
xmsg = peci_get_xfer_msg(uxmsg.tx_len, uxmsg.rx_len);
if (IS_ERR(xmsg)) {
ret = PTR_ERR(xmsg);
break;
}
if (uxmsg.tx_len &&
copy_from_user(xmsg->tx_buf, (__u8 __user *)uxmsg.tx_buf,
uxmsg.tx_len)) {
ret = -EFAULT;
break;
}
xmsg->addr = uxmsg.addr;
xmsg->tx_len = uxmsg.tx_len;
xmsg->rx_len = uxmsg.rx_len;
ret = peci_command(peci_dev->adapter, cmd, xmsg);
if (!ret && xmsg->rx_len &&
copy_to_user((__u8 __user *)uxmsg.rx_buf, xmsg->rx_buf,
xmsg->rx_len))
ret = -EFAULT;
break;
default:
msg = memdup_user(umsg, msg_len);
if (IS_ERR(msg)) {
ret = PTR_ERR(msg);
break;
}
ret = peci_command(peci_dev->adapter, cmd, msg);
if ((!ret || ret == -ETIMEDOUT) &&
copy_to_user(umsg, msg, msg_len))
ret = -EFAULT;
break;
}
peci_put_xfer_msg(xmsg);
kfree(msg);
return (long)ret;
}
static int peci_dev_open(struct inode *inode, struct file *file)
{
struct peci_adapter *adapter;
struct peci_dev *peci_dev;
peci_dev = peci_dev_get_by_minor(iminor(inode));
if (!peci_dev)
return -ENODEV;
adapter = peci_get_adapter(peci_dev->adapter->nr);
if (!adapter)
return -ENODEV;
file->private_data = peci_dev;
return 0;
}
static int peci_dev_release(struct inode *inode, struct file *file)
{
struct peci_dev *peci_dev = file->private_data;
peci_put_adapter(peci_dev->adapter);
file->private_data = NULL;
return 0;
}
static const struct file_operations peci_dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = peci_dev_ioctl,
.open = peci_dev_open,
.release = peci_dev_release,
.llseek = no_llseek,
};
static struct class *peci_dev_class;
static int peci_dev_attach_adapter(struct device *dev, void *dummy)
{
struct peci_adapter *adapter;
struct peci_dev *peci_dev;
dev_t devt;
int ret;
if (dev->type != &peci_adapter_type)
return 0;
adapter = to_peci_adapter(dev);
peci_dev = peci_dev_alloc(adapter);
if (IS_ERR(peci_dev))
return PTR_ERR(peci_dev);
cdev_init(&peci_dev->cdev, &peci_dev_fops);
peci_dev->cdev.owner = THIS_MODULE;
devt = MKDEV(MAJOR(peci_devt), adapter->nr);
ret = cdev_add(&peci_dev->cdev, devt, 1);
if (ret)
goto err_put_dev;
/* register this peci device with the driver core */
peci_dev->dev = device_create(peci_dev_class, &adapter->dev, devt, NULL,
"peci-%d", adapter->nr);
if (IS_ERR(peci_dev->dev)) {
ret = PTR_ERR(peci_dev->dev);
goto err_del_cdev;
}
dev_info(dev, "cdev of adapter [%s] registered as minor %d\n",
adapter->name, adapter->nr);
return 0;
err_del_cdev:
cdev_del(&peci_dev->cdev);
err_put_dev:
peci_dev_put(peci_dev);
return ret;
}
static int peci_dev_detach_adapter(struct device *dev, void *dummy)
{
struct peci_adapter *adapter;
struct peci_dev *peci_dev;
dev_t devt;
if (dev->type != &peci_adapter_type)
return 0;
adapter = to_peci_adapter(dev);
peci_dev = peci_dev_get_by_minor(adapter->nr);
if (!peci_dev)
return 0;
cdev_del(&peci_dev->cdev);
devt = peci_dev->dev->devt;
peci_dev_put(peci_dev);
device_destroy(peci_dev_class, devt);
dev_info(dev, "cdev of adapter [%s] unregistered\n", adapter->name);
return 0;
}
static int peci_dev_notifier_call(struct notifier_block *nb, ulong action,
void *data)
{
struct device *dev = data;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
return peci_dev_attach_adapter(dev, NULL);
case BUS_NOTIFY_DEL_DEVICE:
return peci_dev_detach_adapter(dev, NULL);
}
return 0;
}
static struct notifier_block peci_dev_notifier = {
.notifier_call = peci_dev_notifier_call,
};
static int __init peci_dev_init(void)
{
int ret;
pr_debug("peci /dev entries driver\n");
ret = alloc_chrdev_region(&peci_devt, 0, PECI_MINORS, "peci");
if (ret < 0) {
pr_err("peci: Failed to allocate chr dev region!\n");
bus_unregister(&peci_bus_type);
goto err;
}
peci_dev_class = class_create(THIS_MODULE, KBUILD_MODNAME);
if (IS_ERR(peci_dev_class)) {
ret = PTR_ERR(peci_dev_class);
goto err_unreg_chrdev;
}
peci_dev_class->dev_groups = peci_dev_groups;
/* Keep track of adapters which will be added or removed later */
ret = bus_register_notifier(&peci_bus_type, &peci_dev_notifier);
if (ret)
goto err_destroy_class;
/* Bind to already existing adapters right away */
peci_for_each_dev(NULL, peci_dev_attach_adapter);
return 0;
err_destroy_class:
class_destroy(peci_dev_class);
err_unreg_chrdev:
unregister_chrdev_region(peci_devt, PECI_MINORS);
err:
pr_err("%s: Driver Initialization failed\n", __FILE__);
return ret;
}
static void __exit peci_dev_exit(void)
{
bus_unregister_notifier(&peci_bus_type, &peci_dev_notifier);
peci_for_each_dev(NULL, peci_dev_detach_adapter);
class_destroy(peci_dev_class);
unregister_chrdev_region(peci_devt, PECI_MINORS);
}
module_init(peci_dev_init);
module_exit(peci_dev_exit);
MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
MODULE_DESCRIPTION("PECI /dev entries driver");
MODULE_LICENSE("GPL v2");