Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

36
drivers/misc/mei/Kconfig Normal file
View File

@@ -0,0 +1,36 @@
config INTEL_MEI
tristate "Intel Management Engine Interface"
depends on X86 && PCI && WATCHDOG_CORE
help
The Intel Management Engine (Intel ME) provides Manageability,
Security and Media services for system containing Intel chipsets.
if selected /dev/mei misc device will be created.
For more information see
<http://software.intel.com/en-us/manageability/>
config INTEL_MEI_ME
tristate "ME Enabled Intel Chipsets"
select INTEL_MEI
depends on X86 && PCI && WATCHDOG_CORE
help
MEI support for ME Enabled Intel chipsets.
Supported Chipsets are:
7 Series Chipset Family
6 Series Chipset Family
5 Series Chipset Family
4 Series Chipset Family
Mobile 4 Series Chipset Family
ICH9
82946GZ/GL
82G35 Express
82Q963/Q965
82P965/G965
Mobile PM965/GM965
Mobile GME965/GLE960
82Q35 Express
82G33/G31/P35/P31 Express
82Q33 Express
82X38/X48 Express

19
drivers/misc/mei/Makefile Normal file
View File

@@ -0,0 +1,19 @@
#
# Makefile - Intel Management Engine Interface (Intel MEI) Linux driver
# Copyright (c) 2010-2011, Intel Corporation.
#
obj-$(CONFIG_INTEL_MEI) += mei.o
mei-objs := init.o
mei-objs += hbm.o
mei-objs += interrupt.o
mei-objs += client.o
mei-objs += main.o
mei-objs += amthif.o
mei-objs += wd.o
mei-objs += bus.o
mei-objs += nfc.o
mei-$(CONFIG_DEBUG_FS) += debugfs.o
obj-$(CONFIG_INTEL_MEI_ME) += mei-me.o
mei-me-objs := pci-me.o
mei-me-objs += hw-me.o

734
drivers/misc/mei/amthif.c Normal file
View File

@@ -0,0 +1,734 @@
/*
*
* 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/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#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>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/uuid.h>
#include <linux/jiffies.h>
#include <linux/uaccess.h>
#include <linux/mei.h>
#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,
0xac, 0xa8, 0x46, 0xe0,
0xff, 0x65, 0x81, 0x4c);
/**
* mei_amthif_reset_params - initializes mei device iamthif
*
* @dev: the device structure
*/
void mei_amthif_reset_params(struct mei_device *dev)
{
/* reset iamthif parameters. */
dev->iamthif_current_cb = NULL;
dev->iamthif_msg_buf_size = 0;
dev->iamthif_msg_buf_index = 0;
dev->iamthif_canceled = false;
dev->iamthif_ioctl = false;
dev->iamthif_state = MEI_IAMTHIF_IDLE;
dev->iamthif_timer = 0;
dev->iamthif_stall_timer = 0;
}
/**
* mei_amthif_host_init - mei initialization amthif client.
*
* @dev: the device structure
*
*/
int mei_amthif_host_init(struct mei_device *dev)
{
struct mei_cl *cl = &dev->iamthif_cl;
unsigned char *msg_buf;
int ret, i;
dev->iamthif_state = MEI_IAMTHIF_IDLE;
mei_cl_init(cl, 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;
}
cl->me_client_id = dev->me_clients[i].client_id;
/* Assign iamthif_mtu to the value received from ME */
dev->iamthif_mtu = dev->me_clients[i].props.max_msg_length;
dev_dbg(&dev->pdev->dev, "IAMTHIF_MTU = %d\n",
dev->me_clients[i].props.max_msg_length);
kfree(dev->iamthif_msg_buf);
dev->iamthif_msg_buf = NULL;
/* allocate storage for ME message buffer */
msg_buf = kcalloc(dev->iamthif_mtu,
sizeof(unsigned char), GFP_KERNEL);
if (!msg_buf) {
dev_err(&dev->pdev->dev, "amthif: memory allocation for ME message buffer failed.\n");
return -ENOMEM;
}
dev->iamthif_msg_buf = msg_buf;
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;
}
cl->state = MEI_FILE_CONNECTING;
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;
}
/**
* mei_amthif_find_read_list_entry - finds a amthilist entry for current file
*
* @dev: the device structure
* @file: pointer to file object
*
* returns returned a list entry on success, NULL on failure.
*/
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;
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;
}
return NULL;
}
/**
* mei_amthif_read - read data from AMTHIF client
*
* @dev: the device structure
* @if_num: minor number
* @file: pointer to file object
* @*ubuf: pointer to user data in user space
* @length: data length to read
* @offset: data read offset
*
* Locking: called under "dev->device_lock" lock
*
* returns
* returned data length on success,
* zero if no data to read,
* negative on failure.
*/
int mei_amthif_read(struct mei_device *dev, struct file *file,
char __user *ubuf, size_t length, loff_t *offset)
{
int rets;
int wait_ret;
struct mei_cl_cb *cb = NULL;
struct mei_cl *cl = file->private_data;
unsigned long timeout;
int i;
/* Only Posible if we are in timeout */
if (!cl || cl != &dev->iamthif_cl) {
dev_dbg(&dev->pdev->dev, "bad file ext.\n");
return -ETIMEDOUT;
}
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;
}
dev_dbg(&dev->pdev->dev, "checking amthif data\n");
cb = mei_amthif_find_read_list_entry(dev, file);
/* Check for if we can block or not*/
if (cb == NULL && file->f_flags & O_NONBLOCK)
return -EAGAIN;
dev_dbg(&dev->pdev->dev, "waiting for amthif data\n");
while (cb == NULL) {
/* unlock the Mutex */
mutex_unlock(&dev->device_lock);
wait_ret = wait_event_interruptible(dev->iamthif_cl.wait,
(cb = mei_amthif_find_read_list_entry(dev, file)));
/* Locking again the Mutex */
mutex_lock(&dev->device_lock);
if (wait_ret)
return -ERESTARTSYS;
dev_dbg(&dev->pdev->dev, "woke up from sleep\n");
}
dev_dbg(&dev->pdev->dev, "Got amthif data\n");
dev->iamthif_timer = 0;
if (cb) {
timeout = cb->read_time +
mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER);
dev_dbg(&dev->pdev->dev, "amthif timeout = %lud\n",
timeout);
if (time_after(jiffies, timeout)) {
dev_dbg(&dev->pdev->dev, "amthif Time out\n");
/* 15 sec for the message has expired */
list_del(&cb->list);
rets = -ETIMEDOUT;
goto free;
}
}
/* if the whole message will fit remove it from the list */
if (cb->buf_idx >= *offset && length >= (cb->buf_idx - *offset))
list_del(&cb->list);
else if (cb->buf_idx > 0 && cb->buf_idx <= *offset) {
/* end of the message has been reached */
list_del(&cb->list);
rets = 0;
goto free;
}
/* else means that not full buffer will be read and do not
* remove message from deletion list
*/
dev_dbg(&dev->pdev->dev, "amthif cb->response_buffer size - %d\n",
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,
* 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))
rets = -EFAULT;
else {
rets = length;
if ((*offset + length) < cb->buf_idx) {
*offset += length;
goto out;
}
}
free:
dev_dbg(&dev->pdev->dev, "free amthif cb memory.\n");
*offset = 0;
mei_io_cb_free(cb);
out:
return rets;
}
/**
* mei_amthif_send_cmd - send amthif command to the ME
*
* @dev: the device structure
* @cb: mei call back struct
*
* returns 0 on success, <0 on failure.
*
*/
static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb)
{
struct mei_msg_hdr mei_hdr;
int ret;
if (!dev || !cb)
return -ENODEV;
dev_dbg(&dev->pdev->dev, "write data to amthif client.\n");
dev->iamthif_state = MEI_IAMTHIF_WRITING;
dev->iamthif_current_cb = cb;
dev->iamthif_file_object = cb->file_object;
dev->iamthif_canceled = false;
dev->iamthif_ioctl = true;
dev->iamthif_msg_buf_size = cb->request_buffer.size;
memcpy(dev->iamthif_msg_buf, cb->request_buffer.data,
cb->request_buffer.size);
ret = mei_cl_flow_ctrl_creds(&dev->iamthif_cl);
if (ret < 0)
return ret;
if (ret && dev->hbuf_is_ready) {
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;
} else {
mei_hdr.length = cb->request_buffer.size;
mei_hdr.msg_complete = 1;
}
mei_hdr.host_addr = dev->iamthif_cl.host_client_id;
mei_hdr.me_addr = dev->iamthif_cl.me_client_id;
mei_hdr.reserved = 0;
dev->iamthif_msg_buf_index += mei_hdr.length;
if (mei_write_message(dev, &mei_hdr,
(unsigned char *)dev->iamthif_msg_buf))
return -ENODEV;
if (mei_hdr.msg_complete) {
if (mei_cl_flow_ctrl_reduce(&dev->iamthif_cl))
return -ENODEV;
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");
dev->iamthif_current_cb = cb;
dev->iamthif_file_object = cb->file_object;
list_add_tail(&cb->list, &dev->write_waiting_list.list);
} else {
dev_dbg(&dev->pdev->dev, "message does not complete, so add amthif cb to write list.\n");
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;
}
/**
* mei_amthif_write - write amthif data to amthif client
*
* @dev: the device structure
* @cb: mei call back struct
*
* returns 0 on success, <0 on failure.
*
*/
int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *cb)
{
int ret;
if (!dev || !cb)
return -ENODEV;
ret = mei_io_cb_alloc_resp_buf(cb, dev->iamthif_mtu);
if (ret)
return ret;
cb->fop_type = MEI_FOP_IOCTL;
if (!list_empty(&dev->amthif_cmd_list.list) ||
dev->iamthif_state != MEI_IAMTHIF_IDLE) {
dev_dbg(&dev->pdev->dev,
"amthif state = %d\n", dev->iamthif_state);
dev_dbg(&dev->pdev->dev, "AMTHIF: add cb to the wait list\n");
list_add_tail(&cb->list, &dev->amthif_cmd_list.list);
return 0;
}
return mei_amthif_send_cmd(dev, cb);
}
/**
* mei_amthif_run_next_cmd
*
* @dev: the device structure
*
* returns 0 on success, <0 on failure.
*/
void mei_amthif_run_next_cmd(struct mei_device *dev)
{
struct mei_cl_cb *pos = NULL;
struct mei_cl_cb *next = NULL;
int status;
if (!dev)
return;
dev->iamthif_msg_buf_size = 0;
dev->iamthif_msg_buf_index = 0;
dev->iamthif_canceled = false;
dev->iamthif_ioctl = true;
dev->iamthif_state = MEI_IAMTHIF_IDLE;
dev->iamthif_timer = 0;
dev->iamthif_file_object = NULL;
dev_dbg(&dev->pdev->dev, "complete amthif cmd_list cb.\n");
list_for_each_entry_safe(pos, next, &dev->amthif_cmd_list.list, list) {
list_del(&pos->list);
if (pos->cl && pos->cl == &dev->iamthif_cl) {
status = mei_amthif_send_cmd(dev, pos);
if (status) {
dev_dbg(&dev->pdev->dev,
"amthif write failed status = %d\n",
status);
return;
}
break;
}
}
}
unsigned int mei_amthif_poll(struct mei_device *dev,
struct file *file, poll_table *wait)
{
unsigned int mask = 0;
mutex_unlock(&dev->device_lock);
poll_wait(file, &dev->iamthif_cl.wait, wait);
mutex_lock(&dev->device_lock);
if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE &&
dev->iamthif_file_object == file) {
mask |= (POLLIN | POLLRDNORM);
dev_dbg(&dev->pdev->dev, "run next amthif cb\n");
mei_amthif_run_next_cmd(dev);
}
return mask;
}
/**
* mei_amthif_irq_write_completed - processes completed iamthif operation.
*
* @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_device *dev, s32 *slots,
struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list)
{
struct mei_msg_hdr mei_hdr;
struct mei_cl *cl = cb->cl;
size_t len = dev->iamthif_msg_buf_size - dev->iamthif_msg_buf_index;
u32 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;
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);
mei_hdr.length = len;
mei_hdr.msg_complete = 0;
} else {
/* wait for next time the host buffer is empty */
return 0;
}
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;
}
if (mei_cl_flow_ctrl_reduce(cl))
return -ENODEV;
dev->iamthif_msg_buf_index += mei_hdr.length;
cl->status = 0;
if (mei_hdr.msg_complete) {
dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL;
dev->iamthif_flow_control_pending = true;
/* save iamthif cb sent to amthif client */
cb->buf_idx = dev->iamthif_msg_buf_index;
dev->iamthif_current_cb = cb;
list_move_tail(&cb->list, &dev->write_waiting_list.list);
}
return 0;
}
/**
* mei_amthif_irq_read_message - read routine after ISR to
* handle the read amthif message
*
* @dev: the device structure
* @mei_hdr: header of amthif message
* @complete_list: An instance of our list structure
*
* returns 0 on success, <0 on failure.
*/
int mei_amthif_irq_read_msg(struct mei_device *dev,
struct mei_msg_hdr *mei_hdr,
struct mei_cl_cb *complete_list)
{
struct mei_cl_cb *cb;
unsigned char *buffer;
BUG_ON(mei_hdr->me_addr != dev->iamthif_cl.me_client_id);
BUG_ON(dev->iamthif_state != MEI_IAMTHIF_READING);
buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index;
BUG_ON(dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length);
mei_read_slots(dev, buffer, mei_hdr->length);
dev->iamthif_msg_buf_index += mei_hdr->length;
if (!mei_hdr->msg_complete)
return 0;
dev_dbg(&dev->pdev->dev, "amthif_message_buffer_index =%d\n",
mei_hdr->length);
dev_dbg(&dev->pdev->dev, "completed amthif read.\n ");
if (!dev->iamthif_current_cb)
return -ENODEV;
cb = dev->iamthif_current_cb;
dev->iamthif_current_cb = NULL;
if (!cb->cl)
return -ENODEV;
dev->iamthif_stall_timer = 0;
cb->buf_idx = dev->iamthif_msg_buf_index;
cb->read_time = jiffies;
if (dev->iamthif_ioctl && cb->cl == &dev->iamthif_cl) {
/* found the iamthif cb */
dev_dbg(&dev->pdev->dev, "complete the amthif read cb.\n ");
dev_dbg(&dev->pdev->dev, "add the amthif read cb to complete.\n ");
list_add_tail(&cb->list, &complete_list->list);
}
return 0;
}
/**
* mei_amthif_irq_read - prepares to read amthif data.
*
* @dev: the device structure.
* @slots: free slots.
*
* returns 0, OK; otherwise, error.
*/
int mei_amthif_irq_read(struct mei_device *dev, s32 *slots)
{
u32 msg_slots = mei_data2slots(sizeof(struct hbm_flow_control));
if (*slots < msg_slots)
return -EMSGSIZE;
*slots -= msg_slots;
if (mei_hbm_cl_flow_control_req(dev, &dev->iamthif_cl)) {
dev_dbg(&dev->pdev->dev, "iamthif flow control failed\n");
return -EIO;
}
dev_dbg(&dev->pdev->dev, "iamthif flow control success\n");
dev->iamthif_state = MEI_IAMTHIF_READING;
dev->iamthif_flow_control_pending = false;
dev->iamthif_msg_buf_index = 0;
dev->iamthif_msg_buf_size = 0;
dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER;
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
return 0;
}
/**
* mei_amthif_complete - complete amthif callback.
*
* @dev: the device structure.
* @cb_pos: callback block.
*/
void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb)
{
if (dev->iamthif_canceled != 1) {
dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE;
dev->iamthif_stall_timer = 0;
memcpy(cb->response_buffer.data,
dev->iamthif_msg_buf,
dev->iamthif_msg_buf_index);
list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list);
dev_dbg(&dev->pdev->dev, "amthif read completed\n");
dev->iamthif_timer = jiffies;
dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n",
dev->iamthif_timer);
} else {
mei_amthif_run_next_cmd(dev);
}
dev_dbg(&dev->pdev->dev, "completing amthif call back.\n");
wake_up_interruptible(&dev->iamthif_cl.wait);
}
/**
* mei_clear_list - removes all callbacks associated with file
* from mei_cb_list
*
* @dev: device structure.
* @file: file structure
* @mei_cb_list: callbacks list
*
* mei_clear_list is called to clear resources associated with file
* when application calls close function or Ctrl-C was pressed
*
* returns true if callback removed from the list, false otherwise
*/
static bool mei_clear_list(struct mei_device *dev,
const struct file *file, struct list_head *mei_cb_list)
{
struct mei_cl_cb *cb_pos = NULL;
struct mei_cl_cb *cb_next = NULL;
bool removed = false;
/* list all list member */
list_for_each_entry_safe(cb_pos, cb_next, mei_cb_list, list) {
/* check if list member associated with a file */
if (file == cb_pos->file_object) {
/* remove member from the list */
list_del(&cb_pos->list);
/* check if cb equal to current iamthif cb */
if (dev->iamthif_current_cb == cb_pos) {
dev->iamthif_current_cb = NULL;
/* send flow control to iamthif client */
mei_hbm_cl_flow_control_req(dev,
&dev->iamthif_cl);
}
/* free all allocated buffers */
mei_io_cb_free(cb_pos);
cb_pos = NULL;
removed = true;
}
}
return removed;
}
/**
* mei_clear_lists - removes all callbacks associated with file
*
* @dev: device structure
* @file: file structure
*
* mei_clear_lists is called to clear resources associated with file
* when application calls close function or Ctrl-C was pressed
*
* returns true if callback removed from the list, false otherwise
*/
static bool mei_clear_lists(struct mei_device *dev, struct file *file)
{
bool removed = false;
/* remove callbacks associated with a file */
mei_clear_list(dev, file, &dev->amthif_cmd_list.list);
if (mei_clear_list(dev, file, &dev->amthif_rd_complete_list.list))
removed = true;
mei_clear_list(dev, file, &dev->ctrl_rd_list.list);
if (mei_clear_list(dev, file, &dev->ctrl_wr_list.list))
removed = true;
if (mei_clear_list(dev, file, &dev->write_waiting_list.list))
removed = true;
if (mei_clear_list(dev, file, &dev->write_list.list))
removed = true;
/* check if iamthif_current_cb not NULL */
if (dev->iamthif_current_cb && !removed) {
/* check file and iamthif current cb association */
if (dev->iamthif_current_cb->file_object == file) {
/* remove cb */
mei_io_cb_free(dev->iamthif_current_cb);
dev->iamthif_current_cb = NULL;
removed = true;
}
}
return removed;
}
/**
* mei_amthif_release - the release function
*
* @dev: device structure
* @file: pointer to file structure
*
* returns 0 on success, <0 on error
*/
int mei_amthif_release(struct mei_device *dev, struct file *file)
{
if (dev->open_handle_count > 0)
dev->open_handle_count--;
if (dev->iamthif_file_object == file &&
dev->iamthif_state != MEI_IAMTHIF_IDLE) {
dev_dbg(&dev->pdev->dev, "amthif canceled iamthif state %d\n",
dev->iamthif_state);
dev->iamthif_canceled = true;
if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE) {
dev_dbg(&dev->pdev->dev, "run next amthif iamthif cb\n");
mei_amthif_run_next_cmd(dev);
}
}
if (mei_clear_lists(dev, file))
dev->iamthif_state = MEI_IAMTHIF_IDLE;
return 0;
}

533
drivers/misc/mei/bus.c Normal file
View File

