diff options
Diffstat (limited to 'drivers/misc/mei/hw-me.c')
| -rw-r--r-- | drivers/misc/mei/hw-me.c | 576 | 
1 files changed, 576 insertions, 0 deletions
diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c new file mode 100644 index 00000000000..45ea7185c00 --- /dev/null +++ b/drivers/misc/mei/hw-me.c @@ -0,0 +1,576 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, 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/kthread.h> +#include <linux/interrupt.h> + +#include "mei_dev.h" +#include "hw-me.h" + +#include "hbm.h" + + +/** + * mei_reg_read - Reads 32bit data from the mei device + * + * @dev: the device structure + * @offset: offset from which to read the data + * + * returns register value (u32) + */ +static inline u32 mei_reg_read(const struct mei_me_hw *hw, +			       unsigned long offset) +{ +	return ioread32(hw->mem_addr + offset); +} + + +/** + * mei_reg_write - Writes 32bit data to the mei device + * + * @dev: the device structure + * @offset: offset from which to write the data + * @value: register value to write (u32) + */ +static inline void mei_reg_write(const struct mei_me_hw *hw, +				 unsigned long offset, u32 value) +{ +	iowrite32(value, hw->mem_addr + offset); +} + +/** + * mei_mecbrw_read - Reads 32bit data from ME circular buffer + *  read window register + * + * @dev: the device structure + * + * returns ME_CB_RW register value (u32) + */ +static u32 mei_me_mecbrw_read(const struct mei_device *dev) +{ +	return mei_reg_read(to_me_hw(dev), ME_CB_RW); +} +/** + * mei_mecsr_read - Reads 32bit data from the ME CSR + * + * @dev: the device structure + * + * returns ME_CSR_HA register value (u32) + */ +static inline u32 mei_mecsr_read(const struct mei_me_hw *hw) +{ +	return mei_reg_read(hw, ME_CSR_HA); +} + +/** + * mei_hcsr_read - Reads 32bit data from the host CSR + * + * @dev: the device structure + * + * returns H_CSR register value (u32) + */ +static inline u32 mei_hcsr_read(const struct mei_me_hw *hw) +{ +	return mei_reg_read(hw, H_CSR); +} + +/** + * mei_hcsr_set - writes H_CSR register to the mei device, + * and ignores the H_IS bit for it is write-one-to-zero. + * + * @dev: the device structure + */ +static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) +{ +	hcsr &= ~H_IS; +	mei_reg_write(hw, H_CSR, hcsr); +} + + +/** + * me_hw_config - configure hw dependent settings + * + * @dev: mei device + */ +static void mei_me_hw_config(struct mei_device *dev) +{ +	u32 hcsr = mei_hcsr_read(to_me_hw(dev)); +	/* Doesn't change in runtime */ +	dev->hbuf_depth = (hcsr & H_CBD) >> 24; +} +/** + * mei_clear_interrupts - clear and stop interrupts + * + * @dev: the device structure + */ +static void mei_me_intr_clear(struct mei_device *dev) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	u32 hcsr = mei_hcsr_read(hw); +	if ((hcsr & H_IS) == H_IS) +		mei_reg_write(hw, H_CSR, hcsr); +} +/** + * mei_me_intr_enable - enables mei device interrupts + * + * @dev: the device structure + */ +static void mei_me_intr_enable(struct mei_device *dev) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	u32 hcsr = mei_hcsr_read(hw); +	hcsr |= H_IE; +	mei_hcsr_set(hw, hcsr); +} + +/** + * mei_disable_interrupts - disables mei device interrupts + * + * @dev: the device structure + */ +static void mei_me_intr_disable(struct mei_device *dev) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	u32 hcsr = mei_hcsr_read(hw); +	hcsr  &= ~H_IE; +	mei_hcsr_set(hw, hcsr); +} + +/** + * mei_me_hw_reset - resets fw via mei csr register. + * + * @dev: the device structure + * @interrupts_enabled: if interrupt should be enabled after reset. + */ +static void mei_me_hw_reset(struct mei_device *dev, bool intr_enable) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	u32 hcsr = mei_hcsr_read(hw); + +	dev_dbg(&dev->pdev->dev, "before reset HCSR = 0x%08x.\n", hcsr); + +	hcsr |= (H_RST | H_IG); + +	if (intr_enable) +		hcsr |= H_IE; +	else +		hcsr &= ~H_IE; + +	mei_hcsr_set(hw, hcsr); + +	hcsr = mei_hcsr_read(hw) | H_IG; +	hcsr &= ~H_RST; + +	mei_hcsr_set(hw, hcsr); + +	hcsr = mei_hcsr_read(hw); + +	dev_dbg(&dev->pdev->dev, "current HCSR = 0x%08x.\n", hcsr); +} + +/** + * mei_me_host_set_ready - enable device + * + * @dev - mei device + * returns bool + */ + +static void mei_me_host_set_ready(struct mei_device *dev) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	hw->host_hw_state |= H_IE | H_IG | H_RDY; +	mei_hcsr_set(hw, hw->host_hw_state); +} +/** + * mei_me_host_is_ready - check whether the host has turned ready + * + * @dev - mei device + * returns bool + */ +static bool mei_me_host_is_ready(struct mei_device *dev) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	hw->host_hw_state = mei_hcsr_read(hw); +	return (hw->host_hw_state & H_RDY) == H_RDY; +} + +/** + * mei_me_hw_is_ready - check whether the me(hw) has turned ready + * + * @dev - mei device + * returns bool + */ +static bool mei_me_hw_is_ready(struct mei_device *dev) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	hw->me_hw_state = mei_mecsr_read(hw); +	return (hw->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA; +} + +/** + * mei_hbuf_filled_slots - gets number of device filled buffer slots + * + * @dev: the device structure + * + * returns number of filled slots + */ +static unsigned char mei_hbuf_filled_slots(struct mei_device *dev) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	char read_ptr, write_ptr; + +	hw->host_hw_state = mei_hcsr_read(hw); + +	read_ptr = (char) ((hw->host_hw_state & H_CBRP) >> 8); +	write_ptr = (char) ((hw->host_hw_state & H_CBWP) >> 16); + +	return (unsigned char) (write_ptr - read_ptr); +} + +/** + * mei_hbuf_is_empty - checks if host buffer is empty. + * + * @dev: the device structure + * + * returns true if empty, false - otherwise. + */ +static bool mei_me_hbuf_is_empty(struct mei_device *dev) +{ +	return mei_hbuf_filled_slots(dev) == 0; +} + +/** + * mei_me_hbuf_empty_slots - counts write empty slots. + * + * @dev: the device structure + * + * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count + */ +static int mei_me_hbuf_empty_slots(struct mei_device *dev) +{ +	unsigned char filled_slots, empty_slots; + +	filled_slots = mei_hbuf_filled_slots(dev); +	empty_slots = dev->hbuf_depth - filled_slots; + +	/* check for overflow */ +	if (filled_slots > dev->hbuf_depth) +		return -EOVERFLOW; + +	return empty_slots; +} + +static size_t mei_me_hbuf_max_len(const struct mei_device *dev) +{ +	return dev->hbuf_depth * sizeof(u32) - sizeof(struct mei_msg_hdr); +} + + +/** + * mei_write_message - writes a message to mei device. + * + * @dev: the device structure + * @header: mei HECI header of message + * @buf: message payload will be written + * + * This function returns -EIO if write has failed + */ +static int mei_me_write_message(struct mei_device *dev, +			struct mei_msg_hdr *header, +			unsigned char *buf) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	unsigned long rem, dw_cnt; +	unsigned long length = header->length; +	u32 *reg_buf = (u32 *)buf; +	u32 hcsr; +	int i; +	int empty_slots; + +	dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); + +	empty_slots = mei_hbuf_empty_slots(dev); +	dev_dbg(&dev->pdev->dev, "empty slots = %hu.\n", empty_slots); + +	dw_cnt = mei_data2slots(length); +	if (empty_slots < 0 || dw_cnt > empty_slots) +		return -EIO; + +	mei_reg_write(hw, H_CB_WW, *((u32 *) header)); + +	for (i = 0; i < length / 4; i++) +		mei_reg_write(hw, H_CB_WW, reg_buf[i]); + +	rem = length & 0x3; +	if (rem > 0) { +		u32 reg = 0; +		memcpy(®, &buf[length - rem], rem); +		mei_reg_write(hw, H_CB_WW, reg); +	} + +	hcsr = mei_hcsr_read(hw) | H_IG; +	mei_hcsr_set(hw, hcsr); +	if (!mei_me_hw_is_ready(dev)) +		return -EIO; + +	return 0; +} + +/** + * mei_me_count_full_read_slots - counts read full slots. + * + * @dev: the device structure + * + * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count + */ +static int mei_me_count_full_read_slots(struct mei_device *dev) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	char read_ptr, write_ptr; +	unsigned char buffer_depth, filled_slots; + +	hw->me_hw_state = mei_mecsr_read(hw); +	buffer_depth = (unsigned char)((hw->me_hw_state & ME_CBD_HRA) >> 24); +	read_ptr = (char) ((hw->me_hw_state & ME_CBRP_HRA) >> 8); +	write_ptr = (char) ((hw->me_hw_state & ME_CBWP_HRA) >> 16); +	filled_slots = (unsigned char) (write_ptr - read_ptr); + +	/* check for overflow */ +	if (filled_slots > buffer_depth) +		return -EOVERFLOW; + +	dev_dbg(&dev->pdev->dev, "filled_slots =%08x\n", filled_slots); +	return (int)filled_slots; +} + +/** + * mei_me_read_slots - reads a message from mei device. + * + * @dev: the device structure + * @buffer: message buffer will be written + * @buffer_length: message size will be read + */ +static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, +		    unsigned long buffer_length) +{ +	struct mei_me_hw *hw = to_me_hw(dev); +	u32 *reg_buf = (u32 *)buffer; +	u32 hcsr; + +	for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32)) +		*reg_buf++ = mei_me_mecbrw_read(dev); + +	if (buffer_length > 0) { +		u32 reg = mei_me_mecbrw_read(dev); +		memcpy(reg_buf, ®, buffer_length); +	} + +	hcsr = mei_hcsr_read(hw) | H_IG; +	mei_hcsr_set(hw, hcsr); +	return 0; +} + +/** + * mei_me_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_me_irq_quick_handler(int irq, void *dev_id) +{ +	struct mei_device *dev = (struct mei_device *) dev_id; +	struct mei_me_hw *hw = to_me_hw(dev); +	u32 csr_reg = mei_hcsr_read(hw); + +	if ((csr_reg & H_IS) != H_IS) +		return IRQ_NONE; + +	/* clear H_IS bit in H_CSR */ +	mei_reg_write(hw, H_CSR, csr_reg); + +	return IRQ_WAKE_THREAD; +} + +/** + * mei_me_irq_thread_handler - function called after ISR to handle the interrupt + * processing. + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + * + */ +irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) +{ +	struct mei_device *dev = (struct mei_device *) dev_id; +	struct mei_cl_cb complete_list; +	struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; +	struct mei_cl *cl; +	s32 slots; +	int rets; +	bool  bus_message_received; + + +	dev_dbg(&dev->pdev->dev, "function called after ISR to handle the interrupt processing.\n"); +	/* initialize our complete list */ +	mutex_lock(&dev->device_lock); +	mei_io_list_init(&complete_list); + +	/* Ack the interrupt here +	 * In case of MSI we don't go through the quick handler */ +	if (pci_dev_msi_enabled(dev->pdev)) +		mei_clear_interrupts(dev); + +	/* check if ME wants a reset */ +	if (!mei_hw_is_ready(dev) && +	    dev->dev_state != MEI_DEV_RESETING && +	    dev->dev_state != MEI_DEV_INITIALIZING) { +		dev_dbg(&dev->pdev->dev, "FW not ready.\n"); +		mei_reset(dev, 1); +		mutex_unlock(&dev->device_lock); +		return IRQ_HANDLED; +	} + +	/*  check if we need to start the dev */ +	if (!mei_host_is_ready(dev)) { +		if (mei_hw_is_ready(dev)) { +			dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); + +			mei_host_set_ready(dev); + +			dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); +			/* link is established * start sending messages.  */ + +			dev->dev_state = MEI_DEV_INIT_CLIENTS; + +			mei_hbm_start_req(dev); +			mutex_unlock(&dev->device_lock); +			return IRQ_HANDLED; +		} else { +			dev_dbg(&dev->pdev->dev, "FW not ready.\n"); +			mutex_unlock(&dev->device_lock); +			return IRQ_HANDLED; +		} +	} +	/* 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"); +		rets = mei_irq_read_handler(dev, &complete_list, &slots); +		if (rets) +			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); + +	bus_message_received = false; +	if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { +		dev_dbg(&dev->pdev->dev, "received waiting bus message\n"); +		bus_message_received = true; +	} +	mutex_unlock(&dev->device_lock); +	if (bus_message_received) { +		dev_dbg(&dev->pdev->dev, "wake up dev->wait_recvd_msg\n"); +		wake_up_interruptible(&dev->wait_recvd_msg); +		bus_message_received = false; +	} +	if (list_empty(&complete_list.list)) +		return IRQ_HANDLED; + + +	list_for_each_entry_safe(cb_pos, cb_next, &complete_list.list, list) { +		cl = cb_pos->cl; +		list_del(&cb_pos->list); +		if (cl) { +			if (cl != &dev->iamthif_cl) { +				dev_dbg(&dev->pdev->dev, "completing call back.\n"); +				mei_irq_complete_handler(cl, cb_pos); +				cb_pos = NULL; +			} else if (cl == &dev->iamthif_cl) { +				mei_amthif_complete(dev, cb_pos); +			} +		} +	} +	return IRQ_HANDLED; +} +static const struct mei_hw_ops mei_me_hw_ops = { + +	.host_set_ready = mei_me_host_set_ready, +	.host_is_ready = mei_me_host_is_ready, + +	.hw_is_ready = mei_me_hw_is_ready, +	.hw_reset = mei_me_hw_reset, +	.hw_config  = mei_me_hw_config, + +	.intr_clear = mei_me_intr_clear, +	.intr_enable = mei_me_intr_enable, +	.intr_disable = mei_me_intr_disable, + +	.hbuf_free_slots = mei_me_hbuf_empty_slots, +	.hbuf_is_ready = mei_me_hbuf_is_empty, +	.hbuf_max_len = mei_me_hbuf_max_len, + +	.write = mei_me_write_message, + +	.rdbuf_full_slots = mei_me_count_full_read_slots, +	.read_hdr = mei_me_mecbrw_read, +	.read = mei_me_read_slots +}; + +/** + * init_mei_device - allocates and initializes the mei device structure + * + * @pdev: The pci device structure + * + * 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 *dev; + +	dev = kzalloc(sizeof(struct mei_device) + +			 sizeof(struct mei_me_hw), GFP_KERNEL); +	if (!dev) +		return NULL; + +	mei_device_init(dev); + +	INIT_LIST_HEAD(&dev->wd_cl.link); +	INIT_LIST_HEAD(&dev->iamthif_cl.link); +	mei_io_list_init(&dev->amthif_cmd_list); +	mei_io_list_init(&dev->amthif_rd_complete_list); + +	INIT_DELAYED_WORK(&dev->timer_work, mei_timer); +	INIT_WORK(&dev->init_work, mei_host_client_init); + +	dev->ops = &mei_me_hw_ops; + +	dev->pdev = pdev; +	return dev; +} +  |