Staging: mei: move the mei code out of staging
It's been cleaned up, and there's nothing else left to do, so move it out of staging into drivers/misc/ where all can use it now. Cc: Tomas Winkler <tomas.winkler@intel.com> Cc: Oren Weil <oren.jer.weil@intel.com> Cc: Alan Cox <alan@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
28
drivers/misc/mei/Kconfig
Normal file
28
drivers/misc/mei/Kconfig
Normal file
@@ -0,0 +1,28 @@
|
||||
config INTEL_MEI
|
||||
tristate "Intel Management Engine Interface (Intel MEI)"
|
||||
depends on X86 && PCI && EXPERIMENTAL && 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.
|
||||
|
||||
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
|
||||
|
||||
For more information see
|
||||
<http://software.intel.com/en-us/manageability/>
|
11
drivers/misc/mei/Makefile
Normal file
11
drivers/misc/mei/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# 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 += interrupt.o
|
||||
mei-objs += interface.o
|
||||
mei-objs += iorw.o
|
||||
mei-objs += main.o
|
||||
mei-objs += wd.o
|
10
drivers/misc/mei/TODO
Normal file
10
drivers/misc/mei/TODO
Normal file
@@ -0,0 +1,10 @@
|
||||
TODO:
|
||||
- Cleanup and split the timer function
|
||||
Upon Unstaging:
|
||||
- move mei.h to include/linux/mei.h
|
||||
- Documentation/ioctl/ioctl-number.txt
|
||||
- move mei.txt under Documentation/mei/
|
||||
- move mei-amt-version.c under Documentation/mei
|
||||
- add hostprogs-y for mei-amt-version.c
|
||||
- drop mei_version.h
|
||||
- Updated MAINTAINERS
|
332
drivers/misc/mei/hw.h
Normal file
332
drivers/misc/mei/hw.h
Normal file
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
#define MEI_INTEROP_TIMEOUT (HZ * 7)
|
||||
#define MEI_CONNECT_TIMEOUT 3 /* at least 2 seconds */
|
||||
|
||||
#define CONNECT_TIMEOUT 15 /* HPS definition */
|
||||
#define INIT_CLIENTS_TIMEOUT 15 /* HPS definition */
|
||||
|
||||
#define IAMTHIF_STALL_TIMER 12 /* seconds */
|
||||
#define IAMTHIF_READ_TIMER 10000 /* ms */
|
||||
|
||||
/*
|
||||
* Internal Clients Number
|
||||
*/
|
||||
#define MEI_WD_HOST_CLIENT_ID 1
|
||||
#define MEI_IAMTHIF_HOST_CLIENT_ID 2
|
||||
|
||||
/*
|
||||
* 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 /* Cougerpoint */
|
||||
#define MEI_DEV_ID_PBG_1 0x1D3A /* PBG */
|
||||
|
||||
#define MEI_DEV_ID_PPT_1 0x1E3A /* Pantherpoint PPT */
|
||||
#define MEI_DEV_ID_PPT_2 0x1CBA /* Pantherpoint PPT */
|
||||
#define MEI_DEV_ID_PPT_3 0x1DBA /* Pantherpoint PPT */
|
||||
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
||||
/*
|
||||
* 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_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 {
|
||||
u8 hbm_cmd;
|
||||
u8 me_addr;
|
||||
u8 host_addr;
|
||||
u8 reserved;
|
||||
} __packed;
|
||||
|
||||
struct hbm_client_connect_response {
|
||||
u8 hbm_cmd;
|
||||
u8 me_addr;
|
||||
u8 host_addr;
|
||||
u8 status;
|
||||
} __packed;
|
||||
|
||||
struct hbm_client_disconnect_request {
|
||||
u8 hbm_cmd;
|
||||
u8 me_addr;
|
||||
u8 host_addr;
|
||||
u8 reserved[1];
|
||||
} __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;
|
||||
|
||||
struct mei_me_client {
|
||||
struct mei_client_properties props;
|
||||
u8 client_id;
|
||||
u8 mei_flow_ctrl_creds;
|
||||
} __packed;
|
||||
|
||||
|
||||
#endif
|
735
drivers/misc/mei/init.c
Normal file
735
drivers/misc/mei/init.c
Normal file
@@ -0,0 +1,735 @@
|
||||
/*
|
||||
*
|
||||
* 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 "mei_dev.h"
|
||||
#include "hw.h"
|
||||
#include "interface.h"
|
||||
#include "mei.h"
|
||||
|
||||
const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac,
|
||||
0xa8, 0x46, 0xe0, 0xff, 0x65,
|
||||
0x81, 0x4c);
|
||||
|
||||
/**
|
||||
* mei_io_list_init - Sets up a queue list.
|
||||
*
|
||||
* @list: An instance io list structure
|
||||
* @dev: the device structure
|
||||
*/
|
||||
void mei_io_list_init(struct mei_io_list *list)
|
||||
{
|
||||
/* initialize our queue list */
|
||||
INIT_LIST_HEAD(&list->mei_cb.cb_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_io_list_flush - removes list entry belonging to cl.
|
||||
*
|
||||
* @list: An instance of our list structure
|
||||
* @cl: private data of the file object
|
||||
*/
|
||||
void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl)
|
||||
{
|
||||
struct mei_cl_cb *pos;
|
||||
struct mei_cl_cb *next;
|
||||
|
||||
list_for_each_entry_safe(pos, next, &list->mei_cb.cb_list, cb_list) {
|
||||
if (pos->file_private) {
|
||||
struct mei_cl *cl_tmp;
|
||||
cl_tmp = (struct mei_cl *)pos->file_private;
|
||||
if (mei_cl_cmp_id(cl, cl_tmp))
|
||||
list_del(&pos->cb_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* mei_cl_flush_queues - flushes queue lists belonging to cl.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @cl: private data of the file object
|
||||
*/
|
||||
int mei_cl_flush_queues(struct mei_cl *cl)
|
||||
{
|
||||
if (!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->amthi_cmd_list, cl);
|
||||
mei_io_list_flush(&cl->dev->amthi_read_complete_list, cl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* mei_reset_iamthif_params - initializes mei device iamthif
|
||||
*
|
||||
* @dev: the device structure
|
||||
*/
|
||||
static void mei_reset_iamthif_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* init_mei_device - allocates and initializes the mei device structure
|
||||
*
|
||||
* @pdev: The pci device structure
|
||||
*
|
||||
* returns The mei_device_device pointer on success, NULL on failure.
|
||||
*/
|
||||
struct mei_device *mei_device_init(struct pci_dev *pdev)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
|
||||
dev = kzalloc(sizeof(struct mei_device), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
/* setup our list array */
|
||||
INIT_LIST_HEAD(&dev->file_list);
|
||||
INIT_LIST_HEAD(&dev->wd_cl.link);
|
||||
INIT_LIST_HEAD(&dev->iamthif_cl.link);
|
||||
mutex_init(&dev->device_lock);
|
||||
init_waitqueue_head(&dev->wait_recvd_msg);
|
||||
init_waitqueue_head(&dev->wait_stop_wd);
|
||||
dev->mei_state = MEI_INITIALIZING;
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
dev->wd_interface_reg = false;
|
||||
|
||||
|
||||
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);
|
||||
mei_io_list_init(&dev->amthi_cmd_list);
|
||||
mei_io_list_init(&dev->amthi_read_complete_list);
|
||||
dev->pdev = pdev;
|
||||
return dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_hw_init - initializes host and fw to start work.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns 0 on success, <0 on failure.
|
||||
*/
|
||||
int mei_hw_init(struct mei_device *dev)
|
||||
{
|
||||
int err = 0;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
dev->host_hw_state = mei_hcsr_read(dev);
|
||||
dev->me_hw_state = mei_mecsr_read(dev);
|
||||
dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, mestate = 0x%08x.\n",
|
||||
dev->host_hw_state, dev->me_hw_state);
|
||||
|
||||
/* acknowledge interrupt and stop interupts */
|
||||
if ((dev->host_hw_state & H_IS) == H_IS)
|
||||
mei_reg_write(dev, H_CSR, dev->host_hw_state);
|
||||
|
||||
dev->recvd_msg = false;
|
||||
dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n");
|
||||
|
||||
mei_reset(dev, 1);
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n",
|
||||
dev->host_hw_state, dev->me_hw_state);
|
||||
|
||||
/* wait for ME to turn on ME_RDY */
|
||||
if (!dev->recvd_msg) {
|
||||
mutex_unlock(&dev->device_lock);
|
||||
err = wait_event_interruptible_timeout(dev->wait_recvd_msg,
|
||||
dev->recvd_msg, MEI_INTEROP_TIMEOUT);
|
||||
mutex_lock(&dev->device_lock);
|
||||
}
|
||||
|
||||
if (err <= 0 && !dev->recvd_msg) {
|
||||
dev->mei_state = MEI_DISABLED;
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"wait_event_interruptible_timeout failed"
|
||||
"on wait for ME to turn on ME_RDY.\n");
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(((dev->host_hw_state & H_RDY) == H_RDY) &&
|
||||
((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA))) {
|
||||
dev->mei_state = MEI_DISABLED;
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n",
|
||||
dev->host_hw_state, dev->me_hw_state);
|
||||
|
||||
if (!(dev->host_hw_state & H_RDY))
|
||||
dev_dbg(&dev->pdev->dev, "host turn off H_RDY.\n");
|
||||
|
||||
if (!(dev->me_hw_state & ME_RDY_HRA))
|
||||
dev_dbg(&dev->pdev->dev, "ME turn off ME_RDY.\n");
|
||||
|
||||
dev_err(&dev->pdev->dev, "link layer initialization failed.\n");
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dev->version.major_version != HBM_MAJOR_VERSION ||
|
||||
dev->version.minor_version != HBM_MINOR_VERSION) {
|
||||
dev_dbg(&dev->pdev->dev, "MEI start failed.\n");
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev->recvd_msg = false;
|
||||
dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n",
|
||||
dev->host_hw_state, dev->me_hw_state);
|
||||
dev_dbg(&dev->pdev->dev, "ME turn on ME_RDY and host turn on H_RDY.\n");
|
||||
dev_dbg(&dev->pdev->dev, "link layer has been established.\n");
|
||||
dev_dbg(&dev->pdev->dev, "MEI start success.\n");
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
mutex_unlock(&dev->device_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_hw_reset - resets fw via mei csr register.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @interrupts_enabled: if interrupt should be enabled after reset.
|
||||
*/
|
||||
static void mei_hw_reset(struct mei_device *dev, int interrupts_enabled)
|
||||
{
|
||||
dev->host_hw_state |= (H_RST | H_IG);
|
||||
|
||||
if (interrupts_enabled)
|
||||
mei_enable_interrupts(dev);
|
||||
else
|
||||
mei_disable_interrupts(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
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;
|
||||
bool unexpected;
|
||||
|
||||
if (dev->mei_state == MEI_RECOVERING_FROM_RESET) {
|
||||
dev->need_reset = true;
|
||||
return;
|
||||
}
|
||||
|
||||
unexpected = (dev->mei_state != MEI_INITIALIZING &&
|
||||
dev->mei_state != MEI_DISABLED &&
|
||||
dev->mei_state != MEI_POWER_DOWN &&
|
||||
dev->mei_state != MEI_POWER_UP);
|
||||
|
||||
dev->host_hw_state = mei_hcsr_read(dev);
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "before reset host_hw_state = 0x%08x.\n",
|
||||
dev->host_hw_state);
|
||||
|
||||
mei_hw_reset(dev, interrupts_enabled);
|
||||
|
||||
dev->host_hw_state &= ~H_RST;
|
||||
dev->host_hw_state |= H_IG;
|
||||
|
||||
mei_hcsr_set(dev);
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "currently saved host_hw_state = 0x%08x.\n",
|
||||
dev->host_hw_state);
|
||||
|
||||
dev->need_reset = false;
|
||||
|
||||
if (dev->mei_state != MEI_INITIALIZING) {
|
||||
if (dev->mei_state != MEI_DISABLED &&
|
||||
dev->mei_state != MEI_POWER_DOWN)
|
||||
dev->mei_state = MEI_RESETING;
|
||||
|
||||
list_for_each_entry_safe(cl_pos,
|
||||
cl_next, &dev->file_list, link) {
|
||||
cl_pos->state = MEI_FILE_DISCONNECTED;
|
||||
cl_pos->mei_flow_ctrl_creds = 0;
|
||||
cl_pos->read_cb = NULL;
|
||||
cl_pos->timer_count = 0;
|
||||
}
|
||||
/* remove entry if already in list */
|
||||
dev_dbg(&dev->pdev->dev, "list del iamthif and wd file list.\n");
|
||||
mei_remove_client_from_file_list(dev,
|
||||
dev->wd_cl.host_client_id);
|
||||
|
||||
mei_remove_client_from_file_list(dev,
|
||||
dev->iamthif_cl.host_client_id);
|
||||
|
||||
mei_reset_iamthif_params(dev);
|
||||
dev->wd_due_counter = 0;
|
||||
dev->extra_write_index = 0;
|
||||
}
|
||||
|
||||
dev->me_clients_num = 0;
|
||||
dev->rd_msg_hdr = 0;
|
||||
dev->stop = false;
|
||||
dev->wd_pending = false;
|
||||
|
||||
/* update the state of the registers after reset */
|
||||
dev->host_hw_state = mei_hcsr_read(dev);
|
||||
dev->me_hw_state = mei_mecsr_read(dev);
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "after reset host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n",
|
||||
dev->host_hw_state, dev->me_hw_state);
|
||||
|
||||
if (unexpected)
|
||||
dev_warn(&dev->pdev->dev, "unexpected reset.\n");
|
||||
|
||||
/* Wake up all readings so they can be interrupted */
|
||||
list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) {
|
||||
if (waitqueue_active(&cl_pos->rx_wait)) {
|
||||
dev_dbg(&dev->pdev->dev, "Waking up client!\n");
|
||||
wake_up_interruptible(&cl_pos->rx_wait);
|
||||
}
|
||||
}
|
||||
/* remove all waiting requests */
|
||||
list_for_each_entry_safe(cb_pos, cb_next,
|
||||
&dev->write_list.mei_cb.cb_list, cb_list) {
|
||||
list_del(&cb_pos->cb_list);
|
||||
mei_free_cb_private(cb_pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* host_start_message - mei host sends start message.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns none.
|
||||
*/
|
||||
void mei_host_start_message(struct mei_device *dev)
|
||||
{
|
||||
struct mei_msg_hdr *mei_hdr;
|
||||
struct hbm_host_version_request *host_start_req;
|
||||
|
||||
/* host start message */
|
||||
mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0];
|
||||
mei_hdr->host_addr = 0;
|
||||
mei_hdr->me_addr = 0;
|
||||
mei_hdr->length = sizeof(struct hbm_host_version_request);
|
||||
mei_hdr->msg_complete = 1;
|
||||
mei_hdr->reserved = 0;
|
||||
|
||||
host_start_req =
|
||||
(struct hbm_host_version_request *) &dev->wr_msg_buf[1];
|
||||
memset(host_start_req, 0, sizeof(struct hbm_host_version_request));
|
||||
host_start_req->hbm_cmd = HOST_START_REQ_CMD;
|
||||
host_start_req->host_version.major_version = HBM_MAJOR_VERSION;
|
||||
host_start_req->host_version.minor_version = HBM_MINOR_VERSION;
|
||||
dev->recvd_msg = false;
|
||||
if (mei_write_message(dev, mei_hdr, (unsigned char *)host_start_req,
|
||||
mei_hdr->length)) {
|
||||
dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n");
|
||||
dev->mei_state = MEI_RESETING;
|
||||
mei_reset(dev, 1);
|
||||
}
|
||||
dev->init_clients_state = MEI_START_MESSAGE;
|
||||
dev->init_clients_timer = INIT_CLIENTS_TIMEOUT;
|
||||
return ;
|
||||
}
|
||||
|
||||
/**
|
||||
* host_enum_clients_message - host sends enumeration client request message.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns none.
|
||||
*/
|
||||
void mei_host_enum_clients_message(struct mei_device *dev)
|
||||
{
|
||||
struct mei_msg_hdr *mei_hdr;
|
||||
struct hbm_host_enum_request *host_enum_req;
|
||||
mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0];
|
||||
/* enumerate clients */
|
||||
mei_hdr->host_addr = 0;
|
||||
mei_hdr->me_addr = 0;
|
||||
mei_hdr->length = sizeof(struct hbm_host_enum_request);
|
||||
mei_hdr->msg_complete = 1;
|
||||
mei_hdr->reserved = 0;
|
||||
|
||||
host_enum_req = (struct hbm_host_enum_request *) &dev->wr_msg_buf[1];
|
||||
memset(host_enum_req, 0, sizeof(struct hbm_host_enum_request));
|
||||
host_enum_req->hbm_cmd = HOST_ENUM_REQ_CMD;
|
||||
if (mei_write_message(dev, mei_hdr, (unsigned char *)host_enum_req,
|
||||
mei_hdr->length)) {
|
||||
dev->mei_state = MEI_RESETING;
|
||||
dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n");
|
||||
mei_reset(dev, 1);
|
||||
}
|
||||
dev->init_clients_state = MEI_ENUM_CLIENTS_MESSAGE;
|
||||
dev->init_clients_timer = INIT_CLIENTS_TIMEOUT;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* allocate_me_clients_storage - allocates storage for me clients
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns none.
|
||||
*/
|
||||
void mei_allocate_me_clients_storage(struct mei_device *dev)
|
||||
{
|
||||
struct mei_me_client *clients;
|
||||
int b;
|
||||
|
||||
/* 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 ;
|
||||
|
||||
|
||||
if (dev->me_clients != NULL) {
|
||||
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_dbg(&dev->pdev->dev, "memory allocation for ME clients failed.\n");
|
||||
dev->mei_state = MEI_RESETING;
|
||||
mei_reset(dev, 1);
|
||||
return ;
|
||||
}
|
||||
dev->me_clients = clients;
|
||||
return ;
|
||||
}
|
||||
/**
|
||||
* host_client_properties - reads properties for client
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns:
|
||||
* < 0 - Error.
|
||||
* = 0 - no more clients.
|
||||
* = 1 - still have clients to send properties request.
|
||||
*/
|
||||
int mei_host_client_properties(struct mei_device *dev)
|
||||
{
|
||||
struct mei_msg_hdr *mei_header;
|
||||
struct hbm_props_request *host_cli_req;
|
||||
int b;
|
||||
u8 client_num = dev->me_client_presentation_num;
|
||||
|
||||
b = dev->me_client_index;
|
||||
b = find_next_bit(dev->me_clients_map, MEI_CLIENTS_MAX, b);
|
||||
if (b < MEI_CLIENTS_MAX) {
|
||||
dev->me_clients[client_num].client_id = b;
|
||||
dev->me_clients[client_num].mei_flow_ctrl_creds = 0;
|
||||
mei_header = (struct mei_msg_hdr *)&dev->wr_msg_buf[0];
|
||||
mei_header->host_addr = 0;
|
||||
mei_header->me_addr = 0;
|
||||
mei_header->length = sizeof(struct hbm_props_request);
|
||||
mei_header->msg_complete = 1;
|
||||
mei_header->reserved = 0;
|
||||
|
||||
host_cli_req = (struct hbm_props_request *)&dev->wr_msg_buf[1];
|
||||
|
||||
memset(host_cli_req, 0, sizeof(struct hbm_props_request));
|
||||
|
||||
host_cli_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD;
|
||||
host_cli_req->address = b;
|
||||
|
||||
if (mei_write_message(dev, mei_header,
|
||||
(unsigned char *)host_cli_req,
|
||||
mei_header->length)) {
|
||||
dev->mei_state = MEI_RESETING;
|
||||
dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n");
|
||||
mei_reset(dev, 1);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev->init_clients_timer = INIT_CLIENTS_TIMEOUT;
|
||||
dev->me_client_index = b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_init_file_private - initializes private file structure.
|
||||
*
|
||||
* @priv: private file structure to be initialized
|
||||
* @file: the file structure
|
||||
*/
|
||||
void mei_cl_init(struct mei_cl *priv, struct mei_device *dev)
|
||||
{
|
||||
memset(priv, 0, sizeof(struct mei_cl));
|
||||
init_waitqueue_head(&priv->wait);
|
||||
init_waitqueue_head(&priv->rx_wait);
|
||||
init_waitqueue_head(&priv->tx_wait);
|
||||
INIT_LIST_HEAD(&priv->link);
|
||||
priv->reading_state = MEI_IDLE;
|
||||
priv->writing_state = MEI_IDLE;
|
||||
priv->dev = dev;
|
||||
}
|
||||
|
||||
int mei_find_me_client_index(const struct mei_device *dev, uuid_le cuuid)
|
||||
{
|
||||
int i, res = -1;
|
||||
|
||||
for (i = 0; i < dev->me_clients_num; ++i)
|
||||
if (uuid_le_cmp(cuuid,
|
||||
dev->me_clients[i].props.protocol_name) == 0) {
|
||||
res = i;
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* mei_find_me_client_update_filext - searches for ME client guid
|
||||
* sets client_id in mei_file_private if found
|
||||
* @dev: the device structure
|
||||
* @priv: private file structure to set client_id in
|
||||
* @cguid: searched guid of ME client
|
||||
* @client_id: id of host client to be set in file private structure
|
||||
*
|
||||
* returns ME client index
|
||||
*/
|
||||
u8 mei_find_me_client_update_filext(struct mei_device *dev, struct mei_cl *priv,
|
||||
const uuid_le *cguid, u8 client_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!dev || !priv || !cguid)
|
||||
return 0;
|
||||
|
||||
/* check for valid client id */
|
||||
i = mei_find_me_client_index(dev, *cguid);
|
||||
if (i >= 0) {
|
||||
priv->me_client_id = dev->me_clients[i].client_id;
|
||||
priv->state = MEI_FILE_CONNECTING;
|
||||
priv->host_client_id = client_id;
|
||||
|
||||
list_add_tail(&priv->link, &dev->file_list);
|
||||
return (u8)i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* host_init_iamthif - mei initialization iamthif client.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
*/
|
||||
void mei_host_init_iamthif(struct mei_device *dev)
|
||||
{
|
||||
u8 i;
|
||||
unsigned char *msg_buf;
|
||||
|
||||
mei_cl_init(&dev->iamthif_cl, dev);
|
||||
dev->iamthif_cl.state = MEI_FILE_DISCONNECTED;
|
||||
|
||||
/* find ME amthi client */
|
||||
i = mei_find_me_client_update_filext(dev, &dev->iamthif_cl,
|
||||
&mei_amthi_guid, MEI_IAMTHIF_HOST_CLIENT_ID);
|
||||
if (dev->iamthif_cl.state != MEI_FILE_CONNECTING) {
|
||||
dev_dbg(&dev->pdev->dev, "failed to find iamthif client.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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_dbg(&dev->pdev->dev, "memory allocation for ME message buffer failed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dev->iamthif_msg_buf = msg_buf;
|
||||
|
||||
if (mei_connect(dev, &dev->iamthif_cl)) {
|
||||
dev_dbg(&dev->pdev->dev, "Failed to connect to AMTHI client\n");
|
||||
dev->iamthif_cl.state = MEI_FILE_DISCONNECTED;
|
||||
dev->iamthif_cl.host_client_id = 0;
|
||||
} else {
|
||||
dev->iamthif_cl.timer_count = CONNECT_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_alloc_file_private - allocates a private file structure and sets it up.
|
||||
* @file: the file structure
|
||||
*
|
||||
* 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_disconnect_host_client - sends disconnect message to fw from host client.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @cl: private data of the file object
|
||||
*
|
||||
* Locking: called under "dev->device_lock" lock
|
||||
*
|
||||
* returns 0 on success, <0 on failure.
|
||||
*/
|
||||
int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl)
|
||||
{
|
||||
int rets, err;
|
||||
long timeout = 15; /* 15 seconds */
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
if (!dev || !cl)
|
||||
return -ENODEV;
|
||||
|
||||
if (cl->state != MEI_FILE_DISCONNECTING)
|
||||
return 0;
|
||||
|
||||
cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL);
|
||||
if (!cb)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&cb->cb_list);
|
||||
cb->file_private = cl;
|
||||
cb->major_file_operations = MEI_CLOSE;
|
||||
if (dev->mei_host_buffer_is_empty) {
|
||||
dev->mei_host_buffer_is_empty = false;
|
||||
if (mei_disconnect(dev, cl)) {
|
||||
rets = -ENODEV;
|
||||
dev_dbg(&dev->pdev->dev, "failed to call mei_disconnect.\n");
|
||||
goto free;
|
||||
}
|
||||
mdelay(10); /* Wait for hardware disconnection ready */
|
||||
list_add_tail(&cb->cb_list, &dev->ctrl_rd_list.mei_cb.cb_list);
|
||||
} else {
|
||||
dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n");
|
||||
list_add_tail(&cb->cb_list,
|
||||
&dev->ctrl_wr_list.mei_cb.cb_list);
|
||||
}
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
err = wait_event_timeout(dev->wait_recvd_msg,
|
||||
(MEI_FILE_DISCONNECTED == cl->state),
|
||||
timeout * HZ);
|
||||
|
||||
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_free_cb_private(cb);
|
||||
return rets;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_remove_client_from_file_list -
|
||||
* removes file private data from device file list
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @host_client_id: host client id to be removed
|
||||
*/
|
||||
void mei_remove_client_from_file_list(struct mei_device *dev,
|
||||
u8 host_client_id)
|
||||
{
|
||||
struct mei_cl *cl_pos = NULL;
|
||||
struct mei_cl *cl_next = NULL;
|
||||
list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) {
|
||||
if (host_client_id == cl_pos->host_client_id) {
|
||||
dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n",
|
||||
cl_pos->host_client_id,
|
||||
cl_pos->me_client_id);
|
||||
list_del_init(&cl_pos->link);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
428
drivers/misc/mei/interface.c
Normal file
428
drivers/misc/mei/interface.c
Normal file
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
*
|
||||
* 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 "mei_dev.h"
|
||||
#include "mei.h"
|
||||
#include "interface.h"
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* mei_set_csr_register - 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
|
||||
*/
|
||||
void mei_hcsr_set(struct mei_device *dev)
|
||||
{
|
||||
if ((dev->host_hw_state & H_IS) == H_IS)
|
||||
dev->host_hw_state &= ~H_IS;
|
||||
mei_reg_write(dev, H_CSR, dev->host_hw_state);
|
||||
dev->host_hw_state = mei_hcsr_read(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_csr_enable_interrupts - enables mei device interrupts
|
||||
*
|
||||
* @dev: the device structure
|
||||
*/
|
||||
void mei_enable_interrupts(struct mei_device *dev)
|
||||
{
|
||||
dev->host_hw_state |= H_IE;
|
||||
mei_hcsr_set(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_csr_disable_interrupts - disables mei device interrupts
|
||||
*
|
||||
* @dev: the device structure
|
||||
*/
|
||||
void mei_disable_interrupts(struct mei_device *dev)
|
||||
{
|
||||
dev->host_hw_state &= ~H_IE;
|
||||
mei_hcsr_set(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* _host_get_filled_slots - gets number of device filled buffer slots
|
||||
*
|
||||
* @device: the device structure
|
||||
*
|
||||
* returns number of filled slots
|
||||
*/
|
||||
static unsigned char _host_get_filled_slots(const struct mei_device *dev)
|
||||
{
|
||||
char read_ptr, write_ptr;
|
||||
|
||||
read_ptr = (char) ((dev->host_hw_state & H_CBRP) >> 8);
|
||||
write_ptr = (char) ((dev->host_hw_state & H_CBWP) >> 16);
|
||||
|
||||
return (unsigned char) (write_ptr - read_ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_host_buffer_is_empty - checks if host buffer is empty.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns 1 if empty, 0 - otherwise.
|
||||
*/
|
||||
int mei_host_buffer_is_empty(struct mei_device *dev)
|
||||
{
|
||||
unsigned char filled_slots;
|
||||
|
||||
dev->host_hw_state = mei_hcsr_read(dev);
|
||||
filled_slots = _host_get_filled_slots(dev);
|
||||
|
||||
if (filled_slots == 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_count_empty_write_slots - counts write empty slots.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count
|
||||
*/
|
||||
int mei_count_empty_write_slots(struct mei_device *dev)
|
||||
{
|
||||
unsigned char buffer_depth, filled_slots, empty_slots;
|
||||
|
||||
dev->host_hw_state = mei_hcsr_read(dev);
|
||||
buffer_depth = (unsigned char) ((dev->host_hw_state & H_CBD) >> 24);
|
||||
filled_slots = _host_get_filled_slots(dev);
|
||||
empty_slots = buffer_depth - filled_slots;
|
||||
|
||||
/* check for overflow */
|
||||
if (filled_slots > buffer_depth)
|
||||
return -EOVERFLOW;
|
||||
|
||||
return empty_slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_write_message - writes a message to mei device.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @header: header of message
|
||||
* @write_buffer: message buffer will be written
|
||||
* @write_length: message size will be written
|
||||
*
|
||||
* This function returns -EIO if write has failed
|
||||
*/
|
||||
int mei_write_message(struct mei_device *dev,
|
||||
struct mei_msg_hdr *header,
|
||||
unsigned char *write_buffer,
|
||||
unsigned long write_length)
|
||||
{
|
||||
u32 temp_msg = 0;
|
||||
unsigned long bytes_written = 0;
|
||||
unsigned char buffer_depth, filled_slots, empty_slots;
|
||||
unsigned long dw_to_write;
|
||||
|
||||
dev->host_hw_state = mei_hcsr_read(dev);
|
||||
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"host_hw_state = 0x%08x.\n",
|
||||
dev->host_hw_state);
|
||||
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"mei_write_message header=%08x.\n",
|
||||
*((u32 *) header));
|
||||
|
||||
buffer_depth = (unsigned char) ((dev->host_hw_state & H_CBD) >> 24);
|
||||
filled_slots = _host_get_filled_slots(dev);
|
||||
empty_slots = buffer_depth - filled_slots;
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"filled = %hu, empty = %hu.\n",
|
||||
filled_slots, empty_slots);
|
||||
|
||||
dw_to_write = ((write_length + 3) / 4);
|
||||
|
||||
if (dw_to_write > empty_slots)
|
||||
return -EIO;
|
||||
|
||||
mei_reg_write(dev, H_CB_WW, *((u32 *) header));
|
||||
|
||||
while (write_length >= 4) {
|
||||
mei_reg_write(dev, H_CB_WW,
|
||||
*(u32 *) (write_buffer + bytes_written));
|
||||
bytes_written += 4;
|
||||
write_length -= 4;
|
||||
}
|
||||
|
||||
if (write_length > 0) {
|
||||
memcpy(&temp_msg, &write_buffer[bytes_written], write_length);
|
||||
mei_reg_write(dev, H_CB_WW, temp_msg);
|
||||
}
|
||||
|
||||
dev->host_hw_state |= H_IG;
|
||||
mei_hcsr_set(dev);
|
||||
dev->me_hw_state = mei_mecsr_read(dev);
|
||||
if ((dev->me_hw_state & ME_RDY_HRA) != ME_RDY_HRA)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_count_full_read_slots - counts read full slots.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count
|
||||
*/
|
||||
int mei_count_full_read_slots(struct mei_device *dev)
|
||||
{
|
||||
char read_ptr, write_ptr;
|
||||
unsigned char buffer_depth, filled_slots;
|
||||
|
||||
dev->me_hw_state = mei_mecsr_read(dev);
|
||||
buffer_depth = (unsigned char)((dev->me_hw_state & ME_CBD_HRA) >> 24);
|
||||
read_ptr = (char) ((dev->me_hw_state & ME_CBRP_HRA) >> 8);
|
||||
write_ptr = (char) ((dev->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_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
|
||||
*/
|
||||
void mei_read_slots(struct mei_device *dev, unsigned char *buffer,
|
||||
unsigned long buffer_length)
|
||||
{
|
||||
u32 *reg_buf = (u32 *)buffer;
|
||||
|
||||
for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32))
|
||||
*reg_buf++ = mei_mecbrw_read(dev);
|
||||
|
||||
if (buffer_length > 0) {
|
||||
u32 reg = mei_mecbrw_read(dev);
|
||||
memcpy(reg_buf, ®, buffer_length);
|
||||
}
|
||||
|
||||
dev->host_hw_state |= H_IG;
|
||||
mei_hcsr_set(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_flow_ctrl_creds - checks flow_control credentials.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @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_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl)
|
||||
{
|
||||
int i;
|
||||
|
||||
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_flow_ctrl_reduce - reduces flow_control.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @cl: private data of the file object
|
||||
* @returns
|
||||
* 0 on success
|
||||
* -ENOENT when me client is not found
|
||||
* -EINVAL when ctrl credits are <= 0
|
||||
*/
|
||||
int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl)
|
||||
{
|
||||
int i;
|
||||
|
||||
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_send_flow_control - sends flow control to fw.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @cl: private data of the file object
|
||||
*
|
||||
* This function returns -EIO on write failure
|
||||
*/
|
||||
int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl)
|
||||
{
|
||||
struct mei_msg_hdr *mei_hdr;
|
||||
struct hbm_flow_control *mei_flow_control;
|
||||
|
||||
mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0];
|
||||
mei_hdr->host_addr = 0;
|
||||
mei_hdr->me_addr = 0;
|
||||
mei_hdr->length = sizeof(struct hbm_flow_control);
|
||||
mei_hdr->msg_complete = 1;
|
||||
mei_hdr->reserved = 0;
|
||||
|
||||
mei_flow_control = (struct hbm_flow_control *) &dev->wr_msg_buf[1];
|
||||
memset(mei_flow_control, 0, sizeof(*mei_flow_control));
|
||||
mei_flow_control->host_addr = cl->host_client_id;
|
||||
mei_flow_control->me_addr = cl->me_client_id;
|
||||
mei_flow_control->hbm_cmd = MEI_FLOW_CONTROL_CMD;
|
||||
memset(mei_flow_control->reserved, 0,
|
||||
sizeof(mei_flow_control->reserved));
|
||||
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,
|
||||
(unsigned char *) mei_flow_control,
|
||||
sizeof(struct hbm_flow_control));
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_other_client_is_connecting - checks if other
|
||||
* client with the same client id is connected.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @cl: private data of the file object
|
||||
*
|
||||
* returns 1 if other client is connected, 0 - otherwise.
|
||||
*/
|
||||
int mei_other_client_is_connecting(struct mei_device *dev,
|
||||
struct mei_cl *cl)
|
||||
{
|
||||
struct mei_cl *cl_pos = NULL;
|
||||
struct mei_cl *cl_next = NULL;
|
||||
|
||||
list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) {
|
||||
if ((cl_pos->state == MEI_FILE_CONNECTING) &&
|
||||
(cl_pos != cl) &&
|
||||
cl->me_client_id == cl_pos->me_client_id)
|
||||
return 1;
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_disconnect - sends disconnect message to fw.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @cl: private data of the file object
|
||||
*
|
||||
* This function returns -EIO on write failure
|
||||
*/
|
||||
int mei_disconnect(struct mei_device *dev, struct mei_cl *cl)
|
||||
{
|
||||
struct mei_msg_hdr *mei_hdr;
|
||||
struct hbm_client_disconnect_request *mei_cli_disconnect;
|
||||
|
||||
mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0];
|
||||
mei_hdr->host_addr = 0;
|
||||
mei_hdr->me_addr = 0;
|
||||
mei_hdr->length = sizeof(struct hbm_client_disconnect_request);
|
||||
mei_hdr->msg_complete = 1;
|
||||
mei_hdr->reserved = 0;
|
||||
|
||||
mei_cli_disconnect =
|
||||
(struct hbm_client_disconnect_request *) &dev->wr_msg_buf[1];
|
||||
memset(mei_cli_disconnect, 0, sizeof(*mei_cli_disconnect));
|
||||
mei_cli_disconnect->host_addr = cl->host_client_id;
|
||||
mei_cli_disconnect->me_addr = cl->me_client_id;
|
||||
mei_cli_disconnect->hbm_cmd = CLIENT_DISCONNECT_REQ_CMD;
|
||||
mei_cli_disconnect->reserved[0] = 0;
|
||||
|
||||
return mei_write_message(dev, mei_hdr,
|
||||
(unsigned char *) mei_cli_disconnect,
|
||||
sizeof(struct hbm_client_disconnect_request));
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_connect - sends connect message to fw.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @cl: private data of the file object
|
||||
*
|
||||
* This function returns -EIO on write failure
|
||||
*/
|
||||
int mei_connect(struct mei_device *dev, struct mei_cl *cl)
|
||||
{
|
||||
struct mei_msg_hdr *mei_hdr;
|
||||
struct hbm_client_connect_request *mei_cli_connect;
|
||||
|
||||
mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0];
|
||||
mei_hdr->host_addr = 0;
|
||||
mei_hdr->me_addr = 0;
|
||||
mei_hdr->length = sizeof(struct hbm_client_connect_request);
|
||||
mei_hdr->msg_complete = 1;
|
||||
mei_hdr->reserved = 0;
|
||||
|
||||
mei_cli_connect =
|
||||
(struct hbm_client_connect_request *) &dev->wr_msg_buf[1];
|
||||
mei_cli_connect->host_addr = cl->host_client_id;
|
||||
mei_cli_connect->me_addr = cl->me_client_id;
|
||||
mei_cli_connect->hbm_cmd = CLIENT_CONNECT_REQ_CMD;
|
||||
mei_cli_connect->reserved = 0;
|
||||
|
||||
return mei_write_message(dev, mei_hdr,
|
||||
(unsigned char *) mei_cli_connect,
|
||||
sizeof(struct hbm_client_connect_request));
|
||||
}
|
75
drivers/misc/mei/interface.h
Normal file
75
drivers/misc/mei/interface.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
*
|
||||
* 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 "mei.h"
|
||||
#include "mei_dev.h"
|
||||
|
||||
|
||||
#define AMT_WD_DEFAULT_TIMEOUT 120 /* seconds */
|
||||
#define AMT_WD_MIN_TIMEOUT 120 /* seconds */
|
||||
#define AMT_WD_MAX_TIMEOUT 65535 /* seconds */
|
||||
|
||||
#define MEI_WATCHDOG_DATA_SIZE 16
|
||||
#define MEI_START_WD_DATA_SIZE 20
|
||||
#define MEI_WD_PARAMS_SIZE 4
|
||||
|
||||
|
||||
void mei_read_slots(struct mei_device *dev,
|
||||
unsigned char *buffer,
|
||||
unsigned long buffer_length);
|
||||
|
||||
int mei_write_message(struct mei_device *dev,
|
||||
struct mei_msg_hdr *header,
|
||||
unsigned char *write_buffer,
|
||||
unsigned long write_length);
|
||||
|
||||
int mei_host_buffer_is_empty(struct mei_device *dev);
|
||||
|
||||
int mei_count_full_read_slots(struct mei_device *dev);
|
||||
|
||||
int mei_count_empty_write_slots(struct mei_device *dev);
|
||||
|
||||
int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl);
|
||||
|
||||
int mei_wd_send(struct mei_device *dev);
|
||||
int mei_wd_stop(struct mei_device *dev, bool preserve);
|
||||
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);
|
||||
|
||||
int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl);
|
||||
|
||||
int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl);
|
||||
|
||||
int mei_disconnect(struct mei_device *dev, struct mei_cl *cl);
|
||||
int mei_other_client_is_connecting(struct mei_device *dev, struct mei_cl *cl);
|
||||
int mei_connect(struct mei_device *dev, struct mei_cl *cl);
|
||||
|
||||
#endif /* _MEI_INTERFACE_H_ */
|
1590
drivers/misc/mei/interrupt.c
Normal file
1590
drivers/misc/mei/interrupt.c
Normal file
文件差異過大導致無法顯示
Load Diff
590
drivers/misc/mei/iorw.c
Normal file
590
drivers/misc/mei/iorw.c
Normal file
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
*
|
||||
* 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 "mei_dev.h"
|
||||
#include "hw.h"
|
||||
#include "mei.h"
|
||||
#include "interface.h"
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
int mei_ioctl_connect_client(struct file *file,
|
||||
struct mei_connect_client_data *data)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl_cb *cb;
|
||||
struct mei_client *client;
|
||||
struct mei_cl *cl;
|
||||
struct mei_cl *cl_pos = NULL;
|
||||
struct mei_cl *cl_next = NULL;
|
||||
long timeout = CONNECT_TIMEOUT;
|
||||
int i;
|
||||
int err;
|
||||
int rets;
|
||||
|
||||
cl = file->private_data;
|
||||
if (WARN_ON(!cl || !cl->dev))
|
||||
return -ENODEV;
|
||||
|
||||
dev = cl->dev;
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "mei_ioctl_connect_client() Entry\n");
|
||||
|
||||
|
||||
/* buffered ioctl cb */
|
||||
cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL);
|
||||
if (!cb) {
|
||||
rets = -ENOMEM;
|
||||
goto end;
|
||||
}
|
||||
INIT_LIST_HEAD(&cb->cb_list);
|
||||
|
||||
cb->major_file_operations = MEI_IOCTL;
|
||||
|
||||
if (dev->mei_state != MEI_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_find_me_client_index(dev, data->in_client_uuid);
|
||||
if (i >= 0 && !dev->me_clients[i].props.fixed_address) {
|
||||
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 amthi client then we will use the
|
||||
* existing connection
|
||||
*/
|
||||
if (uuid_le_cmp(data->in_client_uuid, mei_amthi_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);
|
||||
list_for_each_entry_safe(cl_pos, cl_next,
|
||||
&dev->file_list, link) {
|
||||
if (mei_cl_cmp_id(cl, cl_pos)) {
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"remove file private data node host"
|
||||
" client = %d, ME client = %d.\n",
|
||||
cl_pos->host_client_id,
|
||||
cl_pos->me_client_id);
|
||||
list_del(&cl_pos->link);
|
||||
}
|
||||
|
||||
}
|
||||
dev_dbg(&dev->pdev->dev, "free file private data memory.\n");
|
||||
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;
|
||||
}
|
||||
|
||||
if (cl->state != MEI_FILE_CONNECTING) {
|
||||
rets = -ENODEV;
|
||||
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");
|
||||
if (dev->mei_host_buffer_is_empty
|
||||
&& !mei_other_client_is_connecting(dev, cl)) {
|
||||
dev_dbg(&dev->pdev->dev, "Sending Connect Message\n");
|
||||
dev->mei_host_buffer_is_empty = false;
|
||||
if (mei_connect(dev, cl)) {
|
||||
dev_dbg(&dev->pdev->dev, "Sending connect message - failed\n");
|
||||
rets = -ENODEV;
|
||||
goto end;
|
||||
} else {
|
||||
dev_dbg(&dev->pdev->dev, "Sending connect message - succeeded\n");
|
||||
cl->timer_count = MEI_CONNECT_TIMEOUT;
|
||||
cb->file_private = cl;
|
||||
list_add_tail(&cb->cb_list,
|
||||
&dev->ctrl_rd_list.mei_cb.
|
||||
cb_list);
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
dev_dbg(&dev->pdev->dev, "Queuing the connect request due to device busy\n");
|
||||
cb->file_private = cl;
|
||||
dev_dbg(&dev->pdev->dev, "add connect cb to control write list.\n");
|
||||
list_add_tail(&cb->cb_list,
|
||||
&dev->ctrl_wr_list.mei_cb.cb_list);
|
||||
}
|
||||
mutex_unlock(&dev->device_lock);
|
||||
err = wait_event_timeout(dev->wait_recvd_msg,
|
||||
(MEI_FILE_CONNECTED == cl->state ||
|
||||
MEI_FILE_DISCONNECTED == cl->state),
|
||||
timeout * HZ);
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
if (MEI_FILE_CONNECTED == cl->state) {
|
||||
dev_dbg(&dev->pdev->dev, "successfully connected to FW client.\n");
|
||||
rets = cl->status;
|
||||
goto end;
|
||||
} else {
|
||||
dev_dbg(&dev->pdev->dev, "failed to connect to FW client.cl->state = %d.\n",
|
||||
cl->state);
|
||||
if (!err) {
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"wait_event_interruptible_timeout failed on client"
|
||||
" connect message fw response message.\n");
|
||||
}
|
||||
rets = -EFAULT;
|
||||
|
||||
mei_io_list_flush(&dev->ctrl_rd_list, cl);
|
||||
mei_io_list_flush(&dev->ctrl_wr_list, cl);
|
||||
goto end;
|
||||
}
|
||||
rets = 0;
|
||||
end:
|
||||
dev_dbg(&dev->pdev->dev, "free connect cb memory.");
|
||||
kfree(cb);
|
||||
return rets;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_amthi_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 *find_amthi_read_list_entry(
|
||||
struct mei_device *dev,
|
||||
struct file *file)
|
||||
{
|
||||
struct mei_cl *cl_temp;
|
||||
struct mei_cl_cb *pos = NULL;
|
||||
struct mei_cl_cb *next = NULL;
|
||||
|
||||
list_for_each_entry_safe(pos, next,
|
||||
&dev->amthi_read_complete_list.mei_cb.cb_list, cb_list) {
|
||||
cl_temp = (struct mei_cl *)pos->file_private;
|
||||
if (cl_temp && cl_temp == &dev->iamthif_cl &&
|
||||
pos->file_object == file)
|
||||
return pos;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* amthi_read - read data from AMTHI 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 amthi_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;
|
||||
}
|
||||
|
||||
for (i = 0; i < dev->me_clients_num; i++) {
|
||||
if (dev->me_clients[i].client_id ==
|
||||
dev->iamthif_cl.me_client_id)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == dev->me_clients_num) {
|
||||
dev_dbg(&dev->pdev->dev, "amthi client not found.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id))
|
||||
return -ENODEV;
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "checking amthi data\n");
|
||||
cb = find_amthi_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 amthi data\n");
|
||||
while (cb == NULL) {
|
||||
/* unlock the Mutex */
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
wait_ret = wait_event_interruptible(dev->iamthif_cl.wait,
|
||||
(cb = find_amthi_read_list_entry(dev, file)));
|
||||
|
||||
if (wait_ret)
|
||||
return -ERESTARTSYS;
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "woke up from sleep\n");
|
||||
|
||||
/* Locking again the Mutex */
|
||||
mutex_lock(&dev->device_lock);
|
||||
}
|
||||
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "Got amthi data\n");
|
||||
dev->iamthif_timer = 0;
|
||||
|
||||
if (cb) {
|
||||
timeout = cb->read_time +
|
||||
msecs_to_jiffies(IAMTHIF_READ_TIMER);
|
||||
dev_dbg(&dev->pdev->dev, "amthi timeout = %lud\n",
|
||||
timeout);
|
||||
|
||||
if (time_after(jiffies, timeout)) {
|
||||
dev_dbg(&dev->pdev->dev, "amthi Time out\n");
|
||||
/* 15 sec for the message has expired */
|
||||
list_del(&cb->cb_list);
|
||||
rets = -ETIMEDOUT;
|
||||
goto free;
|
||||
}
|
||||
}
|
||||
/* if the whole message will fit remove it from the list */
|
||||
if (cb->information >= *offset && length >= (cb->information - *offset))
|
||||
list_del(&cb->cb_list);
|
||||
else if (cb->information > 0 && cb->information <= *offset) {
|
||||
/* end of the message has been reached */
|
||||
list_del(&cb->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, "amthi cb->response_buffer size - %d\n",
|
||||
cb->response_buffer.size);
|
||||
dev_dbg(&dev->pdev->dev, "amthi cb->information - %lu\n",
|
||||
cb->information);
|
||||
|
||||
/* length is being turncated to PAGE_SIZE, however,
|
||||
* the information may be longer */
|
||||
length = min_t(size_t, length, (cb->information - *offset));
|
||||
|
||||
if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length))
|
||||
rets = -EFAULT;
|
||||
else {
|
||||
rets = length;
|
||||
if ((*offset + length) < cb->information) {
|
||||
*offset += length;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
free:
|
||||
dev_dbg(&dev->pdev->dev, "free amthi cb memory.\n");
|
||||
*offset = 0;
|
||||
mei_free_cb_private(cb);
|
||||
out:
|
||||
return rets;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_start_read - the start read client message function.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @if_num: minor number
|
||||
* @cl: private data of the file object
|
||||
*
|
||||
* returns 0 on success, <0 on failure.
|
||||
*/
|
||||
int mei_start_read(struct mei_device *dev, struct mei_cl *cl)
|
||||
{
|
||||
struct mei_cl_cb *cb;
|
||||
int rets = 0;
|
||||
int i;
|
||||
|
||||
if (cl->state != MEI_FILE_CONNECTED)
|
||||
return -ENODEV;
|
||||
|
||||
if (dev->mei_state != MEI_ENABLED)
|
||||
return -ENODEV;
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "check if read is pending.\n");
|
||||
if (cl->read_pending || cl->read_cb) {
|
||||
dev_dbg(&dev->pdev->dev, "read is pending.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL);
|
||||
if (!cb)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "allocation call back successful. host client = %d, ME client = %d\n",
|
||||
cl->host_client_id, cl->me_client_id);
|
||||
|
||||
for (i = 0; i < dev->me_clients_num; i++) {
|
||||
if (dev->me_clients[i].client_id == cl->me_client_id)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) {
|
||||
rets = -ENODEV;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (i == dev->me_clients_num) {
|
||||
rets = -ENODEV;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
cb->response_buffer.size = dev->me_clients[i].props.max_msg_length;
|
||||
cb->response_buffer.data =
|
||||
kmalloc(cb->response_buffer.size, GFP_KERNEL);
|
||||
if (!cb->response_buffer.data) {
|
||||
rets = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
dev_dbg(&dev->pdev->dev, "allocation call back data success.\n");
|
||||
cb->major_file_operations = MEI_READ;
|
||||
/* make sure information is zero before we start */
|
||||
cb->information = 0;
|
||||
cb->file_private = (void *) cl;
|
||||
cl->read_cb = cb;
|
||||
if (dev->mei_host_buffer_is_empty) {
|
||||
dev->mei_host_buffer_is_empty = false;
|
||||
if (mei_send_flow_control(dev, cl)) {
|
||||
rets = -ENODEV;
|
||||
goto unlock;
|
||||
}
|
||||
list_add_tail(&cb->cb_list, &dev->read_list.mei_cb.cb_list);
|
||||
} else {
|
||||
list_add_tail(&cb->cb_list, &dev->ctrl_wr_list.mei_cb.cb_list);
|
||||
}
|
||||
return rets;
|
||||
unlock:
|
||||
mei_free_cb_private(cb);
|
||||
return rets;
|
||||
}
|
||||
|
||||
/**
|
||||
* amthi_write - write iamthif data to amthi client
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @cb: mei call back struct
|
||||
*
|
||||
* returns 0 on success, <0 on failure.
|
||||
*/
|
||||
int amthi_write(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 amthi 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_flow_ctrl_creds(dev, &dev->iamthif_cl);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret && dev->mei_host_buffer_is_empty) {
|
||||
ret = 0;
|
||||
dev->mei_host_buffer_is_empty = false;
|
||||
if (cb->request_buffer.size >
|
||||
(((dev->host_hw_state & H_CBD) >> 24) * sizeof(u32))
|
||||
-sizeof(struct mei_msg_hdr)) {
|
||||
mei_hdr.length =
|
||||
(((dev->host_hw_state & H_CBD) >> 24) *
|
||||
sizeof(u32)) - sizeof(struct mei_msg_hdr);
|
||||
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),
|
||||
mei_hdr.length))
|
||||
return -ENODEV;
|
||||
|
||||
if (mei_hdr.msg_complete) {
|
||||
if (mei_flow_ctrl_reduce(dev, &dev->iamthif_cl))
|
||||
return -ENODEV;
|
||||
dev->iamthif_flow_control_pending = true;
|
||||
dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL;
|
||||
dev_dbg(&dev->pdev->dev, "add amthi cb to write waiting list\n");
|
||||
dev->iamthif_current_cb = cb;
|
||||
dev->iamthif_file_object = cb->file_object;
|
||||
list_add_tail(&cb->cb_list,
|
||||
&dev->write_waiting_list.mei_cb.cb_list);
|
||||
} else {
|
||||
dev_dbg(&dev->pdev->dev, "message does not complete, "
|
||||
"so add amthi cb to write list.\n");
|
||||
list_add_tail(&cb->cb_list,
|
||||
&dev->write_list.mei_cb.cb_list);
|
||||
}
|
||||
} else {
|
||||
if (!(dev->mei_host_buffer_is_empty))
|
||||
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->cb_list, &dev->write_list.mei_cb.cb_list);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* iamthif_ioctl_send_msg - send cmd data to amthi client
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns 0 on success, <0 on failure.
|
||||
*/
|
||||
void mei_run_next_iamthif_cmd(struct mei_device *dev)
|
||||
{
|
||||
struct mei_cl *cl_tmp;
|
||||
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 amthi cmd_list cb.\n");
|
||||
|
||||
list_for_each_entry_safe(pos, next,
|
||||
&dev->amthi_cmd_list.mei_cb.cb_list, cb_list) {
|
||||
list_del(&pos->cb_list);
|
||||
cl_tmp = (struct mei_cl *)pos->file_private;
|
||||
|
||||
if (cl_tmp && cl_tmp == &dev->iamthif_cl) {
|
||||
status = amthi_write(dev, pos);
|
||||
if (status) {
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"amthi write failed status = %d\n",
|
||||
status);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_free_cb_private - free mei_cb_private related memory
|
||||
*
|
||||
* @cb: mei callback struct
|
||||
*/
|
||||
void mei_free_cb_private(struct mei_cl_cb *cb)
|
||||
{
|
||||
if (cb == NULL)
|
||||
return;
|
||||
|
||||
kfree(cb->request_buffer.data);
|
||||
kfree(cb->response_buffer.data);
|
||||
kfree(cb);
|
||||
}
|
1230
drivers/misc/mei/main.c
Normal file
1230
drivers/misc/mei/main.c
Normal file
文件差異過大導致無法顯示
Load Diff
481
drivers/misc/mei/mei-amt-version.c
Normal file
481
drivers/misc/mei/mei-amt-version.c
Normal file
@@ -0,0 +1,481 @@
|
||||
/******************************************************************************
|
||||
* 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) 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.
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <bits/wordsize.h>
|
||||
#include "mei.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* Intel Management Engine Interface
|
||||
*****************************************************************************/
|
||||
|
||||
#define mei_msg(_me, fmt, ARGS...) do { \
|
||||
if (_me->verbose) \
|
||||
fprintf(stderr, fmt, ##ARGS); \
|
||||
} while (0)
|
||||
|
||||
#define mei_err(_me, fmt, ARGS...) do { \
|
||||
fprintf(stderr, "Error: " fmt, ##ARGS); \
|
||||
} while (0)
|
||||
|
||||
struct mei {
|
||||
uuid_le guid;
|
||||
bool initialized;
|
||||
bool verbose;
|
||||
unsigned int buf_size;
|
||||
unsigned char prot_ver;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static void mei_deinit(struct mei *cl)
|
||||
{
|
||||
if (cl->fd != -1)
|
||||
close(cl->fd);
|
||||
cl->fd = -1;
|
||||
cl->buf_size = 0;
|
||||
cl->prot_ver = 0;
|
||||
cl->initialized = false;
|
||||
}
|
||||
|
||||
static bool mei_init(struct mei *me, const uuid_le *guid,
|
||||
unsigned char req_protocol_version, bool verbose)
|
||||
{
|
||||
int result;
|
||||
struct mei_client *cl;
|
||||
struct mei_connect_client_data data;
|
||||
|
||||
mei_deinit(me);
|
||||
|
||||
me->verbose = verbose;
|
||||
|
||||
me->fd = open("/dev/mei", O_RDWR);
|
||||
if (me->fd == -1) {
|
||||
mei_err(me, "Cannot establish a handle to the Intel MEI driver\n");
|
||||
goto err;
|
||||
}
|
||||
memcpy(&me->guid, guid, sizeof(*guid));
|
||||
memset(&data, 0, sizeof(data));
|
||||
me->initialized = true;
|
||||
|
||||
memcpy(&data.in_client_uuid, &me->guid, sizeof(me->guid));
|
||||
result = ioctl(me->fd, IOCTL_MEI_CONNECT_CLIENT, &data);
|
||||
if (result) {
|
||||
mei_err(me, "IOCTL_MEI_CONNECT_CLIENT receive message. err=%d\n", result);
|
||||
goto err;
|
||||
}
|
||||
cl = &data.out_client_properties;
|
||||
mei_msg(me, "max_message_length %d\n", cl->max_msg_length);
|
||||
mei_msg(me, "protocol_version %d\n", cl->protocol_version);
|
||||
|
||||
if ((req_protocol_version > 0) &&
|
||||
(cl->protocol_version != req_protocol_version)) {
|
||||
mei_err(me, "Intel MEI protocol version not supported\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
me->buf_size = cl->max_msg_length;
|
||||
me->prot_ver = cl->protocol_version;
|
||||
|
||||
return true;
|
||||
err:
|
||||
mei_deinit(me);
|
||||
return false;
|
||||
}
|
||||
|
||||
static ssize_t mei_recv_msg(struct mei *me, unsigned char *buffer,
|
||||
ssize_t len, unsigned long timeout)
|
||||
{
|
||||
ssize_t rc;
|
||||
|
||||
mei_msg(me, "call read length = %zd\n", len);
|
||||
|
||||
rc = read(me->fd, buffer, len);
|
||||
if (rc < 0) {
|
||||
mei_err(me, "read failed with status %zd %s\n",
|
||||
rc, strerror(errno));
|
||||
mei_deinit(me);
|
||||
} else {
|
||||
mei_msg(me, "read succeeded with result %zd\n", rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t mei_send_msg(struct mei *me, const unsigned char *buffer,
|
||||
ssize_t len, unsigned long timeout)
|
||||
{
|
||||
struct timeval tv;
|
||||
ssize_t written;
|
||||
ssize_t rc;
|
||||
fd_set set;
|
||||
|
||||
tv.tv_sec = timeout / 1000;
|
||||
tv.tv_usec = (timeout % 1000) * 1000000;
|
||||
|
||||
mei_msg(me, "call write length = %zd\n", len);
|
||||
|
||||
written = write(me->fd, buffer, len);
|
||||
if (written < 0) {
|
||||
rc = -errno;
|
||||
mei_err(me, "write failed with status %zd %s\n",
|
||||
written, strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
FD_ZERO(&set);
|
||||
FD_SET(me->fd, &set);
|
||||
rc = select(me->fd + 1 , &set, NULL, NULL, &tv);
|
||||
if (rc > 0 && FD_ISSET(me->fd, &set)) {
|
||||
mei_msg(me, "write success\n");
|
||||
} else if (rc == 0) {
|
||||
mei_err(me, "write failed on timeout with status\n");
|
||||
goto out;
|
||||
} else { /* rc < 0 */
|
||||
mei_err(me, "write failed on select with status %zd\n", rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = written;
|
||||
out:
|
||||
if (rc < 0)
|
||||
mei_deinit(me);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Intel Advanced Management Technolgy ME Client
|
||||
***************************************************************************/
|
||||
|
||||
#define AMT_MAJOR_VERSION 1
|
||||
#define AMT_MINOR_VERSION 1
|
||||
|
||||
#define AMT_STATUS_SUCCESS 0x0
|
||||
#define AMT_STATUS_INTERNAL_ERROR 0x1
|
||||
#define AMT_STATUS_NOT_READY 0x2
|
||||
#define AMT_STATUS_INVALID_AMT_MODE 0x3
|
||||
#define AMT_STATUS_INVALID_MESSAGE_LENGTH 0x4
|
||||
|
||||
#define AMT_STATUS_HOST_IF_EMPTY_RESPONSE 0x4000
|
||||
#define AMT_STATUS_SDK_RESOURCES 0x1004
|
||||
|
||||
|
||||
#define AMT_BIOS_VERSION_LEN 65
|
||||
#define AMT_VERSIONS_NUMBER 50
|
||||
#define AMT_UNICODE_STRING_LEN 20
|
||||
|
||||
struct amt_unicode_string {
|
||||
uint16_t length;
|
||||
char string[AMT_UNICODE_STRING_LEN];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct amt_version_type {
|
||||
struct amt_unicode_string description;
|
||||
struct amt_unicode_string version;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct amt_version {
|
||||
uint8_t major;
|
||||
uint8_t minor;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct amt_code_versions {
|
||||
uint8_t bios[AMT_BIOS_VERSION_LEN];
|
||||
uint32_t count;
|
||||
struct amt_version_type versions[AMT_VERSIONS_NUMBER];
|
||||
} __attribute__((packed));
|
||||
|
||||
/***************************************************************************
|
||||
* Intel Advanced Management Technolgy Host Interface
|
||||
***************************************************************************/
|
||||
|
||||
struct amt_host_if_msg_header {
|
||||
struct amt_version version;
|
||||
uint16_t _reserved;
|
||||
uint32_t command;
|
||||
uint32_t length;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct amt_host_if_resp_header {
|
||||
struct amt_host_if_msg_header header;
|
||||
uint32_t status;
|
||||
unsigned char data[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
const uuid_le MEI_IAMTHIF = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, \
|
||||
0xac, 0xa8, 0x46, 0xe0, 0xff, 0x65, 0x81, 0x4c);
|
||||
|
||||
#define AMT_HOST_IF_CODE_VERSIONS_REQUEST 0x0400001A
|
||||
#define AMT_HOST_IF_CODE_VERSIONS_RESPONSE 0x0480001A
|
||||
|
||||
const struct amt_host_if_msg_header CODE_VERSION_REQ = {
|
||||
.version = {AMT_MAJOR_VERSION, AMT_MINOR_VERSION},
|
||||
._reserved = 0,
|
||||
.command = AMT_HOST_IF_CODE_VERSIONS_REQUEST,
|
||||
.length = 0
|
||||
};
|
||||
|
||||
|
||||
struct amt_host_if {
|
||||
struct mei mei_cl;
|
||||
unsigned long send_timeout;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
|
||||
static bool amt_host_if_init(struct amt_host_if *acmd,
|
||||
unsigned long send_timeout, bool verbose)
|
||||
{
|
||||
acmd->send_timeout = (send_timeout) ? send_timeout : 20000;
|
||||
acmd->initialized = mei_init(&acmd->mei_cl, &MEI_IAMTHIF, 0, verbose);
|
||||
return acmd->initialized;
|
||||
}
|
||||
|
||||
static void amt_host_if_deinit(struct amt_host_if *acmd)
|
||||
{
|
||||
mei_deinit(&acmd->mei_cl);
|
||||
acmd->initialized = false;
|
||||
}
|
||||
|
||||
static uint32_t amt_verify_code_versions(const struct amt_host_if_resp_header *resp)
|
||||
{
|
||||
uint32_t status = AMT_STATUS_SUCCESS;
|
||||
struct amt_code_versions *code_ver;
|
||||
size_t code_ver_len;
|
||||
uint32_t ver_type_cnt;
|
||||
uint32_t len;
|
||||
uint32_t i;
|
||||
|
||||
code_ver = (struct amt_code_versions *)resp->data;
|
||||
/* length - sizeof(status) */
|
||||
code_ver_len = resp->header.length - sizeof(uint32_t);
|
||||
ver_type_cnt = code_ver_len -
|
||||
sizeof(code_ver->bios) -
|
||||
sizeof(code_ver->count);
|
||||
if (code_ver->count != ver_type_cnt / sizeof(struct amt_version_type)) {
|
||||
status = AMT_STATUS_INTERNAL_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < code_ver->count; i++) {
|
||||
len = code_ver->versions[i].description.length;
|
||||
|
||||
if (len > AMT_UNICODE_STRING_LEN) {
|
||||
status = AMT_STATUS_INTERNAL_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
len = code_ver->versions[i].version.length;
|
||||
if (code_ver->versions[i].version.string[len] != '\0' ||
|
||||
len != strlen(code_ver->versions[i].version.string)) {
|
||||
status = AMT_STATUS_INTERNAL_ERROR;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
out:
|
||||
return status;
|
||||
}
|
||||
|
||||
static uint32_t amt_verify_response_header(uint32_t command,
|
||||
const struct amt_host_if_msg_header *resp_hdr,
|
||||
uint32_t response_size)
|
||||
{
|
||||
if (response_size < sizeof(struct amt_host_if_resp_header)) {
|
||||
return AMT_STATUS_INTERNAL_ERROR;
|
||||
} else if (response_size != (resp_hdr->length +
|
||||
sizeof(struct amt_host_if_msg_header))) {
|
||||
return AMT_STATUS_INTERNAL_ERROR;
|
||||
} else if (resp_hdr->command != command) {
|
||||
return AMT_STATUS_INTERNAL_ERROR;
|
||||
} else if (resp_hdr->_reserved != 0) {
|
||||
return AMT_STATUS_INTERNAL_ERROR;
|
||||
} else if (resp_hdr->version.major != AMT_MAJOR_VERSION ||
|
||||
resp_hdr->version.minor < AMT_MINOR_VERSION) {
|
||||
return AMT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
return AMT_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint32_t amt_host_if_call(struct amt_host_if *acmd,
|
||||
const unsigned char *command, ssize_t command_sz,
|
||||
uint8_t **read_buf, uint32_t rcmd,
|
||||
unsigned int expected_sz)
|
||||
{
|
||||
uint32_t in_buf_sz;
|
||||
uint32_t out_buf_sz;
|
||||
ssize_t written;
|
||||
uint32_t status;
|
||||
struct amt_host_if_resp_header *msg_hdr;
|
||||
|
||||
in_buf_sz = acmd->mei_cl.buf_size;
|
||||
*read_buf = (uint8_t *)malloc(sizeof(uint8_t) * in_buf_sz);
|
||||
if (*read_buf == NULL)
|
||||
return AMT_STATUS_SDK_RESOURCES;
|
||||
memset(*read_buf, 0, in_buf_sz);
|
||||
msg_hdr = (struct amt_host_if_resp_header *)*read_buf;
|
||||
|
||||
written = mei_send_msg(&acmd->mei_cl,
|
||||
command, command_sz, acmd->send_timeout);
|
||||
if (written != command_sz)
|
||||
return AMT_STATUS_INTERNAL_ERROR;
|
||||
|
||||
out_buf_sz = mei_recv_msg(&acmd->mei_cl, *read_buf, in_buf_sz, 2000);
|
||||
if (out_buf_sz <= 0)
|
||||
return AMT_STATUS_HOST_IF_EMPTY_RESPONSE;
|
||||
|
||||
status = msg_hdr->status;
|
||||
if (status != AMT_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = amt_verify_response_header(rcmd,
|
||||
&msg_hdr->header, out_buf_sz);
|
||||
if (status != AMT_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
if (expected_sz && expected_sz != out_buf_sz)
|
||||
return AMT_STATUS_INTERNAL_ERROR;
|
||||
|
||||
return AMT_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static uint32_t amt_get_code_versions(struct amt_host_if *cmd,
|
||||
struct amt_code_versions *versions)
|
||||
{
|
||||
struct amt_host_if_resp_header *response = NULL;
|
||||
uint32_t status;
|
||||
|
||||
status = amt_host_if_call(cmd,
|
||||
(const unsigned char *)&CODE_VERSION_REQ,
|
||||
sizeof(CODE_VERSION_REQ),
|
||||
(uint8_t **)&response,
|
||||
AMT_HOST_IF_CODE_VERSIONS_RESPONSE, 0);
|
||||
|
||||
if (status != AMT_STATUS_SUCCESS)
|
||||
goto out;
|
||||
|
||||
status = amt_verify_code_versions(response);
|
||||
if (status != AMT_STATUS_SUCCESS)
|
||||
goto out;
|
||||
|
||||
memcpy(versions, response->data, sizeof(struct amt_code_versions));
|
||||
out:
|
||||
if (response != NULL)
|
||||
free(response);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/************************** end of amt_host_if_command ***********************/
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct amt_code_versions ver;
|
||||
struct amt_host_if acmd;
|
||||
unsigned int i;
|
||||
uint32_t status;
|
||||
int ret;
|
||||
bool verbose;
|
||||
|
||||
verbose = (argc > 1 && strcmp(argv[1], "-v") == 0);
|
||||
|
||||
if (!amt_host_if_init(&acmd, 5000, verbose)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
status = amt_get_code_versions(&acmd, &ver);
|
||||
|
||||
amt_host_if_deinit(&acmd);
|
||||
|
||||
switch (status) {
|
||||
case AMT_STATUS_HOST_IF_EMPTY_RESPONSE:
|
||||
printf("Intel AMT: DISABLED\n");
|
||||
ret = 0;
|
||||
break;
|
||||
case AMT_STATUS_SUCCESS:
|
||||
printf("Intel AMT: ENABLED\n");
|
||||
for (i = 0; i < ver.count; i++) {
|
||||
printf("%s:\t%s\n", ver.versions[i].description.string,
|
||||
ver.versions[i].version.string);
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
printf("An error has occurred\n");
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
110
drivers/misc/mei/mei.h
Normal file
110
drivers/misc/mei/mei.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/******************************************************************************
|
||||
* 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 _LINUX_MEI_H
|
||||
#define _LINUX_MEI_H
|
||||
|
||||
#include <linux/uuid.h>
|
||||
|
||||
/*
|
||||
* This IOCTL is used to associate the current file descriptor with a
|
||||
* FW Client (given by UUID). This opens a communication channel
|
||||
* between a host client and a FW client. From this point every read and write
|
||||
* will communicate with the associated FW client.
|
||||
* Only in close() (file_operation release()) the communication between
|
||||
* the clients is disconnected
|
||||
*
|
||||
* The IOCTL argument is a struct with a union that contains
|
||||
* the input parameter and the output parameter for this IOCTL.
|
||||
*
|
||||
* The input parameter is UUID of the FW Client.
|
||||
* The output parameter is the properties of the FW client
|
||||
* (FW protocol version and max message size).
|
||||
*
|
||||
*/
|
||||
#define IOCTL_MEI_CONNECT_CLIENT \
|
||||
_IOWR('H' , 0x01, struct mei_connect_client_data)
|
||||
|
||||
/*
|
||||
* Intel MEI client information struct
|
||||
*/
|
||||
struct mei_client {
|
||||
__u32 max_msg_length;
|
||||
__u8 protocol_version;
|
||||
__u8 reserved[3];
|
||||
};
|
||||
|
||||
/*
|
||||
* IOCTL Connect Client Data structure
|
||||
*/
|
||||
struct mei_connect_client_data {
|
||||
union {
|
||||
uuid_le in_client_uuid;
|
||||
struct mei_client out_client_properties;
|
||||
};
|
||||
};
|
||||
|
||||
#endif /* _LINUX_MEI_H */
|
215
drivers/misc/mei/mei.txt
Normal file
215
drivers/misc/mei/mei.txt
Normal file
@@ -0,0 +1,215 @@
|
||||
Intel(R) Management Engine Interface (Intel(R) MEI)
|
||||
=======================
|
||||
|
||||
Introduction
|
||||
=======================
|
||||
|
||||
The Intel Management Engine (Intel ME) is an isolated and protected computing
|
||||
resource (Co-processor) residing inside certain Intel chipsets. The Intel ME
|
||||
provides support for computer/IT management features. The feature set
|
||||
depends on the Intel chipset SKU.
|
||||
|
||||
The Intel Management Engine Interface (Intel MEI, previously known as HECI)
|
||||
is the interface between the Host and Intel ME. This interface is exposed
|
||||
to the host as a PCI device. The Intel MEI Driver is in charge of the
|
||||
communication channel between a host application and the Intel ME feature.
|
||||
|
||||
Each Intel ME feature (Intel ME Client) is addressed by a GUID/UUID and
|
||||
each client has its own protocol. The protocol is message-based with a
|
||||
header and payload up to 512 bytes.
|
||||
|
||||
Prominent usage of the Intel ME Interface is to communicate with Intel(R)
|
||||
Active Management Technology (Intel AMT)implemented in firmware running on
|
||||
the Intel ME.
|
||||
|
||||
Intel AMT provides the ability to manage a host remotely out-of-band (OOB)
|
||||
even when the operating system running on the host processor has crashed or
|
||||
is in a sleep state.
|
||||
|
||||
Some examples of Intel AMT usage are:
|
||||
- Monitoring hardware state and platform components
|
||||
- Remote power off/on (useful for green computing or overnight IT
|
||||
maintenance)
|
||||
- OS updates
|
||||
- Storage of useful platform information such as software assets
|
||||
- Built-in hardware KVM
|
||||
- Selective network isolation of Ethernet and IP protocol flows based
|
||||
on policies set by a remote management console
|
||||
- IDE device redirection from remote management console
|
||||
|
||||
Intel AMT (OOB) communication is based on SOAP (deprecated
|
||||
starting with Release 6.0) over HTTP/S or WS-Management protocol over
|
||||
HTTP/S that are received from a remote management console application.
|
||||
|
||||
For more information about Intel AMT:
|
||||
http://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide
|
||||
|
||||
Intel MEI Driver
|
||||
=======================
|
||||
|
||||
The driver exposes a misc device called /dev/mei.
|
||||
|
||||
An application maintains communication with an Intel ME feature while
|
||||
/dev/mei is open. The binding to a specific features is performed by calling
|
||||
MEI_CONNECT_CLIENT_IOCTL, which passes the desired UUID.
|
||||
The number of instances of an Intel ME feature that can be opened
|
||||
at the same time depends on the Intel ME feature, but most of the
|
||||
features allow only a single instance.
|
||||
|
||||
The Intel AMT Host Interface (Intel AMTHI) feature supports multiple
|
||||
simultaneous user applications. Therefore, the Intel MEI driver handles
|
||||
this internally by maintaining request queues for the applications.
|
||||
|
||||
The driver is oblivious to data that is passed between firmware feature
|
||||
and host application.
|
||||
|
||||
Because some of the Intel ME features can change the system
|
||||
configuration, the driver by default allows only a privileged
|
||||
user to access it.
|
||||
|
||||
A code snippet for an application communicating with
|
||||
Intel AMTHI client:
|
||||
struct mei_connect_client_data data;
|
||||
fd = open(MEI_DEVICE);
|
||||
|
||||
data.d.in_client_uuid = AMTHI_UUID;
|
||||
|
||||
ioctl(fd, IOCTL_MEI_CONNECT_CLIENT, &data);
|
||||
|
||||
printf("Ver=%d, MaxLen=%ld\n",
|
||||
data.d.in_client_uuid.protocol_version,
|
||||
data.d.in_client_uuid.max_msg_length);
|
||||
|
||||
[...]
|
||||
|
||||
write(fd, amthi_req_data, amthi_req_data_len);
|
||||
|
||||
[...]
|
||||
|
||||
read(fd, &amthi_res_data, amthi_res_data_len);
|
||||
|
||||
[...]
|
||||
close(fd);
|
||||
|
||||
IOCTL:
|
||||
======
|
||||
The Intel MEI Driver supports the following IOCTL command:
|
||||
IOCTL_MEI_CONNECT_CLIENT Connect to firmware Feature (client).
|
||||
|
||||
usage:
|
||||
struct mei_connect_client_data clientData;
|
||||
ioctl(fd, IOCTL_MEI_CONNECT_CLIENT, &clientData);
|
||||
|
||||
inputs:
|
||||
mei_connect_client_data struct contain the following
|
||||
input field:
|
||||
|
||||
in_client_uuid - UUID of the FW Feature that needs
|
||||
to connect to.
|
||||
outputs:
|
||||
out_client_properties - Client Properties: MTU and Protocol Version.
|
||||
|
||||
error returns:
|
||||
EINVAL Wrong IOCTL Number
|
||||
ENODEV Device or Connection is not initialized or ready.
|
||||
(e.g. Wrong UUID)
|
||||
ENOMEM Unable to allocate memory to client internal data.
|
||||
EFAULT Fatal Error (e.g. Unable to access user input data)
|
||||
EBUSY Connection Already Open
|
||||
|
||||
Notes:
|
||||
max_msg_length (MTU) in client properties describes the maximum
|
||||
data that can be sent or received. (e.g. if MTU=2K, can send
|
||||
requests up to bytes 2k and received responses upto 2k bytes).
|
||||
|
||||
Intel ME Applications:
|
||||
==============
|
||||
|
||||
1) Intel Local Management Service (Intel LMS)
|
||||
|
||||
Applications running locally on the platform communicate with Intel AMT Release
|
||||
2.0 and later releases in the same way that network applications do via SOAP
|
||||
over HTTP (deprecated starting with Release 6.0) or with WS-Management over
|
||||
SOAP over HTTP. This means that some Intel AMT features can be accessed from a
|
||||
local application using the same network interface as a remote application
|
||||
communicating with Intel AMT over the network.
|
||||
|
||||
When a local application sends a message addressed to the local Intel AMT host
|
||||
name, the Intel LMS, which listens for traffic directed to the host name,
|
||||
intercepts the message and routes it to the Intel MEI.
|
||||
For more information:
|
||||
http://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide
|
||||
Under "About Intel AMT" => "Local Access"
|
||||
|
||||
For downloading Intel LMS:
|
||||
http://software.intel.com/en-us/articles/download-the-latest-intel-amt-open-source-drivers/
|
||||
|
||||
The Intel LMS opens a connection using the Intel MEI driver to the Intel LMS
|
||||
firmware feature using a defined UUID and then communicates with the feature
|
||||
using a protocol called Intel AMT Port Forwarding Protocol(Intel APF protocol).
|
||||
The protocol is used to maintain multiple sessions with Intel AMT from a
|
||||
single application.
|
||||
|
||||
See the protocol specification in the Intel AMT Software Development Kit(SDK)
|
||||
http://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide
|
||||
Under "SDK Resources" => "Intel(R) vPro(TM) Gateway(MPS)"
|
||||
=> "Information for Intel(R) vPro(TM) Gateway Developers"
|
||||
=> "Description of the Intel AMT Port Forwarding (APF)Protocol"
|
||||
|
||||
2) Intel AMT Remote configuration using a Local Agent
|
||||
A Local Agent enables IT personnel to configure Intel AMT out-of-the-box
|
||||
without requiring installing additional data to enable setup. The remote
|
||||
configuration process may involve an ISV-developed remote configuration
|
||||
agent that runs on the host.
|
||||
For more information:
|
||||
http://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide
|
||||
Under "Setup and Configuration of Intel AMT" =>
|
||||
"SDK Tools Supporting Setup and Configuration" =>
|
||||
"Using the Local Agent Sample"
|
||||
|
||||
An open source Intel AMT configuration utility, implementing a local agent
|
||||
that accesses the Intel MEI driver, can be found here:
|
||||
http://software.intel.com/en-us/articles/download-the-latest-intel-amt-open-source-drivers/
|
||||
|
||||
|
||||
Intel AMT OS Health Watchdog:
|
||||
=============================
|
||||
The Intel AMT Watchdog is an OS Health (Hang/Crash) watchdog.
|
||||
Whenever the OS hangs or crashes, Intel AMT will send an event
|
||||
to any subscriber to this event. This mechanism means that
|
||||
IT knows when a platform crashes even when there is a hard failure on the host.
|
||||
|
||||
The Intel AMT Watchdog is composed of two parts:
|
||||
1) Firmware feature - receives the heartbeats
|
||||
and sends an event when the heartbeats stop.
|
||||
2) Intel MEI driver - connects to the watchdog feature, configures the
|
||||
watchdog and sends the heartbeats.
|
||||
|
||||
The Intel MEI driver uses the kernel watchdog to configure the Intel AMT
|
||||
Watchdog and to send heartbeats to it. The default timeout of the
|
||||
watchdog is 120 seconds.
|
||||
|
||||
If the Intel AMT Watchdog feature does not exist (i.e. the connection failed),
|
||||
the Intel MEI driver will disable the sending of heartbeats.
|
||||
|
||||
Supported Chipsets:
|
||||
==================
|
||||
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
|
||||
|
||||
---
|
||||
linux-mei@linux.intel.com
|
428
drivers/misc/mei/mei_dev.h
Normal file
428
drivers/misc/mei/mei_dev.h
Normal file
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
*
|
||||
* 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 "mei.h"
|
||||
#include "hw.h"
|
||||
|
||||
/*
|
||||
* watch dog definition
|
||||
*/
|
||||
#define MEI_WATCHDOG_DATA_SIZE 16
|
||||
#define MEI_START_WD_DATA_SIZE 20
|
||||
#define MEI_WD_PARAMS_SIZE 4
|
||||
#define MEI_WD_STATE_INDEPENDENCE_MSG_SENT (1 << 0)
|
||||
|
||||
#define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32))
|
||||
|
||||
/*
|
||||
* MEI PCI Device object
|
||||
*/
|
||||
extern struct pci_dev *mei_device;
|
||||
|
||||
|
||||
/*
|
||||
* AMTHI Client UUID
|
||||
*/
|
||||
extern const uuid_le mei_amthi_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 File descriptors/handles
|
||||
* that can be opened to the driver.
|
||||
*
|
||||
* Limit to 253: 255 Total Clients
|
||||
* minus internal client for AMTHI
|
||||
* minus internal client for Watchdog
|
||||
*/
|
||||
#define MEI_MAX_OPEN_HANDLE_COUNT 253
|
||||
|
||||
/*
|
||||
* Number of Maximum MEI Clients
|
||||
*/
|
||||
#define MEI_CLIENTS_MAX 255
|
||||
|
||||
/* 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_states {
|
||||
MEI_INITIALIZING = 0,
|
||||
MEI_INIT_CLIENTS,
|
||||
MEI_ENABLED,
|
||||
MEI_RESETING,
|
||||
MEI_DISABLED,
|
||||
MEI_RECOVERING_FROM_RESET,
|
||||
MEI_POWER_DOWN,
|
||||
MEI_POWER_UP
|
||||
};
|
||||
|
||||
/* init clients states*/
|
||||
enum mei_init_clients_states {
|
||||
MEI_START_MESSAGE = 0,
|
||||
MEI_ENUM_CLIENTS_MESSAGE,
|
||||
MEI_CLIENT_PROPERTIES_MESSAGE
|
||||
};
|
||||
|
||||
enum iamthif_states {
|
||||
MEI_IAMTHIF_IDLE,
|
||||
MEI_IAMTHIF_WRITING,
|
||||
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
|
||||
};
|
||||
|
||||
/* MEI CB */
|
||||
enum mei_cb_major_types {
|
||||
MEI_READ = 0,
|
||||
MEI_WRITE,
|
||||
MEI_IOCTL,
|
||||
MEI_OPEN,
|
||||
MEI_CLOSE
|
||||
};
|
||||
|
||||
/*
|
||||
* Intel MEI message data struct
|
||||
*/
|
||||
struct mei_message_data {
|
||||
u32 size;
|
||||
unsigned char *data;
|
||||
} __packed;
|
||||
|
||||
|
||||
struct mei_cl_cb {
|
||||
struct list_head cb_list;
|
||||
enum mei_cb_major_types major_file_operations;
|
||||
void *file_private;
|
||||
struct mei_message_data request_buffer;
|
||||
struct mei_message_data response_buffer;
|
||||
unsigned long information;
|
||||
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 read_pending;
|
||||
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;
|
||||
};
|
||||
|
||||
struct mei_io_list {
|
||||
struct mei_cl_cb mei_cb;
|
||||
};
|
||||
|
||||
/* MEI private device struct */
|
||||
struct mei_device {
|
||||
struct pci_dev *pdev; /* pointer to pci device struct */
|
||||
/*
|
||||
* lists of queues
|
||||
*/
|
||||
/* array of pointers to aio lists */
|
||||
struct mei_io_list read_list; /* driver read queue */
|
||||
struct mei_io_list write_list; /* driver write queue */
|
||||
struct mei_io_list write_waiting_list; /* write waiting queue */
|
||||
struct mei_io_list ctrl_wr_list; /* managed write IOCTL list */
|
||||
struct mei_io_list ctrl_rd_list; /* managed read IOCTL list */
|
||||
struct mei_io_list amthi_cmd_list; /* amthi list for cmd waiting */
|
||||
|
||||
/* driver managed amthi list for reading completed amthi cmd data */
|
||||
struct mei_io_list amthi_read_complete_list;
|
||||
/*
|
||||
* list of files
|
||||
*/
|
||||
struct list_head file_list;
|
||||
long open_handle_count;
|
||||
/*
|
||||
* memory of device
|
||||
*/
|
||||
unsigned int mem_base;
|
||||
unsigned int mem_length;
|
||||
void __iomem *mem_addr;
|
||||
/*
|
||||
* lock for the device
|
||||
*/
|
||||
struct mutex device_lock; /* device lock */
|
||||
struct delayed_work timer_work; /* MEI timer delayed work (timeouts) */
|
||||
bool recvd_msg;
|
||||
/*
|
||||
* hw states of host and fw(ME)
|
||||
*/
|
||||
u32 host_hw_state;
|
||||
u32 me_hw_state;
|
||||
/*
|
||||
* waiting queue for receive message from FW
|
||||
*/
|
||||
wait_queue_head_t wait_recvd_msg;
|
||||
wait_queue_head_t wait_stop_wd;
|
||||
|
||||
/*
|
||||
* mei device states
|
||||
*/
|
||||
enum mei_states mei_state;
|
||||
enum mei_init_clients_states init_clients_state;
|
||||
u16 init_clients_timer;
|
||||
bool stop;
|
||||
bool need_reset;
|
||||
|
||||
u32 extra_write_index;
|
||||
unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */
|
||||
u32 wr_msg_buf[128]; /* used for control messages */
|
||||
u32 ext_msg_buf[8]; /* for control responses */
|
||||
u32 rd_msg_hdr;
|
||||
|
||||
struct hbm_version version;
|
||||
|
||||
struct mei_me_client *me_clients; /* Note: memory has to be allocated */
|
||||
DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX);
|
||||
DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX);
|
||||
u8 me_clients_num;
|
||||
u8 me_client_presentation_num;
|
||||
u8 me_client_index;
|
||||
bool mei_host_buffer_is_empty;
|
||||
|
||||
struct mei_cl wd_cl;
|
||||
bool wd_pending;
|
||||
bool wd_stopped;
|
||||
bool wd_bypass; /* if false, don't refresh watchdog ME client */
|
||||
u16 wd_timeout; /* seconds ((wd_data[1] << 8) + wd_data[0]) */
|
||||
u16 wd_due_counter;
|
||||
unsigned char wd_data[MEI_START_WD_DATA_SIZE];
|
||||
|
||||
|
||||
|
||||
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;
|
||||
|
||||
bool wd_interface_reg;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* mei init function prototypes
|
||||
*/
|
||||
struct mei_device *mei_device_init(struct pci_dev *pdev);
|
||||
void mei_reset(struct mei_device *dev, int interrupts);
|
||||
int mei_hw_init(struct mei_device *dev);
|
||||
int mei_task_initialize_clients(void *data);
|
||||
int mei_initialize_clients(struct mei_device *dev);
|
||||
int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl);
|
||||
void mei_remove_client_from_file_list(struct mei_device *dev, u8 host_client_id);
|
||||
void mei_host_init_iamthif(struct mei_device *dev);
|
||||
void mei_allocate_me_clients_storage(struct mei_device *dev);
|
||||
|
||||
|
||||
u8 mei_find_me_client_update_filext(struct mei_device *dev,
|
||||
struct mei_cl *priv,
|
||||
const uuid_le *cguid, u8 client_id);
|
||||
|
||||
/*
|
||||
* MEI IO List Functions
|
||||
*/
|
||||
void mei_io_list_init(struct mei_io_list *list);
|
||||
void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl);
|
||||
|
||||
/*
|
||||
* MEI ME 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_flush_queues(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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* MEI Host Client Functions
|
||||
*/
|
||||
void mei_host_start_message(struct mei_device *dev);
|
||||
void mei_host_enum_clients_message(struct mei_device *dev);
|
||||
int mei_host_client_properties(struct mei_device *dev);
|
||||
|
||||
/*
|
||||
* MEI interrupt functions prototype
|
||||
*/
|
||||
irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id);
|
||||
irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id);
|
||||
void mei_timer(struct work_struct *work);
|
||||
|
||||
/*
|
||||
* MEI input output function prototype
|
||||
*/
|
||||
int mei_ioctl_connect_client(struct file *file,
|
||||
struct mei_connect_client_data *data);
|
||||
|
||||
int mei_start_read(struct mei_device *dev, struct mei_cl *cl);
|
||||
|
||||
int amthi_write(struct mei_device *dev, struct mei_cl_cb *priv_cb);
|
||||
|
||||
int amthi_read(struct mei_device *dev, struct file *file,
|
||||
char __user *ubuf, size_t length, loff_t *offset);
|
||||
|
||||
struct mei_cl_cb *find_amthi_read_list_entry(struct mei_device *dev,
|
||||
struct file *file);
|
||||
|
||||
void mei_run_next_iamthif_cmd(struct mei_device *dev);
|
||||
|
||||
void mei_free_cb_private(struct mei_cl_cb *priv_cb);
|
||||
|
||||
int mei_find_me_client_index(const struct mei_device *dev, uuid_le cuuid);
|
||||
|
||||
/*
|
||||
* Register Access Function
|
||||
*/
|
||||
|
||||
/**
|
||||
* mei_reg_read - Reads 32bit data from the mei device
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @offset: offset from which to read the data
|
||||
*
|
||||
* returns register value (u32)
|
||||
*/
|
||||
static inline u32 mei_reg_read(struct mei_device *dev, unsigned long offset)
|
||||
{
|
||||
return ioread32(dev->mem_addr + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_reg_write - Writes 32bit data to the mei device
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @offset: offset from which to write the data
|
||||
* @value: register value to write (u32)
|
||||
*/
|
||||
static inline void mei_reg_write(struct mei_device *dev,
|
||||
unsigned long offset, u32 value)
|
||||
{
|
||||
iowrite32(value, dev->mem_addr + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_hcsr_read - Reads 32bit data from the host CSR
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns the byte read.
|
||||
*/
|
||||
static inline u32 mei_hcsr_read(struct mei_device *dev)
|
||||
{
|
||||
return mei_reg_read(dev, H_CSR);
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_mecsr_read - Reads 32bit data from the ME CSR
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns ME_CSR_HA register value (u32)
|
||||
*/
|
||||
static inline u32 mei_mecsr_read(struct mei_device *dev)
|
||||
{
|
||||
return mei_reg_read(dev, ME_CSR_HA);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_me_cb_rw - Reads 32bit data from the mei ME_CB_RW register
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* returns ME_CB_RW register value (u32)
|
||||
*/
|
||||
static inline u32 mei_mecbrw_read(struct mei_device *dev)
|
||||
{
|
||||
return mei_reg_read(dev, ME_CB_RW);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* mei interface function prototypes
|
||||
*/
|
||||
void mei_hcsr_set(struct mei_device *dev);
|
||||
void mei_csr_clear_his(struct mei_device *dev);
|
||||
|
||||
void mei_enable_interrupts(struct mei_device *dev);
|
||||
void mei_disable_interrupts(struct mei_device *dev);
|
||||
|
||||
#endif
|
379
drivers/misc/mei/wd.c
Normal file
379
drivers/misc/mei/wd.c
Normal file
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
*
|
||||
* 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 "mei_dev.h"
|
||||
#include "hw.h"
|
||||
#include "interface.h"
|
||||
#include "mei.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_PARAMS_SIZE);
|
||||
memcpy(dev->wd_data + MEI_WD_PARAMS_SIZE, &timeout, sizeof(u16));
|
||||
}
|
||||
|
||||
/**
|
||||
* host_init_wd - mei initialization wd.
|
||||
*
|
||||
* @dev: the device structure
|
||||
* returns -ENENT if wd client cannot be found
|
||||
* -EIO if write has failed
|
||||
*/
|
||||
int mei_wd_host_init(struct mei_device *dev)
|
||||
{
|
||||
mei_cl_init(&dev->wd_cl, dev);
|
||||
|
||||
/* look for WD client and connect to it */
|
||||
dev->wd_cl.state = MEI_FILE_DISCONNECTED;
|
||||
dev->wd_timeout = AMT_WD_DEFAULT_TIMEOUT;
|
||||
|
||||
/* find ME WD client */
|
||||
mei_find_me_client_update_filext(dev, &dev->wd_cl,
|
||||
&mei_wd_guid, MEI_WD_HOST_CLIENT_ID);
|
||||
|
||||
dev_dbg(&dev->pdev->dev, "wd: check client\n");
|
||||
if (MEI_FILE_CONNECTING != dev->wd_cl.state) {
|
||||
dev_info(&dev->pdev->dev, "wd: failed to find the client\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (mei_connect(dev, &dev->wd_cl)) {
|
||||
dev_err(&dev->pdev->dev, "wd: failed to connect to the client\n");
|
||||
dev->wd_cl.state = MEI_FILE_DISCONNECTED;
|
||||
dev->wd_cl.host_client_id = 0;
|
||||
return -EIO;
|
||||
}
|
||||
dev->wd_cl.timer_count = 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 *mei_hdr;
|
||||
|
||||
mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0];
|
||||
mei_hdr->host_addr = dev->wd_cl.host_client_id;
|
||||
mei_hdr->me_addr = dev->wd_cl.me_client_id;
|
||||
mei_hdr->msg_complete = 1;
|
||||
mei_hdr->reserved = 0;
|
||||
|
||||
if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_PARAMS_SIZE))
|
||||
mei_hdr->length = MEI_START_WD_DATA_SIZE;
|
||||
else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_PARAMS_SIZE))
|
||||
mei_hdr->length = MEI_WD_PARAMS_SIZE;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return mei_write_message(dev, mei_hdr, dev->wd_data, mei_hdr->length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, bool preserve)
|
||||
{
|
||||
int ret;
|
||||
u16 wd_timeout = dev->wd_timeout;
|
||||
|
||||
cancel_delayed_work(&dev->timer_work);
|
||||
if (dev->wd_cl.state != MEI_FILE_CONNECTED || !dev->wd_timeout)
|
||||
return 0;
|
||||
|
||||
dev->wd_timeout = 0;
|
||||
dev->wd_due_counter = 0;
|
||||
memcpy(dev->wd_data, mei_stop_wd_params, MEI_WD_PARAMS_SIZE);
|
||||
dev->stop = true;
|
||||
|
||||
ret = mei_flow_ctrl_creds(dev, &dev->wd_cl);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (ret && dev->mei_host_buffer_is_empty) {
|
||||
ret = 0;
|
||||
dev->mei_host_buffer_is_empty = false;
|
||||
|
||||
if (!mei_wd_send(dev)) {
|
||||
ret = mei_flow_ctrl_reduce(dev, &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;
|
||||
}
|
||||
dev->wd_stopped = false;
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
ret = wait_event_interruptible_timeout(dev->wait_stop_wd,
|
||||
dev->wd_stopped, 10 * HZ);
|
||||
mutex_lock(&dev->device_lock);
|
||||
if (dev->wd_stopped) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (preserve)
|
||||
dev->wd_timeout = wd_timeout;
|
||||
|
||||
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 = pci_get_drvdata(mei_device);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
if (dev->mei_state != MEI_ENABLED) {
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"wd: mei_state != MEI_ENABLED mei_state = %d\n",
|
||||
dev->mei_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 = pci_get_drvdata(mei_device);
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
mei_wd_stop(dev, false);
|
||||
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 = pci_get_drvdata(mei_device);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Check if we can send the ping to HW*/
|
||||
if (dev->mei_host_buffer_is_empty &&
|
||||
mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) {
|
||||
|
||||
dev->mei_host_buffer_is_empty = 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_flow_ctrl_reduce(dev, &dev->wd_cl)) {
|
||||
dev_err(&dev->pdev->dev,
|
||||
"wd: mei_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 = pci_get_drvdata(mei_device);
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
/* Check Timeout value */
|
||||
if (timeout < AMT_WD_MIN_TIMEOUT || timeout > AMT_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,
|
||||
};
|
||||
|
||||
static struct watchdog_device amt_wd_dev = {
|
||||
.info = &wd_info,
|
||||
.ops = &wd_ops,
|
||||
.timeout = AMT_WD_DEFAULT_TIMEOUT,
|
||||
.min_timeout = AMT_WD_MIN_TIMEOUT,
|
||||
.max_timeout = AMT_WD_MAX_TIMEOUT,
|
||||
};
|
||||
|
||||
|
||||
void mei_watchdog_register(struct mei_device *dev)
|
||||
{
|
||||
dev_dbg(&dev->pdev->dev, "dev->wd_timeout =%d.\n", dev->wd_timeout);
|
||||
|
||||
dev->wd_due_counter = !!dev->wd_timeout;
|
||||
|
||||
if (watchdog_register_device(&amt_wd_dev)) {
|
||||
dev_err(&dev->pdev->dev,
|
||||
"wd: unable to register watchdog device.\n");
|
||||
dev->wd_interface_reg = false;
|
||||
} else {
|
||||
dev_dbg(&dev->pdev->dev,
|
||||
"wd: successfully register watchdog interface.\n");
|
||||
dev->wd_interface_reg = true;
|
||||
}
|
||||
}
|
||||
|
||||
void mei_watchdog_unregister(struct mei_device *dev)
|
||||
{
|
||||
if (dev->wd_interface_reg)
|
||||
watchdog_unregister_device(&amt_wd_dev);
|
||||
dev->wd_interface_reg = false;
|
||||
}
|
||||
|
Reference in New Issue
Block a user