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 | 121 | ||||
| -rw-r--r-- | drivers/misc/mei/bus.c | 27 | ||||
| -rw-r--r-- | drivers/misc/mei/client.c | 527 | ||||
| -rw-r--r-- | drivers/misc/mei/client.h | 39 | ||||
| -rw-r--r-- | drivers/misc/mei/debugfs.c | 58 | ||||
| -rw-r--r-- | drivers/misc/mei/hbm.c | 596 | ||||
| -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 | 340 | ||||
| -rw-r--r-- | drivers/misc/mei/interrupt.c | 319 | ||||
| -rw-r--r-- | drivers/misc/mei/main.c | 88 | ||||
| -rw-r--r-- | drivers/misc/mei/mei_dev.h | 189 | ||||
| -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, 4227 insertions, 1035 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 f6ff711aa5b..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, @@ -58,6 +56,7 @@ void mei_amthif_reset_params(struct mei_device *dev)  	dev->iamthif_state = MEI_IAMTHIF_IDLE;  	dev->iamthif_timer = 0;  	dev->iamthif_stall_timer = 0; +	dev->iamthif_open_count = 0;  }  /** @@ -78,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; @@ -106,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;  }  /** @@ -133,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;  } @@ -173,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); @@ -224,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;  		}  	} @@ -245,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; @@ -298,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; @@ -312,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"); @@ -331,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; @@ -360,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) { @@ -442,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 { @@ -480,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; @@ -720,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 cd2033cd712..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) @@ -428,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); @@ -524,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 892cc4207fa..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,15 +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 || +	return  MEI_FILE_INITIALIZING == cl->state ||  		MEI_FILE_DISCONNECTED == cl->state || -		MEI_FILE_DISCONNECTING == cl->state); +		MEI_FILE_DISCONNECTING == cl->state;  }  bool mei_cl_is_other_connecting(struct mei_cl *cl); @@ -102,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); @@ -115,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 0a0448326e9..804106209d7 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -14,54 +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; -	dev->me_clients_num = 0; -	dev->me_client_presentation_num = 0; -	dev->me_client_index = 0; +	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; +		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;  }  /** @@ -85,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) @@ -101,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; @@ -137,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;  } @@ -153,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); @@ -170,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; @@ -186,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); @@ -200,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;  }  /** @@ -215,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) @@ -226,7 +263,7 @@ static int mei_hbm_prop_req(struct mei_device *dev)  	const size_t len = sizeof(struct hbm_props_request);  	unsigned long next_client_index;  	unsigned long client_num; - +	int ret;  	client_num = dev->me_client_presentation_num; @@ -253,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; @@ -267,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); @@ -286,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 @@ -304,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);  } @@ -315,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;  }  /** @@ -347,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 */ @@ -357,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", @@ -390,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 @@ -399,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; @@ -451,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 @@ -461,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;  } @@ -559,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; @@ -577,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", @@ -597,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; @@ -671,53 +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; -				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 6197018e2f1..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,106 +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); -	/* we're already in reset, cancel the init timer */ -	dev->init_clients_timer = 0; +static void mei_reset_work(struct work_struct *work) +{ +	struct mei_device *dev = +		container_of(work, struct mei_device,  reset_work); +	int ret; -	dev->me_clients_num = 0; -	dev->rd_msg_hdr = 0; -	dev->wd_pending = false; +	mutex_lock(&dev->device_lock); -	if (unexpected) -		dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", -			 mei_dev_state_str(dev->dev_state)); +	ret = mei_reset(dev); -	if (!interrupts_enabled) { -		dev_dbg(&dev->pdev->dev, "intr not enabled end of reset\n"); -		return; -	} +	mutex_unlock(&dev->device_lock); -	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; +	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); @@ -231,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 cabeddd66c1..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; @@ -288,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;  	} @@ -344,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;  	} @@ -401,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); @@ -466,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); @@ -489,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; @@ -648,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 456b322013e..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,11 +472,6 @@ 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 */ @@ -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)  | 