@@ -0,0 +1,533 @@
/*
* Intel Management Engine Interface (Intel MEI) Linux driver
* Copyright (c) 2012-2013, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/mei_cl_bus.h>
#include "mei_dev.h"
#include "hw-me.h"
#include "client.h"
#define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver)
#define to_mei_cl_device(d) container_of(d, struct mei_cl_device, dev)
static int mei_cl_device_match(struct device *dev, struct device_driver *drv)
{
struct mei_cl_device *device = to_mei_cl_device(dev);
struct mei_cl_driver *driver = to_mei_cl_driver(drv);
const struct mei_cl_device_id *id;
if (!device)
return 0;
if (!driver || !driver->id_table)
return 0;
id = driver->id_table;
while (id->name[0]) {
if (!strcmp(dev_name(dev), id->name))
return 1;
id++;
}
return 0;
}
static int mei_cl_device_probe(struct device *dev)
{
struct mei_cl_device *device = to_mei_cl_device(dev);
struct mei_cl_driver *driver;
struct mei_cl_device_id id;
if (!device)
return 0;
driver = to_mei_cl_driver(dev->driver);
if (!driver || !driver->probe)
return -ENODEV;
dev_dbg(dev, "Device probe\n");
strncpy(id.name, dev_name(dev), MEI_CL_NAME_SIZE);
return driver->probe(device, &id);
}
static int mei_cl_device_remove(struct device *dev)
{
struct mei_cl_device *device = to_mei_cl_device(dev);
struct mei_cl_driver *driver;
if (!device || !dev->driver)
return 0;
if (device->event_cb) {
device->event_cb = NULL;
cancel_work_sync(&device->event_work);
}
driver = to_mei_cl_driver(dev->driver);
if (!driver->remove) {
dev->driver = NULL;
return 0;
}
return driver->remove(device);
}
static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
char *buf)
{
int len;
len = snprintf(buf, PAGE_SIZE, "mei:%s\n", dev_name(dev));
return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
}
static struct device_attribute mei_cl_dev_attrs[] = {
__ATTR_RO(modalias),
__ATTR_NULL,
};
static int mei_cl_uevent(struct device *dev, struct kobj_uevent_env *env)
{
if (add_uevent_var(env, "MODALIAS=mei:%s", dev_name(dev)))
return -ENOMEM;
return 0;
}
static struct bus_type mei_cl_bus_type = {
.name = "mei",
.dev_attrs = mei_cl_dev_attrs,
.match = mei_cl_device_match,
.probe = mei_cl_device_probe,
.remove = mei_cl_device_remove,
.uevent = mei_cl_uevent,
};
static void mei_cl_dev_release(struct device *dev)
{
kfree(to_mei_cl_device(dev));
}
static struct device_type mei_cl_device_type = {
.release = mei_cl_dev_release,
};
static struct mei_cl *mei_bus_find_mei_cl_by_uuid(struct mei_device *dev,
uuid_le uuid)
{
struct mei_cl *cl, *next;
list_for_each_entry_safe(cl, next, &dev->device_list, device_link) {
if (!uuid_le_cmp(uuid, cl->device_uuid))
return cl;
}
return NULL;
}
struct mei_cl_device *mei_cl_add_device(struct mei_device *dev,
uuid_le uuid, char *name,
struct mei_cl_ops *ops)
{
struct mei_cl_device *device;
struct mei_cl *cl;
int status;
cl = mei_bus_find_mei_cl_by_uuid(dev, uuid);
if (cl == NULL)
return NULL;
device = kzalloc(sizeof(struct mei_cl_device), GFP_KERNEL);
if (!device)
return NULL;
device->cl = cl;
device->ops = ops;
device->dev.parent = &dev->pdev->dev;
device->dev.bus = &mei_cl_bus_type;
device->dev.type = &mei_cl_device_type;
dev_set_name(&device->dev, "%s", name);
status = device_register(&device->dev);
if (status) {
dev_err(&dev->pdev->dev, "Failed to register MEI device\n");
kfree(device);
return NULL;
}
cl->device = device;
dev_dbg(&device->dev, "client %s registered\n", name);
return device;
}
EXPORT_SYMBOL_GPL(mei_cl_add_device);
void mei_cl_remove_device(struct mei_cl_device *device)
{
device_unregister(&device->dev);
}
EXPORT_SYMBOL_GPL(mei_cl_remove_device);
int __mei_cl_driver_register(struct mei_cl_driver *driver, struct module *owner)
{
int err;
driver->driver.name = driver->name;
driver->driver.owner = owner;
driver->driver.bus = &mei_cl_bus_type;
err = driver_register(&driver->driver);
if (err)
return err;
pr_debug("mei: driver [%s] registered\n", driver->driver.name);
return 0;
}
EXPORT_SYMBOL_GPL(__mei_cl_driver_register);
void mei_cl_driver_unregister(struct mei_cl_driver *driver)
{
driver_unregister(&driver->driver);
pr_debug("mei: driver [%s] unregistered\n", driver->driver.name);
}
EXPORT_SYMBOL_GPL(mei_cl_driver_unregister);
static int ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
bool blocking)
{
struct mei_device *dev;
struct mei_cl_cb *cb;
int id;
int rets;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
if (cl->state != MEI_FILE_CONNECTED)
return -ENODEV;
/* Check if we have an ME client device */
id = mei_me_cl_by_id(dev, cl->me_client_id);
if (id < 0)
return -ENODEV;
if (length > dev->me_clients[id].props.max_msg_length)
return -EINVAL;
cb = mei_io_cb_init(cl, NULL);
if (!cb)
return -ENOMEM;
rets = mei_io_cb_alloc_req_buf(cb, length);
if (rets < 0) {
mei_io_cb_free(cb);
return rets;
}
memcpy(cb->request_buffer.data, buf, length);
mutex_lock(&dev->device_lock);
rets = mei_cl_write(cl, cb, blocking);
mutex_unlock(&dev->device_lock);
if (rets < 0)
mei_io_cb_free(cb);
return rets;
}
int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length)
{
struct mei_device *dev;
struct mei_cl_cb *cb;
size_t r_length;
int err;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
mutex_lock(&dev->device_lock);
if (!cl->read_cb) {
err = mei_cl_read_start(cl, length);
if (err < 0) {
mutex_unlock(&dev->device_lock);
return err;
}
}
if (cl->reading_state != MEI_READ_COMPLETE &&
!waitqueue_active(&cl->rx_wait)) {
mutex_unlock(&dev->device_lock);
if (wait_event_interruptible(cl->rx_wait,
cl->reading_state == MEI_READ_COMPLETE ||
mei_cl_is_transitioning(cl))) {
if (signal_pending(current))
return -EINTR;
return -ERESTARTSYS;
}
mutex_lock(&dev->device_lock);
}
cb = cl->read_cb;
if (cl->reading_state != MEI_READ_COMPLETE) {
r_length = 0;
goto out;
}
r_length = min_t(size_t, length, cb->buf_idx);
memcpy(buf, cb->response_buffer.data, r_length);
mei_io_cb_free(cb);
cl->reading_state = MEI_IDLE;
cl->read_cb = NULL;
out:
mutex_unlock(&dev->device_lock);
return r_length;
}
inline int __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length)
{
return ___mei_cl_send(cl, buf, length, 0);
}
inline int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length)
{
return ___mei_cl_send(cl, buf, length, 1);
}
int mei_cl_send(struct mei_cl_device *device, u8 *buf, size_t length)
{
struct mei_cl *cl = device->cl;
if (cl == NULL)
return -ENODEV;
if (device->ops && device->ops->send)
return device->ops->send(device, buf, length);
return __mei_cl_send(cl, buf, length);
}
EXPORT_SYMBOL_GPL(mei_cl_send);
int mei_cl_recv(struct mei_cl_device *device, u8 *buf, size_t length)
{
struct mei_cl *cl = device->cl;
if (cl == NULL)
return -ENODEV;
if (device->ops && device->ops->recv)
return device->ops->recv(device, buf, length);
return __mei_cl_recv(cl, buf, length);
}
EXPORT_SYMBOL_GPL(mei_cl_recv);
static void mei_bus_event_work(struct work_struct *work)
{
struct mei_cl_device *device;
device = container_of(work, struct mei_cl_device, event_work);
if (device->event_cb)
device->event_cb(device, device->events, device->event_context);
device->events = 0;
/* Prepare for the next read */
mei_cl_read_start(device->cl, 0);
}
int mei_cl_register_event_cb(struct mei_cl_device *device,
mei_cl_event_cb_t event_cb, void *context)
{
if (device->event_cb)
return -EALREADY;
device->events = 0;
device->event_cb = event_cb;
device->event_context = context;
INIT_WORK(&device->event_work, mei_bus_event_work);
mei_cl_read_start(device->cl, 0);
return 0;
}
EXPORT_SYMBOL_GPL(mei_cl_register_event_cb);
void *mei_cl_get_drvdata(const struct mei_cl_device *device)
{
return dev_get_drvdata(&device->dev);
}
EXPORT_SYMBOL_GPL(mei_cl_get_drvdata);
void mei_cl_set_drvdata(struct mei_cl_device *device, void *data)
{
dev_set_drvdata(&device->dev, data);
}
EXPORT_SYMBOL_GPL(mei_cl_set_drvdata);
int mei_cl_enable_device(struct mei_cl_device *device)
{
int err;
struct mei_device *dev;
struct mei_cl *cl = device->cl;
if (cl == NULL)
return -ENODEV;
dev = cl->dev;
mutex_lock(&dev->device_lock);
cl->state = MEI_FILE_CONNECTING;
err = mei_cl_connect(cl, NULL);
if (err < 0) {
mutex_unlock(&dev->device_lock);
dev_err(&dev->pdev->dev, "Could not connect to the ME client");
return err;
}
mutex_unlock(&dev->device_lock);
if (device->event_cb && !cl->read_cb)
mei_cl_read_start(device->cl, 0);
if (!device->ops || !device->ops->enable)
return 0;
return device->ops->enable(device);
}
EXPORT_SYMBOL_GPL(mei_cl_enable_device);
int mei_cl_disable_device(struct mei_cl_device *device)
{
int err;
struct mei_device *dev;
struct mei_cl *cl = device->cl;
if (cl == NULL)
return -ENODEV;
dev = cl->dev;
mutex_lock(&dev->device_lock);
if (cl->state != MEI_FILE_CONNECTED) {
mutex_unlock(&dev->device_lock);
dev_err(&dev->pdev->dev, "Already disconnected");
return 0;
}
cl->state = MEI_FILE_DISCONNECTING;
err = mei_cl_disconnect(cl);
if (err < 0) {
mutex_unlock(&dev->device_lock);
dev_err(&dev->pdev->dev,
"Could not disconnect from the ME client");
return err;
}
/* Flush queues and remove any pending read */
mei_cl_flush_queues(cl);
if (cl->read_cb) {
struct mei_cl_cb *cb = NULL;
cb = mei_cl_find_read_cb(cl);
/* Remove entry from read list */
if (cb)
list_del(&cb->list);
cb = cl->read_cb;
cl->read_cb = NULL;
if (cb) {
mei_io_cb_free(cb);
cb = NULL;
}
}
device->event_cb = NULL;
mutex_unlock(&dev->device_lock);
if (!device->ops || !device->ops->disable)
return 0;
return device->ops->disable(device);
}
EXPORT_SYMBOL_GPL(mei_cl_disable_device);
void mei_cl_bus_rx_event(struct mei_cl *cl)
{
struct mei_cl_device *device = cl->device;
if (!device || !device->event_cb)
return;
set_bit(MEI_CL_EVENT_RX, &device->events);
schedule_work(&device->event_work);
}
int __init mei_cl_bus_init(void)
{
return bus_register(&mei_cl_bus_type);
}
void __exit mei_cl_bus_exit(void)
{
bus_unregister(&mei_cl_bus_type);
}

839
drivers/misc/mei/client.c Normal file
View File

@@ -0,0 +1,839 @@
/*
*
* 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/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hbm.h"
#include "client.h"
/**
* mei_me_cl_by_uuid - locate index of me client
*
* @dev: mei device
* 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;
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;
}
return res;
}
/**
* mei_me_cl_by_id return index to me_clients for client_id
*
* @dev: the device structure
* @client_id: me client id
*
* Locking: called under "dev->device_lock" lock
*
* returns index on success, -ENOENT on failure.
*/
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;
}
/**
* mei_io_list_flush - removes list entry belonging to cl.
*
* @list: An instance of our list structure
* @cl: host client
*/
void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl)
{
struct mei_cl_cb *cb;
struct mei_cl_cb *next;
list_for_each_entry_safe(cb, next, &list->list, list) {
if (cb->cl && mei_cl_cmp_id(cl, cb->cl))
list_del(&cb->list);
}
}
/**
* mei_io_cb_free - free mei_cb_private related memory
*
* @cb: mei callback struct
*/
void mei_io_cb_free(struct mei_cl_cb *cb)
{
if (cb == NULL)
return;
kfree(cb->request_buffer.data);
kfree(cb->response_buffer.data);
kfree(cb);
}
/**
* mei_io_cb_init - allocate and initialize io callback
*
* @cl - mei client
* @fp: pointer to file structure
*
* returns mei_cl_cb pointer or NULL;
*/
struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp)
{
struct mei_cl_cb *cb;
cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL);
if (!cb)
return NULL;
mei_io_list_init(cb);
cb->file_object = fp;
cb->cl = cl;
cb->buf_idx = 0;
return cb;
}
/**
* mei_io_cb_alloc_req_buf - allocate request buffer
*
* @cb: io callback structure
* @length: size of the buffer
*
* returns 0 on success
* -EINVAL if cb is NULL
* -ENOMEM if allocation failed
*/
int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length)
{
if (!cb)
return -EINVAL;
if (length == 0)
return 0;
cb->request_buffer.data = kmalloc(length, GFP_KERNEL);
if (!cb->request_buffer.data)
return -ENOMEM;
cb->request_buffer.size = length;
return 0;
}
/**
* mei_io_cb_alloc_resp_buf - allocate respose buffer
*
* @cb: io callback structure
* @length: size of the buffer
*
* returns 0 on success
* -EINVAL if cb is NULL
* -ENOMEM if allocation failed
*/
int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length)
{
if (!cb)
return -EINVAL;
if (length == 0)
return 0;
cb->response_buffer.data = kmalloc(length, GFP_KERNEL);
if (!cb->response_buffer.data)
return -ENOMEM;
cb->response_buffer.size = length;
return 0;
}
/**
* mei_cl_flush_queues - flushes queue lists belonging to cl.
*
* @cl: host client
*/
int mei_cl_flush_queues(struct mei_cl *cl)
{
if (WARN_ON(!cl || !cl->dev))
return -EINVAL;
dev_dbg(&cl->dev->pdev->dev, "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_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);
mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl);
return 0;
}
/**
* mei_cl_init - initializes intialize cl.
*
* @cl: host client to be initialized
* @dev: mei device
*/
void mei_cl_init(struct mei_cl *cl, struct mei_device *dev)
{
memset(cl, 0, sizeof(struct mei_cl));
init_waitqueue_head(&cl->wait);
init_waitqueue_head(&cl->rx_wait);
init_waitqueue_head(&cl->tx_wait);
INIT_LIST_HEAD(&cl->link);
INIT_LIST_HEAD(&cl->device_link);
cl->reading_state = MEI_IDLE;
cl->writing_state = MEI_IDLE;
cl->dev = dev;
}
/**
* mei_cl_allocate - allocates cl structure and sets it up.
*
* @dev: mei device
* returns The allocated file or NULL on failure
*/
struct mei_cl *mei_cl_allocate(struct mei_device *dev)
{
struct mei_cl *cl;
cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL);
if (!cl)
return NULL;
mei_cl_init(cl, dev);
return cl;
}
/**
* mei_cl_find_read_cb - find this cl's callback in the read list
*
* @cl: host client
*
* returns cb on success, NULL on error
*/
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;
list_for_each_entry_safe(cb, next, &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
*
* @cl - host client
* @id - fixed host id or -1 for genereting one
*
* returns 0 on success
* -EINVAL on incorrect values
* -ENONET if client not found
*/
int mei_cl_link(struct mei_cl *cl, int id)
{
struct mei_device *dev;
if (WARN_ON(!cl || !cl->dev))
return -EINVAL;
dev = cl->dev;
/* If Id is not asigned 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->open_handle_count++;
cl->host_client_id = id;
list_add_tail(&cl->link, &dev->file_list);
set_bit(id, dev->host_clients_map);
cl->state = MEI_FILE_INITIALIZING;
dev_dbg(&dev->pdev->dev, "link cl host id = %d\n", cl->host_client_id);
return 0;
}
/**
* mei_cl_unlink - remove me_cl from the list
*
* @cl: host client
*/
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)
return 0;
/* wd and amthif might not be initialized */
if (!cl->dev)
return 0;
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;
}
}
return 0;
}
void mei_host_client_init(struct work_struct *work)
{
struct mei_device *dev = container_of(work,
struct mei_device, init_work);
struct mei_client_properties *client_props;
int i;
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;
if (!uuid_le_cmp(client_props->protocol_name, mei_amthif_guid))
mei_amthif_host_init(dev);
else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid))
mei_wd_host_init(dev);
else if (!uuid_le_cmp(client_props->protocol_name, mei_nfc_guid))
mei_nfc_host_init(dev);
}
dev->dev_state = MEI_DEV_ENABLED;
mutex_unlock(&dev->device_lock);
}
/**
* mei_cl_disconnect - disconnect host clinet form the me one
*
* @cl: host client
*
* Locking: called under "dev->device_lock" lock
*
* returns 0 on success, <0 on failure.
*/
int mei_cl_disconnect(struct mei_cl *cl)
{
struct mei_device *dev;
struct mei_cl_cb *cb;
int rets, err;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
if (cl->state != MEI_FILE_DISCONNECTING)
return 0;
cb = mei_io_cb_init(cl, NULL);
if (!cb)
return -ENOMEM;
cb->fop_type = MEI_FOP_CLOSE;
if (dev->hbuf_is_ready) {
dev->hbuf_is_ready = false;
if (mei_hbm_cl_disconnect_req(dev, cl)) {
rets = -ENODEV;
dev_err(&dev->pdev->dev, "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");
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
}
mutex_unlock(&dev->device_lock);
err = wait_event_timeout(dev->wait_recvd_msg,
MEI_FILE_DISCONNECTED == cl->state,
mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
mutex_lock(&dev->device_lock);
if (MEI_FILE_DISCONNECTED == cl->state) {
rets = 0;
dev_dbg(&dev->pdev->dev, "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");
if (err)
dev_dbg(&dev->pdev->dev,
"wait failed disconnect err=%08x\n",
err);
dev_dbg(&dev->pdev->dev, "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:
mei_io_cb_free(cb);
return rets;
}
/**
* mei_cl_is_other_connecting - checks if other
* client with the same me client id is connecting
*
* @cl: private data of the file object
*
* returns ture if other client is connected, 0 - otherwise.
*/
bool mei_cl_is_other_connecting(struct mei_cl *cl)
{
struct mei_device *dev;
struct mei_cl *pos;
struct mei_cl *next;
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)
return true;
}
return false;
}
/**
* mei_cl_connect - connect host clinet to the me one
*
* @cl: host client
*
* Locking: called under "dev->device_lock" lock
*
* returns 0 on success, <0 on failure.
*/
int mei_cl_connect(struct mei_cl *cl, struct file *file)
{
struct mei_device *dev;
struct mei_cl_cb *cb;
long timeout = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT);
int rets;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
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;
if (mei_hbm_cl_connect_req(dev, cl)) {
rets = -ENODEV;
goto out;
}
cl->timer_count = MEI_CONNECT_TIMEOUT;
list_add_tail(&cb->list, &dev->ctrl_rd_list.list);
} else {
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
}
mutex_unlock(&dev->device_lock);
rets = wait_event_timeout(dev->wait_recvd_msg,
(cl->state == MEI_FILE_CONNECTED ||
cl->state == MEI_FILE_DISCONNECTED),
timeout * HZ);
mutex_lock(&dev->device_lock);
if (cl->state != MEI_FILE_CONNECTED) {
rets = -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:
mei_io_cb_free(cb);
return rets;
}
/**
* mei_cl_flow_ctrl_creds - checks flow_control credits for cl.
*
* @cl: private data of the file object
*
* returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise.
* -ENOENT if mei_cl is not present
* -EINVAL if single_recv_buf == 0
*/
int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
{
struct mei_device *dev;
int i;
if (WARN_ON(!cl || !cl->dev))
return -EINVAL;
dev = cl->dev;
if (!dev->me_clients_num)
return 0;
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;
}
}
}
return -ENOENT;
}
/**
* mei_cl_flow_ctrl_reduce - reduces flow_control.
*
* @cl: private data of the file object
*
* @returns
* 0 on success
* -ENOENT when me client is not found
* -EINVAL when ctrl credits are <= 0
*/
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
{
struct mei_device *dev;
int i;
if (WARN_ON(!cl || !cl->dev))
return -EINVAL;
dev = cl->dev;
if (!dev->me_clients_num)
return -ENOENT;
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;
}
}
return -ENOENT;
}
/**
* mei_cl_read_start - the start read client message function.
*
* @cl: host client
*
* returns 0 on success, <0 on failure.
*/
int mei_cl_read_start(struct mei_cl *cl, size_t length)
{
struct mei_device *dev;
struct mei_cl_cb *cb;
int rets;
int i;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
if (cl->state != MEI_FILE_CONNECTED)
return -ENODEV;
if (dev->dev_state != MEI_DEV_ENABLED)
return -ENODEV;
if (cl->read_cb) {
dev_dbg(&dev->pdev->dev, "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;
}
cb = mei_io_cb_init(cl, NULL);
if (!cb)
return -ENOMEM;
/* 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;
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;
}
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);
return rets;
}
/**
* mei_cl_write - submit a write cb to mei device
assumes device_lock is locked
*
* @cl: host client
* @cl: write callback with filled data
*
* returns numbe of bytes sent on success, <0 on failure.
*/
int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
{
struct mei_device *dev;
struct mei_msg_data *buf;
struct mei_msg_hdr mei_hdr;
int rets;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
if (WARN_ON(!cb))
return -EINVAL;
dev = cl->dev;
buf = &cb->request_buffer;
dev_dbg(&dev->pdev->dev, "mei_cl_write %d\n", buf->size);
cb->fop_type = MEI_FOP_WRITE;
rets = mei_cl_flow_ctrl_creds(cl);
if (rets < 0)
goto err;
/* Host buffer is not ready, we queue the request */
if (rets == 0 || !dev->hbuf_is_ready) {
cb->buf_idx = 0;
/* unseting complete will enqueue the cb for write */
mei_hdr.msg_complete = 0;
cl->writing_state = MEI_WRITING;
rets = buf->size;
goto out;
}
dev->hbuf_is_ready = false;
/* Check for a maximum length */
if (buf->size > mei_hbuf_max_len(dev)) {
mei_hdr.length = mei_hbuf_max_len(dev);
mei_hdr.msg_complete = 0;
} else {
mei_hdr.length = buf->size;
mei_hdr.msg_complete = 1;
}
mei_hdr.host_addr = cl->host_client_id;
mei_hdr.me_addr = cl->me_client_id;
mei_hdr.reserved = 0;
dev_dbg(&dev->pdev->dev, "write " MEI_HDR_FMT "\n",
MEI_HDR_PRM(&mei_hdr));
if (mei_write_message(dev, &mei_hdr, buf->data)) {
rets = -EIO;
goto err;
}
cl->writing_state = MEI_WRITING;
cb->buf_idx = mei_hdr.length;
rets = buf->size;
out:
if (mei_hdr.msg_complete) {
if (mei_cl_flow_ctrl_reduce(cl)) {
rets = -ENODEV;
goto err;
}
list_add_tail(&cb->list, &dev->write_waiting_list.list);
} else {
list_add_tail(&cb->list, &dev->write_list.list);
}
if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) {
mutex_unlock(&dev->device_lock);
if (wait_event_interruptible(cl->tx_wait,
cl->writing_state == MEI_WRITE_COMPLETE)) {
if (signal_pending(current))
rets = -EINTR;
else
rets = -ERESTARTSYS;
}
mutex_lock(&dev->device_lock);
}
err:
return rets;
}
/**
* mei_cl_all_disconnect - disconnect forcefully all connected clients
*
* @dev - mei device
*/
void mei_cl_all_disconnect(struct mei_device *dev)
{
struct mei_cl *cl, *next;
list_for_each_entry_safe(cl, next, &dev->file_list, link) {
cl->state = MEI_FILE_DISCONNECTED;
cl->mei_flow_ctrl_creds = 0;
cl->read_cb = NULL;
cl->timer_count = 0;
}
}
/**
* mei_cl_all_read_wakeup - wake up all readings so they can be interrupted
*
* @dev - mei device
*/
void mei_cl_all_read_wakeup(struct mei_device *dev)
{
struct mei_cl *cl, *next;
list_for_each_entry_safe(cl, next, &dev->file_list, link) {
if (waitqueue_active(&cl->rx_wait)) {
dev_dbg(&dev->pdev->dev, "Waking up client!\n");
wake_up_interruptible(&cl->rx_wait);
}
}
}
/**
* mei_cl_all_write_clear - clear all pending writes
* @dev - mei device
*/
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);
}
}

