x86: Add support for the Simple Firmware Interface (SFI)

This provides a way of passing information to Linux without requiring the
full ACPI horror. Provide a rudimentary implementation sufficient to be
recognised and parsed by Linux.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
This commit is contained in:
Simon Glass 2015-04-28 20:25:10 -06:00
parent 11f4dc1583
commit 6388e35725
5 changed files with 311 additions and 0 deletions

View File

@ -393,6 +393,20 @@ config GENERATE_PIRQ_TABLE
It specifies the interrupt router information as well how all the PCI It specifies the interrupt router information as well how all the PCI
devices' interrupt pins are wired to PIRQs. devices' interrupt pins are wired to PIRQs.
config GENERATE_SFI_TABLE
bool "Generate a SFI (Simple Firmware Interface) table"
help
The Simple Firmware Interface (SFI) provides a lightweight method
for platform firmware to pass information to the operating system
via static tables in memory. Kernel SFI support is required to
boot on SFI-only platforms. If you have ACPI tables then these are
used instead.
U-Boot writes this table in write_sfi_table() just before booting
the OS.
For more information, see http://simplefirmware.org
endmenu endmenu
config MAX_PIRQ_LINKS config MAX_PIRQ_LINKS

137
arch/x86/include/asm/sfi.h Normal file
View File

@ -0,0 +1,137 @@
/*
* Copyright(c) 2009 Intel Corporation. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0+ BSD-3-Clause
*/
#ifndef _LINUX_SFI_H
#define _LINUX_SFI_H
#include <errno.h>
#include <linux/types.h>
/* Table signatures reserved by the SFI specification */
#define SFI_SIG_SYST "SYST"
#define SFI_SIG_FREQ "FREQ"
#define SFI_SIG_CPUS "CPUS"
#define SFI_SIG_MTMR "MTMR"
#define SFI_SIG_MRTC "MRTC"
#define SFI_SIG_MMAP "MMAP"
#define SFI_SIG_APIC "APIC"
#define SFI_SIG_XSDT "XSDT"
#define SFI_SIG_WAKE "WAKE"
#define SFI_SIG_DEVS "DEVS"
#define SFI_SIG_GPIO "GPIO"
#define SFI_SIGNATURE_SIZE 4
#define SFI_OEM_ID_SIZE 6
#define SFI_OEM_TABLE_ID_SIZE 8
#define SFI_NAME_LEN 16
#define SFI_TABLE_MAX_ENTRIES 16
#define SFI_GET_NUM_ENTRIES(ptable, entry_type) \
((ptable->header.len - sizeof(struct sfi_table_header)) / \
(sizeof(entry_type)))
/*
* Table structures must be byte-packed to match the SFI specification,
* as they are provided by the BIOS.
*/
struct __packed sfi_table_header {
char sig[SFI_SIGNATURE_SIZE];
u32 len;
u8 rev;
u8 csum;
char oem_id[SFI_OEM_ID_SIZE];
char oem_table_id[SFI_OEM_TABLE_ID_SIZE];
};
struct __packed sfi_table_simple {
struct sfi_table_header header;
u64 pentry[1];
};
/* Comply with UEFI spec 2.1 */
struct __packed sfi_mem_entry {
u32 type;
u64 phys_start;
u64 virt_start;
u64 pages;
u64 attrib;
};
struct __packed sfi_cpu_table_entry {
u32 apic_id;
};
struct __packed sfi_cstate_table_entry {
u32 hint; /* MWAIT hint */
u32 latency; /* latency in ms */
};
struct __packed sfi_apic_table_entry {
u64 phys_addr; /* phy base addr for APIC reg */
};
struct __packed sfi_freq_table_entry {
u32 freq_mhz; /* in MHZ */
u32 latency; /* transition latency in ms */
u32 ctrl_val; /* value to write to PERF_CTL */
};
struct __packed sfi_wake_table_entry {
u64 phys_addr; /* pointer to where the wake vector locates */
};
struct __packed sfi_timer_table_entry {
u64 phys_addr; /* phy base addr for the timer */
u32 freq_hz; /* in HZ */
u32 irq;
};
struct __packed sfi_rtc_table_entry {
u64 phys_addr; /* phy base addr for the RTC */
u32 irq;
};
struct __packed sfi_device_table_entry {
u8 type; /* bus type, I2C, SPI or ...*/
u8 host_num; /* attached to host 0, 1...*/
u16 addr;
u8 irq;
u32 max_freq;
char name[SFI_NAME_LEN];
};
enum {
SFI_DEV_TYPE_SPI = 0,
SFI_DEV_TYPE_I2C,
SFI_DEV_TYPE_UART,
SFI_DEV_TYPE_HSI,
SFI_DEV_TYPE_IPC,
SFI_DEV_TYPE_SD,
};
struct __packed sfi_gpio_table_entry {
char controller_name[SFI_NAME_LEN];
u16 pin_no;
char pin_name[SFI_NAME_LEN];
};
struct sfi_xsdt_header {
uint32_t oem_revision;
uint32_t creator_id;
uint32_t creator_revision;
};
typedef int (*sfi_table_handler) (struct sfi_table_header *table);
/**
* write_sfi_table() - Write Simple Firmware Interface tables
*
* @base: Address to write table to
* @return address to use for the next table
*/
u32 write_sfi_table(u32 base);
#endif /*_LINUX_SFI_H */

View File

