Andrey Petrov (andrey.petrov@intel.com) just uploaded a new patch set to gerrit, which you can find at https://review.coreboot.org/18547
-gerrit
commit 4db348a0266f35f7908641ebc4a0a0c868d08590 Author: Andrey Petrov andrey.petrov@intel.com Date: Wed Mar 1 15:51:57 2017 -0800
drivers/intel/txe: Add basic HECI driver to send/receive messages
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 but hopefully it will work on other platforms although some will probably require quirks.
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 --- src/drivers/intel/txe/Kconfig | 5 + src/drivers/intel/txe/Makefile.inc | 2 + src/drivers/intel/txe/txe.c | 445 +++++++++++++++++++++++++++++++++++++ src/drivers/intel/txe/txe.h | 60 +++++ 4 files changed, 512 insertions(+)
diff --git a/src/drivers/intel/txe/Kconfig b/src/drivers/intel/txe/Kconfig new file mode 100644 index 0000000..be5ceb1 --- /dev/null +++ b/src/drivers/intel/txe/Kconfig @@ -0,0 +1,5 @@ +config TXE_COMMON + bool + default n + help + Unified TXE driver diff --git a/src/drivers/intel/txe/Makefile.inc b/src/drivers/intel/txe/Makefile.inc new file mode 100644 index 0000000..4d7bf43 --- /dev/null +++ b/src/drivers/intel/txe/Makefile.inc @@ -0,0 +1,2 @@ +romstage-$(CONFIG_TXE_COMMON) += txe.c +ramstage-$(CONFIG_TXE_COMMON) += txe.c diff --git a/src/drivers/intel/txe/txe.c b/src/drivers/intel/txe/txe.c new file mode 100644 index 0000000..4353592 --- /dev/null +++ b/src/drivers/intel/txe/txe.c @@ -0,0 +1,445 @@ +/* + * 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 <lib.h> +#include <soc/pci_devs.h> +#include <soc/pci_ids.h> +#include <string.h> +#include <timer.h> +#include "txe.h" + +/* default for early boot */ +#define HECI1_BASE_ADDRESS 0xfed1a000 + +#define HECI_DELAY_READY (15 * 1000) +#define HECI_DELAY 100 +#define HECI_SEND_TIMEOUT (5 * 1000) +#define HECI_READ_TIMEOUT (5 * 1000) + +#define SLOT_SIZE 4 + +struct txe_device { + uintptr_t sec_bar; +} g_txe 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 txe_device *txe = car_get_var_ptr(&g_txe); + device_t dev = HECI1_DEV; + u8 pcireg; + + /* assume initialized, nothing to do */ + if (txe->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); + + txe->sec_bar = tempbar; +} + +static uint32_t read_bar(uint32_t offset) +{ + struct txe_device *txe = car_get_var_ptr(&g_txe); + return read32((void *)(txe->sec_bar + offset)); +} + +static void write_bar(uint32_t offset, uint32_t val) +{ + struct txe_device *txe = car_get_var_ptr(&g_txe); + return write32((void *)(txe->sec_bar + offset), val); +} + +static uint32_t read_me_csr(void) +{ + return read_bar(MMIO_ME_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 uint8_t get_cb_msg_count(uint32_t data) +{ + int8_t read_offset = (data >> 8); + int8_t write_offset = (data >> 16); + return write_offset - read_offset; +} + +static uint32_t me_filled_slots(void) +{ + return get_cb_msg_count(read_me_csr()); +} + +static uint8_t host_filled_slots(void) +{ + union me_csr csr; + csr.data = read_me_csr(); + + return csr.fields.me_cir_buff - get_cb_msg_count(csr.data); +} + +static void clear_int(void) +{ + union me_csr csr; + csr.data = read_host_csr(); + csr.fields.int_sts = 1; + write_host_csr(csr.data); +} + +static uint32_t read_slot(void) +{ + return read_bar(MMIO_ME_CB_RW); +} + +static void write_slot(uint32_t val) +{ + write_bar(MMIO_ME_CB_WW, val); +} + +static int wait_write_slots(uint32_t cnt) +{ + struct stopwatch sw; + + stopwatch_init_msecs_expire(&sw, HECI_SEND_TIMEOUT); + while (host_filled_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(uint32_t cnt) +{ + struct stopwatch sw; + + stopwatch_init_msecs_expire(&sw, HECI_READ_TIMEOUT); + while (me_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 uint32_t bytes_to_slots(uint32_t bytes) +{ + return ALIGN_UP(bytes, SLOT_SIZE) / SLOT_SIZE; +} + +static int me_ready(void) +{ + union me_csr csr; + csr.data = read_me_csr(); + return !!csr.fields.host_ready; +} + +static int wait_heci_ready(void) +{ + struct stopwatch sw; + + stopwatch_init_msecs_expire(&sw, HECI_DELAY_READY); + while (!me_ready()) { + udelay(HECI_DELAY); + if (stopwatch_expired(&sw)) + return 0; + } + + return 1; +} + +static void host_gen_interrupt(void) +{ + union me_csr csr; + csr.data = read_host_csr(); + csr.fields.int_gen = 1; + write_host_csr(csr.data); +} + +static int +send_one_message(union mei_header *hdr, void *buff) +{ + uint32_t pend_len, pend_slots, i, remainder, tmp; + uint32_t *p = (uint32_t *)buff; + + /* Get space for the header */ + if (!wait_write_slots(1)) + return 0; + + /* First, write header */ + write_slot(hdr->data); + + pend_len = hdr->fields.length; + pend_slots = bytes_to_slots(pend_len); + + if (!wait_write_slots(pend_slots)) + return 0; + + /* Write the body in whole slots */ + 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 (!me_ready()) + return 0; + + return hdr->fields.length; +} + +/* + * Send message msg of size len to host from client. + * Returns 1 on success and 0 otherwise. + */ +int heci_send(void *msg, uint32_t len, uint8_t host, uint8_t client) +{ + union me_csr csr; + union mei_header hdr; + uint32_t sent = 0, remaining, slots, cb_size; + 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.data = read_me_csr(); + /* reserve one slot for header */ + cb_size = (csr.fields.me_cir_buff - 1) * SLOT_SIZE - 1; + + remaining = len; + slots = len / SLOT_SIZE; + + /* + * Fragment the message into smaller messages not exceeding useful + * circullar buffer length. Mark last message complete. + */ + do { + hdr.fields.length = MIN(cb_size, remaining); + hdr.fields.client_address = client; + hdr.fields.host_address = host; + hdr.fields.is_complete = (remaining <= cb_size); + sent = send_one_message(&hdr, p); + p += sent; + remaining -= sent; + } while (remaining > 0 && sent != 0); + + return remaining == 0; +} + +static uint32_t +recv_one_message(union mei_header *hdr, void *buff, uint32_t maxlen) +{ + uint32_t *p = (uint32_t *)buff; + uint32_t recv_slots, recv_len, reminder, reg, i; + + /* first get the header */ + if (!wait_read_slots(1)) + return 0; + + hdr->data = read_slot(); + recv_len = hdr->fields.length; + + if (!recv_len) + printk(BIOS_WARNING, "HECI: message is zero-sized\n"); + + recv_slots = bytes_to_slots(recv_len); + + 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; + } + + reminder = recv_len % SLOT_SIZE; + + if (reminder) { + reg = read_slot(); + memcpy(p, ®, reminder); + } + + return recv_len; +} + +/* + * 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 actual received size. Returns 0 on failure and 1 + * otherwise. + */ +int heci_receive(void *buff, uint32_t *maxlen) +{ + union mei_header hdr; + uint32_t left, received; + uint8_t *p = (uint8_t *)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.fields.is_complete && !me_filled_slots()) + host_gen_interrupt(); + } while (received && !hdr.fields.is_complete && left > 0); + + *maxlen = p - (uint8_t *) buff; + + /* If ME is not ready, something went wrong and we received junk */ + if (!me_ready()) + return 0; + + return hdr.fields.is_complete; +} + +/* + * 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) +{ + union me_csr csr; + + /* send reset request */ + csr.data = read_host_csr(); + csr.fields.host_reset = 1; + csr.fields.int_gen = 1; + write_host_csr(csr.data); + + if (wait_heci_ready()) { + /* device is back on its feet, clear reset */ + csr.data = read_host_csr(); + csr.fields.host_reset = 0; + csr.fields.int_gen = 1; + csr.fields.host_ready = 1; + write_host_csr(csr.data); + return 1; + } + + printk(BIOS_CRIT, "HECI: reset failed\n"); + + return 0; +} + +#if ENV_RAMSTAGE + +static void update_sec_bar(struct device *dev) +{ + g_txe.sec_bar = find_resource(dev, PCI_BASE_ADDRESS_0)->base; +} + +static void txe_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 txe_ops = { + .set_resources = txe_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 txe_driver __pci_driver = { + .ops = &txe_ops, + .vendor = PCI_VENDOR_ID_INTEL, + /* SoC/chipset needs to provide PCI device ID */ + .device = PCI_DEVICE_ID_HECI1 +}; + +#endif diff --git a/src/drivers/intel/txe/txe.h b/src/drivers/intel/txe/txe.h new file mode 100644 index 0000000..7d3aeca --- /dev/null +++ b/src/drivers/intel/txe/txe.h @@ -0,0 +1,60 @@ +/* + * 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 _DRIVERS_INTEL_TXE_H_ +#define _DRIVERS_INTEL_TXE_H_ + +#define MMIO_ME_CB_RW 0x08 +#define MMIO_ME_CSR 0x0c + +#define MMIO_ME_CB_WW 0x00 +#define MMIO_HOST_CSR 0x04 + +union mei_header { + u32 data; + struct { + u32 client_address: 8; + u32 host_address: 8; + u32 length: 9; + u32 reserved: 6; + u32 is_complete: 1; + } __attribute__ ((packed)) fields; +}; + +union me_csr { + u32 data; + struct { + u32 int_en: 1; + u32 int_sts: 1; + u32 int_gen: 1; + u32 host_ready: 1; + u32 host_reset: 1; + u32 rsv: 3; + u32 me_read_offset: 8; + u32 me_write_offset: 8; + u32 me_cir_buff: 8; + } __attribute__ ((packed)) fields; +}; + +/* set up device for use in early boot enviroument with temp bar */ +void heci_init(uintptr_t bar); +/* receive not less than maxlen into buff, returns 0 on failure */ +int heci_receive(void *buff, uint32_t *maxlen); +/* send message of size len, returns 0 on failure */ +int heci_send(void *msg, uint32_t len, uint8_t host, uint8_t client); +/* attempt device reset, returns 0 on failure */ +int heci_reset(void); +#endif // _DRIVERS_INTEL_TXE_H_