Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447// SPDX-License-Identifier: GPL-2.0/* Copyright (C) 2023-2026 Intel Corporation */#include <linux/atomic.h>#include <linux/cdev.h>#include <linux/cleanup.h>#include <linux/device.h>#include <linux/err.h>#include <linux/export.h>#include <linux/fs.h>#include <linux/issei.h>#include <linux/kobject.h>#include <linux/list.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/poll.h>#include <linux/sched/signal.h>#include <linux/slab.h>#include <linux/types.h>#include <linux/wait.h>#include <linux/xarray.h>
#include "issei_dev.h"#include "host_client.h"#include "cdev.h"
struct class *issei_class;static dev_t issei_devt;
#define ISSEI_MAX_DEVS MINORMASK
static DEFINE_XARRAY_ALLOC(issei_minor_xa);
static int issei_open(struct inode *inode, struct file *fp){ struct issei_host_client *cl; struct issei_device *idev;
xa_lock(&issei_minor_xa); idev = xa_load(&issei_minor_xa, iminor(inode)); if (idev) get_device(&idev->dev); xa_unlock(&issei_minor_xa); if (!idev) return -ENODEV;
cl = issei_cl_create(idev, fp); if (IS_ERR(cl)) { put_device(&idev->dev); return PTR_ERR(cl); } fp->private_data = cl;
return nonseekable_open(inode, fp);}
static int issei_release(struct inode *inode, struct file *fp){ struct issei_host_client *cl = fp->private_data; struct issei_device *idev = cl->idev;
issei_cl_remove(cl); put_device(&idev->dev);
return 0;}
static long issei_ioctl(struct file *file, unsigned int cmd, unsigned long data){ struct issei_host_client *cl = file->private_data; struct issei_connect_client_data conn; struct issei_device *idev = cl->idev; int ret;
switch (cmd) { case IOCTL_ISSEI_CONNECT_CLIENT: dev_dbg(&idev->dev, "IOCTL_ISSEI_CONNECT_CLIENT\n");
if (idev->rst_state != ISSEI_RST_STATE_DONE) { dev_dbg(&idev->dev, "Device is in transition\n"); return -ENODEV; }
if (copy_from_user(&conn, (char __user *)data, sizeof(conn))) { dev_dbg(&idev->dev, "failed to copy data from userland\n"); return -EFAULT; }
ret = issei_cl_connect(cl, (uuid_t *)&conn.in_client_uuid, &conn.out_client_properties.max_msg_length, &conn.out_client_properties.protocol_version, &conn.out_client_properties.flags); if (ret) return ret;
if (copy_to_user((char __user *)data, &conn, sizeof(conn))) { dev_dbg(&idev->dev, "failed to copy data to userland\n"); issei_cl_disconnect(cl); return -EFAULT; } return 0;
case IOCTL_ISSEI_DISCONNECT_CLIENT: dev_dbg(&idev->dev, "IOCTL_ISSEI_DISCONNECT_CLIENT\n");
if (idev->rst_state != ISSEI_RST_STATE_DONE) { dev_dbg(&idev->dev, "Device is in transition\n"); return -ENODEV; }
return issei_cl_disconnect(cl);
default: return -ENOIOCTLCMD; }}
static ssize_t issei_write(struct file *file, const char __user *ubuf, size_t length, loff_t *offset){ struct issei_host_client *cl = file->private_data; struct issei_device *idev = cl->idev; ssize_t ret;
if (!length) return 0;
if (idev->rst_state != ISSEI_RST_STATE_DONE) { dev_dbg(&idev->dev, "Device is in transition\n"); return -EBUSY; }
/* sanity check */ if (length > idev->dma.length.h2f) { dev_dbg(&idev->dev, "Write is too big %zu > %zu\n", length, idev->dma.length.h2f); return -EFBIG; }
u8 *buf __free(kfree) = memdup_user(ubuf, length); if (IS_ERR(buf)) { dev_dbg(&idev->dev, "failed to copy data from userland\n"); return PTR_ERR(buf); }
do { ret = issei_cl_write(cl, buf, length); if (ret < 0 && ret != -EAGAIN) return ret; /* buf is consumed by issei_cl_write on success */ if (ret >= 0) retain_and_null_ptr(buf); if (wait_event_interruptible(cl->write_wait, issei_cl_check_write(cl) != 1)) { issei_cl_clean_all_wbuf(cl); if (signal_pending(current)) return -EINTR; return -ERESTARTSYS; } } while (ret == -EAGAIN);
return ret;}
static ssize_t issei_read(struct file *file, char __user *ubuf, size_t length, loff_t *offset){ struct issei_host_client *cl = file->private_data; struct issei_device *idev = cl->idev; u8 *data = NULL; ssize_t ret;
if (!length) return 0;
if (idev->rst_state != ISSEI_RST_STATE_DONE) { dev_dbg(&idev->dev, "Device is in transition\n"); return -EBUSY; }
/* sanity check */ if (length > idev->dma.length.f2h) { dev_dbg(&idev->dev, "Read is too big %zu > %zu\n", length, idev->dma.length.f2h); return -EFBIG; }
ret = issei_cl_read(cl, &data, length); if (ret < 0) { if (ret != -ENOENT) return ret;
if (wait_event_interruptible(cl->read_wait, issei_cl_check_read(cl) != 0)) { if (signal_pending(current)) return -EINTR; return -ERESTARTSYS; }
ret = issei_cl_read(cl, &data, length); if (ret < 0) return ret; }
if (copy_to_user(ubuf, data, ret)) { dev_dbg(&idev->dev, "failed to copy data to userland\n"); ret = -EFAULT; } else { *offset = 0; }
kfree(data);
return ret;}
static __poll_t issei_poll(struct file *file, poll_table *wait){ __poll_t req_events = poll_requested_events(wait); struct issei_host_client *cl = file->private_data; struct issei_device *idev = cl->idev; __poll_t mask = 0; int ret;
if (idev->rst_state != ISSEI_RST_STATE_DONE) { dev_dbg(&idev->dev, "Device is in transition\n"); return EPOLLERR; }
if (req_events & (EPOLLIN | EPOLLRDNORM)) { poll_wait(file, &cl->read_wait, wait); ret = issei_cl_check_read(cl); if (ret == 1) mask |= EPOLLIN | EPOLLRDNORM; else if (ret < 0) mask |= EPOLLERR; }
if (req_events & (EPOLLOUT | EPOLLWRNORM)) { poll_wait(file, &cl->write_wait, wait); ret = issei_cl_check_write(cl); if (ret == 0) mask |= EPOLLOUT | EPOLLWRNORM; else if (ret < 0) mask |= EPOLLERR; }
return mask;}
static ssize_t fw_ver_show(struct device *device, struct device_attribute *attr, char *buf){ struct issei_device *idev = dev_get_drvdata(device);
return sysfs_emit(buf, "%u.%u.%u.%u\n", idev->fw_version[0], idev->fw_version[1], idev->fw_version[2], idev->fw_version[3]);}static DEVICE_ATTR_RO(fw_ver);
static struct attribute *issei_attrs[] = { &dev_attr_fw_ver.attr, NULL};ATTRIBUTE_GROUPS(issei);
static const struct file_operations issei_fops = { .owner = THIS_MODULE, .open = issei_open, .unlocked_ioctl = issei_ioctl, .compat_ioctl = compat_ptr_ioctl, .write = issei_write, .read = issei_read, .release = issei_release, .poll = issei_poll,};
static void issei_device_release(struct device *dev){ kfree(dev_get_drvdata(dev));}
static void issei_device_init(struct issei_device *idev, struct device *parent, const struct issei_dma_length *dma_length, const struct issei_hw_ops *ops){ idev->parent = parent; idev->power_down = false; init_waitqueue_head(&idev->wait_has_data); idev->has_data = false; init_waitqueue_head(&idev->wait_rst_state); idev->rst_state = ISSEI_RST_STATE_INIT;
mutex_init(&idev->client_lock); INIT_LIST_HEAD(&idev->host_client_list); idev->host_client_last_id = 0; idev->host_client_count = 0; INIT_LIST_HEAD(&idev->fw_client_list); INIT_LIST_HEAD(&idev->write_queue); idev->last_write_ts = 0;
idev->dma.length = *dma_length;
idev->ops = ops;}
/** * issei_register: register issei character device * @hw_size: size of the hardware structure to allocate * @parent: parent device * @dma_length: structure with DMA sizes * @ops: hardware-related operations * * Return: pointer allocated to issei_device structure, error on failure */struct issei_device *issei_register(size_t hw_size, struct device *parent, const struct issei_dma_length *dma_length, const struct issei_hw_ops *ops){ struct issei_device *idev; u32 minor; int ret, devno;
idev = kzalloc(sizeof(*idev) + hw_size, GFP_KERNEL); if (!idev) return ERR_PTR(-ENOMEM);
issei_device_init(idev, parent, dma_length, ops);
ret = xa_alloc(&issei_minor_xa, &minor, idev, XA_LIMIT(0, ISSEI_MAX_DEVS), GFP_KERNEL); if (ret < 0) { dev_err(&idev->dev, "Failed to allocate minor. ret = %d\n", ret); kfree(idev); return ERR_PTR(ret); }
idev->minor = minor; devno = MKDEV(MAJOR(issei_devt), idev->minor);
device_initialize(&idev->dev); idev->dev.devt = devno; idev->dev.class = issei_class; idev->dev.parent = parent; idev->dev.groups = issei_groups; idev->dev.release = issei_device_release; dev_set_drvdata(&idev->dev, idev);
idev->cdev = cdev_alloc(); if (!idev->cdev) { ret = -ENOMEM; goto err; } idev->cdev->ops = &issei_fops; if (parent->driver) idev->cdev->owner = parent->driver->owner; cdev_set_parent(idev->cdev, &idev->dev.kobj);
ret = cdev_add(idev->cdev, devno, 1); if (ret) { dev_err(parent, "unable to add device %d:%u ret = %d\n", MAJOR(issei_devt), idev->minor, ret); goto err_del_cdev; }
ret = dev_set_name(&idev->dev, "issei%u", idev->minor); if (ret) { dev_err(parent, "unable to set name to device %d:%u ret = %d\n", MAJOR(issei_devt), idev->minor, ret); goto err_del_cdev; }
ret = device_add(&idev->dev); if (ret) { dev_err(parent, "unable to add device %d:%u ret = %d\n", MAJOR(issei_devt), idev->minor, ret); goto err_del_cdev; }
idev->fw_clients = kset_create_and_add("fw_clients", NULL, &idev->dev.kobj); if (!idev->fw_clients) { ret = -ENOMEM; goto err_del_dev; }
return idev;
err_del_dev: device_del(&idev->dev);err_del_cdev: cdev_del(idev->cdev);err: put_device(&idev->dev); xa_erase(&issei_minor_xa, minor);
return ERR_PTR(ret);}EXPORT_SYMBOL_GPL(issei_register);
/** * issei_deregister: remove issei character device * @idev: the device structure */void issei_deregister(struct issei_device *idev){ u32 minor = idev->minor;
cdev_del(idev->cdev);
kset_unregister(idev->fw_clients);
device_del(&idev->dev);
put_device(&idev->dev);
xa_erase(&issei_minor_xa, minor);}EXPORT_SYMBOL_GPL(issei_deregister);
static int __init issei_cdev_init(void){ int ret;
issei_class = class_create("issei"); if (IS_ERR(issei_class)) { pr_err("couldn't create class\n"); return PTR_ERR(issei_class); }
ret = alloc_chrdev_region(&issei_devt, 0, ISSEI_MAX_DEVS, "issei"); if (ret < 0) { pr_err("unable to allocate char dev region\n"); class_destroy(issei_class); return ret; }
return 0;}
static void __exit issei_cdev_exit(void){ unregister_chrdev_region(issei_devt, ISSEI_MAX_DEVS); class_destroy(issei_class);}
module_init(issei_cdev_init);module_exit(issei_cdev_exit);
MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface");MODULE_LICENSE("GPL");