107
drivers/misc/mei/client.h Normal file
View File

@@ -0,0 +1,107 @@
/*
*
* 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.
*
*/
#ifndef _MEI_CLIENT_H_
#define _MEI_CLIENT_H_
#include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/poll.h>
#include <linux/mei.h>
#include "mei_dev.h"
int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *cuuid);
int mei_me_cl_by_id(struct mei_device *dev, u8 client_id);
/*
* MEI IO Functions
*/
struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp);
void mei_io_cb_free(struct mei_cl_cb *priv_cb);
int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length);
int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length);
/**
* mei_io_list_init - Sets up a queue list.
*
* @list: An instance cl callback structure
*/
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
*/
struct mei_cl *mei_cl_allocate(struct mei_device *dev);
void mei_cl_init(struct mei_cl *cl, struct mei_device *dev);
int mei_cl_link(struct mei_cl *cl, int id);
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);
}
static inline bool mei_cl_is_transitioning(struct mei_cl *cl)
{
return (MEI_FILE_INITIALIZING == cl->state ||
MEI_FILE_DISCONNECTED == cl->state ||
MEI_FILE_DISCONNECTING == cl->state);
}
int mei_cl_flow_ctrl_creds(struct mei_cl *cl);
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl);
/*
* MEI input output function prototype
*/
bool mei_cl_is_other_connecting(struct mei_cl *cl);
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);
void mei_host_client_init(struct work_struct *work);
void mei_cl_all_disconnect(struct mei_device *dev);
void mei_cl_all_read_wakeup(struct mei_device *dev);
void mei_cl_all_write_clear(struct mei_device *dev);
#endif /* _MEI_CLIENT_H_ */

143
drivers/misc/mei/debugfs.c Normal file
View File

@@ -0,0 +1,143 @@
/*
*
* Intel Management Engine Interface (Intel MEI) Linux driver
* Copyright (c) 2012-2013, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/pci.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hw.h"
static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
struct mei_device *dev = fp->private_data;
struct mei_me_client *cl;
const size_t bufsz = 1024;
char *buf = kzalloc(bufsz, GFP_KERNEL);
int i;
int pos = 0;
int ret;
if (!buf)
return -ENOMEM;
pos += scnprintf(buf + pos, bufsz - pos,
" |id|addr| UUID |con|msg len|\n");
mutex_lock(&dev->device_lock);
/* if the driver is not enabled the list won't b consitent */
if (dev->dev_state != MEI_DEV_ENABLED)
goto out;
for (i = 0; i < dev->me_clients_num; i++) {
cl = &dev->me_clients[i];
/* skip me clients that cannot be connected */
if (cl->props.max_number_of_connections == 0)
continue;
pos += scnprintf(buf + pos, bufsz - pos,
"%2d|%2d|%4d|%pUl|%3d|%7d|\n",
i, cl->client_id,
cl->props.fixed_address,
&cl->props.protocol_name,
cl->props.max_number_of_connections,
cl->props.max_msg_length);
}
out:
mutex_unlock(&dev->device_lock);
ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, pos);
kfree(buf);
return ret;
}
static const struct file_operations mei_dbgfs_fops_meclients = {
.open = simple_open,
.read = mei_dbgfs_read_meclients,
.llseek = generic_file_llseek,
};
static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
struct mei_device *dev = fp->private_data;
const size_t bufsz = 1024;
char *buf = kzalloc(bufsz, GFP_KERNEL);
int pos = 0;
int ret;
if (!buf)
return -ENOMEM;
pos += scnprintf(buf + pos, bufsz - pos, "%s\n",
mei_dev_state_str(dev->dev_state));
ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, pos);
kfree(buf);
return ret;
}
static const struct file_operations mei_dbgfs_fops_devstate = {
.open = simple_open,
.read = mei_dbgfs_read_devstate,
.llseek = generic_file_llseek,
};
/**
* mei_dbgfs_deregister - Remove the debugfs files and directories
* @mei - pointer to mei device private dat
*/
void mei_dbgfs_deregister(struct mei_device *dev)
{
if (!dev->dbgfs_dir)
return;
debugfs_remove_recursive(dev->dbgfs_dir);
dev->dbgfs_dir = NULL;
}
/**
* Add the debugfs files
*
*/
int mei_dbgfs_register(struct mei_device *dev, const char *name)
{
struct dentry *dir, *f;
dir = debugfs_create_dir(name, NULL);
if (!dir)
return -ENOMEM;
f = debugfs_create_file("meclients", S_IRUSR, dir,
dev, &mei_dbgfs_fops_meclients);
if (!f) {
dev_err(&dev->pdev->dev, "meclients: registration failed\n");
goto err;
}
f = debugfs_create_file("devstate", S_IRUSR, dir,
dev, &mei_dbgfs_fops_devstate);
if (!f) {
dev_err(&dev->pdev->dev, "devstate: registration failed\n");
goto err;
}
dev->dbgfs_dir = dir;
return 0;
err:
mei_dbgfs_deregister(dev);
return -ENODEV;
}

696
drivers/misc/mei/hbm.c Normal file
View File

@@ -0,0 +1,696 @@
/*
*
* 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/sched.h>
#include <linux/wait.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hbm.h"
#include "hw-me.h"
/**
* mei_hbm_me_cl_allocate - allocates storage for me clients
*
* @dev: the device structure
*
* returns none.
*/
static void 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;
/* 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;
dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%zd.\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;
}
dev->me_clients = clients;
return;
}
/**
* mei_hbm_cl_hdr - construct client hbm header
*
* @cl: - client
* @hbm_cmd: host bus message command
* @buf: buffer for cl header
* @len: buffer length
*/
static inline
void mei_hbm_cl_hdr(struct mei_cl *cl, u8 hbm_cmd, void *buf, size_t len)
{
struct mei_hbm_cl_cmd *cmd = buf;
memset(cmd, 0, len);
cmd->hbm_cmd = hbm_cmd;
cmd->host_addr = cl->host_client_id;
cmd->me_addr = cl->me_client_id;
}
/**
* same_disconn_addr - tells if they have the same address
*
* @file: private data of the file object.
* @disconn: disconnection request.
*
* returns true if addres are same
*/
static inline
bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf)
{
struct mei_hbm_cl_cmd *cmd = buf;
return cl->host_client_id == cmd->host_addr &&
cl->me_client_id == cmd->me_addr;
}
/**
* 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;
if (dev->hbm_state > MEI_HBM_START)
return 0;
mutex_unlock(&dev->device_lock);
ret = wait_event_interruptible_timeout(dev->wait_recvd_msg,
dev->hbm_state == MEI_HBM_IDLE ||
dev->hbm_state > MEI_HBM_START,
mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT));
mutex_lock(&dev->device_lock);
if (ret <= 0 && (dev->hbm_state <= MEI_HBM_START)) {
dev->hbm_state = MEI_HBM_IDLE;
dev_err(&dev->pdev->dev, "wating for mei start failed\n");
return -ETIMEDOUT;
}
return 0;
}
/**
* mei_hbm_start_req - sends start request message.
*
* @dev: the device structure
*/
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);
mei_hbm_hdr(mei_hdr, len);
/* host start message */
start_req = (struct hbm_host_version_request *)dev->wr_msg.data;
memset(start_req, 0, len);
start_req->hbm_cmd = HOST_START_REQ_CMD;
start_req->host_version.major_version = HBM_MAJOR_VERSION;
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 writet failed\n");
dev->dev_state = MEI_DEV_RESETTING;
mei_reset(dev, 1);
return -ENODEV;
}
dev->hbm_state = MEI_HBM_START;
dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT;
return 0;
}
/*
* mei_hbm_enum_clients_req - sends enumeration client request message.
*
* @dev: the device structure
*
* returns none.
*/
static void 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);
/* enumerate clients */
mei_hbm_hdr(mei_hdr, len);
enum_req = (struct hbm_host_enum_request *)dev->wr_msg.data;
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);
}
dev->hbm_state = MEI_HBM_ENUM_CLIENTS;
dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT;
return;
}
/**
* mei_hbm_prop_req - request property for a single client
*
* @dev: the device structure
*
* returns none.
*/
static int mei_hbm_prop_req(struct mei_device *dev)
{
struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr;
struct hbm_props_request *prop_req;
const size_t len = sizeof(struct hbm_props_request);
unsigned long next_client_index;
unsigned long client_num;
client_num = dev->me_client_presentation_num;
next_client_index = find_next_bit(dev->me_clients_map, MEI_CLIENTS_MAX,
dev->me_client_index);
/* We got all client properties */
if (next_client_index == MEI_CLIENTS_MAX) {
dev->hbm_state = MEI_HBM_STARTED;
schedule_work(&dev->init_work);
return 0;
}
dev->me_clients[client_num].client_id = next_client_index;
dev->me_clients[client_num].mei_flow_ctrl_creds = 0;
mei_hbm_hdr(mei_hdr, len);
prop_req = (struct hbm_props_request *)dev->wr_msg.data;
memset(prop_req, 0, sizeof(struct hbm_props_request));
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;
}
dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT;
dev->me_client_index = next_client_index;
return 0;
}
/**
* mei_hbm_stop_req_prepare - perpare stop request message
*
* @dev - mei device
* @mei_hdr - mei message header
* @data - hbm message body buffer
*/
static void mei_hbm_stop_req_prepare(struct mei_device *dev,
struct mei_msg_hdr *mei_hdr, unsigned char *data)
{
struct hbm_host_stop_request *req =
(struct hbm_host_stop_request *)data;
const size_t len = sizeof(struct hbm_host_stop_request);
mei_hbm_hdr(mei_hdr, len);
memset(req, 0, len);
req->hbm_cmd = HOST_STOP_REQ_CMD;
req->reason = DRIVER_STOP_REQUEST;
}
/**
* mei_hbm_cl_flow_control_req - sends flow control requst.
*
* @dev: the device structure
* @cl: client info
*
* This function returns -EIO on write failure
*/
int mei_hbm_cl_flow_control_req(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_flow_control);
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);
return mei_write_message(dev, mei_hdr, dev->wr_msg.data);
}
/**
* mei_hbm_add_single_flow_creds - adds single buffer credentials.
*
* @dev: the device structure
* @flow: flow control.
*/
static void 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 */
}
}
}
}
/**
* mei_hbm_cl_flow_control_res - flow control response from me
*
* @dev: the device structure
* @flow_control: flow control response bus message
*/
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;
if (!flow_control->host_addr) {
/* single receive buffer */
mei_hbm_add_single_flow_creds(dev, flow_control);
return;
}
/* normal connection */
list_for_each_entry_safe(cl, next, &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",
flow_control->host_addr, flow_control->me_addr);
dev_dbg(&dev->pdev->dev, "flow control credentials = %d.\n",
cl->mei_flow_ctrl_creds);
break;
}
}
}
/**
* mei_hbm_cl_disconnect_req - sends disconnect message to fw.
*
* @dev: the device structure
* @cl: a client to disconnect from
*
* This function returns -EIO on write failure
*/
int mei_hbm_cl_disconnect_req(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_request);
mei_hbm_hdr(mei_hdr, len);
mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_REQ_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
* @rs: disconnect response bus message
*/
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);
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)
cl->state = MEI_FILE_DISCONNECTED;
cl->status = 0;
cl->timer_count = 0;
break;
}
}
}
/**
* mei_hbm_cl_connect_req - send connection request to specific me client
*
* @dev: the device structure
* @cl: a client to connect to
*
* returns -EIO on write failure
*/
int mei_hbm_cl_connect_req(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_request);
mei_hbm_hdr(mei_hdr, len);
mei_hbm_cl_hdr(cl, CLIENT_CONNECT_REQ_CMD, dev->wr_msg.data, len);
return mei_write_message(dev, mei_hdr, dev->wr_msg.data);
}
/**
* mei_hbm_cl_connect_res - connect resposne from the ME
*
* @dev: the device structure
* @rs: connect response bus message
*/
static void mei_hbm_cl_connect_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,
"connect_response:\n"
"ME Client = %d\n"
"Host Client = %d\n"
"Status = %d\n",
rs->me_addr,
rs->host_addr,
rs->status);
/* if WD or iamthif client treat specially */
if (is_treat_specially_client(&dev->wd_cl, rs)) {
dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n");
mei_watchdog_register(dev);
return;
}
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) {
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;
}
}
}
}
/**
* mei_hbm_fw_disconnect_req - disconnect request initiated by me
* host sends disoconnect response
*
* @dev: the device structure.
* @disconnect_req: disconnect request bus message from the me
*/
static void 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);
list_for_each_entry_safe(cl, next, &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);
break;
}
}
}
/**
* mei_hbm_dispatch - bottom half read routine after ISR to
* handle the read bus message cmd processing.
*
* @dev: the device structure
* @mei_hdr: header of bus message
*/
void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
{
struct mei_bus_message *mei_msg;
struct mei_me_client *me_client;
struct hbm_host_version_response *version_res;
struct hbm_client_connect_response *connect_res;
struct hbm_client_connect_response *disconnect_res;
struct hbm_client_connect_request *disconnect_req;
struct hbm_flow_control *flow_control;
struct hbm_props_response *props_res;
struct hbm_host_enum_response *enum_res;
/* read the message to our buffer */
BUG_ON(hdr->length >= sizeof(dev->rd_msg_buf));
mei_read_slots(dev, dev->rd_msg_buf, hdr->length);
mei_msg = (struct mei_bus_message *)dev->rd_msg_buf;
switch (mei_msg->hbm_cmd) {
case HOST_START_RES_CMD:
version_res = (struct hbm_host_version_response *)mei_msg;
if (!version_res->host_version_supported) {
dev->version = version_res->me_max_version;
dev_dbg(&dev->pdev->dev, "version mismatch.\n");
dev->hbm_state = MEI_HBM_STOP;
mei_hbm_stop_req_prepare(dev, &dev->wr_msg.hdr,
dev->wr_msg.data);
mei_write_message(dev, &dev->wr_msg.hdr,
dev->wr_msg.data);
return;
}
dev->version.major_version = HBM_MAJOR_VERSION;
dev->version.minor_version = HBM_MINOR_VERSION;
if (dev->dev_state == MEI_DEV_INIT_CLIENTS &&
dev->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;
}
wake_up_interruptible(&dev->wait_recvd_msg);
dev_dbg(&dev->pdev->dev, "host start response message received.\n");
break;
case CLIENT_CONNECT_RES_CMD:
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:
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:
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 HOST_CLIENT_PROPERTIES_RES_CMD:
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 (me_client->client_id != props_res->address) {
dev_err(&dev->pdev->dev, "reset: host properties response address mismatch\n");
mei_reset(dev, 1);
return;
}
if (dev->dev_state != MEI_DEV_INIT_CLIENTS ||
dev->hbm_state != MEI_HBM_CLIENT_PROPERTIES) {
dev_err(&dev->pdev->dev, "reset: unexpected properties response\n");
mei_reset(dev, 1);
return;
}
me_client->props = props_res->client_properties;
dev->me_client_index++;
dev->me_client_presentation_num++;
/* request property for the next client */
mei_hbm_prop_req(dev);
break;
case HOST_ENUM_RES_CMD:
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;
}
break;
case HOST_STOP_RES_CMD:
if (dev->hbm_state != MEI_HBM_STOP)
dev_err(&dev->pdev->dev, "unexpected stop response hbm.\n");
dev->dev_state = MEI_DEV_DISABLED;
dev_info(&dev->pdev->dev, "reset: FW stop response.\n");
mei_reset(dev, 1);
break;
case CLIENT_DISCONNECT_REQ_CMD:
/* search for client */
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);
break;
default:
BUG();
break;
}
}

