diff options
Diffstat (limited to 'drivers/misc/mei')
-rw-r--r-- | drivers/misc/mei/Kconfig | 5 | ||||
-rw-r--r-- | drivers/misc/mei/Makefile | 9 | ||||
-rw-r--r-- | drivers/misc/mei/amthif.c | 27 | ||||
-rw-r--r-- | drivers/misc/mei/bus.c | 528 | ||||
-rw-r--r-- | drivers/misc/mei/client.c | 140 | ||||
-rw-r--r-- | drivers/misc/mei/client.h | 7 | ||||
-rw-r--r-- | drivers/misc/mei/debugfs.c | 143 | ||||
-rw-r--r-- | drivers/misc/mei/hbm.c | 91 | ||||
-rw-r--r-- | drivers/misc/mei/hbm.h | 25 | ||||
-rw-r--r-- | drivers/misc/mei/hw-me.c | 132 | ||||
-rw-r--r-- | drivers/misc/mei/hw-me.h | 6 | ||||
-rw-r--r-- | drivers/misc/mei/init.c | 70 | ||||
-rw-r--r-- | drivers/misc/mei/interrupt.c | 246 | ||||
-rw-r--r-- | drivers/misc/mei/main.c | 127 | ||||
-rw-r--r-- | drivers/misc/mei/mei_dev.h | 166 | ||||
-rw-r--r-- | drivers/misc/mei/nfc.c | 554 | ||||
-rw-r--r-- | drivers/misc/mei/pci-me.c | 47 | ||||
-rw-r--r-- | drivers/misc/mei/wd.c | 4 |
18 files changed, 1931 insertions, 396 deletions
diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index d21b4d006a55..c76fa31e9bf6 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -10,10 +10,9 @@ config INTEL_MEI <http://software.intel.com/en-us/manageability/> config INTEL_MEI_ME - bool "ME Enabled Intel Chipsets" - depends on INTEL_MEI + tristate "ME Enabled Intel Chipsets" + select INTEL_MEI depends on X86 && PCI && WATCHDOG_CORE - default y help MEI support for ME Enabled Intel chipsets. diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 040af6c7b147..08698a466268 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -10,5 +10,10 @@ mei-objs += client.o mei-objs += main.o mei-objs += amthif.o mei-objs += wd.o -mei-$(CONFIG_INTEL_MEI_ME) += pci-me.o -mei-$(CONFIG_INTEL_MEI_ME) += hw-me.o +mei-objs += bus.o +mei-objs += nfc.o +mei-$(CONFIG_DEBUG_FS) += debugfs.o + +obj-$(CONFIG_INTEL_MEI_ME) += mei-me.o +mei-me-objs := pci-me.o +mei-me-objs += hw-me.o diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index c86d7e3839a4..b3e50984d2c8 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -60,7 +60,7 @@ void mei_amthif_reset_params(struct mei_device *dev) } /** - * mei_amthif_host_init_ - mei initialization amthif client. + * mei_amthif_host_init - mei initialization amthif client. * * @dev: the device structure * @@ -433,7 +433,7 @@ unsigned int mei_amthif_poll(struct mei_device *dev, /** - * mei_amthif_irq_process_completed - processes completed iamthif operation. + * mei_amthif_irq_write_completed - processes completed iamthif operation. * * @dev: the device structure. * @slots: free slots. @@ -449,7 +449,7 @@ int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, struct mei_msg_hdr mei_hdr; struct mei_cl *cl = cb->cl; size_t len = dev->iamthif_msg_buf_size - dev->iamthif_msg_buf_index; - size_t msg_slots = mei_data2slots(len); + u32 msg_slots = mei_data2slots(len); mei_hdr.host_addr = cl->host_client_id; mei_hdr.me_addr = cl->me_client_id; @@ -505,14 +505,15 @@ int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, * mei_amthif_irq_read_message - read routine after ISR to * handle the read amthif message * - * @complete_list: An instance of our list structure * @dev: the device structure * @mei_hdr: header of amthif message + * @complete_list: An instance of our list structure * * returns 0 on success, <0 on failure. */ -int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list, - struct mei_device *dev, struct mei_msg_hdr *mei_hdr) +int mei_amthif_irq_read_msg(struct mei_device *dev, + struct mei_msg_hdr *mei_hdr, + struct mei_cl_cb *complete_list) { struct mei_cl_cb *cb; unsigned char *buffer; @@ -530,8 +531,7 @@ int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list, if (!mei_hdr->msg_complete) return 0; - dev_dbg(&dev->pdev->dev, - "amthif_message_buffer_index =%d\n", + dev_dbg(&dev->pdev->dev, "amthif_message_buffer_index =%d\n", mei_hdr->length); dev_dbg(&dev->pdev->dev, "completed amthif read.\n "); @@ -566,12 +566,13 @@ int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list, */ int mei_amthif_irq_read(struct mei_device *dev, s32 *slots) { + u32 msg_slots = mei_data2slots(sizeof(struct hbm_flow_control)); - if (((*slots) * sizeof(u32)) < (sizeof(struct mei_msg_hdr) - + sizeof(struct hbm_flow_control))) { + if (*slots < msg_slots) return -EMSGSIZE; - } - *slots -= mei_data2slots(sizeof(struct hbm_flow_control)); + + *slots -= msg_slots; + if (mei_hbm_cl_flow_control_req(dev, &dev->iamthif_cl)) { dev_dbg(&dev->pdev->dev, "iamthif flow control failed\n"); return -EIO; @@ -703,7 +704,7 @@ static bool mei_clear_lists(struct mei_device *dev, struct file *file) /** * mei_amthif_release - the release function * -* @inode: pointer to inode structure +* @dev: device structure * @file: pointer to file structure * * returns 0 on success, <0 on error diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c new file mode 100644 index 000000000000..1e935eacaa7f --- /dev/null +++ b/drivers/misc/mei/bus.c @@ -0,0 +1,528 @@ +/* + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2012-2013, 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/module.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/mei_cl_bus.h> + +#include "mei_dev.h" +#include "hw-me.h" +#include "client.h" + +#define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver) +#define to_mei_cl_device(d) container_of(d, struct mei_cl_device, dev) + +static int mei_cl_device_match(struct device *dev, struct device_driver *drv) +{ + struct mei_cl_device *device = to_mei_cl_device(dev); + struct mei_cl_driver *driver = to_mei_cl_driver(drv); + const struct mei_cl_device_id *id; + + if (!device) + return 0; + + if (!driver || !driver->id_table) + return 0; + + id = driver->id_table; + + while (id->name[0]) { + if (!strcmp(dev_name(dev), id->name)) + return 1; + + id++; + } + + return 0; +} + +static int mei_cl_device_probe(struct device *dev) +{ + struct mei_cl_device *device = to_mei_cl_device(dev); + struct mei_cl_driver *driver; + struct mei_cl_device_id id; + + if (!device) + return 0; + + driver = to_mei_cl_driver(dev->driver); + if (!driver || !driver->probe) + return -ENODEV; + + dev_dbg(dev, "Device probe\n"); + + strncpy(id.name, dev_name(dev), MEI_CL_NAME_SIZE); + + return driver->probe(device, &id); +} + +static int mei_cl_device_remove(struct device *dev) +{ + struct mei_cl_device *device = to_mei_cl_device(dev); + struct mei_cl_driver *driver; + + if (!device || !dev->driver) + return 0; + + if (device->event_cb) { + device->event_cb = NULL; + cancel_work_sync(&device->event_work); + } + + driver = to_mei_cl_driver(dev->driver); + if (!driver->remove) { + dev->driver = NULL; + + return 0; + } + + return driver->remove(device); +} + +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + int len; + + len = snprintf(buf, PAGE_SIZE, "mei:%s\n", dev_name(dev)); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} + +static struct device_attribute mei_cl_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int mei_cl_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + if (add_uevent_var(env, "MODALIAS=mei:%s", dev_name(dev))) + return -ENOMEM; + + return 0; +} + +static struct bus_type mei_cl_bus_type = { + .name = "mei", + .dev_attrs = mei_cl_dev_attrs, + .match = mei_cl_device_match, + .probe = mei_cl_device_probe, + .remove = mei_cl_device_remove, + .uevent = mei_cl_uevent, +}; + +static void mei_cl_dev_release(struct device *dev) +{ + kfree(to_mei_cl_device(dev)); +} + +static struct device_type mei_cl_device_type = { + .release = mei_cl_dev_release, +}; + +static struct mei_cl *mei_bus_find_mei_cl_by_uuid(struct mei_device *dev, + uuid_le uuid) +{ + struct mei_cl *cl, *next; + + list_for_each_entry_safe(cl, next, &dev->device_list, device_link) { + if (!uuid_le_cmp(uuid, cl->device_uuid)) + return cl; + } + + return NULL; +} +struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, + uuid_le uuid, char *name, + struct mei_cl_ops *ops) +{ + struct mei_cl_device *device; + struct mei_cl *cl; + int status; + + cl = mei_bus_find_mei_cl_by_uuid(dev, uuid); + if (cl == NULL) + return NULL; + + device = kzalloc(sizeof(struct mei_cl_device), GFP_KERNEL); + if (!device) + return NULL; + + device->cl = cl; + device->ops = ops; + + device->dev.parent = &dev->pdev->dev; + device->dev.bus = &mei_cl_bus_type; + device->dev.type = &mei_cl_device_type; + + dev_set_name(&device->dev, "%s", name); + + status = device_register(&device->dev); + if (status) { + dev_err(&dev->pdev->dev, "Failed to register MEI device\n"); + kfree(device); + return NULL; + } + + cl->device = device; + + dev_dbg(&device->dev, "client %s registered\n", name); + + return device; +} +EXPORT_SYMBOL_GPL(mei_cl_add_device); + +void mei_cl_remove_device(struct mei_cl_device *device) +{ + device_unregister(&device->dev); +} +EXPORT_SYMBOL_GPL(mei_cl_remove_device); + +int __mei_cl_driver_register(struct mei_cl_driver *driver, struct module *owner) +{ + int err; + + driver->driver.name = driver->name; + driver->driver.owner = owner; + driver->driver.bus = &mei_cl_bus_type; + + err = driver_register(&driver->driver); + if (err) + return err; + + pr_debug("mei: driver [%s] registered\n", driver->driver.name); + + return 0; +} +EXPORT_SYMBOL_GPL(__mei_cl_driver_register); + +void mei_cl_driver_unregister(struct mei_cl_driver *driver) +{ + driver_unregister(&driver->driver); + + pr_debug("mei: driver [%s] unregistered\n", driver->driver.name); +} +EXPORT_SYMBOL_GPL(mei_cl_driver_unregister); + +static int ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, + bool blocking) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + int id; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + if (cl->state != MEI_FILE_CONNECTED) + return -ENODEV; + + /* Check if we have an ME client device */ + id = mei_me_cl_by_id(dev, cl->me_client_id); + if (id < 0) + return -ENODEV; + + if (length > dev->me_clients[id].props.max_msg_length) + return -EINVAL; + + cb = mei_io_cb_init(cl, NULL); + if (!cb) + return -ENOMEM; + + rets = mei_io_cb_alloc_req_buf(cb, length); + if (rets < 0) { + mei_io_cb_free(cb); + return rets; + } + + memcpy(cb->request_buffer.data, buf, length); + + mutex_lock(&dev->device_lock); + + rets = mei_cl_write(cl, cb, blocking); + + mutex_unlock(&dev->device_lock); + if (rets < 0) + mei_io_cb_free(cb); + + return rets; +} + +int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + size_t r_length; + int err; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (!cl->read_cb) { + err = mei_cl_read_start(cl, length); + if (err < 0) { + mutex_unlock(&dev->device_lock); + return err; + } + } + + if (cl->reading_state != MEI_READ_COMPLETE && + !waitqueue_active(&cl->rx_wait)) { + mutex_unlock(&dev->device_lock); + + if (wait_event_interruptible(cl->rx_wait, + (MEI_READ_COMPLETE == cl->reading_state))) { + if (signal_pending(current)) + return -EINTR; + return -ERESTARTSYS; + } + + mutex_lock(&dev->device_lock); + } + + cb = cl->read_cb; + + if (cl->reading_state != MEI_READ_COMPLETE) { + r_length = 0; + goto out; + } + + r_length = min_t(size_t, length, cb->buf_idx); + + memcpy(buf, cb->response_buffer.data, r_length); + + mei_io_cb_free(cb); + cl->reading_state = MEI_IDLE; + cl->read_cb = NULL; + +out: + mutex_unlock(&dev->device_lock); + + return r_length; +} + +inline int __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length) +{ + return ___mei_cl_send(cl, buf, length, 0); +} + +inline int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length) +{ + return ___mei_cl_send(cl, buf, length, 1); +} + +int mei_cl_send(struct mei_cl_device *device, u8 *buf, size_t length) +{ + struct mei_cl *cl = device->cl; + + if (cl == NULL) + return -ENODEV; + + if (device->ops && device->ops->send) + return device->ops->send(device, buf, length); + + return __mei_cl_send(cl, buf, length); +} +EXPORT_SYMBOL_GPL(mei_cl_send); + +int mei_cl_recv(struct mei_cl_device *device, u8 *buf, size_t length) +{ + struct mei_cl *cl = device->cl; + + if (cl == NULL) + return -ENODEV; + + if (device->ops && device->ops->recv) + return device->ops->recv(device, buf, length); + + return __mei_cl_recv(cl, buf, length); +} +EXPORT_SYMBOL_GPL(mei_cl_recv); + +static void mei_bus_event_work(struct work_struct *work) +{ + struct mei_cl_device *device; + + device = container_of(work, struct mei_cl_device, event_work); + + if (device->event_cb) + device->event_cb(device, device->events, device->event_context); + + device->events = 0; + + /* Prepare for the next read */ + mei_cl_read_start(device->cl, 0); +} + +int mei_cl_register_event_cb(struct mei_cl_device *device, + mei_cl_event_cb_t event_cb, void *context) +{ + if (device->event_cb) + return -EALREADY; + + device->events = 0; + device->event_cb = event_cb; + device->event_context = context; + INIT_WORK(&device->event_work, mei_bus_event_work); + + mei_cl_read_start(device->cl, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(mei_cl_register_event_cb); + +void *mei_cl_get_drvdata(const struct mei_cl_device *device) +{ + return dev_get_drvdata(&device->dev); +} +EXPORT_SYMBOL_GPL(mei_cl_get_drvdata); + +void mei_cl_set_drvdata(struct mei_cl_device *device, void *data) +{ + dev_set_drvdata(&device->dev, data); +} +EXPORT_SYMBOL_GPL(mei_cl_set_drvdata); + +int mei_cl_enable_device(struct mei_cl_device *device) +{ + int err; + struct mei_device *dev; + struct mei_cl *cl = device->cl; + + if (cl == NULL) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + cl->state = MEI_FILE_CONNECTING; + + err = mei_cl_connect(cl, NULL); + if (err < 0) { + mutex_unlock(&dev->device_lock); + dev_err(&dev->pdev->dev, "Could not connect to the ME client"); + + return err; + } + + mutex_unlock(&dev->device_lock); + + if (device->event_cb && !cl->read_cb) + mei_cl_read_start(device->cl, 0); + + if (!device->ops || !device->ops->enable) + return 0; + + return device->ops->enable(device); +} +EXPORT_SYMBOL_GPL(mei_cl_enable_device); + +int mei_cl_disable_device(struct mei_cl_device *device) +{ + int err; + struct mei_device *dev; + struct mei_cl *cl = device->cl; + + if (cl == NULL) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (cl->state != MEI_FILE_CONNECTED) { + mutex_unlock(&dev->device_lock); + dev_err(&dev->pdev->dev, "Already disconnected"); + + return 0; + } + + cl->state = MEI_FILE_DISCONNECTING; + + err = mei_cl_disconnect(cl); + if (err < 0) { + mutex_unlock(&dev->device_lock); + dev_err(&dev->pdev->dev, + "Could not disconnect from the ME client"); + + return err; + } + + /* Flush queues and remove any pending read */ + mei_cl_flush_queues(cl); + + if (cl->read_cb) { + struct mei_cl_cb *cb = NULL; + + cb = mei_cl_find_read_cb(cl); + /* Remove entry from read list */ + if (cb) + list_del(&cb->list); + + cb = cl->read_cb; + cl->read_cb = NULL; + + if (cb) { + mei_io_cb_free(cb); + cb = NULL; + } + } + + mutex_unlock(&dev->device_lock); + + if (!device->ops || !device->ops->disable) + return 0; + + return device->ops->disable(device); +} +EXPORT_SYMBOL_GPL(mei_cl_disable_device); + +void mei_cl_bus_rx_event(struct mei_cl *cl) +{ + struct mei_cl_device *device = cl->device; + + if (!device || !device->event_cb) + return; + + set_bit(MEI_CL_EVENT_RX, &device->events); + + schedule_work(&device->event_work); +} + +int __init mei_cl_bus_init(void) +{ + return bus_register(&mei_cl_bus_type); +} + +void __exit mei_cl_bus_exit(void) +{ + bus_unregister(&mei_cl_bus_type); +} diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 1569afe935de..e310ca6ed1a3 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -109,7 +109,7 @@ void mei_io_cb_free(struct mei_cl_cb *cb) * mei_io_cb_init - allocate and initialize io callback * * @cl - mei client - * @file: pointer to file structure + * @fp: pointer to file structure * * returns mei_cl_cb pointer or NULL; */ @@ -132,8 +132,8 @@ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) /** * mei_io_cb_alloc_req_buf - allocate request buffer * - * @cb - io callback structure - * @size: size of the buffer + * @cb: io callback structure + * @length: size of the buffer * * returns 0 on success * -EINVAL if cb is NULL @@ -154,10 +154,10 @@ int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) return 0; } /** - * mei_io_cb_alloc_req_buf - allocate respose buffer + * mei_io_cb_alloc_resp_buf - allocate respose buffer * - * @cb - io callback structure - * @size: size of the buffer + * @cb: io callback structure + * @length: size of the buffer * * returns 0 on success * -EINVAL if cb is NULL @@ -183,7 +183,6 @@ int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) /** * mei_cl_flush_queues - flushes queue lists belonging to cl. * - * @dev: the device structure * @cl: host client */ int mei_cl_flush_queues(struct mei_cl *cl) @@ -216,6 +215,7 @@ void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) init_waitqueue_head(&cl->rx_wait); init_waitqueue_head(&cl->tx_wait); INIT_LIST_HEAD(&cl->link); + INIT_LIST_HEAD(&cl->device_link); cl->reading_state = MEI_IDLE; cl->writing_state = MEI_IDLE; cl->dev = dev; @@ -243,7 +243,8 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) /** * mei_cl_find_read_cb - find this cl's callback in the read list * - * @dev: device structure + * @cl: host client + * * returns cb on success, NULL on error */ struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) @@ -262,6 +263,7 @@ struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) * * @cl - host client * @id - fixed host id or -1 for genereting one + * * returns 0 on success * -EINVAL on incorrect values * -ENONET if client not found @@ -301,7 +303,7 @@ int mei_cl_link(struct mei_cl *cl, int id) /** * mei_cl_unlink - remove me_cl from the list * - * @dev: the device structure + * @cl: host client */ int mei_cl_unlink(struct mei_cl *cl) { @@ -357,6 +359,9 @@ void mei_host_client_init(struct work_struct *work) mei_amthif_host_init(dev); else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid)) mei_wd_host_init(dev); + else if (!uuid_le_cmp(client_props->protocol_name, mei_nfc_guid)) + mei_nfc_host_init(dev); + } dev->dev_state = MEI_DEV_ENABLED; @@ -534,7 +539,6 @@ out: /** * mei_cl_flow_ctrl_creds - checks flow_control credits for cl. * - * @dev: the device structure * @cl: private data of the file object * * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. @@ -575,8 +579,8 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl) /** * mei_cl_flow_ctrl_reduce - reduces flow_control. * - * @dev: the device structure * @cl: private data of the file object + * * @returns * 0 on success * -ENOENT when me client is not found @@ -614,13 +618,13 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) } /** - * mei_cl_start_read - the start read client message function. + * mei_cl_read_start - the start read client message function. * * @cl: host client * * returns 0 on success, <0 on failure. */ -int mei_cl_read_start(struct mei_cl *cl) +int mei_cl_read_start(struct mei_cl *cl, size_t length) { struct mei_device *dev; struct mei_cl_cb *cb; @@ -653,8 +657,9 @@ int mei_cl_read_start(struct mei_cl *cl) if (!cb) return -ENOMEM; - rets = mei_io_cb_alloc_resp_buf(cb, - dev->me_clients[i].props.max_msg_length); + /* always allocate at least client max message */ + length = max_t(size_t, length, dev->me_clients[i].props.max_msg_length); + rets = mei_io_cb_alloc_resp_buf(cb, length); if (rets) goto err; @@ -677,6 +682,111 @@ err: } /** + * mei_cl_write - submit a write cb to mei device + assumes device_lock is locked + * + * @cl: host client + * @cl: write callback with filled data + * + * returns numbe of bytes sent on success, <0 on failure. + */ +int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) +{ + struct mei_device *dev; + struct mei_msg_data *buf; + struct mei_msg_hdr mei_hdr; + int rets; + + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + if (WARN_ON(!cb)) + return -EINVAL; + + dev = cl->dev; + + + buf = &cb->request_buffer; + + dev_dbg(&dev->pdev->dev, "mei_cl_write %d\n", buf->size); + + + cb->fop_type = MEI_FOP_WRITE; + + rets = mei_cl_flow_ctrl_creds(cl); + if (rets < 0) + goto err; + + /* Host buffer is not ready, we queue the request */ + if (rets == 0 || !dev->hbuf_is_ready) { + cb->buf_idx = 0; + /* unseting complete will enqueue the cb for write */ + mei_hdr.msg_complete = 0; + cl->writing_state = MEI_WRITING; + rets = buf->size; + goto out; + } + + dev->hbuf_is_ready = false; + + /* Check for a maximum length */ + if (buf->size > mei_hbuf_max_len(dev)) { + mei_hdr.length = mei_hbuf_max_len(dev); + mei_hdr.msg_complete = 0; + } else { + mei_hdr.length = buf->size; + mei_hdr.msg_complete = 1; + } + + mei_hdr.host_addr = cl->host_client_id; + mei_hdr.me_addr = cl->me_client_id; + mei_hdr.reserved = 0; + + dev_dbg(&dev->pdev->dev, "write " MEI_HDR_FMT "\n", + MEI_HDR_PRM(&mei_hdr)); + + + if (mei_write_message(dev, &mei_hdr, buf->data)) { + rets = -EIO; + goto err; + } + + cl->writing_state = MEI_WRITING; + cb->buf_idx = mei_hdr.length; + + rets = buf->size; +out: + if (mei_hdr.msg_complete) { + if (mei_cl_flow_ctrl_reduce(cl)) { + rets = -ENODEV; + goto err; + } + list_add_tail(&cb->list, &dev->write_waiting_list.list); + } else { + list_add_tail(&cb->list, &dev->write_list.list); + } + + + if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { + + mutex_unlock(&dev->device_lock); + if (wait_event_interruptible(cl->tx_wait, + cl->writing_state == MEI_WRITE_COMPLETE)) { + if (signal_pending(current)) + rets = -EINTR; + else + rets = -ERESTARTSYS; + } + mutex_lock(&dev->device_lock); + } +err: + return rets; +} + + + +/** * mei_cl_all_disconnect - disconnect forcefully all connected clients * * @dev - mei device diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 214b2397ec3e..cfdb144526aa 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -86,17 +86,16 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl); */ bool mei_cl_is_other_connecting(struct mei_cl *cl); int mei_cl_disconnect(struct mei_cl *cl); - -int mei_cl_read_start(struct mei_cl *cl); - int mei_cl_connect(struct mei_cl *cl, struct file *file); +int mei_cl_read_start(struct mei_cl *cl, size_t length); +int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking); void mei_host_client_init(struct work_struct *work); + void mei_cl_all_disconnect(struct mei_device *dev); void mei_cl_all_read_wakeup(struct mei_device *dev); void mei_cl_all_write_clear(struct mei_device *dev); - #endif /* _MEI_CLIENT_H_ */ diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c new file mode 100644 index 000000000000..e3870f22d238 --- /dev/null +++ b/drivers/misc/mei/debugfs.c @@ -0,0 +1,143 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2012-2013, 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/slab.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/pci.h> + +#include <linux/mei.h> + +#include "mei_dev.h" +#include "hw.h" + +static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct mei_device *dev = fp->private_data; + struct mei_me_client *cl; + const size_t bufsz = 1024; + char *buf = kzalloc(bufsz, GFP_KERNEL); + int i; + int pos = 0; + int ret; + + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, + " |id|addr| UUID |con|msg len|\n"); + + mutex_lock(&dev->device_lock); + + /* if the driver is not enabled the list won't b consitent */ + if (dev->dev_state != MEI_DEV_ENABLED) + goto out; + + for (i = 0; i < dev->me_clients_num; i++) { + cl = &dev->me_clients[i]; + + /* skip me clients that cannot be connected */ + if (cl->props.max_number_of_connections == 0) + continue; + + pos += scnprintf(buf + pos, bufsz - pos, + "%2d|%2d|%4d|%pUl|%3d|%7d|\n", + i, cl->client_id, + cl->props.fixed_address, + &cl->props.protocol_name, + cl->props.max_number_of_connections, + cl->props.max_msg_length); + } +out: + mutex_unlock(&dev->device_lock); + ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, pos); + kfree(buf); + return ret; +} + +static const struct file_operations mei_dbgfs_fops_meclients = { + .open = simple_open, + .read = mei_dbgfs_read_meclients, + .llseek = generic_file_llseek, +}; + +static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct mei_device *dev = fp->private_data; + const size_t bufsz = 1024; + char *buf = kzalloc(bufsz, GFP_KERNEL); + int pos = 0; + int ret; + + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", + mei_dev_state_str(dev->dev_state)); + ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, pos); + kfree(buf); + return ret; +} +static const struct file_operations mei_dbgfs_fops_devstate = { + .open = simple_open, + .read = mei_dbgfs_read_devstate, + .llseek = generic_file_llseek, +}; + +/** + * mei_dbgfs_deregister - Remove the debugfs files and directories + * @mei - pointer to mei device private dat + */ +void mei_dbgfs_deregister(struct mei_device *dev) +{ + if (!dev->dbgfs_dir) + return; + debugfs_remove_recursive(dev->dbgfs_dir); + dev->dbgfs_dir = NULL; +} + +/** + * Add the debugfs files + * + */ +int mei_dbgfs_register(struct mei_device *dev, const char *name) +{ + struct dentry *dir, *f; + dir = debugfs_create_dir(name, NULL); + if (!dir) + return -ENOMEM; + + f = debugfs_create_file("meclients", S_IRUSR, dir, + dev, &mei_dbgfs_fops_meclients); + if (!f) { + dev_err(&dev->pdev->dev, "meclients: registration failed\n"); + goto err; + } + f = debugfs_create_file("devstate", S_IRUSR, dir, + dev, &mei_dbgfs_fops_devstate); + if (!f) { + dev_err(&dev->pdev->dev, "devstate: registration failed\n"); + goto err; + } + dev->dbgfs_dir = dir; + return 0; +err: + mei_dbgfs_deregister(dev); + return -ENODEV; +} + diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index fb9e63ba3bb1..6916045166eb 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -52,7 +52,7 @@ static void mei_hbm_me_cl_allocate(struct mei_device *dev) sizeof(struct mei_me_client), GFP_KERNEL); if (!clients) { dev_err(&dev->pdev->dev, "memory allocation for ME clients failed.\n"); - dev->dev_state = MEI_DEV_RESETING; + dev->dev_state = MEI_DEV_RESETTING; mei_reset(dev, 1); return; } @@ -62,6 +62,7 @@ static void mei_hbm_me_cl_allocate(struct mei_device *dev) /** * mei_hbm_cl_hdr - construct client hbm header + * * @cl: - client * @hbm_cmd: host bus message command * @buf: buffer for cl header @@ -123,12 +124,33 @@ static bool is_treat_specially_client(struct mei_cl *cl, return false; } +int mei_hbm_start_wait(struct mei_device *dev) +{ + int ret; + if (dev->hbm_state > MEI_HBM_START) + return 0; + + mutex_unlock(&dev->device_lock); + ret = wait_event_interruptible_timeout(dev->wait_recvd_msg, + dev->hbm_state == MEI_HBM_IDLE || + dev->hbm_state > MEI_HBM_START, + mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT)); + mutex_lock(&dev->device_lock); + + if (ret <= 0 && (dev->hbm_state <= MEI_HBM_START)) { + dev->hbm_state = MEI_HBM_IDLE; + dev_err(&dev->pdev->dev, "wating for mei start failed\n"); + return -ETIMEDOUT; + } + return 0; +} + /** * mei_hbm_start_req - sends start request message. * * @dev: the device structure */ -void mei_hbm_start_req(struct mei_device *dev) +int mei_hbm_start_req(struct mei_device *dev) { struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; struct hbm_host_version_request *start_req; @@ -143,18 +165,19 @@ void mei_hbm_start_req(struct mei_device *dev) start_req->host_version.major_version = HBM_MAJOR_VERSION; start_req->host_version.minor_version = HBM_MINOR_VERSION; - dev->recvd_msg = false; + dev->hbm_state = MEI_HBM_IDLE; if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { - dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n"); - dev->dev_state = MEI_DEV_RESETING; + dev_err(&dev->pdev->dev, "version message writet failed\n"); + dev->dev_state = MEI_DEV_RESETTING; mei_reset(dev, 1); + return -ENODEV; } - dev->init_clients_state = MEI_START_MESSAGE; + dev->hbm_state = MEI_HBM_START; dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; - return ; + return 0; } -/** +/* * mei_hbm_enum_clients_req - sends enumeration client request message. * * @dev: the device structure @@ -174,17 +197,17 @@ static void mei_hbm_enum_clients_req(struct mei_device *dev) enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { - dev->dev_state = MEI_DEV_RESETING; - dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); + dev->dev_state = MEI_DEV_RESETTING; + dev_err(&dev->pdev->dev, "enumeration request write failed.\n"); mei_reset(dev, 1); } - dev->init_clients_state = MEI_ENUM_CLIENTS_MESSAGE; + dev->hbm_state = MEI_HBM_ENUM_CLIENTS; dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; return; } /** - * mei_hbm_prop_requsest - request property for a single client + * mei_hbm_prop_req - request property for a single client * * @dev: the device structure * @@ -208,6 +231,7 @@ static int mei_hbm_prop_req(struct mei_device *dev) /* We got all client properties */ if (next_client_index == MEI_CLIENTS_MAX) { + dev->hbm_state = MEI_HBM_STARTED; schedule_work(&dev->init_work); return 0; @@ -226,8 +250,8 @@ static int mei_hbm_prop_req(struct mei_device *dev) prop_req->address = next_client_index; if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { - dev->dev_state = MEI_DEV_RESETING; - dev_err(&dev->pdev->dev, "Properties request command failed\n"); + dev->dev_state = MEI_DEV_RESETTING; + dev_err(&dev->pdev->dev, "properties request write failed\n"); mei_reset(dev, 1); return -EIO; @@ -283,9 +307,9 @@ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) } /** - * add_single_flow_creds - adds single buffer credentials. + * mei_hbm_add_single_flow_creds - adds single buffer credentials. * - * @file: private data ot the file object. + * @dev: the device structure * @flow: flow control. */ static void mei_hbm_add_single_flow_creds(struct mei_device *dev, @@ -477,7 +501,7 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, /** - * mei_client_disconnect_request - disconnect request initiated by me + * mei_hbm_fw_disconnect_req - disconnect request initiated by me * host sends disoconnect response * * @dev: the device structure. @@ -542,27 +566,28 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->version = version_res->me_max_version; dev_dbg(&dev->pdev->dev, "version mismatch.\n"); + dev->hbm_state = MEI_HBM_STOP; mei_hbm_stop_req_prepare(dev, &dev->wr_msg.hdr, dev->wr_msg.data); mei_write_message(dev, &dev->wr_msg.hdr, dev->wr_msg.data); + return; } dev->version.major_version = HBM_MAJOR_VERSION; dev->version.minor_version = HBM_MINOR_VERSION; if (dev->dev_state == MEI_DEV_INIT_CLIENTS && - dev->init_clients_state == MEI_START_MESSAGE) { + dev->hbm_state == MEI_HBM_START) { dev->init_clients_timer = 0; mei_hbm_enum_clients_req(dev); } else { - dev->recvd_msg = false; - dev_dbg(&dev->pdev->dev, "reset due to received hbm: host start\n"); + dev_err(&dev->pdev->dev, "reset: wrong host start response\n"); mei_reset(dev, 1); return; } - dev->recvd_msg = true; + wake_up_interruptible(&dev->wait_recvd_msg); dev_dbg(&dev->pdev->dev, "host start response message received.\n"); break; @@ -591,23 +616,20 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) me_client = &dev->me_clients[dev->me_client_presentation_num]; if (props_res->status || !dev->me_clients) { - dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message wrong status.\n"); + dev_err(&dev->pdev->dev, "reset: properties response hbm wrong status.\n"); mei_reset(dev, 1); return; } if (me_client->client_id != props_res->address) { - dev_err(&dev->pdev->dev, - "Host client properties reply mismatch\n"); + dev_err(&dev->pdev->dev, "reset: host properties response address mismatch\n"); mei_reset(dev, 1); - return; } if (dev->dev_state != MEI_DEV_INIT_CLIENTS || - dev->init_clients_state != MEI_CLIENT_PROPERTIES_MESSAGE) { - dev_err(&dev->pdev->dev, - "Unexpected client properties reply\n"); + dev->hbm_state != MEI_HBM_CLIENT_PROPERTIES) { + dev_err(&dev->pdev->dev, "reset: unexpected properties response\n"); mei_reset(dev, 1); return; @@ -626,26 +648,28 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) enum_res = (struct hbm_host_enum_response *) mei_msg; memcpy(dev->me_clients_map, enum_res->valid_addresses, 32); if (dev->dev_state == MEI_DEV_INIT_CLIENTS && - dev->init_clients_state == MEI_ENUM_CLIENTS_MESSAGE) { + dev->hbm_state == MEI_HBM_ENUM_CLIENTS) { dev->init_clients_timer = 0; dev->me_client_presentation_num = 0; dev->me_client_index = 0; mei_hbm_me_cl_allocate(dev); - dev->init_clients_state = - MEI_CLIENT_PROPERTIES_MESSAGE; + dev->hbm_state = MEI_HBM_CLIENT_PROPERTIES; /* first property reqeust */ mei_hbm_prop_req(dev); } else { - dev_dbg(&dev->pdev->dev, "reset due to received host enumeration clients response bus message.\n"); + dev_err(&dev->pdev->dev, "reset: unexpected enumeration response hbm.\n"); mei_reset(dev, 1); return; } break; case HOST_STOP_RES_CMD: + + if (dev->hbm_state != MEI_HBM_STOP) + dev_err(&dev->pdev->dev, "unexpected stop response hbm.\n"); dev->dev_state = MEI_DEV_DISABLED; - dev_dbg(&dev->pdev->dev, "resetting because of FW stop response.\n"); + dev_info(&dev->pdev->dev, "reset: FW stop response.\n"); mei_reset(dev, 1); break; @@ -657,6 +681,7 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) case ME_STOP_REQ_CMD: + dev->hbm_state = MEI_HBM_STOP; mei_hbm_stop_req_prepare(dev, &dev->wr_ext_msg.hdr, dev->wr_ext_msg.data); break; diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h index b552afbaf85c..e80dc24ef3e2 100644 --- a/drivers/misc/mei/hbm.h +++ b/drivers/misc/mei/hbm.h @@ -17,6 +17,27 @@ #ifndef _MEI_HBM_H_ #define _MEI_HBM_H_ +struct mei_device; +struct mei_msg_hdr; +struct mei_cl; + +/** + * enum mei_hbm_state - host bus message protocol state + * + * @MEI_HBM_IDLE : protocol not started + * @MEI_HBM_START : start request message was sent + * @MEI_HBM_ENUM_CLIENTS : enumeration request was sent + * @MEI_HBM_CLIENT_PROPERTIES : acquiring clients properties + */ +enum mei_hbm_state { + MEI_HBM_IDLE = 0, + MEI_HBM_START, + MEI_HBM_ENUM_CLIENTS, + MEI_HBM_CLIENT_PROPERTIES, + MEI_HBM_STARTED, + MEI_HBM_STOP, +}; + void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr); static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) @@ -28,8 +49,8 @@ static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) hdr->reserved = 0; } -void mei_hbm_start_req(struct mei_device *dev); - +int mei_hbm_start_req(struct mei_device *dev); +int mei_hbm_start_wait(struct mei_device *dev); int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 642c6223fa6c..822170f00348 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -26,14 +26,14 @@ /** - * mei_reg_read - Reads 32bit data from the mei device + * mei_me_reg_read - Reads 32bit data from the mei device * * @dev: the device structure * @offset: offset from which to read the data * * returns register value (u32) */ -static inline u32 mei_reg_read(const struct mei_me_hw *hw, +static inline u32 mei_me_reg_read(const struct mei_me_hw *hw, unsigned long offset) { return ioread32(hw->mem_addr + offset); @@ -41,20 +41,20 @@ static inline u32 mei_reg_read(const struct mei_me_hw *hw, /** - * mei_reg_write - Writes 32bit data to the mei device + * mei_me_reg_write - Writes 32bit data to the mei device * * @dev: the device structure * @offset: offset from which to write the data * @value: register value to write (u32) */ -static inline void mei_reg_write(const struct mei_me_hw *hw, +static inline void mei_me_reg_write(const struct mei_me_hw *hw, unsigned long offset, u32 value) { iowrite32(value, hw->mem_addr + offset); } /** - * mei_mecbrw_read - Reads 32bit data from ME circular buffer + * mei_me_mecbrw_read - Reads 32bit data from ME circular buffer * read window register * * @dev: the device structure @@ -63,18 +63,18 @@ static inline void mei_reg_write(const struct mei_me_hw *hw, */ static u32 mei_me_mecbrw_read(const struct mei_device *dev) { - return mei_reg_read(to_me_hw(dev), ME_CB_RW); + return mei_me_reg_read(to_me_hw(dev), ME_CB_RW); } /** - * mei_mecsr_read - Reads 32bit data from the ME CSR + * mei_me_mecsr_read - Reads 32bit data from the ME CSR * * @dev: the device structure * * returns ME_CSR_HA register value (u32) */ -static inline u32 mei_mecsr_read(const struct mei_me_hw *hw) +static inline u32 mei_me_mecsr_read(const struct mei_me_hw *hw) { - return mei_reg_read(hw, ME_CSR_HA); + return mei_me_reg_read(hw, ME_CSR_HA); } /** @@ -86,7 +86,7 @@ static inline u32 mei_mecsr_read(const struct mei_me_hw *hw) */ static inline u32 mei_hcsr_read(const struct mei_me_hw *hw) { - return mei_reg_read(hw, H_CSR); + return mei_me_reg_read(hw, H_CSR); } /** @@ -98,12 +98,12 @@ static inline u32 mei_hcsr_read(const struct mei_me_hw *hw) static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) { hcsr &= ~H_IS; - mei_reg_write(hw, H_CSR, hcsr); + mei_me_reg_write(hw, H_CSR, hcsr); } /** - * me_hw_config - configure hw dependent settings + * mei_me_hw_config - configure hw dependent settings * * @dev: mei device */ @@ -123,7 +123,7 @@ static void mei_me_intr_clear(struct mei_device *dev) struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr = mei_hcsr_read(hw); if ((hcsr & H_IS) == H_IS) - mei_reg_write(hw, H_CSR, hcsr); + mei_me_reg_write(hw, H_CSR, hcsr); } /** * mei_me_intr_enable - enables mei device interrupts @@ -169,7 +169,7 @@ static void mei_me_hw_reset_release(struct mei_device *dev) * mei_me_hw_reset - resets fw via mei csr register. * * @dev: the device structure - * @interrupts_enabled: if interrupt should be enabled after reset. + * @intr_enable: if interrupt should be enabled after reset. */ static void mei_me_hw_reset(struct mei_device *dev, bool intr_enable) { @@ -228,10 +228,42 @@ static bool mei_me_host_is_ready(struct mei_device *dev) static bool mei_me_hw_is_ready(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); - hw->me_hw_state = mei_mecsr_read(hw); + hw->me_hw_state = mei_me_mecsr_read(hw); return (hw->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA; } +static int mei_me_hw_ready_wait(struct mei_device *dev) +{ + int err; + if (mei_me_hw_is_ready(dev)) + return 0; + + mutex_unlock(&dev->device_lock); + err = wait_event_interruptible_timeout(dev->wait_hw_ready, + dev->recvd_hw_ready, MEI_INTEROP_TIMEOUT); + mutex_lock(&dev->device_lock); + if (!err && !dev->recvd_hw_ready) { + dev_err(&dev->pdev->dev, + "wait hw ready failed. status = 0x%x\n", err); + return -ETIMEDOUT; + } + + dev->recvd_hw_ready = false; + return 0; +} + +static int mei_me_hw_start(struct mei_device *dev) +{ + int ret = mei_me_hw_ready_wait(dev); + if (ret) + return ret; + dev_dbg(&dev->pdev->dev, "hw is ready\n"); + + mei_me_host_set_ready(dev); + return ret; +} + + /** * mei_hbuf_filled_slots - gets number of device filled buffer slots * @@ -253,7 +285,7 @@ static unsigned char mei_hbuf_filled_slots(struct mei_device *dev) } /** - * mei_hbuf_is_empty - checks if host buffer is empty. + * mei_me_hbuf_is_empty - checks if host buffer is empty. * * @dev: the device structure * @@ -305,10 +337,11 @@ static int mei_me_write_message(struct mei_device *dev, unsigned char *buf) { struct mei_me_hw *hw = to_me_hw(dev); - unsigned long rem, dw_cnt; + unsigned long rem; unsigned long length = header->length; u32 *reg_buf = (u32 *)buf; u32 hcsr; + u32 dw_cnt; int i; int empty_slots; @@ -321,16 +354,16 @@ static int mei_me_write_message(struct mei_device *dev, if (empty_slots < 0 || dw_cnt > empty_slots) return -EIO; - mei_reg_write(hw, H_CB_WW, *((u32 *) header)); + mei_me_reg_write(hw, H_CB_WW, *((u32 *) header)); for (i = 0; i < length / 4; i++) - mei_reg_write(hw, H_CB_WW, reg_buf[i]); + mei_me_reg_write(hw, H_CB_WW, reg_buf[i]); rem = length & 0x3; if (rem > 0) { u32 reg = 0; memcpy(®, &buf[length - rem], rem); - mei_reg_write(hw, H_CB_WW, reg); + mei_me_reg_write(hw, H_CB_WW, reg); } hcsr = mei_hcsr_read(hw) | H_IG; @@ -354,7 +387,7 @@ static int mei_me_count_full_read_slots(struct mei_device *dev) char read_ptr, write_ptr; unsigned char buffer_depth, filled_slots; - hw->me_hw_state = mei_mecsr_read(hw); + hw->me_hw_state = mei_me_mecsr_read(hw); buffer_depth = (unsigned char)((hw->me_hw_state & ME_CBD_HRA) >> 24); read_ptr = (char) ((hw->me_hw_state & ME_CBRP_HRA) >> 8); write_ptr = (char) ((hw->me_hw_state & ME_CBWP_HRA) >> 16); @@ -414,7 +447,7 @@ irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id) return IRQ_NONE; /* clear H_IS bit in H_CSR */ - mei_reg_write(hw, H_CSR, csr_reg); + mei_me_reg_write(hw, H_CSR, csr_reg); return IRQ_WAKE_THREAD; } @@ -433,12 +466,8 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) { struct mei_device *dev = (struct mei_device *) dev_id; struct mei_cl_cb complete_list; - struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; - struct mei_cl *cl; s32 slots; int rets; - bool bus_message_received; - dev_dbg(&dev->pdev->dev, "function called after ISR to handle the interrupt processing.\n"); /* initialize our complete list */ @@ -452,7 +481,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if ME wants a reset */ if (!mei_hw_is_ready(dev) && - dev->dev_state != MEI_DEV_RESETING && + dev->dev_state != MEI_DEV_RESETTING && dev->dev_state != MEI_DEV_INITIALIZING) { dev_dbg(&dev->pdev->dev, "FW not ready.\n"); mei_reset(dev, 1); @@ -465,14 +494,9 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) if (mei_hw_is_ready(dev)) { dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); - mei_host_set_ready(dev); + dev->recvd_hw_ready = true; + wake_up_interruptible(&dev->wait_hw_ready); - dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); - /* link is established * start sending messages. */ - - dev->dev_state = MEI_DEV_INIT_CLIENTS; - - mei_hbm_start_req(dev); mutex_unlock(&dev->device_lock); return IRQ_HANDLED; } else { @@ -499,44 +523,20 @@ end: dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); dev->hbuf_is_ready = mei_hbuf_is_ready(dev); - bus_message_received = false; - if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { - dev_dbg(&dev->pdev->dev, "received waiting bus message\n"); - bus_message_received = true; - } mutex_unlock(&dev->device_lock); - if (bus_message_received) { - dev_dbg(&dev->pdev->dev, "wake up dev->wait_recvd_msg\n"); - wake_up_interruptible(&dev->wait_recvd_msg); - bus_message_received = false; - } - if (list_empty(&complete_list.list)) - return IRQ_HANDLED; + mei_irq_compl_handler(dev, &complete_list); - list_for_each_entry_safe(cb_pos, cb_next, &complete_list.list, list) { - cl = cb_pos->cl; - list_del(&cb_pos->list); - if (cl) { - if (cl != &dev->iamthif_cl) { - dev_dbg(&dev->pdev->dev, "completing call back.\n"); - mei_irq_complete_handler(cl, cb_pos); - cb_pos = NULL; - } else if (cl == &dev->iamthif_cl) { - mei_amthif_complete(dev, cb_pos); - } - } - } return IRQ_HANDLED; } static const struct mei_hw_ops mei_me_hw_ops = { - .host_set_ready = mei_me_host_set_ready, .host_is_ready = mei_me_host_is_ready, .hw_is_ready = mei_me_hw_is_ready, .hw_reset = mei_me_hw_reset, - .hw_config = mei_me_hw_config, + .hw_config = mei_me_hw_config, + .hw_start = mei_me_hw_start, .intr_clear = mei_me_intr_clear, .intr_enable = mei_me_intr_enable, @@ -554,7 +554,7 @@ static const struct mei_hw_ops mei_me_hw_ops = { }; /** - * init_mei_device - allocates and initializes the mei device structure + * mei_me_dev_init - allocates and initializes the mei device structure * * @pdev: The pci device structure * @@ -571,14 +571,6 @@ struct mei_device *mei_me_dev_init(struct pci_dev *pdev) mei_device_init(dev); - INIT_LIST_HEAD(&dev->wd_cl.link); - INIT_LIST_HEAD(&dev->iamthif_cl.link); - mei_io_list_init(&dev->amthif_cmd_list); - mei_io_list_init(&dev->amthif_rd_complete_list); - - INIT_DELAYED_WORK(&dev->timer_work, mei_timer); - INIT_WORK(&dev->init_work, mei_host_client_init); - dev->ops = &mei_me_hw_ops; dev->pdev = pdev; diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 8518d3eeb838..80bd829fbd9a 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -36,12 +36,6 @@ struct mei_me_hw { struct mei_device *mei_me_dev_init(struct pci_dev *pdev); -/* get slots (dwords) from a message length + header (bytes) */ -static inline unsigned char mei_data2slots(size_t length) -{ - return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4); -} - irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id); irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id); diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 356179991a2e..713d89fedc46 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -14,6 +14,7 @@ * */ +#include <linux/export.h> #include <linux/pci.h> #include <linux/sched.h> #include <linux/wait.h> @@ -22,6 +23,7 @@ #include <linux/mei.h> #include "mei_dev.h" +#include "hbm.h" #include "client.h" const char *mei_dev_state_str(int state) @@ -31,9 +33,8 @@ const char *mei_dev_state_str(int state) MEI_DEV_STATE(INITIALIZING); MEI_DEV_STATE(INIT_CLIENTS); MEI_DEV_STATE(ENABLED); - MEI_DEV_STATE(RESETING); + MEI_DEV_STATE(RESETTING); MEI_DEV_STATE(DISABLED); - MEI_DEV_STATE(RECOVERING_FROM_RESET); MEI_DEV_STATE(POWER_DOWN); MEI_DEV_STATE(POWER_UP); default: @@ -46,7 +47,9 @@ void mei_device_init(struct mei_device *dev) { /* setup our list array */ INIT_LIST_HEAD(&dev->file_list); + INIT_LIST_HEAD(&dev->device_list); mutex_init(&dev->device_lock); + init_waitqueue_head(&dev->wait_hw_ready); init_waitqueue_head(&dev->wait_recvd_msg); init_waitqueue_head(&dev->wait_stop_wd); dev->dev_state = MEI_DEV_INITIALIZING; @@ -56,19 +59,27 @@ void mei_device_init(struct mei_device *dev) mei_io_list_init(&dev->write_waiting_list); mei_io_list_init(&dev->ctrl_wr_list); mei_io_list_init(&dev->ctrl_rd_list); + + INIT_DELAYED_WORK(&dev->timer_work, mei_timer); + INIT_WORK(&dev->init_work, mei_host_client_init); + + INIT_LIST_HEAD(&dev->wd_cl.link); + INIT_LIST_HEAD(&dev->iamthif_cl.link); + mei_io_list_init(&dev->amthif_cmd_list); + mei_io_list_init(&dev->amthif_rd_complete_list); + } +EXPORT_SYMBOL_GPL(mei_device_init); /** - * mei_hw_init - initializes host and fw to start work. + * mei_start - initializes host and fw to start work. * * @dev: the device structure * * returns 0 on success, <0 on failure. */ -int mei_hw_init(struct mei_device *dev) +int mei_start(struct mei_device *dev) { - int ret = 0; - mutex_lock(&dev->device_lock); /* acknowledge interrupt and stop interupts */ @@ -76,29 +87,15 @@ int mei_hw_init(struct mei_device *dev) mei_hw_config(dev); - dev->recvd_msg = false; dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); mei_reset(dev, 1); - /* wait for ME to turn on ME_RDY */ - if (!dev->recvd_msg) { - mutex_unlock(&dev->device_lock); - ret = wait_event_interruptible_timeout(dev->wait_recvd_msg, - dev->recvd_msg, - mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT)); - mutex_lock(&dev->device_lock); - } - - if (ret <= 0 && !dev->recvd_msg) { - dev->dev_state = MEI_DEV_DISABLED; - dev_dbg(&dev->pdev->dev, - "wait_event_interruptible_timeout failed" - "on wait for ME to turn on ME_RDY.\n"); + if (mei_hbm_start_wait(dev)) { + dev_err(&dev->pdev->dev, "HBM haven't started"); goto err; } - if (!mei_host_is_ready(dev)) { dev_err(&dev->pdev->dev, "host is not ready.\n"); goto err; @@ -115,7 +112,6 @@ int mei_hw_init(struct mei_device *dev) goto err; } - dev->recvd_msg = false; dev_dbg(&dev->pdev->dev, "link layer has been established.\n"); mutex_unlock(&dev->device_lock); @@ -126,6 +122,7 @@ err: mutex_unlock(&dev->device_lock); return -ENODEV; } +EXPORT_SYMBOL_GPL(mei_start); /** * mei_reset - resets host and fw. @@ -137,9 +134,6 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) { bool unexpected; - if (dev->dev_state == MEI_DEV_RECOVERING_FROM_RESET) - return; - unexpected = (dev->dev_state != MEI_DEV_INITIALIZING && dev->dev_state != MEI_DEV_DISABLED && dev->dev_state != MEI_DEV_POWER_DOWN && @@ -147,11 +141,12 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) mei_hw_reset(dev, interrupts_enabled); + dev->hbm_state = MEI_HBM_IDLE; if (dev->dev_state != MEI_DEV_INITIALIZING) { if (dev->dev_state != MEI_DEV_DISABLED && dev->dev_state != MEI_DEV_POWER_DOWN) - dev->dev_state = MEI_DEV_RESETING; + dev->dev_state = MEI_DEV_RESETTING; mei_cl_all_disconnect(dev); @@ -176,12 +171,27 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", mei_dev_state_str(dev->dev_state)); + if (!interrupts_enabled) { + dev_dbg(&dev->pdev->dev, "intr not enabled end of reset\n"); + return; + } + + mei_hw_start(dev); + + dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); + /* link is established * start sending messages. */ + + dev->dev_state = MEI_DEV_INIT_CLIENTS; + + mei_hbm_start_req(dev); + /* wake up all readings so they can be interrupted */ mei_cl_all_read_wakeup(dev); /* remove all waiting requests */ mei_cl_all_write_clear(dev); } +EXPORT_SYMBOL_GPL(mei_reset); void mei_stop(struct mei_device *dev) { @@ -193,14 +203,18 @@ void mei_stop(struct mei_device *dev) mei_wd_stop(dev); + mei_nfc_host_exit(); + dev->dev_state = MEI_DEV_POWER_DOWN; mei_reset(dev, 0); mutex_unlock(&dev->device_lock); flush_scheduled_work(); -} + mei_watchdog_unregister(dev); +} +EXPORT_SYMBOL_GPL(mei_stop); diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 3535b2676c97..2ad736989410 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -15,6 +15,7 @@ */ +#include <linux/export.h> #include <linux/pci.h> #include <linux/kthread.h> #include <linux/interrupt.h> @@ -30,103 +31,153 @@ /** - * mei_complete_handler - processes completed operation. + * mei_cl_complete_handler - processes completed operation for a client * * @cl: private data of the file object. - * @cb_pos: callback block. + * @cb: callback block. */ -void mei_irq_complete_handler(struct mei_cl *cl, struct mei_cl_cb *cb_pos) +static void mei_cl_complete_handler(struct mei_cl *cl, struct mei_cl_cb *cb) { - if (cb_pos->fop_type == MEI_FOP_WRITE) { - mei_io_cb_free(cb_pos); - cb_pos = NULL; + if (cb->fop_type == MEI_FOP_WRITE) { + mei_io_cb_free(cb); + cb = NULL; cl->writing_state = MEI_WRITE_COMPLETE; if (waitqueue_active(&cl->tx_wait)) wake_up_interruptible(&cl->tx_wait); - } else if (cb_pos->fop_type == MEI_FOP_READ && + } else if (cb->fop_type == MEI_FOP_READ && MEI_READING == cl->reading_state) { cl->reading_state = MEI_READ_COMPLETE; if (waitqueue_active(&cl->rx_wait)) wake_up_interruptible(&cl->rx_wait); + else + mei_cl_bus_rx_event(cl); + + } +} + +/** + * mei_irq_compl_handler - dispatch complete handelers + * for the completed callbacks + * + * @dev - mei device + * @compl_list - list of completed cbs + */ +void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *compl_list) +{ + struct mei_cl_cb *cb, *next; + struct mei_cl *cl; + + list_for_each_entry_safe(cb, next, &compl_list->list, list) { + cl = cb->cl; + list_del(&cb->list); + if (!cl) + continue; + dev_dbg(&dev->pdev->dev, "completing call back.\n"); + if (cl == &dev->iamthif_cl) + mei_amthif_complete(dev, cb); + else + mei_cl_complete_handler(cl, cb); } } +EXPORT_SYMBOL_GPL(mei_irq_compl_handler); /** - * _mei_irq_thread_state_ok - checks if mei header matches file private data + * mei_cl_hbm_equal - check if hbm is addressed to the client * - * @cl: private data of the file object + * @cl: host client * @mei_hdr: header of mei client message * - * returns !=0 if matches, 0 if no match. + * returns true if matches, false otherwise + */ +static inline int mei_cl_hbm_equal(struct mei_cl *cl, + struct mei_msg_hdr *mei_hdr) +{ + return cl->host_client_id == mei_hdr->host_addr && + cl->me_client_id == mei_hdr->me_addr; +} +/** + * mei_cl_is_reading - checks if the client + is the one to read this message + * + * @cl: mei client + * @mei_hdr: header of mei message + * + * returns true on match and false otherwise */ -static int _mei_irq_thread_state_ok(struct mei_cl *cl, - struct mei_msg_hdr *mei_hdr) +static bool mei_cl_is_reading(struct mei_cl *cl, struct mei_msg_hdr *mei_hdr) { - return (cl->host_client_id == mei_hdr->host_addr && - cl->me_client_id == mei_hdr->me_addr && + return mei_cl_hbm_equal(cl, mei_hdr) && cl->state == MEI_FILE_CONNECTED && - MEI_READ_COMPLETE != cl->reading_state); + cl->reading_state != MEI_READ_COMPLETE; } /** - * mei_irq_thread_read_client_message - bottom half read routine after ISR to - * handle the read mei client message data processing. + * mei_irq_read_client_message - process client message * - * @complete_list: An instance of our list structure * @dev: the device structure * @mei_hdr: header of mei client message + * @complete_list: An instance of our list structure * * returns 0 on success, <0 on failure. */ -static int mei_irq_thread_read_client_message(struct mei_cl_cb *complete_list, - struct mei_device *dev, - struct mei_msg_hdr *mei_hdr) +static int mei_cl_irq_read_msg(struct mei_device *dev, + struct mei_msg_hdr *mei_hdr, + struct mei_cl_cb *complete_list) { struct mei_cl *cl; - struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + struct mei_cl_cb *cb, *next; unsigned char *buffer = NULL; - dev_dbg(&dev->pdev->dev, "start client msg\n"); - if (list_empty(&dev->read_list.list)) - goto quit; + list_for_each_entry_safe(cb, next, &dev->read_list.list, list) { + cl = cb->cl; + if (!cl || !mei_cl_is_reading(cl, mei_hdr)) + continue; - list_for_each_entry_safe(cb_pos, cb_next, &dev->read_list.list, list) { - cl = cb_pos->cl; - if (cl && _mei_irq_thread_state_ok(cl, mei_hdr)) { - cl->reading_state = MEI_READING; - buffer = cb_pos->response_buffer.data + cb_pos->buf_idx; + cl->reading_state = MEI_READING; - if (cb_pos->response_buffer.size < - mei_hdr->length + cb_pos->buf_idx) { - dev_dbg(&dev->pdev->dev, "message overflow.\n"); - list_del(&cb_pos->list); + if (cb->response_buffer.size == 0 || + cb->response_buffer.data == NULL) { + dev_err(&dev->pdev->dev, "response buffer is not allocated.\n"); + list_del(&cb->list); + return -ENOMEM; + } + + if (cb->response_buffer.size < mei_hdr->length + cb->buf_idx) { + dev_dbg(&dev->pdev->dev, "message overflow. size %d len %d idx %ld\n", + cb->response_buffer.size, + mei_hdr->length, cb->buf_idx); + buffer = krealloc(cb->response_buffer.data, + mei_hdr->length + cb->buf_idx, + GFP_KERNEL); + + if (!buffer) { + dev_err(&dev->pdev->dev, "allocation failed.\n"); + list_del(&cb->list); return -ENOMEM; } - if (buffer) - mei_read_slots(dev, buffer, mei_hdr->length); - - cb_pos->buf_idx += mei_hdr->length; - if (mei_hdr->msg_complete) { - cl->status = 0; - list_del(&cb_pos->list); - dev_dbg(&dev->pdev->dev, - "completed read H cl = %d, ME cl = %d, length = %lu\n", - cl->host_client_id, - cl->me_client_id, - cb_pos->buf_idx); - - list_add_tail(&cb_pos->list, - &complete_list->list); - } - - break; + cb->response_buffer.data = buffer; + cb->response_buffer.size = + mei_hdr->length + cb->buf_idx; } + buffer = cb->response_buffer.data + cb->buf_idx; + mei_read_slots(dev, buffer, mei_hdr->length); + + cb->buf_idx += mei_hdr->length; + if (mei_hdr->msg_complete) { + cl->status = 0; + list_del(&cb->list); + dev_dbg(&dev->pdev->dev, "completed read H cl = %d, ME cl = %d, length = %lu\n", + cl->host_client_id, + cl->me_client_id, + cb->buf_idx); + list_add_tail(&cb->list, &complete_list->list); + } + break; } -quit: dev_dbg(&dev->pdev->dev, "message read\n"); if (!buffer) { mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); @@ -153,31 +204,33 @@ static int _mei_irq_thread_close(struct mei_device *dev, s32 *slots, struct mei_cl *cl, struct mei_cl_cb *cmpl_list) { - if ((*slots * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + - sizeof(struct hbm_client_connect_request))) - return -EBADMSG; + u32 msg_slots = + mei_data2slots(sizeof(struct hbm_client_connect_request)); - *slots -= mei_data2slots(sizeof(struct hbm_client_connect_request)); + if (*slots < msg_slots) + return -EMSGSIZE; + + *slots -= msg_slots; if (mei_hbm_cl_disconnect_req(dev, cl)) { cl->status = 0; cb_pos->buf_idx = 0; list_move_tail(&cb_pos->list, &cmpl_list->list); - return -EMSGSIZE; - } else { - cl->state = MEI_FILE_DISCONNECTING; - cl->status = 0; - cb_pos->buf_idx = 0; - list_move_tail(&cb_pos->list, &dev->ctrl_rd_list.list); - cl->timer_count = MEI_CONNECT_TIMEOUT; + return -EIO; } + cl->state = MEI_FILE_DISCONNECTING; + cl->status = 0; + cb_pos->buf_idx = 0; + list_move_tail(&cb_pos->list, &dev->ctrl_rd_list.list); + cl->timer_count = MEI_CONNECT_TIMEOUT; + return 0; } /** - * _mei_hb_read - processes read related operation. + * _mei_irq_thread_read - processes read related operation. * * @dev: the device structure. * @slots: free slots. @@ -192,14 +245,15 @@ static int _mei_irq_thread_read(struct mei_device *dev, s32 *slots, struct mei_cl *cl, struct mei_cl_cb *cmpl_list) { - if ((*slots * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + - sizeof(struct hbm_flow_control))) { + u32 msg_slots = mei_data2slots(sizeof(struct hbm_flow_control)); + + if (*slots < msg_slots) { /* return the cancel routine */ list_del(&cb_pos->list); - return -EBADMSG; + return -EMSGSIZE; } - *slots -= mei_data2slots(sizeof(struct hbm_flow_control)); + *slots -= msg_slots; if (mei_hbm_cl_flow_control_req(dev, cl)) { cl->status = -ENODEV; @@ -229,15 +283,19 @@ static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, struct mei_cl *cl, struct mei_cl_cb *cmpl_list) { - if ((*slots * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + - sizeof(struct hbm_client_connect_request))) { + u32 msg_slots = + mei_data2slots(sizeof(struct hbm_client_connect_request)); + + if (*slots < msg_slots) { /* return the cancel routine */ list_del(&cb_pos->list); - return -EBADMSG; + return -EMSGSIZE; } + *slots -= msg_slots; + cl->state = MEI_FILE_CONNECTING; - *slots -= mei_data2slots(sizeof(struct hbm_client_connect_request)); + if (mei_hbm_cl_connect_req(dev, cl)) { cl->status = -ENODEV; cb_pos->buf_idx = 0; @@ -266,7 +324,7 @@ static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, struct mei_msg_hdr mei_hdr; struct mei_cl *cl = cb->cl; size_t len = cb->request_buffer.size - cb->buf_idx; - size_t msg_slots = mei_data2slots(len); + u32 msg_slots = mei_data2slots(len); mei_hdr.host_addr = cl->host_client_id; mei_hdr.me_addr = cl->me_client_id; @@ -298,19 +356,20 @@ static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, return -ENODEV; } - if (mei_cl_flow_ctrl_reduce(cl)) - return -ENODEV; cl->status = 0; cb->buf_idx += mei_hdr.length; - if (mei_hdr.msg_complete) + if (mei_hdr.msg_complete) { + if (mei_cl_flow_ctrl_reduce(cl)) + return -ENODEV; list_move_tail(&cb->list, &dev->write_waiting_list.list); + } return 0; } /** - * mei_irq_thread_read_handler - bottom half read routine after ISR to + * mei_irq_read_handler - bottom half read routine after ISR to * handle the read processing. * * @dev: the device structure @@ -350,8 +409,7 @@ int mei_irq_read_handler(struct mei_device *dev, " client = %d, ME client = %d\n", cl_pos->host_client_id, cl_pos->me_client_id); - if (cl_pos->host_client_id == mei_hdr->host_addr && - cl_pos->me_client_id == mei_hdr->me_addr) + if (mei_cl_hbm_equal(cl_pos, mei_hdr)) break; } @@ -362,7 +420,7 @@ int mei_irq_read_handler(struct mei_device *dev, } } if (((*slots) * sizeof(u32)) < mei_hdr->length) { - dev_dbg(&dev->pdev->dev, + dev_err(&dev->pdev->dev, "we can't read the message slots =%08x.\n", *slots); /* we can't read the message */ @@ -378,20 +436,19 @@ int mei_irq_read_handler(struct mei_device *dev, } else if (mei_hdr->host_addr == dev->iamthif_cl.host_client_id && (MEI_FILE_CONNECTED == dev->iamthif_cl.state) && (dev->iamthif_state == MEI_IAMTHIF_READING)) { - dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_iamthif_message.\n"); + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_iamthif_message.\n"); dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); - ret = mei_amthif_irq_read_message(cmpl_list, dev, mei_hdr); + ret = mei_amthif_irq_read_msg(dev, mei_hdr, cmpl_list); if (ret) goto end; } else { - dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_client_message.\n"); - ret = mei_irq_thread_read_client_message(cmpl_list, - dev, mei_hdr); + dev_dbg(&dev->pdev->dev, "call mei_cl_irq_read_msg.\n"); + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); + ret = mei_cl_irq_read_msg(dev, mei_hdr, cmpl_list); if (ret) goto end; - } /* reset the number of slots and header */ @@ -400,7 +457,7 @@ int mei_irq_read_handler(struct mei_device *dev, if (*slots == -EOVERFLOW) { /* overflow - reset */ - dev_dbg(&dev->pdev->dev, "resetting due to slots overflow.\n"); + dev_err(&dev->pdev->dev, "resetting due to slots overflow.\n"); /* set the event since message has been read */ ret = -ERANGE; goto end; @@ -408,6 +465,7 @@ int mei_irq_read_handler(struct mei_device *dev, end: return ret; } +EXPORT_SYMBOL_GPL(mei_irq_read_handler); /** @@ -419,8 +477,7 @@ end: * * returns 0 on success, <0 on failure. */ -int mei_irq_write_handler(struct mei_device *dev, - struct mei_cl_cb *cmpl_list) +int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) { struct mei_cl *cl; @@ -559,6 +616,7 @@ int mei_irq_write_handler(struct mei_device *dev, } return 0; } +EXPORT_SYMBOL_GPL(mei_irq_write_handler); @@ -586,8 +644,8 @@ void mei_timer(struct work_struct *work) if (dev->dev_state == MEI_DEV_INIT_CLIENTS) { if (dev->init_clients_timer) { if (--dev->init_clients_timer == 0) { - dev_dbg(&dev->pdev->dev, "IMEI reset due to init clients timeout ,init clients state = %d.\n", - dev->init_clients_state); + dev_err(&dev->pdev->dev, "reset: init clients timeout hbm_state = %d.\n", + dev->hbm_state); mei_reset(dev, 1); } } @@ -598,7 +656,7 @@ void mei_timer(struct work_struct *work) list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { if (cl_pos->timer_count) { if (--cl_pos->timer_count == 0) { - dev_dbg(&dev->pdev->dev, "HECI reset due to connect/disconnect timeout.\n"); + dev_err(&dev->pdev->dev, "reset: connect/disconnect timeout.\n"); mei_reset(dev, 1); goto out; } @@ -607,7 +665,7 @@ void mei_timer(struct work_struct *work) if (dev->iamthif_stall_timer) { if (--dev->iamthif_stall_timer == 0) { - dev_dbg(&dev->pdev->dev, "resetting because of hang to amthi.\n"); + dev_err(&dev->pdev->dev, "reset: amthif hanged.\n"); mei_reset(dev, 1); dev->iamthif_msg_buf_size = 0; dev->iamthif_msg_buf_index = 0; diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 903f809b21f7..7c44c8dbae42 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -48,7 +48,7 @@ * * @inode: pointer to inode structure * @file: pointer to file structure - * + e * returns 0 on success, <0 on error */ static int mei_open(struct inode *inode, struct file *file) @@ -244,7 +244,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, goto out; } - err = mei_cl_read_start(cl); + err = mei_cl_read_start(cl, length); if (err && err != -EBUSY) { dev_dbg(&dev->pdev->dev, "mei start read failure with status = %d\n", err); @@ -292,9 +292,8 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, } /* now copy the data to user space */ copy_buffer: - dev_dbg(&dev->pdev->dev, "cb->response_buffer size - %d\n", - cb->response_buffer.size); - dev_dbg(&dev->pdev->dev, "cb->buf_idx - %lu\n", cb->buf_idx); + dev_dbg(&dev->pdev->dev, "buf.size = %d buf.idx= %ld\n", + cb->response_buffer.size, cb->buf_idx); if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) { rets = -EMSGSIZE; goto free; @@ -342,11 +341,10 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, { struct mei_cl *cl = file->private_data; struct mei_cl_cb *write_cb = NULL; - struct mei_msg_hdr mei_hdr; struct mei_device *dev; unsigned long timeout = 0; int rets; - int i; + int id; if (WARN_ON(!cl || !cl->dev)) return -ENODEV; @@ -357,24 +355,24 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, if (dev->dev_state != MEI_DEV_ENABLED) { rets = -ENODEV; - goto err; + goto out; } - i = mei_me_cl_by_id(dev, cl->me_client_id); - if (i < 0) { + id = mei_me_cl_by_id(dev, cl->me_client_id); + if (id < 0) { rets = -ENODEV; - goto err; + goto out; } - if (length > dev->me_clients[i].props.max_msg_length || length <= 0) { + if (length > dev->me_clients[id].props.max_msg_length || length <= 0) { rets = -EMSGSIZE; - goto err; + goto out; } if (cl->state != MEI_FILE_CONNECTED) { - rets = -ENODEV; dev_err(&dev->pdev->dev, "host client = %d, is not connected to ME client = %d", cl->host_client_id, cl->me_client_id); - goto err; + rets = -ENODEV; + goto out; } if (cl == &dev->iamthif_cl) { write_cb = mei_amthif_find_read_list_entry(dev, file); @@ -412,17 +410,15 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, if (!write_cb) { dev_err(&dev->pdev->dev, "write cb allocation failed\n"); rets = -ENOMEM; - goto err; + goto out; } rets = mei_io_cb_alloc_req_buf(write_cb, length); if (rets) - goto err; - - dev_dbg(&dev->pdev->dev, "cb request size = %zd\n", length); + goto out; rets = copy_from_user(write_cb->request_buffer.data, ubuf, length); if (rets) - goto err; + goto out; cl->sm_state = 0; if (length == 4 && @@ -440,65 +436,17 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, if (rets) { dev_err(&dev->pdev->dev, "amthif write failed with status = %d\n", rets); - goto err; + goto out; } mutex_unlock(&dev->device_lock); return length; } - write_cb->fop_type = MEI_FOP_WRITE; - - dev_dbg(&dev->pdev->dev, "host client = %d, ME client = %d\n", - cl->host_client_id, cl->me_client_id); - rets = mei_cl_flow_ctrl_creds(cl); - if (rets < 0) - goto err; - - if (rets == 0 || !dev->hbuf_is_ready) { - write_cb->buf_idx = 0; - mei_hdr.msg_complete = 0; - cl->writing_state = MEI_WRITING; - goto out; - } - - dev->hbuf_is_ready = false; - if (length > mei_hbuf_max_len(dev)) { - mei_hdr.length = mei_hbuf_max_len(dev); - mei_hdr.msg_complete = 0; - } else { - mei_hdr.length = length; - mei_hdr.msg_complete = 1; - } - mei_hdr.host_addr = cl->host_client_id; - mei_hdr.me_addr = cl->me_client_id; - mei_hdr.reserved = 0; - - dev_dbg(&dev->pdev->dev, "write " MEI_HDR_FMT "\n", - MEI_HDR_PRM(&mei_hdr)); - if (mei_write_message(dev, &mei_hdr, write_cb->request_buffer.data)) { - rets = -ENODEV; - goto err; - } - cl->writing_state = MEI_WRITING; - write_cb->buf_idx = mei_hdr.length; - + rets = mei_cl_write(cl, write_cb, false); out: - if (mei_hdr.msg_complete) { - if (mei_cl_flow_ctrl_reduce(cl)) { - rets = -ENODEV; - goto err; - } - list_add_tail(&write_cb->list, &dev->write_waiting_list.list); - } else { - list_add_tail(&write_cb->list, &dev->write_list.list); - } - mutex_unlock(&dev->device_lock); - return length; - -err: - mutex_unlock(&dev->device_lock); - mei_io_cb_free(write_cb); + if (rets < 0) + mei_io_cb_free(write_cb); return rets; } @@ -753,17 +701,44 @@ static struct miscdevice mei_misc_device = { .minor = MISC_DYNAMIC_MINOR, }; -int mei_register(struct device *dev) + +int mei_register(struct mei_device *dev) { - mei_misc_device.parent = dev; - return misc_register(&mei_misc_device); + int ret; + mei_misc_device.parent = &dev->pdev->dev; + ret = misc_register(&mei_misc_device); + if (ret) + return ret; + + if (mei_dbgfs_register(dev, mei_misc_device.name)) + dev_err(&dev->pdev->dev, "cannot register debugfs\n"); + + return 0; } +EXPORT_SYMBOL_GPL(mei_register); -void mei_deregister(void) +void mei_deregister(struct mei_device *dev) { + mei_dbgfs_deregister(dev); misc_deregister(&mei_misc_device); mei_misc_device.parent = NULL; } +EXPORT_SYMBOL_GPL(mei_deregister); + +static int __init mei_init(void) +{ + return mei_cl_bus_init(); +} + +static void __exit mei_exit(void) +{ + mei_cl_bus_exit(); +} + +module_init(mei_init); +module_exit(mei_exit); +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) Management Engine Interface"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 97873812e33b..4de5140e7379 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -21,9 +21,11 @@ #include <linux/watchdog.h> #include <linux/poll.h> #include <linux/mei.h> +#include <linux/mei_cl_bus.h> #include "hw.h" #include "hw-me-regs.h" +#include "hbm.h" /* * watch dog definition @@ -95,22 +97,14 @@ enum mei_dev_state { MEI_DEV_INITIALIZING = 0, MEI_DEV_INIT_CLIENTS, MEI_DEV_ENABLED, - MEI_DEV_RESETING, + MEI_DEV_RESETTING, MEI_DEV_DISABLED, - MEI_DEV_RECOVERING_FROM_RESET, MEI_DEV_POWER_DOWN, MEI_DEV_POWER_UP }; const char *mei_dev_state_str(int state); -/* init clients states*/ -enum mei_init_clients_states { - MEI_START_MESSAGE = 0, - MEI_ENUM_CLIENTS_MESSAGE, - MEI_CLIENT_PROPERTIES_MESSAGE -}; - enum iamthif_states { MEI_IAMTHIF_IDLE, MEI_IAMTHIF_WRITING, @@ -153,7 +147,7 @@ enum mei_cb_file_ops { /* * Intel MEI message data struct */ -struct mei_message_data { +struct mei_msg_data { u32 size; unsigned char *data; }; @@ -184,8 +178,8 @@ struct mei_cl_cb { struct list_head list; struct mei_cl *cl; enum mei_cb_file_ops fop_type; - struct mei_message_data request_buffer; - struct mei_message_data response_buffer; + struct mei_msg_data request_buffer; + struct mei_msg_data response_buffer; unsigned long buf_idx; unsigned long read_time; struct file *file_object; @@ -209,15 +203,20 @@ struct mei_cl { enum mei_file_transaction_states writing_state; int sm_state; struct mei_cl_cb *read_cb; + + /* MEI CL bus data */ + struct mei_cl_device *device; + struct list_head device_link; + uuid_le device_uuid; }; /** struct mei_hw_ops * - * @host_set_ready - notify FW that host side is ready * @host_is_ready - query for host readiness * @hw_is_ready - query if hw is ready * @hw_reset - reset hw + * @hw_start - start hw after reset * @hw_config - configure hw * @intr_clear - clear pending interrupts @@ -237,11 +236,11 @@ struct mei_cl { */ struct mei_hw_ops { - void (*host_set_ready) (struct mei_device *dev); bool (*host_is_ready) (struct mei_device *dev); bool (*hw_is_ready) (struct mei_device *dev); void (*hw_reset) (struct mei_device *dev, bool enable); + int (*hw_start) (struct mei_device *dev); void (*hw_config) (struct mei_device *dev); void (*intr_clear) (struct mei_device *dev); @@ -263,9 +262,77 @@ struct mei_hw_ops { unsigned char *buf, unsigned long len); }; +/* MEI bus API*/ + +/** + * struct mei_cl_ops - MEI CL device ops + * This structure allows ME host clients to implement technology + * specific operations. + * + * @enable: Enable an MEI CL device. Some devices require specific + * HECI commands to initialize completely. + * @disable: Disable an MEI CL device. + * @send: Tx hook for the device. This allows ME host clients to trap + * the device driver buffers before actually physically + * pushing it to the ME. + * @recv: Rx hook for the device. This allows ME host clients to trap the + * ME buffers before forwarding them to the device driver. + */ +struct mei_cl_ops { + int (*enable)(struct mei_cl_device *device); + int (*disable)(struct mei_cl_device *device); + int (*send)(struct mei_cl_device *device, u8 *buf, size_t length); + int (*recv)(struct mei_cl_device *device, u8 *buf, size_t length); +}; + +struct mei_cl_device *mei_cl_add_device(struct mei_device *dev, + uuid_le uuid, char *name, + struct mei_cl_ops *ops); +void mei_cl_remove_device(struct mei_cl_device *device); + +int __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length); +int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length); +int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length); +void mei_cl_bus_rx_event(struct mei_cl *cl); +int mei_cl_bus_init(void); +void mei_cl_bus_exit(void); + + +/** + * struct mei_cl_device - MEI device handle + * An mei_cl_device pointer is returned from mei_add_device() + * and links MEI bus clients to their actual ME host client pointer. + * Drivers for MEI devices will get an mei_cl_device pointer + * when being probed and shall use it for doing ME bus I/O. + * + * @dev: linux driver model device pointer + * @uuid: me client uuid + * @cl: mei client + * @ops: ME transport ops + * @event_cb: Drivers register this callback to get asynchronous ME + * events (e.g. Rx buffer pending) notifications. + * @events: Events bitmask sent to the driver. + * @priv_data: client private data + */ +struct mei_cl_device { + struct device dev; + + struct mei_cl *cl; + + const struct mei_cl_ops *ops; + + struct work_struct event_work; + mei_cl_event_cb_t event_cb; + void *event_context; + unsigned long events; + + void *priv_data; +}; + /** * struct mei_device - MEI private device struct + * @hbm_state - state of host bus message protocol * @mem_addr - mem mapped base register address * @hbuf_depth - depth of hardware host/write buffer is slots @@ -296,11 +363,12 @@ struct mei_device { */ struct mutex device_lock; /* device lock */ struct delayed_work timer_work; /* MEI timer delayed work (timeouts) */ - bool recvd_msg; + bool recvd_hw_ready; /* * waiting queue for receive message from FW */ + wait_queue_head_t wait_hw_ready; wait_queue_head_t wait_recvd_msg; wait_queue_head_t wait_stop_wd; @@ -308,7 +376,7 @@ struct mei_device { * mei device states */ enum mei_dev_state dev_state; - enum mei_init_clients_states init_clients_state; + enum mei_hbm_state hbm_state; u16 init_clients_timer; unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ @@ -365,6 +433,14 @@ struct mei_device { struct work_struct init_work; + /* List of bus devices */ + struct list_head device_list; + +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs_dir; +#endif /* CONFIG_DEBUG_FS */ + + const struct mei_hw_ops *ops; char hw[0] __aligned(sizeof(void *)); }; @@ -374,13 +450,23 @@ static inline unsigned long mei_secs_to_jiffies(unsigned long sec) return msecs_to_jiffies(sec * MSEC_PER_SEC); } +/** + * mei_data2slots - get slots - number of (dwords) from a message length + * + size of the mei header + * @length - size of the messages in bytes + * returns - number of slots + */ +static inline u32 mei_data2slots(size_t length) +{ + return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4); +} /* * mei init function prototypes */ void mei_device_init(struct mei_device *dev); void mei_reset(struct mei_device *dev, int interrupts); -int mei_hw_init(struct mei_device *dev); +int mei_start(struct mei_device *dev); void mei_stop(struct mei_device *dev); /* @@ -392,8 +478,7 @@ int mei_irq_read_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list, s32 *slots); int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list); - -void mei_irq_complete_handler(struct mei_cl *cl, struct mei_cl_cb *cb_pos); +void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list); /* * AMTHIF - AMT Host Interface Functions @@ -417,6 +502,25 @@ struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev, void mei_amthif_run_next_cmd(struct mei_device *dev); +int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list); + +void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb); +int mei_amthif_irq_read_msg(struct mei_device *dev, + struct mei_msg_hdr *mei_hdr, + struct mei_cl_cb *complete_list); +int mei_amthif_irq_read(struct mei_device *dev, s32 *slots); + +/* + * NFC functions + */ +int mei_nfc_host_init(struct mei_device *dev); +void mei_nfc_host_exit(void); + +/* + * NFC Client UUID + */ +extern const uuid_le mei_nfc_guid; int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list); @@ -455,6 +559,11 @@ static inline void mei_hw_reset(struct mei_device *dev, bool enable) dev->ops->hw_reset(dev, enable); } +static inline void mei_hw_start(struct mei_device *dev) +{ + dev->ops->hw_start(dev); +} + static inline void mei_clear_interrupts(struct mei_device *dev) { dev->ops->intr_clear(dev); @@ -470,10 +579,6 @@ static inline void mei_disable_interrupts(struct mei_device *dev) dev->ops->intr_disable(dev); } -static inline void mei_host_set_ready(struct mei_device *dev) -{ - dev->ops->host_set_ready(dev); -} static inline bool mei_host_is_ready(struct mei_device *dev) { return dev->ops->host_is_ready(dev); @@ -521,8 +626,19 @@ static inline int mei_count_full_read_slots(struct mei_device *dev) return dev->ops->rdbuf_full_slots(dev); } -int mei_register(struct device *dev); -void mei_deregister(void); +#if IS_ENABLED(CONFIG_DEBUG_FS) +int mei_dbgfs_register(struct mei_device *dev, const char *name); +void mei_dbgfs_deregister(struct mei_device *dev); +#else +static inline int mei_dbgfs_register(struct mei_device *dev, const char *name) +{ + return 0; +} +static inline void mei_dbgfs_deregister(struct mei_device *dev) {} +#endif /* CONFIG_DEBUG_FS */ + +int mei_register(struct mei_device *dev); +void mei_deregister(struct mei_device *dev); #define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d comp=%1d" #define MEI_HDR_PRM(hdr) \ diff --git a/drivers/misc/mei/nfc.c b/drivers/misc/mei/nfc.c new file mode 100644 index 000000000000..3adf8a70f26e --- /dev/null +++ b/drivers/misc/mei/nfc.c @@ -0,0 +1,554 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2013, 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/kernel.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/pci.h> +#include <linux/mei_cl_bus.h> + +#include "mei_dev.h" +#include "client.h" + +struct mei_nfc_cmd { + u8 command; + u8 status; + u16 req_id; + u32 reserved; + u16 data_size; + u8 sub_command; + u8 data[]; +} __packed; + +struct mei_nfc_reply { + u8 command; + u8 status; + u16 req_id; + u32 reserved; + u16 data_size; + u8 sub_command; + u8 reply_status; + u8 data[]; +} __packed; + +struct mei_nfc_if_version { + u8 radio_version_sw[3]; + u8 reserved[3]; + u8 radio_version_hw[3]; + u8 i2c_addr; + u8 fw_ivn; + u8 vendor_id; + u8 radio_type; +} __packed; + +struct mei_nfc_connect { + u8 fw_ivn; + u8 vendor_id; +} __packed; + +struct mei_nfc_connect_resp { + u8 fw_ivn; + u8 vendor_id; + u16 me_major; + u16 me_minor; + u16 me_hotfix; + u16 me_build; +} __packed; + +struct mei_nfc_hci_hdr { + u8 cmd; + u8 status; + u16 req_id; + u32 reserved; + u16 data_size; +} __packed; + +#define MEI_NFC_CMD_MAINTENANCE 0x00 +#define MEI_NFC_CMD_HCI_SEND 0x01 +#define MEI_NFC_CMD_HCI_RECV 0x02 + +#define MEI_NFC_SUBCMD_CONNECT 0x00 +#define MEI_NFC_SUBCMD_IF_VERSION 0x01 + +#define MEI_NFC_HEADER_SIZE 10 + +/** mei_nfc_dev - NFC mei device + * + * @cl: NFC host client + * @cl_info: NFC info host client + * @init_work: perform connection to the info client + * @fw_ivn: NFC Intervace Version Number + * @vendor_id: NFC manufacturer ID + * @radio_type: NFC radio type + */ +struct mei_nfc_dev { + struct mei_cl *cl; + struct mei_cl *cl_info; + struct work_struct init_work; + wait_queue_head_t send_wq; + u8 fw_ivn; + u8 vendor_id; + u8 radio_type; + char *bus_name; + + u16 req_id; + u16 recv_req_id; +}; + +static struct mei_nfc_dev nfc_dev; + +/* UUIDs for NFC F/W clients */ +const uuid_le mei_nfc_guid = UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50, + 0x94, 0xd4, 0x50, 0x26, + 0x67, 0x23, 0x77, 0x5c); + +static const uuid_le mei_nfc_info_guid = UUID_LE(0xd2de1625, 0x382d, 0x417d, + 0x48, 0xa4, 0xef, 0xab, + 0xba, 0x8a, 0x12, 0x06); + +/* Vendors */ +#define MEI_NFC_VENDOR_INSIDE 0x00 +#define MEI_NFC_VENDOR_NXP 0x01 + +/* Radio types */ +#define MEI_NFC_VENDOR_INSIDE_UREAD 0x00 +#define MEI_NFC_VENDOR_NXP_PN544 0x01 + +static void mei_nfc_free(struct mei_nfc_dev *ndev) +{ + if (ndev->cl) { + list_del(&ndev->cl->device_link); + mei_cl_unlink(ndev->cl); + kfree(ndev->cl); + } + + if (ndev->cl_info) { + list_del(&ndev->cl_info->device_link); + mei_cl_unlink(ndev->cl_info); + kfree(ndev->cl_info); + } +} + +static int mei_nfc_build_bus_name(struct mei_nfc_dev *ndev) +{ + struct mei_device *dev; + + if (!ndev->cl) + return -ENODEV; + + dev = ndev->cl->dev; + + switch (ndev->vendor_id) { + case MEI_NFC_VENDOR_INSIDE: + switch (ndev->radio_type) { + case MEI_NFC_VENDOR_INSIDE_UREAD: + ndev->bus_name = "microread"; + return 0; + + default: + dev_err(&dev->pdev->dev, "Unknow radio type 0x%x\n", + ndev->radio_type); + + return -EINVAL; + } + + case MEI_NFC_VENDOR_NXP: + switch (ndev->radio_type) { + case MEI_NFC_VENDOR_NXP_PN544: + ndev->bus_name = "pn544"; + return 0; + default: + dev_err(&dev->pdev->dev, "Unknow radio type 0x%x\n", + ndev->radio_type); + + return -EINVAL; + } + + default: + dev_err(&dev->pdev->dev, "Unknow vendor ID 0x%x\n", + ndev->vendor_id); + + return -EINVAL; + } + + return 0; +} + +static int mei_nfc_connect(struct mei_nfc_dev *ndev) +{ + struct mei_device *dev; + struct mei_cl *cl; + struct mei_nfc_cmd *cmd, *reply; + struct mei_nfc_connect *connect; + struct mei_nfc_connect_resp *connect_resp; + size_t connect_length, connect_resp_length; + int bytes_recv, ret; + + cl = ndev->cl; + dev = cl->dev; + + connect_length = sizeof(struct mei_nfc_cmd) + + sizeof(struct mei_nfc_connect); + + connect_resp_length = sizeof(struct mei_nfc_cmd) + + sizeof(struct mei_nfc_connect_resp); + + cmd = kzalloc(connect_length, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + connect = (struct mei_nfc_connect *)cmd->data; + + reply = kzalloc(connect_resp_length, GFP_KERNEL); + if (!reply) { + kfree(cmd); + return -ENOMEM; + } + + connect_resp = (struct mei_nfc_connect_resp *)reply->data; + + cmd->command = MEI_NFC_CMD_MAINTENANCE; + cmd->data_size = 3; + cmd->sub_command = MEI_NFC_SUBCMD_CONNECT; + connect->fw_ivn = ndev->fw_ivn; + connect->vendor_id = ndev->vendor_id; + + ret = __mei_cl_send(cl, (u8 *)cmd, connect_length); + if (ret < 0) { + dev_err(&dev->pdev->dev, "Could not send connect cmd\n"); + goto err; + } + + bytes_recv = __mei_cl_recv(cl, (u8 *)reply, connect_resp_length); + if (bytes_recv < 0) { + dev_err(&dev->pdev->dev, "Could not read connect response\n"); + ret = bytes_recv; + goto err; + } + + dev_info(&dev->pdev->dev, "IVN 0x%x Vendor ID 0x%x\n", + connect_resp->fw_ivn, connect_resp->vendor_id); + + dev_info(&dev->pdev->dev, "ME FW %d.%d.%d.%d\n", + connect_resp->me_major, connect_resp->me_minor, + connect_resp->me_hotfix, connect_resp->me_build); + + ret = 0; + +err: + kfree(reply); + kfree(cmd); + + return ret; +} + +static int mei_nfc_if_version(struct mei_nfc_dev *ndev) +{ + struct mei_device *dev; + struct mei_cl *cl; + + struct mei_nfc_cmd cmd; + struct mei_nfc_reply *reply = NULL; + struct mei_nfc_if_version *version; + size_t if_version_length; + int bytes_recv, ret; + + cl = ndev->cl_info; + dev = cl->dev; + + memset(&cmd, 0, sizeof(struct mei_nfc_cmd)); + cmd.command = MEI_NFC_CMD_MAINTENANCE; + cmd.data_size = 1; + cmd.sub_command = MEI_NFC_SUBCMD_IF_VERSION; + + ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd)); + if (ret < 0) { + dev_err(&dev->pdev->dev, "Could not send IF version cmd\n"); + return ret; + } + + /* to be sure on the stack we alloc memory */ + if_version_length = sizeof(struct mei_nfc_reply) + + sizeof(struct mei_nfc_if_version); + + reply = kzalloc(if_version_length, GFP_KERNEL); + if (!reply) + return -ENOMEM; + + bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length); + if (bytes_recv < 0 || bytes_recv < sizeof(struct mei_nfc_reply)) { + dev_err(&dev->pdev->dev, "Could not read IF version\n"); + ret = -EIO; + goto err; + } + + version = (struct mei_nfc_if_version *)reply->data; + + ndev->fw_ivn = version->fw_ivn; + ndev->vendor_id = version->vendor_id; + ndev->radio_type = version->radio_type; + +err: + kfree(reply); + return ret; +} + +static int mei_nfc_enable(struct mei_cl_device *cldev) +{ + struct mei_device *dev; + struct mei_nfc_dev *ndev = &nfc_dev; + int ret; + + dev = ndev->cl->dev; + + ret = mei_nfc_connect(ndev); + if (ret < 0) { + dev_err(&dev->pdev->dev, "Could not connect to NFC"); + return ret; + } + + return 0; +} + +static int mei_nfc_disable(struct mei_cl_device *cldev) +{ + return 0; +} + +static int mei_nfc_send(struct mei_cl_device *cldev, u8 *buf, size_t length) +{ + struct mei_device *dev; + struct mei_nfc_dev *ndev; + struct mei_nfc_hci_hdr *hdr; + u8 *mei_buf; + int err; + + ndev = (struct mei_nfc_dev *) cldev->priv_data; + dev = ndev->cl->dev; + + mei_buf = kzalloc(length + MEI_NFC_HEADER_SIZE, GFP_KERNEL); + if (!mei_buf) + return -ENOMEM; + + hdr = (struct mei_nfc_hci_hdr *) mei_buf; + hdr->cmd = MEI_NFC_CMD_HCI_SEND; + hdr->status = 0; + hdr->req_id = ndev->req_id; + hdr->reserved = 0; + hdr->data_size = length; + + memcpy(mei_buf + MEI_NFC_HEADER_SIZE, buf, length); + + err = __mei_cl_send(ndev->cl, mei_buf, length + MEI_NFC_HEADER_SIZE); + if (err < 0) + return err; + + kfree(mei_buf); + + if (!wait_event_interruptible_timeout(ndev->send_wq, + ndev->recv_req_id == ndev->req_id, HZ)) { + dev_err(&dev->pdev->dev, "NFC MEI command timeout\n"); + err = -ETIMEDOUT; + } else { + ndev->req_id++; + } + + return err; +} + +static int mei_nfc_recv(struct mei_cl_device *cldev, u8 *buf, size_t length) +{ + struct mei_nfc_dev *ndev; + struct mei_nfc_hci_hdr *hci_hdr; + int received_length; + + ndev = (struct mei_nfc_dev *)cldev->priv_data; + + received_length = __mei_cl_recv(ndev->cl, buf, length); + if (received_length < 0) + return received_length; + + hci_hdr = (struct mei_nfc_hci_hdr *) buf; + + if (hci_hdr->cmd == MEI_NFC_CMD_HCI_SEND) { + ndev->recv_req_id = hci_hdr->req_id; + wake_up(&ndev->send_wq); + + return 0; + } + + return received_length; +} + +static struct mei_cl_ops nfc_ops = { + .enable = mei_nfc_enable, + .disable = mei_nfc_disable, + .send = mei_nfc_send, + .recv = mei_nfc_recv, +}; + +static void mei_nfc_init(struct work_struct *work) +{ + struct mei_device *dev; + struct mei_cl_device *cldev; + struct mei_nfc_dev *ndev; + struct mei_cl *cl_info; + + ndev = container_of(work, struct mei_nfc_dev, init_work); + + cl_info = ndev->cl_info; + dev = cl_info->dev; + + mutex_lock(&dev->device_lock); + + if (mei_cl_connect(cl_info, NULL) < 0) { + mutex_unlock(&dev->device_lock); + dev_err(&dev->pdev->dev, + "Could not connect to the NFC INFO ME client"); + + goto err; + } + + mutex_unlock(&dev->device_lock); + + if (mei_nfc_if_version(ndev) < 0) { + dev_err(&dev->pdev->dev, "Could not get the NFC interfave version"); + + goto err; + } + + dev_info(&dev->pdev->dev, + "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", + ndev->fw_ivn, ndev->vendor_id, ndev->radio_type); + + mutex_lock(&dev->device_lock); + + if (mei_cl_disconnect(cl_info) < 0) { + mutex_unlock(&dev->device_lock); + dev_err(&dev->pdev->dev, + "Could not disconnect the NFC INFO ME client"); + + goto err; + } + + mutex_unlock(&dev->device_lock); + + if (mei_nfc_build_bus_name(ndev) < 0) { + dev_err(&dev->pdev->dev, + "Could not build the bus ID name\n"); + return; + } + + cldev = mei_cl_add_device(dev, mei_nfc_guid, ndev->bus_name, &nfc_ops); + if (!cldev) { + dev_err(&dev->pdev->dev, + "Could not add the NFC device to the MEI bus\n"); + + goto err; + } + + cldev->priv_data = ndev; + + + return; + +err: + mei_nfc_free(ndev); + + return; +} + + +int mei_nfc_host_init(struct mei_device *dev) +{ + struct mei_nfc_dev *ndev = &nfc_dev; + struct mei_cl *cl_info, *cl = NULL; + int i, ret; + + /* already initialzed */ + if (ndev->cl_info) + return 0; + + cl_info = mei_cl_allocate(dev); + cl = mei_cl_allocate(dev); + + if (!cl || !cl_info) { + ret = -ENOMEM; + goto err; + } + + /* check for valid client id */ + i = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid); + if (i < 0) { + dev_info(&dev->pdev->dev, "nfc: failed to find the client\n"); + ret = -ENOENT; + goto err; + } + + cl_info->me_client_id = dev->me_clients[i].client_id; + + ret = mei_cl_link(cl_info, MEI_HOST_CLIENT_ID_ANY); + if (ret) + goto err; + + cl_info->device_uuid = mei_nfc_info_guid; + + list_add_tail(&cl_info->device_link, &dev->device_list); + + /* check for valid client id */ + i = mei_me_cl_by_uuid(dev, &mei_nfc_guid); + if (i < 0) { + dev_info(&dev->pdev->dev, "nfc: failed to find the client\n"); + ret = -ENOENT; + goto err; + } + + cl->me_client_id = dev->me_clients[i].client_id; + + ret = mei_cl_link(cl, MEI_HOST_CLIENT_ID_ANY); + if (ret) + goto err; + + cl->device_uuid = mei_nfc_guid; + + list_add_tail(&cl->device_link, &dev->device_list); + + ndev->cl_info = cl_info; + ndev->cl = cl; + ndev->req_id = 1; + + INIT_WORK(&ndev->init_work, mei_nfc_init); + init_waitqueue_head(&ndev->send_wq); + schedule_work(&ndev->init_work); + + return 0; + +err: + mei_nfc_free(ndev); + + return ret; +} + +void mei_nfc_host_exit(void) +{ + struct mei_nfc_dev *ndev = &nfc_dev; + + if (ndev->cl && ndev->cl->device) + mei_cl_remove_device(ndev->cl->device); + + mei_nfc_free(ndev); +} diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index b8b5c9c3ad03..a727464e9c3f 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -47,7 +47,7 @@ static struct pci_dev *mei_pdev; /* mei_pci_tbl - PCI Device ID Table */ -static DEFINE_PCI_DEVICE_TABLE(mei_pci_tbl) = { +static DEFINE_PCI_DEVICE_TABLE(mei_me_pci_tbl) = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, @@ -86,18 +86,19 @@ static DEFINE_PCI_DEVICE_TABLE(mei_pci_tbl) = { {0, } }; -MODULE_DEVICE_TABLE(pci, mei_pci_tbl); +MODULE_DEVICE_TABLE(pci, mei_me_pci_tbl); static DEFINE_MUTEX(mei_mutex); /** * mei_quirk_probe - probe for devices that doesn't valid ME interface + * * @pdev: PCI device structure * @ent: entry into pci_device_table * * returns true if ME Interface is valid, false otherwise */ -static bool mei_quirk_probe(struct pci_dev *pdev, +static bool mei_me_quirk_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { u32 reg; @@ -119,7 +120,7 @@ static bool mei_quirk_probe(struct pci_dev *pdev, * * returns 0 on success, <0 on failure. */ -static int mei_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct mei_device *dev; struct mei_me_hw *hw; @@ -127,7 +128,7 @@ static int mei_probe(struct pci_dev *pdev, const struct pci_device_id *ent) mutex_lock(&mei_mutex); - if (!mei_quirk_probe(pdev, ent)) { + if (!mei_me_quirk_probe(pdev, ent)) { err = -ENODEV; goto end; } @@ -184,20 +185,19 @@ static int mei_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto disable_msi; } - if (mei_hw_init(dev)) { + if (mei_start(dev)) { dev_err(&pdev->dev, "init hw failure.\n"); err = -ENODEV; goto release_irq; } - err = mei_register(&pdev->dev); + err = mei_register(dev); if (err) goto release_irq; mei_pdev = pdev; pci_set_drvdata(pdev, dev); - schedule_delayed_work(&dev->timer_work, HZ); mutex_unlock(&mei_mutex); @@ -233,7 +233,7 @@ end: * mei_remove is called by the PCI subsystem to alert the driver * that it should release a PCI device. */ -static void mei_remove(struct pci_dev *pdev) +static void mei_me_remove(struct pci_dev *pdev) { struct mei_device *dev; struct mei_me_hw *hw; @@ -253,8 +253,6 @@ static void mei_remove(struct pci_dev *pdev) mei_pdev = NULL; - mei_watchdog_unregister(dev); - /* disable interrupts */ mei_disable_interrupts(dev); @@ -265,16 +263,17 @@ static void mei_remove(struct pci_dev *pdev) if (hw->mem_addr) pci_iounmap(pdev, hw->mem_addr); + mei_deregister(dev); + kfree(dev); pci_release_regions(pdev); pci_disable_device(pdev); - mei_deregister(); } #ifdef CONFIG_PM -static int mei_pci_suspend(struct device *device) +static int mei_me_pci_suspend(struct device *device) { struct pci_dev *pdev = to_pci_dev(device); struct mei_device *dev = pci_get_drvdata(pdev); @@ -294,7 +293,7 @@ static int mei_pci_suspend(struct device *device) return 0; } -static int mei_pci_resume(struct device *device) +static int mei_me_pci_resume(struct device *device) { struct pci_dev *pdev = to_pci_dev(device); struct mei_device *dev; @@ -334,24 +333,24 @@ static int mei_pci_resume(struct device *device) return err; } -static SIMPLE_DEV_PM_OPS(mei_pm_ops, mei_pci_suspend, mei_pci_resume); -#define MEI_PM_OPS (&mei_pm_ops) +static SIMPLE_DEV_PM_OPS(mei_me_pm_ops, mei_me_pci_suspend, mei_me_pci_resume); +#define MEI_ME_PM_OPS (&mei_me_pm_ops) #else -#define MEI_PM_OPS NULL +#define MEI_ME_PM_OPS NULL #endif /* CONFIG_PM */ /* * PCI driver structure */ -static struct pci_driver mei_driver = { +static struct pci_driver mei_me_driver = { .name = KBUILD_MODNAME, - .id_table = mei_pci_tbl, - .probe = mei_probe, - .remove = mei_remove, - .shutdown = mei_remove, - .driver.pm = MEI_PM_OPS, + .id_table = mei_me_pci_tbl, + .probe = mei_me_probe, + .remove = mei_me_remove, + .shutdown = mei_me_remove, + .driver.pm = MEI_ME_PM_OPS, }; -module_pci_driver(mei_driver); +module_pci_driver(mei_me_driver); MODULE_AUTHOR("Intel Corporation"); MODULE_DESCRIPTION("Intel(R) Management Engine Interface"); diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 2413247fc392..6251a4ee7067 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -58,6 +58,7 @@ static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout) * mei_wd_host_init - connect to the watchdog client * * @dev: the device structure + * * returns -ENENT if wd client cannot be found * -EIO if write has failed * 0 on success @@ -317,7 +318,8 @@ end: * * returns 0 if success, negative errno code for failure */ -static int mei_wd_ops_set_timeout(struct watchdog_device *wd_dev, unsigned int timeout) +static int mei_wd_ops_set_timeout(struct watchdog_device *wd_dev, + unsigned int timeout) { struct mei_device *dev; |