// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2012-2019, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/sched/signal.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/mei_cl_bus.h>
#include "mei_dev.h"
#include "client.h"
#define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver)
/**
* __mei_cl_send - internal client send (write)
*
* @cl: host client
* @buf: buffer to send
* @length: buffer length
* @mode: sending mode
*
* Return: written size bytes or < 0 on error
*/
ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
unsigned int mode)
{
struct mei_device *bus;
struct mei_cl_cb *cb;
ssize_t rets;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
bus = cl->dev;
mutex_lock(&bus->device_lock);
if (bus->dev_state != MEI_DEV_ENABLED) {
rets = -ENODEV;
goto out;
}
if (!mei_cl_is_connected(cl)) {
rets = -ENODEV;
goto out;
}
/* Check if we have an ME client device */
if (!mei_me_cl_is_active(cl->me_cl)) {
rets = -ENOTTY;
goto out;
}
if (length > mei_cl_mtu(cl)) {
rets = -EFBIG;
goto out;
}
while (cl->tx_cb_queued >= bus->tx_queue_limit) {
mutex_unlock(&bus->device_lock);
rets = wait_event_interruptible(cl->tx_wait,
cl->writing_state == MEI_WRITE_COMPLETE ||
(!mei_cl_is_connected(cl)));
mutex_lock(&bus->device_lock);
if (rets) {
if (signal_pending(current))
rets = -EINTR;
goto out;
}
if (!mei_cl_is_connected(cl)) {
rets = -ENODEV;
goto out;
}
}
cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, NULL);
if (!cb) {
rets = -ENOMEM;
goto out;
}
cb->internal = !!(mode & MEI_CL_IO_TX_INTERNAL);
cb->blocking = !!(mode & MEI_CL_IO_TX_BLOCKING);
memcpy(cb->buf.data, buf, length);
rets = mei_cl_write(cl, cb);
out:
mutex_unlock(&bus->device_lock);
return rets;
}
/**
* __mei_cl_recv - internal client receive (read)
*
* @cl: host client
* @buf: buffer to receive
* @length: buffer length
* @mode: io mode
* @timeout: recv timeout, 0 for infinite timeout
*
* Return: read size in bytes of < 0 on error
*/
ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length,
unsigned int mode, unsigned long timeout)
{
struct mei_device *bus;
struct mei_cl_cb *cb;
size_t r_length;
ssize_t rets;
bool nonblock = !!(mode & MEI_CL_IO_RX_NONBLOCK);
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
bus = cl->dev;
mutex_lock(&bus->device_lock);
if (bus->dev_state != MEI_DEV_ENABLED) {
rets = -ENODEV;
goto out;
}
cb = mei_cl_read_cb(cl, NULL);
if (cb)
goto copy;
rets = mei_cl_read_start(cl, length, NULL);
if (rets && rets != -EBUSY)
goto out;
if (nonblock) {
rets = -EAGAIN;
goto out;
}
/* wait on event only if there is no other waiter */
/* synchronized under device mutex */
if (!waitqueue_active(&cl->rx_wait)) {
mutex_unlock(&bus->device_lock);
if (timeout) {
rets = wait_event_interruptible_timeout
(cl->rx_wait,
(!list_empty(&cl->rd_completed)) ||
(!mei_cl_is_connected(cl)),
msecs_to_jiffies(timeout));
if (rets == 0)
return -ETIME;
if (rets < 0) {
if (signal_pending(current))
return -EINTR;
return -ERESTARTSYS;
}
} else