60
drivers/misc/mei/hbm.h Normal file
View File

@@ -0,0 +1,60 @@
/*
*
* 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.
*
*/
#ifndef _MEI_HBM_H_
#define _MEI_HBM_H_
struct mei_device;
struct mei_msg_hdr;
struct mei_cl;
/**
* enum mei_hbm_state - host bus message protocol state
*
* @MEI_HBM_IDLE : protocol not started
* @MEI_HBM_START : start request message was sent
* @MEI_HBM_ENUM_CLIENTS : enumeration request was sent
* @MEI_HBM_CLIENT_PROPERTIES : acquiring clients properties
*/
enum mei_hbm_state {
MEI_HBM_IDLE = 0,
MEI_HBM_START,
MEI_HBM_ENUM_CLIENTS,
MEI_HBM_CLIENT_PROPERTIES,
MEI_HBM_STARTED,
MEI_HBM_STOP,
};
void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr);
static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length)
{
hdr->host_addr = 0;
hdr->me_addr = 0;
hdr->length = length;
hdr->msg_complete = 1;
hdr->reserved = 0;
}
int mei_hbm_start_req(struct mei_device *dev);
int mei_hbm_start_wait(struct mei_device *dev);
int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl);
int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl);
int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl);
#endif /* _MEI_HBM_H_ */

View File

@@ -0,0 +1,167 @@
/******************************************************************************
* 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) 2003 - 2012 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
* USA
*
* The full GNU General Public License is included in this distribution
* in the file called LICENSE.GPL.
*
* Contact Information:
* Intel Corporation.
* linux-mei@linux.intel.com
* http://www.intel.com
*
* BSD LICENSE
*
* Copyright(c) 2003 - 2012 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_MEI_REGS_H_
#define _MEI_HW_MEI_REGS_H_
/*
* MEI device IDs
*/
#define MEI_DEV_ID_82946GZ 0x2974 /* 82946GZ/GL */
#define MEI_DEV_ID_82G35 0x2984 /* 82G35 Express */
#define MEI_DEV_ID_82Q965 0x2994 /* 82Q963/Q965 */
#define MEI_DEV_ID_82G965 0x29A4 /* 82P965/G965 */
#define MEI_DEV_ID_82GM965 0x2A04 /* Mobile PM965/GM965 */
#define MEI_DEV_ID_82GME965 0x2A14 /* Mobile GME965/GLE960 */
#define MEI_DEV_ID_ICH9_82Q35 0x29B4 /* 82Q35 Express */
#define MEI_DEV_ID_ICH9_82G33 0x29C4 /* 82G33/G31/P35/P31 Express */
#define MEI_DEV_ID_ICH9_82Q33 0x29D4 /* 82Q33 Express */
#define MEI_DEV_ID_ICH9_82X38 0x29E4 /* 82X38/X48 Express */
#define MEI_DEV_ID_ICH9_3200 0x29F4 /* 3200/3210 Server */
#define MEI_DEV_ID_ICH9_6 0x28B4 /* Bearlake */
#define MEI_DEV_ID_ICH9_7 0x28C4 /* Bearlake */
#define MEI_DEV_ID_ICH9_8 0x28D4 /* Bearlake */
#define MEI_DEV_ID_ICH9_9 0x28E4 /* Bearlake */
#define MEI_DEV_ID_ICH9_10 0x28F4 /* Bearlake */
#define MEI_DEV_ID_ICH9M_1 0x2A44 /* Cantiga */
#define MEI_DEV_ID_ICH9M_2 0x2A54 /* Cantiga */
#define MEI_DEV_ID_ICH9M_3 0x2A64 /* Cantiga */
#define MEI_DEV_ID_ICH9M_4 0x2A74 /* Cantiga */
#define MEI_DEV_ID_ICH10_1 0x2E04 /* Eaglelake */
#define MEI_DEV_ID_ICH10_2 0x2E14 /* Eaglelake */
#define MEI_DEV_ID_ICH10_3 0x2E24 /* Eaglelake */
#define MEI_DEV_ID_ICH10_4 0x2E34 /* Eaglelake */
#define MEI_DEV_ID_IBXPK_1 0x3B64 /* Calpella */
#define MEI_DEV_ID_IBXPK_2 0x3B65 /* Calpella */
#define MEI_DEV_ID_CPT_1 0x1C3A /* Couger Point */
#define MEI_DEV_ID_PBG_1 0x1D3A /* C600/X79 Patsburg */
#define MEI_DEV_ID_PPT_1 0x1E3A /* Panther Point */
#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_LP 0x9C3A /* Lynx Point LP */
/*
* MEI HW Section
*/
/* MEI registers */
/* H_CB_WW - Host Circular Buffer (CB) Write Window register */
#define H_CB_WW 0
/* H_CSR - Host Control Status register */
#define H_CSR 4
/* ME_CB_RW - ME Circular Buffer Read Window register (read only) */
#define ME_CB_RW 8
/* ME_CSR_HA - ME Control Status Host Access register (read only) */
#define ME_CSR_HA 0xC
/* register bits of H_CSR (Host Control Status register) */
/* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */
#define H_CBD 0xFF000000
/* Host Circular Buffer Write Pointer */
#define H_CBWP 0x00FF0000
/* Host Circular Buffer Read Pointer */
#define H_CBRP 0x0000FF00
/* Host Reset */
#define H_RST 0x00000010
/* Host Ready */
#define H_RDY 0x00000008
/* Host Interrupt Generate */
#define H_IG 0x00000004
/* Host Interrupt Status */
#define H_IS 0x00000002
/* Host Interrupt Enable */
#define H_IE 0x00000001
/* register bits of ME_CSR_HA (ME Control Status Host Access register) */
/* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only
access to ME_CBD */
#define ME_CBD_HRA 0xFF000000
/* ME CB Write Pointer HRA - host read only access to ME_CBWP */
#define ME_CBWP_HRA 0x00FF0000
/* ME CB Read Pointer HRA - host read only access to ME_CBRP */
#define ME_CBRP_HRA 0x0000FF00
/* 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 */
#define ME_RDY_HRA 0x00000008
/* ME Interrupt Generate HRA - host read only access to ME_IG */
#define ME_IG_HRA 0x00000004
/* ME Interrupt Status HRA - host read only access to ME_IS */
#define ME_IS_HRA 0x00000002
/* ME Interrupt Enable HRA - host read only access to ME_IE */
#define ME_IE_HRA 0x00000001
#endif /* _MEI_HW_MEI_REGS_H_ */

583
drivers/misc/mei/hw-me.c Normal file
View File

@@ -0,0 +1,583 @@
/*
*
* 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_me_reg_read - Reads 32bit data from the mei device
*
* @dev: the device structure
* @offset: offset from which to read the data
*
* returns register value (u32)
*/
static inline u32 mei_me_reg_read(const struct mei_me_hw *hw,
unsigned long offset)
{
return ioread32(hw->mem_addr + offset);
}
/**
* mei_me_reg_write - Writes 32bit data to the mei device
*
* @dev: the device structure
* @offset: offset from which to write the data
* @value: register value to write (u32)
*/
static inline void mei_me_reg_write(const struct mei_me_hw *hw,
unsigned long offset, u32 value)
{
iowrite32(value, hw->mem_addr + offset);
}
/**
* mei_me_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_me_reg_read(to_me_hw(dev), ME_CB_RW);
}
/**
* mei_me_mecsr_read - Reads 32bit data from the ME CSR
*
* @dev: the device structure
*
* returns ME_CSR_HA register value (u32)
*/
static inline u32 mei_me_mecsr_read(const struct mei_me_hw *hw)
{
return mei_me_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_me_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_me_reg_write(hw, H_CSR, hcsr);
}
/**
* mei_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_me_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_release - release device from the reset
*
* @dev: the device structure
*/
static void mei_me_hw_reset_release(struct mei_device *dev)
{
struct mei_me_hw *hw = to_me_hw(dev);
u32 hcsr = mei_hcsr_read(hw);
hcsr |= H_IG;
hcsr &= ~H_RST;
mei_hcsr_set(hw, hcsr);
}
/**
* mei_me_hw_reset - resets fw via mei csr register.
*
* @dev: the device structure
* @intr_enable: 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);
hcsr |= H_RST | H_IG | H_IS;
if (intr_enable)
hcsr |= H_IE;
else
hcsr &= ~H_IE;
mei_me_reg_write(hw, H_CSR, hcsr);
if (dev->dev_state == MEI_DEV_POWER_DOWN)
mei_me_hw_reset_release(dev);
dev_dbg(&dev->pdev->dev, "current HCSR = 0x%08x.\n", mei_hcsr_read(hw));
}
/**
* 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_me_mecsr_read(hw);
return (hw->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA;
}
static int mei_me_hw_ready_wait(struct mei_device *dev)
{
int err;
if (mei_me_hw_is_ready(dev))
return 0;
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));
mutex_lock(&dev->device_lock);
if (!err && !dev->recvd_hw_ready) {
if (!err)
err = -ETIMEDOUT;
dev_err(&dev->pdev->dev,
"wait hw ready failed. status = %d\n", err);
return err;
}
dev->recvd_hw_ready = false;
return 0;
}
static int mei_me_hw_start(struct mei_device *dev)
{
int ret = mei_me_hw_ready_wait(dev);
if (ret)
return ret;
dev_dbg(&dev->pdev->dev, "hw is ready\n");
mei_me_host_set_ready(dev);
return ret;
}
/**
* mei_hbuf_filled_slots - gets number of device filled buffer slots
*
* @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_me_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;
unsigned long length = header->length;
u32 *reg_buf = (u32 *)buf;
u32 hcsr;
u32 dw_cnt;
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_me_reg_write(hw, H_CB_WW, *((u32 *) header));
for (i = 0; i < length / 4; i++)
mei_me_reg_write(hw, H_CB_WW, reg_buf[i]);
rem = length & 0x3;
if (rem > 0) {
u32 reg = 0;
memcpy(&reg, &buf[length - rem], rem);
mei_me_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_me_mecsr_read(hw);
buffer_depth = (unsigned char)((hw->me_hw_state & ME_CBD_HRA) >> 24);
read_ptr = (char) ((hw->me_hw_state & ME_CBRP_HRA) >> 8);
write_ptr = (char) ((hw->me_hw_state & ME_CBWP_HRA) >> 16);
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, &reg, 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_me_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;
s32 slots;
int rets;
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_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;
}
/* 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");
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;
}
}
/* 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);
mutex_unlock(&dev->device_lock);
mei_irq_compl_handler(dev, &complete_list);
return IRQ_HANDLED;
}
static const struct mei_hw_ops mei_me_hw_ops = {
.host_is_ready = mei_me_host_is_ready,
.hw_is_ready = mei_me_hw_is_ready,
.hw_reset = mei_me_hw_reset,
.hw_config = mei_me_hw_config,
.hw_start = mei_me_hw_start,
.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
};
/**
* mei_me_dev_init - 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);
dev->ops = &mei_me_hw_ops;
dev->pdev = pdev;
return dev;
}

42
drivers/misc/mei/hw-me.h Normal file
View File

@@ -0,0 +1,42 @@
/*
*
* 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.
*
*/
#ifndef _MEI_INTERFACE_H_
#define _MEI_INTERFACE_H_
#include <linux/mei.h>
#include "mei_dev.h"
#include "client.h"
struct mei_me_hw {
void __iomem *mem_addr;
/*
* hw states of host and fw(ME)
*/
u32 host_hw_state;
u32 me_hw_state;
};
#define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw)
struct mei_device *mei_me_dev_init(struct pci_dev *pdev);
irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id);
irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id);
#endif /* _MEI_INTERFACE_H_ */

250
drivers/misc/mei/hw.h Normal file
View File

@@ -0,0 +1,250 @@
/*
*
* 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.
*
*/
#ifndef _MEI_HW_TYPES_H_
#define _MEI_HW_TYPES_H_
#include <linux/uuid.h>
/*
* Timeouts in Seconds
*/
#define MEI_INTEROP_TIMEOUT 7 /* Timeout on ready message */
#define MEI_CONNECT_TIMEOUT 3 /* HPS: at least 2 seconds */
#define MEI_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */
#define MEI_CLIENTS_INIT_TIMEOUT 15 /* HPS: Clients Enumeration Timeout */
#define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */
#define MEI_IAMTHIF_READ_TIMER 10 /* HPS */
/*
* MEI Version
*/
#define HBM_MINOR_VERSION 0
#define HBM_MAJOR_VERSION 1
#define HBM_TIMEOUT 1 /* 1 second */
/* Host bus message command opcode */
#define MEI_HBM_CMD_OP_MSK 0x7f
/* Host bus message command RESPONSE */
#define MEI_HBM_CMD_RES_MSK 0x80
/*
* MEI Bus Message Command IDs
*/
#define HOST_START_REQ_CMD 0x01
#define HOST_START_RES_CMD 0x81
#define HOST_STOP_REQ_CMD 0x02
#define HOST_STOP_RES_CMD 0x82
#define ME_STOP_REQ_CMD 0x03
#define HOST_ENUM_REQ_CMD 0x04
#define HOST_ENUM_RES_CMD 0x84
#define HOST_CLIENT_PROPERTIES_REQ_CMD 0x05
#define HOST_CLIENT_PROPERTIES_RES_CMD 0x85
#define CLIENT_CONNECT_REQ_CMD 0x06
#define CLIENT_CONNECT_RES_CMD 0x86
#define CLIENT_DISCONNECT_REQ_CMD 0x07
#define CLIENT_DISCONNECT_RES_CMD 0x87
#define MEI_FLOW_CONTROL_CMD 0x08
/*
* MEI Stop Reason
* used by hbm_host_stop_request.reason
*/
enum mei_stop_reason_types {
DRIVER_STOP_REQUEST = 0x00,
DEVICE_D1_ENTRY = 0x01,
DEVICE_D2_ENTRY = 0x02,
DEVICE_D3_ENTRY = 0x03,
SYSTEM_S1_ENTRY = 0x04,
SYSTEM_S2_ENTRY = 0x05,
SYSTEM_S3_ENTRY = 0x06,
SYSTEM_S4_ENTRY = 0x07,
SYSTEM_S5_ENTRY = 0x08
};
/*
* 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
};
/*
* Client Disconnect Status
*/
enum client_disconnect_status_types {
CDS_SUCCESS = 0x00
};
/*
* MEI BUS Interface Section
*/
struct mei_msg_hdr {
u32 me_addr:8;
u32 host_addr:8;
u32 length:9;
u32 reserved:6;
u32 msg_complete:1;
} __packed;
struct mei_bus_message {
u8 hbm_cmd;
u8 data[0];
} __packed;
/**
* struct hbm_cl_cmd - client specific host bus command
* CONNECT, DISCONNECT, and FlOW CONTROL
*
* @hbm_cmd - bus message command header
* @me_addr - address of the client in ME
* @host_addr - address of the client in the driver
* @data
*/
struct mei_hbm_cl_cmd {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 data;
};
struct hbm_version {
u8 minor_version;
u8 major_version;
} __packed;
struct hbm_host_version_request {
u8 hbm_cmd;
u8 reserved;
struct hbm_version host_version;
} __packed;
struct hbm_host_version_response {
u8 hbm_cmd;
u8 host_version_supported;
struct hbm_version me_max_version;
} __packed;
struct hbm_host_stop_request {
u8 hbm_cmd;
u8 reason;
u8 reserved[2];
} __packed;
struct hbm_host_stop_response {
u8 hbm_cmd;
u8 reserved[3];
} __packed;
struct hbm_me_stop_request {
u8 hbm_cmd;
u8 reason;
u8 reserved[2];
} __packed;
struct hbm_host_enum_request {
u8 hbm_cmd;
u8 reserved[3];
} __packed;
struct hbm_host_enum_response {
u8 hbm_cmd;
u8 reserved[3];
u8 valid_addresses[32];
} __packed;
struct mei_client_properties {
uuid_le protocol_name;
u8 protocol_version;
u8 max_number_of_connections;
u8 fixed_address;
u8 single_recv_buf;
u32 max_msg_length;
} __packed;
struct hbm_props_request {
u8 hbm_cmd;
u8 address;
u8 reserved[2];
} __packed;
struct hbm_props_response {
u8 hbm_cmd;
u8 address;
u8 status;
u8 reserved[1];
struct mei_client_properties client_properties;
} __packed;
/**
* struct hbm_client_connect_request - connect/disconnect request
*
* @hbm_cmd - bus message command header
* @me_addr - address of the client in ME
* @host_addr - address of the client in the driver
* @reserved
*/
struct hbm_client_connect_request {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 reserved;
} __packed;
/**
* struct hbm_client_connect_response - connect/disconnect response
*
* @hbm_cmd - bus message command header
* @me_addr - address of the client in ME
* @host_addr - address of the client in the driver
* @status - status of the request
*/
struct hbm_client_connect_response {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 status;
} __packed;
#define MEI_FC_MESSAGE_RESERVED_LENGTH 5
struct hbm_flow_control {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 reserved[MEI_FC_MESSAGE_RESERVED_LENGTH];
} __packed;
#endif

224
drivers/misc/mei/init.c Normal file
View File

