soc/intel/common/block: Add HECI driver
Add common driver that can send/receive HECI messages. This driver is inspired by Linux kernel mei driver and somewhat based on Skylake's. Currently it has been only tested on Apollolake. BUG=b:35586975 BRANCH=reef TEST=tested on Apollolake to send single messages and receive both fragmented and non-fragmented versions. Change-Id: Ie3772700270f4f333292b80d59f79555851780f7 Signed-off-by: Andrey Petrov <andrey.petrov@intel.com> Reviewed-on: https://review.coreboot.org/18547 Reviewed-by: Aaron Durbin <adurbin@chromium.org> Tested-by: build bot (Jenkins)
This commit is contained in:
parent
fba7489574
commit
04a72c4019
|
@ -0,0 +1,7 @@
|
|||
config SOC_INTEL_COMMON_BLOCK_CSE
|
||||
bool
|
||||
default n
|
||||
help
|
||||
Driver for communication with Converged Security Engine (CSE)
|
||||
over Host Embedded Controller Interface (HECI)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_CSE) += cse.c
|
||||
ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_CSE) += cse.c
|
|
@ -0,0 +1,477 @@
|
|||
/*
|
||||
* This file is part of the coreboot project.
|
||||
*
|
||||
* Copyright 2017 Intel Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <arch/early_variables.h>
|
||||
#include <commonlib/helpers.h>
|
||||
#include <console/console.h>
|
||||
#include <delay.h>
|
||||
#include <device/pci.h>
|
||||
#include <device/pci_ids.h>
|
||||
#include <device/pci_ops.h>
|
||||
#include <intelblocks/cse.h>
|
||||
#include <soc/pci_devs.h>
|
||||
#include <soc/pci_ids.h>
|
||||
#include <string.h>
|
||||
#include <timer.h>
|
||||
|
||||
/* default window for early boot, must be at least 12 bytes in size */
|
||||
#define HECI1_BASE_ADDRESS 0xfed1a000
|
||||
|
||||
/* Wait up to 15 sec for HECI to get ready */
|
||||
#define HECI_DELAY_READY (15 * 1000)
|
||||
/* Wait up to 100 usec between circullar buffer polls */
|
||||
#define HECI_DELAY 100
|
||||
/* Wait up to 5 sec for CSE to chew something we sent */
|
||||
#define HECI_SEND_TIMEOUT (5 * 1000)
|
||||
/* Wait up to 5 sec for CSE to blurp a reply */
|
||||
#define HECI_READ_TIMEOUT (5 * 1000)
|
||||
|
||||
#define SLOT_SIZE sizeof(uint32_t)
|
||||
|
||||
#define MMIO_CSE_CB_WW 0x00
|
||||
#define MMIO_HOST_CSR 0x04
|
||||
#define MMIO_CSE_CB_RW 0x08
|
||||
#define MMIO_CSE_CSR 0x0c
|
||||
|
||||
#define CSR_IE (1 << 0)
|
||||
#define CSR_IS (1 << 1)
|
||||
#define CSR_IG (1 << 2)
|
||||
#define CSR_READY (1 << 3)
|
||||
#define CSR_RESET (1 << 4)
|
||||
#define CSR_RP_START 8
|
||||
#define CSR_RP (((1 << 8) - 1) << CSR_RP_START)
|
||||
#define CSR_WP_START 16
|
||||
#define CSR_WP (((1 << 8) - 1) << CSR_WP_START)
|
||||
#define CSR_CBD_START 24
|
||||
#define CSR_CBD (((1 << 8) - 1) << CSR_CBD_START)
|
||||
|
||||
#define MEI_HDR_IS_COMPLETE (1 << 31)
|
||||
#define MEI_HDR_LENGTH_START 16
|
||||
#define MEI_HDR_LENGTH_SIZE 9
|
||||
#define MEI_HDR_LENGTH (((1 << MEI_HDR_LENGTH_SIZE) - 1) \
|
||||
<< MEI_HDR_LENGTH_START)
|
||||
#define MEI_HDR_HOST_ADDR_START 8
|
||||
#define MEI_HDR_HOST_ADDR (((1 << 8) - 1) << MEI_HDR_HOST_ADDR_START)
|
||||
#define MEI_HDR_CSE_ADDR_START 0
|
||||
#define MEI_HDR_CSE_ADDR (((1 << 8) - 1) << MEI_HDR_CSE_ADDR_START)
|
||||
|
||||
|
||||
struct cse_device {
|
||||
uintptr_t sec_bar;
|
||||
} g_cse CAR_GLOBAL;
|
||||
|
||||
/*
|
||||
* Initialize the device with provided temporary BAR. If BAR is 0 use a
|
||||
* default. This is intended for pre-mem usage only where BARs haven't been
|
||||
* assigned yet and devices are not enabled.
|
||||
*/
|
||||
void heci_init(uintptr_t tempbar)
|
||||
{
|
||||
struct cse_device *cse = car_get_var_ptr(&g_cse);
|
||||
device_t dev = HECI1_DEV;
|
||||
u8 pcireg;
|
||||
|
||||
/* Assume it is already initialized, nothing else to do */
|
||||
if (cse->sec_bar)
|
||||
return;
|
||||
|
||||
/* Use default pre-ram bar */
|
||||
if (!tempbar)
|
||||
tempbar = HECI1_BASE_ADDRESS;
|
||||
|
||||
/* Assign Resources to HECI1 */
|
||||
/* Clear BIT 1-2 of Command Register */
|
||||
pcireg = pci_read_config8(dev, PCI_COMMAND);
|
||||
pcireg &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY);
|
||||
pci_write_config8(dev, PCI_COMMAND, pcireg);
|
||||
|
||||
/* Program Temporary BAR for HECI1 */
|
||||
pci_write_config32(dev, PCI_BASE_ADDRESS_0, tempbar);
|
||||
pci_write_config32(dev, PCI_BASE_ADDRESS_1, 0x0);
|
||||
|
||||
/* Enable Bus Master and MMIO Space */
|
||||
pcireg = pci_read_config8(dev, PCI_COMMAND);
|
||||
pcireg |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
|
||||
pci_write_config8(dev, PCI_COMMAND, pcireg);
|
||||
|
||||
cse->sec_bar = tempbar;
|
||||
}
|
||||
|
||||
static uint32_t read_bar(uint32_t offset)
|
||||
{
|
||||
struct cse_device *cse = car_get_var_ptr(&g_cse);
|
||||
return read32((void *)(cse->sec_bar + offset));
|
||||
}
|
||||
|
||||
static void write_bar(uint32_t offset, uint32_t val)
|
||||
{
|
||||
struct cse_device *cse = car_get_var_ptr(&g_cse);
|
||||
return write32((void *)(cse->sec_bar + offset), val);
|
||||
}
|
||||
|
||||
static uint32_t read_cse_csr(void)
|
||||
{
|
||||
return read_bar(MMIO_CSE_CSR);
|
||||
}
|
||||
|
||||
static uint32_t read_host_csr(void)
|
||||
{
|
||||
return read_bar(MMIO_HOST_CSR);
|
||||
}
|
||||
|
||||
static void write_host_csr(uint32_t data)
|
||||
{
|
||||
write_bar(MMIO_HOST_CSR, data);
|
||||
}
|
||||
|
||||
static size_t filled_slots(uint32_t data)
|
||||
{
|
||||
uint8_t wp, rp;
|
||||
rp = data >> CSR_RP_START;
|
||||
wp = data >> CSR_WP_START;
|
||||
return (uint8_t) (wp - rp);
|
||||
}
|
||||
|
||||
static size_t cse_filled_slots(void)
|
||||
{
|
||||
return filled_slots(read_cse_csr());
|
||||
}
|
||||
|
||||
static size_t host_empty_slots(void)
|
||||
{
|
||||
uint32_t csr;
|
||||
csr = read_host_csr();
|
||||
|
||||
return ((csr & CSR_CBD) >> CSR_CBD_START) - filled_slots(csr);
|
||||
}
|
||||
|
||||
static void clear_int(void)
|
||||
{
|
||||
uint32_t csr;
|
||||
csr = read_host_csr();
|
||||
csr |= CSR_IS;
|
||||
write_host_csr(csr);
|
||||
}
|
||||
|
||||
static uint32_t read_slot(void)
|
||||
{
|
||||
return read_bar(MMIO_CSE_CB_RW);
|
||||
}
|
||||
|
||||
static void write_slot(uint32_t val)
|
||||
{
|
||||
write_bar(MMIO_CSE_CB_WW, val);
|
||||
}
|
||||
|
||||
static int wait_write_slots(size_t cnt)
|
||||
{
|
||||
struct stopwatch sw;
|
||||
|
||||
stopwatch_init_msecs_expire(&sw, HECI_SEND_TIMEOUT);
|
||||
while (host_empty_slots() < cnt) {
|
||||
udelay(HECI_DELAY);
|
||||
if (stopwatch_expired(&sw)) {
|
||||
printk(BIOS_ERR, "HECI: timeout, buffer not drained\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int wait_read_slots(size_t cnt)
|
||||
{
|
||||
struct stopwatch sw;
|
||||
|
||||
stopwatch_init_msecs_expire(&sw, HECI_READ_TIMEOUT);
|
||||
while (cse_filled_slots() < cnt) {
|
||||
udelay(HECI_DELAY);
|
||||
if (stopwatch_expired(&sw)) {
|
||||
printk(BIOS_ERR, "HECI: timed out reading answer!\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* get number of full 4-byte slots */
|
||||
static size_t bytes_to_slots(size_t bytes)
|
||||
{
|
||||
return ALIGN_UP(bytes, SLOT_SIZE) / SLOT_SIZE;
|
||||
}
|
||||
|
||||
static int cse_ready(void)
|
||||
{
|
||||
uint32_t csr;
|
||||
csr = read_cse_csr();
|
||||
return csr & CSR_READY;
|
||||
}
|
||||
|
||||
static int wait_heci_ready(void)
|
||||
{
|
||||
struct stopwatch sw;
|
||||
|
||||
stopwatch_init_msecs_expire(&sw, HECI_DELAY_READY);
|
||||
while (!cse_ready()) {
|
||||
udelay(HECI_DELAY);
|
||||
if (stopwatch_expired(&sw))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void host_gen_interrupt(void)
|
||||
{
|
||||
uint32_t csr;
|
||||
csr = read_host_csr();
|
||||
csr |= CSR_IG;
|
||||
write_host_csr(csr);
|
||||
}
|
||||
|
||||
static size_t hdr_get_length(uint32_t hdr)
|
||||
{
|
||||
return (hdr & MEI_HDR_LENGTH) >> MEI_HDR_LENGTH_START;
|
||||
}
|
||||
|
||||
static int
|
||||
send_one_message(uint32_t hdr, const void *buff)
|
||||
{
|
||||
size_t pend_len, pend_slots, remainder, i;
|
||||
uint32_t tmp;
|
||||
const uint32_t *p = buff;
|
||||
|
||||
/* Get space for the header */
|
||||
if (!wait_write_slots(1))
|
||||
return 0;
|
||||
|
||||
/* First, write header */
|
||||
write_slot(hdr);
|
||||
|
||||
pend_len = hdr_get_length(hdr);
|
||||
pend_slots = bytes_to_slots(pend_len);
|
||||
|
||||
if (!wait_write_slots(pend_slots))
|
||||
return 0;
|
||||
|
||||
/* Write the body in whole slots */
|
||||
i = 0;
|
||||
while (i < ALIGN_DOWN(pend_len, SLOT_SIZE)) {
|
||||
write_slot(*p++);
|
||||
i += SLOT_SIZE;
|
||||
}
|
||||
|
||||
remainder = pend_len % SLOT_SIZE;
|
||||
/* Pad to 4 bytes not touching caller's buffer */
|
||||
if (remainder) {
|
||||
memcpy(&tmp, p, remainder);
|
||||
write_slot(tmp);
|
||||
}
|
||||
|
||||
host_gen_interrupt();
|
||||
|
||||
/* Make sure nothing bad happened during transmission */
|
||||
if (!cse_ready())
|
||||
return 0;
|
||||
|
||||
return pend_len;
|
||||
}
|
||||
|
||||
int
|
||||
heci_send(const void *msg, size_t len, uint8_t host_addr, uint8_t client_addr)
|
||||
{
|
||||
uint32_t csr, hdr;
|
||||
size_t sent = 0, remaining, cb_size, max_length;
|
||||
uint8_t *p = (uint8_t *) msg;
|
||||
|
||||
if (!msg || !len)
|
||||
return 0;
|
||||
|
||||
clear_int();
|
||||
|
||||
if (!wait_heci_ready()) {
|
||||
printk(BIOS_ERR, "HECI: not ready\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
csr = read_cse_csr();
|
||||
cb_size = ((csr & CSR_CBD) >> CSR_CBD_START) * SLOT_SIZE;
|
||||
/*
|
||||
* Reserve one slot for the header. Limit max message length by 9
|
||||
* bits that are available in the header.
|
||||
*/
|
||||
max_length = MIN(cb_size, (1 << MEI_HDR_LENGTH_SIZE) - 1) - SLOT_SIZE;
|
||||
remaining = len;
|
||||
|
||||
/*
|
||||
* Fragment the message into smaller messages not exceeding useful
|
||||
* circullar buffer length. Mark last message complete.
|
||||
*/
|
||||
do {
|
||||
hdr = MIN(max_length, remaining) << MEI_HDR_LENGTH_START;
|
||||
hdr |= client_addr << MEI_HDR_CSE_ADDR_START;
|
||||
hdr |= host_addr << MEI_HDR_HOST_ADDR_START;
|
||||
hdr |= (MIN(max_length, remaining) == remaining) ?
|
||||
MEI_HDR_IS_COMPLETE: 0;
|
||||
sent = send_one_message(hdr, p);
|
||||
p += sent;
|
||||
remaining -= sent;
|
||||
} while (remaining > 0 && sent != 0);
|
||||
|
||||
return remaining == 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
recv_one_message(uint32_t *hdr, void *buff, size_t maxlen)
|
||||
{
|
||||
uint32_t reg, *p = buff;
|
||||
size_t recv_slots, recv_len, remainder, i;
|
||||
|
||||
/* first get the header */
|
||||
if (!wait_read_slots(1))
|
||||
return 0;
|
||||
|
||||
*hdr = read_slot();
|
||||
recv_len = hdr_get_length(*hdr);
|
||||
|
||||
if (!recv_len)
|
||||
printk(BIOS_WARNING, "HECI: message is zero-sized\n");
|
||||
|
||||
recv_slots = bytes_to_slots(recv_len);
|
||||
|
||||
i = 0;
|
||||
if (recv_len > maxlen) {
|
||||
printk(BIOS_ERR, "HECI: response is too big\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* wait for the rest of messages to arrive */
|
||||
wait_read_slots(recv_slots);
|
||||
|
||||
/* fetch whole slots first */
|
||||
while (i < ALIGN_DOWN(recv_len, SLOT_SIZE)) {
|
||||
*p++ = read_slot();
|
||||
i += SLOT_SIZE;
|
||||
}
|
||||
|
||||
remainder = recv_len % SLOT_SIZE;
|
||||
|
||||
if (remainder) {
|
||||
reg = read_slot();
|
||||
memcpy(p, ®, remainder);
|
||||
}
|
||||
|
||||
return recv_len;
|
||||
}
|
||||
|
||||
int heci_receive(void *buff, size_t *maxlen)
|
||||
{
|
||||
size_t left, received;
|
||||
uint32_t hdr = 0;
|
||||
uint8_t *p = buff;
|
||||
|
||||
if (!buff || !maxlen || !*maxlen)
|
||||
return 0;
|
||||
|
||||
left = *maxlen;
|
||||
|
||||
clear_int();
|
||||
|
||||
if (!wait_heci_ready()) {
|
||||
printk(BIOS_ERR, "HECI: not ready\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive multiple packets until we meet one marked complete or we run
|
||||
* out of space in caller-provided buffer.
|
||||
*/
|
||||
do {
|
||||
received = recv_one_message(&hdr, p, left);
|
||||
left -= received;
|
||||
p += received;
|
||||
/* If we read out everything ping to send more */
|
||||
if (!(hdr & MEI_HDR_IS_COMPLETE) && !cse_filled_slots())
|
||||
host_gen_interrupt();
|
||||
} while (received && !(hdr & MEI_HDR_IS_COMPLETE) && left > 0);
|
||||
|
||||
*maxlen = p - (uint8_t *) buff;
|
||||
|
||||
/* If ME is not ready, something went wrong and we received junk */
|
||||
if (!cse_ready())
|
||||
return 0;
|
||||
|
||||
return !!((hdr & MEI_HDR_IS_COMPLETE) && received);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to reset the device. This is useful when host and ME are out
|
||||
* of sync during transmission or ME didn't understand the message.
|
||||
*/
|
||||
int heci_reset(void)
|
||||
{
|
||||
uint32_t csr;
|
||||
|
||||
/* Send reset request */
|
||||
csr = read_host_csr();
|
||||
csr |= CSR_RESET;
|
||||
csr |= CSR_IG;
|
||||
write_host_csr(csr);
|
||||
|
||||
if (wait_heci_ready()) {
|
||||
/* Device is back on its imaginary feet, clear reset */
|
||||
csr = read_host_csr();
|
||||
csr &= ~CSR_RESET;
|
||||
csr |= CSR_IG;
|
||||
csr |= CSR_READY;
|
||||
write_host_csr(csr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printk(BIOS_CRIT, "HECI: reset failed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if ENV_RAMSTAGE
|
||||
|
||||
static void update_sec_bar(struct device *dev)
|
||||
{
|
||||
g_cse.sec_bar = find_resource(dev, PCI_BASE_ADDRESS_0)->base;
|
||||
}
|
||||
|
||||
static void cse_set_resources(struct device *dev)
|
||||
{
|
||||
if (dev->path.pci.devfn == HECI1_DEVFN)
|
||||
update_sec_bar(dev);
|
||||
|
||||
pci_dev_set_resources(dev);
|
||||
}
|
||||
|
||||
static struct device_operations cse_ops = {
|
||||
.set_resources = cse_set_resources,
|
||||
.read_resources = pci_dev_read_resources,
|
||||
.enable_resources = pci_dev_enable_resources,
|
||||
.init = pci_dev_init,
|
||||
.enable_resources = pci_dev_enable_resources
|
||||
};
|
||||
|
||||
static const struct pci_driver cse_driver __pci_driver = {
|
||||
.ops = &cse_ops,
|
||||
.vendor = PCI_VENDOR_ID_INTEL,
|
||||
/* SoC/chipset needs to provide PCI device ID */
|
||||
.device = PCI_DEVICE_ID_HECI1
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of the coreboot project.
|
||||
*
|
||||
* Copyright (C) 2017 Intel Corp.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef SOC_INTEL_COMMON_CSE_H
|
||||
#define SOC_INTEL_COMMON_CSE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* set up device for use in early boot enviroument with temp bar */
|
||||
void heci_init(uintptr_t bar);
|
||||
/*
|
||||
* Receive message into buff not exceeding maxlen. Message is considered
|
||||
* successfully received if a 'complete' indication is read from ME side
|
||||
* and there was enough space in the buffer to fit that message. maxlen
|
||||
* is updated with size of message that was received. Returns 0 on failure
|
||||
* and 1 on success.
|
||||
* In case of error heci_reset() may be requiered.
|
||||
*/
|
||||
int heci_receive(void *buff, size_t *maxlen);
|
||||
/*
|
||||
* Send message msg of size len to host from host_addr to cse_addr.
|
||||
* Returns 1 on success and 0 otherwise.
|
||||
* In case of error heci_reset() may be requiered.
|
||||
*/
|
||||
int
|
||||
heci_send(const void *msg, size_t len, uint8_t host_addr, uint8_t cse_addr);
|
||||
/*
|
||||
* Attempt device reset. This is useful and perhaps only thing left to do when
|
||||
* CPU and CSE are out of sync or CSE fails to respond.
|
||||
* Returns 0 on failure a 1 on success.
|
||||
*/
|
||||
int heci_reset(void);
|
||||
|
||||
#define BIOS_HOST_ADDR 0x00
|
||||
#define HECI_MKHI_ADDR 0x07
|
||||
|
||||
#endif // SOC_INTEL_COMMON_MSR_H
|
Loading…
Reference in New Issue