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 a9ef1836692c8c15e237d0b4bdb8ad1b8d5eef2c Author: Andrey Petrov andrey.petrov@intel.com Date: Wed Mar 1 15:51:57 2017 -0800
drivers/intel/txe: 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 --- src/drivers/intel/txe/Kconfig | 5 + src/drivers/intel/txe/Makefile.inc | 2 + src/drivers/intel/txe/txe.c | 399 +++++++++++++++++++++++++++++++++++++ src/drivers/intel/txe/txe.h | 58 ++++++ 4 files changed, 464 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..a6ab7f8 --- /dev/null +++ b/src/drivers/intel/txe/txe.c @@ -0,0 +1,399 @@ +/* + * 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; + +void heci_init(uintptr_t bar) { + + struct txe_device *txe = car_get_var_ptr(&g_txe); + device_t dev = HECI1_DEV; + u8 pcireg; + + if (!bar) + bar = HECI1_BASE_ADDRESS; + + /* assume initialized, nothing to do */ + if (txe->sec_bar) + return; + + /* 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, bar); + 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); + + /* Open my own bar, with blackjack and hookers */ + pci_write_config32(dev, PCI_BASE_ADDRESS_0, bar); + + txe->sec_bar = bar; +} + +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_READ_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 */ + wait_write_slots(1); + + /* first, write header */ + write_slot(hdr->data); + + pend_len = hdr->fields.length; + pend_slots = bytes_to_slots(pend_len); + + wait_write_slots(pend_slots); + + /* 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(); + + if (!me_ready()) + return 0; + + return hdr->fields.length; +} + +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, 4)) { + *p++ = read_slot(); + i += SLOT_SIZE; + } + + reminder = recv_len % SLOT_SIZE; + + if (reminder) { + reg = read_slot(); + memcpy(p, ®, reminder); + } + + return recv_len; +} + +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 + * or we run out of space. + */ + do { + received = recv_one_message(&hdr, p, left); + left -= received; + p += received; + /* if read out all, 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; +} + +#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 uart_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 uart_driver __pci_driver = { + .ops = &uart_ops, + .vendor = PCI_VENDOR_ID_INTEL, + /* expectation is that soc/chipset code provides this */ + .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..b837dbf --- /dev/null +++ b/src/drivers/intel/txe/txe.h @@ -0,0 +1,58 @@ +/* + * 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); +#endif // _DRIVERS_INTEL_TXE_H_