mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-28 20:02:17 +00:00
![Oren Weil](/assets/img/avatar_default.png)
Implementation of the communication between host and ME. connect/disconnect to/from a client, send MEI message, read MEI message, flow control handling. Each MEI message has mei_msg_hdr followed by a payload. Driver is oblivious the payload. ME Address/ID - This is the logical address of the ME feature/client of that message. Host Address/ID - This is the logical address of the Host client of that message Length - This is the Length of message payload in bytes Reserved - reserved for future use. Message Complete - This bit is used to indicative that this is the last message of multi message MEI transfer of a client message that is larger then the MEI circular buffer. Payload - Message payload (data) up to 512bytes The HW data registers are consist two circular buffers, one for data from ME and other data from Host application. Each buffer has two pointers, read_ptr (H_CBRP) and write_ptr (H_CBWP). The buffers size is defined by depth value that exists in the status registers (H_CBD and ME_CBD_HRA). Every read from ME circular buffer cause read_ptr++ Every write to the Host circular buffer write_ptr++ Flow control MEI message that ME and MEI Driver use to notify each other that a ME feature/client or Host client buffer is ready to receive data. Signed-off-by: Tomas Winkler <tomas.winkler@intel.com> Signed-off-by: Itzhak Tzeel-Krupp <itzhak.tzeel-krupp@intel.com> Signed-off-by: Oren Weil <oren.jer.weil@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
447 lines
11 KiB
C
447 lines
11 KiB
C
/*
|
|
*
|
|
* Intel Management Engine Interface (Intel MEI) Linux driver
|
|
* Copyright (c) 2003-2011, 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
|
|
*
|
|
* returns 1 if success, 0 - otherwise.
|
|
*/
|
|
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 0;
|
|
|
|
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 0;
|
|
|
|
dev->write_hang = 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* 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 i = 0;
|
|
unsigned char temp_buf[sizeof(u32)];
|
|
|
|
while (buffer_length >= sizeof(u32)) {
|
|
((u32 *) buffer)[i] = mei_mecbrw_read(dev);
|
|
|
|
dev_dbg(&dev->pdev->dev,
|
|
"buffer[%d]= %d\n",
|
|
i, ((u32 *) buffer)[i]);
|
|
|
|
i++;
|
|
buffer_length -= sizeof(u32);
|
|
}
|
|
|
|
if (buffer_length > 0) {
|
|
*((u32 *) &temp_buf) = mei_mecbrw_read(dev);
|
|
memcpy(&buffer[i * 4], temp_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->num_mei_me_clients)
|
|
return 0;
|
|
|
|
if (cl->mei_flow_ctrl_creds > 0)
|
|
return 1;
|
|
|
|
for (i = 0; i < dev->num_mei_me_clients; 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 wehn ctrl credits are <= 0
|
|
*/
|
|
int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl)
|
|
{
|
|
int i;
|
|
|
|
if (!dev->num_mei_me_clients)
|
|
return -ENOENT;
|
|
|
|
for (i = 0; i < dev->num_mei_me_clients; 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
|
|
*
|
|
* returns 1 if success, 0 - otherwise.
|
|
*/
|
|
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->cmd.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);
|
|
if (!mei_write_message(dev, mei_hdr,
|
|
(unsigned char *) mei_flow_control,
|
|
sizeof(struct hbm_flow_control)))
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* returns 1 if success, 0 - otherwise.
|
|
*/
|
|
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->cmd.cmd = CLIENT_DISCONNECT_REQ_CMD;
|
|
mei_cli_disconnect->reserved[0] = 0;
|
|
|
|
if (!mei_write_message(dev, mei_hdr,
|
|
(unsigned char *) mei_cli_disconnect,
|
|
sizeof(struct hbm_client_disconnect_request)))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* mei_connect - sends connect message to fw.
|
|
*
|
|
* @dev: the device structure
|
|
* @cl: private data of the file object
|
|
*
|
|
* returns 1 if success, 0 - otherwise.
|
|
*/
|
|
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->cmd.cmd = CLIENT_CONNECT_REQ_CMD;
|
|
mei_cli_connect->reserved = 0;
|
|
|
|
if (!mei_write_message(dev, mei_hdr,
|
|
(unsigned char *) mei_cli_connect,
|
|
sizeof(struct hbm_client_connect_request)))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|