| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #define __SIMPLE_DEVICE__ |
| |
| #include <assert.h> |
| #include <commonlib/helpers.h> |
| #include <console/console.h> |
| #include <device/mmio.h> |
| #include <delay.h> |
| #include <device/pci.h> |
| #include <device/pci_ids.h> |
| #include <device/pci_ops.h> |
| #include <intelblocks/cse.h> |
| #include <intelblocks/me.h> |
| #include <intelblocks/pmclib.h> |
| #include <intelblocks/post_codes.h> |
| #include <option.h> |
| #include <security/vboot/misc.h> |
| #include <security/vboot/vboot_common.h> |
| #include <soc/intel/common/reset.h> |
| #include <soc/iomap.h> |
| #include <soc/pci_devs.h> |
| #include <string.h> |
| #include <timer.h> |
| #include <types.h> |
| |
| #define HECI_BASE_SIZE (4 * KiB) |
| |
| #define MAX_HECI_MESSAGE_RETRY_COUNT 5 |
| |
| /* Wait up to 15 sec for HECI to get ready */ |
| #define HECI_DELAY_READY_MS (15 * 1000) |
| /* Wait up to 100 usec between circular buffer polls */ |
| #define HECI_DELAY_US 100 |
| /* Wait up to 5 sec for CSE to chew something we sent */ |
| #define HECI_SEND_TIMEOUT_MS (5 * 1000) |
| /* Wait up to 5 sec for CSE to blurp a reply */ |
| #define HECI_READ_TIMEOUT_MS (5 * 1000) |
| /* Wait up to 1 ms for CSE CIP */ |
| #define HECI_CIP_TIMEOUT_US 1000 |
| /* Wait up to 5 seconds for CSE to boot from RO(BP1) */ |
| #define CSE_DELAY_BOOT_TO_RO_MS (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 MMIO_CSE_DEVIDLE 0x800 |
| #define CSE_DEV_IDLE (1 << 2) |
| #define CSE_DEV_CIP (1 << 0) |
| |
| #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) |
| |
| /* Get HECI BAR 0 from PCI configuration space */ |
| static uintptr_t get_cse_bar(pci_devfn_t dev) |
| { |
| uintptr_t bar; |
| |
| bar = pci_read_config32(dev, PCI_BASE_ADDRESS_0); |
| assert(bar != 0); |
| /* |
| * Bits 31-12 are the base address as per EDS for SPI, |
| * Don't care about 0-11 bit |
| */ |
| return bar & ~PCI_BASE_ADDRESS_MEM_ATTR_MASK; |
| } |
| |
| static void heci_assign_resource(pci_devfn_t dev, uintptr_t tempbar) |
| { |
| u16 pcireg; |
| |
| /* Assign Resources */ |
| /* Clear BIT 1-2 of Command Register */ |
| pcireg = pci_read_config16(dev, PCI_COMMAND); |
| pcireg &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); |
| pci_write_config16(dev, PCI_COMMAND, pcireg); |
| |
| /* Program Temporary BAR for HECI device */ |
| pci_write_config32(dev, PCI_BASE_ADDRESS_0, tempbar); |
| pci_write_config32(dev, PCI_BASE_ADDRESS_1, 0x0); |
| |
| /* Enable Bus Master and MMIO Space */ |
| pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); |
| } |
| |
| /* |
| * Initialize the CSE 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 cse_init(uintptr_t tempbar) |
| { |
| pci_devfn_t dev = PCH_DEV_CSE; |
| |
| /* Check if device enabled */ |
| if (!is_cse_enabled()) |
| return; |
| |
| /* Assume it is already initialized, nothing else to do */ |
| if (get_cse_bar(dev)) |
| return; |
| |
| /* Use default pre-ram bar */ |
| if (!tempbar) |
| tempbar = HECI1_BASE_ADDRESS; |
| |
| /* Assign HECI resource and enable the resource */ |
| heci_assign_resource(dev, tempbar); |
| |
| /* Trigger HECI Reset and make Host ready for communication with CSE */ |
| heci_reset(); |
| } |
| |
| static uint32_t read_bar(pci_devfn_t dev, uint32_t offset) |
| { |
| return read32p(get_cse_bar(dev) + offset); |
| } |
| |
| static void write_bar(pci_devfn_t dev, uint32_t offset, uint32_t val) |
| { |
| return write32p(get_cse_bar(dev) + offset, val); |
| } |
| |
| static uint32_t read_cse_csr(void) |
| { |
| return read_bar(PCH_DEV_CSE, MMIO_CSE_CSR); |
| } |
| |
| static uint32_t read_host_csr(void) |
| { |
| return read_bar(PCH_DEV_CSE, MMIO_HOST_CSR); |
| } |
| |
| static void write_host_csr(uint32_t data) |
| { |
| write_bar(PCH_DEV_CSE, 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(PCH_DEV_CSE, MMIO_CSE_CB_RW); |
| } |
| |
| static void write_slot(uint32_t val) |
| { |
| write_bar(PCH_DEV_CSE, MMIO_CSE_CB_WW, val); |
| } |
| |
| static int wait_write_slots(size_t cnt) |
| { |
| struct stopwatch sw; |
| |
| stopwatch_init_msecs_expire(&sw, HECI_SEND_TIMEOUT_MS); |
| while (host_empty_slots() < cnt) { |
| udelay(HECI_DELAY_US); |
| 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_MS); |
| while (cse_filled_slots() < cnt) { |
| udelay(HECI_DELAY_US); |
| 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 bool cse_check_hfs1_com(int mode) |
| { |
| union me_hfsts1 hfs1; |
| hfs1.data = me_read_config32(PCI_ME_HFSTS1); |
| return hfs1.fields.operation_mode == mode; |
| } |
| |
| static bool cse_is_hfs1_fw_init_complete(void) |
| { |
| union me_hfsts1 hfs1; |
| hfs1.data = me_read_config32(PCI_ME_HFSTS1); |
| if (hfs1.fields.fw_init_complete) |
| return true; |
| return false; |
| } |
| |
| bool cse_is_hfs1_cws_normal(void) |
| { |
| union me_hfsts1 hfs1; |
| hfs1.data = me_read_config32(PCI_ME_HFSTS1); |
| if (hfs1.fields.working_state == ME_HFS1_CWS_NORMAL) |
| return true; |
| return false; |
| } |
| |
| bool cse_is_hfs1_com_normal(void) |
| { |
| return cse_check_hfs1_com(ME_HFS1_COM_NORMAL); |
| } |
| |
| bool cse_is_hfs1_com_secover_mei_msg(void) |
| { |
| return cse_check_hfs1_com(ME_HFS1_COM_SECOVER_MEI_MSG); |
| } |
| |
| bool cse_is_hfs1_com_soft_temp_disable(void) |
| { |
| return cse_check_hfs1_com(ME_HFS1_COM_SOFT_TEMP_DISABLE); |
| } |
| |
| /* |
| * Starting from TGL platform, HFSTS1.spi_protection_mode replaces mfg_mode to indicate |
| * SPI protection status as well as end-of-manufacturing(EOM) status where EOM flow is |
| * triggered in single staged operation (either through first boot with required MFIT |
| * configuratin or FPT /CLOSEMANUF). |
| * In staged manufacturing flow, spi_protection_mode alone doesn't indicate the EOM status. |
| * |
| * HFSTS1.spi_protection_mode description: |
| * mfg_mode = 0 means SPI protection is on. |
| * mfg_mode = 1 means SPI is unprotected. |
| */ |
| bool cse_is_hfs1_spi_protected(void) |
| { |
| union me_hfsts1 hfs1; |
| hfs1.data = me_read_config32(PCI_ME_HFSTS1); |
| return !hfs1.fields.mfg_mode; |
| } |
| |
| bool cse_is_hfs3_fw_sku_lite(void) |
| { |
| union me_hfsts3 hfs3; |
| hfs3.data = me_read_config32(PCI_ME_HFSTS3); |
| return hfs3.fields.fw_sku == ME_HFS3_FW_SKU_LITE; |
| } |
| |
| /* Makes the host ready to communicate with CSE */ |
| void cse_set_host_ready(void) |
| { |
| uint32_t csr; |
| csr = read_host_csr(); |
| csr &= ~CSR_RESET; |
| csr |= (CSR_IG | CSR_READY); |
| write_host_csr(csr); |
| } |
| |
| /* Polls for ME mode ME_HFS1_COM_SECOVER_MEI_MSG for 15 seconds */ |
| uint8_t cse_wait_sec_override_mode(void) |
| { |
| struct stopwatch sw; |
| stopwatch_init_msecs_expire(&sw, HECI_DELAY_READY_MS); |
| while (!cse_is_hfs1_com_secover_mei_msg()) { |
| udelay(HECI_DELAY_US); |
| if (stopwatch_expired(&sw)) { |
| printk(BIOS_ERR, "HECI: Timed out waiting for SEC_OVERRIDE mode!\n"); |
| return 0; |
| } |
| } |
| printk(BIOS_DEBUG, "HECI: CSE took %lld ms to enter security override mode\n", |
| stopwatch_duration_msecs(&sw)); |
| return 1; |
| } |
| |
| /* |
| * Polls for CSE's current operation mode 'Soft Temporary Disable'. |
| * The CSE enters the current operation mode when it boots from RO(BP1). |
| */ |
| uint8_t cse_wait_com_soft_temp_disable(void) |
| { |
| struct stopwatch sw; |
| stopwatch_init_msecs_expire(&sw, CSE_DELAY_BOOT_TO_RO_MS); |
| while (!cse_is_hfs1_com_soft_temp_disable()) { |
| udelay(HECI_DELAY_US); |
| if (stopwatch_expired(&sw)) { |
| printk(BIOS_ERR, "HECI: Timed out waiting for CSE to boot from RO!\n"); |
| return 0; |
| } |
| } |
| printk(BIOS_SPEW, "HECI: CSE took %lld ms to boot from RO\n", |
| stopwatch_duration_msecs(&sw)); |
| return 1; |
| } |
| |
| static int wait_heci_ready(void) |
| { |
| struct stopwatch sw; |
| |
| stopwatch_init_msecs_expire(&sw, HECI_DELAY_READY_MS); |
| while (!cse_ready()) { |
| udelay(HECI_DELAY_US); |
| 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; |
| } |
| |
| enum cse_tx_rx_status |
| heci_send(const void *msg, size_t len, uint8_t host_addr, uint8_t client_addr) |
| { |
| uint8_t retry; |
| uint32_t csr, hdr; |
| size_t sent, remaining, cb_size, max_length; |
| const uint8_t *p; |
| |
| if (!msg || !len) |
| return CSE_TX_ERR_INPUT; |
| |
| clear_int(); |
| |
| for (retry = 0; retry < MAX_HECI_MESSAGE_RETRY_COUNT; retry++) { |
| p = msg; |
| |
| if (!wait_heci_ready()) { |
| printk(BIOS_ERR, "HECI: not ready\n"); |
| continue; |
| } |
| |
| csr = read_host_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 circular 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); |
| |
| if (!remaining) |
| return CSE_TX_RX_SUCCESS; |
| } |
| |
| printk(BIOS_DEBUG, "HECI: Trigger HECI reset\n"); |
| heci_reset(); |
| return CSE_TX_ERR_CSE_NOT_READY; |
| } |
| |
| static enum cse_tx_rx_status |
| recv_one_message(uint32_t *hdr, void *buff, size_t maxlen, size_t *recv_len) |
| { |
| uint32_t reg, *p = buff; |
| size_t recv_slots, remainder, i; |
| |
| /* first get the header */ |
| if (!wait_read_slots(1)) |
| return CSE_RX_ERR_TIMEOUT; |
| |
| *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 CSE_RX_ERR_RESP_LEN_MISMATCH; |
| } |
| |
| /* 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; |
| } |
| |
| /* |
| * If ME is not ready, something went wrong and |
| * we received junk |
| */ |
| if (!cse_ready()) |
| return CSE_RX_ERR_CSE_NOT_READY; |
| |
| remainder = *recv_len % SLOT_SIZE; |
| |
| if (remainder) { |
| reg = read_slot(); |
| memcpy(p, ®, remainder); |
| } |
| return CSE_TX_RX_SUCCESS; |
| } |
| |
| enum cse_tx_rx_status heci_receive(void *buff, size_t *maxlen) |
| { |
| uint8_t retry; |
| size_t left, received; |
| uint32_t hdr = 0; |
| uint8_t *p; |
| enum cse_tx_rx_status ret = CSE_RX_ERR_TIMEOUT; |
| |
| if (!buff || !maxlen || !*maxlen) |
| return CSE_RX_ERR_INPUT; |
| |
| clear_int(); |
| |
| for (retry = 0; retry < MAX_HECI_MESSAGE_RETRY_COUNT; retry++) { |
| p = buff; |
| left = *maxlen; |
| |
| if (!wait_heci_ready()) { |
| printk(BIOS_ERR, "HECI: not ready\n"); |
| continue; |
| } |
| |
| /* |
| * Receive multiple packets until we meet one marked |
| * complete or we run out of space in caller-provided buffer. |
| */ |
| do { |
| ret = recv_one_message(&hdr, p, left, &received); |
| if (ret) { |
| printk(BIOS_ERR, "HECI: Failed to receive!\n"); |
| goto CSE_RX_ERR_HANDLE; |
| } |
| 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); |
| |
| if ((hdr & MEI_HDR_IS_COMPLETE) && received) { |
| *maxlen = p - (uint8_t *)buff; |
| if (CONFIG(SOC_INTEL_CSE_SERVER_SKU)) |
| clear_int(); |
| return CSE_TX_RX_SUCCESS; |
| } |
| } |
| |
| CSE_RX_ERR_HANDLE: |
| printk(BIOS_DEBUG, "HECI: Trigger HECI Reset\n"); |
| heci_reset(); |
| return CSE_RX_ERR_CSE_NOT_READY; |
| } |
| |
| enum cse_tx_rx_status heci_send_receive(const void *snd_msg, size_t snd_sz, void *rcv_msg, |
| size_t *rcv_sz, uint8_t cse_addr) |
| { |
| enum cse_tx_rx_status ret; |
| |
| ret = heci_send(snd_msg, snd_sz, BIOS_HOST_ADDR, cse_addr); |
| if (ret) { |
| printk(BIOS_ERR, "HECI: send Failed\n"); |
| return ret; |
| } |
| |
| if (rcv_msg != NULL) { |
| ret = heci_receive(rcv_msg, rcv_sz); |
| if (ret) { |
| printk(BIOS_ERR, "HECI: receive Failed\n"); |
| return ret; |
| } |
| } |
| return ret; |
| } |
| |
| /* |
| * 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; |
| |
| /* Clear post code to prevent eventlog entry from unknown code. */ |
| post_code(POST_CODE_ZERO); |
| |
| /* Send reset request */ |
| csr = read_host_csr(); |
| csr |= (CSR_RESET | CSR_IG); |
| write_host_csr(csr); |
| |
| if (wait_heci_ready()) { |
| /* Device is back on its imaginary feet, clear reset */ |
| cse_set_host_ready(); |
| return 1; |
| } |
| |
| printk(BIOS_CRIT, "HECI: reset failed\n"); |
| |
| return 0; |
| } |
| |
| bool is_cse_devfn_visible(unsigned int devfn) |
| { |
| int slot = PCI_SLOT(devfn); |
| int func = PCI_FUNC(devfn); |
| |
| if (!is_devfn_enabled(devfn)) { |
| printk(BIOS_WARNING, "HECI: CSE device %02x.%01x is disabled\n", slot, func); |
| return false; |
| } |
| |
| if (pci_read_config16(PCI_DEV(0, slot, func), PCI_VENDOR_ID) == 0xFFFF) { |
| printk(BIOS_WARNING, "HECI: CSE device %02x.%01x is hidden\n", slot, func); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool is_cse_enabled(void) |
| { |
| return is_cse_devfn_visible(PCH_DEVFN_CSE); |
| } |
| |
| uint32_t me_read_config32(int offset) |
| { |
| return pci_read_config32(PCH_DEV_CSE, offset); |
| } |
| |
| static bool cse_is_global_reset_allowed(void) |
| { |
| /* |
| * Allow sending GLOBAL_RESET command only if: |
| * - CSE's current working state is Normal and current operation mode is Normal. |
| * - (or) CSE's current working state is normal and current operation mode can |
| * be Soft Temp Disable or Security Override Mode if CSE's Firmware SKU is |
| * Lite. |
| */ |
| if (!cse_is_hfs1_cws_normal()) |
| return false; |
| |
| if (cse_is_hfs1_com_normal()) |
| return true; |
| |
| if (cse_is_hfs3_fw_sku_lite()) { |
| if (cse_is_hfs1_com_soft_temp_disable() || cse_is_hfs1_com_secover_mei_msg()) |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * Sends GLOBAL_RESET_REQ cmd to CSE with reset type GLOBAL_RESET. |
| * Returns 0 on failure and 1 on success. |
| */ |
| static int cse_request_reset(enum rst_req_type rst_type) |
| { |
| int status; |
| struct mkhi_hdr reply; |
| struct reset_message { |
| struct mkhi_hdr hdr; |
| uint8_t req_origin; |
| uint8_t reset_type; |
| } __packed; |
| struct reset_message msg = { |
| .hdr = { |
| .group_id = MKHI_GROUP_ID_CBM, |
| .command = MKHI_CBM_GLOBAL_RESET_REQ, |
| }, |
| .req_origin = GR_ORIGIN_BIOS_POST, |
| .reset_type = rst_type |
| }; |
| size_t reply_size; |
| |
| printk(BIOS_DEBUG, "HECI: Global Reset(Type:%d) Command\n", rst_type); |
| |
| if (!(rst_type == GLOBAL_RESET || rst_type == CSE_RESET_ONLY)) { |
| printk(BIOS_ERR, "HECI: Unsupported reset type is requested\n"); |
| return 0; |
| } |
| |
| if (!cse_is_global_reset_allowed() || !is_cse_enabled()) { |
| printk(BIOS_ERR, "HECI: CSE does not meet required prerequisites\n"); |
| return 0; |
| } |
| |
| heci_reset(); |
| |
| reply_size = sizeof(reply); |
| memset(&reply, 0, reply_size); |
| |
| if (rst_type == CSE_RESET_ONLY) |
| status = heci_send(&msg, sizeof(msg), BIOS_HOST_ADDR, HECI_MKHI_ADDR); |
| else |
| status = heci_send_receive(&msg, sizeof(msg), &reply, &reply_size, |
| HECI_MKHI_ADDR); |
| |
| printk(BIOS_DEBUG, "HECI: Global Reset %s!\n", !status ? "success" : "failure"); |
| return status; |
| } |
| |
| int cse_request_global_reset(void) |
| { |
| return cse_request_reset(GLOBAL_RESET); |
| } |
| |
| static bool cse_is_hmrfpo_enable_allowed(void) |
| { |
| /* |
| * Allow sending HMRFPO ENABLE command only if: |
| * - CSE's current working state is Normal and current operation mode is Normal |
| * - (or) cse's current working state is normal and current operation mode is |
| * Soft Temp Disable if CSE's Firmware SKU is Lite |
| */ |
| if (!cse_is_hfs1_cws_normal()) |
| return false; |
| |
| if (cse_is_hfs1_com_normal()) |
| return true; |
| |
| if (cse_is_hfs3_fw_sku_lite() && cse_is_hfs1_com_soft_temp_disable()) |
| return true; |
| |
| return false; |
| } |
| |
| /* Sends HMRFPO Enable command to CSE */ |
| enum cb_err cse_hmrfpo_enable(void) |
| { |
| struct hmrfpo_enable_msg { |
| struct mkhi_hdr hdr; |
| uint32_t nonce[2]; |
| } __packed; |
| |
| /* HMRFPO Enable message */ |
| struct hmrfpo_enable_msg msg = { |
| .hdr = { |
| .group_id = MKHI_GROUP_ID_HMRFPO, |
| .command = MKHI_HMRFPO_ENABLE, |
| }, |
| .nonce = {0}, |
| }; |
| |
| /* HMRFPO Enable response */ |
| struct hmrfpo_enable_resp { |
| struct mkhi_hdr hdr; |
| /* Base addr for factory data area, not relevant for client SKUs */ |
| uint32_t fct_base; |
| /* Length of factory data area, not relevant for client SKUs */ |
| uint32_t fct_limit; |
| uint8_t status; |
| uint8_t reserved[3]; |
| } __packed; |
| |
| struct hmrfpo_enable_resp resp; |
| size_t resp_size = sizeof(struct hmrfpo_enable_resp); |
| |
| if (cse_is_hfs1_com_secover_mei_msg()) { |
| printk(BIOS_DEBUG, "HECI: CSE is already in security override mode, " |
| "skip sending HMRFPO_ENABLE command to CSE\n"); |
| return CB_SUCCESS; |
| } |
| |
| printk(BIOS_DEBUG, "HECI: Send HMRFPO Enable Command\n"); |
| |
| if (!cse_is_hmrfpo_enable_allowed()) { |
| printk(BIOS_ERR, "HECI: CSE does not meet required prerequisites\n"); |
| return CB_ERR; |
| } |
| |
| if (heci_send_receive(&msg, sizeof(struct hmrfpo_enable_msg), |
| &resp, &resp_size, HECI_MKHI_ADDR)) |
| return CB_ERR; |
| |
| if (resp.hdr.result) { |
| printk(BIOS_ERR, "HECI: Resp Failed:%d\n", resp.hdr.result); |
| return CB_ERR; |
| } |
| |
| if (resp.status) { |
| printk(BIOS_ERR, "HECI: HMRFPO_Enable Failed (resp status: %d)\n", resp.status); |
| return CB_ERR; |
| } |
| |
| return CB_SUCCESS; |
| } |
| |
| /* |
| * Sends HMRFPO Get Status command to CSE to get the HMRFPO status. |
| * The status can be DISABLED/LOCKED/ENABLED |
| */ |
| int cse_hmrfpo_get_status(void) |
| { |
| struct hmrfpo_get_status_msg { |
| struct mkhi_hdr hdr; |
| } __packed; |
| |
| struct hmrfpo_get_status_resp { |
| struct mkhi_hdr hdr; |
| uint8_t status; |
| uint8_t reserved[3]; |
| } __packed; |
| |
| struct hmrfpo_get_status_msg msg = { |
| .hdr = { |
| .group_id = MKHI_GROUP_ID_HMRFPO, |
| .command = MKHI_HMRFPO_GET_STATUS, |
| }, |
| }; |
| struct hmrfpo_get_status_resp resp; |
| size_t resp_size = sizeof(struct hmrfpo_get_status_resp); |
| |
| printk(BIOS_INFO, "HECI: Sending Get HMRFPO Status Command\n"); |
| |
| if (!cse_is_hfs1_cws_normal()) { |
| printk(BIOS_ERR, "HECI: CSE's current working state is not Normal\n"); |
| return -1; |
| } |
| |
| if (heci_send_receive(&msg, sizeof(struct hmrfpo_get_status_msg), |
| &resp, &resp_size, HECI_MKHI_ADDR)) { |
| printk(BIOS_ERR, "HECI: HMRFPO send/receive fail\n"); |
| return -1; |
| } |
| |
| if (resp.hdr.result) { |
| printk(BIOS_ERR, "HECI: HMRFPO Resp Failed:%d\n", |
| resp.hdr.result); |
| return -1; |
| } |
| |
| return resp.status; |
| } |
| |
| void print_me_fw_version(void *unused) |
| { |
| struct me_fw_ver_resp resp = {0}; |
| |
| /* Ignore if UART debugging is disabled */ |
| if (!CONFIG(CONSOLE_SERIAL)) |
| return; |
| |
| if (get_me_fw_version(&resp) == CB_SUCCESS) { |
| printk(BIOS_DEBUG, "ME: Version: %d.%d.%d.%d\n", resp.code.major, |
| resp.code.minor, resp.code.hotfix, resp.code.build); |
| return; |
| } |
| printk(BIOS_DEBUG, "ME: Version: Unavailable\n"); |
| } |
| |
| enum cb_err get_me_fw_version(struct me_fw_ver_resp *resp) |
| { |
| const struct mkhi_hdr fw_ver_msg = { |
| .group_id = MKHI_GROUP_ID_GEN, |
| .command = MKHI_GEN_GET_FW_VERSION, |
| }; |
| |
| if (resp == NULL) { |
| printk(BIOS_ERR, "%s failed, null pointer parameter\n", __func__); |
| return CB_ERR; |
| } |
| size_t resp_size = sizeof(*resp); |
| |
| /* Ignore if CSE is disabled */ |
| if (!is_cse_enabled()) |
| return CB_ERR; |
| |
| /* |
| * Ignore if ME Firmware SKU type is Lite since |
| * print_boot_partition_info() logs RO(BP1) and RW(BP2) versions. |
| */ |
| if (cse_is_hfs3_fw_sku_lite()) |
| return CB_ERR; |
| |
| /* |
| * Prerequisites: |
| * 1) HFSTS1 Current Working State is Normal |
| * 2) HFSTS1 Current Operation Mode is Normal |
| * 3) It's after DRAM INIT DONE message (taken care of by calling it |
| * during ramstage |
| */ |
| if (!cse_is_hfs1_cws_normal() || !cse_is_hfs1_com_normal()) |
| return CB_ERR; |
| |
| heci_reset(); |
| |
| if (heci_send_receive(&fw_ver_msg, sizeof(fw_ver_msg), resp, &resp_size, |
| HECI_MKHI_ADDR)) |
| return CB_ERR; |
| |
| if (resp->hdr.result) |
| return CB_ERR; |
| |
| |
| return CB_SUCCESS; |
| } |
| |
| void cse_trigger_vboot_recovery(enum csme_failure_reason reason) |
| { |
| printk(BIOS_DEBUG, "cse: CSE status registers: HFSTS1: 0x%x, HFSTS2: 0x%x " |
| "HFSTS3: 0x%x\n", me_read_config32(PCI_ME_HFSTS1), |
| me_read_config32(PCI_ME_HFSTS2), me_read_config32(PCI_ME_HFSTS3)); |
| |
| if (CONFIG(VBOOT)) |
| vboot_fail_and_reboot(vboot_get_context(), VB2_RECOVERY_INTEL_CSE_LITE_SKU, |
| reason); |
| |
| die("cse: Failed to trigger recovery mode(recovery subcode:%d)\n", reason); |
| } |
| |
| static bool disable_cse_idle(pci_devfn_t dev) |
| { |
| struct stopwatch sw; |
| uint32_t dev_idle_ctrl = read_bar(dev, MMIO_CSE_DEVIDLE); |
| dev_idle_ctrl &= ~CSE_DEV_IDLE; |
| write_bar(dev, MMIO_CSE_DEVIDLE, dev_idle_ctrl); |
| |
| stopwatch_init_usecs_expire(&sw, HECI_CIP_TIMEOUT_US); |
| do { |
| dev_idle_ctrl = read_bar(dev, MMIO_CSE_DEVIDLE); |
| if ((dev_idle_ctrl & CSE_DEV_CIP) == CSE_DEV_CIP) |
| return true; |
| udelay(HECI_DELAY_US); |
| } while (!stopwatch_expired(&sw)); |
| |
| return false; |
| } |
| |
| static void enable_cse_idle(pci_devfn_t dev) |
| { |
| uint32_t dev_idle_ctrl = read_bar(dev, MMIO_CSE_DEVIDLE); |
| dev_idle_ctrl |= CSE_DEV_IDLE; |
| write_bar(dev, MMIO_CSE_DEVIDLE, dev_idle_ctrl); |
| } |
| |
| enum cse_device_state get_cse_device_state(unsigned int devfn) |
| { |
| pci_devfn_t dev = PCI_DEV(0, PCI_SLOT(devfn), PCI_FUNC(devfn)); |
| uint32_t dev_idle_ctrl = read_bar(dev, MMIO_CSE_DEVIDLE); |
| if ((dev_idle_ctrl & CSE_DEV_IDLE) == CSE_DEV_IDLE) |
| return DEV_IDLE; |
| |
| return DEV_ACTIVE; |
| } |
| |
| static enum cse_device_state ensure_cse_active(pci_devfn_t dev) |
| { |
| if (!disable_cse_idle(dev)) |
| return DEV_IDLE; |
| pci_or_config32(dev, PCI_COMMAND, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); |
| |
| return DEV_ACTIVE; |
| } |
| |
| static void ensure_cse_idle(pci_devfn_t dev) |
| { |
| enable_cse_idle(dev); |
| |
| pci_and_config32(dev, PCI_COMMAND, ~(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER)); |
| } |
| |
| bool set_cse_device_state(unsigned int devfn, enum cse_device_state requested_state) |
| { |
| enum cse_device_state current_state = get_cse_device_state(devfn); |
| pci_devfn_t dev = PCI_DEV(0, PCI_SLOT(devfn), PCI_FUNC(devfn)); |
| |
| if (current_state == requested_state) |
| return true; |
| |
| if (requested_state == DEV_ACTIVE) |
| return ensure_cse_active(dev) == requested_state; |
| else |
| ensure_cse_idle(dev); |
| |
| return true; |
| } |
| |
| void cse_set_to_d0i3(void) |
| { |
| if (!is_cse_devfn_visible(PCH_DEVFN_CSE)) |
| return; |
| |
| set_cse_device_state(PCH_DEVFN_CSE, DEV_IDLE); |
| } |
| |
| /* Function to set D0I3 for all HECI devices */ |
| void heci_set_to_d0i3(void) |
| { |
| for (int i = 0; i < CONFIG_MAX_HECI_DEVICES; i++) { |
| unsigned int devfn = PCI_DEVFN(PCH_DEV_SLOT_CSE, i); |
| if (!is_cse_devfn_visible(devfn)) |
| continue; |
| |
| set_cse_device_state(devfn, DEV_IDLE); |
| } |
| } |
| |
| /* Initialize the HECI devices. */ |
| void heci_init(void) |
| { |
| for (int i = 0; i < CONFIG_MAX_HECI_DEVICES; i++) { |
| unsigned int devfn = PCI_DEVFN(PCH_DEV_SLOT_CSE, i); |
| pci_devfn_t dev = PCI_DEV(0, PCI_SLOT(devfn), PCI_FUNC(devfn)); |
| |
| if (!is_cse_devfn_visible(devfn)) |
| continue; |
| |
| /* Assume it is already initialized, nothing else to do */ |
| if (get_cse_bar(dev)) |
| return; |
| |
| heci_assign_resource(dev, HECI1_BASE_ADDRESS + (i * HECI_BASE_SIZE)); |
| |
| ensure_cse_active(dev); |
| } |
| /* Trigger HECI Reset and make Host ready for communication with CSE */ |
| heci_reset(); |
| } |
| |
| void cse_control_global_reset_lock(void) |
| { |
| /* |
| * As per ME BWG recommendation the BIOS should not lock down CF9GR bit during |
| * manufacturing and re-manufacturing environment if HFSTS1 [4] is set. Note: |
| * this recommendation is not applicable for CSE-Lite SKUs where BIOS should set |
| * CF9LOCK bit irrespectively. |
| * |
| * Other than that, make sure payload/OS can't trigger global reset. |
| * |
| * BIOS must also ensure that CF9GR is cleared and locked (Bit31 of ETR3) |
| * prior to transferring control to the OS. |
| */ |
| if (CONFIG(SOC_INTEL_CSE_LITE_SKU) || cse_is_hfs1_spi_protected()) |
| pmc_global_reset_disable_and_lock(); |
| else |
| pmc_global_reset_enable(false); |
| } |
| |
| enum cb_err cse_get_fw_feature_state(uint32_t *feature_state) |
| { |
| struct fw_feature_state_msg { |
| struct mkhi_hdr hdr; |
| uint32_t rule_id; |
| } __packed; |
| |
| /* Get Firmware Feature State message */ |
| struct fw_feature_state_msg msg = { |
| .hdr = { |
| .group_id = MKHI_GROUP_ID_FWCAPS, |
| .command = MKHI_FWCAPS_GET_FW_FEATURE_STATE, |
| }, |
| .rule_id = ME_FEATURE_STATE_RULE_ID |
| }; |
| |
| /* Get Firmware Feature State response */ |
| struct fw_feature_state_resp { |
| struct mkhi_hdr hdr; |
| uint32_t rule_id; |
| uint8_t rule_len; |
| uint32_t fw_runtime_status; |
| } __packed; |
| |
| struct fw_feature_state_resp resp; |
| size_t resp_size = sizeof(struct fw_feature_state_resp); |
| |
| /* Ignore if CSE is disabled or input buffer is invalid */ |
| if (!is_cse_enabled() || !feature_state) |
| return CB_ERR; |
| |
| /* |
| * Prerequisites: |
| * 1) HFSTS1 Current Working State is Normal |
| * 2) HFSTS1 Current Operation Mode is Normal |
| * 3) It's after DRAM INIT DONE message (taken care of by calling it |
| * during ramstage) |
| */ |
| if (!cse_is_hfs1_cws_normal() || !cse_is_hfs1_com_normal() || !ENV_RAMSTAGE) |
| return CB_ERR; |
| |
| printk(BIOS_DEBUG, "HECI: Send GET FW FEATURE STATE Command\n"); |
| |
| if (heci_send_receive(&msg, sizeof(struct fw_feature_state_msg), |
| &resp, &resp_size, HECI_MKHI_ADDR)) |
| return CB_ERR; |
| |
| if (resp.hdr.result) { |
| printk(BIOS_ERR, "HECI: Resp Failed:%d\n", resp.hdr.result); |
| return CB_ERR; |
| } |
| |
| if (resp.rule_len != sizeof(resp.fw_runtime_status)) { |
| printk(BIOS_ERR, "HECI: GET FW FEATURE STATE has invalid rule data length\n"); |
| return CB_ERR; |
| } |
| |
| *feature_state = resp.fw_runtime_status; |
| |
| return CB_SUCCESS; |
| } |
| |
| void cse_enable_ptt(bool state) |
| { |
| struct fw_feature_shipment_override_msg { |
| struct mkhi_hdr hdr; |
| uint32_t enable_mask; |
| uint32_t disable_mask; |
| } __packed; |
| |
| /* FW Feature Shipment Time State Override message */ |
| struct fw_feature_shipment_override_msg msg = { |
| .hdr = { |
| .group_id = MKHI_GROUP_ID_GEN, |
| .command = MKHI_GEN_FW_FEATURE_SHIPMENT_OVER, |
| }, |
| .enable_mask = 0, |
| .disable_mask = 0 |
| }; |
| |
| /* FW Feature Shipment Time State Override response */ |
| struct fw_feature_shipment_override_resp { |
| struct mkhi_hdr hdr; |
| uint32_t data; |
| } __packed; |
| |
| struct fw_feature_shipment_override_resp resp; |
| size_t resp_size = sizeof(struct fw_feature_shipment_override_resp); |
| uint32_t feature_status; |
| |
| /* Ignore if CSE is disabled */ |
| if (!is_cse_enabled()) |
| return; |
| |
| printk(BIOS_DEBUG, "Requested to change PTT state to %sabled\n", state ? "en" : "dis"); |
| |
| /* |
| * Prerequisites: |
| * 1) HFSTS1 Current Working State is Normal |
| * 2) HFSTS1 Current Operation Mode is Normal |
| * 3) It's after DRAM INIT DONE message (taken care of by calling it |
| * during ramstage |
| * 4) HFSTS1 FW Init Complete is set |
| * 5) Before EOP issued to CSE |
| */ |
| if (!cse_is_hfs1_cws_normal() || !cse_is_hfs1_com_normal() || |
| !cse_is_hfs1_fw_init_complete() || !ENV_RAMSTAGE) { |
| printk(BIOS_ERR, "HECI: Unmet prerequisites for" |
| "FW FEATURE SHIPMENT TIME STATE OVERRIDE\n"); |
| return; |
| } |
| |
| if (cse_get_fw_feature_state(&feature_status) != CB_SUCCESS) { |
| printk(BIOS_ERR, "HECI: Cannot determine current feature status\n"); |
| return; |
| } |
| |
| if (!!(feature_status & ME_FW_FEATURE_PTT) == state) { |
| printk(BIOS_DEBUG, "HECI: PTT is already in the requested state\n"); |
| return; |
| } |
| |
| printk(BIOS_DEBUG, "HECI: Send FW FEATURE SHIPMENT TIME STATE OVERRIDE Command\n"); |
| |
| if (state) |
| msg.enable_mask |= ME_FW_FEATURE_PTT; |
| else |
| msg.disable_mask |= ME_FW_FEATURE_PTT; |
| |
| if (heci_send_receive(&msg, sizeof(struct fw_feature_shipment_override_msg), |
| &resp, &resp_size, HECI_MKHI_ADDR)) |
| return; |
| |
| if (resp.hdr.result) { |
| printk(BIOS_ERR, "HECI: Resp Failed:%d\n", resp.hdr.result); |
| return; |
| } |
| |
| /* Global reset is required after acceptance of the command */ |
| if (resp.data == 0) { |
| printk(BIOS_DEBUG, "HECI: FW FEATURE SHIPMENT TIME STATE OVERRIDE success\n"); |
| do_global_reset(); |
| } else { |
| printk(BIOS_ERR, "HECI: FW FEATURE SHIPMENT TIME STATE OVERRIDE error (%x)\n", |
| resp.data); |
| } |
| } |
| |
| #if ENV_RAMSTAGE |
| |
| /* |
| * Disable the Intel (CS)Management Engine via HECI based on a cmos value |
| * of `me_state`. A value of `0` will result in a (CS)ME state of `0` (working) |
| * and value of `1` will result in a (CS)ME state of `3` (disabled). |
| * |
| * It isn't advised to use this in combination with me_cleaner. |
| * |
| * It is advisable to have a second cmos option called `me_state_counter`. |
| * Whilst not essential, it avoid reboots loops if the (CS)ME fails to |
| * change states after 3 attempts. Some versions of the (CS)ME need to be |
| * reset 3 times. |
| * |
| * Ideal cmos values would be: |
| * |
| * # coreboot config options: cpu |
| * 432 1 e 5 me_state |
| * 440 4 h 0 me_state_counter |
| * |
| * #ID value text |
| * 5 0 Enable |
| * 5 1 Disable |
| */ |
| |
| static void me_reset_with_count(void) |
| { |
| unsigned int cmos_me_state_counter = get_uint_option("me_state_counter", UINT_MAX); |
| |
| if (cmos_me_state_counter != UINT_MAX) { |
| printk(BIOS_DEBUG, "CMOS: me_state_counter = %u\n", cmos_me_state_counter); |
| /* Avoid boot loops by only trying a state change 3 times */ |
| if (cmos_me_state_counter < ME_DISABLE_ATTEMPTS) { |
| cmos_me_state_counter++; |
| set_uint_option("me_state_counter", cmos_me_state_counter); |
| printk(BIOS_DEBUG, "ME: Reset attempt %u/%u.\n", cmos_me_state_counter, |
| ME_DISABLE_ATTEMPTS); |
| do_global_reset(); |
| } else { |
| /* |
| * If the (CS)ME fails to change states after 3 attempts, it will |
| * likely need a cold boot, or recovering. |
| */ |
| printk(BIOS_ERR, "Failed to change ME state in %u attempts!\n", |
| ME_DISABLE_ATTEMPTS); |
| |
| } |
| } else { |
| printk(BIOS_DEBUG, "ME: Resetting"); |
| do_global_reset(); |
| } |
| } |
| |
| static void cse_set_state(struct device *dev) |
| { |
| |
| /* (CS)ME Disable Command */ |
| struct me_disable_command { |
| struct mkhi_hdr hdr; |
| uint32_t rule_id; |
| uint8_t rule_len; |
| uint32_t rule_data; |
| } __packed me_disable = { |
| .hdr = { |
| .group_id = MKHI_GROUP_ID_FWCAPS, |
| .command = MKHI_SET_ME_DISABLE, |
| }, |
| .rule_id = ME_DISABLE_RULE_ID, |
| .rule_len = ME_DISABLE_RULE_LENGTH, |
| .rule_data = ME_DISABLE_COMMAND, |
| }; |
| |
| struct me_disable_reply { |
| struct mkhi_hdr hdr; |
| uint32_t rule_id; |
| } __packed; |
| |
| struct me_disable_reply disable_reply; |
| |
| size_t disable_reply_size; |
| |
| /* (CS)ME Enable Command */ |
| struct me_enable_command { |
| struct mkhi_hdr hdr; |
| } me_enable = { |
| .hdr = { |
| .group_id = MKHI_GROUP_ID_BUP_COMMON, |
| .command = MKHI_SET_ME_ENABLE, |
| }, |
| }; |
| |
| struct me_enable_reply { |
| struct mkhi_hdr hdr; |
| } __packed; |
| |
| struct me_enable_reply enable_reply; |
| |
| size_t enable_reply_size; |
| |
| /* Function Start */ |
| |
| int send; |
| int result; |
| /* |
| * Check if the CMOS value "me_state" exists, if it doesn't, then |
| * don't do anything. |
| */ |
| const unsigned int cmos_me_state = get_uint_option("me_state", UINT_MAX); |
| |
| if (cmos_me_state == UINT_MAX) |
| return; |
| |
| printk(BIOS_DEBUG, "CMOS: me_state = %u\n", cmos_me_state); |
| |
| /* |
| * We only take action if the me_state doesn't match the CS(ME) working state |
| */ |
| |
| const unsigned int soft_temp_disable = cse_is_hfs1_com_soft_temp_disable(); |
| |
| if (cmos_me_state && !soft_temp_disable) { |
| /* me_state should be disabled, but it's enabled */ |
| printk(BIOS_DEBUG, "ME needs to be disabled.\n"); |
| send = heci_send_receive(&me_disable, sizeof(me_disable), |
| &disable_reply, &disable_reply_size, HECI_MKHI_ADDR); |
| result = disable_reply.hdr.result; |
| } else if (!cmos_me_state && soft_temp_disable) { |
| /* me_state should be enabled, but it's disabled */ |
| printk(BIOS_DEBUG, "ME needs to be enabled.\n"); |
| send = heci_send_receive(&me_enable, sizeof(me_enable), |
| &enable_reply, &enable_reply_size, HECI_MKHI_ADDR); |
| result = enable_reply.hdr.result; |
| } else { |
| printk(BIOS_DEBUG, "ME is %s.\n", cmos_me_state ? "disabled" : "enabled"); |
| unsigned int cmos_me_state_counter = get_uint_option("me_state_counter", |
| UINT_MAX); |
| /* set me_state_counter to 0 */ |
| if ((cmos_me_state_counter != UINT_MAX && cmos_me_state_counter != 0)) |
| set_uint_option("me_state_counter", 0); |
| return; |
| } |
| |
| printk(BIOS_DEBUG, "HECI: ME state change send %s!\n", |
| !send ? "success" : "failure"); |
| printk(BIOS_DEBUG, "HECI: ME state change result %s!\n", |
| result ? "success" : "failure"); |
| |
| /* |
| * Reset if the result was successful, or if the send failed as some older |
| * version of the Intel (CS)ME won't successfully receive the message unless reset |
| * twice. |
| */ |
| if (send || !result) |
| me_reset_with_count(); |
| } |
| |
| /* |
| * `cse_final_ready_to_boot` function is native implementation of equivalent events |
| * performed by FSP NotifyPhase(Ready To Boot) API invocations. |
| * |
| * Operations are: |
| * 1. Perform global reset lock. |
| * 2. Put HECI1 to D0i3 and disable the HECI1 if the user selects |
| * DISABLE_HECI1_AT_PRE_BOOT config or CSE HFSTS1 Operation Mode is |
| * `Software Temporary Disable`. |
| */ |
| static void cse_final_ready_to_boot(void) |
| { |
| cse_control_global_reset_lock(); |
| |
| if (CONFIG(DISABLE_HECI1_AT_PRE_BOOT) || cse_is_hfs1_com_soft_temp_disable()) { |
| cse_set_to_d0i3(); |
| heci1_disable(); |
| } |
| } |
| |
| /* |
| * `cse_final_end_of_firmware` function is native implementation of equivalent events |
| * performed by FSP NotifyPhase(End of Firmware) API invocations. |
| * |
| * Operations are: |
| * 1. Set D0I3 for all HECI devices. |
| */ |
| static void cse_final_end_of_firmware(void) |
| { |
| heci_set_to_d0i3(); |
| } |
| |
| /* |
| * This function to perform essential post EOP cse related operations |
| * upon SoC selecting `SOC_INTEL_CSE_SEND_EOP_LATE` config |
| */ |
| void cse_late_finalize(void) |
| { |
| if (!CONFIG(SOC_INTEL_CSE_SEND_EOP_LATE) && |
| !CONFIG(SOC_INTEL_CSE_SEND_EOP_ASYNC)) |
| return; |
| |
| if (!CONFIG(USE_FSP_NOTIFY_PHASE_READY_TO_BOOT)) |
| cse_final_ready_to_boot(); |
| |
| if (!CONFIG(USE_FSP_NOTIFY_PHASE_END_OF_FIRMWARE)) |
| cse_final_end_of_firmware(); |
| } |
| |
| static void intel_cse_get_rw_version(void) |
| { |
| struct cse_fw_partition_info *version = cbmem_find(CBMEM_ID_CSE_PARTITION_VERSION); |
| if (version == NULL) |
| return; |
| |
| printk(BIOS_DEBUG, "CSE RW Firmware Version: %d.%d.%d.%d\n", |
| version->cur_cse_fw_version.major, |
| version->cur_cse_fw_version.minor, |
| version->cur_cse_fw_version.hotfix, |
| version->cur_cse_fw_version.build); |
| } |
| |
| /* |
| * `cse_final` function is native implementation of equivalent events performed by |
| * each FSP NotifyPhase() API invocations. It also displays CSE firmware version |
| * if stored in CBMEM region. |
| */ |
| static void cse_final(struct device *dev) |
| { |
| if (CONFIG(SOC_INTEL_CSE_LITE_SKU)) |
| intel_cse_get_rw_version(); |
| /* |
| * SoC user can have two options for sending EOP: |
| * 1. Choose to send EOP late |
| * 2. Choose to send EOP cmd asynchronously |
| * |
| * In case of sending EOP in asynchronous mode, the EOP command |
| * has most likely not been completed yet. The finalization steps |
| * will be run once the EOP command has successfully been completed. |
| */ |
| if (CONFIG(SOC_INTEL_CSE_SEND_EOP_LATE) || |
| CONFIG(SOC_INTEL_CSE_SEND_EOP_ASYNC)) |
| return; |
| |
| /* 1. Send EOP to CSE if not done.*/ |
| if (CONFIG(SOC_INTEL_CSE_SET_EOP)) |
| cse_send_end_of_post(); |
| |
| if (!CONFIG(USE_FSP_NOTIFY_PHASE_READY_TO_BOOT)) |
| cse_final_ready_to_boot(); |
| |
| if (!CONFIG(USE_FSP_NOTIFY_PHASE_END_OF_FIRMWARE)) |
| cse_final_end_of_firmware(); |
| } |
| |
| struct device_operations cse_ops = { |
| .set_resources = pci_dev_set_resources, |
| .read_resources = pci_dev_read_resources, |
| .enable_resources = pci_dev_enable_resources, |
| .init = pci_dev_init, |
| .ops_pci = &pci_dev_ops_pci, |
| .enable = cse_set_state, |
| .final = cse_final, |
| }; |
| |
| static const unsigned short pci_device_ids[] = { |
| PCI_DID_INTEL_MTL_CSE0, |
| PCI_DID_INTEL_APL_CSE0, |
| PCI_DID_INTEL_GLK_CSE0, |
| PCI_DID_INTEL_CNL_CSE0, |
| PCI_DID_INTEL_LWB_CSE0, |
| PCI_DID_INTEL_LWB_CSE0_SUPER, |
| PCI_DID_INTEL_CNP_H_CSE0, |
| PCI_DID_INTEL_CMP_CSE0, |
| PCI_DID_INTEL_CMP_H_CSE0, |
| PCI_DID_INTEL_TGL_CSE0, |
| PCI_DID_INTEL_TGL_H_CSE0, |
| PCI_DID_INTEL_MCC_CSE0, |
| PCI_DID_INTEL_MCC_CSE1, |
| PCI_DID_INTEL_MCC_CSE2, |
| PCI_DID_INTEL_MCC_CSE3, |
| PCI_DID_INTEL_JSP_CSE0, |
| PCI_DID_INTEL_JSP_CSE1, |
| PCI_DID_INTEL_JSP_CSE2, |
| PCI_DID_INTEL_JSP_CSE3, |
| PCI_DID_INTEL_ADP_P_CSE0, |
| PCI_DID_INTEL_ADP_P_CSE1, |
| PCI_DID_INTEL_ADP_P_CSE2, |
| PCI_DID_INTEL_ADP_P_CSE3, |
| PCI_DID_INTEL_ADP_S_CSE0, |
| PCI_DID_INTEL_ADP_S_CSE1, |
| PCI_DID_INTEL_ADP_S_CSE2, |
| PCI_DID_INTEL_ADP_S_CSE3, |
| PCI_DID_INTEL_ADP_M_CSE0, |
| PCI_DID_INTEL_ADP_M_CSE1, |
| PCI_DID_INTEL_ADP_M_CSE2, |
| PCI_DID_INTEL_ADP_M_CSE3, |
| PCI_DID_INTEL_RPP_S_CSE0, |
| PCI_DID_INTEL_RPP_S_CSE1, |
| PCI_DID_INTEL_RPP_S_CSE2, |
| PCI_DID_INTEL_RPP_S_CSE3, |
| 0, |
| }; |
| |
| static const struct pci_driver cse_driver __pci_driver = { |
| .ops = &cse_ops, |
| .vendor = PCI_VID_INTEL, |
| /* SoC/chipset needs to provide PCI device ID */ |
| .devices = pci_device_ids |
| }; |
| |
| #endif |