// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2003-2018, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/sched/signal.h>
#include <linux/uuid.h>
#include <linux/compat.h>
#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "client.h"
static struct class *mei_class;
static dev_t mei_devt;
#define MEI_MAX_DEVS MINORMASK
static DEFINE_MUTEX(mei_minor_lock);
static DEFINE_IDR(mei_idr);
/**
* mei_open - the open function
*
* @inode: pointer to inode structure
* @file: pointer to file structure
*
* Return: 0 on success, <0 on error
*/
static int mei_open(struct inode *inode, struct file *file)
{
struct mei_device *dev;
struct mei_cl *cl;
int err;
dev = container_of(inode->i_cdev, struct mei_device, cdev);
if (!dev)
return -ENODEV;
mutex_lock(&dev->device_lock);
if (dev->dev_state != MEI_DEV_ENABLED) {
dev_dbg(dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n",
mei_dev_state_str(dev->dev_state));
err = -ENODEV;
goto err_unlock;
}
cl = mei_cl_alloc_linked(dev);
if (IS_ERR(cl)) {
err = PTR_ERR(cl);
goto err_unlock;
}
cl->fp = file;
file->private_data = cl;
mutex_unlock(&dev->device_lock);
return nonseekable_open(inode, file);
err_unlock:
mutex_unlock(&dev->device_lock);
return err;
}
/**
* mei_release - the release function
*
* @inode: pointer to inode structure
* @file: pointer to file structure
*
* Return: 0 on success, <0 on error
*/
static int mei_release(struct inode *inode, struct file *file)
{
struct mei_cl *cl = file->private_data;
struct mei_device *dev;
int rets;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
mutex_lock(&dev->device_lock);
rets = mei_cl_disconnect(cl);
mei_cl_flush_queues(cl, file);
cl_dbg(dev, cl, "removing\n");
mei_cl_unlink(cl);
file->private_data = NULL;
kfree(cl);
mutex_unlock(&dev->device_lock);
return rets;
}
/**
* mei_read - the read function.
*
* @file: pointer to file structure
* @ubuf: pointer to user buffer
* @length: buffer length
* @offset: data offset in buffer
*
* Return: >=0 data length on success , <0 on error
*/
static ssize_t mei_read(struct file *file, char __user *ubuf,
size_t length, loff_t *offset)
{
struct mei_cl *cl = file->private_data;
struct mei_device *dev;
struct mei_cl_cb *cb = NULL;
bool nonblock = !!(file->f_flags & O_NONBLOCK);
ssize_t rets;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
mutex_lock(&dev->device_lock);
if (dev->dev_state != MEI_DEV_ENABLED) {
rets = -ENODEV;
goto out;
}
if (length == 0) {
rets = 0;
goto out;
}
if (ubuf == NULL) {
rets = -EMSGSIZE;
goto out;
}
cb = mei_cl_read_cb(cl, file);
if (cb)
goto copy_buffer;
if (*offset > 0)
*offset = 0;
rets = mei_cl_read_start(cl, length, file);
if (rets && rets != -EBUSY) {
cl_dbg(dev, cl, "mei start read failure status = %zd\n", rets);
goto out;
}
if (nonblock) {
rets = -EAGAIN;
goto out;
}
mutex_unlock(&dev->device_lock);
if (wait_event_interruptible(cl->rx_wait,
!list_empty(&cl->rd_completed) ||
!mei_cl_is_connected(cl))) {
if (signal_pending(current))
return -EINTR;
return -ERESTARTSYS;
}
mutex_lock(&dev->device_lock);
if (!mei_cl_is_connected(cl)) {
rets = -ENODEV;
goto out;
}
cb = mei_cl_read_cb(cl, file);
if (!cb) {
rets = 0;
goto out;
}
copy_buffer:
/* now copy the data to user space */
if (cb->status) {
rets = cb->status;
cl_dbg(dev, cl,