@@ -0,0 +1,224 @@
/*
*
* 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/export.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hbm.h"
#include "client.h"
const char *mei_dev_state_str(int state)
{
#define MEI_DEV_STATE(state) case MEI_DEV_##state: return #state
switch (state) {
MEI_DEV_STATE(INITIALIZING);
MEI_DEV_STATE(INIT_CLIENTS);
MEI_DEV_STATE(ENABLED);
MEI_DEV_STATE(RESETTING);
MEI_DEV_STATE(DISABLED);
MEI_DEV_STATE(POWER_DOWN);
MEI_DEV_STATE(POWER_UP);
default:
return "unkown";
}
#undef MEI_DEV_STATE
}
void mei_device_init(struct mei_device *dev)
{
/* setup our list array */
INIT_LIST_HEAD(&dev->file_list);
INIT_LIST_HEAD(&dev->device_list);
mutex_init(&dev->device_lock);
init_waitqueue_head(&dev->wait_hw_ready);
init_waitqueue_head(&dev->wait_recvd_msg);
init_waitqueue_head(&dev->wait_stop_wd);
dev->dev_state = MEI_DEV_INITIALIZING;
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_LIST_HEAD(&dev->wd_cl.link);
INIT_LIST_HEAD(&dev->iamthif_cl.link);
mei_io_list_init(&dev->amthif_cmd_list);
mei_io_list_init(&dev->amthif_rd_complete_list);
}
EXPORT_SYMBOL_GPL(mei_device_init);
/**
* mei_start - initializes host and fw to start work.
*
* @dev: the device structure
*
* returns 0 on success, <0 on failure.
*/
int mei_start(struct mei_device *dev)
{
mutex_lock(&dev->device_lock);
/* acknowledge interrupt and stop interupts */
mei_clear_interrupts(dev);
mei_hw_config(dev);
dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n");
mei_reset(dev, 1);
if (mei_hbm_start_wait(dev)) {
dev_err(&dev->pdev->dev, "HBM haven't started");
goto err;
}
if (!mei_host_is_ready(dev)) {
dev_err(&dev->pdev->dev, "host is not ready.\n");
goto err;
}
if (!mei_hw_is_ready(dev)) {
dev_err(&dev->pdev->dev, "ME is not ready.\n");
goto err;
}
if (dev->version.major_version != HBM_MAJOR_VERSION ||
dev->version.minor_version != HBM_MINOR_VERSION) {
dev_dbg(&dev->pdev->dev, "MEI start failed.\n");
goto err;
}
dev_dbg(&dev->pdev->dev, "link layer has been established.\n");
mutex_unlock(&dev->device_lock);
return 0;
err:
dev_err(&dev->pdev->dev, "link layer initialization failed.\n");
dev->dev_state = MEI_DEV_DISABLED;
mutex_unlock(&dev->device_lock);
return -ENODEV;
}
EXPORT_SYMBOL_GPL(mei_start);
/**
* mei_reset - resets host and fw.
*
* @dev: the device structure
* @interrupts_enabled: if interrupt should be enabled after reset.
*/
void mei_reset(struct mei_device *dev, int interrupts_enabled)
{
bool unexpected;
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);
mei_hw_reset(dev, interrupts_enabled);
dev->hbm_state = MEI_HBM_IDLE;
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;
mei_cl_all_disconnect(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);
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));
}
/* we're already in reset, cancel the init timer */
dev->init_clients_timer = 0;
dev->me_clients_num = 0;
dev->rd_msg_hdr = 0;
dev->wd_pending = false;
if (unexpected)
dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n",
mei_dev_state_str(dev->dev_state));
if (!interrupts_enabled) {
dev_dbg(&dev->pdev->dev, "intr not enabled end of reset\n");
return;
}
mei_hw_start(dev);
dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n");
/* link is established * start sending messages. */
dev->dev_state = MEI_DEV_INIT_CLIENTS;
mei_hbm_start_req(dev);
/* wake up all readings so they can be interrupted */
mei_cl_all_read_wakeup(dev);
/* remove all waiting requests */
mei_cl_all_write_clear(dev);
}
EXPORT_SYMBOL_GPL(mei_reset);
void mei_stop(struct mei_device *dev)
{
dev_dbg(&dev->pdev->dev, "stopping the device.\n");
flush_scheduled_work();
mutex_lock(&dev->device_lock);
cancel_delayed_work(&dev->timer_work);
mei_wd_stop(dev);
mei_nfc_host_exit();
dev->dev_state = MEI_DEV_POWER_DOWN;
mei_reset(dev, 0);
mutex_unlock(&dev->device_lock);
mei_watchdog_unregister(dev);
}
EXPORT_SYMBOL_GPL(mei_stop);

View File

@@ -0,0 +1,725 @@
/*
*
* 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/export.h>
#include <linux/pci.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/jiffies.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hbm.h"
#include "hw-me.h"
#include "client.h"
/**
* mei_cl_complete_handler - processes completed operation for a client
*
* @cl: private data of the file object.
* @cb: callback block.
*/
static void mei_cl_complete_handler(struct mei_cl *cl, struct mei_cl_cb *cb)
{
if (cb->fop_type == MEI_FOP_WRITE) {
mei_io_cb_free(cb);
cb = NULL;
cl->writing_state = MEI_WRITE_COMPLETE;
if (waitqueue_active(&cl->tx_wait))
wake_up_interruptible(&cl->tx_wait);
} else if (cb->fop_type == MEI_FOP_READ &&
MEI_READING == cl->reading_state) {
cl->reading_state = MEI_READ_COMPLETE;
if (waitqueue_active(&cl->rx_wait))
wake_up_interruptible(&cl->rx_wait);
else
mei_cl_bus_rx_event(cl);
}
}
/**
* mei_irq_compl_handler - dispatch complete handelers
* for the completed callbacks
*
* @dev - mei device
* @compl_list - list of completed cbs
*/
void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *compl_list)
{
struct mei_cl_cb *cb, *next;
struct mei_cl *cl;
list_for_each_entry_safe(cb, next, &compl_list->list, list) {
cl = cb->cl;
list_del(&cb->list);
if (!cl)
continue;
dev_dbg(&dev->pdev->dev, "completing call back.\n");
if (cl == &dev->iamthif_cl)
mei_amthif_complete(dev, cb);
else
mei_cl_complete_handler(cl, cb);
}
}
EXPORT_SYMBOL_GPL(mei_irq_compl_handler);
/**
* mei_cl_hbm_equal - check if hbm is addressed to the client
*
* @cl: host client
* @mei_hdr: header of mei client message
*
* returns true if matches, false otherwise
*/
static inline int mei_cl_hbm_equal(struct mei_cl *cl,
struct mei_msg_hdr *mei_hdr)
{
return cl->host_client_id == mei_hdr->host_addr &&
cl->me_client_id == mei_hdr->me_addr;
}
/**
* mei_cl_is_reading - checks if the client
is the one to read this message
*
* @cl: mei client
* @mei_hdr: header of mei message
*
* returns true on match and false otherwise
*/
static bool mei_cl_is_reading(struct mei_cl *cl, struct mei_msg_hdr *mei_hdr)
{
return mei_cl_hbm_equal(cl, mei_hdr) &&
cl->state == MEI_FILE_CONNECTED &&
cl->reading_state != MEI_READ_COMPLETE;
}
/**
* mei_irq_read_client_message - process client message
*
* @dev: the device structure
* @mei_hdr: header of mei client message
* @complete_list: An instance of our list structure
*
* returns 0 on success, <0 on failure.
*/
static int mei_cl_irq_read_msg(struct mei_device *dev,
struct mei_msg_hdr *mei_hdr,
struct mei_cl_cb *complete_list)
{
struct mei_cl *cl;
struct mei_cl_cb *cb, *next;
unsigned char *buffer = NULL;
list_for_each_entry_safe(cb, next, &dev->read_list.list, list) {
cl = cb->cl;
if (!cl || !mei_cl_is_reading(cl, mei_hdr))
continue;
cl->reading_state = MEI_READING;
if (cb->response_buffer.size == 0 ||
cb->response_buffer.data == NULL) {
dev_err(&dev->pdev->dev, "response buffer is not allocated.\n");
list_del(&cb->list);
return -ENOMEM;
}
if (cb->response_buffer.size < mei_hdr->length + cb->buf_idx) {
dev_dbg(&dev->pdev->dev, "message overflow. size %d len %d idx %ld\n",
cb->response_buffer.size,
mei_hdr->length, cb->buf_idx);
buffer = krealloc(cb->response_buffer.data,
mei_hdr->length + cb->buf_idx,
GFP_KERNEL);
if (!buffer) {
dev_err(&dev->pdev->dev, "allocation failed.\n");
list_del(&cb->list);
return -ENOMEM;
}
cb->response_buffer.data = buffer;
cb->response_buffer.size =
mei_hdr->length + cb->buf_idx;
}
buffer = cb->response_buffer.data + cb->buf_idx;
mei_read_slots(dev, buffer, mei_hdr->length);
cb->buf_idx += mei_hdr->length;
if (mei_hdr->msg_complete) {
cl->status = 0;
list_del(&cb->list);
dev_dbg(&dev->pdev->dev, "completed read H cl = %d, ME cl = %d, length = %lu\n",
cl->host_client_id,
cl->me_client_id,
cb->buf_idx);
list_add_tail(&cb->list, &complete_list->list);
}
break;
}
dev_dbg(&dev->pdev->dev, "message read\n");
if (!buffer) {
mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length);
dev_dbg(&dev->pdev->dev, "discarding message " MEI_HDR_FMT "\n",
MEI_HDR_PRM(mei_hdr));
}
return 0;
}
/**
* _mei_irq_thread_close - processes close related operation.
*
* @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.
*/
static int _mei_irq_thread_close(struct mei_device *dev, s32 *slots,
struct mei_cl_cb *cb_pos,
struct mei_cl *cl,
struct mei_cl_cb *cmpl_list)
{
u32 msg_slots =
mei_data2slots(sizeof(struct hbm_client_connect_request));
if (*slots < msg_slots)
return -EMSGSIZE;
*slots -= msg_slots;
if (mei_hbm_cl_disconnect_req(dev, cl)) {
cl->status = 0;
cb_pos->buf_idx = 0;
list_move_tail(&cb_pos->list, &cmpl_list->list);
return -EIO;
}
cl->state = MEI_FILE_DISCONNECTING;
cl->status = 0;
cb_pos->buf_idx = 0;
list_move_tail(&cb_pos->list, &dev->ctrl_rd_list.list);
cl->timer_count = MEI_CONNECT_TIMEOUT;
return 0;
}
/**
* _mei_irq_thread_read - processes read related operation.
*
* @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.
*/
static int _mei_irq_thread_read(struct mei_device *dev, s32 *slots,
struct mei_cl_cb *cb_pos,
struct mei_cl *cl,
struct mei_cl_cb *cmpl_list)
{
u32 msg_slots = mei_data2slots(sizeof(struct hbm_flow_control));
if (*slots < msg_slots) {
/* return the cancel routine */
list_del(&cb_pos->list);
return -EMSGSIZE;
}
*slots -= msg_slots;
if (mei_hbm_cl_flow_control_req(dev, cl)) {
cl->status = -ENODEV;
cb_pos->buf_idx = 0;
list_move_tail(&cb_pos->list, &cmpl_list->list);
return -ENODEV;
}
list_move_tail(&cb_pos->list, &dev->read_list.list);
return 0;
}
/**
* _mei_irq_thread_ioctl - processes ioctl related operation.
*
* @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.
*/
static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots,
struct mei_cl_cb *cb_pos,
struct mei_cl *cl,
struct mei_cl_cb *cmpl_list)
{
u32 msg_slots =
mei_data2slots(sizeof(struct hbm_client_connect_request));
if (*slots < msg_slots) {
/* return the cancel routine */
list_del(&cb_pos->list);
return -EMSGSIZE;
}
*slots -= msg_slots;
cl->state = MEI_FILE_CONNECTING;
if (mei_hbm_cl_connect_req(dev, cl)) {
cl->status = -ENODEV;
cb_pos->buf_idx = 0;
list_del(&cb_pos->list);
return -ENODEV;
} else {
list_move_tail(&cb_pos->list, &dev->ctrl_rd_list.list);
cl->timer_count = MEI_CONNECT_TIMEOUT;
}
return 0;
}
/**
* mei_irq_thread_write_complete - write messages to device.
*
* @dev: the device structure.
* @slots: free slots.
* @cb: callback block.
* @cmpl_list: complete list.
*
* returns 0, OK; otherwise, error.
*/
static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots,
struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list)
{
struct mei_msg_hdr mei_hdr;
struct mei_cl *cl = cb->cl;
size_t len = cb->request_buffer.size - cb->buf_idx;
u32 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;
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);
mei_hdr.length = len;
mei_hdr.msg_complete = 0;
} else {
/* wait for next time the host buffer is empty */
return 0;
}
dev_dbg(&dev->pdev->dev, "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;
list_move_tail(&cb->list, &cmpl_list->list);
return -ENODEV;
}
cl->status = 0;
cb->buf_idx += mei_hdr.length;
if (mei_hdr.msg_complete) {
if (mei_cl_flow_ctrl_reduce(cl))
return -ENODEV;
list_move_tail(&cb->list, &dev->write_waiting_list.list);
}
return 0;
}
/**
* mei_irq_read_handler - bottom half read routine after ISR to
* handle the read processing.
*
* @dev: the device structure
* @cmpl_list: An instance of our list structure
* @slots: slots to read.
*
* returns 0 on success, <0 on failure.
*/
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;
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);
}
mei_hdr = (struct mei_msg_hdr *) &dev->rd_msg_hdr;
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");
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 (&cl_pos->link == &dev->file_list) {
dev_dbg(&dev->pdev->dev, "corrupted message header\n");
ret = -EBADMSG;
goto end;
}
}
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;
}
/* 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)) {
dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_iamthif_message.\n");
dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr));
ret = mei_amthif_irq_read_msg(dev, mei_hdr, cmpl_list);
if (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)
goto end;
}
/* reset the number of slots and header */
*slots = mei_count_full_read_slots(dev);
dev->rd_msg_hdr = 0;
if (*slots == -EOVERFLOW) {
/* overflow - reset */
dev_err(&dev->pdev->dev, "resetting due to slots overflow.\n");
/* set the event since message has been read */
ret = -ERANGE;
goto end;
}
end:
return ret;
}
EXPORT_SYMBOL_GPL(mei_irq_read_handler);
/**
* mei_irq_write_handler - dispatch write requests
* after irq received
*
* @dev: the device structure
* @cmpl_list: An instance of our list structure
*
* returns 0 on success, <0 on failure.
*/
int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
{
struct mei_cl *cl;
struct mei_cl_cb *pos = NULL, *next = NULL;
struct mei_cl_cb *list;
s32 slots;
int ret;
if (!mei_hbuf_is_ready(dev)) {
dev_dbg(&dev->pdev->dev, "host buffer is not empty.\n");
return 0;
}
slots = mei_hbuf_empty_slots(dev);
if (slots <= 0)
return -EMSGSIZE;
/* complete all waiting for write CB */
dev_dbg(&dev->pdev->dev, "complete all waiting for write cb.\n");
list = &dev->write_waiting_list;
list_for_each_entry_safe(pos, next, &list->list, list) {
cl = pos->cl;
if (cl == NULL)
continue;
cl->status = 0;
list_del(&pos->list);
if (MEI_WRITING == cl->writing_state &&
pos->fop_type == MEI_FOP_WRITE &&
cl != &dev->iamthif_cl) {
dev_dbg(&dev->pdev->dev, "MEI WRITE COMPLETE\n");
cl->writing_state = MEI_WRITE_COMPLETE;
list_add_tail(&pos->list, &cmpl_list->list);
}
if (cl == &dev->iamthif_cl) {
dev_dbg(&dev->pdev->dev, "check iamthif flow control.\n");
if (dev->iamthif_flow_control_pending) {
ret = mei_amthif_irq_read(dev, &slots);
if (ret)
return ret;
}
}
}
if (dev->wd_state == MEI_WD_STOPPING) {
dev->wd_state = MEI_WD_IDLE;
wake_up_interruptible(&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 (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;
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);
}
}
/* complete control write list CB */
dev_dbg(&dev->pdev->dev, "complete control write list cb.\n");
list_for_each_entry_safe(pos, next, &dev->ctrl_wr_list.list, list) {
cl = pos->cl;
if (!cl) {
list_del(&pos->list);
return -ENODEV;
}
switch (pos->fop_type) {
case MEI_FOP_CLOSE:
/* send disconnect message */
ret = _mei_irq_thread_close(dev, &slots, pos,
cl, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_READ:
/* send flow control message */
ret = _mei_irq_thread_read(dev, &slots, pos,
cl, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_IOCTL:
/* connect message */
if (mei_cl_is_other_connecting(cl))
continue;
ret = _mei_irq_thread_ioctl(dev, &slots, pos,
cl, cmpl_list);
if (ret)
return ret;
break;
default:
BUG();
}
}
/* complete write list CB */
dev_dbg(&dev->pdev->dev, "complete write list cb.\n");
list_for_each_entry_safe(pos, next, &dev->write_list.list, list) {
cl = pos->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(dev, &slots,
pos, cmpl_list);
else
ret = mei_irq_thread_write_complete(dev, &slots, pos,
cmpl_list);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(mei_irq_write_handler);
/**
* mei_timer - timer function.
*
* @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_cb *cb_pos = NULL;
struct mei_cl_cb *cb_next = NULL;
struct mei_device *dev = container_of(work,
struct mei_device, timer_work.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);
}
}
}
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);
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->iamthif_msg_buf_size = 0;
dev->iamthif_msg_buf_index = 0;
dev->iamthif_canceled = false;
dev->iamthif_ioctl = true;
dev->iamthif_state = MEI_IAMTHIF_IDLE;
dev->iamthif_timer = 0;
mei_io_cb_free(dev->iamthif_current_cb);
dev->iamthif_current_cb = NULL;
dev->iamthif_file_object = NULL;
mei_amthif_run_next_cmd(dev);
}
}
if (dev->iamthif_timer) {
timeout = dev->iamthif_timer +
mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER);
dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n",
dev->iamthif_timer);
dev_dbg(&dev->pdev->dev, "timeout = %ld\n", timeout);
dev_dbg(&dev->pdev->dev, "jiffies = %ld\n", jiffies);
if (time_after(jiffies, timeout)) {
/*
* User didn't read the AMTHI data on time (15sec)
* freeing AMTHI for other requests
*/
dev_dbg(&dev->pdev->dev, "freeing AMTHI for other requests\n");
list_for_each_entry_safe(cb_pos, cb_next,
&dev->amthif_rd_complete_list.list, list) {
cl_pos = cb_pos->file_object->private_data;
/* Finding the AMTHI entry. */
if (cl_pos == &dev->iamthif_cl)
list_del(&cb_pos->list);
}
mei_io_cb_free(dev->iamthif_current_cb);
dev->iamthif_current_cb = NULL;
dev->iamthif_file_object->private_data = NULL;
dev->iamthif_file_object = NULL;
dev->iamthif_timer = 0;
mei_amthif_run_next_cmd(dev);
}
}
out:
schedule_delayed_work(&dev->timer_work, 2 * HZ);
mutex_unlock(&dev->device_lock);
}

740
drivers/misc/mei/main.c Normal file
View File

@@ -0,0 +1,740 @@
/*
*
* 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.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#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>
#include <linux/uuid.h>
#include <linux/compat.h>
#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hw-me.h"
#include "client.h"
/**
* mei_open - the open function
*
* @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)
{
struct miscdevice *misc = file->private_data;
struct pci_dev *pdev;
struct mei_cl *cl;
struct mei_device *dev;
int err;
err = -ENODEV;
if (!misc->parent)
goto out;
pdev = container_of(misc->parent, struct pci_dev, dev);
dev = pci_get_drvdata(pdev);
if (!dev)
goto out;
mutex_lock(&dev->device_lock);
err = -ENOMEM;
cl = mei_cl_allocate(dev);
if (!cl)
goto out_unlock;
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;
}
err = mei_cl_link(cl, MEI_HOST_CLIENT_ID_ANY);
if (err)
goto out_unlock;
file->private_data = cl;
mutex_unlock(&dev->device_lock);
return nonseekable_open(inode, file);
out_unlock:
mutex_unlock(&dev->device_lock);
kfree(cl);
out:
return err;
}
/**
* mei_release - the release function
*
* @inode: pointer to inode structure
* @file: pointer to file structure
*
* returns 0 on success, <0 on error
*/
static int mei_release(struct inode *inode, struct file *file)
{
struct mei_cl *cl = file->private_data;
struct mei_cl_cb *cb;
struct mei_device *dev;
int rets = 0;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
mutex_lock(&dev->device_lock);
if (cl == &dev->iamthif_cl) {
rets = mei_amthif_release(dev, file);
goto out;
}
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);
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);
if (dev->open_handle_count > 0) {
clear_bit(cl->host_client_id, dev->host_clients_map);
dev->open_handle_count--;
}
mei_cl_unlink(cl);
/* free read cb */
cb = NULL;
if (cl->read_cb) {
cb = mei_cl_find_read_cb(cl);
/* Remove entry from read list */
if (cb)
list_del(&cb->list);
cb = cl->read_cb;
cl->read_cb = NULL;
}
file->private_data = NULL;
if (cb) {
mei_io_cb_free(cb);
cb = NULL;
}
kfree(cl);
out:
mutex_unlock(&dev->device_lock);
return rets;
}
/**
* mei_read - the read function.
*
* @file: pointer to file structure
* @ubuf: pointer to user buffer
* @length: buffer length
* @offset: data offset in buffer
*
* returns >=0 data length on success , <0 on error
*/
static ssize_t mei_read(struct file *file, char __user *ubuf,
size_t length, loff_t *offset)
{
struct mei_cl *cl = file->private_data;
struct mei_cl_cb *cb_pos = NULL;
struct mei_cl_cb *cb = NULL;
struct mei_device *dev;
int i;
int rets;
int err;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
mutex_lock(&dev->device_lock);
if (dev->dev_state != MEI_DEV_ENABLED) {
rets = -ENODEV;
goto out;
}
if ((cl->sm_state & MEI_WD_STATE_INDEPENDENCE_MSG_SENT) == 0) {
/* Do not allow to read watchdog client */
i = mei_me_cl_by_uuid(dev, &mei_wd_guid);
if (i >= 0) {
struct mei_me_client *me_client = &dev->me_clients[i];
if (cl->me_client_id == me_client->client_id) {
rets = -EBADF;
goto out;
}
}
} else {
cl->sm_state &= ~MEI_WD_STATE_INDEPENDENCE_MSG_SENT;
}
if (cl == &dev->iamthif_cl) {
rets = mei_amthif_read(dev, file, ubuf, length, offset);
goto out;
}
if (cl->read_cb && cl->read_cb->buf_idx > *offset) {
cb = cl->read_cb;
goto copy_buffer;
} else if (cl->read_cb && cl->read_cb->buf_idx > 0 &&
cl->read_cb->buf_idx <= *offset) {
cb = cl->read_cb;
rets = 0;
goto free;
} else if ((!cl->read_cb || !cl->read_cb->buf_idx) && *offset > 0) {
/*Offset needs to be cleaned for contiguous reads*/
*offset = 0;
rets = 0;
goto out;
}
err = mei_cl_read_start(cl, length);
if (err && err != -EBUSY) {
dev_dbg(&dev->pdev->dev,
"mei start read failure with status = %d\n", err);
rets = err;
goto out;
}
if (MEI_READ_COMPLETE != cl->reading_state &&
!waitqueue_active(&cl->rx_wait)) {
if (file->f_flags & O_NONBLOCK) {
rets = -EAGAIN;
goto out;
}
mutex_unlock(&dev->device_lock);
if (wait_event_interruptible(cl->rx_wait,
MEI_READ_COMPLETE == cl->reading_state ||
mei_cl_is_transitioning(cl))) {
if (signal_pending(current))
return -EINTR;
return -ERESTARTSYS;
}
mutex_lock(&dev->device_lock);
if (mei_cl_is_transitioning(cl)) {
rets = -EBUSY;
goto out;
}
}
cb = cl->read_cb;
if (!cb) {
rets = -ENODEV;
goto out;
}
if (cl->reading_state != MEI_READ_COMPLETE) {
rets = 0;
goto out;
}
/* now copy the data to user space */
copy_buffer:
dev_dbg(&dev->pdev->dev, "buf.size = %d buf.idx= %ld\n",
cb->response_buffer.size, cb->buf_idx);
if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) {
rets = -EMSGSIZE;
goto free;
}
/* length is being truncated to PAGE_SIZE,
* however buf_idx may point beyond that */
length = min_t(size_t, length, cb->buf_idx - *offset);
if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) {
rets = -EFAULT;
goto free;
}
rets = length;
*offset += length;
if ((unsigned long)*offset < cb->buf_idx)
goto out;
free:
cb_pos = mei_cl_find_read_cb(cl);
/* Remove entry from read list */
if (cb_pos)
list_del(&cb_pos->list);
mei_io_cb_free(cb);
cl->reading_state = MEI_IDLE;
cl->read_cb = NULL;
out:
dev_dbg(&dev->pdev->dev, "end mei read rets= %d\n", rets);
mutex_unlock(&dev->device_lock);
return rets;
}
/**
* mei_write - the write function.
*
* @file: pointer to file structure
* @ubuf: pointer to user buffer
* @length: buffer length
* @offset: data offset in buffer
*
* returns >=0 data length on success , <0 on error
*/
static ssize_t mei_write(struct file *file, const char __user *ubuf,
size_t length, loff_t *offset)
{
struct mei_cl *cl = file->private_data;
struct mei_cl_cb *write_cb = NULL;
struct mei_device *dev;
unsigned long timeout = 0;
int rets;
int id;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
mutex_lock(&dev->device_lock);
if (dev->dev_state != MEI_DEV_ENABLED) {
rets = -ENODEV;
goto out;
}
id = mei_me_cl_by_id(dev, cl->me_client_id);
if (id < 0) {
rets = -ENODEV;
goto out;
}
if (length > dev->me_clients[id].props.max_msg_length || length <= 0) {
rets = -EMSGSIZE;
goto out;
}
if (cl->state != MEI_FILE_CONNECTED) {
dev_err(&dev->pdev->dev, "host client = %d, is not connected to ME client = %d",
cl->host_client_id, cl->me_client_id);
rets = -ENODEV;
goto out;
}
if (cl == &dev->iamthif_cl) {
write_cb = mei_amthif_find_read_list_entry(dev, file);
if (write_cb) {
timeout = write_cb->read_time +
mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER);
if (time_after(jiffies, timeout) ||
cl->reading_state == MEI_READ_COMPLETE) {
*offset = 0;
list_del(&write_cb->list);
mei_io_cb_free(write_cb);
write_cb = NULL;
}
}
}
/* free entry used in read */
if (cl->reading_state == MEI_READ_COMPLETE) {
*offset = 0;
write_cb = mei_cl_find_read_cb(cl);
if (write_cb) {
list_del(&write_cb->list);
mei_io_cb_free(write_cb);
write_cb = NULL;
cl->reading_state = MEI_IDLE;
cl->read_cb = NULL;
}
} else if (cl->reading_state == MEI_IDLE)
*offset = 0;
write_cb = mei_io_cb_init(cl, file);
if (!write_cb) {
dev_err(&dev->pdev->dev, "write cb allocation failed\n");
rets = -ENOMEM;
goto out;
}
rets = mei_io_cb_alloc_req_buf(write_cb, length);
if (rets)
goto out;
rets = copy_from_user(write_cb->request_buffer.data, ubuf, length);
if (rets)
goto out;
cl->sm_state = 0;
if (length == 4 &&
((memcmp(mei_wd_state_independence_msg[0],
write_cb->request_buffer.data, 4) == 0) ||
(memcmp(mei_wd_state_independence_msg[1],
write_cb->request_buffer.data, 4) == 0) ||
(memcmp(mei_wd_state_independence_msg[2],
write_cb->request_buffer.data, 4) == 0)))
cl->sm_state |= MEI_WD_STATE_INDEPENDENCE_MSG_SENT;
if (cl == &dev->iamthif_cl) {
rets = mei_amthif_write(dev, write_cb);
if (rets) {
dev_err(&dev->pdev->dev,
"amthif write failed with status = %d\n", rets);
goto out;
}
mutex_unlock(&dev->device_lock);
return length;
}
rets = mei_cl_write(cl, write_cb, false);
out:
mutex_unlock(&dev->device_lock);
if (rets < 0)
mei_io_cb_free(write_cb);
return rets;
}
/**
* mei_ioctl_connect_client - the connect to fw client IOCTL function
*
* @dev: the device structure
* @data: IOCTL connect data, input and output parameters
* @file: private data of the file object
*
* Locking: called under "dev->device_lock" lock
*
* returns 0 on success, <0 on failure.
*/
static int mei_ioctl_connect_client(struct file *file,
struct mei_connect_client_data *data)
{
struct mei_device *dev;
struct mei_client *client;
struct mei_cl *cl;
int i;
int rets;
cl = file->private_data;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
if (dev->dev_state != MEI_DEV_ENABLED) {
rets = -ENODEV;
goto end;
}
if (cl->state != MEI_FILE_INITIALIZING &&
cl->state != MEI_FILE_DISCONNECTED) {
rets = -EBUSY;
goto end;
}
/* find ME client we're trying to connect to */
i = mei_me_cl_by_uuid(dev, &data->in_client_uuid);
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;
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);
dev_dbg(&dev->pdev->dev, "FW Client - Protocol Version = %d\n",
dev->me_clients[i].props.protocol_version);
dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n",
dev->me_clients[i].props.max_msg_length);
/* if we're connecting to amthif client then we will use the
* existing connection
*/
if (uuid_le_cmp(data->in_client_uuid, mei_amthif_guid) == 0) {
dev_dbg(&dev->pdev->dev, "FW Client is amthi\n");
if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) {
rets = -ENODEV;
goto end;
}
clear_bit(cl->host_client_id, dev->host_clients_map);
mei_cl_unlink(cl);
kfree(cl);
cl = NULL;
file->private_data = &dev->iamthif_cl;
client = &data->out_client_properties;
client->max_msg_length =
dev->me_clients[i].props.max_msg_length;
client->protocol_version =
dev->me_clients[i].props.protocol_version;
rets = dev->iamthif_cl.status;
goto end;
}
/* prepare the output buffer */
client = &data->out_client_properties;
client->max_msg_length = dev->me_clients[i].props.max_msg_length;
client->protocol_version = dev->me_clients[i].props.protocol_version;
dev_dbg(&dev->pdev->dev, "Can connect?\n");
rets = mei_cl_connect(cl, file);
end:
return rets;
}
/**
* mei_ioctl - the IOCTL function
*
* @file: pointer to file structure
* @cmd: ioctl command
* @data: pointer to mei message structure
*
* returns 0 on success , <0 on error
*/
static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
{
struct mei_device *dev;
struct mei_cl *cl = file->private_data;
struct mei_connect_client_data *connect_data = NULL;
int rets;
if (cmd != IOCTL_MEI_CONNECT_CLIENT)
return -EINVAL;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
dev = cl->dev;
dev_dbg(&dev->pdev->dev, "IOCTL cmd = 0x%x", cmd);
mutex_lock(&dev->device_lock);
if (dev->dev_state != MEI_DEV_ENABLED) {
rets = -ENODEV;
goto out;
}
dev_dbg(&dev->pdev->dev, ": IOCTL_MEI_CONNECT_CLIENT.\n");
connect_data = kzalloc(sizeof(struct mei_connect_client_data),
GFP_KERNEL);
if (!connect_data) {
rets = -ENOMEM;
goto out;
}
dev_dbg(&dev->pdev->dev, "copy connect data from user\n");
if (copy_from_user(connect_data, (char __user *)data,
sizeof(struct mei_connect_client_data))) {
dev_dbg(&dev->pdev->dev, "failed to copy data from userland\n");
rets = -EFAULT;
goto out;
}
rets = mei_ioctl_connect_client(file, connect_data);
/* if all is ok, copying the data back to user. */
if (rets)
goto out;
dev_dbg(&dev->pdev->dev, "copy connect data to user\n");
if (copy_to_user((char __user *)data, connect_data,
sizeof(struct mei_connect_client_data))) {
dev_dbg(&dev->pdev->dev, "failed to copy data to userland\n");
rets = -EFAULT;
goto out;
}
out:
kfree(connect_data);
mutex_unlock(&dev->device_lock);
return rets;
}
/**
* mei_compat_ioctl - the compat IOCTL function
*
* @file: pointer to file structure
* @cmd: ioctl command
* @data: pointer to mei message structure
*
* returns 0 on success , <0 on error
*/
#ifdef CONFIG_COMPAT
static long mei_compat_ioctl(struct file *file,
unsigned int cmd, unsigned long data)
{
return mei_ioctl(file, cmd, (unsigned long)compat_ptr(data));
}
#endif
/**
* mei_poll - the poll function
*
* @file: pointer to file structure
* @wait: pointer to poll_table structure
*
* returns poll mask
*/
static unsigned int mei_poll(struct file *file, poll_table *wait)
{
struct mei_cl *cl = file->private_data;
struct mei_device *dev;
unsigned int mask = 0;
if (WARN_ON(!cl || !cl->dev))
return mask;
dev = cl->dev;
mutex_lock(&dev->device_lock);
if (dev->dev_state != MEI_DEV_ENABLED)
goto out;
if (cl == &dev->iamthif_cl) {
mask = mei_amthif_poll(dev, file, wait);
goto out;
}
mutex_unlock(&dev->device_lock);
poll_wait(file, &cl->tx_wait, wait);
mutex_lock(&dev->device_lock);
if (MEI_WRITE_COMPLETE == cl->writing_state)
mask |= (POLLIN | POLLRDNORM);
out:
mutex_unlock(&dev->device_lock);
return mask;
}
/*
* file operations structure will be used for mei char device.
*/
static const struct file_operations mei_fops = {
.owner = THIS_MODULE,
.read = mei_read,
.unlocked_ioctl = mei_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = mei_compat_ioctl,
#endif
.open = mei_open,
.release = mei_release,
.write = mei_write,
.poll = mei_poll,
.llseek = no_llseek
};
/*
* Misc Device Struct
*/
static struct miscdevice mei_misc_device = {
.name = "mei",
.fops = &mei_fops,
.minor = MISC_DYNAMIC_MINOR,
};
int mei_register(struct mei_device *dev)
{
int ret;
mei_misc_device.parent = &dev->pdev->dev;
ret = misc_register(&mei_misc_device);
if (ret)
return ret;
if (mei_dbgfs_register(dev, mei_misc_device.name))
dev_err(&dev->pdev->dev, "cannot register debugfs\n");
return 0;
}
EXPORT_SYMBOL_GPL(mei_register);
void mei_deregister(struct mei_device *dev)
{
mei_dbgfs_deregister(dev);
misc_deregister(&mei_misc_device);
mei_misc_device.parent = NULL;
}
EXPORT_SYMBOL_GPL(mei_deregister);
static int __init mei_init(void)
{
return mei_cl_bus_init();
}
static void __exit mei_exit(void)
{
mei_cl_bus_exit();
}
module_init(mei_init);
module_exit(mei_exit);
MODULE_AUTHOR("Intel Corporation");
MODULE_DESCRIPTION("Intel(R) Management Engine Interface");
MODULE_LICENSE("GPL v2");

