diff options
author | Andrey Petrov <andrey.petrov@intel.com> | 2017-03-01 15:51:57 -0800 |
---|---|---|
committer | Martin Roth <martinroth@google.com> | 2017-03-09 04:40:12 +0100 |
commit | 04a72c4019475062da8449e58e21a0bcec4a6831 (patch) | |
tree | b9c6e12279cab47839a9aaae60e802abe12d8867 /src/soc/intel/common | |
parent | fba74895745d9fe498bc15023192a8876576c356 (diff) |
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)
Diffstat (limited to 'src/soc/intel/common')
-rw-r--r-- | src/soc/intel/common/block/cse/Kconfig | 7 | ||||
-rw-r--r-- | src/soc/intel/common/block/cse/Makefile.inc | 2 | ||||
-rw-r--r-- | src/soc/intel/common/block/cse/cse.c | 477 | ||||
-rw-r--r-- | src/soc/intel/common/block/include/intelblocks/cse.h | 50 |
4 files changed, 536 insertions, 0 deletions
diff --git a/src/soc/intel/common/block/cse/Kconfig b/src/soc/intel/common/block/cse/Kconfig new file mode 100644 index 0000000000..441ff93cd7 --- /dev/null +++ b/src/soc/intel/common/block/cse/Kconfig @@ -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) + diff --git a/src/soc/intel/common/block/cse/Makefile.inc b/src/soc/intel/common/block/cse/Makefile.inc new file mode 100644 index 0000000000..fe7b13c7bb --- /dev/null +++ b/src/soc/intel/common/block/cse/Makefile.inc @@ -0,0 +1,2 @@ +romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_CSE) += cse.c +ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_CSE) += cse.c diff --git a/src/soc/intel/common/block/cse/cse.c b/src/soc/intel/common/block/cse/cse.c new file mode 100644 index 0000000000..77cedb1407 --- /dev/null +++ b/src/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 diff --git a/src/soc/intel/common/block/include/intelblocks/cse.h b/src/soc/intel/common/block/include/intelblocks/cse.h new file mode 100644 index 0000000000..d7c4d9f93e --- /dev/null +++ b/src/soc/intel/common/block/include/intelblocks/cse.h @@ -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 |