diff options
Diffstat (limited to 'drivers/misc/mei')
| -rw-r--r-- | drivers/misc/mei/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/misc/mei/Makefile | 6 | ||||
| -rw-r--r-- | drivers/misc/mei/amthif.c | 122 | ||||
| -rw-r--r-- | drivers/misc/mei/bus.c | 32 | ||||
| -rw-r--r-- | drivers/misc/mei/client.c | 527 | ||||
| -rw-r--r-- | drivers/misc/mei/client.h | 41 | ||||
| -rw-r--r-- | drivers/misc/mei/debugfs.c | 58 | ||||
| -rw-r--r-- | drivers/misc/mei/hbm.c | 600 | ||||
| -rw-r--r-- | drivers/misc/mei/hbm.h | 10 | ||||
| -rw-r--r-- | drivers/misc/mei/hw-me-regs.h | 20 | ||||
| -rw-r--r-- | drivers/misc/mei/hw-me.c | 384 | ||||
| -rw-r--r-- | drivers/misc/mei/hw-me.h | 16 | ||||
| -rw-r--r-- | drivers/misc/mei/hw-txe-regs.h | 294 | ||||
| -rw-r--r-- | drivers/misc/mei/hw-txe.c | 1190 | ||||
| -rw-r--r-- | drivers/misc/mei/hw-txe.h | 77 | ||||
| -rw-r--r-- | drivers/misc/mei/hw.h | 48 | ||||
| -rw-r--r-- | drivers/misc/mei/init.c | 339 | ||||
| -rw-r--r-- | drivers/misc/mei/interrupt.c | 319 | ||||
| -rw-r--r-- | drivers/misc/mei/main.c | 99 | ||||
| -rw-r--r-- | drivers/misc/mei/mei_dev.h | 195 | ||||
| -rw-r--r-- | drivers/misc/mei/nfc.c | 38 | ||||
| -rw-r--r-- | drivers/misc/mei/pci-me.c | 283 | ||||
| -rw-r--r-- | drivers/misc/mei/pci-txe.c | 436 | ||||
| -rw-r--r-- | drivers/misc/mei/wd.c | 147 |
24 files changed, 4247 insertions, 1043 deletions
diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index c76fa31e9bf..d23384dde73 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -34,3 +34,12 @@ config INTEL_MEI_ME 82Q33 Express 82X38/X48 Express +config INTEL_MEI_TXE + tristate "Intel Trusted Execution Environment with ME Interface" + select INTEL_MEI + depends on X86 && PCI && WATCHDOG_CORE + help + MEI Support for Trusted Execution Environment device on Intel SoCs + + Supported SoCs: + Intel Bay Trail diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 08698a46626..8ebc6cda137 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -1,6 +1,6 @@ # # Makefile - Intel Management Engine Interface (Intel MEI) Linux driver -# Copyright (c) 2010-2011, Intel Corporation. +# Copyright (c) 2010-2014, Intel Corporation. # obj-$(CONFIG_INTEL_MEI) += mei.o mei-objs := init.o @@ -17,3 +17,7 @@ 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 + +obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o +mei-txe-objs := pci-txe.o +mei-txe-objs += hw-txe.o diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index d0fdc134068..0d6234db00f 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -21,7 +21,6 @@ #include <linux/fcntl.h> #include <linux/aio.h> #include <linux/pci.h> -#include <linux/init.h> #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/list.h> @@ -35,7 +34,6 @@ #include "mei_dev.h" #include "hbm.h" -#include "hw-me.h" #include "client.h" const uuid_le mei_amthif_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, @@ -57,6 +55,8 @@ void mei_amthif_reset_params(struct mei_device *dev) dev->iamthif_ioctl = false; dev->iamthif_state = MEI_IAMTHIF_IDLE; dev->iamthif_timer = 0; + dev->iamthif_stall_timer = 0; + dev->iamthif_open_count = 0; } /** @@ -77,8 +77,9 @@ int mei_amthif_host_init(struct mei_device *dev) i = mei_me_cl_by_uuid(dev, &mei_amthif_guid); if (i < 0) { - dev_info(&dev->pdev->dev, "amthif: failed to find the client\n"); - return -ENOENT; + dev_info(&dev->pdev->dev, + "amthif: failed to find the client %d\n", i); + return -ENOTTY; } cl->me_client_id = dev->me_clients[i].client_id; @@ -105,20 +106,16 @@ int mei_amthif_host_init(struct mei_device *dev) ret = mei_cl_link(cl, MEI_IAMTHIF_HOST_CLIENT_ID); if (ret < 0) { - dev_err(&dev->pdev->dev, "amthif: failed link client\n"); - return -ENOENT; + dev_err(&dev->pdev->dev, + "amthif: failed link client %d\n", ret); + return ret; } - cl->state = MEI_FILE_CONNECTING; + ret = mei_cl_connect(cl, NULL); - if (mei_hbm_cl_connect_req(dev, cl)) { - dev_dbg(&dev->pdev->dev, "amthif: Failed to connect to ME client\n"); - cl->state = MEI_FILE_DISCONNECTED; - cl->host_client_id = 0; - } else { - cl->timer_count = MEI_CONNECT_TIMEOUT; - } - return 0; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + + return ret; } /** @@ -132,14 +129,12 @@ int mei_amthif_host_init(struct mei_device *dev) struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev, struct file *file) { - struct mei_cl_cb *pos = NULL; - struct mei_cl_cb *next = NULL; + struct mei_cl_cb *cb; - list_for_each_entry_safe(pos, next, - &dev->amthif_rd_complete_list.list, list) { - if (pos->cl && pos->cl == &dev->iamthif_cl && - pos->file_object == file) - return pos; + list_for_each_entry(cb, &dev->amthif_rd_complete_list.list, list) { + if (cb->cl && cb->cl == &dev->iamthif_cl && + cb->file_object == file) + return cb; } return NULL; } @@ -172,17 +167,16 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, unsigned long timeout; int i; - /* Only Posible if we are in timeout */ + /* Only possible if we are in timeout */ if (!cl || cl != &dev->iamthif_cl) { dev_dbg(&dev->pdev->dev, "bad file ext.\n"); - return -ETIMEDOUT; + return -ETIME; } i = mei_me_cl_by_id(dev, dev->iamthif_cl.me_client_id); - if (i < 0) { dev_dbg(&dev->pdev->dev, "amthif client not found.\n"); - return -ENODEV; + return -ENOTTY; } dev_dbg(&dev->pdev->dev, "checking amthif data\n"); cb = mei_amthif_find_read_list_entry(dev, file); @@ -223,7 +217,7 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, dev_dbg(&dev->pdev->dev, "amthif Time out\n"); /* 15 sec for the message has expired */ list_del(&cb->list); - rets = -ETIMEDOUT; + rets = -ETIME; goto free; } } @@ -244,13 +238,14 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, cb->response_buffer.size); dev_dbg(&dev->pdev->dev, "amthif cb->buf_idx - %lu\n", cb->buf_idx); - /* length is being turncated to PAGE_SIZE, however, + /* length is being truncated to PAGE_SIZE, however, * the buf_idx may point beyond */ length = min_t(size_t, length, (cb->buf_idx - *offset)); - if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) + if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) { + dev_dbg(&dev->pdev->dev, "failed to copy data to userland\n"); rets = -EFAULT; - else { + } else { rets = length; if ((*offset + length) < cb->buf_idx) { *offset += length; @@ -297,9 +292,8 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) if (ret < 0) return ret; - if (ret && dev->hbuf_is_ready) { + if (ret && mei_hbuf_acquire(dev)) { ret = 0; - dev->hbuf_is_ready = false; if (cb->request_buffer.size > mei_hbuf_max_len(dev)) { mei_hdr.length = mei_hbuf_max_len(dev); mei_hdr.msg_complete = 0; @@ -311,14 +305,15 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) mei_hdr.host_addr = dev->iamthif_cl.host_client_id; mei_hdr.me_addr = dev->iamthif_cl.me_client_id; mei_hdr.reserved = 0; + mei_hdr.internal = 0; dev->iamthif_msg_buf_index += mei_hdr.length; - if (mei_write_message(dev, &mei_hdr, - (unsigned char *)dev->iamthif_msg_buf)) - return -ENODEV; + ret = mei_write_message(dev, &mei_hdr, dev->iamthif_msg_buf); + if (ret) + return ret; if (mei_hdr.msg_complete) { if (mei_cl_flow_ctrl_reduce(&dev->iamthif_cl)) - return -ENODEV; + return -EIO; dev->iamthif_flow_control_pending = true; dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; dev_dbg(&dev->pdev->dev, "add amthif cb to write waiting list\n"); @@ -330,10 +325,6 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) list_add_tail(&cb->list, &dev->write_list.list); } } else { - if (!dev->hbuf_is_ready) - dev_dbg(&dev->pdev->dev, "host buffer is not empty"); - - dev_dbg(&dev->pdev->dev, "No flow control credentials, so add iamthif cb to write list.\n"); list_add_tail(&cb->list, &dev->write_list.list); } return 0; @@ -359,7 +350,7 @@ int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *cb) if (ret) return ret; - cb->fop_type = MEI_FOP_IOCTL; + cb->fop_type = MEI_FOP_WRITE; if (!list_empty(&dev->amthif_cmd_list.list) || dev->iamthif_state != MEI_IAMTHIF_IDLE) { @@ -441,35 +432,48 @@ unsigned int mei_amthif_poll(struct mei_device *dev, /** - * mei_amthif_irq_write_completed - processes completed iamthif operation. + * mei_amthif_irq_write - write iamthif command in irq thread context. * * @dev: the device structure. - * @slots: free slots. * @cb_pos: callback block. * @cl: private data of the file object. * @cmpl_list: complete list. * * returns 0, OK; otherwise, error. */ -int mei_amthif_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb, - s32 *slots, struct mei_cl_cb *cmpl_list) +int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, + struct mei_cl_cb *cmpl_list) { struct mei_device *dev = cl->dev; struct mei_msg_hdr mei_hdr; size_t len = dev->iamthif_msg_buf_size - dev->iamthif_msg_buf_index; u32 msg_slots = mei_data2slots(len); + int slots; + int rets; + + rets = mei_cl_flow_ctrl_creds(cl); + if (rets < 0) + return rets; + + if (rets == 0) { + cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); + return 0; + } mei_hdr.host_addr = cl->host_client_id; mei_hdr.me_addr = cl->me_client_id; mei_hdr.reserved = 0; + mei_hdr.internal = 0; - if (*slots >= msg_slots) { + slots = mei_hbuf_empty_slots(dev); + + if (slots >= msg_slots) { mei_hdr.length = len; mei_hdr.msg_complete = 1; /* Split the message only if we can write the whole host buffer */ - } else if (*slots == dev->hbuf_depth) { - msg_slots = *slots; - len = (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); + } else if (slots == dev->hbuf_depth) { + msg_slots = slots; + len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); mei_hdr.length = len; mei_hdr.msg_complete = 0; } else { @@ -479,17 +483,17 @@ int mei_amthif_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb, dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(&mei_hdr)); - *slots -= msg_slots; - if (mei_write_message(dev, &mei_hdr, - dev->iamthif_msg_buf + dev->iamthif_msg_buf_index)) { - dev->iamthif_state = MEI_IAMTHIF_IDLE; - cl->status = -ENODEV; - list_del(&cb->list); - return -ENODEV; + rets = mei_write_message(dev, &mei_hdr, + dev->iamthif_msg_buf + dev->iamthif_msg_buf_index); + if (rets) { + dev->iamthif_state = MEI_IAMTHIF_IDLE; + cl->status = rets; + list_del(&cb->list); + return rets; } if (mei_cl_flow_ctrl_reduce(cl)) - return -ENODEV; + return -EIO; dev->iamthif_msg_buf_index += mei_hdr.length; cl->status = 0; @@ -719,8 +723,8 @@ static bool mei_clear_lists(struct mei_device *dev, struct file *file) */ int mei_amthif_release(struct mei_device *dev, struct file *file) { - if (dev->open_handle_count > 0) - dev->open_handle_count--; + if (dev->iamthif_open_count > 0) + dev->iamthif_open_count--; if (dev->iamthif_file_object == file && dev->iamthif_state != MEI_IAMTHIF_IDLE) { diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 6d0282c08a0..0e993ef28b9 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -26,7 +26,6 @@ #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) @@ -145,9 +144,9 @@ static struct device_type mei_cl_device_type = { static struct mei_cl *mei_bus_find_mei_cl_by_uuid(struct mei_device *dev, uuid_le uuid) { - struct mei_cl *cl, *next; + struct mei_cl *cl; - list_for_each_entry_safe(cl, next, &dev->device_list, device_link) { + list_for_each_entry(cl, &dev->device_list, device_link) { if (!uuid_le_cmp(uuid, cl->device_uuid)) return cl; } @@ -245,10 +244,10 @@ static int ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, /* Check if we have an ME client device */ id = mei_me_cl_by_id(dev, cl->me_client_id); if (id < 0) - return -ENODEV; + return id; if (length > dev->me_clients[id].props.max_msg_length) - return -EINVAL; + return -EFBIG; cb = mei_io_cb_init(cl, NULL); if (!cb) @@ -297,10 +296,13 @@ int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) 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))) { + cl->reading_state == MEI_READ_COMPLETE || + mei_cl_is_transitioning(cl))) { + if (signal_pending(current)) return -EINTR; return -ERESTARTSYS; @@ -425,8 +427,6 @@ int mei_cl_enable_device(struct mei_cl_device *device) mutex_lock(&dev->device_lock); - cl->state = MEI_FILE_CONNECTING; - err = mei_cl_connect(cl, NULL); if (err < 0) { mutex_unlock(&dev->device_lock); @@ -521,6 +521,22 @@ void mei_cl_bus_rx_event(struct mei_cl *cl) schedule_work(&device->event_work); } +void mei_cl_bus_remove_devices(struct mei_device *dev) +{ + struct mei_cl *cl, *next; + + mutex_lock(&dev->device_lock); + list_for_each_entry_safe(cl, next, &dev->device_list, device_link) { + if (cl->device) + mei_cl_remove_device(cl->device); + + list_del(&cl->device_link); + mei_cl_unlink(cl); + kfree(cl); + } + mutex_unlock(&dev->device_lock); +} + int __init mei_cl_bus_init(void) { return bus_register(&mei_cl_bus_type); diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index e0684b4d9a0..59d20c599b1 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -18,6 +18,7 @@ #include <linux/sched.h> #include <linux/wait.h> #include <linux/delay.h> +#include <linux/pm_runtime.h> #include <linux/mei.h> @@ -29,20 +30,21 @@ * mei_me_cl_by_uuid - locate index of me client * * @dev: mei device + * + * Locking: called under "dev->device_lock" lock + * * returns me client index or -ENOENT if not found */ int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid) { - int i, res = -ENOENT; + int i; for (i = 0; i < dev->me_clients_num; ++i) if (uuid_le_cmp(*uuid, - dev->me_clients[i].props.protocol_name) == 0) { - res = i; - break; - } + dev->me_clients[i].props.protocol_name) == 0) + return i; - return res; + return -ENOENT; } @@ -60,37 +62,79 @@ int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid) int mei_me_cl_by_id(struct mei_device *dev, u8 client_id) { int i; + for (i = 0; i < dev->me_clients_num; i++) if (dev->me_clients[i].client_id == client_id) - break; - if (WARN_ON(dev->me_clients[i].client_id != client_id)) - return -ENOENT; - - if (i == dev->me_clients_num) - return -ENOENT; + return i; - return i; + return -ENOENT; } /** - * mei_io_list_flush - removes list entry belonging to cl. + * mei_cl_cmp_id - tells if the clients are the same * - * @list: An instance of our list structure - * @cl: host client + * @cl1: host client 1 + * @cl2: host client 2 + * + * returns true - if the clients has same host and me ids + * false - otherwise */ -void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) +static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, + const struct mei_cl *cl2) +{ + return cl1 && cl2 && + (cl1->host_client_id == cl2->host_client_id) && + (cl1->me_client_id == cl2->me_client_id); +} + +/** + * mei_io_list_flush - removes cbs belonging to cl. + * + * @list: an instance of our list structure + * @cl: host client, can be NULL for flushing the whole list + * @free: whether to free the cbs + */ +static void __mei_io_list_flush(struct mei_cl_cb *list, + struct mei_cl *cl, bool free) { struct mei_cl_cb *cb; struct mei_cl_cb *next; + /* enable removing everything if no cl is specified */ list_for_each_entry_safe(cb, next, &list->list, list) { - if (cb->cl && mei_cl_cmp_id(cl, cb->cl)) + if (!cl || (cb->cl && mei_cl_cmp_id(cl, cb->cl))) { list_del(&cb->list); + if (free) + mei_io_cb_free(cb); + } } } /** + * mei_io_list_flush - removes list entry belonging to cl. + * + * @list: An instance of our list structure + * @cl: host client + */ +static inline void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) +{ + __mei_io_list_flush(list, cl, false); +} + + +/** + * mei_io_list_free - removes cb belonging to cl and free them + * + * @list: An instance of our list structure + * @cl: host client + */ +static inline void mei_io_list_free(struct mei_cl_cb *list, struct mei_cl *cl) +{ + __mei_io_list_flush(list, cl, true); +} + +/** * mei_io_cb_free - free mei_cb_private related memory * * @cb: mei callback struct @@ -154,7 +198,7 @@ int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) return 0; } /** - * mei_io_cb_alloc_resp_buf - allocate respose buffer + * mei_io_cb_alloc_resp_buf - allocate response buffer * * @cb: io callback structure * @length: size of the buffer @@ -187,13 +231,17 @@ int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) */ int mei_cl_flush_queues(struct mei_cl *cl) { + struct mei_device *dev; + if (WARN_ON(!cl || !cl->dev)) return -EINVAL; - dev_dbg(&cl->dev->pdev->dev, "remove list entry belonging to cl\n"); + dev = cl->dev; + + cl_dbg(dev, cl, "remove list entry belonging to cl\n"); mei_io_list_flush(&cl->dev->read_list, cl); - mei_io_list_flush(&cl->dev->write_list, cl); - mei_io_list_flush(&cl->dev->write_waiting_list, cl); + mei_io_list_free(&cl->dev->write_list, cl); + mei_io_list_free(&cl->dev->write_waiting_list, cl); mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); @@ -203,7 +251,7 @@ int mei_cl_flush_queues(struct mei_cl *cl) /** - * mei_cl_init - initializes intialize cl. + * mei_cl_init - initializes cl. * * @cl: host client to be initialized * @dev: mei device @@ -250,19 +298,18 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) { struct mei_device *dev = cl->dev; - struct mei_cl_cb *cb = NULL; - struct mei_cl_cb *next = NULL; + struct mei_cl_cb *cb; - list_for_each_entry_safe(cb, next, &dev->read_list.list, list) + list_for_each_entry(cb, &dev->read_list.list, list) if (mei_cl_cmp_id(cl, cb->cl)) return cb; return NULL; } -/** mei_cl_link: allocte host id in the host map +/** mei_cl_link: allocate host id in the host map * * @cl - host client - * @id - fixed host id or -1 for genereting one + * @id - fixed host id or -1 for generic one * * returns 0 on success * -EINVAL on incorrect values @@ -271,20 +318,28 @@ struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) int mei_cl_link(struct mei_cl *cl, int id) { struct mei_device *dev; + long open_handle_count; if (WARN_ON(!cl || !cl->dev)) return -EINVAL; dev = cl->dev; - /* If Id is not asigned get one*/ + /* If Id is not assigned get one*/ if (id == MEI_HOST_CLIENT_ID_ANY) id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); if (id >= MEI_CLIENTS_MAX) { - dev_err(&dev->pdev->dev, "id exceded %d", MEI_CLIENTS_MAX) ; - return -ENOENT; + dev_err(&dev->pdev->dev, "id exceeded %d", MEI_CLIENTS_MAX); + return -EMFILE; + } + + open_handle_count = dev->open_handle_count + dev->iamthif_open_count; + if (open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { + dev_err(&dev->pdev->dev, "open_handle_count exceeded %d", + MEI_MAX_OPEN_HANDLE_COUNT); + return -EMFILE; } dev->open_handle_count++; @@ -296,7 +351,7 @@ int mei_cl_link(struct mei_cl *cl, int id) cl->state = MEI_FILE_INITIALIZING; - dev_dbg(&dev->pdev->dev, "link cl host id = %d\n", cl->host_client_id); + cl_dbg(dev, cl, "link cl\n"); return 0; } @@ -308,7 +363,6 @@ int mei_cl_link(struct mei_cl *cl, int id) int mei_cl_unlink(struct mei_cl *cl) { struct mei_device *dev; - struct mei_cl *pos, *next; /* don't shout on error exit path */ if (!cl) @@ -320,14 +374,19 @@ int mei_cl_unlink(struct mei_cl *cl) dev = cl->dev; - list_for_each_entry_safe(pos, next, &dev->file_list, link) { - if (cl->host_client_id == pos->host_client_id) { - dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", - pos->host_client_id, pos->me_client_id); - list_del_init(&pos->link); - break; - } - } + cl_dbg(dev, cl, "unlink client"); + + if (dev->open_handle_count > 0) + dev->open_handle_count--; + + /* never clear the 0 bit */ + if (cl->host_client_id) + clear_bit(cl->host_client_id, dev->host_clients_map); + + list_del_init(&cl->link); + + cl->state = MEI_FILE_INITIALIZING; + return 0; } @@ -341,17 +400,6 @@ void mei_host_client_init(struct work_struct *work) mutex_lock(&dev->device_lock); - bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); - dev->open_handle_count = 0; - - /* - * Reserving the first three client IDs - * 0: Reserved for MEI Bus Message communications - * 1: Reserved for Watchdog - * 2: Reserved for AMTHI - */ - bitmap_set(dev->host_clients_map, 0, 3); - for (i = 0; i < dev->me_clients_num; i++) { client_props = &dev->me_clients[i].props; @@ -365,13 +413,41 @@ void mei_host_client_init(struct work_struct *work) } dev->dev_state = MEI_DEV_ENABLED; + dev->reset_count = 0; mutex_unlock(&dev->device_lock); + + pm_runtime_mark_last_busy(&dev->pdev->dev); + dev_dbg(&dev->pdev->dev, "rpm: autosuspend\n"); + pm_runtime_autosuspend(&dev->pdev->dev); } +/** + * mei_hbuf_acquire: try to acquire host buffer + * + * @dev: the device structure + * returns true if host buffer was acquired + */ +bool mei_hbuf_acquire(struct mei_device *dev) +{ + if (mei_pg_state(dev) == MEI_PG_ON || + dev->pg_event == MEI_PG_EVENT_WAIT) { + dev_dbg(&dev->pdev->dev, "device is in pg\n"); + return false; + } + + if (!dev->hbuf_is_ready) { + dev_dbg(&dev->pdev->dev, "hbuf is not ready\n"); + return false; + } + + dev->hbuf_is_ready = false; + + return true; +} /** - * mei_cl_disconnect - disconnect host clinet form the me one + * mei_cl_disconnect - disconnect host client from the me one * * @cl: host client * @@ -390,25 +466,35 @@ int mei_cl_disconnect(struct mei_cl *cl) dev = cl->dev; + cl_dbg(dev, cl, "disconnecting"); + if (cl->state != MEI_FILE_DISCONNECTING) return 0; + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + cb = mei_io_cb_init(cl, NULL); - if (!cb) - return -ENOMEM; + if (!cb) { + rets = -ENOMEM; + goto free; + } cb->fop_type = MEI_FOP_CLOSE; - if (dev->hbuf_is_ready) { - dev->hbuf_is_ready = false; + if (mei_hbuf_acquire(dev)) { if (mei_hbm_cl_disconnect_req(dev, cl)) { rets = -ENODEV; - dev_err(&dev->pdev->dev, "failed to disconnect.\n"); + cl_err(dev, cl, "failed to disconnect.\n"); goto free; } mdelay(10); /* Wait for hardware disconnection ready */ list_add_tail(&cb->list, &dev->ctrl_rd_list.list); } else { - dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n"); + cl_dbg(dev, cl, "add disconnect cb to control write list\n"); list_add_tail(&cb->list, &dev->ctrl_wr_list.list); } @@ -421,23 +507,25 @@ int mei_cl_disconnect(struct mei_cl *cl) mutex_lock(&dev->device_lock); if (MEI_FILE_DISCONNECTED == cl->state) { rets = 0; - dev_dbg(&dev->pdev->dev, "successfully disconnected from FW client.\n"); + cl_dbg(dev, cl, "successfully disconnected from FW client.\n"); } else { rets = -ENODEV; if (MEI_FILE_DISCONNECTED != cl->state) - dev_dbg(&dev->pdev->dev, "wrong status client disconnect.\n"); + cl_err(dev, cl, "wrong status client disconnect.\n"); if (err) - dev_dbg(&dev->pdev->dev, - "wait failed disconnect err=%08x\n", - err); + cl_dbg(dev, cl, "wait failed disconnect err=%d\n", err); - dev_dbg(&dev->pdev->dev, "failed to disconnect from FW client.\n"); + cl_err(dev, cl, "failed to disconnect from FW client.\n"); } mei_io_list_flush(&dev->ctrl_rd_list, cl); mei_io_list_flush(&dev->ctrl_wr_list, cl); free: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + mei_io_cb_free(cb); return rets; } @@ -449,22 +537,22 @@ free: * * @cl: private data of the file object * - * returns ture if other client is connected, 0 - otherwise. + * returns true if other client is connected, false - otherwise. */ bool mei_cl_is_other_connecting(struct mei_cl *cl) { struct mei_device *dev; - struct mei_cl *pos; - struct mei_cl *next; + struct mei_cl *ocl; /* the other client */ if (WARN_ON(!cl || !cl->dev)) return false; dev = cl->dev; - list_for_each_entry_safe(pos, next, &dev->file_list, link) { - if ((pos->state == MEI_FILE_CONNECTING) && - (pos != cl) && cl->me_client_id == pos->me_client_id) + list_for_each_entry(ocl, &dev->file_list, link) { + if (ocl->state == MEI_FILE_CONNECTING && + ocl != cl && + cl->me_client_id == ocl->me_client_id) return true; } @@ -473,7 +561,7 @@ bool mei_cl_is_other_connecting(struct mei_cl *cl) } /** - * mei_cl_connect - connect host clinet to the me one + * mei_cl_connect - connect host client to the me one * * @cl: host client * @@ -492,17 +580,24 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) dev = cl->dev; + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + cb = mei_io_cb_init(cl, file); if (!cb) { rets = -ENOMEM; goto out; } - cb->fop_type = MEI_FOP_IOCTL; - - if (dev->hbuf_is_ready && !mei_cl_is_other_connecting(cl)) { - dev->hbuf_is_ready = false; + cb->fop_type = MEI_FOP_CONNECT; + /* run hbuf acquire last so we don't have to undo */ + if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) { + cl->state = MEI_FILE_CONNECTING; if (mei_hbm_cl_connect_req(dev, cl)) { rets = -ENODEV; goto out; @@ -514,23 +609,28 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) } mutex_unlock(&dev->device_lock); - rets = wait_event_timeout(dev->wait_recvd_msg, - (cl->state == MEI_FILE_CONNECTED || - cl->state == MEI_FILE_DISCONNECTED), - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + wait_event_timeout(dev->wait_recvd_msg, + (cl->state == MEI_FILE_CONNECTED || + cl->state == MEI_FILE_DISCONNECTED), + mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); mutex_lock(&dev->device_lock); if (cl->state != MEI_FILE_CONNECTED) { - rets = -EFAULT; + /* something went really wrong */ + if (!cl->status) + cl->status = -EFAULT; mei_io_list_flush(&dev->ctrl_rd_list, cl); mei_io_list_flush(&dev->ctrl_wr_list, cl); - goto out; } rets = cl->status; out: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + mei_io_cb_free(cb); return rets; } @@ -547,7 +647,8 @@ out: int mei_cl_flow_ctrl_creds(struct mei_cl *cl) { struct mei_device *dev; - int i; + struct mei_me_client *me_cl; + int id; if (WARN_ON(!cl || !cl->dev)) return -EINVAL; @@ -560,19 +661,19 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl) if (cl->mei_flow_ctrl_creds > 0) return 1; - for (i = 0; i < dev->me_clients_num; i++) { - struct mei_me_client *me_cl = &dev->me_clients[i]; - if (me_cl->client_id == cl->me_client_id) { - if (me_cl->mei_flow_ctrl_creds) { - if (WARN_ON(me_cl->props.single_recv_buf == 0)) - return -EINVAL; - return 1; - } else { - return 0; - } - } + id = mei_me_cl_by_id(dev, cl->me_client_id); + if (id < 0) { + cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); + return id; } - return -ENOENT; + + me_cl = &dev->me_clients[id]; + if (me_cl->mei_flow_ctrl_creds) { + if (WARN_ON(me_cl->props.single_recv_buf == 0)) + return -EINVAL; + return 1; + } + return 0; } /** @@ -588,32 +689,31 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl) int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) { struct mei_device *dev; - int i; + struct mei_me_client *me_cl; + int id; if (WARN_ON(!cl || !cl->dev)) return -EINVAL; dev = cl->dev; - if (!dev->me_clients_num) - return -ENOENT; + id = mei_me_cl_by_id(dev, cl->me_client_id); + if (id < 0) { + cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); + return id; + } - for (i = 0; i < dev->me_clients_num; i++) { - struct mei_me_client *me_cl = &dev->me_clients[i]; - if (me_cl->client_id == cl->me_client_id) { - if (me_cl->props.single_recv_buf != 0) { - if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) - return -EINVAL; - dev->me_clients[i].mei_flow_ctrl_creds--; - } else { - if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) - return -EINVAL; - cl->mei_flow_ctrl_creds--; - } - return 0; - } + me_cl = &dev->me_clients[id]; + if (me_cl->props.single_recv_buf != 0) { + if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) + return -EINVAL; + me_cl->mei_flow_ctrl_creds--; + } else { + if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) + return -EINVAL; + cl->mei_flow_ctrl_creds--; } - return -ENOENT; + return 0; } /** @@ -639,74 +739,111 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) return -ENODEV; if (cl->read_cb) { - dev_dbg(&dev->pdev->dev, "read is pending.\n"); + cl_dbg(dev, cl, "read is pending.\n"); return -EBUSY; } i = mei_me_cl_by_id(dev, cl->me_client_id); if (i < 0) { - dev_err(&dev->pdev->dev, "no such me client %d\n", - cl->me_client_id); - return -ENODEV; + cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); + return -ENOTTY; + } + + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; } cb = mei_io_cb_init(cl, NULL); - if (!cb) - return -ENOMEM; + if (!cb) { + rets = -ENOMEM; + goto out; + } /* 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; + goto out; cb->fop_type = MEI_FOP_READ; - cl->read_cb = cb; - if (dev->hbuf_is_ready) { - dev->hbuf_is_ready = false; - if (mei_hbm_cl_flow_control_req(dev, cl)) { - rets = -ENODEV; - goto err; - } + if (mei_hbuf_acquire(dev)) { + rets = mei_hbm_cl_flow_control_req(dev, cl); + if (rets < 0) + goto out; + list_add_tail(&cb->list, &dev->read_list.list); } else { list_add_tail(&cb->list, &dev->ctrl_wr_list.list); } - return rets; -err: - mei_io_cb_free(cb); + + cl->read_cb = cb; + +out: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + + if (rets) + mei_io_cb_free(cb); + return rets; } /** - * mei_cl_irq_write_complete - write a message to device + * mei_cl_irq_write - write a message to device * from the interrupt thread context * * @cl: client * @cb: callback block. - * @slots: free slots. * @cmpl_list: complete list. * * returns 0, OK; otherwise error. */ -int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb, - s32 *slots, struct mei_cl_cb *cmpl_list) +int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, + struct mei_cl_cb *cmpl_list) { - struct mei_device *dev = cl->dev; + struct mei_device *dev; + struct mei_msg_data *buf; struct mei_msg_hdr mei_hdr; - size_t len = cb->request_buffer.size - cb->buf_idx; - u32 msg_slots = mei_data2slots(len); + size_t len; + u32 msg_slots; + int slots; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + buf = &cb->request_buffer; + + rets = mei_cl_flow_ctrl_creds(cl); + if (rets < 0) + return rets; + + if (rets == 0) { + cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); + return 0; + } + + slots = mei_hbuf_empty_slots(dev); + len = buf->size - cb->buf_idx; + msg_slots = mei_data2slots(len); mei_hdr.host_addr = cl->host_client_id; mei_hdr.me_addr = cl->me_client_id; mei_hdr.reserved = 0; + mei_hdr.internal = cb->internal; - if (*slots >= msg_slots) { + if (slots >= msg_slots) { mei_hdr.length = len; mei_hdr.msg_complete = 1; /* Split the message only if we can write the whole host buffer */ - } else if (*slots == dev->hbuf_depth) { - msg_slots = *slots; - len = (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); + } else if (slots == dev->hbuf_depth) { + msg_slots = slots; + len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); mei_hdr.length = len; mei_hdr.msg_complete = 0; } else { @@ -714,16 +851,14 @@ int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb, return 0; } - dev_dbg(&dev->pdev->dev, "buf: size = %d idx = %lu\n", + cl_dbg(dev, cl, "buf: size = %d idx = %lu\n", cb->request_buffer.size, cb->buf_idx); - dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(&mei_hdr)); - *slots -= msg_slots; - if (mei_write_message(dev, &mei_hdr, - cb->request_buffer.data + cb->buf_idx)) { - cl->status = -ENODEV; + rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx); + if (rets) { + cl->status = rets; list_move_tail(&cb->list, &cmpl_list->list); - return -ENODEV; + return rets; } cl->status = 0; @@ -732,7 +867,7 @@ int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb, if (mei_hdr.msg_complete) { if (mei_cl_flow_ctrl_reduce(cl)) - return -ENODEV; + return -EIO; list_move_tail(&cb->list, &dev->write_waiting_list.list); } @@ -746,7 +881,7 @@ int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb, * @cl: host client * @cl: write callback with filled data * - * returns numbe of bytes sent on success, <0 on failure. + * returns number of bytes sent on success, <0 on failure. */ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) { @@ -767,25 +902,39 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) buf = &cb->request_buffer; - dev_dbg(&dev->pdev->dev, "mei_cl_write %d\n", buf->size); + cl_dbg(dev, cl, "mei_cl_write %d\n", buf->size); + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } cb->fop_type = MEI_FOP_WRITE; + cb->buf_idx = 0; + cl->writing_state = MEI_IDLE; + + mei_hdr.host_addr = cl->host_client_id; + mei_hdr.me_addr = cl->me_client_id; + mei_hdr.reserved = 0; + mei_hdr.msg_complete = 0; + mei_hdr.internal = cb->internal; 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; + if (rets == 0) { + cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); + rets = buf->size; + goto out; + } + if (!mei_hbuf_acquire(dev)) { + cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n"); rets = buf->size; goto out; } - - dev->hbuf_is_ready = false; /* Check for a maximum length */ if (buf->size > mei_hbuf_max_len(dev)) { @@ -796,29 +945,19 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) 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; + rets = mei_write_message(dev, &mei_hdr, buf->data); + if (rets) 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; + rets = mei_cl_flow_ctrl_reduce(cl); + if (rets < 0) goto err; - } + list_add_tail(&cb->list, &dev->write_waiting_list.list); } else { list_add_tail(&cb->list, &dev->write_list.list); @@ -828,16 +967,23 @@ out: 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; - } + rets = wait_event_interruptible(cl->tx_wait, + cl->writing_state == MEI_WRITE_COMPLETE); mutex_lock(&dev->device_lock); + /* wait_event_interruptible returns -ERESTARTSYS */ + if (rets) { + if (signal_pending(current)) + rets = -EINTR; + goto err; + } } + + rets = buf->size; err: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + return rets; } @@ -877,12 +1023,11 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) void mei_cl_all_disconnect(struct mei_device *dev) { - struct mei_cl *cl, *next; + struct mei_cl *cl; - list_for_each_entry_safe(cl, next, &dev->file_list, link) { + list_for_each_entry(cl, &dev->file_list, link) { cl->state = MEI_FILE_DISCONNECTED; cl->mei_flow_ctrl_creds = 0; - cl->read_cb = NULL; cl->timer_count = 0; } } @@ -895,14 +1040,14 @@ void mei_cl_all_disconnect(struct mei_device *dev) */ void mei_cl_all_wakeup(struct mei_device *dev) { - struct mei_cl *cl, *next; - list_for_each_entry_safe(cl, next, &dev->file_list, link) { + struct mei_cl *cl; + list_for_each_entry(cl, &dev->file_list, link) { if (waitqueue_active(&cl->rx_wait)) { - dev_dbg(&dev->pdev->dev, "Waking up reading client!\n"); + cl_dbg(dev, cl, "Waking up reading client!\n"); wake_up_interruptible(&cl->rx_wait); } if (waitqueue_active(&cl->tx_wait)) { - dev_dbg(&dev->pdev->dev, "Waking up writing client!\n"); + cl_dbg(dev, cl, "Waking up writing client!\n"); wake_up_interruptible(&cl->tx_wait); } } @@ -915,12 +1060,8 @@ void mei_cl_all_wakeup(struct mei_device *dev) */ void mei_cl_all_write_clear(struct mei_device *dev) { - struct mei_cl_cb *cb, *next; - - list_for_each_entry_safe(cb, next, &dev->write_list.list, list) { - list_del(&cb->list); - mei_io_cb_free(cb); - } + mei_io_list_free(&dev->write_list, NULL); + mei_io_list_free(&dev->write_waiting_list, NULL); } diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 9eb031e9207..96d5de0389f 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -45,8 +45,6 @@ static inline void mei_io_list_init(struct mei_cl_cb *list) { INIT_LIST_HEAD(&list->list); } -void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl); - /* * MEI Host Client Functions */ @@ -61,22 +59,6 @@ int mei_cl_unlink(struct mei_cl *cl); int mei_cl_flush_queues(struct mei_cl *cl); struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl); -/** - * mei_cl_cmp_id - tells if file private data have same id - * - * @fe1: private data of 1. file object - * @fe2: private data of 2. file object - * - * returns true - if ids are the same and not NULL - */ -static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, - const struct mei_cl *cl2) -{ - return cl1 && cl2 && - (cl1->host_client_id == cl2->host_client_id) && - (cl1->me_client_id == cl2->me_client_id); -} - int mei_cl_flow_ctrl_creds(struct mei_cl *cl); @@ -86,9 +68,15 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl); */ static inline bool mei_cl_is_connected(struct mei_cl *cl) { - return (cl->dev && + return cl->dev && cl->dev->dev_state == MEI_DEV_ENABLED && - cl->state == MEI_FILE_CONNECTED); + cl->state == MEI_FILE_CONNECTED; +} +static inline bool mei_cl_is_transitioning(struct mei_cl *cl) +{ + return MEI_FILE_INITIALIZING == cl->state || + MEI_FILE_DISCONNECTED == cl->state || + MEI_FILE_DISCONNECTING == cl->state; } bool mei_cl_is_other_connecting(struct mei_cl *cl); @@ -96,8 +84,8 @@ int mei_cl_disconnect(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); -int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb, - s32 *slots, struct mei_cl_cb *cmpl_list); +int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, + struct mei_cl_cb *cmpl_list); void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb); @@ -109,4 +97,13 @@ void mei_cl_all_disconnect(struct mei_device *dev); void mei_cl_all_wakeup(struct mei_device *dev); void mei_cl_all_write_clear(struct mei_device *dev); +#define MEI_CL_FMT "cl:host=%02d me=%02d " +#define MEI_CL_PRM(cl) (cl)->host_client_id, (cl)->me_client_id + +#define cl_dbg(dev, cl, format, arg...) \ + dev_dbg(&(dev)->pdev->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + +#define cl_err(dev, cl, format, arg...) \ + dev_err(&(dev)->pdev->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + #endif /* _MEI_CLIENT_H_ */ diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c index e3870f22d23..ced5b777c70 100644 --- a/drivers/misc/mei/debugfs.c +++ b/drivers/misc/mei/debugfs.c @@ -43,7 +43,7 @@ static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf, mutex_lock(&dev->device_lock); - /* if the driver is not enabled the list won't b consitent */ + /* if the driver is not enabled the list won't be consistent */ if (dev->dev_state != MEI_DEV_ENABLED) goto out; @@ -75,6 +75,54 @@ static const struct file_operations mei_dbgfs_fops_meclients = { .llseek = generic_file_llseek, }; +static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct mei_device *dev = fp->private_data; + struct mei_cl *cl; + const size_t bufsz = 1024; + char *buf; + int i = 0; + int pos = 0; + int ret; + + if (!dev) + return -ENODEV; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, + " |me|host|state|rd|wr|\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; + + list_for_each_entry(cl, &dev->file_list, link) { + + pos += scnprintf(buf + pos, bufsz - pos, + "%2d|%2d|%4d|%5d|%2d|%2d|\n", + i, cl->me_client_id, cl->host_client_id, cl->state, + cl->reading_state, cl->writing_state); + i++; + } +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_active = { + .open = simple_open, + .read = mei_dbgfs_read_active, + .llseek = generic_file_llseek, +}; + static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf, size_t cnt, loff_t *ppos) { @@ -101,7 +149,7 @@ static const struct file_operations mei_dbgfs_fops_devstate = { /** * mei_dbgfs_deregister - Remove the debugfs files and directories - * @mei - pointer to mei device private dat + * @mei - pointer to mei device private data */ void mei_dbgfs_deregister(struct mei_device *dev) { @@ -128,6 +176,12 @@ int mei_dbgfs_register(struct mei_device *dev, const char *name) dev_err(&dev->pdev->dev, "meclients: registration failed\n"); goto err; } + f = debugfs_create_file("active", S_IRUSR, dir, + dev, &mei_dbgfs_fops_active); + 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) { diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 6127ab64bb3..804106209d7 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -14,50 +14,111 @@ * */ +#include <linux/export.h> #include <linux/pci.h> #include <linux/sched.h> #include <linux/wait.h> #include <linux/mei.h> +#include <linux/pm_runtime.h> #include "mei_dev.h" #include "hbm.h" -#include "hw-me.h" +#include "client.h" + +static const char *mei_cl_conn_status_str(enum mei_cl_connect_status status) +{ +#define MEI_CL_CS(status) case MEI_CL_CONN_##status: return #status + switch (status) { + MEI_CL_CS(SUCCESS); + MEI_CL_CS(NOT_FOUND); + MEI_CL_CS(ALREADY_STARTED); + MEI_CL_CS(OUT_OF_RESOURCES); + MEI_CL_CS(MESSAGE_SMALL); + default: return "unknown"; + } +#undef MEI_CL_CCS +} + +/** + * mei_cl_conn_status_to_errno - convert client connect response + * status to error code + * + * @status: client connect response status + * + * returns corresponding error code + */ +static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) +{ + switch (status) { + case MEI_CL_CONN_SUCCESS: return 0; + case MEI_CL_CONN_NOT_FOUND: return -ENOTTY; + case MEI_CL_CONN_ALREADY_STARTED: return -EBUSY; + case MEI_CL_CONN_OUT_OF_RESOURCES: return -EBUSY; + case MEI_CL_CONN_MESSAGE_SMALL: return -EINVAL; + default: return -EINVAL; + } +} + +/** + * mei_hbm_idle - set hbm to idle state + * + * @dev: the device structure + */ +void mei_hbm_idle(struct mei_device *dev) +{ + dev->init_clients_timer = 0; + dev->hbm_state = MEI_HBM_IDLE; +} + +/** + * mei_hbm_reset - reset hbm counters and book keeping data structurs + * + * @dev: the device structure + */ +void mei_hbm_reset(struct mei_device *dev) +{ + dev->me_clients_num = 0; + dev->me_client_presentation_num = 0; + dev->me_client_index = 0; + + kfree(dev->me_clients); + dev->me_clients = NULL; + + mei_hbm_idle(dev); +} /** * mei_hbm_me_cl_allocate - allocates storage for me clients * * @dev: the device structure * - * returns none. + * returns 0 on success -ENOMEM on allocation failure */ -static void mei_hbm_me_cl_allocate(struct mei_device *dev) +static int mei_hbm_me_cl_allocate(struct mei_device *dev) { struct mei_me_client *clients; int b; + mei_hbm_reset(dev); + /* count how many ME clients we have */ for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) dev->me_clients_num++; - if (dev->me_clients_num <= 0) - return; - - kfree(dev->me_clients); - dev->me_clients = NULL; + if (dev->me_clients_num == 0) + return 0; - dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%zd.\n", + dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%ld.\n", dev->me_clients_num * sizeof(struct mei_me_client)); /* allocate storage for ME clients representation */ clients = kcalloc(dev->me_clients_num, 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_RESETTING; - mei_reset(dev, 1); - return; + return -ENOMEM; } dev->me_clients = clients; - return; + return 0; } /** @@ -81,12 +142,12 @@ void mei_hbm_cl_hdr(struct mei_cl *cl, u8 hbm_cmd, void *buf, size_t len) } /** - * same_disconn_addr - tells if they have the same address + * mei_hbm_cl_addr_equal - tells if they have the same address * - * @file: private data of the file object. - * @disconn: disconnection request. + * @cl: - client + * @buf: buffer with cl header * - * returns true if addres are same + * returns true if addresses are the same */ static inline bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) @@ -97,33 +158,6 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) } -/** - * is_treat_specially_client - checks if the message belongs - * to the file private data. - * - * @cl: private data of the file object - * @rs: connect response bus message - * - */ -static bool is_treat_specially_client(struct mei_cl *cl, - struct hbm_client_connect_response *rs) -{ - if (mei_hbm_cl_addr_equal(cl, rs)) { - if (!rs->status) { - cl->state = MEI_FILE_CONNECTED; - cl->status = 0; - - } else { - cl->state = MEI_FILE_DISCONNECTED; - cl->status = -ENODEV; - } - cl->timer_count = 0; - - return true; - } - return false; -} - int mei_hbm_start_wait(struct mei_device *dev) { int ret; @@ -133,14 +167,14 @@ int mei_hbm_start_wait(struct mei_device *dev) 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)); + dev->hbm_state >= MEI_HBM_STARTED, + mei_secs_to_jiffies(MEI_HBM_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, "waiting for mei start failed\n"); - return -ETIMEDOUT; + return -ETIME; } return 0; } @@ -149,12 +183,15 @@ int mei_hbm_start_wait(struct mei_device *dev) * mei_hbm_start_req - sends start request message. * * @dev: the device structure + * + * returns 0 on success and < 0 on failure */ 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; const size_t len = sizeof(struct hbm_host_version_request); + int ret; mei_hbm_hdr(mei_hdr, len); @@ -166,12 +203,13 @@ int mei_hbm_start_req(struct mei_device *dev) start_req->host_version.minor_version = HBM_MINOR_VERSION; dev->hbm_state = MEI_HBM_IDLE; - if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { - dev_err(&dev->pdev->dev, "version message write failed\n"); - dev->dev_state = MEI_DEV_RESETTING; - mei_reset(dev, 1); - return -ENODEV; + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) { + dev_err(&dev->pdev->dev, "version message write failed: ret = %d\n", + ret); + return ret; } + dev->hbm_state = MEI_HBM_START; dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; return 0; @@ -182,13 +220,15 @@ int mei_hbm_start_req(struct mei_device *dev) * * @dev: the device structure * - * returns none. + * returns 0 on success and < 0 on failure */ -static void mei_hbm_enum_clients_req(struct mei_device *dev) +static int mei_hbm_enum_clients_req(struct mei_device *dev) { struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; struct hbm_host_enum_request *enum_req; const size_t len = sizeof(struct hbm_host_enum_request); + int ret; + /* enumerate clients */ mei_hbm_hdr(mei_hdr, len); @@ -196,14 +236,15 @@ static void mei_hbm_enum_clients_req(struct mei_device *dev) memset(enum_req, 0, len); enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; - if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { - dev->dev_state = MEI_DEV_RESETTING; - dev_err(&dev->pdev->dev, "enumeration request write failed.\n"); - mei_reset(dev, 1); + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) { + dev_err(&dev->pdev->dev, "enumeration request write failed: ret = %d.\n", + ret); + return ret; } dev->hbm_state = MEI_HBM_ENUM_CLIENTS; dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; - return; + return 0; } /** @@ -211,7 +252,7 @@ static void mei_hbm_enum_clients_req(struct mei_device *dev) * * @dev: the device structure * - * returns none. + * returns 0 on success and < 0 on failure */ static int mei_hbm_prop_req(struct mei_device *dev) @@ -221,8 +262,8 @@ static int mei_hbm_prop_req(struct mei_device *dev) struct hbm_props_request *prop_req; const size_t len = sizeof(struct hbm_props_request); unsigned long next_client_index; - u8 client_num; - + unsigned long client_num; + int ret; client_num = dev->me_client_presentation_num; @@ -249,12 +290,11 @@ static int mei_hbm_prop_req(struct mei_device *dev) prop_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; prop_req->address = next_client_index; - if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { - dev->dev_state = MEI_DEV_RESETTING; - dev_err(&dev->pdev->dev, "properties request write failed\n"); - mei_reset(dev, 1); - - return -EIO; + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) { + dev_err(&dev->pdev->dev, "properties request write failed: ret = %d\n", + ret); + return ret; } dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; @@ -263,18 +303,47 @@ static int mei_hbm_prop_req(struct mei_device *dev) return 0; } +/* + * mei_hbm_pg - sends pg command + * + * @dev: the device structure + * @pg_cmd: the pg command code + * + * This function returns -EIO on write failure + */ +int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd) +{ + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; + struct hbm_power_gate *req; + const size_t len = sizeof(struct hbm_power_gate); + int ret; + + mei_hbm_hdr(mei_hdr, len); + + req = (struct hbm_power_gate *)dev->wr_msg.data; + memset(req, 0, len); + req->hbm_cmd = pg_cmd; + + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) + dev_err(&dev->pdev->dev, "power gate command write failed.\n"); + return ret; +} +EXPORT_SYMBOL_GPL(mei_hbm_pg); + /** - * mei_hbm_stop_req_prepare - perpare stop request message + * mei_hbm_stop_req - send stop request message * * @dev - mei device - * @mei_hdr - mei message header - * @data - hbm message body buffer + * @cl: client info + * + * This function returns -EIO on write failure */ -static void mei_hbm_stop_req_prepare(struct mei_device *dev, - struct mei_msg_hdr *mei_hdr, unsigned char *data) +static int mei_hbm_stop_req(struct mei_device *dev) { + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; struct hbm_host_stop_request *req = - (struct hbm_host_stop_request *)data; + (struct hbm_host_stop_request *)dev->wr_msg.data; const size_t len = sizeof(struct hbm_host_stop_request); mei_hbm_hdr(mei_hdr, len); @@ -282,10 +351,12 @@ static void mei_hbm_stop_req_prepare(struct mei_device *dev, memset(req, 0, len); req->hbm_cmd = HOST_STOP_REQ_CMD; req->reason = DRIVER_STOP_REQUEST; + + return mei_write_message(dev, mei_hdr, dev->wr_msg.data); } /** - * mei_hbm_cl_flow_control_req - sends flow control requst. + * mei_hbm_cl_flow_control_req - sends flow control request. * * @dev: the device structure * @cl: client info @@ -300,8 +371,7 @@ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) mei_hbm_hdr(mei_hdr, len); mei_hbm_cl_hdr(cl, MEI_FLOW_CONTROL_CMD, dev->wr_msg.data, len); - dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", - cl->host_client_id, cl->me_client_id); + cl_dbg(dev, cl, "sending flow control\n"); return mei_write_message(dev, mei_hdr, dev->wr_msg.data); } @@ -311,27 +381,34 @@ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) * * @dev: the device structure * @flow: flow control. + * + * return 0 on success, < 0 otherwise */ -static void mei_hbm_add_single_flow_creds(struct mei_device *dev, +static int mei_hbm_add_single_flow_creds(struct mei_device *dev, struct hbm_flow_control *flow) { - struct mei_me_client *client; - int i; - - for (i = 0; i < dev->me_clients_num; i++) { - client = &dev->me_clients[i]; - if (client && flow->me_addr == client->client_id) { - if (client->props.single_recv_buf) { - client->mei_flow_ctrl_creds++; - dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", - flow->me_addr); - dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", - client->mei_flow_ctrl_creds); - } else { - BUG(); /* error in flow control */ - } - } + struct mei_me_client *me_cl; + int id; + + id = mei_me_cl_by_id(dev, flow->me_addr); + if (id < 0) { + dev_err(&dev->pdev->dev, "no such me client %d\n", + flow->me_addr); + return id; + } + + me_cl = &dev->me_clients[id]; + if (me_cl->props.single_recv_buf) { + me_cl->mei_flow_ctrl_creds++; + dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", + flow->me_addr); + dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", + me_cl->mei_flow_ctrl_creds); + } else { + BUG(); /* error in flow control */ } + + return 0; } /** @@ -343,8 +420,7 @@ static void mei_hbm_add_single_flow_creds(struct mei_device *dev, static void mei_hbm_cl_flow_control_res(struct mei_device *dev, struct hbm_flow_control *flow_control) { - struct mei_cl *cl = NULL; - struct mei_cl *next = NULL; + struct mei_cl *cl; if (!flow_control->host_addr) { /* single receive buffer */ @@ -353,7 +429,7 @@ static void mei_hbm_cl_flow_control_res(struct mei_device *dev, } /* normal connection */ - list_for_each_entry_safe(cl, next, &dev->file_list, link) { + list_for_each_entry(cl, &dev->file_list, link) { if (mei_hbm_cl_addr_equal(cl, flow_control)) { cl->mei_flow_ctrl_creds++; dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n", @@ -386,6 +462,25 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) } /** + * mei_hbm_cl_disconnect_rsp - sends disconnect respose to the FW + * + * @dev: the device structure + * @cl: a client to disconnect from + * + * This function returns -EIO on write failure + */ +int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; + const size_t len = sizeof(struct hbm_client_connect_response); + + mei_hbm_hdr(mei_hdr, len); + mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, dev->wr_msg.data, len); + + return mei_write_message(dev, mei_hdr, dev->wr_msg.data); +} + +/** * mei_hbm_cl_disconnect_res - disconnect response from ME * * @dev: the device structure @@ -395,29 +490,23 @@ static void mei_hbm_cl_disconnect_res(struct mei_device *dev, struct hbm_client_connect_response *rs) { struct mei_cl *cl; - struct mei_cl_cb *pos = NULL, *next = NULL; - - dev_dbg(&dev->pdev->dev, - "disconnect_response:\n" - "ME Client = %d\n" - "Host Client = %d\n" - "Status = %d\n", - rs->me_addr, - rs->host_addr, - rs->status); - - list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { - cl = pos->cl; - - if (!cl) { - list_del(&pos->list); + struct mei_cl_cb *cb, *next; + + dev_dbg(&dev->pdev->dev, "hbm: disconnect response cl:host=%02d me=%02d status=%d\n", + rs->me_addr, rs->host_addr, rs->status); + + list_for_each_entry_safe(cb, next, &dev->ctrl_rd_list.list, list) { + cl = cb->cl; + + /* this should not happen */ + if (WARN_ON(!cl)) { + list_del(&cb->list); return; } - dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in ctrl_rd_list.\n"); if (mei_hbm_cl_addr_equal(cl, rs)) { - list_del(&pos->list); - if (!rs->status) + list_del(&cb->list); + if (rs->status == MEI_CL_DISCONN_SUCCESS) cl->state = MEI_FILE_DISCONNECTED; cl->status = 0; @@ -447,7 +536,7 @@ int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl) } /** - * mei_hbm_cl_connect_res - connect resposne from the ME + * mei_hbm_cl_connect_res - connect response from the ME * * @dev: the device structure * @rs: connect response bus message @@ -457,81 +546,78 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, { struct mei_cl *cl; - struct mei_cl_cb *pos = NULL, *next = NULL; + struct mei_cl_cb *cb, *next; - dev_dbg(&dev->pdev->dev, - "connect_response:\n" - "ME Client = %d\n" - "Host Client = %d\n" - "Status = %d\n", - rs->me_addr, - rs->host_addr, - rs->status); + dev_dbg(&dev->pdev->dev, "hbm: connect response cl:host=%02d me=%02d status=%s\n", + rs->me_addr, rs->host_addr, + mei_cl_conn_status_str(rs->status)); - /* if WD or iamthif client treat specially */ + cl = NULL; - if (is_treat_specially_client(&dev->wd_cl, rs)) { - dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n"); - mei_watchdog_register(dev); + list_for_each_entry_safe(cb, next, &dev->ctrl_rd_list.list, list) { - return; - } + cl = cb->cl; + /* this should not happen */ + if (WARN_ON(!cl)) { + list_del_init(&cb->list); + continue; + } - if (is_treat_specially_client(&dev->iamthif_cl, rs)) { - dev->iamthif_state = MEI_IAMTHIF_IDLE; - return; - } - list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { + if (cb->fop_type != MEI_FOP_CONNECT) + continue; - cl = pos->cl; - if (!cl) { - list_del(&pos->list); - return; - } - if (pos->fop_type == MEI_FOP_IOCTL) { - if (is_treat_specially_client(cl, rs)) { - list_del(&pos->list); - cl->status = 0; - cl->timer_count = 0; - break; - } + if (mei_hbm_cl_addr_equal(cl, rs)) { + list_del(&cb->list); + break; } } + + if (!cl) + return; + + cl->timer_count = 0; + if (rs->status == MEI_CL_CONN_SUCCESS) + cl->state = MEI_FILE_CONNECTED; + else + cl->state = MEI_FILE_DISCONNECTED; + cl->status = mei_cl_conn_status_to_errno(rs->status); } /** - * mei_hbm_fw_disconnect_req - disconnect request initiated by me - * host sends disoconnect response + * mei_hbm_fw_disconnect_req - disconnect request initiated by ME firmware + * host sends disconnect response * * @dev: the device structure. * @disconnect_req: disconnect request bus message from the me + * + * returns -ENOMEM on allocation failure */ -static void mei_hbm_fw_disconnect_req(struct mei_device *dev, +static int mei_hbm_fw_disconnect_req(struct mei_device *dev, struct hbm_client_connect_request *disconnect_req) { - struct mei_cl *cl, *next; - const size_t len = sizeof(struct hbm_client_connect_response); + struct mei_cl *cl; + struct mei_cl_cb *cb; - list_for_each_entry_safe(cl, next, &dev->file_list, link) { + list_for_each_entry(cl, &dev->file_list, link) { if (mei_hbm_cl_addr_equal(cl, disconnect_req)) { dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", disconnect_req->host_addr, disconnect_req->me_addr); cl->state = MEI_FILE_DISCONNECTED; cl->timer_count = 0; - if (cl == &dev->wd_cl) - dev->wd_pending = false; - else if (cl == &dev->iamthif_cl) - dev->iamthif_timer = 0; - - /* prepare disconnect response */ - mei_hbm_hdr(&dev->wr_ext_msg.hdr, len); - mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, - dev->wr_ext_msg.data, len); + + cb = mei_io_cb_init(cl, NULL); + if (!cb) + return -ENOMEM; + cb->fop_type = MEI_FOP_DISCONNECT_RSP; + cl_dbg(dev, cl, "add disconnect response as first\n"); + list_add(&cb->list, &dev->ctrl_wr_list.list); + break; } } + return 0; } @@ -555,8 +641,10 @@ bool mei_hbm_version_is_supported(struct mei_device *dev) * * @dev: the device structure * @mei_hdr: header of bus message + * + * returns 0 on success and < 0 on failure */ -void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) +int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) { struct mei_bus_message *mei_msg; struct mei_me_client *me_client; @@ -573,8 +661,20 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) mei_read_slots(dev, dev->rd_msg_buf, hdr->length); mei_msg = (struct mei_bus_message *)dev->rd_msg_buf; + /* ignore spurious message and prevent reset nesting + * hbm is put to idle during system reset + */ + if (dev->hbm_state == MEI_HBM_IDLE) { + dev_dbg(&dev->pdev->dev, "hbm: state is idle ignore spurious messages\n"); + return 0; + } + switch (mei_msg->hbm_cmd) { case HOST_START_RES_CMD: + dev_dbg(&dev->pdev->dev, "hbm: start: response message received.\n"); + + dev->init_clients_timer = 0; + version_res = (struct hbm_host_version_response *)mei_msg; dev_dbg(&dev->pdev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n", @@ -593,73 +693,107 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) } if (!mei_hbm_version_is_supported(dev)) { - dev_warn(&dev->pdev->dev, "hbm version mismatch: stopping the driver.\n"); + dev_warn(&dev->pdev->dev, "hbm: start: version mismatch - stopping the driver.\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); + dev->hbm_state = MEI_HBM_STOPPED; + if (mei_hbm_stop_req(dev)) { + dev_err(&dev->pdev->dev, "hbm: start: failed to send stop request\n"); + return -EIO; + } + break; + } - return; + if (dev->dev_state != MEI_DEV_INIT_CLIENTS || + dev->hbm_state != MEI_HBM_START) { + dev_err(&dev->pdev->dev, "hbm: start: state mismatch, [%d, %d]\n", + dev->dev_state, dev->hbm_state); + return -EPROTO; } - if (dev->dev_state == MEI_DEV_INIT_CLIENTS && - dev->hbm_state == MEI_HBM_START) { - dev->init_clients_timer = 0; - mei_hbm_enum_clients_req(dev); - } else { - dev_err(&dev->pdev->dev, "reset: wrong host start response\n"); - mei_reset(dev, 1); - return; + dev->hbm_state = MEI_HBM_STARTED; + + if (mei_hbm_enum_clients_req(dev)) { + dev_err(&dev->pdev->dev, "hbm: start: failed to send enumeration request\n"); + return -EIO; } wake_up_interruptible(&dev->wait_recvd_msg); - dev_dbg(&dev->pdev->dev, "host start response message received.\n"); break; case CLIENT_CONNECT_RES_CMD: + dev_dbg(&dev->pdev->dev, "hbm: client connect response: message received.\n"); + connect_res = (struct hbm_client_connect_response *) mei_msg; mei_hbm_cl_connect_res(dev, connect_res); - dev_dbg(&dev->pdev->dev, "client connect response message received.\n"); wake_up(&dev->wait_recvd_msg); break; case CLIENT_DISCONNECT_RES_CMD: + dev_dbg(&dev->pdev->dev, "hbm: client disconnect response: message received.\n"); + disconnect_res = (struct hbm_client_connect_response *) mei_msg; mei_hbm_cl_disconnect_res(dev, disconnect_res); - dev_dbg(&dev->pdev->dev, "client disconnect response message received.\n"); wake_up(&dev->wait_recvd_msg); break; case MEI_FLOW_CONTROL_CMD: + dev_dbg(&dev->pdev->dev, "hbm: client flow control response: message received.\n"); + flow_control = (struct hbm_flow_control *) mei_msg; mei_hbm_cl_flow_control_res(dev, flow_control); - dev_dbg(&dev->pdev->dev, "client flow control response message received.\n"); + break; + + case MEI_PG_ISOLATION_ENTRY_RES_CMD: + dev_dbg(&dev->pdev->dev, "power gate isolation entry response received\n"); + dev->pg_event = MEI_PG_EVENT_RECEIVED; + if (waitqueue_active(&dev->wait_pg)) + wake_up(&dev->wait_pg); + break; + + case MEI_PG_ISOLATION_EXIT_REQ_CMD: + dev_dbg(&dev->pdev->dev, "power gate isolation exit request received\n"); + dev->pg_event = MEI_PG_EVENT_RECEIVED; + if (waitqueue_active(&dev->wait_pg)) + wake_up(&dev->wait_pg); + else + /* + * If the driver is not waiting on this then + * this is HW initiated exit from PG. + * Start runtime pm resume sequence to exit from PG. + */ + pm_request_resume(&dev->pdev->dev); break; case HOST_CLIENT_PROPERTIES_RES_CMD: + dev_dbg(&dev->pdev->dev, "hbm: properties response: message received.\n"); + + dev->init_clients_timer = 0; + + if (dev->me_clients == NULL) { + dev_err(&dev->pdev->dev, "hbm: properties response: mei_clients not allocated\n"); + return -EPROTO; + } + props_res = (struct hbm_props_response *)mei_msg; me_client = &dev->me_clients[dev->me_client_presentation_num]; - if (props_res->status || !dev->me_clients) { - dev_err(&dev->pdev->dev, "reset: properties response hbm wrong status.\n"); - mei_reset(dev, 1); - return; + if (props_res->status) { + dev_err(&dev->pdev->dev, "hbm: properties response: wrong status = %d\n", + props_res->status); + return -EPROTO; } if (me_client->client_id != props_res->address) { - dev_err(&dev->pdev->dev, "reset: host properties response address mismatch\n"); - mei_reset(dev, 1); - return; + dev_err(&dev->pdev->dev, "hbm: properties response: address mismatch %d ?= %d\n", + me_client->client_id, props_res->address); + return -EPROTO; } if (dev->dev_state != MEI_DEV_INIT_CLIENTS || dev->hbm_state != MEI_HBM_CLIENT_PROPERTIES) { - dev_err(&dev->pdev->dev, "reset: unexpected properties response\n"); - mei_reset(dev, 1); - - return; + dev_err(&dev->pdev->dev, "hbm: properties response: state mismatch, [%d, %d]\n", + dev->dev_state, dev->hbm_state); + return -EPROTO; } me_client->props = props_res->client_properties; @@ -667,55 +801,79 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->me_client_presentation_num++; /* request property for the next client */ - mei_hbm_prop_req(dev); + if (mei_hbm_prop_req(dev)) + return -EIO; break; case HOST_ENUM_RES_CMD: + dev_dbg(&dev->pdev->dev, "hbm: enumeration response: message received\n"); + + dev->init_clients_timer = 0; + 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->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->hbm_state = MEI_HBM_CLIENT_PROPERTIES; - - /* first property reqeust */ - mei_hbm_prop_req(dev); - } else { - dev_err(&dev->pdev->dev, "reset: unexpected enumeration response hbm.\n"); - mei_reset(dev, 1); - return; + BUILD_BUG_ON(sizeof(dev->me_clients_map) + < sizeof(enum_res->valid_addresses)); + memcpy(dev->me_clients_map, enum_res->valid_addresses, + sizeof(enum_res->valid_addresses)); + + if (dev->dev_state != MEI_DEV_INIT_CLIENTS || + dev->hbm_state != MEI_HBM_ENUM_CLIENTS) { + dev_err(&dev->pdev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n", + dev->dev_state, dev->hbm_state); + return -EPROTO; + } + + if (mei_hbm_me_cl_allocate(dev)) { + dev_err(&dev->pdev->dev, "hbm: enumeration response: cannot allocate clients array\n"); + return -ENOMEM; } + + dev->hbm_state = MEI_HBM_CLIENT_PROPERTIES; + + /* first property request */ + if (mei_hbm_prop_req(dev)) + return -EIO; + break; case HOST_STOP_RES_CMD: + dev_dbg(&dev->pdev->dev, "hbm: stop response: message received\n"); + + dev->init_clients_timer = 0; - if (dev->hbm_state != MEI_HBM_STOP) - dev_err(&dev->pdev->dev, "unexpected stop response hbm.\n"); - dev->dev_state = MEI_DEV_DISABLED; - dev_info(&dev->pdev->dev, "reset: FW stop response.\n"); - mei_reset(dev, 1); + if (dev->hbm_state != MEI_HBM_STOPPED) { + dev_err(&dev->pdev->dev, "hbm: stop response: state mismatch, [%d, %d]\n", + dev->dev_state, dev->hbm_state); + return -EPROTO; + } + + dev->dev_state = MEI_DEV_POWER_DOWN; + dev_info(&dev->pdev->dev, "hbm: stop response: resetting.\n"); + /* force the reset */ + return -EPROTO; break; case CLIENT_DISCONNECT_REQ_CMD: - /* search for client */ + dev_dbg(&dev->pdev->dev, "hbm: disconnect request: message received\n"); + disconnect_req = (struct hbm_client_connect_request *)mei_msg; mei_hbm_fw_disconnect_req(dev, disconnect_req); break; 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); + dev_dbg(&dev->pdev->dev, "hbm: stop request: message received\n"); + dev->hbm_state = MEI_HBM_STOPPED; + if (mei_hbm_stop_req(dev)) { + dev_err(&dev->pdev->dev, "hbm: start: failed to send stop request\n"); + return -EIO; + } break; default: BUG(); break; } + return 0; } diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h index 4ae2e56e404..683eb2835ce 100644 --- a/drivers/misc/mei/hbm.h +++ b/drivers/misc/mei/hbm.h @@ -32,13 +32,13 @@ struct mei_cl; enum mei_hbm_state { MEI_HBM_IDLE = 0, MEI_HBM_START, + MEI_HBM_STARTED, MEI_HBM_ENUM_CLIENTS, MEI_HBM_CLIENT_PROPERTIES, - MEI_HBM_STARTED, - MEI_HBM_STOP, + MEI_HBM_STOPPED, }; -void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr); +int 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) { @@ -49,12 +49,16 @@ static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) hdr->reserved = 0; } +void mei_hbm_idle(struct mei_device *dev); +void mei_hbm_reset(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_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); bool mei_hbm_version_is_supported(struct mei_device *dev); +int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd); #endif /* _MEI_HBM_H_ */ diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index 6a203b6e834..a7856c0ac57 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -109,8 +109,17 @@ #define MEI_DEV_ID_PPT_2 0x1CBA /* Panther Point */ #define MEI_DEV_ID_PPT_3 0x1DBA /* Panther Point */ -#define MEI_DEV_ID_LPT 0x8C3A /* Lynx Point */ +#define MEI_DEV_ID_LPT_H 0x8C3A /* Lynx Point H */ +#define MEI_DEV_ID_LPT_W 0x8D3A /* Lynx Point - Wellsburg */ #define MEI_DEV_ID_LPT_LP 0x9C3A /* Lynx Point LP */ +#define MEI_DEV_ID_LPT_HR 0x8CBA /* Lynx Point H Refresh */ + +#define MEI_DEV_ID_WPT_LP 0x9CBA /* Wildcat Point LP */ + +/* Host Firmware Status Registers in PCI Config Space */ +#define PCI_CFG_HFS_1 0x40 +#define PCI_CFG_HFS_2 0x48 + /* * MEI HW Section */ @@ -124,6 +133,8 @@ #define ME_CB_RW 8 /* ME_CSR_HA - ME Control Status Host Access register (read only) */ #define ME_CSR_HA 0xC +/* H_HGC_CSR - PGI register */ +#define H_HPG_CSR 0x10 /* register bits of H_CSR (Host Control Status register) */ @@ -153,6 +164,8 @@ access to ME_CBD */ #define ME_CBWP_HRA 0x00FF0000 /* ME CB Read Pointer HRA - host read only access to ME_CBRP */ #define ME_CBRP_HRA 0x0000FF00 +/* ME Power Gate Isolation Capability HRA - host ready only access */ +#define ME_PGIC_HRA 0x00000040 /* ME Reset HRA - host read only access to ME_RST */ #define ME_RST_HRA 0x00000010 /* ME Ready HRA - host read only access to ME_RDY */ @@ -164,4 +177,9 @@ access to ME_CBD */ /* ME Interrupt Enable HRA - host read only access to ME_IE */ #define ME_IE_HRA 0x00000001 + +/* register bits - H_HPG_CSR */ +#define H_HPG_CSR_PGIHEXR 0x00000001 +#define H_HPG_CSR_PGI 0x00000002 + #endif /* _MEI_HW_MEI_REGS_H_ */ diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 3412adcdaeb..6a2d272cea4 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -20,10 +20,10 @@ #include <linux/interrupt.h> #include "mei_dev.h" -#include "hw-me.h" - #include "hbm.h" +#include "hw-me.h" +#include "hw-me-regs.h" /** * mei_me_reg_read - Reads 32bit data from the mei device @@ -109,10 +109,27 @@ static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) */ static void mei_me_hw_config(struct mei_device *dev) { + struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr = mei_hcsr_read(to_me_hw(dev)); /* Doesn't change in runtime */ dev->hbuf_depth = (hcsr & H_CBD) >> 24; + + hw->pg_state = MEI_PG_OFF; } + +/** + * mei_me_pg_state - translate internal pg state + * to the mei power gating state + * + * @hw - me hardware + * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise + */ +static inline enum mei_pg_state mei_me_pg_state(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + return hw->pg_state; +} + /** * mei_clear_interrupts - clear and stop interrupts * @@ -164,6 +181,9 @@ static void mei_me_hw_reset_release(struct mei_device *dev) hcsr |= H_IG; hcsr &= ~H_RST; mei_hcsr_set(hw, hcsr); + + /* complete this write before we set host ready on another CPU */ + mmiowb(); } /** * mei_me_hw_reset - resets fw via mei csr register. @@ -183,9 +203,22 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) else hcsr &= ~H_IE; + dev->recvd_hw_ready = false; mei_me_reg_write(hw, H_CSR, hcsr); - if (dev->dev_state == MEI_DEV_POWER_DOWN) + /* + * Host reads the H_CSR once to ensure that the + * posted write to H_CSR completes. + */ + hcsr = mei_hcsr_read(hw); + + if ((hcsr & H_RST) == 0) + dev_warn(&dev->pdev->dev, "H_RST is not set = 0x%08X", hcsr); + + if ((hcsr & H_RDY) == H_RDY) + dev_warn(&dev->pdev->dev, "H_RDY is not cleared 0x%08X", hcsr); + + if (intr_enable == false) mei_me_hw_reset_release(dev); return 0; @@ -201,6 +234,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) static void mei_me_host_set_ready(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); + hw->host_hw_state = mei_hcsr_read(hw); hw->host_hw_state |= H_IE | H_IG | H_RDY; mei_hcsr_set(hw, hw->host_hw_state); } @@ -233,18 +267,15 @@ static bool mei_me_hw_is_ready(struct mei_device *dev) static int mei_me_hw_ready_wait(struct mei_device *dev) { int err; - if (mei_me_hw_is_ready(dev)) - return 0; - dev->recvd_hw_ready = false; mutex_unlock(&dev->device_lock); err = wait_event_interruptible_timeout(dev->wait_hw_ready, dev->recvd_hw_ready, - mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT)); + mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT)); mutex_lock(&dev->device_lock); if (!err && !dev->recvd_hw_ready) { if (!err) - err = -ETIMEDOUT; + err = -ETIME; dev_err(&dev->pdev->dev, "wait hw ready failed. status = %d\n", err); return err; @@ -303,7 +334,7 @@ static bool mei_me_hbuf_is_empty(struct mei_device *dev) * * @dev: the device structure * - * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count + * returns -EOVERFLOW if overflow, otherwise empty slots count */ static int mei_me_hbuf_empty_slots(struct mei_device *dev) { @@ -326,7 +357,7 @@ static size_t mei_me_hbuf_max_len(const struct mei_device *dev) /** - * mei_write_message - writes a message to mei device. + * mei_me_write_message - writes a message to mei device. * * @dev: the device structure * @header: mei HECI header of message @@ -354,7 +385,7 @@ static int mei_me_write_message(struct mei_device *dev, dw_cnt = mei_data2slots(length); if (empty_slots < 0 || dw_cnt > empty_slots) - return -EIO; + return -EMSGSIZE; mei_me_reg_write(hw, H_CB_WW, *((u32 *) header)); @@ -381,7 +412,7 @@ static int mei_me_write_message(struct mei_device *dev, * * @dev: the device structure * - * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count + * returns -EOVERFLOW if overflow, otherwise filled slots count */ static int mei_me_count_full_read_slots(struct mei_device *dev) { @@ -431,6 +462,144 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, } /** + * mei_me_pg_enter - write pg enter register to mei device. + * + * @dev: the device structure + */ +static void mei_me_pg_enter(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, H_HPG_CSR); + reg |= H_HPG_CSR_PGI; + mei_me_reg_write(hw, H_HPG_CSR, reg); +} + +/** + * mei_me_pg_enter - write pg enter register to mei device. + * + * @dev: the device structure + */ +static void mei_me_pg_exit(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, H_HPG_CSR); + + WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n"); + + reg |= H_HPG_CSR_PGIHEXR; + mei_me_reg_write(hw, H_HPG_CSR, reg); +} + +/** + * mei_me_pg_set_sync - perform pg entry procedure + * + * @dev: the device structure + * + * returns 0 on success an error code otherwise + */ +int mei_me_pg_set_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); + int ret; + + dev->pg_event = MEI_PG_EVENT_WAIT; + + ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_ENTRY_REQ_CMD); + if (ret) + return ret; + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + mutex_lock(&dev->device_lock); + + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) { + mei_me_pg_enter(dev); + ret = 0; + } else { + ret = -ETIME; + } + + dev->pg_event = MEI_PG_EVENT_IDLE; + hw->pg_state = MEI_PG_ON; + + return ret; +} + +/** + * mei_me_pg_unset_sync - perform pg exit procedure + * + * @dev: the device structure + * + * returns 0 on success an error code otherwise + */ +int mei_me_pg_unset_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); + int ret; + + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) + goto reply; + + dev->pg_event = MEI_PG_EVENT_WAIT; + + mei_me_pg_exit(dev); + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + mutex_lock(&dev->device_lock); + +reply: + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) + ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_EXIT_RES_CMD); + else + ret = -ETIME; + + dev->pg_event = MEI_PG_EVENT_IDLE; + hw->pg_state = MEI_PG_OFF; + + return ret; +} + +/** + * mei_me_pg_is_enabled - detect if PG is supported by HW + * + * @dev: the device structure + * + * returns: true is pg supported, false otherwise + */ +static bool mei_me_pg_is_enabled(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, ME_CSR_HA); + + if ((reg & ME_PGIC_HRA) == 0) + goto notsupported; + + if (dev->version.major_version < HBM_MAJOR_VERSION_PGI) + goto notsupported; + + if (dev->version.major_version == HBM_MAJOR_VERSION_PGI && + dev->version.minor_version < HBM_MINOR_VERSION_PGI) + goto notsupported; + + return true; + +notsupported: + dev_dbg(&dev->pdev->dev, "pg: not supported: HGP = %d hbm version %d.%d ?= %d.%d\n", + !!(reg & ME_PGIC_HRA), + dev->version.major_version, + dev->version.minor_version, + HBM_MAJOR_VERSION_PGI, + HBM_MINOR_VERSION_PGI); + + return false; +} + +/** * mei_me_irq_quick_handler - The ISR of the MEI device * * @irq: The irq number @@ -469,7 +638,7 @@ 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; s32 slots; - int rets; + int rets = 0; dev_dbg(&dev->pdev->dev, "function called after ISR to handle the interrupt processing.\n"); /* initialize our complete list */ @@ -482,59 +651,123 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) mei_clear_interrupts(dev); /* check if ME wants a reset */ - if (!mei_hw_is_ready(dev) && - dev->dev_state != MEI_DEV_RESETTING && - dev->dev_state != MEI_DEV_INITIALIZING && - dev->dev_state != MEI_DEV_POWER_DOWN && - dev->dev_state != MEI_DEV_POWER_UP) { - dev_dbg(&dev->pdev->dev, "FW not ready.\n"); - mei_reset(dev, 1); - mutex_unlock(&dev->device_lock); - return IRQ_HANDLED; + if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) { + dev_warn(&dev->pdev->dev, "FW not ready: resetting.\n"); + schedule_work(&dev->reset_work); + goto end; } /* check if we need to start the dev */ if (!mei_host_is_ready(dev)) { if (mei_hw_is_ready(dev)) { + mei_me_hw_reset_release(dev); dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); dev->recvd_hw_ready = true; wake_up_interruptible(&dev->wait_hw_ready); - - mutex_unlock(&dev->device_lock); - return IRQ_HANDLED; } else { - dev_dbg(&dev->pdev->dev, "Reset Completed.\n"); - mei_me_hw_reset_release(dev); - mutex_unlock(&dev->device_lock); - return IRQ_HANDLED; + dev_dbg(&dev->pdev->dev, "Spurious Interrupt\n"); } + goto end; } /* check slots available for reading */ slots = mei_count_full_read_slots(dev); while (slots > 0) { - /* we have urgent data to send so break the read */ - if (dev->wr_ext_msg.hdr.length) - break; - dev_dbg(&dev->pdev->dev, "slots =%08x\n", slots); - dev_dbg(&dev->pdev->dev, "call mei_irq_read_handler.\n"); + dev_dbg(&dev->pdev->dev, "slots to read = %08x\n", slots); rets = mei_irq_read_handler(dev, &complete_list, &slots); - if (rets) + /* There is a race between ME write and interrupt delivery: + * Not all data is always available immediately after the + * interrupt, so try to read again on the next interrupt. + */ + if (rets == -ENODATA) + break; + + if (rets && dev->dev_state != MEI_DEV_RESETTING) { + dev_err(&dev->pdev->dev, "mei_irq_read_handler ret = %d.\n", + rets); + schedule_work(&dev->reset_work); goto end; + } } - rets = mei_irq_write_handler(dev, &complete_list); -end: - dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); + dev->hbuf_is_ready = mei_hbuf_is_ready(dev); - mutex_unlock(&dev->device_lock); + /* + * During PG handshake only allowed write is the replay to the + * PG exit message, so block calling write function + * if the pg state is not idle + */ + if (dev->pg_event == MEI_PG_EVENT_IDLE) { + rets = mei_irq_write_handler(dev, &complete_list); + dev->hbuf_is_ready = mei_hbuf_is_ready(dev); + } mei_irq_compl_handler(dev, &complete_list); +end: + dev_dbg(&dev->pdev->dev, "interrupt thread end ret = %d\n", rets); + mutex_unlock(&dev->device_lock); return IRQ_HANDLED; } + +/** + * mei_me_fw_status - retrieve fw status from the pci config space + * + * @dev: the device structure + * @fw_status: fw status registers storage + * + * returns 0 on success an error code otherwise + */ +static int mei_me_fw_status(struct mei_device *dev, + struct mei_fw_status *fw_status) +{ + const u32 pci_cfg_reg[] = {PCI_CFG_HFS_1, PCI_CFG_HFS_2}; + int i; + + if (!fw_status) + return -EINVAL; + + switch (dev->pdev->device) { + case MEI_DEV_ID_IBXPK_1: + case MEI_DEV_ID_IBXPK_2: + case MEI_DEV_ID_CPT_1: + case MEI_DEV_ID_PBG_1: + case MEI_DEV_ID_PPT_1: + case MEI_DEV_ID_PPT_2: + case MEI_DEV_ID_PPT_3: + case MEI_DEV_ID_LPT_H: + case MEI_DEV_ID_LPT_W: + case MEI_DEV_ID_LPT_LP: + case MEI_DEV_ID_LPT_HR: + case MEI_DEV_ID_WPT_LP: + fw_status->count = 2; + break; + case MEI_DEV_ID_ICH10_1: + case MEI_DEV_ID_ICH10_2: + case MEI_DEV_ID_ICH10_3: + case MEI_DEV_ID_ICH10_4: + fw_status->count = 1; + break; + default: + fw_status->count = 0; + break; + } + + for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + pci_cfg_reg[i], &fw_status->status[i]); + if (ret) + return ret; + } + return 0; +} + static const struct mei_hw_ops mei_me_hw_ops = { + .pg_state = mei_me_pg_state, + + .fw_status = mei_me_fw_status, .host_is_ready = mei_me_host_is_ready, .hw_is_ready = mei_me_hw_is_ready, @@ -542,6 +775,8 @@ static const struct mei_hw_ops mei_me_hw_ops = { .hw_config = mei_me_hw_config, .hw_start = mei_me_hw_start, + .pg_is_enabled = mei_me_pg_is_enabled, + .intr_clear = mei_me_intr_clear, .intr_enable = mei_me_intr_enable, .intr_disable = mei_me_intr_disable, @@ -557,14 +792,81 @@ static const struct mei_hw_ops mei_me_hw_ops = { .read = mei_me_read_slots }; +static bool mei_me_fw_type_nm(struct pci_dev *pdev) +{ + u32 reg; + pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®); + /* make sure that bit 9 (NM) is up and bit 10 (DM) is down */ + return (reg & 0x600) == 0x200; +} + +#define MEI_CFG_FW_NM \ + .quirk_probe = mei_me_fw_type_nm + +static bool mei_me_fw_type_sps(struct pci_dev *pdev) +{ + u32 reg; + /* Read ME FW Status check for SPS Firmware */ + pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); + /* if bits [19:16] = 15, running SPS Firmware */ + return (reg & 0xf0000) == 0xf0000; +} + +#define MEI_CFG_FW_SPS \ + .quirk_probe = mei_me_fw_type_sps + + +#define MEI_CFG_LEGACY_HFS \ + .fw_status.count = 0 + +#define MEI_CFG_ICH_HFS \ + .fw_status.count = 1, \ + .fw_status.status[0] = PCI_CFG_HFS_1 + +#define MEI_CFG_PCH_HFS \ + .fw_status.count = 2, \ + .fw_status.status[0] = PCI_CFG_HFS_1, \ + .fw_status.status[1] = PCI_CFG_HFS_2 + + +/* ICH Legacy devices */ +const struct mei_cfg mei_me_legacy_cfg = { + MEI_CFG_LEGACY_HFS, +}; + +/* ICH devices */ +const struct mei_cfg mei_me_ich_cfg = { + MEI_CFG_ICH_HFS, +}; + +/* PCH devices */ +const struct mei_cfg mei_me_pch_cfg = { + MEI_CFG_PCH_HFS, +}; + + +/* PCH Cougar Point and Patsburg with quirk for Node Manager exclusion */ +const struct mei_cfg mei_me_pch_cpt_pbg_cfg = { + MEI_CFG_PCH_HFS, + MEI_CFG_FW_NM, +}; + +/* PCH Lynx Point with quirk for SPS Firmware exclusion */ +const struct mei_cfg mei_me_lpt_cfg = { + MEI_CFG_PCH_HFS, + MEI_CFG_FW_SPS, +}; + /** * mei_me_dev_init - allocates and initializes the mei device structure * * @pdev: The pci device structure + * @cfg: per device generation config * * returns The mei_device_device pointer on success, NULL on failure. */ -struct mei_device *mei_me_dev_init(struct pci_dev *pdev) +struct mei_device *mei_me_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg) { struct mei_device *dev; @@ -573,7 +875,7 @@ struct mei_device *mei_me_dev_init(struct pci_dev *pdev) if (!dev) return NULL; - mei_device_init(dev); + mei_device_init(dev, cfg); dev->ops = &mei_me_hw_ops; diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 80bd829fbd9..12b0f4bbe1f 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -20,9 +20,12 @@ #define _MEI_INTERFACE_H_ #include <linux/mei.h> +#include <linux/irqreturn.h> #include "mei_dev.h" #include "client.h" +#define MEI_ME_RPM_TIMEOUT 500 /* ms */ + struct mei_me_hw { void __iomem *mem_addr; /* @@ -30,11 +33,22 @@ struct mei_me_hw { */ u32 host_hw_state; u32 me_hw_state; + enum mei_pg_state pg_state; }; #define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) -struct mei_device *mei_me_dev_init(struct pci_dev *pdev); +extern const struct mei_cfg mei_me_legacy_cfg; +extern const struct mei_cfg mei_me_ich_cfg; +extern const struct mei_cfg mei_me_pch_cfg; +extern const struct mei_cfg mei_me_pch_cpt_pbg_cfg; +extern const struct mei_cfg mei_me_lpt_cfg; + +struct mei_device *mei_me_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg); + +int mei_me_pg_set_sync(struct mei_device *dev); +int mei_me_pg_unset_sync(struct mei_device *dev); 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/hw-txe-regs.h b/drivers/misc/mei/hw-txe-regs.h new file mode 100644 index 00000000000..f19229c4e65 --- /dev/null +++ b/drivers/misc/mei/hw-txe-regs.h @@ -0,0 +1,294 @@ +/****************************************************************************** + * Intel Management Engine Interface (Intel MEI) Linux driver + * Intel MEI Interface Header + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2013 - 2014 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that 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. + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING + * + * Contact Information: + * Intel Corporation. + * linux-mei@linux.intel.com + * http://www.intel.com + * + * BSD LICENSE + * + * Copyright(c) 2013 - 2014 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef _MEI_HW_TXE_REGS_H_ +#define _MEI_HW_TXE_REGS_H_ + +#include "hw.h" + +#define SEC_ALIVENESS_TIMER_TIMEOUT (5 * MSEC_PER_SEC) +#define SEC_ALIVENESS_WAIT_TIMEOUT (1 * MSEC_PER_SEC) +#define SEC_RESET_WAIT_TIMEOUT (1 * MSEC_PER_SEC) +#define SEC_READY_WAIT_TIMEOUT (5 * MSEC_PER_SEC) +#define START_MESSAGE_RESPONSE_WAIT_TIMEOUT (5 * MSEC_PER_SEC) +#define RESET_CANCEL_WAIT_TIMEOUT (1 * MSEC_PER_SEC) + +enum { + SEC_BAR, + BRIDGE_BAR, + + NUM_OF_MEM_BARS +}; + +/* SeC FW Status Register + * + * FW uses this register in order to report its status to host. + * This register resides in PCI-E config space. + */ +#define PCI_CFG_TXE_FW_STS0 0x40 +# define PCI_CFG_TXE_FW_STS0_WRK_ST_MSK 0x0000000F +# define PCI_CFG_TXE_FW_STS0_OP_ST_MSK 0x000001C0 +# define PCI_CFG_TXE_FW_STS0_FW_INIT_CMPLT 0x00000200 +# define PCI_CFG_TXE_FW_STS0_ERR_CODE_MSK 0x0000F000 +# define PCI_CFG_TXE_FW_STS0_OP_MODE_MSK 0x000F0000 +# define PCI_CFG_TXE_FW_STS0_RST_CNT_MSK 0x00F00000 +#define PCI_CFG_TXE_FW_STS1 0x48 + +#define IPC_BASE_ADDR 0x80400 /* SeC IPC Base Address */ + +/* IPC Input Doorbell Register */ +#define SEC_IPC_INPUT_DOORBELL_REG (0x0000 + IPC_BASE_ADDR) + +/* IPC Input Status Register + * This register indicates whether or not processing of + * the most recent command has been completed by the SEC + * New commands and payloads should not be written by the Host + * until this indicates that the previous command has been processed. + */ +#define SEC_IPC_INPUT_STATUS_REG (0x0008 + IPC_BASE_ADDR) +# define SEC_IPC_INPUT_STATUS_RDY BIT(0) + +/* IPC Host Interrupt Status Register */ +#define SEC_IPC_HOST_INT_STATUS_REG (0x0010 + IPC_BASE_ADDR) +#define SEC_IPC_HOST_INT_STATUS_OUT_DB BIT(0) +#define SEC_IPC_HOST_INT_STATUS_IN_RDY BIT(1) +#define SEC_IPC_HOST_INT_STATUS_HDCP_M0_RCVD BIT(5) +#define SEC_IPC_HOST_INT_STATUS_ILL_MEM_ACCESS BIT(17) +#define SEC_IPC_HOST_INT_STATUS_AES_HKEY_ERR BIT(18) +#define SEC_IPC_HOST_INT_STATUS_DES_HKEY_ERR BIT(19) +#define SEC_IPC_HOST_INT_STATUS_TMRMTB_OVERFLOW BIT(21) + +/* Convenient mask for pending interrupts */ +#define SEC_IPC_HOST_INT_STATUS_PENDING \ + (SEC_IPC_HOST_INT_STATUS_OUT_DB| \ + SEC_IPC_HOST_INT_STATUS_IN_RDY) + +/* IPC Host Interrupt Mask Register */ +#define SEC_IPC_HOST_INT_MASK_REG (0x0014 + IPC_BASE_ADDR) + +# define SEC_IPC_HOST_INT_MASK_OUT_DB BIT(0) /* Output Doorbell Int Mask */ +# define SEC_IPC_HOST_INT_MASK_IN_RDY BIT(1) /* Input Ready Int Mask */ + +/* IPC Input Payload RAM */ +#define SEC_IPC_INPUT_PAYLOAD_REG (0x0100 + IPC_BASE_ADDR) +/* IPC Shared Payload RAM */ +#define IPC_SHARED_PAYLOAD_REG (0x0200 + IPC_BASE_ADDR) + +/* SeC Address Translation Table Entry 2 - Ctrl + * + * This register resides also in SeC's PCI-E Memory space. + */ +#define SATT2_CTRL_REG 0x1040 +# define SATT2_CTRL_VALID_MSK BIT(0) +# define SATT2_CTRL_BR_BASE_ADDR_REG_SHIFT 8 +# define SATT2_CTRL_BRIDGE_HOST_EN_MSK BIT(12) + +/* SATT Table Entry 2 SAP Base Address Register */ +#define SATT2_SAP_BA_REG 0x1044 +/* SATT Table Entry 2 SAP Size Register. */ +#define SATT2_SAP_SIZE_REG 0x1048 + /* SATT Table Entry 2 SAP Bridge Address - LSB Register */ +#define SATT2_BRG_BA_LSB_REG 0x104C + +/* Host High-level Interrupt Status Register */ +#define HHISR_REG 0x2020 +/* Host High-level Interrupt Enable Register + * + * Resides in PCI memory space. This is the top hierarchy for + * interrupts from SeC to host, aggregating both interrupts that + * arrive through HICR registers as well as interrupts + * that arrive via IPC. + */ +#define HHIER_REG 0x2024 +#define IPC_HHIER_SEC BIT(0) +#define IPC_HHIER_BRIDGE BIT(1) +#define IPC_HHIER_MSK (IPC_HHIER_SEC | IPC_HHIER_BRIDGE) + +/* Host High-level Interrupt Mask Register. + * + * Resides in PCI memory space. + * This is the top hierarchy for masking interrupts from SeC to host. + */ +#define HHIMR_REG 0x2028 +#define IPC_HHIMR_SEC BIT(0) +#define IPC_HHIMR_BRIDGE BIT(1) + +/* Host High-level IRQ Status Register */ +#define HHIRQSR_REG 0x202C + +/* Host Interrupt Cause Register 0 - SeC IPC Readiness + * + * This register is both an ICR to Host from PCI Memory Space + * and it is also exposed in the SeC memory space. + * This register is used by SeC's IPC driver in order + * to synchronize with host about IPC interface state. + */ +#define HICR_SEC_IPC_READINESS_REG 0x2040 +#define HICR_SEC_IPC_READINESS_HOST_RDY BIT(0) +#define HICR_SEC_IPC_READINESS_SEC_RDY BIT(1) +#define HICR_SEC_IPC_READINESS_SYS_RDY \ + (HICR_SEC_IPC_READINESS_HOST_RDY | \ + HICR_SEC_IPC_READINESS_SEC_RDY) +#define HICR_SEC_IPC_READINESS_RDY_CLR BIT(2) + +/* Host Interrupt Cause Register 1 - Aliveness Response */ +/* This register is both an ICR to Host from PCI Memory Space + * and it is also exposed in the SeC memory space. + * The register may be used by SeC to ACK a host request for aliveness. + */ +#define HICR_HOST_ALIVENESS_RESP_REG 0x2044 +#define HICR_HOST_ALIVENESS_RESP_ACK BIT(0) + +/* Host Interrupt Cause Register 2 - SeC IPC Output Doorbell */ +#define HICR_SEC_IPC_OUTPUT_DOORBELL_REG 0x2048 + +/* Host Interrupt Status Register. + * + * Resides in PCI memory space. + * This is the main register involved in generating interrupts + * from SeC to host via HICRs. + * The interrupt generation rules are as follows: + * An interrupt will be generated whenever for any i, + * there is a transition from a state where at least one of + * the following conditions did not hold, to a state where + * ALL the following conditions hold: + * A) HISR.INT[i]_STS == 1. + * B) HIER.INT[i]_EN == 1. + */ +#define HISR_REG 0x2060 +#define HISR_INT_0_STS BIT(0) +#define HISR_INT_1_STS BIT(1) +#define HISR_INT_2_STS BIT(2) +#define HISR_INT_3_STS BIT(3) +#define HISR_INT_4_STS BIT(4) +#define HISR_INT_5_STS BIT(5) +#define HISR_INT_6_STS BIT(6) +#define HISR_INT_7_STS BIT(7) +#define HISR_INT_STS_MSK \ + (HISR_INT_0_STS | HISR_INT_1_STS | HISR_INT_2_STS) + +/* Host Interrupt Enable Register. Resides in PCI memory space. */ +#define HIER_REG 0x2064 +#define HIER_INT_0_EN BIT(0) +#define HIER_INT_1_EN BIT(1) +#define HIER_INT_2_EN BIT(2) +#define HIER_INT_3_EN BIT(3) +#define HIER_INT_4_EN BIT(4) +#define HIER_INT_5_EN BIT(5) +#define HIER_INT_6_EN BIT(6) +#define HIER_INT_7_EN BIT(7) + +#define HIER_INT_EN_MSK \ + (HIER_INT_0_EN | HIER_INT_1_EN | HIER_INT_2_EN) + + +/* SEC Memory Space IPC output payload. + * + * This register is part of the output payload which SEC provides to host. + */ +#define BRIDGE_IPC_OUTPUT_PAYLOAD_REG 0x20C0 + +/* SeC Interrupt Cause Register - Host Aliveness Request + * This register is both an ICR to SeC and it is also exposed + * in the host-visible PCI memory space. + * The register is used by host to request SeC aliveness. + */ +#define SICR_HOST_ALIVENESS_REQ_REG 0x214C +#define SICR_HOST_ALIVENESS_REQ_REQUESTED BIT(0) + + +/* SeC Interrupt Cause Register - Host IPC Readiness + * + * This register is both an ICR to SeC and it is also exposed + * in the host-visible PCI memory space. + * This register is used by the host's SeC driver uses in order + * to synchronize with SeC about IPC interface state. + */ +#define SICR_HOST_IPC_READINESS_REQ_REG 0x2150 + + +#define SICR_HOST_IPC_READINESS_HOST_RDY BIT(0) +#define SICR_HOST_IPC_READINESS_SEC_RDY BIT(1) +#define SICR_HOST_IPC_READINESS_SYS_RDY \ + (SICR_HOST_IPC_READINESS_HOST_RDY | \ + SICR_HOST_IPC_READINESS_SEC_RDY) +#define SICR_HOST_IPC_READINESS_RDY_CLR BIT(2) + +/* SeC Interrupt Cause Register - SeC IPC Output Status + * + * This register indicates whether or not processing of the most recent + * command has been completed by the Host. + * New commands and payloads should not be written by SeC until this + * register indicates that the previous command has been processed. + */ +#define SICR_SEC_IPC_OUTPUT_STATUS_REG 0x2154 +# define SEC_IPC_OUTPUT_STATUS_RDY BIT(0) + + + +/* MEI IPC Message payload size 64 bytes */ +#define PAYLOAD_SIZE 64 + +/* MAX size for SATT range 32MB */ +#define SATT_RANGE_MAX (32 << 20) + + +#endif /* _MEI_HW_TXE_REGS_H_ */ + diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c new file mode 100644 index 00000000000..93273783dec --- /dev/null +++ b/drivers/misc/mei/hw-txe.c @@ -0,0 +1,1190 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2013-2014, 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/pci.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/irqreturn.h> + +#include <linux/mei.h> + +#include "mei_dev.h" +#include "hw-txe.h" +#include "client.h" +#include "hbm.h" + +/** + * mei_txe_reg_read - Reads 32bit data from the device + * + * @base_addr: registers base address + * @offset: register offset + * + */ +static inline u32 mei_txe_reg_read(void __iomem *base_addr, + unsigned long offset) +{ + return ioread32(base_addr + offset); +} + +/** + * mei_txe_reg_write - Writes 32bit data to the device + * + * @base_addr: registers base address + * @offset: register offset + * @value: the value to write + */ +static inline void mei_txe_reg_write(void __iomem *base_addr, + unsigned long offset, u32 value) +{ + iowrite32(value, base_addr + offset); +} + +/** + * mei_txe_sec_reg_read_silent - Reads 32bit data from the SeC BAR + * + * @dev: the device structure + * @offset: register offset + * + * Doesn't check for aliveness while Reads 32bit data from the SeC BAR + */ +static inline u32 mei_txe_sec_reg_read_silent(struct mei_txe_hw *hw, + unsigned long offset) +{ + return mei_txe_reg_read(hw->mem_addr[SEC_BAR], offset); +} + +/** + * mei_txe_sec_reg_read - Reads 32bit data from the SeC BAR + * + * @dev: the device structure + * @offset: register offset + * + * Reads 32bit data from the SeC BAR and shout loud if aliveness is not set + */ +static inline u32 mei_txe_sec_reg_read(struct mei_txe_hw *hw, + unsigned long offset) +{ + WARN(!hw->aliveness, "sec read: aliveness not asserted\n"); + return mei_txe_sec_reg_read_silent(hw, offset); +} +/** + * mei_txe_sec_reg_write_silent - Writes 32bit data to the SeC BAR + * doesn't check for aliveness + * + * @dev: the device structure + * @offset: register offset + * @value: value to write + * + * Doesn't check for aliveness while writes 32bit data from to the SeC BAR + */ +static inline void mei_txe_sec_reg_write_silent(struct mei_txe_hw *hw, + unsigned long offset, u32 value) +{ + mei_txe_reg_write(hw->mem_addr[SEC_BAR], offset, value); +} + +/** + * mei_txe_sec_reg_write - Writes 32bit data to the SeC BAR + * + * @dev: the device structure + * @offset: register offset + * @value: value to write + * + * Writes 32bit data from the SeC BAR and shout loud if aliveness is not set + */ +static inline void mei_txe_sec_reg_write(struct mei_txe_hw *hw, + unsigned long offset, u32 value) +{ + WARN(!hw->aliveness, "sec write: aliveness not asserted\n"); + mei_txe_sec_reg_write_silent(hw, offset, value); +} +/** + * mei_txe_br_reg_read - Reads 32bit data from the Bridge BAR + * + * @hw: the device structure + * @offset: offset from which to read the data + * + */ +static inline u32 mei_txe_br_reg_read(struct mei_txe_hw *hw, + unsigned long offset) +{ + return mei_txe_reg_read(hw->mem_addr[BRIDGE_BAR], offset); +} + +/** + * mei_txe_br_reg_write - Writes 32bit data to the Bridge BAR + * + * @hw: the device structure + * @offset: offset from which to write the data + * @value: the byte to write + */ +static inline void mei_txe_br_reg_write(struct mei_txe_hw *hw, + unsigned long offset, u32 value) +{ + mei_txe_reg_write(hw->mem_addr[BRIDGE_BAR], offset, value); +} + +/** + * mei_txe_aliveness_set - request for aliveness change + * + * @dev: the device structure + * @req: requested aliveness value + * + * Request for aliveness change and returns true if the change is + * really needed and false if aliveness is already + * in the requested state + * Requires device lock to be held + */ +static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) +{ + + struct mei_txe_hw *hw = to_txe_hw(dev); + bool do_req = hw->aliveness != req; + + dev_dbg(&dev->pdev->dev, "Aliveness current=%d request=%d\n", + hw->aliveness, req); + if (do_req) { + dev->pg_event = MEI_PG_EVENT_WAIT; + mei_txe_br_reg_write(hw, SICR_HOST_ALIVENESS_REQ_REG, req); + } + return do_req; +} + + +/** + * mei_txe_aliveness_req_get - get aliveness requested register value + * + * @dev: the device structure + * + * Extract HICR_HOST_ALIVENESS_RESP_ACK bit from + * from HICR_HOST_ALIVENESS_REQ register value + */ +static u32 mei_txe_aliveness_req_get(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + u32 reg; + reg = mei_txe_br_reg_read(hw, SICR_HOST_ALIVENESS_REQ_REG); + return reg & SICR_HOST_ALIVENESS_REQ_REQUESTED; +} + +/** + * mei_txe_aliveness_get - get aliveness response register value + * @dev: the device structure + * + * Extract HICR_HOST_ALIVENESS_RESP_ACK bit + * from HICR_HOST_ALIVENESS_RESP register value + */ +static u32 mei_txe_aliveness_get(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + u32 reg; + reg = mei_txe_br_reg_read(hw, HICR_HOST_ALIVENESS_RESP_REG); + return reg & HICR_HOST_ALIVENESS_RESP_ACK; +} + +/** + * mei_txe_aliveness_poll - waits for aliveness to settle + * + * @dev: the device structure + * @expected: expected aliveness value + * + * Polls for HICR_HOST_ALIVENESS_RESP.ALIVENESS_RESP to be set + * returns > 0 if the expected value was received, -ETIME otherwise + */ +static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + int t = 0; + + do { + hw->aliveness = mei_txe_aliveness_get(dev); + if (hw->aliveness == expected) { + dev->pg_event = MEI_PG_EVENT_IDLE; + dev_dbg(&dev->pdev->dev, + "aliveness settled after %d msecs\n", t); + return t; + } + mutex_unlock(&dev->device_lock); + msleep(MSEC_PER_SEC / 5); + mutex_lock(&dev->device_lock); + t += MSEC_PER_SEC / 5; + } while (t < SEC_ALIVENESS_WAIT_TIMEOUT); + + dev->pg_event = MEI_PG_EVENT_IDLE; + dev_err(&dev->pdev->dev, "aliveness timed out\n"); + return -ETIME; +} + +/** + * mei_txe_aliveness_wait - waits for aliveness to settle + * + * @dev: the device structure + * @expected: expected aliveness value + * + * Waits for HICR_HOST_ALIVENESS_RESP.ALIVENESS_RESP to be set + * returns returns 0 on success and < 0 otherwise + */ +static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + const unsigned long timeout = + msecs_to_jiffies(SEC_ALIVENESS_WAIT_TIMEOUT); + long err; + int ret; + + hw->aliveness = mei_txe_aliveness_get(dev); + if (hw->aliveness == expected) + return 0; + + mutex_unlock(&dev->device_lock); + err = wait_event_timeout(hw->wait_aliveness_resp, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + mutex_lock(&dev->device_lock); + + hw->aliveness = mei_txe_aliveness_get(dev); + ret = hw->aliveness == expected ? 0 : -ETIME; + + if (ret) + dev_warn(&dev->pdev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", + err, hw->aliveness, dev->pg_event); + else + dev_dbg(&dev->pdev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", + jiffies_to_msecs(timeout - err), + hw->aliveness, dev->pg_event); + + dev->pg_event = MEI_PG_EVENT_IDLE; + return ret; +} + +/** + * mei_txe_aliveness_set_sync - sets an wait for aliveness to complete + * + * @dev: the device structure + * + * returns returns 0 on success and < 0 otherwise + */ +int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req) +{ + if (mei_txe_aliveness_set(dev, req)) + return mei_txe_aliveness_wait(dev, req); + return 0; +} + +/** + * mei_txe_pg_is_enabled - detect if PG is supported by HW + * + * @dev: the device structure + * + * returns: true is pg supported, false otherwise + */ +static bool mei_txe_pg_is_enabled(struct mei_device *dev) +{ + return true; +} + +/** + * mei_txe_pg_state - translate aliveness register value + * to the mei power gating state + * + * @dev: the device structure + * + * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise + */ +static inline enum mei_pg_state mei_txe_pg_state(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + return hw->aliveness ? MEI_PG_OFF : MEI_PG_ON; +} + +/** + * mei_txe_input_ready_interrupt_enable - sets the Input Ready Interrupt + * + * @dev: the device structure + */ +static void mei_txe_input_ready_interrupt_enable(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + u32 hintmsk; + /* Enable the SEC_IPC_HOST_INT_MASK_IN_RDY interrupt */ + hintmsk = mei_txe_sec_reg_read(hw, SEC_IPC_HOST_INT_MASK_REG); + hintmsk |= SEC_IPC_HOST_INT_MASK_IN_RDY; + mei_txe_sec_reg_write(hw, SEC_IPC_HOST_INT_MASK_REG, hintmsk); +} + +/** + * mei_txe_input_doorbell_set + * - Sets bit 0 in SEC_IPC_INPUT_DOORBELL.IPC_INPUT_DOORBELL. + * @dev: the device structure + */ +static void mei_txe_input_doorbell_set(struct mei_txe_hw *hw) +{ + /* Clear the interrupt cause */ + clear_bit(TXE_INTR_IN_READY_BIT, &hw->intr_cause); + mei_txe_sec_reg_write(hw, SEC_IPC_INPUT_DOORBELL_REG, 1); +} + +/** + * mei_txe_output_ready_set - Sets the SICR_SEC_IPC_OUTPUT_STATUS bit to 1 + * + * @dev: the device structure + */ +static void mei_txe_output_ready_set(struct mei_txe_hw *hw) +{ + mei_txe_br_reg_write(hw, + SICR_SEC_IPC_OUTPUT_STATUS_REG, + SEC_IPC_OUTPUT_STATUS_RDY); +} + +/** + * mei_txe_is_input_ready - check if TXE is ready for receiving data + * + * @dev: the device structure + */ +static bool mei_txe_is_input_ready(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + u32 status; + status = mei_txe_sec_reg_read(hw, SEC_IPC_INPUT_STATUS_REG); + return !!(SEC_IPC_INPUT_STATUS_RDY & status); +} + +/** + * mei_txe_intr_clear - clear all interrupts + * + * @dev: the device structure + */ +static inline void mei_txe_intr_clear(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + mei_txe_sec_reg_write_silent(hw, SEC_IPC_HOST_INT_STATUS_REG, + SEC_IPC_HOST_INT_STATUS_PENDING); + mei_txe_br_reg_write(hw, HISR_REG, HISR_INT_STS_MSK); + mei_txe_br_reg_write(hw, HHISR_REG, IPC_HHIER_MSK); +} + +/** + * mei_txe_intr_disable - disable all interrupts + * + * @dev: the device structure + */ +static void mei_txe_intr_disable(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + mei_txe_br_reg_write(hw, HHIER_REG, 0); + mei_txe_br_reg_write(hw, HIER_REG, 0); +} +/** + * mei_txe_intr_disable - enable all interrupts + * + * @dev: the device structure + */ +static void mei_txe_intr_enable(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + mei_txe_br_reg_write(hw, HHIER_REG, IPC_HHIER_MSK); + mei_txe_br_reg_write(hw, HIER_REG, HIER_INT_EN_MSK); +} + +/** + * mei_txe_pending_interrupts - check if there are pending interrupts + * only Aliveness, Input ready, and output doorbell are of relevance + * + * @dev: the device structure + * + * Checks if there are pending interrupts + * only Aliveness, Readiness, Input ready, and Output doorbell are relevant + */ +static bool mei_txe_pending_interrupts(struct mei_device *dev) +{ + + struct mei_txe_hw *hw = to_txe_hw(dev); + bool ret = (hw->intr_cause & (TXE_INTR_READINESS | + TXE_INTR_ALIVENESS | + TXE_INTR_IN_READY | + TXE_INTR_OUT_DB)); + + if (ret) { + dev_dbg(&dev->pdev->dev, + "Pending Interrupts InReady=%01d Readiness=%01d, Aliveness=%01d, OutDoor=%01d\n", + !!(hw->intr_cause & TXE_INTR_IN_READY), + !!(hw->intr_cause & TXE_INTR_READINESS), + !!(hw->intr_cause & TXE_INTR_ALIVENESS), + !!(hw->intr_cause & TXE_INTR_OUT_DB)); + } + return ret; +} + +/** + * mei_txe_input_payload_write - write a dword to the host buffer + * at offset idx + * + * @dev: the device structure + * @idx: index in the host buffer + * @value: value + */ +static void mei_txe_input_payload_write(struct mei_device *dev, + unsigned long idx, u32 value) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + mei_txe_sec_reg_write(hw, SEC_IPC_INPUT_PAYLOAD_REG + + (idx * sizeof(u32)), value); +} + +/** + * mei_txe_out_data_read - read dword from the device buffer + * at offset idx + * + * @dev: the device structure + * @idx: index in the device buffer + * + * returns register value at index + */ +static u32 mei_txe_out_data_read(const struct mei_device *dev, + unsigned long idx) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + return mei_txe_br_reg_read(hw, + BRIDGE_IPC_OUTPUT_PAYLOAD_REG + (idx * sizeof(u32))); +} + +/* Readiness */ + +/** + * mei_txe_readiness_set_host_rdy + * + * @dev: the device structure + */ +static void mei_txe_readiness_set_host_rdy(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + mei_txe_br_reg_write(hw, + SICR_HOST_IPC_READINESS_REQ_REG, + SICR_HOST_IPC_READINESS_HOST_RDY); +} + +/** + * mei_txe_readiness_clear + * + * @dev: the device structure + */ +static void mei_txe_readiness_clear(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + mei_txe_br_reg_write(hw, SICR_HOST_IPC_READINESS_REQ_REG, + SICR_HOST_IPC_READINESS_RDY_CLR); +} +/** + * mei_txe_readiness_get - Reads and returns + * the HICR_SEC_IPC_READINESS register value + * + * @dev: the device structure + */ +static u32 mei_txe_readiness_get(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + return mei_txe_br_reg_read(hw, HICR_SEC_IPC_READINESS_REG); +} + + +/** + * mei_txe_readiness_is_sec_rdy - check readiness + * for HICR_SEC_IPC_READINESS_SEC_RDY + * + * @readiness - cached readiness state + */ +static inline bool mei_txe_readiness_is_sec_rdy(u32 readiness) +{ + return !!(readiness & HICR_SEC_IPC_READINESS_SEC_RDY); +} + +/** + * mei_txe_hw_is_ready - check if the hw is ready + * + * @dev: the device structure + */ +static bool mei_txe_hw_is_ready(struct mei_device *dev) +{ + u32 readiness = mei_txe_readiness_get(dev); + return mei_txe_readiness_is_sec_rdy(readiness); +} + +/** + * mei_txe_host_is_ready - check if the host is ready + * + * @dev: the device structure + */ +static inline bool mei_txe_host_is_ready(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + u32 reg = mei_txe_br_reg_read(hw, HICR_SEC_IPC_READINESS_REG); + return !!(reg & HICR_SEC_IPC_READINESS_HOST_RDY); +} + +/** + * mei_txe_readiness_wait - wait till readiness settles + * + * @dev: the device structure + * + * returns 0 on success and -ETIME on timeout + */ +static int mei_txe_readiness_wait(struct mei_device *dev) +{ + if (mei_txe_hw_is_ready(dev)) + return 0; + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_hw_ready, dev->recvd_hw_ready, + msecs_to_jiffies(SEC_RESET_WAIT_TIMEOUT)); + mutex_lock(&dev->device_lock); + if (!dev->recvd_hw_ready) { + dev_err(&dev->pdev->dev, "wait for readiness failed\n"); + return -ETIME; + } + + dev->recvd_hw_ready = false; + return 0; +} + +/** + * mei_txe_hw_config - configure hardware at the start of the devices + * + * @dev: the device structure + * + * Configure hardware at the start of the device should be done only + * once at the device probe time + */ +static void mei_txe_hw_config(struct mei_device *dev) +{ + + struct mei_txe_hw *hw = to_txe_hw(dev); + /* Doesn't change in runtime */ + dev->hbuf_depth = PAYLOAD_SIZE / 4; + + hw->aliveness = mei_txe_aliveness_get(dev); + hw->readiness = mei_txe_readiness_get(dev); + + dev_dbg(&dev->pdev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n", + hw->aliveness, hw->readiness); +} + + +/** + * mei_txe_write - writes a message to device. + * + * @dev: the device structure + * @header: header of message + * @buf: message buffer will be written + * returns 1 if success, 0 - otherwise. + */ + +static int mei_txe_write(struct mei_device *dev, + struct mei_msg_hdr *header, unsigned char *buf) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + unsigned long rem; + unsigned long length; + int slots = dev->hbuf_depth; + u32 *reg_buf = (u32 *)buf; + u32 dw_cnt; + int i; + + if (WARN_ON(!header || !buf)) + return -EINVAL; + + length = header->length; + + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); + + dw_cnt = mei_data2slots(length); + if (dw_cnt > slots) + return -EMSGSIZE; + + if (WARN(!hw->aliveness, "txe write: aliveness not asserted\n")) + return -EAGAIN; + + /* Enable Input Ready Interrupt. */ + mei_txe_input_ready_interrupt_enable(dev); + + if (!mei_txe_is_input_ready(dev)) { + struct mei_fw_status fw_status; + mei_fw_status(dev, &fw_status); + dev_err(&dev->pdev->dev, "Input is not ready " FW_STS_FMT "\n", + FW_STS_PRM(fw_status)); + return -EAGAIN; + } + + mei_txe_input_payload_write(dev, 0, *((u32 *)header)); + + for (i = 0; i < length / 4; i++) + mei_txe_input_payload_write(dev, i + 1, reg_buf[i]); + + rem = length & 0x3; + if (rem > 0) { + u32 reg = 0; + memcpy(®, &buf[length - rem], rem); + mei_txe_input_payload_write(dev, i + 1, reg); + } + + /* after each write the whole buffer is consumed */ + hw->slots = 0; + + /* Set Input-Doorbell */ + mei_txe_input_doorbell_set(hw); + + return 0; +} + +/** + * mei_txe_hbuf_max_len - mimics the me hbuf circular buffer + * + * @dev: the device structure + * + * returns the PAYLOAD_SIZE - 4 + */ +static size_t mei_txe_hbuf_max_len(const struct mei_device *dev) +{ + return PAYLOAD_SIZE - sizeof(struct mei_msg_hdr); +} + +/** + * mei_txe_hbuf_empty_slots - mimics the me hbuf circular buffer + * + * @dev: the device structure + * + * returns always hbuf_depth + */ +static int mei_txe_hbuf_empty_slots(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + return hw->slots; +} + +/** + * mei_txe_count_full_read_slots - mimics the me device circular buffer + * + * @dev: the device structure + * + * returns always buffer size in dwords count + */ +static int mei_txe_count_full_read_slots(struct mei_device *dev) +{ + /* read buffers has static size */ + return PAYLOAD_SIZE / 4; +} + +/** + * mei_txe_read_hdr - read message header which is always in 4 first bytes + * + * @dev: the device structure + * + * returns mei message header + */ + +static u32 mei_txe_read_hdr(const struct mei_device *dev) +{ + return mei_txe_out_data_read(dev, 0); +} +/** + * mei_txe_read - reads a message from the txe device. + * + * @dev: the device structure + * @buf: message buffer will be written + * @len: message size will be read + * + * returns -EINVAL on error wrong argument and 0 on success + */ +static int mei_txe_read(struct mei_device *dev, + unsigned char *buf, unsigned long len) +{ + + struct mei_txe_hw *hw = to_txe_hw(dev); + u32 i; + u32 *reg_buf = (u32 *)buf; + u32 rem = len & 0x3; + + if (WARN_ON(!buf || !len)) + return -EINVAL; + + dev_dbg(&dev->pdev->dev, + "buffer-length = %lu buf[0]0x%08X\n", + len, mei_txe_out_data_read(dev, 0)); + + for (i = 0; i < len / 4; i++) { + /* skip header: index starts from 1 */ + u32 reg = mei_txe_out_data_read(dev, i + 1); + dev_dbg(&dev->pdev->dev, "buf[%d] = 0x%08X\n", i, reg); + *reg_buf++ = reg; + } + + if (rem) { + u32 reg = mei_txe_out_data_read(dev, i + 1); + memcpy(reg_buf, ®, rem); + } + + mei_txe_output_ready_set(hw); + return 0; +} + +/** + * mei_txe_hw_reset - resets host and fw. + * + * @dev: the device structure + * @intr_enable: if interrupt should be enabled after reset. + * + * returns 0 on success and < 0 in case of error + */ +static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + + u32 aliveness_req; + /* + * read input doorbell to ensure consistency between Bridge and SeC + * return value might be garbage return + */ + (void)mei_txe_sec_reg_read_silent(hw, SEC_IPC_INPUT_DOORBELL_REG); + + aliveness_req = mei_txe_aliveness_req_get(dev); + hw->aliveness = mei_txe_aliveness_get(dev); + + /* Disable interrupts in this stage we will poll */ + mei_txe_intr_disable(dev); + + /* + * If Aliveness Request and Aliveness Response are not equal then + * wait for them to be equal + * Since we might have interrupts disabled - poll for it + */ + if (aliveness_req != hw->aliveness) + if (mei_txe_aliveness_poll(dev, aliveness_req) < 0) { + dev_err(&dev->pdev->dev, + "wait for aliveness settle failed ... bailing out\n"); + return -EIO; + } + + /* + * If Aliveness Request and Aliveness Response are set then clear them + */ + if (aliveness_req) { + mei_txe_aliveness_set(dev, 0); + if (mei_txe_aliveness_poll(dev, 0) < 0) { + dev_err(&dev->pdev->dev, + "wait for aliveness failed ... bailing out\n"); + return -EIO; + } + } + + /* + * Set rediness RDY_CLR bit + */ + mei_txe_readiness_clear(dev); + + return 0; +} + +/** + * mei_txe_hw_start - start the hardware after reset + * + * @dev: the device structure + * + * returns 0 on success and < 0 in case of error + */ +static int mei_txe_hw_start(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + int ret; + + u32 hisr; + + /* bring back interrupts */ + mei_txe_intr_enable(dev); + + ret = mei_txe_readiness_wait(dev); + if (ret < 0) { + dev_err(&dev->pdev->dev, "wating for readiness failed\n"); + return ret; + } + + /* + * If HISR.INT2_STS interrupt status bit is set then clear it. + */ + hisr = mei_txe_br_reg_read(hw, HISR_REG); + if (hisr & HISR_INT_2_STS) + mei_txe_br_reg_write(hw, HISR_REG, HISR_INT_2_STS); + + /* Clear the interrupt cause of OutputDoorbell */ + clear_bit(TXE_INTR_OUT_DB_BIT, &hw->intr_cause); + + ret = mei_txe_aliveness_set_sync(dev, 1); + if (ret < 0) { + dev_err(&dev->pdev->dev, "wait for aliveness failed ... bailing out\n"); + return ret; + } + + /* enable input ready interrupts: + * SEC_IPC_HOST_INT_MASK.IPC_INPUT_READY_INT_MASK + */ + mei_txe_input_ready_interrupt_enable(dev); + + + /* Set the SICR_SEC_IPC_OUTPUT_STATUS.IPC_OUTPUT_READY bit */ + mei_txe_output_ready_set(hw); + + /* Set bit SICR_HOST_IPC_READINESS.HOST_RDY + */ + mei_txe_readiness_set_host_rdy(dev); + + return 0; +} + +/** + * mei_txe_check_and_ack_intrs - translate multi BAR interrupt into + * single bit mask and acknowledge the interrupts + * + * @dev: the device structure + * @do_ack: acknowledge interrupts + */ +static bool mei_txe_check_and_ack_intrs(struct mei_device *dev, bool do_ack) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + u32 hisr; + u32 hhisr; + u32 ipc_isr; + u32 aliveness; + bool generated; + + /* read interrupt registers */ + hhisr = mei_txe_br_reg_read(hw, HHISR_REG); + generated = (hhisr & IPC_HHIER_MSK); + if (!generated) + goto out; + + hisr = mei_txe_br_reg_read(hw, HISR_REG); + + aliveness = mei_txe_aliveness_get(dev); + if (hhisr & IPC_HHIER_SEC && aliveness) + ipc_isr = mei_txe_sec_reg_read_silent(hw, + SEC_IPC_HOST_INT_STATUS_REG); + else + ipc_isr = 0; + + generated = generated || + (hisr & HISR_INT_STS_MSK) || + (ipc_isr & SEC_IPC_HOST_INT_STATUS_PENDING); + + if (generated && do_ack) { + /* Save the interrupt causes */ + hw->intr_cause |= hisr & HISR_INT_STS_MSK; + if (ipc_isr & SEC_IPC_HOST_INT_STATUS_IN_RDY) + hw->intr_cause |= TXE_INTR_IN_READY; + + + mei_txe_intr_disable(dev); + /* Clear the interrupts in hierarchy: + * IPC and Bridge, than the High Level */ + mei_txe_sec_reg_write_silent(hw, + SEC_IPC_HOST_INT_STATUS_REG, ipc_isr); + mei_txe_br_reg_write(hw, HISR_REG, hisr); + mei_txe_br_reg_write(hw, HHISR_REG, hhisr); + } + +out: + return generated; +} + +/** + * mei_txe_irq_quick_handler - The ISR of the MEI device + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + */ +irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id) +{ + struct mei_device *dev = dev_id; + + if (mei_txe_check_and_ack_intrs(dev, true)) + return IRQ_WAKE_THREAD; + return IRQ_NONE; +} + + +/** + * mei_txe_irq_thread_handler - txe interrupt thread + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + * + */ +irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + struct mei_txe_hw *hw = to_txe_hw(dev); + struct mei_cl_cb complete_list; + s32 slots; + int rets = 0; + + dev_dbg(&dev->pdev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n", + mei_txe_br_reg_read(hw, HHISR_REG), + mei_txe_br_reg_read(hw, HISR_REG), + mei_txe_sec_reg_read_silent(hw, SEC_IPC_HOST_INT_STATUS_REG)); + + + /* initialize our complete list */ + mutex_lock(&dev->device_lock); + mei_io_list_init(&complete_list); + + if (pci_dev_msi_enabled(dev->pdev)) + mei_txe_check_and_ack_intrs(dev, true); + + /* show irq events */ + mei_txe_pending_interrupts(dev); + + hw->aliveness = mei_txe_aliveness_get(dev); + hw->readiness = mei_txe_readiness_get(dev); + + /* Readiness: + * Detection of TXE driver going through reset + * or TXE driver resetting the HECI interface. + */ + if (test_and_clear_bit(TXE_INTR_READINESS_BIT, &hw->intr_cause)) { + dev_dbg(&dev->pdev->dev, "Readiness Interrupt was received...\n"); + + /* Check if SeC is going through reset */ + if (mei_txe_readiness_is_sec_rdy(hw->readiness)) { + dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); + dev->recvd_hw_ready = true; + } else { + dev->recvd_hw_ready = false; + if (dev->dev_state != MEI_DEV_RESETTING) { + + dev_warn(&dev->pdev->dev, "FW not ready: resetting.\n"); + schedule_work(&dev->reset_work); + goto end; + + } + } + wake_up(&dev->wait_hw_ready); + } + + /************************************************************/ + /* Check interrupt cause: + * Aliveness: Detection of SeC acknowledge of host request that + * it remain alive or host cancellation of that request. + */ + + if (test_and_clear_bit(TXE_INTR_ALIVENESS_BIT, &hw->intr_cause)) { + /* Clear the interrupt cause */ + dev_dbg(&dev->pdev->dev, + "Aliveness Interrupt: Status: %d\n", hw->aliveness); + dev->pg_event = MEI_PG_EVENT_RECEIVED; + if (waitqueue_active(&hw->wait_aliveness_resp)) + wake_up(&hw->wait_aliveness_resp); + } + + + /* Output Doorbell: + * Detection of SeC having sent output to host + */ + slots = mei_count_full_read_slots(dev); + if (test_and_clear_bit(TXE_INTR_OUT_DB_BIT, &hw->intr_cause)) { + /* Read from TXE */ + rets = mei_irq_read_handler(dev, &complete_list, &slots); + if (rets && dev->dev_state != MEI_DEV_RESETTING) { + dev_err(&dev->pdev->dev, + "mei_irq_read_handler ret = %d.\n", rets); + + schedule_work(&dev->reset_work); + goto end; + } + } + /* Input Ready: Detection if host can write to SeC */ + if (test_and_clear_bit(TXE_INTR_IN_READY_BIT, &hw->intr_cause)) { + dev->hbuf_is_ready = true; + hw->slots = dev->hbuf_depth; + } + + if (hw->aliveness && dev->hbuf_is_ready) { + /* get the real register value */ + dev->hbuf_is_ready = mei_hbuf_is_ready(dev); + rets = mei_irq_write_handler(dev, &complete_list); + if (rets && rets != -EMSGSIZE) + dev_err(&dev->pdev->dev, "mei_irq_write_handler ret = %d.\n", + rets); + dev->hbuf_is_ready = mei_hbuf_is_ready(dev); + } + + mei_irq_compl_handler(dev, &complete_list); + +end: + dev_dbg(&dev->pdev->dev, "interrupt thread end ret = %d\n", rets); + + mutex_unlock(&dev->device_lock); + + mei_enable_interrupts(dev); + return IRQ_HANDLED; +} + + +/** + * mei_txe_fw_status - retrieve fw status from the pci config space + * + * @dev: the device structure + * @fw_status: fw status registers storage + * + * returns: 0 on success an error code otherwise + */ +static int mei_txe_fw_status(struct mei_device *dev, + struct mei_fw_status *fw_status) +{ + const u32 pci_cfg_reg[] = {PCI_CFG_TXE_FW_STS0, PCI_CFG_TXE_FW_STS1}; + int i; + + if (!fw_status) + return -EINVAL; + + fw_status->count = 2; + + for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + pci_cfg_reg[i], &fw_status->status[i]); + if (ret) + return ret; + } + + return 0; +} + +static const struct mei_hw_ops mei_txe_hw_ops = { + + .fw_status = mei_txe_fw_status, + .host_is_ready = mei_txe_host_is_ready, + + .pg_state = mei_txe_pg_state, + + .hw_is_ready = mei_txe_hw_is_ready, + .hw_reset = mei_txe_hw_reset, + .hw_config = mei_txe_hw_config, + .hw_start = mei_txe_hw_start, + + .pg_is_enabled = mei_txe_pg_is_enabled, + + .intr_clear = mei_txe_intr_clear, + .intr_enable = mei_txe_intr_enable, + .intr_disable = mei_txe_intr_disable, + + .hbuf_free_slots = mei_txe_hbuf_empty_slots, + .hbuf_is_ready = mei_txe_is_input_ready, + .hbuf_max_len = mei_txe_hbuf_max_len, + + .write = mei_txe_write, + + .rdbuf_full_slots = mei_txe_count_full_read_slots, + .read_hdr = mei_txe_read_hdr, + + .read = mei_txe_read, + +}; + +#define MEI_CFG_TXE_FW_STS \ + .fw_status.count = 2, \ + .fw_status.status[0] = PCI_CFG_TXE_FW_STS0, \ + .fw_status.status[1] = PCI_CFG_TXE_FW_STS1 + +const struct mei_cfg mei_txe_cfg = { + MEI_CFG_TXE_FW_STS, +}; + + +/** + * mei_txe_dev_init - allocates and initializes txe hardware specific structure + * + * @pdev - pci device + * @cfg - per device generation config + * + * returns struct mei_device * on success or NULL; + * + */ +struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg) +{ + struct mei_device *dev; + struct mei_txe_hw *hw; + + dev = kzalloc(sizeof(struct mei_device) + + sizeof(struct mei_txe_hw), GFP_KERNEL); + if (!dev) + return NULL; + + mei_device_init(dev, cfg); + + hw = to_txe_hw(dev); + + init_waitqueue_head(&hw->wait_aliveness_resp); + + dev->ops = &mei_txe_hw_ops; + + dev->pdev = pdev; + return dev; +} + +/** + * mei_txe_setup_satt2 - SATT2 configuration for DMA support. + * + * @dev: the device structure + * @addr: physical address start of the range + * @range: physical range size + */ +int mei_txe_setup_satt2(struct mei_device *dev, phys_addr_t addr, u32 range) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + + u32 lo32 = lower_32_bits(addr); + u32 hi32 = upper_32_bits(addr); + u32 ctrl; + + /* SATT is limited to 36 Bits */ + if (hi32 & ~0xF) + return -EINVAL; + + /* SATT has to be 16Byte aligned */ + if (lo32 & 0xF) + return -EINVAL; + + /* SATT range has to be 4Bytes aligned */ + if (range & 0x4) + return -EINVAL; + + /* SATT is limited to 32 MB range*/ + if (range > SATT_RANGE_MAX) + return -EINVAL; + + ctrl = SATT2_CTRL_VALID_MSK; + ctrl |= hi32 << SATT2_CTRL_BR_BASE_ADDR_REG_SHIFT; + + mei_txe_br_reg_write(hw, SATT2_SAP_SIZE_REG, range); + mei_txe_br_reg_write(hw, SATT2_BRG_BA_LSB_REG, lo32); + mei_txe_br_reg_write(hw, SATT2_CTRL_REG, ctrl); + dev_dbg(&dev->pdev->dev, "SATT2: SAP_SIZE_OFFSET=0x%08X, BRG_BA_LSB_OFFSET=0x%08X, CTRL_OFFSET=0x%08X\n", + range, lo32, ctrl); + + return 0; +} diff --git a/drivers/misc/mei/hw-txe.h b/drivers/misc/mei/hw-txe.h new file mode 100644 index 00000000000..e244af79167 --- /dev/null +++ b/drivers/misc/mei/hw-txe.h @@ -0,0 +1,77 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2013-2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef _MEI_HW_TXE_H_ +#define _MEI_HW_TXE_H_ + +#include <linux/irqreturn.h> + +#include "hw.h" +#include "hw-txe-regs.h" + +#define MEI_TXI_RPM_TIMEOUT 500 /* ms */ + +/* Flatten Hierarchy interrupt cause */ +#define TXE_INTR_READINESS_BIT 0 /* HISR_INT_0_STS */ +#define TXE_INTR_READINESS HISR_INT_0_STS +#define TXE_INTR_ALIVENESS_BIT 1 /* HISR_INT_1_STS */ +#define TXE_INTR_ALIVENESS HISR_INT_1_STS +#define TXE_INTR_OUT_DB_BIT 2 /* HISR_INT_2_STS */ +#define TXE_INTR_OUT_DB HISR_INT_2_STS +#define TXE_INTR_IN_READY_BIT 8 /* beyond HISR */ +#define TXE_INTR_IN_READY BIT(8) + +/** + * struct mei_txe_hw - txe hardware specifics + * + * @mem_addr: SeC and BRIDGE bars + * @aliveness: aliveness (power gating) state of the hardware + * @readiness: readiness state of the hardware + * @wait_aliveness_resp: aliveness wait queue + * @intr_cause: translated interrupt cause + */ +struct mei_txe_hw { + void __iomem *mem_addr[NUM_OF_MEM_BARS]; + u32 aliveness; + u32 readiness; + u32 slots; + + wait_queue_head_t wait_aliveness_resp; + + unsigned long intr_cause; +}; + +#define to_txe_hw(dev) (struct mei_txe_hw *)((dev)->hw) + +static inline struct mei_device *hw_txe_to_mei(struct mei_txe_hw *hw) +{ + return container_of((void *)hw, struct mei_device, hw); +} + +extern const struct mei_cfg mei_txe_cfg; + +struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg); + +irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id); +irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id); + +int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req); + +int mei_txe_setup_satt2(struct mei_device *dev, phys_addr_t addr, u32 range); + + +#endif /* _MEI_HW_TXE_H_ */ diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index cb2f556b425..dd448e58cc8 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -22,7 +22,7 @@ /* * Timeouts in Seconds */ -#define MEI_INTEROP_TIMEOUT 7 /* Timeout on ready message */ +#define MEI_HW_READY_TIMEOUT 2 /* Timeout on ready message */ #define MEI_CONNECT_TIMEOUT 3 /* HPS: at least 2 seconds */ #define MEI_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */ @@ -31,13 +31,20 @@ #define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */ #define MEI_IAMTHIF_READ_TIMER 10 /* HPS */ +#define MEI_PGI_TIMEOUT 1 /* PG Isolation time response 1 sec */ +#define MEI_HBM_TIMEOUT 1 /* 1 second */ /* * MEI Version */ -#define HBM_MINOR_VERSION 0 +#define HBM_MINOR_VERSION 1 #define HBM_MAJOR_VERSION 1 -#define HBM_TIMEOUT 1 /* 1 second */ + +/* + * MEI version with PGI support + */ +#define HBM_MINOR_VERSION_PGI 1 +#define HBM_MAJOR_VERSION_PGI 1 /* Host bus message command opcode */ #define MEI_HBM_CMD_OP_MSK 0x7f @@ -69,6 +76,11 @@ #define MEI_FLOW_CONTROL_CMD 0x08 +#define MEI_PG_ISOLATION_ENTRY_REQ_CMD 0x0a +#define MEI_PG_ISOLATION_ENTRY_RES_CMD 0x8a +#define MEI_PG_ISOLATION_EXIT_REQ_CMD 0x0b +#define MEI_PG_ISOLATION_EXIT_RES_CMD 0x8b + /* * MEI Stop Reason * used by hbm_host_stop_request.reason @@ -89,19 +101,19 @@ enum mei_stop_reason_types { * Client Connect Status * used by hbm_client_connect_response.status */ -enum client_connect_status_types { - CCS_SUCCESS = 0x00, - CCS_NOT_FOUND = 0x01, - CCS_ALREADY_STARTED = 0x02, - CCS_OUT_OF_RESOURCES = 0x03, - CCS_MESSAGE_SMALL = 0x04 +enum mei_cl_connect_status { + MEI_CL_CONN_SUCCESS = 0x00, + MEI_CL_CONN_NOT_FOUND = 0x01, + MEI_CL_CONN_ALREADY_STARTED = 0x02, + MEI_CL_CONN_OUT_OF_RESOURCES = 0x03, + MEI_CL_CONN_MESSAGE_SMALL = 0x04 }; /* * Client Disconnect Status */ -enum client_disconnect_status_types { - CDS_SUCCESS = 0x00 +enum mei_cl_disconnect_status { + MEI_CL_DISCONN_SUCCESS = 0x00 }; /* @@ -111,7 +123,8 @@ struct mei_msg_hdr { u32 me_addr:8; u32 host_addr:8; u32 length:9; - u32 reserved:6; + u32 reserved:5; + u32 internal:1; u32 msg_complete:1; } __packed; @@ -207,6 +220,17 @@ struct hbm_props_response { } __packed; /** + * struct hbm_power_gate - power gate request/response + * + * @hbm_cmd - bus message command header + * @reserved[3] + */ +struct hbm_power_gate { + u8 hbm_cmd; + u8 reserved[3]; +} __packed; + +/** * struct hbm_client_connect_request - connect/disconnect request * * @hbm_cmd - bus message command header diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 92c73118b13..00692922248 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -43,33 +43,120 @@ const char *mei_dev_state_str(int state) #undef MEI_DEV_STATE } -void mei_device_init(struct mei_device *dev) + +/** + * mei_cancel_work. Cancel mei background jobs + * + * @dev: the device structure + * + * returns 0 on success or < 0 if the reset hasn't succeeded + */ +void mei_cancel_work(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; + cancel_work_sync(&dev->init_work); + cancel_work_sync(&dev->reset_work); - mei_io_list_init(&dev->read_list); - mei_io_list_init(&dev->write_list); - mei_io_list_init(&dev->write_waiting_list); - mei_io_list_init(&dev->ctrl_wr_list); - mei_io_list_init(&dev->ctrl_rd_list); + cancel_delayed_work(&dev->timer_work); +} +EXPORT_SYMBOL_GPL(mei_cancel_work); - INIT_DELAYED_WORK(&dev->timer_work, mei_timer); - INIT_WORK(&dev->init_work, mei_host_client_init); +/** + * mei_reset - resets host and fw. + * + * @dev: the device structure + */ +int mei_reset(struct mei_device *dev) +{ + enum mei_dev_state state = dev->dev_state; + bool interrupts_enabled; + int ret; - 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); + if (state != MEI_DEV_INITIALIZING && + state != MEI_DEV_DISABLED && + state != MEI_DEV_POWER_DOWN && + state != MEI_DEV_POWER_UP) { + struct mei_fw_status fw_status; + mei_fw_status(dev, &fw_status); + dev_warn(&dev->pdev->dev, + "unexpected reset: dev_state = %s " FW_STS_FMT "\n", + mei_dev_state_str(state), FW_STS_PRM(fw_status)); + } + + /* we're already in reset, cancel the init timer + * if the reset was called due the hbm protocol error + * we need to call it before hw start + * so the hbm watchdog won't kick in + */ + mei_hbm_idle(dev); + /* enter reset flow */ + interrupts_enabled = state != MEI_DEV_POWER_DOWN; + dev->dev_state = MEI_DEV_RESETTING; + + dev->reset_count++; + if (dev->reset_count > MEI_MAX_CONSEC_RESET) { + dev_err(&dev->pdev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); + dev->dev_state = MEI_DEV_DISABLED; + return -ENODEV; + } + + ret = mei_hw_reset(dev, interrupts_enabled); + /* fall through and remove the sw state even if hw reset has failed */ + + /* no need to clean up software state in case of power up */ + if (state != MEI_DEV_INITIALIZING && + state != MEI_DEV_POWER_UP) { + + /* remove all waiting requests */ + mei_cl_all_write_clear(dev); + + mei_cl_all_disconnect(dev); + + /* wake up all readers and writers so they can be interrupted */ + mei_cl_all_wakeup(dev); + + /* remove entry if already in list */ + dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); + mei_cl_unlink(&dev->wd_cl); + mei_cl_unlink(&dev->iamthif_cl); + mei_amthif_reset_params(dev); + } + + mei_hbm_reset(dev); + + dev->rd_msg_hdr = 0; + dev->wd_pending = false; + + if (ret) { + dev_err(&dev->pdev->dev, "hw_reset failed ret = %d\n", ret); + return ret; + } + + if (state == MEI_DEV_POWER_DOWN) { + dev_dbg(&dev->pdev->dev, "powering down: end of reset\n"); + dev->dev_state = MEI_DEV_DISABLED; + return 0; + } + + ret = mei_hw_start(dev); + if (ret) { + dev_err(&dev->pdev->dev, "hw_start failed ret = %d\n", ret); + return ret; + } + + dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); + + dev->dev_state = MEI_DEV_INIT_CLIENTS; + ret = mei_hbm_start_req(dev); + if (ret) { + dev_err(&dev->pdev->dev, "hbm_start failed ret = %d\n", ret); + dev->dev_state = MEI_DEV_RESETTING; + return ret; + } + + return 0; } -EXPORT_SYMBOL_GPL(mei_device_init); +EXPORT_SYMBOL_GPL(mei_reset); /** * mei_start - initializes host and fw to start work. @@ -80,16 +167,32 @@ EXPORT_SYMBOL_GPL(mei_device_init); */ int mei_start(struct mei_device *dev) { + int ret; mutex_lock(&dev->device_lock); - /* acknowledge interrupt and stop interupts */ + /* acknowledge interrupt and stop interrupts */ mei_clear_interrupts(dev); mei_hw_config(dev); dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); - mei_reset(dev, 1); + dev->reset_count = 0; + do { + dev->dev_state = MEI_DEV_INITIALIZING; + ret = mei_reset(dev); + + if (ret == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) { + dev_err(&dev->pdev->dev, "reset failed ret = %d", ret); + goto err; + } + } while (ret); + + /* we cannot start the device w/o hbm start message completed */ + if (dev->dev_state == MEI_DEV_DISABLED) { + dev_err(&dev->pdev->dev, "reset failed"); + goto err; + } if (mei_hbm_start_wait(dev)) { dev_err(&dev->pdev->dev, "HBM haven't started"); @@ -124,103 +227,79 @@ err: EXPORT_SYMBOL_GPL(mei_start); /** - * mei_reset - resets host and fw. + * mei_restart - restart device after suspend * * @dev: the device structure - * @interrupts_enabled: if interrupt should be enabled after reset. + * + * returns 0 on success or -ENODEV if the restart hasn't succeeded */ -void mei_reset(struct mei_device *dev, int interrupts_enabled) +int mei_restart(struct mei_device *dev) { - bool unexpected; - int ret; + int err; - unexpected = (dev->dev_state != MEI_DEV_INITIALIZING && - dev->dev_state != MEI_DEV_DISABLED && - dev->dev_state != MEI_DEV_POWER_DOWN && - dev->dev_state != MEI_DEV_POWER_UP); + mutex_lock(&dev->device_lock); - ret = mei_hw_reset(dev, interrupts_enabled); - if (ret) { - dev_err(&dev->pdev->dev, "hw reset failed disabling the device\n"); - interrupts_enabled = false; - dev->dev_state = MEI_DEV_DISABLED; - } + mei_clear_interrupts(dev); - dev->hbm_state = MEI_HBM_IDLE; + dev->dev_state = MEI_DEV_POWER_UP; + dev->reset_count = 0; - if (dev->dev_state != MEI_DEV_INITIALIZING && - dev->dev_state != MEI_DEV_POWER_UP) { - if (dev->dev_state != MEI_DEV_DISABLED && - dev->dev_state != MEI_DEV_POWER_DOWN) - dev->dev_state = MEI_DEV_RESETTING; + err = mei_reset(dev); - /* remove all waiting requests */ - mei_cl_all_write_clear(dev); + mutex_unlock(&dev->device_lock); - mei_cl_all_disconnect(dev); + if (err == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) { + dev_err(&dev->pdev->dev, "device disabled = %d\n", err); + return -ENODEV; + } - /* wake up all readings so they can be interrupted */ - mei_cl_all_wakeup(dev); + /* try to start again */ + if (err) + schedule_work(&dev->reset_work); - /* remove entry if already in list */ - dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); - mei_cl_unlink(&dev->wd_cl); - if (dev->open_handle_count > 0) - dev->open_handle_count--; - mei_cl_unlink(&dev->iamthif_cl); - if (dev->open_handle_count > 0) - dev->open_handle_count--; - mei_amthif_reset_params(dev); - memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg)); - } + return 0; +} +EXPORT_SYMBOL_GPL(mei_restart); - dev->me_clients_num = 0; - dev->rd_msg_hdr = 0; - dev->wd_pending = false; +static void mei_reset_work(struct work_struct *work) +{ + struct mei_device *dev = + container_of(work, struct mei_device, reset_work); + int ret; - if (unexpected) - dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", - mei_dev_state_str(dev->dev_state)); + mutex_lock(&dev->device_lock); - if (!interrupts_enabled) { - dev_dbg(&dev->pdev->dev, "intr not enabled end of reset\n"); - return; - } + ret = mei_reset(dev); - ret = mei_hw_start(dev); - if (ret) { - dev_err(&dev->pdev->dev, "hw_start failed disabling the device\n"); - dev->dev_state = MEI_DEV_DISABLED; + mutex_unlock(&dev->device_lock); + + if (dev->dev_state == MEI_DEV_DISABLED) { + dev_err(&dev->pdev->dev, "device disabled = %d\n", ret); return; } - 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); - + /* retry reset in case of failure */ + if (ret) + schedule_work(&dev->reset_work); } -EXPORT_SYMBOL_GPL(mei_reset); void mei_stop(struct mei_device *dev) { dev_dbg(&dev->pdev->dev, "stopping the device.\n"); - flush_scheduled_work(); + mei_cancel_work(dev); - mutex_lock(&dev->device_lock); + mei_nfc_host_exit(dev); - cancel_delayed_work(&dev->timer_work); + mei_cl_bus_remove_devices(dev); - mei_wd_stop(dev); + mutex_lock(&dev->device_lock); - mei_nfc_host_exit(); + mei_wd_stop(dev); dev->dev_state = MEI_DEV_POWER_DOWN; - mei_reset(dev, 0); + mei_reset(dev); mutex_unlock(&dev->device_lock); @@ -228,5 +307,89 @@ void mei_stop(struct mei_device *dev) } EXPORT_SYMBOL_GPL(mei_stop); +/** + * mei_write_is_idle - check if the write queues are idle + * + * @dev: the device structure + * + * returns true of there is no pending write + */ +bool mei_write_is_idle(struct mei_device *dev) +{ + bool idle = (dev->dev_state == MEI_DEV_ENABLED && + list_empty(&dev->ctrl_wr_list.list) && + list_empty(&dev->write_list.list)); + + dev_dbg(&dev->pdev->dev, "write pg: is idle[%d] state=%s ctrl=%d write=%d\n", + idle, + mei_dev_state_str(dev->dev_state), + list_empty(&dev->ctrl_wr_list.list), + list_empty(&dev->write_list.list)); + + return idle; +} +EXPORT_SYMBOL_GPL(mei_write_is_idle); + +int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) +{ + int i; + const struct mei_fw_status *fw_src = &dev->cfg->fw_status; + + if (!fw_status) + return -EINVAL; + + fw_status->count = fw_src->count; + for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + fw_src->status[i], &fw_status->status[i]); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mei_fw_status); +void mei_device_init(struct mei_device *dev, const struct mei_cfg *cfg) +{ + /* 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_pg); + init_waitqueue_head(&dev->wait_recvd_msg); + init_waitqueue_head(&dev->wait_stop_wd); + dev->dev_state = MEI_DEV_INITIALIZING; + dev->reset_count = 0; + + mei_io_list_init(&dev->read_list); + mei_io_list_init(&dev->write_list); + 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_WORK(&dev->reset_work, mei_reset_work); + + 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); + + bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); + dev->open_handle_count = 0; + + /* + * Reserving the first client ID + * 0: Reserved for MEI Bus Message communications + */ + bitmap_set(dev->host_clients_map, 0, 1); + + dev->pg_event = MEI_PG_EVENT_IDLE; + dev->cfg = cfg; +} +EXPORT_SYMBOL_GPL(mei_device_init); diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 4b59cb742de..4e3cba6da3f 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -26,12 +26,11 @@ #include "mei_dev.h" #include "hbm.h" -#include "hw-me.h" #include "client.h" /** - * mei_irq_compl_handler - dispatch complete handelers + * mei_irq_compl_handler - dispatch complete handlers * for the completed callbacks * * @dev - mei device @@ -113,13 +112,13 @@ static int mei_cl_irq_read_msg(struct mei_device *dev, if (cb->response_buffer.size == 0 || cb->response_buffer.data == NULL) { - dev_err(&dev->pdev->dev, "response buffer is not allocated.\n"); + cl_err(dev, cl, "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", + cl_dbg(dev, cl, "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, @@ -127,7 +126,7 @@ static int mei_cl_irq_read_msg(struct mei_device *dev, GFP_KERNEL); if (!buffer) { - dev_err(&dev->pdev->dev, "allocation failed.\n"); + cl_err(dev, cl, "allocation failed.\n"); list_del(&cb->list); return -ENOMEM; } @@ -143,9 +142,7 @@ static int mei_cl_irq_read_msg(struct mei_device *dev, 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, + cl_dbg(dev, cl, "completed read length = %lu\n", cb->buf_idx); list_add_tail(&cb->list, &complete_list->list); } @@ -163,29 +160,63 @@ static int mei_cl_irq_read_msg(struct mei_device *dev, } /** + * mei_cl_irq_disconnect_rsp - send disconnection response message + * + * @cl: client + * @cb: callback block. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb, + struct mei_cl_cb *cmpl_list) +{ + struct mei_device *dev = cl->dev; + u32 msg_slots; + int slots; + int ret; + + slots = mei_hbuf_empty_slots(dev); + msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_response)); + + if (slots < msg_slots) + return -EMSGSIZE; + + ret = mei_hbm_cl_disconnect_rsp(dev, cl); + + cl->state = MEI_FILE_DISCONNECTED; + cl->status = 0; + list_del(&cb->list); + mei_io_cb_free(cb); + + return ret; +} + + + +/** * mei_cl_irq_close - processes close related operation from * interrupt thread context - send disconnect request * * @cl: client * @cb: callback block. - * @slots: free slots. * @cmpl_list: complete list. * * returns 0, OK; otherwise, error. */ static int mei_cl_irq_close(struct mei_cl *cl, struct mei_cl_cb *cb, - s32 *slots, struct mei_cl_cb *cmpl_list) + struct mei_cl_cb *cmpl_list) { struct mei_device *dev = cl->dev; + u32 msg_slots; + int slots; - u32 msg_slots = - mei_data2slots(sizeof(struct hbm_client_connect_request)); + msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); + slots = mei_hbuf_empty_slots(dev); - if (*slots < msg_slots) + if (slots < msg_slots) return -EMSGSIZE; - *slots -= msg_slots; - if (mei_hbm_cl_disconnect_req(dev, cl)) { cl->status = 0; cb->buf_idx = 0; @@ -209,32 +240,32 @@ static int mei_cl_irq_close(struct mei_cl *cl, struct mei_cl_cb *cb, * * @cl: client * @cb: callback block. - * @slots: free slots. * @cmpl_list: complete list. * * returns 0, OK; otherwise, error. */ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, - s32 *slots, struct mei_cl_cb *cmpl_list) + struct mei_cl_cb *cmpl_list) { struct mei_device *dev = cl->dev; + u32 msg_slots; + int slots; + int ret; - u32 msg_slots = mei_data2slots(sizeof(struct hbm_flow_control)); + msg_slots = mei_data2slots(sizeof(struct hbm_flow_control)); + slots = mei_hbuf_empty_slots(dev); - if (*slots < msg_slots) { - /* return the cancel routine */ - list_del(&cb->list); + if (slots < msg_slots) return -EMSGSIZE; - } - - *slots -= msg_slots; - if (mei_hbm_cl_flow_control_req(dev, cl)) { - cl->status = -ENODEV; + ret = mei_hbm_cl_flow_control_req(dev, cl); + if (ret) { + cl->status = ret; cb->buf_idx = 0; list_move_tail(&cb->list, &cmpl_list->list); - return -ENODEV; + return ret; } + list_move_tail(&cb->list, &dev->read_list.list); return 0; @@ -242,39 +273,39 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, /** - * mei_cl_irq_ioctl - processes client ioctl related operation from the - * interrupt thread context - send connection request + * mei_cl_irq_connect - send connect request in irq_thread context * * @cl: client * @cb: callback block. - * @slots: free slots. * @cmpl_list: complete list. * * returns 0, OK; otherwise, error. */ -static int mei_cl_irq_ioctl(struct mei_cl *cl, struct mei_cl_cb *cb, - s32 *slots, struct mei_cl_cb *cmpl_list) +static int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, + struct mei_cl_cb *cmpl_list) { struct mei_device *dev = cl->dev; + u32 msg_slots; + int slots; + int ret; - u32 msg_slots = - mei_data2slots(sizeof(struct hbm_client_connect_request)); + msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); + slots = mei_hbuf_empty_slots(dev); - if (*slots < msg_slots) { - /* return the cancel routine */ - list_del(&cb->list); - return -EMSGSIZE; - } + if (mei_cl_is_other_connecting(cl)) + return 0; - *slots -= msg_slots; + if (slots < msg_slots) + return -EMSGSIZE; cl->state = MEI_FILE_CONNECTING; - if (mei_hbm_cl_connect_req(dev, cl)) { - cl->status = -ENODEV; + ret = mei_hbm_cl_connect_req(dev, cl); + if (ret) { + cl->status = ret; cb->buf_idx = 0; list_del(&cb->list); - return -ENODEV; + return ret; } list_move_tail(&cb->list, &dev->ctrl_rd_list.list); @@ -297,13 +328,11 @@ int mei_irq_read_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list, s32 *slots) { struct mei_msg_hdr *mei_hdr; - struct mei_cl *cl_pos = NULL; - struct mei_cl *cl_next = NULL; - int ret = 0; + struct mei_cl *cl; + int ret; if (!dev->rd_msg_hdr) { dev->rd_msg_hdr = mei_read_hdr(dev); - dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); (*slots)--; dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); } @@ -311,61 +340,67 @@ int mei_irq_read_handler(struct mei_device *dev, dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); if (mei_hdr->reserved || !dev->rd_msg_hdr) { - dev_dbg(&dev->pdev->dev, "corrupted message header.\n"); + dev_err(&dev->pdev->dev, "corrupted message header 0x%08X\n", + dev->rd_msg_hdr); ret = -EBADMSG; goto end; } - if (mei_hdr->host_addr || mei_hdr->me_addr) { - list_for_each_entry_safe(cl_pos, cl_next, - &dev->file_list, link) { - dev_dbg(&dev->pdev->dev, - "list_for_each_entry_safe read host" - " client = %d, ME client = %d\n", - cl_pos->host_client_id, - cl_pos->me_client_id); - if (mei_cl_hbm_equal(cl_pos, mei_hdr)) - break; - } + if (mei_slots2data(*slots) < mei_hdr->length) { + dev_err(&dev->pdev->dev, "less data available than length=%08x.\n", + *slots); + /* we can't read the message */ + ret = -ENODATA; + goto end; + } - if (&cl_pos->link == &dev->file_list) { - dev_dbg(&dev->pdev->dev, "corrupted message header\n"); - ret = -EBADMSG; + /* HBM message */ + if (mei_hdr->host_addr == 0 && mei_hdr->me_addr == 0) { + ret = mei_hbm_dispatch(dev, mei_hdr); + if (ret) { + dev_dbg(&dev->pdev->dev, "mei_hbm_dispatch failed ret = %d\n", + ret); goto end; } + goto reset_slots; } - if (((*slots) * sizeof(u32)) < mei_hdr->length) { - dev_err(&dev->pdev->dev, - "we can't read the message slots =%08x.\n", - *slots); - /* we can't read the message */ - ret = -ERANGE; - goto end; + + /* find recipient cl */ + list_for_each_entry(cl, &dev->file_list, link) { + if (mei_cl_hbm_equal(cl, mei_hdr)) { + cl_dbg(dev, cl, "got a message\n"); + break; + } } - /* decide where to read the message too */ - if (!mei_hdr->host_addr) { - dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_bus_message.\n"); - mei_hbm_dispatch(dev, mei_hdr); - dev_dbg(&dev->pdev->dev, "end mei_irq_thread_read_bus_message.\n"); - } 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)) { + /* if no recipient cl was found we assume corrupted header */ + if (&cl->link == &dev->file_list) { + dev_err(&dev->pdev->dev, "no destination client found 0x%08X\n", + dev->rd_msg_hdr); + ret = -EBADMSG; + goto end; + } - 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)); + if (mei_hdr->host_addr == dev->iamthif_cl.host_client_id && + MEI_FILE_CONNECTED == dev->iamthif_cl.state && + dev->iamthif_state == MEI_IAMTHIF_READING) { ret = mei_amthif_irq_read_msg(dev, mei_hdr, cmpl_list); - if (ret) + if (ret) { + dev_err(&dev->pdev->dev, "mei_amthif_irq_read_msg failed = %d\n", + ret); goto end; + } } else { - 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) + if (ret) { + dev_err(&dev->pdev->dev, "mei_cl_irq_read_msg failed = %d\n", + ret); goto end; + } } +reset_slots: /* reset the number of slots and header */ *slots = mei_count_full_read_slots(dev); dev->rd_msg_hdr = 0; @@ -401,10 +436,10 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) s32 slots; int ret; - if (!mei_hbuf_is_ready(dev)) { - dev_dbg(&dev->pdev->dev, "host buffer is not empty.\n"); + + if (!mei_hbuf_acquire(dev)) return 0; - } + slots = mei_hbuf_empty_slots(dev); if (slots <= 0) return -EMSGSIZE; @@ -420,15 +455,14 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) cl->status = 0; list_del(&cb->list); - if (MEI_WRITING == cl->writing_state && - cb->fop_type == MEI_FOP_WRITE && + if (cb->fop_type == MEI_FOP_WRITE && cl != &dev->iamthif_cl) { - dev_dbg(&dev->pdev->dev, "MEI WRITE COMPLETE\n"); + cl_dbg(dev, cl, "MEI WRITE COMPLETE\n"); cl->writing_state = MEI_WRITE_COMPLETE; list_add_tail(&cb->list, &cmpl_list->list); } if (cl == &dev->iamthif_cl) { - dev_dbg(&dev->pdev->dev, "check iamthif flow control.\n"); + cl_dbg(dev, cl, "check iamthif flow control.\n"); if (dev->iamthif_flow_control_pending) { ret = mei_amthif_irq_read(dev, &slots); if (ret) @@ -439,29 +473,16 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) if (dev->wd_state == MEI_WD_STOPPING) { dev->wd_state = MEI_WD_IDLE; - wake_up_interruptible(&dev->wait_stop_wd); + wake_up(&dev->wait_stop_wd); } - if (dev->wr_ext_msg.hdr.length) { - mei_write_message(dev, &dev->wr_ext_msg.hdr, - dev->wr_ext_msg.data); - slots -= mei_data2slots(dev->wr_ext_msg.hdr.length); - dev->wr_ext_msg.hdr.length = 0; - } - if (dev->dev_state == MEI_DEV_ENABLED) { + if (mei_cl_is_connected(&dev->wd_cl)) { if (dev->wd_pending && mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) { - if (mei_wd_send(dev)) - dev_dbg(&dev->pdev->dev, "wd send failed.\n"); - else if (mei_cl_flow_ctrl_reduce(&dev->wd_cl)) - return -ENODEV; - + ret = mei_wd_send(dev); + if (ret) + return ret; dev->wd_pending = false; - - if (dev->wd_state == MEI_WD_RUNNING) - slots -= mei_data2slots(MEI_WD_START_MSG_SIZE); - else - slots -= mei_data2slots(MEI_WD_STOP_MSG_SIZE); } } @@ -476,28 +497,31 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) switch (cb->fop_type) { case MEI_FOP_CLOSE: /* send disconnect message */ - ret = mei_cl_irq_close(cl, cb, &slots, cmpl_list); + ret = mei_cl_irq_close(cl, cb, cmpl_list); if (ret) return ret; break; case MEI_FOP_READ: /* send flow control message */ - ret = mei_cl_irq_read(cl, cb, &slots, cmpl_list); + ret = mei_cl_irq_read(cl, cb, cmpl_list); if (ret) return ret; break; - case MEI_FOP_IOCTL: + case MEI_FOP_CONNECT: /* connect message */ - if (mei_cl_is_other_connecting(cl)) - continue; - ret = mei_cl_irq_ioctl(cl, cb, &slots, cmpl_list); + ret = mei_cl_irq_connect(cl, cb, cmpl_list); if (ret) return ret; break; - + case MEI_FOP_DISCONNECT_RSP: + /* send disconnect resp */ + ret = mei_cl_irq_disconnect_rsp(cl, cb, cmpl_list); + if (ret) + return ret; + break; default: BUG(); } @@ -509,19 +533,10 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) cl = cb->cl; if (cl == NULL) continue; - if (mei_cl_flow_ctrl_creds(cl) <= 0) { - dev_dbg(&dev->pdev->dev, - "No flow control credentials for client %d, not sending.\n", - cl->host_client_id); - continue; - } - if (cl == &dev->iamthif_cl) - ret = mei_amthif_irq_write_complete(cl, cb, - &slots, cmpl_list); + ret = mei_amthif_irq_write(cl, cb, cmpl_list); else - ret = mei_cl_irq_write_complete(cl, cb, - &slots, cmpl_list); + ret = mei_cl_irq_write(cl, cb, cmpl_list); if (ret) return ret; } @@ -536,13 +551,11 @@ EXPORT_SYMBOL_GPL(mei_irq_write_handler); * * @work: pointer to the work_struct structure * - * NOTE: This function is called by timer interrupt work */ void mei_timer(struct work_struct *work) { unsigned long timeout; - struct mei_cl *cl_pos = NULL; - struct mei_cl *cl_next = NULL; + struct mei_cl *cl; struct mei_cl_cb *cb_pos = NULL; struct mei_cl_cb *cb_next = NULL; @@ -551,33 +564,42 @@ void mei_timer(struct work_struct *work) mutex_lock(&dev->device_lock); - if (dev->dev_state != MEI_DEV_ENABLED) { - if (dev->dev_state == MEI_DEV_INIT_CLIENTS) { - if (dev->init_clients_timer) { - if (--dev->init_clients_timer == 0) { - dev_err(&dev->pdev->dev, "reset: init clients timeout hbm_state = %d.\n", - dev->hbm_state); - mei_reset(dev, 1); - } + + /* Catch interrupt stalls during HBM init handshake */ + if (dev->dev_state == MEI_DEV_INIT_CLIENTS && + dev->hbm_state != MEI_HBM_IDLE) { + + if (dev->init_clients_timer) { + if (--dev->init_clients_timer == 0) { + dev_err(&dev->pdev->dev, "timer: init clients timeout hbm_state = %d.\n", + dev->hbm_state); + mei_reset(dev); + goto out; } } - goto out; } + + if (dev->dev_state != MEI_DEV_ENABLED) + goto out; + /*** connect/disconnect timeouts ***/ - 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_err(&dev->pdev->dev, "reset: connect/disconnect timeout.\n"); - mei_reset(dev, 1); + list_for_each_entry(cl, &dev->file_list, link) { + if (cl->timer_count) { + if (--cl->timer_count == 0) { + dev_err(&dev->pdev->dev, "timer: connect/disconnect timeout.\n"); + mei_reset(dev); goto out; } } } + if (!mei_cl_is_connected(&dev->iamthif_cl)) + goto out; + if (dev->iamthif_stall_timer) { if (--dev->iamthif_stall_timer == 0) { - dev_err(&dev->pdev->dev, "reset: amthif hanged.\n"); - mei_reset(dev, 1); + dev_err(&dev->pdev->dev, "timer: amthif hanged.\n"); + mei_reset(dev); dev->iamthif_msg_buf_size = 0; dev->iamthif_msg_buf_index = 0; dev->iamthif_canceled = false; @@ -613,10 +635,10 @@ void mei_timer(struct work_struct *work) list_for_each_entry_safe(cb_pos, cb_next, &dev->amthif_rd_complete_list.list, list) { - cl_pos = cb_pos->file_object->private_data; + cl = cb_pos->file_object->private_data; /* Finding the AMTHI entry. */ - if (cl_pos == &dev->iamthif_cl) + if (cl == &dev->iamthif_cl) list_del(&cb_pos->list); } mei_io_cb_free(dev->iamthif_current_cb); @@ -630,7 +652,8 @@ void mei_timer(struct work_struct *work) } } out: - schedule_delayed_work(&dev->timer_work, 2 * HZ); + if (dev->dev_state != MEI_DEV_DISABLED) + schedule_delayed_work(&dev->timer_work, 2 * HZ); mutex_unlock(&dev->device_lock); } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 173ff095be0..66f0a1a0645 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -13,9 +13,6 @@ * more details. * */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/kernel.h> @@ -40,7 +37,6 @@ #include <linux/mei.h> #include "mei_dev.h" -#include "hw-me.h" #include "client.h" /** @@ -48,7 +44,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) @@ -60,48 +56,45 @@ static int mei_open(struct inode *inode, struct file *file) int err; - err = -ENODEV; if (!misc->parent) - goto out; + return -ENODEV; pdev = container_of(misc->parent, struct pci_dev, dev); dev = pci_get_drvdata(pdev); if (!dev) - goto out; + return -ENODEV; mutex_lock(&dev->device_lock); - err = -ENOMEM; - cl = mei_cl_allocate(dev); - if (!cl) - goto out_unlock; + + cl = NULL; err = -ENODEV; if (dev->dev_state != MEI_DEV_ENABLED) { dev_dbg(&dev->pdev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", mei_dev_state_str(dev->dev_state)); - goto out_unlock; - } - err = -EMFILE; - if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { - dev_err(&dev->pdev->dev, "open_handle_count exceded %d", - MEI_MAX_OPEN_HANDLE_COUNT); - goto out_unlock; + goto err_unlock; } + err = -ENOMEM; + cl = mei_cl_allocate(dev); + if (!cl) + goto err_unlock; + + /* open_handle_count check is handled in the mei_cl_link */ err = mei_cl_link(cl, MEI_HOST_CLIENT_ID_ANY); if (err) - goto out_unlock; + goto err_unlock; file->private_data = cl; + mutex_unlock(&dev->device_lock); return nonseekable_open(inode, file); -out_unlock: +err_unlock: mutex_unlock(&dev->device_lock); kfree(cl); -out: return err; } @@ -132,22 +125,12 @@ static int mei_release(struct inode *inode, struct file *file) } if (cl->state == MEI_FILE_CONNECTED) { cl->state = MEI_FILE_DISCONNECTING; - dev_dbg(&dev->pdev->dev, - "disconnecting client host client = %d, " - "ME client = %d\n", - cl->host_client_id, - cl->me_client_id); + cl_dbg(dev, cl, "disconnecting\n"); rets = mei_cl_disconnect(cl); } mei_cl_flush_queues(cl); - dev_dbg(&dev->pdev->dev, "remove client host client = %d, ME client = %d\n", - cl->host_client_id, - cl->me_client_id); + cl_dbg(dev, cl, "removing\n"); - if (dev->open_handle_count > 0) { - clear_bit(cl->host_client_id, dev->host_clients_map); - dev->open_handle_count--; - } mei_cl_unlink(cl); @@ -165,10 +148,7 @@ static int mei_release(struct inode *inode, struct file *file) file->private_data = NULL; - if (cb) { - mei_io_cb_free(cb); - cb = NULL; - } + mei_io_cb_free(cb); kfree(cl); out: @@ -203,12 +183,18 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, dev = cl->dev; + mutex_lock(&dev->device_lock); if (dev->dev_state != MEI_DEV_ENABLED) { rets = -ENODEV; goto out; } + if (length == 0) { + rets = 0; + goto out; + } + if (cl == &dev->iamthif_cl) { rets = mei_amthif_read(dev, file, ubuf, length, offset); goto out; @@ -249,19 +235,16 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, mutex_unlock(&dev->device_lock); if (wait_event_interruptible(cl->rx_wait, - (MEI_READ_COMPLETE == cl->reading_state || - MEI_FILE_INITIALIZING == cl->state || - MEI_FILE_DISCONNECTED == cl->state || - MEI_FILE_DISCONNECTING == cl->state))) { + MEI_READ_COMPLETE == cl->reading_state || + mei_cl_is_transitioning(cl))) { + if (signal_pending(current)) return -EINTR; return -ERESTARTSYS; } mutex_lock(&dev->device_lock); - if (MEI_FILE_INITIALIZING == cl->state || - MEI_FILE_DISCONNECTED == cl->state || - MEI_FILE_DISCONNECTING == cl->state) { + if (mei_cl_is_transitioning(cl)) { rets = -EBUSY; goto out; } @@ -291,6 +274,7 @@ copy_buffer: length = min_t(size_t, length, cb->buf_idx - *offset); if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) { + dev_dbg(&dev->pdev->dev, "failed to copy data to userland\n"); rets = -EFAULT; goto free; } @@ -347,11 +331,17 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, id = mei_me_cl_by_id(dev, cl->me_client_id); if (id < 0) { - rets = -ENODEV; + rets = -ENOTTY; goto out; } - if (length > dev->me_clients[id].props.max_msg_length || length <= 0) { - rets = -EMSGSIZE; + + if (length == 0) { + rets = 0; + goto out; + } + + if (length > dev->me_clients[id].props.max_msg_length) { + rets = -EFBIG; goto out; } @@ -404,8 +394,11 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, goto out; rets = copy_from_user(write_cb->request_buffer.data, ubuf, length); - if (rets) + if (rets) { + dev_dbg(&dev->pdev->dev, "failed to copy data from userland\n"); + rets = -EFAULT; goto out; + } if (cl == &dev->iamthif_cl) { rets = mei_amthif_write(dev, write_cb); @@ -469,12 +462,11 @@ static int mei_ioctl_connect_client(struct file *file, if (i < 0 || dev->me_clients[i].props.fixed_address) { dev_dbg(&dev->pdev->dev, "Cannot connect to FW Client UUID = %pUl\n", &data->in_client_uuid); - rets = -ENODEV; + rets = -ENOTTY; goto end; } cl->me_client_id = dev->me_clients[i].client_id; - cl->state = MEI_FILE_CONNECTING; dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", cl->me_client_id); @@ -492,11 +484,11 @@ static int mei_ioctl_connect_client(struct file *file, rets = -ENODEV; goto end; } - clear_bit(cl->host_client_id, dev->host_clients_map); mei_cl_unlink(cl); kfree(cl); cl = NULL; + dev->iamthif_open_count++; file->private_data = &dev->iamthif_cl; client = &data->out_client_properties; @@ -651,8 +643,7 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) goto out; } - if (MEI_WRITE_COMPLETE == cl->writing_state) - mask |= (POLLIN | POLLRDNORM); + mask |= (POLLIN | POLLRDNORM); out: mutex_unlock(&dev->device_lock); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 7b918b2fb89..5c7e990e2f2 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -24,7 +24,6 @@ #include <linux/mei_cl_bus.h> #include "hw.h" -#include "hw-me-regs.h" #include "hbm.h" /* @@ -61,11 +60,16 @@ extern const uuid_le mei_wd_guid; #define MEI_CLIENTS_MAX 256 /* + * maximum number of consecutive resets + */ +#define MEI_MAX_CONSEC_RESET 3 + +/* * Number of File descriptors/handles * that can be opened to the driver. * * Limit to 255: 256 Total Clients - * minus internal client for MEI Bus Messags + * minus internal client for MEI Bus Messages */ #define MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 1) @@ -125,16 +129,18 @@ enum mei_wd_states { /** * enum mei_cb_file_ops - file operation associated with the callback - * @MEI_FOP_READ - read - * @MEI_FOP_WRITE - write - * @MEI_FOP_IOCTL - ioctl - * @MEI_FOP_OPEN - open - * @MEI_FOP_CLOSE - close + * @MEI_FOP_READ - read + * @MEI_FOP_WRITE - write + * @MEI_FOP_CONNECT - connect + * @MEI_FOP_DISCONNECT_RSP - disconnect response + * @MEI_FOP_OPEN - open + * @MEI_FOP_CLOSE - close */ enum mei_cb_file_ops { MEI_FOP_READ = 0, MEI_FOP_WRITE, - MEI_FOP_IOCTL, + MEI_FOP_CONNECT, + MEI_FOP_DISCONNECT_RSP, MEI_FOP_OPEN, MEI_FOP_CLOSE }; @@ -147,6 +153,20 @@ struct mei_msg_data { unsigned char *data; }; +/* Maximum number of processed FW status registers */ +#define MEI_FW_STATUS_MAX 2 + +/* + * struct mei_fw_status - storage of FW status data + * + * @count - number of actually available elements in array + * @status - FW status registers + */ +struct mei_fw_status { + int count; + u32 status[MEI_FW_STATUS_MAX]; +}; + /** * struct mei_me_client - representation of me (fw) client * @@ -178,9 +198,10 @@ struct mei_cl_cb { unsigned long buf_idx; unsigned long read_time; struct file *file_object; + u32 internal:1; }; -/* MEI client instance carried as file->pirvate_data*/ +/* MEI client instance carried as file->private_data*/ struct mei_cl { struct list_head link; struct mei_device *dev; @@ -206,6 +227,7 @@ struct mei_cl { /** struct mei_hw_ops * + * @fw_status - read FW status from PCI config space * @host_is_ready - query for host readiness * @hw_is_ready - query if hw is ready @@ -213,6 +235,9 @@ struct mei_cl { * @hw_start - start hw after reset * @hw_config - configure hw + * @pg_state - power gating state of the device + * @pg_is_enabled - is power gating enabled + * @intr_clear - clear pending interrupts * @intr_enable - enable interrupts * @intr_disable - disable interrupts @@ -230,20 +255,25 @@ struct mei_cl { */ struct mei_hw_ops { - bool (*host_is_ready) (struct mei_device *dev); + int (*fw_status)(struct mei_device *dev, + struct mei_fw_status *fw_status); + bool (*host_is_ready)(struct mei_device *dev); - bool (*hw_is_ready) (struct mei_device *dev); - int (*hw_reset) (struct mei_device *dev, bool enable); - int (*hw_start) (struct mei_device *dev); - void (*hw_config) (struct mei_device *dev); + bool (*hw_is_ready)(struct mei_device *dev); + int (*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); - void (*intr_enable) (struct mei_device *dev); - void (*intr_disable) (struct mei_device *dev); + enum mei_pg_state (*pg_state)(struct mei_device *dev); + bool (*pg_is_enabled)(struct mei_device *dev); - int (*hbuf_free_slots) (struct mei_device *dev); - bool (*hbuf_is_ready) (struct mei_device *dev); - size_t (*hbuf_max_len) (const struct mei_device *dev); + void (*intr_clear)(struct mei_device *dev); + void (*intr_enable)(struct mei_device *dev); + void (*intr_disable)(struct mei_device *dev); + + int (*hbuf_free_slots)(struct mei_device *dev); + bool (*hbuf_is_ready)(struct mei_device *dev); + size_t (*hbuf_max_len)(const struct mei_device *dev); int (*write)(struct mei_device *dev, struct mei_msg_hdr *hdr, @@ -252,7 +282,7 @@ struct mei_hw_ops { int (*rdbuf_full_slots)(struct mei_device *dev); u32 (*read_hdr)(const struct mei_device *dev); - int (*read) (struct mei_device *dev, + int (*read)(struct mei_device *dev, unsigned char *buf, unsigned long len); }; @@ -288,6 +318,7 @@ 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); +void mei_cl_bus_remove_devices(struct mei_device *dev); int mei_cl_bus_init(void); void mei_cl_bus_exit(void); @@ -323,16 +354,61 @@ struct mei_cl_device { void *priv_data; }; + + /** + * enum mei_pg_event - power gating transition events + * + * @MEI_PG_EVENT_IDLE: the driver is not in power gating transition + * @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete + * @MEI_PG_EVENT_RECEIVED: the driver received pg event + */ +enum mei_pg_event { + MEI_PG_EVENT_IDLE, + MEI_PG_EVENT_WAIT, + MEI_PG_EVENT_RECEIVED, +}; + +/** + * enum mei_pg_state - device internal power gating state + * + * @MEI_PG_OFF: device is not power gated - it is active + * @MEI_PG_ON: device is power gated - it is in lower power state + */ +enum mei_pg_state { + MEI_PG_OFF = 0, + MEI_PG_ON = 1, +}; + +/* + * mei_cfg + * + * @fw_status - FW status + * @quirk_probe - device exclusion quirk + */ +struct mei_cfg { + const struct mei_fw_status fw_status; + bool (*quirk_probe)(struct pci_dev *pdev); +}; + + +#define MEI_PCI_DEVICE(dev, cfg) \ + .vendor = PCI_VENDOR_ID_INTEL, .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, \ + .driver_data = (kernel_ulong_t)&(cfg) + + /** * struct mei_device - MEI private device struct + * @reset_count - limits the number of consecutive resets * @hbm_state - state of host bus message protocol + * @pg_event - power gating event * @mem_addr - mem mapped base register address * @hbuf_depth - depth of hardware host/write buffer is slots * @hbuf_is_ready - query if the host host/write buffer is ready * @wr_msg - the buffer for hbm control messages - * @wr_ext_msg - the buffer for hbm control responses (set in read cycle) + * @cfg - per device generation config and ops */ struct mei_device { struct pci_dev *pdev; /* pointer to pci device struct */ @@ -363,16 +439,26 @@ struct mei_device { * waiting queue for receive message from FW */ wait_queue_head_t wait_hw_ready; + wait_queue_head_t wait_pg; wait_queue_head_t wait_recvd_msg; wait_queue_head_t wait_stop_wd; /* * mei device states */ + unsigned long reset_count; enum mei_dev_state dev_state; enum mei_hbm_state hbm_state; u16 init_clients_timer; + /* + * Power Gating support + */ + enum mei_pg_event pg_event; +#ifdef CONFIG_PM_RUNTIME + struct dev_pm_domain pg_domain; +#endif /* CONFIG_PM_RUNTIME */ + unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ u32 rd_msg_hdr; @@ -386,19 +472,14 @@ struct mei_device { unsigned char data[128]; } wr_msg; - struct { - struct mei_msg_hdr hdr; - unsigned char data[4]; /* All HBM messages are 4 bytes */ - } wr_ext_msg; /* for control responses */ - struct hbm_version version; struct mei_me_client *me_clients; /* Note: memory has to be allocated */ DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX); DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX); - u8 me_clients_num; - u8 me_client_presentation_num; - u8 me_client_index; + unsigned long me_clients_num; + unsigned long me_client_presentation_num; + unsigned long me_client_index; struct mei_cl wd_cl; enum mei_wd_states wd_state; @@ -414,6 +495,7 @@ struct mei_device { struct file *iamthif_file_object; struct mei_cl iamthif_cl; struct mei_cl_cb *iamthif_current_cb; + long iamthif_open_count; int iamthif_mtu; unsigned long iamthif_timer; u32 iamthif_stall_timer; @@ -426,6 +508,7 @@ struct mei_device { bool iamthif_canceled; struct work_struct init_work; + struct work_struct reset_work; /* List of bus devices */ struct list_head device_list; @@ -436,6 +519,7 @@ struct mei_device { const struct mei_hw_ops *ops; + const struct mei_cfg *cfg; char hw[0] __aligned(sizeof(void *)); }; @@ -455,13 +539,25 @@ static inline u32 mei_data2slots(size_t length) return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4); } +/** + * mei_slots2data- get data in slots - bytes from slots + * @slots - number of available slots + * returns - number of bytes in slots + */ +static inline u32 mei_slots2data(int slots) +{ + return slots * 4; +} + /* * mei init function prototypes */ -void mei_device_init(struct mei_device *dev); -void mei_reset(struct mei_device *dev, int interrupts); +void mei_device_init(struct mei_device *dev, const struct mei_cfg *cfg); +int mei_reset(struct mei_device *dev); int mei_start(struct mei_device *dev); +int mei_restart(struct mei_device *dev); void mei_stop(struct mei_device *dev); +void mei_cancel_work(struct mei_device *dev); /* * MEI interrupt functions prototype @@ -496,8 +592,8 @@ 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_cl *cl, struct mei_cl_cb *cb, - s32 *slots, struct mei_cl_cb *cmpl_list); +int mei_amthif_irq_write(struct mei_cl *cl, 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, @@ -509,7 +605,7 @@ 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); +void mei_nfc_host_exit(struct mei_device *dev); /* * NFC Client UUID @@ -524,7 +620,7 @@ int mei_wd_host_init(struct mei_device *dev); * once we got connection to the WD Client * @dev - mei device */ -void mei_watchdog_register(struct mei_device *dev); +int mei_watchdog_register(struct mei_device *dev); /* * mei_watchdog_unregister - Unregistering watchdog interface * @dev - mei device @@ -535,10 +631,22 @@ void mei_watchdog_unregister(struct mei_device *dev); * Register Access Function */ + static inline void mei_hw_config(struct mei_device *dev) { dev->ops->hw_config(dev); } + +static inline enum mei_pg_state mei_pg_state(struct mei_device *dev) +{ + return dev->ops->pg_state(dev); +} + +static inline bool mei_pg_is_enabled(struct mei_device *dev) +{ + return dev->ops->pg_is_enabled(dev); +} + static inline int mei_hw_reset(struct mei_device *dev, bool enable) { return dev->ops->hw_reset(dev, enable); @@ -611,6 +719,17 @@ static inline int mei_count_full_read_slots(struct mei_device *dev) return dev->ops->rdbuf_full_slots(dev); } +int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status); + +#define FW_STS_FMT "%08X %08X" +#define FW_STS_PRM(fw_status) \ + (fw_status).count > 0 ? (fw_status).status[0] : 0xDEADBEEF, \ + (fw_status).count > 1 ? (fw_status).status[1] : 0xDEADBEEF + +bool mei_hbuf_acquire(struct mei_device *dev); + +bool mei_write_is_idle(struct mei_device *dev); + #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); @@ -625,9 +744,9 @@ static inline void mei_dbgfs_deregister(struct mei_device *dev) {} 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_FMT "hdr:host=%02d me=%02d len=%d internal=%1d comp=%1d" #define MEI_HDR_PRM(hdr) \ (hdr)->host_addr, (hdr)->me_addr, \ - (hdr)->length, (hdr)->msg_complete + (hdr)->length, (hdr)->internal, (hdr)->msg_complete #endif diff --git a/drivers/misc/mei/nfc.c b/drivers/misc/mei/nfc.c index d0c6907dfd9..3095fc514a6 100644 --- a/drivers/misc/mei/nfc.c +++ b/drivers/misc/mei/nfc.c @@ -92,7 +92,7 @@ struct mei_nfc_hci_hdr { * @cl: NFC host client * @cl_info: NFC info host client * @init_work: perform connection to the info client - * @fw_ivn: NFC Intervace Version Number + * @fw_ivn: NFC Interface Version Number * @vendor_id: NFC manufacturer ID * @radio_type: NFC radio type */ @@ -163,7 +163,7 @@ static int mei_nfc_build_bus_name(struct mei_nfc_dev *ndev) return 0; default: - dev_err(&dev->pdev->dev, "Unknow radio type 0x%x\n", + dev_err(&dev->pdev->dev, "Unknown radio type 0x%x\n", ndev->radio_type); return -EINVAL; @@ -175,14 +175,14 @@ static int mei_nfc_build_bus_name(struct mei_nfc_dev *ndev) ndev->bus_name = "pn544"; return 0; default: - dev_err(&dev->pdev->dev, "Unknow radio type 0x%x\n", + dev_err(&dev->pdev->dev, "Unknown radio type 0x%x\n", ndev->radio_type); return -EINVAL; } default: - dev_err(&dev->pdev->dev, "Unknow vendor ID 0x%x\n", + dev_err(&dev->pdev->dev, "Unknown vendor ID 0x%x\n", ndev->vendor_id); return -EINVAL; @@ -364,7 +364,7 @@ static int mei_nfc_send(struct mei_cl_device *cldev, u8 *buf, size_t length) 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; + err = -ETIME; } else { ndev->req_id++; } @@ -428,7 +428,7 @@ static void mei_nfc_init(struct work_struct *work) mutex_unlock(&dev->device_lock); if (mei_nfc_if_version(ndev) < 0) { - dev_err(&dev->pdev->dev, "Could not get the NFC interfave version"); + dev_err(&dev->pdev->dev, "Could not get the NFC interface version"); goto err; } @@ -469,7 +469,9 @@ static void mei_nfc_init(struct work_struct *work) return; err: + mutex_lock(&dev->device_lock); mei_nfc_free(ndev); + mutex_unlock(&dev->device_lock); return; } @@ -481,12 +483,15 @@ int mei_nfc_host_init(struct mei_device *dev) struct mei_cl *cl_info, *cl = NULL; int i, ret; - /* already initialzed */ + /* already initialized */ if (ndev->cl_info) return 0; - cl_info = mei_cl_allocate(dev); - cl = mei_cl_allocate(dev); + ndev->cl_info = mei_cl_allocate(dev); + ndev->cl = mei_cl_allocate(dev); + + cl = ndev->cl; + cl_info = ndev->cl_info; if (!cl || !cl_info) { ret = -ENOMEM; @@ -497,7 +502,7 @@ int mei_nfc_host_init(struct mei_device *dev) 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; + ret = -ENOTTY; goto err; } @@ -515,7 +520,7 @@ int mei_nfc_host_init(struct mei_device *dev) 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; + ret = -ENOTTY; goto err; } @@ -527,10 +532,9 @@ int mei_nfc_host_init(struct mei_device *dev) 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); @@ -545,12 +549,10 @@ err: return ret; } -void mei_nfc_host_exit(void) +void mei_nfc_host_exit(struct mei_device *dev) { struct mei_nfc_dev *ndev = &nfc_dev; + cancel_work_sync(&ndev->init_work); +} - 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 1b3844e8237..1b46c64a649 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -13,9 +13,6 @@ * more details. * */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/kernel.h> @@ -27,7 +24,6 @@ #include <linux/aio.h> #include <linux/pci.h> #include <linux/poll.h> -#include <linux/init.h> #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/sched.h> @@ -37,47 +33,55 @@ #include <linux/interrupt.h> #include <linux/miscdevice.h> +#include <linux/pm_runtime.h> + #include <linux/mei.h> #include "mei_dev.h" -#include "hw-me.h" #include "client.h" +#include "hw-me-regs.h" +#include "hw-me.h" /* mei_pci_tbl - PCI Device ID Table */ -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)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_LP)}, +static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_82946GZ, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82G35, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82Q965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82G965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82GM965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82GME965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82Q35, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82G33, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82Q33, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82X38, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_3200, mei_me_legacy_cfg)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_6, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_7, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_8, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_9, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_10, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_1, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_2, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_3, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_4, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_1, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_2, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_3, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_4, mei_me_ich_cfg)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_2, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_CPT_1, mei_me_pch_cpt_pbg_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PBG_1, mei_me_pch_cpt_pbg_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_2, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_3, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_H, mei_me_lpt_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_W, mei_me_lpt_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_LP, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_HR, mei_me_lpt_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_WPT_LP, mei_me_pch_cfg)}, /* required last entry */ {0, } @@ -85,28 +89,33 @@ static DEFINE_PCI_DEVICE_TABLE(mei_me_pci_tbl) = { MODULE_DEVICE_TABLE(pci, mei_me_pci_tbl); +#ifdef CONFIG_PM_RUNTIME +static inline void mei_me_set_pm_domain(struct mei_device *dev); +static inline void mei_me_unset_pm_domain(struct mei_device *dev); +#else +static inline void mei_me_set_pm_domain(struct mei_device *dev) {} +static inline void mei_me_unset_pm_domain(struct mei_device *dev) {} +#endif /* CONFIG_PM_RUNTIME */ + /** * mei_quirk_probe - probe for devices that doesn't valid ME interface * * @pdev: PCI device structure - * @ent: entry into pci_device_table + * @cfg: per generation config * * returns true if ME Interface is valid, false otherwise */ static bool mei_me_quirk_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct mei_cfg *cfg) { - u32 reg; - if (ent->device == MEI_DEV_ID_PBG_1) { - pci_read_config_dword(pdev, 0x48, ®); - /* make sure that bit 9 is up and bit 10 is down */ - if ((reg & 0x600) == 0x200) { - dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); - return false; - } + if (cfg->quirk_probe && cfg->quirk_probe(pdev)) { + dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); + return false; } + return true; } + /** * mei_probe - Device Initialization Routine * @@ -117,15 +126,14 @@ static bool mei_me_quirk_probe(struct pci_dev *pdev, */ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { + const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); struct mei_device *dev; struct mei_me_hw *hw; int err; - if (!mei_me_quirk_probe(pdev, ent)) { - err = -ENODEV; - goto end; - } + if (!mei_me_quirk_probe(pdev, cfg)) + return -ENODEV; /* enable pci dev */ err = pci_enable_device(pdev); @@ -141,8 +149,23 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) dev_err(&pdev->dev, "failed to get pci regions.\n"); goto disable_device; } + + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) || + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64))) { + + err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + err = dma_set_coherent_mask(&pdev->dev, + DMA_BIT_MASK(32)); + } + if (err) { + dev_err(&pdev->dev, "No usable DMA configuration, aborting\n"); + goto release_regions; + } + + /* allocates and initializes the mei dev structure */ - dev = mei_me_dev_init(pdev); + dev = mei_me_dev_init(pdev, cfg); if (!dev) { err = -ENOMEM; goto release_regions; @@ -181,6 +204,9 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto release_irq; } + pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + err = mei_register(dev); if (err) goto release_irq; @@ -189,13 +215,24 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) schedule_delayed_work(&dev->timer_work, HZ); - pr_debug("initialization successful.\n"); + /* + * For not wake-able HW runtime pm framework + * can't be used on pci device level. + * Use domain runtime pm callbacks instead. + */ + if (!pci_dev_run_wake(pdev)) + mei_me_set_pm_domain(dev); + + if (mei_pg_is_enabled(dev)) + pm_runtime_put_noidle(&pdev->dev); + + dev_dbg(&pdev->dev, "initialization successful.\n"); return 0; release_irq: + mei_cancel_work(dev); mei_disable_interrupts(dev); - flush_scheduled_work(); free_irq(pdev->irq, dev); disable_msi: pci_disable_msi(pdev); @@ -228,18 +265,23 @@ static void mei_me_remove(struct pci_dev *pdev) if (!dev) return; + if (mei_pg_is_enabled(dev)) + pm_runtime_get_noresume(&pdev->dev); + hw = to_me_hw(dev); - dev_err(&pdev->dev, "stop\n"); + dev_dbg(&pdev->dev, "stop\n"); mei_stop(dev); + if (!pci_dev_run_wake(pdev)) + mei_me_unset_pm_domain(dev); + /* disable interrupts */ mei_disable_interrupts(dev); free_irq(pdev->irq, dev); pci_disable_msi(pdev); - pci_set_drvdata(pdev, NULL); if (hw->mem_addr) pci_iounmap(pdev, hw->mem_addr); @@ -253,7 +295,7 @@ static void mei_me_remove(struct pci_dev *pdev) } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP static int mei_me_pci_suspend(struct device *device) { struct pci_dev *pdev = to_pci_dev(device); @@ -262,7 +304,7 @@ static int mei_me_pci_suspend(struct device *device) if (!dev) return -ENODEV; - dev_err(&pdev->dev, "suspend\n"); + dev_dbg(&pdev->dev, "suspend\n"); mei_stop(dev); @@ -304,18 +346,125 @@ static int mei_me_pci_resume(struct device *device) return err; } - mutex_lock(&dev->device_lock); - dev->dev_state = MEI_DEV_POWER_UP; - mei_clear_interrupts(dev); - mei_reset(dev, 1); - mutex_unlock(&dev->device_lock); + err = mei_restart(dev); + if (err) + return err; /* Start timer if stopped in suspend */ schedule_delayed_work(&dev->timer_work, HZ); - return err; + return 0; } -static SIMPLE_DEV_PM_OPS(mei_me_pm_ops, mei_me_pci_suspend, mei_me_pci_resume); +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int mei_me_pm_runtime_idle(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + + dev_dbg(&pdev->dev, "rpm: me: runtime_idle\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + if (mei_write_is_idle(dev)) + pm_schedule_suspend(device, MEI_ME_RPM_TIMEOUT * 2); + + return -EBUSY; +} + +static int mei_me_pm_runtime_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: me: runtime suspend\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (mei_write_is_idle(dev)) + ret = mei_me_pg_set_sync(dev); + else + ret = -EAGAIN; + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: me: runtime suspend ret=%d\n", ret); + + return ret; +} + +static int mei_me_pm_runtime_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: me: runtime resume\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + ret = mei_me_pg_unset_sync(dev); + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: me: runtime resume ret = %d\n", ret); + + return ret; +} + +/** + * mei_me_set_pm_domain - fill and set pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_me_set_pm_domain(struct mei_device *dev) +{ + struct pci_dev *pdev = dev->pdev; + + if (pdev->dev.bus && pdev->dev.bus->pm) { + dev->pg_domain.ops = *pdev->dev.bus->pm; + + dev->pg_domain.ops.runtime_suspend = mei_me_pm_runtime_suspend; + dev->pg_domain.ops.runtime_resume = mei_me_pm_runtime_resume; + dev->pg_domain.ops.runtime_idle = mei_me_pm_runtime_idle; + + pdev->dev.pm_domain = &dev->pg_domain; + } +} + +/** + * mei_me_unset_pm_domain - clean pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_me_unset_pm_domain(struct mei_device *dev) +{ + /* stop using pm callbacks if any */ + dev->pdev->dev.pm_domain = NULL; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM +static const struct dev_pm_ops mei_me_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mei_me_pci_suspend, + mei_me_pci_resume) + SET_RUNTIME_PM_OPS( + mei_me_pm_runtime_suspend, + mei_me_pm_runtime_resume, + mei_me_pm_runtime_idle) +}; + #define MEI_ME_PM_OPS (&mei_me_pm_ops) #else #define MEI_ME_PM_OPS NULL diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c new file mode 100644 index 00000000000..2343c6236df --- /dev/null +++ b/drivers/misc/mei/pci-txe.c @@ -0,0 +1,436 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2013-2014, 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/kernel.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/uuid.h> +#include <linux/jiffies.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/pm_runtime.h> + +#include <linux/mei.h> + + +#include "mei_dev.h" +#include "hw-txe.h" + +static const struct pci_device_id mei_txe_pci_tbl[] = { + {MEI_PCI_DEVICE(0x0F18, mei_txe_cfg)}, /* Baytrail */ + {0, } +}; +MODULE_DEVICE_TABLE(pci, mei_txe_pci_tbl); + +#ifdef CONFIG_PM_RUNTIME +static inline void mei_txe_set_pm_domain(struct mei_device *dev); +static inline void mei_txe_unset_pm_domain(struct mei_device *dev); +#else +static inline void mei_txe_set_pm_domain(struct mei_device *dev) {} +static inline void mei_txe_unset_pm_domain(struct mei_device *dev) {} +#endif /* CONFIG_PM_RUNTIME */ + +static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) +{ + int i; + for (i = SEC_BAR; i < NUM_OF_MEM_BARS; i++) { + if (hw->mem_addr[i]) { + pci_iounmap(pdev, hw->mem_addr[i]); + hw->mem_addr[i] = NULL; + } + } +} +/** + * mei_probe - Device Initialization Routine + * + * @pdev: PCI device structure + * @ent: entry in mei_txe_pci_tbl + * + * returns 0 on success, <0 on failure. + */ +static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); + struct mei_device *dev; + struct mei_txe_hw *hw; + int err; + int i; + + /* enable pci dev */ + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "failed to enable pci device.\n"); + goto end; + } + /* set PCI host mastering */ + pci_set_master(pdev); + /* pci request regions for mei driver */ + err = pci_request_regions(pdev, KBUILD_MODNAME); + if (err) { + dev_err(&pdev->dev, "failed to get pci regions.\n"); + goto disable_device; + } + + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(36)); + if (err) { + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&pdev->dev, "No suitable DMA available.\n"); + goto release_regions; + } + } + + /* allocates and initializes the mei dev structure */ + dev = mei_txe_dev_init(pdev, cfg); + if (!dev) { + err = -ENOMEM; + goto release_regions; + } + hw = to_txe_hw(dev); + + /* mapping IO device memory */ + for (i = SEC_BAR; i < NUM_OF_MEM_BARS; i++) { + hw->mem_addr[i] = pci_iomap(pdev, i, 0); + if (!hw->mem_addr[i]) { + dev_err(&pdev->dev, "mapping I/O device memory failure.\n"); + err = -ENOMEM; + goto free_device; + } + } + + + pci_enable_msi(pdev); + + /* clear spurious interrupts */ + mei_clear_interrupts(dev); + + /* request and enable interrupt */ + if (pci_dev_msi_enabled(pdev)) + err = request_threaded_irq(pdev->irq, + NULL, + mei_txe_irq_thread_handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev); + else + err = request_threaded_irq(pdev->irq, + mei_txe_irq_quick_handler, + mei_txe_irq_thread_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); + if (err) { + dev_err(&pdev->dev, "mei: request_threaded_irq failure. irq = %d\n", + pdev->irq); + goto free_device; + } + + if (mei_start(dev)) { + dev_err(&pdev->dev, "init hw failure.\n"); + err = -ENODEV; + goto release_irq; + } + + pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_TXI_RPM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + + err = mei_register(dev); + if (err) + goto release_irq; + + pci_set_drvdata(pdev, dev); + + /* + * For not wake-able HW runtime pm framework + * can't be used on pci device level. + * Use domain runtime pm callbacks instead. + */ + if (!pci_dev_run_wake(pdev)) + mei_txe_set_pm_domain(dev); + + pm_runtime_put_noidle(&pdev->dev); + + return 0; + +release_irq: + + mei_cancel_work(dev); + + /* disable interrupts */ + mei_disable_interrupts(dev); + + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + +free_device: + mei_txe_pci_iounmap(pdev, hw); + + kfree(dev); +release_regions: + pci_release_regions(pdev); +disable_device: + pci_disable_device(pdev); +end: + dev_err(&pdev->dev, "initialization failed.\n"); + return err; +} + +/** + * mei_remove - Device Removal Routine + * + * @pdev: PCI device structure + * + * mei_remove is called by the PCI subsystem to alert the driver + * that it should release a PCI device. + */ +static void mei_txe_remove(struct pci_dev *pdev) +{ + struct mei_device *dev; + struct mei_txe_hw *hw; + + dev = pci_get_drvdata(pdev); + if (!dev) { + dev_err(&pdev->dev, "mei: dev =NULL\n"); + return; + } + + pm_runtime_get_noresume(&pdev->dev); + + hw = to_txe_hw(dev); + + mei_stop(dev); + + if (!pci_dev_run_wake(pdev)) + mei_txe_unset_pm_domain(dev); + + /* disable interrupts */ + mei_disable_interrupts(dev); + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + + pci_set_drvdata(pdev, NULL); + + mei_txe_pci_iounmap(pdev, hw); + + mei_deregister(dev); + + kfree(dev); + + pci_release_regions(pdev); + pci_disable_device(pdev); +} + + +#ifdef CONFIG_PM_SLEEP +static int mei_txe_pci_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev = pci_get_drvdata(pdev); + + if (!dev) + return -ENODEV; + + dev_dbg(&pdev->dev, "suspend\n"); + + mei_stop(dev); + + mei_disable_interrupts(dev); + + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + + return 0; +} + +static int mei_txe_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int err; + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + pci_enable_msi(pdev); + + mei_clear_interrupts(dev); + + /* request and enable interrupt */ + if (pci_dev_msi_enabled(pdev)) + err = request_threaded_irq(pdev->irq, + NULL, + mei_txe_irq_thread_handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev); + else + err = request_threaded_irq(pdev->irq, + mei_txe_irq_quick_handler, + mei_txe_irq_thread_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); + if (err) { + dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n", + pdev->irq); + return err; + } + + err = mei_restart(dev); + + return err; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int mei_txe_pm_runtime_idle(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + + dev_dbg(&pdev->dev, "rpm: txe: runtime_idle\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + if (mei_write_is_idle(dev)) + pm_schedule_suspend(device, MEI_TXI_RPM_TIMEOUT * 2); + + return -EBUSY; +} +static int mei_txe_pm_runtime_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: txe: runtime suspend\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (mei_write_is_idle(dev)) + ret = mei_txe_aliveness_set_sync(dev, 0); + else + ret = -EAGAIN; + + /* + * If everything is okay we're about to enter PCI low + * power state (D3) therefor we need to disable the + * interrupts towards host. + * However if device is not wakeable we do not enter + * D-low state and we need to keep the interrupt kicking + */ + if (!ret && pci_dev_run_wake(pdev)) + mei_disable_interrupts(dev); + + dev_dbg(&pdev->dev, "rpm: txe: runtime suspend ret=%d\n", ret); + + mutex_unlock(&dev->device_lock); + return ret; +} + +static int mei_txe_pm_runtime_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: txe: runtime resume\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + mei_enable_interrupts(dev); + + ret = mei_txe_aliveness_set_sync(dev, 1); + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: txe: runtime resume ret = %d\n", ret); + + return ret; +} + +/** + * mei_txe_set_pm_domain - fill and set pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_txe_set_pm_domain(struct mei_device *dev) +{ + struct pci_dev *pdev = dev->pdev; + + if (pdev->dev.bus && pdev->dev.bus->pm) { + dev->pg_domain.ops = *pdev->dev.bus->pm; + + dev->pg_domain.ops.runtime_suspend = mei_txe_pm_runtime_suspend; + dev->pg_domain.ops.runtime_resume = mei_txe_pm_runtime_resume; + dev->pg_domain.ops.runtime_idle = mei_txe_pm_runtime_idle; + + pdev->dev.pm_domain = &dev->pg_domain; + } +} + +/** + * mei_txe_unset_pm_domain - clean pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_txe_unset_pm_domain(struct mei_device *dev) +{ + /* stop using pm callbacks if any */ + dev->pdev->dev.pm_domain = NULL; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM +static const struct dev_pm_ops mei_txe_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mei_txe_pci_suspend, + mei_txe_pci_resume) + SET_RUNTIME_PM_OPS( + mei_txe_pm_runtime_suspend, + mei_txe_pm_runtime_resume, + mei_txe_pm_runtime_idle) +}; + +#define MEI_TXE_PM_OPS (&mei_txe_pm_ops) +#else +#define MEI_TXE_PM_OPS NULL +#endif /* CONFIG_PM */ + +/* + * PCI driver structure + */ +static struct pci_driver mei_txe_driver = { + .name = KBUILD_MODNAME, + .id_table = mei_txe_pci_tbl, + .probe = mei_txe_probe, + .remove = mei_txe_remove, + .shutdown = mei_txe_remove, + .driver.pm = MEI_TXE_PM_OPS, +}; + +module_pci_driver(mei_txe_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) Trusted Execution Environment Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index b8921432e89..a84a664dfcc 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -25,7 +25,6 @@ #include "mei_dev.h" #include "hbm.h" -#include "hw-me.h" #include "client.h" static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; @@ -53,14 +52,14 @@ static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout) * * @dev: the device structure * - * returns -ENENT if wd client cannot be found + * returns -ENOTTY if wd client cannot be found * -EIO if write has failed * 0 on success */ int mei_wd_host_init(struct mei_device *dev) { struct mei_cl *cl = &dev->wd_cl; - int i; + int id; int ret; mei_cl_init(cl, dev); @@ -70,32 +69,35 @@ int mei_wd_host_init(struct mei_device *dev) /* check for valid client id */ - i = mei_me_cl_by_uuid(dev, &mei_wd_guid); - if (i < 0) { + id = mei_me_cl_by_uuid(dev, &mei_wd_guid); + if (id < 0) { dev_info(&dev->pdev->dev, "wd: failed to find the client\n"); - return -ENOENT; + return -ENOTTY; } - cl->me_client_id = dev->me_clients[i].client_id; + cl->me_client_id = dev->me_clients[id].client_id; ret = mei_cl_link(cl, MEI_WD_HOST_CLIENT_ID); if (ret < 0) { dev_info(&dev->pdev->dev, "wd: failed link client\n"); - return -ENOENT; + return ret; } - cl->state = MEI_FILE_CONNECTING; + ret = mei_cl_connect(cl, NULL); - if (mei_hbm_cl_connect_req(dev, cl)) { - dev_err(&dev->pdev->dev, "wd: failed to connect to the client\n"); - cl->state = MEI_FILE_DISCONNECTED; - cl->host_client_id = 0; - return -EIO; + if (ret) { + dev_err(&dev->pdev->dev, "wd: failed to connect = %d\n", ret); + mei_cl_unlink(cl); + return ret; } - cl->timer_count = MEI_CONNECT_TIMEOUT; - return 0; + ret = mei_watchdog_register(dev); + if (ret) { + mei_cl_disconnect(cl); + mei_cl_unlink(cl); + } + return ret; } /** @@ -106,24 +108,42 @@ int mei_wd_host_init(struct mei_device *dev) * returns 0 if success, * -EIO when message send fails * -EINVAL when invalid message is to be sent + * -ENODEV on flow control failure */ int mei_wd_send(struct mei_device *dev) { + struct mei_cl *cl = &dev->wd_cl; struct mei_msg_hdr hdr; + int ret; - hdr.host_addr = dev->wd_cl.host_client_id; - hdr.me_addr = dev->wd_cl.me_client_id; + hdr.host_addr = cl->host_client_id; + hdr.me_addr = cl->me_client_id; hdr.msg_complete = 1; hdr.reserved = 0; + hdr.internal = 0; if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE)) hdr.length = MEI_WD_START_MSG_SIZE; else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_HDR_SIZE)) hdr.length = MEI_WD_STOP_MSG_SIZE; - else + else { + dev_err(&dev->pdev->dev, "wd: invalid message is to be sent, aborting\n"); return -EINVAL; + } + + ret = mei_write_message(dev, &hdr, dev->wd_data); + if (ret) { + dev_err(&dev->pdev->dev, "wd: write message failed\n"); + return ret; + } - return mei_write_message(dev, &hdr, dev->wd_data); + ret = mei_cl_flow_ctrl_reduce(cl); + if (ret) { + dev_err(&dev->pdev->dev, "wd: flow_ctrl_reduce failed.\n"); + return ret; + } + + return 0; } /** @@ -132,9 +152,11 @@ int mei_wd_send(struct mei_device *dev) * @dev: the device structure * @preserve: indicate if to keep the timeout value * - * returns 0 if success, - * -EIO when message send fails + * returns 0 if success + * on error: + * -EIO when message send fails * -EINVAL when invalid message is to be sent + * -ETIME on message timeout */ int mei_wd_stop(struct mei_device *dev) { @@ -150,20 +172,12 @@ int mei_wd_stop(struct mei_device *dev) ret = mei_cl_flow_ctrl_creds(&dev->wd_cl); if (ret < 0) - goto out; - - if (ret && dev->hbuf_is_ready) { - ret = 0; - dev->hbuf_is_ready = false; - - if (!mei_wd_send(dev)) { - ret = mei_cl_flow_ctrl_reduce(&dev->wd_cl); - if (ret) - goto out; - } else { - dev_err(&dev->pdev->dev, "wd: send stop failed\n"); - } + goto err; + if (ret && mei_hbuf_acquire(dev)) { + ret = mei_wd_send(dev); + if (ret) + goto err; dev->wd_pending = false; } else { dev->wd_pending = true; @@ -171,21 +185,21 @@ int mei_wd_stop(struct mei_device *dev) mutex_unlock(&dev->device_lock); - ret = wait_event_interruptible_timeout(dev->wait_stop_wd, - dev->wd_state == MEI_WD_IDLE, - msecs_to_jiffies(MEI_WD_STOP_TIMEOUT)); + ret = wait_event_timeout(dev->wait_stop_wd, + dev->wd_state == MEI_WD_IDLE, + msecs_to_jiffies(MEI_WD_STOP_TIMEOUT)); mutex_lock(&dev->device_lock); - if (dev->wd_state == MEI_WD_IDLE) { - dev_dbg(&dev->pdev->dev, "wd: stop completed ret=%d.\n", ret); - ret = 0; - } else { - if (!ret) - ret = -ETIMEDOUT; + if (dev->wd_state != MEI_WD_IDLE) { + /* timeout */ + ret = -ETIME; dev_warn(&dev->pdev->dev, "wd: stop failed to complete ret=%d.\n", ret); + goto err; } - -out: + dev_dbg(&dev->pdev->dev, "wd: stop completed after %u msec\n", + MEI_WD_STOP_TIMEOUT - jiffies_to_msecs(ret)); + return 0; +err: return ret; } @@ -259,8 +273,8 @@ static int mei_wd_ops_stop(struct watchdog_device *wd_dev) */ static int mei_wd_ops_ping(struct watchdog_device *wd_dev) { - int ret = 0; struct mei_device *dev; + int ret; dev = watchdog_get_drvdata(wd_dev); if (!dev) @@ -276,25 +290,18 @@ static int mei_wd_ops_ping(struct watchdog_device *wd_dev) dev->wd_state = MEI_WD_RUNNING; + ret = mei_cl_flow_ctrl_creds(&dev->wd_cl); + if (ret < 0) + goto end; /* Check if we can send the ping to HW*/ - if (dev->hbuf_is_ready && mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) { + if (ret && mei_hbuf_acquire(dev)) { - dev->hbuf_is_ready = false; dev_dbg(&dev->pdev->dev, "wd: sending ping\n"); - if (mei_wd_send(dev)) { - dev_err(&dev->pdev->dev, "wd: send failed.\n"); - ret = -EIO; - goto end; - } - - if (mei_cl_flow_ctrl_reduce(&dev->wd_cl)) { - dev_err(&dev->pdev->dev, - "wd: mei_cl_flow_ctrl_reduce() failed.\n"); - ret = -EIO; + ret = mei_wd_send(dev); + if (ret) goto end; - } - + dev->wd_pending = false; } else { dev->wd_pending = true; } @@ -362,17 +369,25 @@ static struct watchdog_device amt_wd_dev = { }; -void mei_watchdog_register(struct mei_device *dev) +int mei_watchdog_register(struct mei_device *dev) { - if (watchdog_register_device(&amt_wd_dev)) { - dev_err(&dev->pdev->dev, - "wd: unable to register watchdog device.\n"); - return; + + int ret; + + /* unlock to perserve correct locking order */ + mutex_unlock(&dev->device_lock); + ret = watchdog_register_device(&amt_wd_dev); + mutex_lock(&dev->device_lock); + if (ret) { + dev_err(&dev->pdev->dev, "wd: unable to register watchdog device = %d.\n", + ret); + return ret; } dev_dbg(&dev->pdev->dev, "wd: successfully register watchdog interface.\n"); watchdog_set_drvdata(&amt_wd_dev, dev); + return 0; } void mei_watchdog_unregister(struct mei_device *dev) |