648
drivers/misc/mei/mei_dev.h Normal file
View File

@@ -0,0 +1,648 @@
/*
*
* 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.
*
*/
#ifndef _MEI_DEV_H_
#define _MEI_DEV_H_
#include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/poll.h>
#include <linux/mei.h>
#include <linux/mei_cl_bus.h>
#include "hw.h"
#include "hw-me-regs.h"
#include "hbm.h"
/*
* watch dog definition
*/
#define MEI_WD_HDR_SIZE 4
#define MEI_WD_STOP_MSG_SIZE MEI_WD_HDR_SIZE
#define MEI_WD_START_MSG_SIZE (MEI_WD_HDR_SIZE + 16)
#define MEI_WD_DEFAULT_TIMEOUT 120 /* seconds */
#define MEI_WD_MIN_TIMEOUT 120 /* seconds */
#define MEI_WD_MAX_TIMEOUT 65535 /* seconds */
#define MEI_WD_STOP_TIMEOUT 10 /* msecs */
#define MEI_WD_STATE_INDEPENDENCE_MSG_SENT (1 << 0)
#define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32))
/*
* AMTHI Client UUID
*/
extern const uuid_le mei_amthif_guid;
/*
* Watchdog Client UUID
*/
extern const uuid_le mei_wd_guid;
/*
* Watchdog independence state message
*/
extern const u8 mei_wd_state_independence_msg[3][4];
/*
* Number of Maximum MEI Clients
*/
#define MEI_CLIENTS_MAX 256
/*
* 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
*/
#define MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 1)
/*
* Internal Clients Number
*/
#define MEI_HOST_CLIENT_ID_ANY (-1)
#define MEI_HBM_HOST_CLIENT_ID 0 /* not used, just for documentation */
#define MEI_WD_HOST_CLIENT_ID 1
#define MEI_IAMTHIF_HOST_CLIENT_ID 2
/* File state */
enum file_state {
MEI_FILE_INITIALIZING = 0,
MEI_FILE_CONNECTING,
MEI_FILE_CONNECTED,
MEI_FILE_DISCONNECTING,
MEI_FILE_DISCONNECTED
};
/* MEI device states */
enum mei_dev_state {
MEI_DEV_INITIALIZING = 0,
MEI_DEV_INIT_CLIENTS,
MEI_DEV_ENABLED,
MEI_DEV_RESETTING,
MEI_DEV_DISABLED,
MEI_DEV_POWER_DOWN,
MEI_DEV_POWER_UP
};
const char *mei_dev_state_str(int state);
enum iamthif_states {
MEI_IAMTHIF_IDLE,
MEI_IAMTHIF_WRITING,
MEI_IAMTHIF_FLOW_CONTROL,
MEI_IAMTHIF_READING,
MEI_IAMTHIF_READ_COMPLETE
};
enum mei_file_transaction_states {
MEI_IDLE,
MEI_WRITING,
MEI_WRITE_COMPLETE,
MEI_FLOW_CONTROL,
MEI_READING,
MEI_READ_COMPLETE
};
enum mei_wd_states {
MEI_WD_IDLE,
MEI_WD_RUNNING,
MEI_WD_STOPPING,
};
/**
* 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
*/
enum mei_cb_file_ops {
MEI_FOP_READ = 0,
MEI_FOP_WRITE,
MEI_FOP_IOCTL,
MEI_FOP_OPEN,
MEI_FOP_CLOSE
};
/*
* Intel MEI message data struct
*/
struct mei_msg_data {
u32 size;
unsigned char *data;
};
/**
* struct mei_me_client - representation of me (fw) client
*
* @props - client properties
* @client_id - me client id
* @mei_flow_ctrl_creds - flow control credits
*/
struct mei_me_client {
struct mei_client_properties props;
u8 client_id;
u8 mei_flow_ctrl_creds;
};
struct mei_cl;
/**
* struct mei_cl_cb - file operation callback structure
*
* @cl - file client who is running this operation
* @fop_type - file operation type
*/
struct mei_cl_cb {
struct list_head list;
struct mei_cl *cl;
enum mei_cb_file_ops fop_type;
struct mei_msg_data request_buffer;
struct mei_msg_data response_buffer;
unsigned long buf_idx;
unsigned long read_time;
struct file *file_object;
};
/* MEI client instance carried as file->pirvate_data*/
struct mei_cl {
struct list_head link;
struct mei_device *dev;
enum file_state state;
wait_queue_head_t tx_wait;
wait_queue_head_t rx_wait;
wait_queue_head_t wait;
int status;
/* ID of client connected */
u8 host_client_id;
u8 me_client_id;
u8 mei_flow_ctrl_creds;
u8 timer_count;
enum mei_file_transaction_states reading_state;
enum mei_file_transaction_states writing_state;
int sm_state;
struct mei_cl_cb *read_cb;
/* MEI CL bus data */
struct mei_cl_device *device;
struct list_head device_link;
uuid_le device_uuid;
};
/** struct mei_hw_ops
*
* @host_is_ready - query for host readiness
* @hw_is_ready - query if hw is ready
* @hw_reset - reset hw
* @hw_start - start hw after reset
* @hw_config - configure hw
* @intr_clear - clear pending interrupts
* @intr_enable - enable interrupts
* @intr_disable - disable interrupts
* @hbuf_free_slots - query for write buffer empty slots
* @hbuf_is_ready - query if write buffer is empty
* @hbuf_max_len - query for write buffer max len
* @write - write a message to FW
* @rdbuf_full_slots - query how many slots are filled
* @read_hdr - get first 4 bytes (header)
* @read - read a buffer from the FW
*/
struct mei_hw_ops {
bool (*host_is_ready) (struct mei_device *dev);
bool (*hw_is_ready) (struct mei_device *dev);
void (*hw_reset) (struct mei_device *dev, bool enable);
int (*hw_start) (struct mei_device *dev);
void (*hw_config) (struct mei_device *dev);
void (*intr_clear) (struct mei_device *dev);
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,
unsigned char *buf);
int (*rdbuf_full_slots)(struct mei_device *dev);
u32 (*read_hdr)(const struct mei_device *dev);
int (*read) (struct mei_device *dev,
unsigned char *buf, unsigned long len);
};
/* MEI bus API*/
/**
* struct mei_cl_ops - MEI CL device ops
* This structure allows ME host clients to implement technology
* specific operations.
*
* @enable: Enable an MEI CL device. Some devices require specific
* HECI commands to initialize completely.
* @disable: Disable an MEI CL device.
* @send: Tx hook for the device. This allows ME host clients to trap
* the device driver buffers before actually physically
* pushing it to the ME.
* @recv: Rx hook for the device. This allows ME host clients to trap the
* ME buffers before forwarding them to the device driver.
*/
struct mei_cl_ops {
int (*enable)(struct mei_cl_device *device);
int (*disable)(struct mei_cl_device *device);
int (*send)(struct mei_cl_device *device, u8 *buf, size_t length);
int (*recv)(struct mei_cl_device *device, u8 *buf, size_t length);
};
struct mei_cl_device *mei_cl_add_device(struct mei_device *dev,
uuid_le uuid, char *name,
struct mei_cl_ops *ops);
void mei_cl_remove_device(struct mei_cl_device *device);
int __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length);
int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length);
int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length);
void mei_cl_bus_rx_event(struct mei_cl *cl);
int mei_cl_bus_init(void);
void mei_cl_bus_exit(void);
/**
* struct mei_cl_device - MEI device handle
* An mei_cl_device pointer is returned from mei_add_device()
* and links MEI bus clients to their actual ME host client pointer.
* Drivers for MEI devices will get an mei_cl_device pointer
* when being probed and shall use it for doing ME bus I/O.
*
* @dev: linux driver model device pointer
* @uuid: me client uuid
* @cl: mei client
* @ops: ME transport ops
* @event_cb: Drivers register this callback to get asynchronous ME
* events (e.g. Rx buffer pending) notifications.
* @events: Events bitmask sent to the driver.
* @priv_data: client private data
*/
struct mei_cl_device {
struct device dev;
struct mei_cl *cl;
const struct mei_cl_ops *ops;
struct work_struct event_work;
mei_cl_event_cb_t event_cb;
void *event_context;
unsigned long events;
void *priv_data;
};
/**
* struct mei_device - MEI private device struct
* @hbm_state - state of host bus message protocol
* @mem_addr - mem mapped base register address
* @hbuf_depth - depth of hardware host/write buffer is slots
* @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)
*/
struct mei_device {
struct pci_dev *pdev; /* pointer to pci device struct */
/*
* lists of queues
*/
/* array of pointers to aio lists */
struct mei_cl_cb read_list; /* driver read queue */
struct mei_cl_cb write_list; /* driver write queue */
struct mei_cl_cb write_waiting_list; /* write waiting queue */
struct mei_cl_cb ctrl_wr_list; /* managed write IOCTL list */
struct mei_cl_cb ctrl_rd_list; /* managed read IOCTL list */
/*
* list of files
*/
struct list_head file_list;
long open_handle_count;
/*
* lock for the device
*/
struct mutex device_lock; /* device lock */
struct delayed_work timer_work; /* MEI timer delayed work (timeouts) */
bool recvd_hw_ready;
/*
* waiting queue for receive message from FW
*/
wait_queue_head_t wait_hw_ready;
wait_queue_head_t wait_recvd_msg;
wait_queue_head_t wait_stop_wd;
/*
* mei device states
*/
enum mei_dev_state dev_state;
enum mei_hbm_state hbm_state;
u16 init_clients_timer;
unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */
u32 rd_msg_hdr;
/* write buffer */
u8 hbuf_depth;
bool hbuf_is_ready;
/* used for control messages */
struct {
struct mei_msg_hdr hdr;
unsigned char data[128];
} wr_msg;
struct {
struct mei_msg_hdr hdr;
unsigned char data[4]; /* All HBM messages are 4 bytes */
} wr_ext_msg; /* for control responses */
struct hbm_version version;
struct mei_me_client *me_clients; /* Note: memory has to be allocated */
DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX);
DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX);
unsigned long me_clients_num;
unsigned long me_client_presentation_num;
unsigned long me_client_index;
struct mei_cl wd_cl;
enum mei_wd_states wd_state;
bool wd_pending;
u16 wd_timeout;
unsigned char wd_data[MEI_WD_START_MSG_SIZE];
/* amthif list for cmd waiting */
struct mei_cl_cb amthif_cmd_list;
/* driver managed amthif list for reading completed amthif cmd data */
struct mei_cl_cb amthif_rd_complete_list;
struct file *iamthif_file_object;
struct mei_cl iamthif_cl;
struct mei_cl_cb *iamthif_current_cb;
int iamthif_mtu;
unsigned long iamthif_timer;
u32 iamthif_stall_timer;
unsigned char *iamthif_msg_buf; /* Note: memory has to be allocated */
u32 iamthif_msg_buf_size;
u32 iamthif_msg_buf_index;
enum iamthif_states iamthif_state;
bool iamthif_flow_control_pending;
bool iamthif_ioctl;
bool iamthif_canceled;
struct work_struct init_work;
/* List of bus devices */
struct list_head device_list;
#if IS_ENABLED(CONFIG_DEBUG_FS)
struct dentry *dbgfs_dir;
#endif /* CONFIG_DEBUG_FS */
const struct mei_hw_ops *ops;
char hw[0] __aligned(sizeof(void *));
};
static inline unsigned long mei_secs_to_jiffies(unsigned long sec)
{
return msecs_to_jiffies(sec * MSEC_PER_SEC);
}
/**
* mei_data2slots - get slots - number of (dwords) from a message length
* + size of the mei header
* @length - size of the messages in bytes
* returns - number of slots
*/
static inline u32 mei_data2slots(size_t length)
{
return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4);
}
/*
* mei init function prototypes
*/
void mei_device_init(struct mei_device *dev);
void mei_reset(struct mei_device *dev, int interrupts);
int mei_start(struct mei_device *dev);
void mei_stop(struct mei_device *dev);
/*
* MEI interrupt functions prototype
*/
void mei_timer(struct work_struct *work);
int mei_irq_read_handler(struct mei_device *dev,
struct mei_cl_cb *cmpl_list, s32 *slots);
int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list);
void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list);
/*
* AMTHIF - AMT Host Interface Functions
*/
void mei_amthif_reset_params(struct mei_device *dev);
int mei_amthif_host_init(struct mei_device *dev);
int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *priv_cb);
int mei_amthif_read(struct mei_device *dev, struct file *file,
char __user *ubuf, size_t length, loff_t *offset);
unsigned int mei_amthif_poll(struct mei_device *dev,
struct file *file, poll_table *wait);
int mei_amthif_release(struct mei_device *dev, struct file *file);
struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev,
struct file *file);
void mei_amthif_run_next_cmd(struct mei_device *dev);
int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots,
struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list);
void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb);
int mei_amthif_irq_read_msg(struct mei_device *dev,
struct mei_msg_hdr *mei_hdr,
struct mei_cl_cb *complete_list);
int mei_amthif_irq_read(struct mei_device *dev, s32 *slots);
/*
* NFC functions
*/
int mei_nfc_host_init(struct mei_device *dev);
void mei_nfc_host_exit(void);
/*
* NFC Client UUID
*/
extern const uuid_le mei_nfc_guid;
int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots,
struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list);
void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb);
int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list,
struct mei_device *dev, struct mei_msg_hdr *mei_hdr);
int mei_amthif_irq_read(struct mei_device *dev, s32 *slots);
int mei_wd_send(struct mei_device *dev);
int mei_wd_stop(struct mei_device *dev);
int mei_wd_host_init(struct mei_device *dev);
/*
* mei_watchdog_register - Registering watchdog interface
* once we got connection to the WD Client
* @dev - mei device
*/
void mei_watchdog_register(struct mei_device *dev);
/*
* mei_watchdog_unregister - Unregistering watchdog interface
* @dev - mei device
*/
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 void mei_hw_reset(struct mei_device *dev, bool enable)
{
dev->ops->hw_reset(dev, enable);
}
static inline void mei_hw_start(struct mei_device *dev)
{
dev->ops->hw_start(dev);
}
static inline void mei_clear_interrupts(struct mei_device *dev)
{
dev->ops->intr_clear(dev);
}
static inline void mei_enable_interrupts(struct mei_device *dev)
{
dev->ops->intr_enable(dev);
}
static inline void mei_disable_interrupts(struct mei_device *dev)
{
dev->ops->intr_disable(dev);
}
static inline bool mei_host_is_ready(struct mei_device *dev)
{
return dev->ops->host_is_ready(dev);
}
static inline bool mei_hw_is_ready(struct mei_device *dev)
{
return dev->ops->hw_is_ready(dev);
}
static inline bool mei_hbuf_is_ready(struct mei_device *dev)
{
return dev->ops->hbuf_is_ready(dev);
}
static inline int mei_hbuf_empty_slots(struct mei_device *dev)
{
return dev->ops->hbuf_free_slots(dev);
}
static inline size_t mei_hbuf_max_len(const struct mei_device *dev)
{
return dev->ops->hbuf_max_len(dev);
}
static inline int mei_write_message(struct mei_device *dev,
struct mei_msg_hdr *hdr,
unsigned char *buf)
{
return dev->ops->write(dev, hdr, buf);
}
static inline u32 mei_read_hdr(const struct mei_device *dev)
{
return dev->ops->read_hdr(dev);
}
static inline void mei_read_slots(struct mei_device *dev,
unsigned char *buf, unsigned long len)
{
dev->ops->read(dev, buf, len);
}
static inline int mei_count_full_read_slots(struct mei_device *dev)
{
return dev->ops->rdbuf_full_slots(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);
#else
static inline int mei_dbgfs_register(struct mei_device *dev, const char *name)
{
return 0;
}
static inline void mei_dbgfs_deregister(struct mei_device *dev) {}
#endif /* CONFIG_DEBUG_FS */
int mei_register(struct mei_device *dev);
void mei_deregister(struct mei_device *dev);
#define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d comp=%1d"
#define MEI_HDR_PRM(hdr) \
(hdr)->host_addr, (hdr)->me_addr, \
(hdr)->length, (hdr)->msg_complete
#endif

556
drivers/misc/mei/nfc.c Normal file
View File

@@ -0,0 +1,556 @@
/*
*
* Intel Management Engine Interface (Intel MEI) Linux driver
* Copyright (c) 2003-2013, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/mei_cl_bus.h>
#include "mei_dev.h"
#include "client.h"
struct mei_nfc_cmd {
u8 command;
u8 status;
u16 req_id;
u32 reserved;
u16 data_size;
u8 sub_command;
u8 data[];
} __packed;
struct mei_nfc_reply {
u8 command;
u8 status;
u16 req_id;
u32 reserved;
u16 data_size;
u8 sub_command;
u8 reply_status;
u8 data[];
} __packed;
struct mei_nfc_if_version {
u8 radio_version_sw[3];
u8 reserved[3];
u8 radio_version_hw[3];
u8 i2c_addr;
u8 fw_ivn;
u8 vendor_id;
u8 radio_type;
} __packed;
struct mei_nfc_connect {
u8 fw_ivn;
u8 vendor_id;
} __packed;
struct mei_nfc_connect_resp {
u8 fw_ivn;
u8 vendor_id;
u16 me_major;
u16 me_minor;
u16 me_hotfix;
u16 me_build;
} __packed;
struct mei_nfc_hci_hdr {
u8 cmd;
u8 status;
u16 req_id;
u32 reserved;
u16 data_size;
} __packed;
#define MEI_NFC_CMD_MAINTENANCE 0x00
#define MEI_NFC_CMD_HCI_SEND 0x01
#define MEI_NFC_CMD_HCI_RECV 0x02
#define MEI_NFC_SUBCMD_CONNECT 0x00
#define MEI_NFC_SUBCMD_IF_VERSION 0x01
#define MEI_NFC_HEADER_SIZE 10
/** mei_nfc_dev - NFC mei device
*
* @cl: NFC host client
* @cl_info: NFC info host client
* @init_work: perform connection to the info client
* @fw_ivn: NFC Intervace Version Number
* @vendor_id: NFC manufacturer ID
* @radio_type: NFC radio type
*/
struct mei_nfc_dev {
struct mei_cl *cl;
struct mei_cl *cl_info;
struct work_struct init_work;
wait_queue_head_t send_wq;
u8 fw_ivn;
u8 vendor_id;
u8 radio_type;
char *bus_name;
u16 req_id;
u16 recv_req_id;
};
static struct mei_nfc_dev nfc_dev;
/* UUIDs for NFC F/W clients */
const uuid_le mei_nfc_guid = UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50,
0x94, 0xd4, 0x50, 0x26,
0x67, 0x23, 0x77, 0x5c);
static const uuid_le mei_nfc_info_guid = UUID_LE(0xd2de1625, 0x382d, 0x417d,
0x48, 0xa4, 0xef, 0xab,
0xba, 0x8a, 0x12, 0x06);
/* Vendors */
#define MEI_NFC_VENDOR_INSIDE 0x00
#define MEI_NFC_VENDOR_NXP 0x01
/* Radio types */
#define MEI_NFC_VENDOR_INSIDE_UREAD 0x00
#define MEI_NFC_VENDOR_NXP_PN544 0x01
static void mei_nfc_free(struct mei_nfc_dev *ndev)
{
if (ndev->cl) {
list_del(&ndev->cl->device_link);
mei_cl_unlink(ndev->cl);
kfree(ndev->cl);
}
if (ndev->cl_info) {
list_del(&ndev->cl_info->device_link);
mei_cl_unlink(ndev->cl_info);
kfree(ndev->cl_info);
}
memset(ndev, 0, sizeof(struct mei_nfc_dev));
}
static int mei_nfc_build_bus_name(struct mei_nfc_dev *ndev)
{
struct mei_device *dev;
if (!ndev->cl)
return -ENODEV;
dev = ndev->cl->dev;
switch (ndev->vendor_id) {
case MEI_NFC_VENDOR_INSIDE:
switch (ndev->radio_type) {
case MEI_NFC_VENDOR_INSIDE_UREAD:
ndev->bus_name = "microread";
return 0;
default:
dev_err(&dev->pdev->dev, "Unknow radio type 0x%x\n",
ndev->radio_type);
return -EINVAL;
}
case MEI_NFC_VENDOR_NXP:
switch (ndev->radio_type) {
case MEI_NFC_VENDOR_NXP_PN544:
ndev->bus_name = "pn544";
return 0;
default:
dev_err(&dev->pdev->dev, "Unknow radio type 0x%x\n",
ndev->radio_type);
return -EINVAL;
}
default:
dev_err(&dev->pdev->dev, "Unknow vendor ID 0x%x\n",
ndev->vendor_id);
return -EINVAL;
}
return 0;
}
static int mei_nfc_connect(struct mei_nfc_dev *ndev)
{
struct mei_device *dev;
struct mei_cl *cl;
struct mei_nfc_cmd *cmd, *reply;
struct mei_nfc_connect *connect;
struct mei_nfc_connect_resp *connect_resp;
size_t connect_length, connect_resp_length;
int bytes_recv, ret;
cl = ndev->cl;
dev = cl->dev;
connect_length = sizeof(struct mei_nfc_cmd) +
sizeof(struct mei_nfc_connect);
connect_resp_length = sizeof(struct mei_nfc_cmd) +
sizeof(struct mei_nfc_connect_resp);
cmd = kzalloc(connect_length, GFP_KERNEL);
if (!cmd)
return -ENOMEM;
connect = (struct mei_nfc_connect *)cmd->data;
reply = kzalloc(connect_resp_length, GFP_KERNEL);
if (!reply) {
kfree(cmd);
return -ENOMEM;
}
connect_resp = (struct mei_nfc_connect_resp *)reply->data;
cmd->command = MEI_NFC_CMD_MAINTENANCE;
cmd->data_size = 3;
cmd->sub_command = MEI_NFC_SUBCMD_CONNECT;
connect->fw_ivn = ndev->fw_ivn;
connect->vendor_id = ndev->vendor_id;
ret = __mei_cl_send(cl, (u8 *)cmd, connect_length);
if (ret < 0) {
dev_err(&dev->pdev->dev, "Could not send connect cmd\n");
goto err;
}
bytes_recv = __mei_cl_recv(cl, (u8 *)reply, connect_resp_length);
if (bytes_recv < 0) {
dev_err(&dev->pdev->dev, "Could not read connect response\n");
ret = bytes_recv;
goto err;
}
dev_info(&dev->pdev->dev, "IVN 0x%x Vendor ID 0x%x\n",
connect_resp->fw_ivn, connect_resp->vendor_id);
dev_info(&dev->pdev->dev, "ME FW %d.%d.%d.%d\n",
connect_resp->me_major, connect_resp->me_minor,
connect_resp->me_hotfix, connect_resp->me_build);
ret = 0;
err:
kfree(reply);
kfree(cmd);
return ret;
}
static int mei_nfc_if_version(struct mei_nfc_dev *ndev)
{
struct mei_device *dev;
struct mei_cl *cl;
struct mei_nfc_cmd cmd;
struct mei_nfc_reply *reply = NULL;
struct mei_nfc_if_version *version;
size_t if_version_length;
int bytes_recv, ret;
cl = ndev->cl_info;
dev = cl->dev;
memset(&cmd, 0, sizeof(struct mei_nfc_cmd));
cmd.command = MEI_NFC_CMD_MAINTENANCE;
cmd.data_size = 1;
cmd.sub_command = MEI_NFC_SUBCMD_IF_VERSION;
ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd));
if (ret < 0) {
dev_err(&dev->pdev->dev, "Could not send IF version cmd\n");
return ret;
}
/* to be sure on the stack we alloc memory */
if_version_length = sizeof(struct mei_nfc_reply) +
sizeof(struct mei_nfc_if_version);
reply = kzalloc(if_version_length, GFP_KERNEL);
if (!reply)
return -ENOMEM;
bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length);
if (bytes_recv < 0 || bytes_recv < sizeof(struct mei_nfc_reply)) {
dev_err(&dev->pdev->dev, "Could not read IF version\n");
ret = -EIO;
goto err;
}
version = (struct mei_nfc_if_version *)reply->data;
ndev->fw_ivn = version->fw_ivn;
ndev->vendor_id = version->vendor_id;
ndev->radio_type = version->radio_type;
err:
kfree(reply);
return ret;
}
static int mei_nfc_enable(struct mei_cl_device *cldev)
{
struct mei_device *dev;
struct mei_nfc_dev *ndev = &nfc_dev;
int ret;
dev = ndev->cl->dev;
ret = mei_nfc_connect(ndev);
if (ret < 0) {
dev_err(&dev->pdev->dev, "Could not connect to NFC");
return ret;
}
return 0;
}
static int mei_nfc_disable(struct mei_cl_device *cldev)
{
return 0;
}
static int mei_nfc_send(struct mei_cl_device *cldev, u8 *buf, size_t length)
{
struct mei_device *dev;
struct mei_nfc_dev *ndev;
struct mei_nfc_hci_hdr *hdr;
u8 *mei_buf;
int err;
ndev = (struct mei_nfc_dev *) cldev->priv_data;
dev = ndev->cl->dev;
mei_buf = kzalloc(length + MEI_NFC_HEADER_SIZE, GFP_KERNEL);
if (!mei_buf)
return -ENOMEM;
hdr = (struct mei_nfc_hci_hdr *) mei_buf;
hdr->cmd = MEI_NFC_CMD_HCI_SEND;
hdr->status = 0;
hdr->req_id = ndev->req_id;
hdr->reserved = 0;
hdr->data_size = length;
memcpy(mei_buf + MEI_NFC_HEADER_SIZE, buf, length);
err = __mei_cl_send(ndev->cl, mei_buf, length + MEI_NFC_HEADER_SIZE);
if (err < 0)
return err;
kfree(mei_buf);
if (!wait_event_interruptible_timeout(ndev->send_wq,
ndev->recv_req_id == ndev->req_id, HZ)) {
dev_err(&dev->pdev->dev, "NFC MEI command timeout\n");
err = -ETIMEDOUT;
} else {
ndev->req_id++;
}
return err;
}
static int mei_nfc_recv(struct mei_cl_device *cldev, u8 *buf, size_t length)
{
struct mei_nfc_dev *ndev;
struct mei_nfc_hci_hdr *hci_hdr;
int received_length;
ndev = (struct mei_nfc_dev *)cldev->priv_data;
received_length = __mei_cl_recv(ndev->cl, buf, length);
if (received_length < 0)
return received_length;
hci_hdr = (struct mei_nfc_hci_hdr *) buf;
if (hci_hdr->cmd == MEI_NFC_CMD_HCI_SEND) {
ndev->recv_req_id = hci_hdr->req_id;
wake_up(&ndev->send_wq);
return 0;
}
return received_length;
}
static struct mei_cl_ops nfc_ops = {
.enable = mei_nfc_enable,
.disable = mei_nfc_disable,
.send = mei_nfc_send,
.recv = mei_nfc_recv,
};
static void mei_nfc_init(struct work_struct *work)
{
struct mei_device *dev;
struct mei_cl_device *cldev;
struct mei_nfc_dev *ndev;
struct mei_cl *cl_info;
ndev = container_of(work, struct mei_nfc_dev, init_work);
cl_info = ndev->cl_info;
dev = cl_info->dev;
mutex_lock(&dev->device_lock);
if (mei_cl_connect(cl_info, NULL) < 0) {
mutex_unlock(&dev->device_lock);
dev_err(&dev->pdev->dev,
"Could not connect to the NFC INFO ME client");
goto err;
}
mutex_unlock(&dev->device_lock);
if (mei_nfc_if_version(ndev) < 0) {
dev_err(&dev->pdev->dev, "Could not get the NFC interfave version");
goto err;
}
dev_info(&dev->pdev->dev,
"NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n",
ndev->fw_ivn, ndev->vendor_id, ndev->radio_type);
mutex_lock(&dev->device_lock);
if (mei_cl_disconnect(cl_info) < 0) {
mutex_unlock(&dev->device_lock);
dev_err(&dev->pdev->dev,
"Could not disconnect the NFC INFO ME client");
goto err;
}
mutex_unlock(&dev->device_lock);
if (mei_nfc_build_bus_name(ndev) < 0) {
dev_err(&dev->pdev->dev,
"Could not build the bus ID name\n");
return;
}
cldev = mei_cl_add_device(dev, mei_nfc_guid, ndev->bus_name, &nfc_ops);
if (!cldev) {
dev_err(&dev->pdev->dev,
"Could not add the NFC device to the MEI bus\n");
goto err;
}
cldev->priv_data = ndev;
return;
err:
mei_nfc_free(ndev);
return;
}
int mei_nfc_host_init(struct mei_device *dev)
{
struct mei_nfc_dev *ndev = &nfc_dev;
struct mei_cl *cl_info, *cl = NULL;
int i, ret;
/* already initialzed */
if (ndev->cl_info)
return 0;
cl_info = mei_cl_allocate(dev);
cl = mei_cl_allocate(dev);
if (!cl || !cl_info) {
ret = -ENOMEM;
goto err;
}
/* check for valid client id */
i = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid);
if (i < 0) {
dev_info(&dev->pdev->dev, "nfc: failed to find the client\n");
ret = -ENOENT;
goto err;
}
cl_info->me_client_id = dev->me_clients[i].client_id;
ret = mei_cl_link(cl_info, MEI_HOST_CLIENT_ID_ANY);
if (ret)
goto err;
cl_info->device_uuid = mei_nfc_info_guid;
list_add_tail(&cl_info->device_link, &dev->device_list);
/* check for valid client id */
i = mei_me_cl_by_uuid(dev, &mei_nfc_guid);
if (i < 0) {
dev_info(&dev->pdev->dev, "nfc: failed to find the client\n");
ret = -ENOENT;
goto err;
}
cl->me_client_id = dev->me_clients[i].client_id;
ret = mei_cl_link(cl, MEI_HOST_CLIENT_ID_ANY);
if (ret)
goto err;
cl->device_uuid = mei_nfc_guid;
list_add_tail(&cl->device_link, &dev->device_list);
ndev->cl_info = cl_info;
ndev->cl = cl;
ndev->req_id = 1;
INIT_WORK(&ndev->init_work, mei_nfc_init);
init_waitqueue_head(&ndev->send_wq);
schedule_work(&ndev->init_work);
return 0;
err:
mei_nfc_free(ndev);
return ret;
}
void mei_nfc_host_exit(void)
{
struct mei_nfc_dev *ndev = &nfc_dev;
if (ndev->cl && ndev->cl->device)
mei_cl_remove_device(ndev->cl->device);
mei_nfc_free(ndev);
}