@ -26,6 +26,7 @@ obj-y += pirq_routing.o
obj-y += relocate.o obj-y += relocate.o
obj-y += physmem.o obj-y += physmem.o
obj-$(CONFIG_X86_RAMTEST) += ramtest.o obj-$(CONFIG_X86_RAMTEST) += ramtest.o
obj-y += sfi.o
obj-y += string.o obj-y += string.o
obj-y += tables.o obj-y += tables.o
obj-$(CONFIG_SYS_X86_TSC_TIMER) += tsc_timer.o obj-$(CONFIG_SYS_X86_TSC_TIMER) += tsc_timer.o

154
arch/x86/lib/sfi.c Normal file
View File

@ -0,0 +1,154 @@
/*
* Copyright (c) 2015 Google, Inc
* Written by Simon Glass <sjg@chromium.org>
*
* SPDX-License-Identifier: GPL-2.0+
*/
/*
* Intel Simple Firmware Interface (SFI)
*
* Yet another way to pass information to the Linux kernel.
*
* See https://simplefirmware.org/ for details
*/
#include <common.h>
#include <cpu.h>
#include <dm.h>
#include <asm/cpu.h>
#include <asm/ioapic.h>
#include <asm/sfi.h>
#include <asm/tables.h>
#include <dm/uclass-internal.h>
struct table_info {
u32 base;
int ptr;
u32 entry_start;
u64 table[SFI_TABLE_MAX_ENTRIES];
int count;
};
static void *get_entry_start(struct table_info *tab)
{
if (tab->count == SFI_TABLE_MAX_ENTRIES)
return NULL;
tab->entry_start = tab->base + tab->ptr;
tab->table[tab->count] = tab->entry_start;
tab->entry_start += sizeof(struct sfi_table_header);
return (void *)tab->entry_start;
}
static void finish_table(struct table_info *tab, const char *sig, void *entry)
{
struct sfi_table_header *hdr;
hdr = (struct sfi_table_header *)(tab->base + tab->ptr);
strcpy(hdr->sig, sig);
hdr->len = sizeof(*hdr) + ((ulong)entry - tab->entry_start);
hdr->rev = 1;
strncpy(hdr->oem_id, "U-Boot", SFI_OEM_ID_SIZE);
strncpy(hdr->oem_table_id, "Table v1", SFI_OEM_TABLE_ID_SIZE);
hdr->csum = 0;
hdr->csum = table_compute_checksum(hdr, hdr->len);
tab->ptr += hdr->len;
tab->ptr = ALIGN(tab->ptr, 16);
tab->count++;
}
static int sfi_write_system_header(struct table_info *tab)
{
u64 *entry = get_entry_start(tab);
int i;
if (!entry)
return -ENOSPC;
for (i = 0; i < tab->count; i++)
*entry++ = tab->table[i];
finish_table(tab, SFI_SIG_SYST, entry);
return 0;
}
static int sfi_write_cpus(struct table_info *tab)
{
struct sfi_cpu_table_entry *entry = get_entry_start(tab);
struct udevice *dev;
int count = 0;
if (!entry)
return -ENOSPC;
for (uclass_find_first_device(UCLASS_CPU, &dev);
dev;
uclass_find_next_device(&dev)) {
struct cpu_platdata *plat = dev_get_parent_platdata(dev);
if (!device_active(dev))
continue;
entry->apic_id = plat->cpu_id;
entry++;
count++;
}
/* Omit the table if there is only one CPU */
if (count > 1)
finish_table(tab, SFI_SIG_CPUS, entry);
return 0;
}
static int sfi_write_apic(struct table_info *tab)
{
struct sfi_apic_table_entry *entry = get_entry_start(tab);
if (!entry)
return -ENOSPC;
entry->phys_addr = IO_APIC_ADDR;
entry++;
finish_table(tab, SFI_SIG_APIC, entry);
return 0;
}
static int sfi_write_xsdt(struct table_info *tab)
{
struct sfi_xsdt_header *entry = get_entry_start(tab);
if (!entry)
return -ENOSPC;
entry->oem_revision = 1;
entry->creator_id = 1;
entry->creator_revision = 1;
entry++;
finish_table(tab, SFI_SIG_XSDT, entry);
return 0;
}
u32 write_sfi_table(u32 base)
{
struct table_info table;
table.base = base;
table.ptr = 0;
table.count = 0;
sfi_write_cpus(&table);
sfi_write_apic(&table);
/*
* The SFI specification marks the XSDT table as option, but Linux 4.0
* crashes on start-up when it is not provided.
*/
sfi_write_xsdt(&table);
/* Finally, write out the system header which points to the others */
sfi_write_system_header(&table);
return base + table.ptr;
}

View File

@ -5,6 +5,7 @@
*/ */
#include <common.h> #include <common.h>
#include <asm/sfi.h>
#include <asm/tables.h> #include <asm/tables.h>
u8 table_compute_checksum(void *v, int len) u8 table_compute_checksum(void *v, int len)
@ -27,4 +28,8 @@ void write_tables(void)
rom_table_end = write_pirq_routing_table(rom_table_end); rom_table_end = write_pirq_routing_table(rom_table_end);
rom_table_end = ALIGN(rom_table_end, 1024); rom_table_end = ALIGN(rom_table_end, 1024);
#endif #endif
#ifdef CONFIG_GENERATE_SFI_TABLE
rom_table_end = write_sfi_table(rom_table_end);
rom_table_end = ALIGN(rom_table_end, 1024);
#endif
} }