On 二, 2017-01-03 at 10:54 +0100, Jiri Kosina wrote: > On Fri, 23 Dec 2016, Even Xu wrote: > > [ ... snip ... ] > > > > +static ssize_t ishtp_cl_write(struct file *file, const char __user > > *ubuf, > > + size_t length, loff_t *offset) > > +{ > > + struct ishtp_cl_miscdev *ishtp_cl_misc = file- > > >private_data; > > + struct ishtp_cl *cl; > > + void *write_buf; > > + struct ishtp_device *dev; > > + int ret; > > + > > + /* Non-blocking semantics are not supported */ > > + if (file->f_flags & O_NONBLOCK) { > > + ret = -EINVAL; > > + goto out_unlock; > When taking the error path here you'd try to unlock > ishtp_cl_misc->cl_mutex before actually acquiring it. > Thanks for your comments, Jiri, I update my patch below: =============================================================== Intel ISHFW supports many different clients, in hid/intel-ish-hid/ishtp bus driver, it creates following client devices: HID client: interface of sensor configure and sensor event report. SMHI client: interface of sensor calibration, ISHFW debug, ISHFW performance analysis and manufacture support. Trace client: interface of ISHFW debug log output. Trace configure client: interface of ISHFW debug log configuration, such as output port, log level, filter. ISHFW loader client: interface of customized ISHFW loader. HID client has been handle by hid/intel-ish-hid/intel-ishtp-hid client driver, and rest of the clients export interface using miscellaneous drivers. This interface is used by user space tools for debugging and calibration of sensors. Signed-off-by: Even Xu <even.xu@xxxxxxxxx> Reviewed-by: Andriy Shevchenko <andriy.shevchenko@xxxxxxxxx> Reviewed-by: Srinivas Pandruvada <srinivas.pandruvada@xxxxxxxxxxxxxxx> --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/intel-ish-client/Kconfig | 15 + drivers/misc/intel-ish-client/Makefile | 8 + .../misc/intel-ish-client/intel-ishtp-clients.c | 882 +++++++++++++++++++++ include/uapi/linux/intel-ishtp-clients.h | 73 ++ 6 files changed, 980 insertions(+) create mode 100644 drivers/misc/intel-ish-client/Kconfig create mode 100644 drivers/misc/intel-ish-client/Makefile create mode 100644 drivers/misc/intel-ish-client/intel-ishtp-clients.c create mode 100644 include/uapi/linux/intel-ishtp-clients.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 64971ba..a89849f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -778,4 +778,5 @@ source "drivers/misc/mic/Kconfig" source "drivers/misc/genwqe/Kconfig" source "drivers/misc/echo/Kconfig" source "drivers/misc/cxl/Kconfig" +source "drivers/misc/intel-ish-client/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 3198336..c54015d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_PANEL) += panel.o +obj-$(CONFIG_INTEL_ISH_CLIENT) += intel-ish-client/ lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o diff --git a/drivers/misc/intel-ish-client/Kconfig b/drivers/misc/intel-ish-client/Kconfig new file mode 100644 index 0000000..6fa9cc0 --- /dev/null +++ b/drivers/misc/intel-ish-client/Kconfig @@ -0,0 +1,15 @@ +menu "Intel ISH Client support" + depends on INTEL_ISH_HID + +config INTEL_ISH_CLIENT + tristate "Intel Integrated Sensor Hub Client" + help + The Integrated Sensor Hub (ISH) supports many clients, Intel ISH client + driver supports following clients: + SMHI client: calibration & perfermance & menufacture tool interface + trace configure client: ISHFW trace parameter configure interface + trace tool client: ISHFW trace log output interface + loader client: loading customized ISHFW interface + + Say Y here if you want to support Intel ISH clients. If unsure, say N. +endmenu diff --git a/drivers/misc/intel-ish-client/Makefile b/drivers/misc/intel-ish-client/Makefile new file mode 100644 index 0000000..29a5461 --- /dev/null +++ b/drivers/misc/intel-ish-client/Makefile @@ -0,0 +1,8 @@ +# +# Makefile - Intel ISH client driver +# Copyright (c) 2014-2016, Intel Corporation. +# +# +obj-$(CONFIG_INTEL_ISH_CLIENT) += intel-ishtp-clients.o + +ccflags-y += -I$(srctree)/drivers/hid/intel-ish-hid/ishtp diff --git a/drivers/misc/intel-ish-client/intel-ishtp-clients.c b/drivers/misc/intel-ish-client/intel-ishtp-clients.c new file mode 100644 index 0000000..4ca3ab8 --- /dev/null +++ b/drivers/misc/intel-ish-client/intel-ishtp-clients.c @@ -0,0 +1,882 @@ +/* + * ISHTP clients driver + * + * Copyright (c) 2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/capability.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/intel-ishtp-clients.h> +#include <linux/ioctl.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uuid.h> +#include <linux/uaccess.h> + +#include "ishtp-dev.h" +#include "client.h" + +/* + * ISH client misc driver structure + */ +struct ishtp_cl_miscdev { + struct miscdevice cl_miscdev; + struct ishtp_cl_device *cl_device; + struct ishtp_cl *cl; + + /* Wait queue for waiting ISHFW event/message */ + wait_queue_head_t read_wait; + /* Read buffer, will point to available ISHFW message */ + struct ishtp_cl_rb *read_rb; + + /* + * cl member can be freed/changed by ISHFW reset and release() calling, + * so must pay attention to protect cl while try to access it. This + * mutex is used to protect cl member. + */ + struct mutex cl_mutex; + + struct work_struct reset_work; +}; + +/* ISH client GUIDs */ +/* SMHI client UUID: bb579a2e-cc54-4450-b1d0-5e7520dcad25 */ +static const uuid_le ishtp_smhi_guid = + UUID_LE(0xbb579a2e, 0xcc54, 0x4450, + 0xb1, 0xd0, 0x5e, 0x75, 0x20, 0xdc, 0xad, 0x25); + +/* Trace log client UUID: c1cc78b9-b693-4e54-9191-5169cb027c25 */ +static const uuid_le ishtp_trace_guid = + UUID_LE(0xc1cc78b9, 0xb693, 0x4e54, + 0x91, 0x91, 0x51, 0x69, 0xcb, 0x02, 0x7c, 0x25); + +/* Trace config client UUID: 1f050626-d505-4e94-b189-535d7de19cf2 */ +static const uuid_le ishtp_traceconfig_guid = + UUID_LE(0x1f050626, 0xd505, 0x4e94, + 0xb1, 0x89, 0x53, 0x5d, 0x7d, 0xe1, 0x9c, 0xf2); + +/* ISHFW loader client UUID: c804d06a-55bd-4ea7-aded-1e31228c76dc */ +static const uuid_le ishtp_loader_guid = + UUID_LE(0xc804d06a, 0x55bd, 0x4ea7, + 0xad, 0xed, 0x1e, 0x31, 0x22, 0x8c, 0x76, 0xdc); + +static int ishtp_cl_open(struct inode *inode, struct file *file) +{ + struct miscdevice *misc = file->private_data; + struct ishtp_cl *cl; + struct ishtp_device *dev; + struct ishtp_cl_miscdev *ishtp_cl_misc; + int ret; + + /* Non-blocking semantics are not supported */ + if (file->f_flags & O_NONBLOCK) + return -EINVAL; + + ishtp_cl_misc = container_of(misc, + struct ishtp_cl_miscdev, cl_miscdev); + if (!ishtp_cl_misc || !ishtp_cl_misc->cl_device) + return -ENODEV; + + dev = ishtp_cl_misc->cl_device->ishtp_dev; + if (!dev) + return -ENODEV; + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + /* + * Every client only supports one opened instance + * at the sametime. + */ + if (ishtp_cl_misc->cl) { + ret = -EBUSY; + goto out_unlock; + } + + cl = ishtp_cl_allocate(dev); + if (!cl) { + ret = -ENOMEM; + goto out_free; + } + + if (dev->dev_state != ISHTP_DEV_ENABLED) { + ret = -ENODEV; + goto out_free; + } + + ret = ishtp_cl_link(cl, ISHTP_HOST_CLIENT_ID_ANY); + if (ret) + goto out_free; + + ishtp_cl_misc->cl = cl; + + file->private_data = ishtp_cl_misc; + + mutex_unlock(&ishtp_cl_misc->cl_mutex); + + return nonseekable_open(inode, file); + +out_free: + kfree(cl); +out_unlock: + mutex_unlock(&ishtp_cl_misc->cl_mutex); + return ret; +} + +#define WAIT_FOR_SEND_SLICE_MS 100 +#define WAIT_FOR_SEND_COUNT 10 + +static int ishtp_cl_release(struct inode *inode, struct file *file) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc = file->private_data; + struct ishtp_cl *cl; + struct ishtp_cl_rb *rb; + struct ishtp_device *dev; + int try = WAIT_FOR_SEND_COUNT; + int ret; + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + /* Wake up from waiting if anyone wait on it */ + wake_up_interruptible(&ishtp_cl_misc->read_wait); + + cl = ishtp_cl_misc->cl; + dev = cl->dev; + + /* + * May happen if device sent FW reset or was intentionally + * halted by host SW. The client is then invalid. + */ + if ((dev->dev_state == ISHTP_DEV_ENABLED) && + (cl->state == ISHTP_CL_CONNECTED)) { + /* + * Check and wait 1s for message in tx_list to be sent. + */ + do { + if (!ishtp_cl_tx_empty(cl)) + msleep_interruptible(WAIT_FOR_SEND_SLICE_MS); + else + break; + } while (--try); + + cl->state = ISHTP_CL_DISCONNECTING; + ret = ishtp_cl_disconnect(cl); + } + + ishtp_cl_unlink(cl); + ishtp_cl_flush_queues(cl); + /* Disband and free all Tx and Rx client-level rings */ + ishtp_cl_free(cl); + + ishtp_cl_misc->cl = NULL; + + rb = ishtp_cl_misc->read_rb; + if (rb) { + ishtp_cl_io_rb_recycle(rb); + ishtp_cl_misc->read_rb = NULL; + } + + file->private_data = NULL; + + mutex_unlock(&ishtp_cl_misc->cl_mutex); + + return ret; +} + +static ssize_t ishtp_cl_read(struct file *file, char __user *ubuf, + size_t length, loff_t *offset) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc = file->private_data; + struct ishtp_cl *cl; + struct ishtp_cl_rb *rb; + struct ishtp_device *dev; + int ret = 0; + + /* Non-blocking semantics are not supported */ + if (file->f_flags & O_NONBLOCK) + return -EINVAL; + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + cl = ishtp_cl_misc->cl; + + /* + * ISHFW reset will cause cl be freed and re-allocated. + * So must make sure cl is re-allocated successfully. + */ + if (!cl || !cl->dev) { + ret = -ENODEV; + goto out_unlock; + } + + dev = cl->dev; + if (dev->dev_state != ISHTP_DEV_ENABLED) { + ret = -ENODEV; + goto out_unlock; + } + + if (ishtp_cl_misc->read_rb) + goto get_rb; + + rb = ishtp_cl_rx_get_rb(cl); + if (rb) + goto copy_buffer; + + /* + * Release mutex for other operation can be processed parallelly + * during waiting. + */ + mutex_unlock(&ishtp_cl_misc->cl_mutex); + + if (wait_event_interruptible(ishtp_cl_misc->read_wait, + ishtp_cl_misc->read_rb != NULL)) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "Wake up not successful;" + "signal pending = %d signal = %08lX\n", + signal_pending(current), + current->pending.signal.sig[0]); + return -ERESTARTSYS; + } + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + /* + * waitqueue can be woken up in many cases, so must check + * if dev and cl is still available. + */ + if (dev->dev_state != ISHTP_DEV_ENABLED) { + ret = -ENODEV; + goto out_unlock; + } + + cl = ishtp_cl_misc->cl; + if (!cl) { + ret = -ENODEV; + goto out_unlock; + } + + if (cl->state == ISHTP_CL_INITIALIZING || + cl->state == ISHTP_CL_DISCONNECTED || + cl->state == ISHTP_CL_DISCONNECTING) { + ret = -EBUSY; + goto out_unlock; + } + +get_rb: + rb = ishtp_cl_misc->read_rb; + if (!rb) { + ret = -ENODEV; + goto out_unlock; + } + +copy_buffer: + /* Now copy the data to user space */ + if (!length || !ubuf || *offset > rb->buf_idx) { + ret = -EMSGSIZE; + goto out_unlock; + } + + /* + * length is being truncated, however buf_idx may + * point beyond that. + */ + length = min_t(size_t, length, rb->buf_idx - *offset); + + if (copy_to_user(ubuf, rb->buffer.data + *offset, length)) { + ret = -EFAULT; + goto out_unlock; + } + + *offset += length; + if ((unsigned long)*offset < rb->buf_idx) + goto out_unlock; + + ishtp_cl_io_rb_recycle(rb); + ishtp_cl_misc->read_rb = NULL; + *offset = 0; + +out_unlock: + mutex_unlock(&ishtp_cl_misc->cl_mutex); + return ret < 0 ? ret : length; +} + +static ssize_t ishtp_cl_write(struct file *file, const char __user *ubuf, + size_t length, loff_t *offset) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc = file->private_data; + struct ishtp_cl *cl; + void *write_buf; + struct ishtp_device *dev; + int ret; + + /* Non-blocking semantics are not supported */ + if (file->f_flags & O_NONBLOCK) + return -EINVAL; + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + cl = ishtp_cl_misc->cl; + + /* + * ISHFW reset will cause cl be freed and re-allocated. + * So must make sure cl is re-allocated successfully. + */ + if (!cl || !cl->dev) { + ret = -ENODEV; + goto out_unlock; + } + + dev = cl->dev; + + if (dev->dev_state != ISHTP_DEV_ENABLED) { + ret = -ENODEV; + goto out_unlock; + } + + if (cl->state != ISHTP_CL_CONNECTED) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "host client = %d isn't connected to fw client = %d\n", + cl->host_client_id, cl->fw_client_id); + ret = -ENODEV; + goto out_unlock; + } + + if (length <= 0 || length > cl->device->fw_client->props.max_msg_length) { + ret = -EMSGSIZE; + goto out_unlock; + } + + write_buf = memdup_user(ubuf, length); + if (IS_ERR(write_buf)) { + ret = PTR_ERR(write_buf); + goto out_unlock; + } + + ret = ishtp_cl_send(cl, write_buf, length); + + kfree(write_buf); + +out_unlock: + mutex_unlock(&ishtp_cl_misc->cl_mutex); + + return ret < 0 ? ret : length; +} + +static int ishtp_cl_ioctl_connect_client(struct file *file, + struct ishtp_connect_client_data *data) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc = file->private_data; + struct ishtp_device *dev; + struct ishtp_client *client; + struct ishtp_cl_device *cl_device; + struct ishtp_cl *cl = ishtp_cl_misc->cl; + struct ishtp_fw_client *fw_client; + + if (!cl || !cl->dev) + return -ENODEV; + + dev = cl->dev; + + if (dev->dev_state != ISHTP_DEV_ENABLED) + return -ENODEV; + + if (cl->state != ISHTP_CL_INITIALIZING && + cl->state != ISHTP_CL_DISCONNECTED) + return -EBUSY; + + cl_device = ishtp_cl_misc->cl_device; + + if (uuid_le_cmp(data->in_client_uuid, + cl_device->fw_client->props.protocol_name) != 0) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "Required uuid don't match current client uuid\n"); + return -EFAULT; + } + + /* Find the fw client we're trying to connect to */ + fw_client = ishtp_fw_cl_get_client(dev, &data->in_client_uuid); + if (!fw_client) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "Don't find current client uuid\n"); + return -ENOENT; + } + + cl->fw_client_id = fw_client->client_id; + cl->state = ISHTP_CL_CONNECTING; + + /* Prepare the output buffer */ + client = &data->out_client_properties; + client->max_msg_length = fw_client->props.max_msg_length; + client->protocol_version = fw_client->props.protocol_version; + + return ishtp_cl_connect(cl); +} + +static long ishtp_cl_ioctl(struct file *file, unsigned int cmd, + unsigned long data) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc = file->private_data; + struct ishtp_cl *cl; + struct ishtp_device *dev; + struct ishtp_connect_client_data *connect_data; + char fw_stat_buf[20]; + unsigned int ring_size; + int ret = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + cl = ishtp_cl_misc->cl; + + /* + * ISHFW reset will cause cl be freed and re-allocated. + * So must make sure cl is re-allocated successfully. + */ + if (!cl || !cl->dev) { + mutex_unlock(&ishtp_cl_misc->cl_mutex); + return -ENODEV; + } + + dev = cl->dev; + + switch (cmd) { + case IOCTL_ISH_HW_RESET: + ret = ish_hw_reset(dev); + break; + + case IOCTL_ISHTP_SET_RX_FIFO_SIZE: + ring_size = data; + + if (ring_size > CL_MAX_RX_RING_SIZE) { + ret = -EINVAL; + break; + } + + if (cl->state != ISHTP_CL_INITIALIZING) { + ret = -EBUSY; + break; + } + + cl->rx_ring_size = ring_size; + break; + + case IOCTL_ISHTP_SET_TX_FIFO_SIZE: + ring_size = data; + + if (ring_size > CL_MAX_TX_RING_SIZE) { + ret = -EINVAL; + break; + } + + if (cl->state != ISHTP_CL_INITIALIZING) { + ret = -EBUSY; + break; + } + + cl->tx_ring_size = ring_size; + break; + + case IOCTL_ISH_GET_FW_STATUS: + if (!data) { + ret = -ENOMEM; + break; + } + + snprintf(fw_stat_buf, sizeof(fw_stat_buf), + "%08X\n", dev->ops->get_fw_status(dev)); + + if (copy_to_user((char __user *)data, fw_stat_buf, + strlen(fw_stat_buf))) { + ret = -EFAULT; + break; + } + + ret = strlen(fw_stat_buf); + break; + + case IOCTL_ISHTP_CONNECT_CLIENT: + if (dev->dev_state != ISHTP_DEV_ENABLED) { + ret = -ENODEV; + break; + } + + connect_data = memdup_user((char __user *)data, + sizeof(struct ishtp_connect_client_data)); + if (IS_ERR(connect_data)) { + ret = PTR_ERR(connect_data); + break; + } + + ret = ishtp_cl_ioctl_connect_client(file, connect_data); + if (ret) { + kfree(connect_data); + break; + } + + /* If all is ok, copying the data back to user. */ + if (copy_to_user((char __user *)data, connect_data, + sizeof(struct ishtp_connect_client_data))) + ret = -EFAULT; + + kfree(connect_data); + + break; + + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&ishtp_cl_misc->cl_mutex); + + return ret; +} + +/* + * File operations structure will be used for ishtp client misc device. + */ +static const struct file_operations ishtp_cl_fops = { + .owner = THIS_MODULE, + .read = ishtp_cl_read, + .unlocked_ioctl = ishtp_cl_ioctl, + .open = ishtp_cl_open, + .release = ishtp_cl_release, + .write = ishtp_cl_write, + .llseek = no_llseek +}; + +/** + * ishtp_cl_event_cb() - ISHTP client driver event callback + * @cl_device: ISHTP client device instance + * + * This function gets called on related event recevied from ISHFW. + * It will remove event buffer exists on in_process list to related + * client device and wait up client driver to process. + */ +static void ishtp_cl_event_cb(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc; + struct ishtp_cl *cl; + struct ishtp_cl_rb *rb; + + ishtp_cl_misc = ishtp_get_drvdata(cl_device); + if (!ishtp_cl_misc) + return; + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + /* + * If this waitqueue is active, cl_mutex is locked by read(), it's safe + * to access ishtp_cl_misc and cl. + */ + if (waitqueue_active(&ishtp_cl_misc->read_wait)) { + + /* + * If already has read_rb, wake up waitqueue directly. + */ + if (ishtp_cl_misc->read_rb) { + mutex_unlock(&ishtp_cl_misc->cl_mutex); + wake_up_interruptible(&ishtp_cl_misc->read_wait); + return; + } + + cl = ishtp_cl_misc->cl; + + rb = ishtp_cl_rx_get_rb(cl); + if (rb) + ishtp_cl_misc->read_rb = rb; + + wake_up_interruptible(&ishtp_cl_misc->read_wait); + } + + mutex_unlock(&ishtp_cl_misc->cl_mutex); +} + +/** + * ishtp_cl_reset_handler() - ISHTP client driver reset work handler + * @work: work struct + * + * This function gets called on reset workqueue scheduled when ISHFW + * reset happen. It will disconnect and remove current ishtp_cl, then + * create a new ishtp_cl and re-connect again. + */ +static void ishtp_cl_reset_handler(struct work_struct *work) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc; + struct ishtp_device *dev; + struct ishtp_cl_device *cl_device; + struct ishtp_cl *cl; + struct ishtp_fw_client *fw_client; + int ret = 0; + + ishtp_cl_misc = container_of(work, + struct ishtp_cl_miscdev, reset_work); + + dev = ishtp_cl_misc->cl_device->ishtp_dev; + if (!dev) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "This cl_device not link to ishtp_dev\n"); + return; + } + + cl_device = ishtp_cl_misc->cl_device; + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + /* Wake up from waiting if anyone wait on it */ + wake_up_interruptible(&ishtp_cl_misc->read_wait); + + cl = ishtp_cl_misc->cl; + if (cl) { + ishtp_cl_flush_queues(cl); + ishtp_cl_free(cl); + + cl = NULL; + + cl = ishtp_cl_allocate(dev); + if (!cl) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "Allocate ishtp_cl failed\n"); + ret = -ENOMEM; + goto out_unlock; + } + + if (dev->dev_state != ISHTP_DEV_ENABLED) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "Ishtp dev isn't enabled\n"); + ret = -ENODEV; + goto out_free; + } + + ret = ishtp_cl_link(cl, ISHTP_HOST_CLIENT_ID_ANY); + if (ret) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "Can not link to ishtp\n"); + goto out_free; + } + + fw_client = ishtp_fw_cl_get_client(dev, + &cl_device->fw_client->props.protocol_name); + if (!fw_client) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "Don't find related fw client\n"); + ret = -ENOENT; + goto out_free; + } + + cl->fw_client_id = fw_client->client_id; + cl->state = ISHTP_CL_CONNECTING; + + ret = ishtp_cl_connect(cl); + if (ret) { + dev_err(&ishtp_cl_misc->cl_device->dev, + "Connect to fw failed\n"); + goto out_free; + } + + ishtp_cl_misc->cl = cl; + } + + /* After reset, must register event callback again */ + ishtp_register_event_cb(cl_device, ishtp_cl_event_cb); + +out_free: + if (ret) { + ishtp_cl_free(cl); + ishtp_cl_misc->cl = NULL; + + dev_err(&ishtp_cl_misc->cl_device->dev, "Reset failed\n"); + } + +out_unlock: + mutex_unlock(&ishtp_cl_misc->cl_mutex); +} + +/** + * ishtp_cl_probe() - ISHTP client driver probe + * @cl_device: ISHTP client device instance + * + * This function gets called on device create on ISHTP bus + * + * Return: 0 on success, non zero on error + */ +static int ishtp_cl_probe(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc; + int ret; + + if (!cl_device) + return -ENODEV; + + ishtp_cl_misc = kzalloc(sizeof(struct ishtp_cl_miscdev), + GFP_KERNEL); + if (!ishtp_cl_misc) + return -ENOMEM; + + if (uuid_le_cmp(ishtp_smhi_guid, + cl_device->fw_client->props.protocol_name) == 0) { + ishtp_cl_misc->cl_miscdev.name = "ish-smhi"; + } else if (uuid_le_cmp(ishtp_trace_guid, + cl_device->fw_client->props.protocol_name) == 0) { + ishtp_cl_misc->cl_miscdev.name = "ish-trace"; + } else if (uuid_le_cmp(ishtp_traceconfig_guid, + cl_device->fw_client->props.protocol_name) == 0) { + ishtp_cl_misc->cl_miscdev.name = "ish-tracec"; + } else if (uuid_le_cmp(ishtp_loader_guid, + cl_device->fw_client->props.protocol_name) == 0) { + ishtp_cl_misc->cl_miscdev.name = "ish-loader"; + } else { + dev_err(&cl_device->dev, "Not supported client\n"); + ret = -ENODEV; + goto release_mem; + } + + ishtp_cl_misc->cl_miscdev.parent = &cl_device->dev; + ishtp_cl_misc->cl_miscdev.fops = &ishtp_cl_fops; + ishtp_cl_misc->cl_miscdev.minor = MISC_DYNAMIC_MINOR, + + ret = misc_register(&ishtp_cl_misc->cl_miscdev); + if (ret) { + dev_err(&cl_device->dev, "misc device register failed\n"); + goto release_mem; + } + + ishtp_cl_misc->cl_device = cl_device; + + init_waitqueue_head(&ishtp_cl_misc->read_wait); + + ishtp_set_drvdata(cl_device, ishtp_cl_misc); + + ishtp_get_device(cl_device); + + mutex_init(&ishtp_cl_misc->cl_mutex); + + INIT_WORK(&ishtp_cl_misc->reset_work, ishtp_cl_reset_handler); + + /* Register event callback */ + ishtp_register_event_cb(cl_device, ishtp_cl_event_cb); + + return 0; + +release_mem: + kfree(ishtp_cl_misc); + + return ret; +} + +/** + * ishtp_cl_remove() - ISHTP client driver remove + * @cl_device: ISHTP client device instance + * + * This function gets called on device remove on ISHTP bus + * + * Return: 0 + */ +static int ishtp_cl_remove(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc; + struct ishtp_cl *cl; + + ishtp_cl_misc = ishtp_get_drvdata(cl_device); + if (!ishtp_cl_misc) + return -ENODEV; + + if (!ishtp_cl_misc->cl_miscdev.parent) + return -ENODEV; + + /* Wake up from waiting if anyone wait on it */ + wake_up_interruptible(&ishtp_cl_misc->read_wait); + + mutex_lock(&ishtp_cl_misc->cl_mutex); + + cl = ishtp_cl_misc->cl; + if (cl) { + cl->state = ISHTP_CL_DISCONNECTING; + ishtp_cl_disconnect(cl); + ishtp_cl_unlink(cl); + ishtp_cl_flush_queues(cl); + ishtp_cl_free(cl); + ishtp_cl_misc->cl = NULL; + } + + mutex_unlock(&ishtp_cl_misc->cl_mutex); + + mutex_destroy(&ishtp_cl_misc->cl_mutex); + + misc_deregister(&ishtp_cl_misc->cl_miscdev); + + ishtp_put_device(cl_device); + + kfree(ishtp_cl_misc); + + return 0; +} + +/** + * ishtp_cl_reset() - ISHTP client driver reset + * @cl_device: ISHTP client device instance + * + * This function gets called on device reset on ISHTP bus. + * If client is connected, needs to disconnect client and + * reconnect again. + * + * Return: 0 + */ +static int ishtp_cl_reset(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl_miscdev *ishtp_cl_misc; + + ishtp_cl_misc = ishtp_get_drvdata(cl_device); + if (!ishtp_cl_misc) { + dev_err(&cl_device->dev, "Client driver not ready yet\n"); + return -ENODEV; + } + + schedule_work(&ishtp_cl_misc->reset_work); + + return 0; +} + +static struct ishtp_cl_driver ishtp_cl_driver = { + .name = "ishtp-client", + .probe = ishtp_cl_probe, + .remove = ishtp_cl_remove, + .reset = ishtp_cl_reset, +}; + +static int __init ishtp_client_init(void) +{ + /* Register ISHTP client device driver with ISHTP Bus */ + return ishtp_cl_driver_register(&ishtp_cl_driver); +} + +static void __exit ishtp_client_exit(void) +{ + ishtp_cl_driver_unregister(&ishtp_cl_driver); +} + +/* To make sure ISHTP bus driver loaded first */ +late_initcall(ishtp_client_init); +module_exit(ishtp_client_exit); + +MODULE_DESCRIPTION("ISH ISHTP client driver"); +MODULE_AUTHOR("Even Xu <even.xu@xxxxxxxxx>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ishtp:*"); diff --git a/include/uapi/linux/intel-ishtp-clients.h b/include/uapi/linux/intel-ishtp-clients.h new file mode 100644 index 0000000..792500a --- /dev/null +++ b/include/uapi/linux/intel-ishtp-clients.h @@ -0,0 +1,73 @@ +/* + * Intel ISHTP Clients Interface Header + * + * Copyright (c) 2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _INTEL_ISHTP_CLIENTS_H +#define _INTEL_ISHTP_CLIENTS_H + +#include <linux/ioctl.h> +#include <linux/miscdevice.h> +#include <linux/mutex.h> +#include <linux/types.h> +#include <linux/uuid.h> + +/* + * This IOCTL is used to associate the current file descriptor with a + * FW Client (given by UUID). This opens a communication channel + * between a host client and a FW client. From this point every read and write + * will communicate with the associated FW client. + * Only in close() (file_operation release()) the communication between + * the clients is disconnected + * + * The IOCTL argument is a struct with a union that contains + * the input parameter and the output parameter for this IOCTL. + * + * The input parameter is UUID of the FW Client. + * The output parameter is the properties of the FW client + * (FW protocol version and max message size). + * + */ +#define IOCTL_ISHTP_CONNECT_CLIENT _IOWR('H', 0x81, \ + struct ishtp_connect_client_data) + +/* Configuration: set number of Rx/Tx buffers. Must be used before connection */ +#define IOCTL_ISHTP_SET_RX_FIFO_SIZE _IOWR('H', 0x82, long) +#define IOCTL_ISHTP_SET_TX_FIFO_SIZE _IOWR('H', 0x83, long) + +/* Get FW status */ +#define IOCTL_ISH_GET_FW_STATUS _IO('H', 0x84) + +#define IOCTL_ISH_HW_RESET _IO('H', 0x85) + +/* + * Intel ISHTP client information struct + */ +struct ishtp_client { + __u32 max_msg_length; + __u8 protocol_version; + __u8 reserved[3]; +}; + +/* + * IOCTL Connect client data structure + */ +struct ishtp_connect_client_data { + union { + uuid_le in_client_uuid; + struct ishtp_client out_client_properties; + }; +}; + +#endif /* _INTEL_ISHTP_CLIENTS_H */ -- 2.7.4��.n��������+%������w��{.n�����{��)��^n�r������&��z�ޗ�zf���h���~����������_��+v���)ߣ