358
drivers/misc/mei/pci-me.c Normal file
View File

@@ -0,0 +1,358 @@
/*
*
* 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.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#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>
#include <linux/uuid.h>
#include <linux/compat.h>
#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hw-me.h"
#include "client.h"
/* AMT device is a singleton on the platform */
static struct pci_dev *mei_pdev;
/* 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)},
/* required last entry */
{0, }
};
MODULE_DEVICE_TABLE(pci, mei_me_pci_tbl);
static DEFINE_MUTEX(mei_mutex);
/**
* mei_quirk_probe - probe for devices that doesn't valid ME interface
*
* @pdev: PCI device structure
* @ent: entry into pci_device_table
*
* returns true if ME Interface is valid, false otherwise
*/
static bool mei_me_quirk_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
u32 reg;
if (ent->device == MEI_DEV_ID_PBG_1) {
pci_read_config_dword(pdev, 0x48, &reg);
/* 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;
}
}
return true;
}
/**
* mei_probe - Device Initialization Routine
*
* @pdev: PCI device structure
* @ent: entry in kcs_pci_tbl
*
* returns 0 on success, <0 on failure.
*/
static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct mei_device *dev;
struct mei_me_hw *hw;
int err;
mutex_lock(&mei_mutex);
if (!mei_me_quirk_probe(pdev, ent)) {
err = -ENODEV;
goto end;
}
if (mei_pdev) {
err = -EEXIST;
goto end;
}
/* 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;
}
/* allocates and initializes the mei dev structure */
dev = mei_me_dev_init(pdev);
if (!dev) {
err = -ENOMEM;
goto release_regions;
}
hw = to_me_hw(dev);
/* mapping IO device memory */
hw->mem_addr = pci_iomap(pdev, 0, 0);
if (!hw->mem_addr) {
dev_err(&pdev->dev, "mapping I/O device memory failure.\n");
err = -ENOMEM;
goto free_device;
}
pci_enable_msi(pdev);
/* request and enable interrupt */
if (pci_dev_msi_enabled(pdev))
err = request_threaded_irq(pdev->irq,
NULL,
mei_me_irq_thread_handler,
IRQF_ONESHOT, KBUILD_MODNAME, dev);
else
err = request_threaded_irq(pdev->irq,
mei_me_irq_quick_handler,
mei_me_irq_thread_handler,
IRQF_SHARED, KBUILD_MODNAME, dev);
if (err) {
dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n",
pdev->irq);
goto disable_msi;
}
if (mei_start(dev)) {
dev_err(&pdev->dev, "init hw failure.\n");
err = -ENODEV;
goto release_irq;
}
err = mei_register(dev);
if (err)
goto release_irq;
mei_pdev = pdev;
pci_set_drvdata(pdev, dev);
schedule_delayed_work(&dev->timer_work, HZ);
mutex_unlock(&mei_mutex);
pr_debug("initialization successful.\n");
return 0;
release_irq:
mei_disable_interrupts(dev);
flush_scheduled_work();
free_irq(pdev->irq, dev);
disable_msi:
pci_disable_msi(pdev);
pci_iounmap(pdev, hw->mem_addr);
free_device:
kfree(dev);
release_regions:
pci_release_regions(pdev);
disable_device:
pci_disable_device(pdev);
end:
mutex_unlock(&mei_mutex);
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_me_remove(struct pci_dev *pdev)
{
struct mei_device *dev;
struct mei_me_hw *hw;
if (mei_pdev != pdev)
return;
dev = pci_get_drvdata(pdev);
if (!dev)
return;
hw = to_me_hw(dev);
dev_err(&pdev->dev, "stop\n");
mei_stop(dev);
mei_pdev = NULL;
/* 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);
mei_deregister(dev);
kfree(dev);
pci_release_regions(pdev);
pci_disable_device(pdev);
}
#ifdef CONFIG_PM
static int mei_me_pci_suspend(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
struct mei_device *dev = pci_get_drvdata(pdev);
if (!dev)
return -ENODEV;
dev_err(&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_me_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);
/* request and enable interrupt */
if (pci_dev_msi_enabled(pdev))
err = request_threaded_irq(pdev->irq,
NULL,
mei_me_irq_thread_handler,
IRQF_ONESHOT, KBUILD_MODNAME, dev);
else
err = request_threaded_irq(pdev->irq,
mei_me_irq_quick_handler,
mei_me_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;
}
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);
/* Start timer if stopped in suspend */
schedule_delayed_work(&dev->timer_work, HZ);
return err;
}
static SIMPLE_DEV_PM_OPS(mei_me_pm_ops, mei_me_pci_suspend, mei_me_pci_resume);
#define MEI_ME_PM_OPS (&mei_me_pm_ops)
#else
#define MEI_ME_PM_OPS NULL
#endif /* CONFIG_PM */
/*
* PCI driver structure
*/
static struct pci_driver mei_me_driver = {
.name = KBUILD_MODNAME,
.id_table = mei_me_pci_tbl,
.probe = mei_me_probe,
.remove = mei_me_remove,
.shutdown = mei_me_remove,
.driver.pm = MEI_ME_PM_OPS,
};
module_pci_driver(mei_me_driver);
MODULE_AUTHOR("Intel Corporation");
MODULE_DESCRIPTION("Intel(R) Management Engine Interface");
MODULE_LICENSE("GPL v2");

392
drivers/misc/mei/wd.c Normal file
View File

@@ -0,0 +1,392 @@
/*
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/watchdog.h>
#include <linux/mei.h>
#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 };
static const u8 mei_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 };
const u8 mei_wd_state_independence_msg[3][4] = {
{0x05, 0x02, 0x51, 0x10},
{0x05, 0x02, 0x52, 0x10},
{0x07, 0x02, 0x01, 0x10}
};
/*
* AMT Watchdog Device
*/
#define INTEL_AMT_WATCHDOG_ID "INTCAMT"
/* UUIDs for AMT F/W clients */
const uuid_le mei_wd_guid = UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, 0x89,
0x9D, 0xA9, 0x15, 0x14, 0xCB,
0x32, 0xAB);
static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout)
{
dev_dbg(&dev->pdev->dev, "wd: set timeout=%d.\n", timeout);
memcpy(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE);
memcpy(dev->wd_data + MEI_WD_HDR_SIZE, &timeout, sizeof(u16));
}
/**
* mei_wd_host_init - connect to the watchdog client
*
* @dev: the device structure
*
* returns -ENENT if wd client cannot be found
* -EIO if write has failed
* 0 on success
*/
int mei_wd_host_init(struct mei_device *dev)
{
struct mei_cl *cl = &dev->wd_cl;
int i;
int ret;
mei_cl_init(cl, dev);
dev->wd_timeout = MEI_WD_DEFAULT_TIMEOUT;
dev->wd_state = MEI_WD_IDLE;
/* check for valid client id */
i = mei_me_cl_by_uuid(dev, &mei_wd_guid);
if (i < 0) {
dev_info(&dev->pdev->dev, "wd: failed to find the client\n");
return -ENOENT;
}
cl->me_client_id = dev->me_clients[i].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;
}
cl->state = MEI_FILE_CONNECTING;
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;
}
cl->timer_count = MEI_CONNECT_TIMEOUT;
return 0;
}
/**
* mei_wd_send - sends watch dog message to fw.
*
* @dev: the device structure
*
* returns 0 if success,
* -EIO when message send fails
* -EINVAL when invalid message is to be sent
*/
int mei_wd_send(struct mei_device *dev)
{
struct mei_msg_hdr hdr;
hdr.host_addr = dev->wd_cl.host_client_id;
hdr.me_addr = dev->wd_cl.me_client_id;
hdr.msg_complete = 1;
hdr.reserved = 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
return -EINVAL;
return mei_write_message(dev, &hdr, dev->wd_data);
}
/**
* mei_wd_stop - sends watchdog stop message to fw.
*
* @dev: the device structure
* @preserve: indicate if to keep the timeout value
*
* returns 0 if success,
* -EIO when message send fails
* -EINVAL when invalid message is to be sent
*/
int mei_wd_stop(struct mei_device *dev)
{
int ret;
if (dev->wd_cl.state != MEI_FILE_CONNECTED ||
dev->wd_state != MEI_WD_RUNNING)
return 0;
memcpy(dev->wd_data, mei_stop_wd_params, MEI_WD_STOP_MSG_SIZE);
dev->wd_state = MEI_WD_STOPPING;
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");
}
dev->wd_pending = false;
} else {
dev->wd_pending = true;
}
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));
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;
dev_warn(&dev->pdev->dev,
"wd: stop failed to complete ret=%d.\n", ret);
}
out:
return ret;
}
/*
* mei_wd_ops_start - wd start command from the watchdog core.
*
* @wd_dev - watchdog device struct
*
* returns 0 if success, negative errno code for failure
*/
static int mei_wd_ops_start(struct watchdog_device *wd_dev)
{
int err = -ENODEV;
struct mei_device *dev;
dev = watchdog_get_drvdata(wd_dev);
if (!dev)
return -ENODEV;
mutex_lock(&dev->device_lock);
if (dev->dev_state != MEI_DEV_ENABLED) {
dev_dbg(&dev->pdev->dev,
"wd: dev_state != MEI_DEV_ENABLED dev_state = %s\n",
mei_dev_state_str(dev->dev_state));
goto end_unlock;
}
if (dev->wd_cl.state != MEI_FILE_CONNECTED) {
dev_dbg(&dev->pdev->dev,
"MEI Driver is not connected to Watchdog Client\n");
goto end_unlock;
}
mei_wd_set_start_timeout(dev, dev->wd_timeout);
err = 0;
end_unlock:
mutex_unlock(&dev->device_lock);
return err;
}
/*
* mei_wd_ops_stop - wd stop command from the watchdog core.
*
* @wd_dev - watchdog device struct
*
* returns 0 if success, negative errno code for failure
*/
static int mei_wd_ops_stop(struct watchdog_device *wd_dev)
{
struct mei_device *dev;
dev = watchdog_get_drvdata(wd_dev);
if (!dev)
return -ENODEV;
mutex_lock(&dev->device_lock);
mei_wd_stop(dev);
mutex_unlock(&dev->device_lock);
return 0;
}
/*
* mei_wd_ops_ping - wd ping command from the watchdog core.
*
* @wd_dev - watchdog device struct
*
* returns 0 if success, negative errno code for failure
*/
static int mei_wd_ops_ping(struct watchdog_device *wd_dev)
{
int ret = 0;
struct mei_device *dev;
dev = watchdog_get_drvdata(wd_dev);
if (!dev)
return -ENODEV;
mutex_lock(&dev->device_lock);
if (dev->wd_cl.state != MEI_FILE_CONNECTED) {
dev_err(&dev->pdev->dev, "wd: not connected.\n");
ret = -ENODEV;
goto end;
}
dev->wd_state = MEI_WD_RUNNING;
/* Check if we can send the ping to HW*/
if (dev->hbuf_is_ready && mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) {
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;
goto end;
}
} else {
dev->wd_pending = true;
}
end:
mutex_unlock(&dev->device_lock);
return ret;
}
/*
* mei_wd_ops_set_timeout - wd set timeout command from the watchdog core.
*
* @wd_dev - watchdog device struct
* @timeout - timeout value to set
*
* returns 0 if success, negative errno code for failure
*/
static int mei_wd_ops_set_timeout(struct watchdog_device *wd_dev,
unsigned int timeout)
{
struct mei_device *dev;
dev = watchdog_get_drvdata(wd_dev);
if (!dev)
return -ENODEV;
/* Check Timeout value */
if (timeout < MEI_WD_MIN_TIMEOUT || timeout > MEI_WD_MAX_TIMEOUT)
return -EINVAL;
mutex_lock(&dev->device_lock);
dev->wd_timeout = timeout;
wd_dev->timeout = timeout;
mei_wd_set_start_timeout(dev, dev->wd_timeout);
mutex_unlock(&dev->device_lock);
return 0;
}
/*
* Watchdog Device structs
*/
static const struct watchdog_ops wd_ops = {
.owner = THIS_MODULE,
.start = mei_wd_ops_start,
.stop = mei_wd_ops_stop,
.ping = mei_wd_ops_ping,
.set_timeout = mei_wd_ops_set_timeout,
};
static const struct watchdog_info wd_info = {
.identity = INTEL_AMT_WATCHDOG_ID,
.options = WDIOF_KEEPALIVEPING |
WDIOF_SETTIMEOUT |
WDIOF_ALARMONLY,
};
static struct watchdog_device amt_wd_dev = {
.info = &wd_info,
.ops = &wd_ops,
.timeout = MEI_WD_DEFAULT_TIMEOUT,
.min_timeout = MEI_WD_MIN_TIMEOUT,
.max_timeout = MEI_WD_MAX_TIMEOUT,
};
void 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;
}
dev_dbg(&dev->pdev->dev,
"wd: successfully register watchdog interface.\n");
watchdog_set_drvdata(&amt_wd_dev, dev);
}
void mei_watchdog_unregister(struct mei_device *dev)
{
if (watchdog_get_drvdata(&amt_wd_dev) == NULL)
return;
watchdog_set_drvdata(&amt_wd_dev, NULL);
watchdog_unregister_device(&amt_wd_dev);
}