| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (c) 2017 Intel Corporation |
| */ |
| |
| #include <init.h> |
| #include <log.h> |
| #include <asm/e820.h> |
| #include <asm/global_data.h> |
| #include <asm/sfi.h> |
| #include <linux/printk.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| /* |
| * SFI tables are part of the first stage bootloader. |
| * |
| * U-Boot finds the System Table by searching 16-byte boundaries between |
| * physical address 0x000E0000 and 0x000FFFFF. U-Boot shall search this region |
| * starting at the low address and shall stop searching when the 1st valid SFI |
| * System Table is found. |
| */ |
| #define SFI_BASE_ADDR 0x000E0000 |
| #define SFI_LENGTH 0x00020000 |
| #define SFI_TABLE_LENGTH 16 |
| |
| static int sfi_table_check(struct sfi_table_header *sbh) |
| { |
| char chksum = 0; |
| char *pos = (char *)sbh; |
| u32 i; |
| |
| if (sbh->len < SFI_TABLE_LENGTH) |
| return -ENXIO; |
| |
| if (sbh->len > SFI_LENGTH) |
| return -ENXIO; |
| |
| for (i = 0; i < sbh->len; i++) |
| chksum += *pos++; |
| |
| if (chksum) |
| pr_err("sfi: Invalid checksum\n"); |
| |
| /* Checksum is OK if zero */ |
| return chksum ? -EILSEQ : 0; |
| } |
| |
| static int sfi_table_is_type(struct sfi_table_header *sbh, const char *signature) |
| { |
| return !strncmp(sbh->sig, signature, SFI_SIGNATURE_SIZE) && |
| !sfi_table_check(sbh); |
| } |
| |
| static struct sfi_table_simple *sfi_get_table_by_sig(unsigned long addr, |
| const char *signature) |
| { |
| struct sfi_table_simple *sb; |
| u32 i; |
| |
| for (i = 0; i < SFI_LENGTH; i += SFI_TABLE_LENGTH) { |
| sb = (struct sfi_table_simple *)(addr + i); |
| if (sfi_table_is_type(&sb->header, signature)) |
| return sb; |
| } |
| |
| return NULL; |
| } |
| |
| static struct sfi_table_simple *sfi_search_mmap(void) |
| { |
| struct sfi_table_header *sbh; |
| struct sfi_table_simple *sb; |
| u32 sys_entry_cnt; |
| u32 i; |
| |
| /* Find SYST table */ |
| sb = sfi_get_table_by_sig(SFI_BASE_ADDR, SFI_SIG_SYST); |
| if (!sb) { |
| pr_err("sfi: failed to locate SYST table\n"); |
| return NULL; |
| } |
| |
| sys_entry_cnt = (sb->header.len - sizeof(*sbh)) / 8; |
| |
| /* Search through each SYST entry for MMAP table */ |
| for (i = 0; i < sys_entry_cnt; i++) { |
| sbh = (struct sfi_table_header *)(unsigned long)sb->pentry[i]; |
| |
| if (sfi_table_is_type(sbh, SFI_SIG_MMAP)) |
| return (struct sfi_table_simple *)sbh; |
| } |
| |
| pr_err("sfi: failed to locate SFI MMAP table\n"); |
| return NULL; |
| } |
| |
| #define sfi_for_each_mentry(i, sb, mentry) \ |
| for (i = 0, mentry = (struct sfi_mem_entry *)sb->pentry; \ |
| i < SFI_GET_NUM_ENTRIES(sb, struct sfi_mem_entry); \ |
| i++, mentry++) \ |
| |
| static unsigned int sfi_setup_e820(unsigned int max_entries, |
| struct e820_entry *entries) |
| { |
| struct sfi_table_simple *sb; |
| struct sfi_mem_entry *mentry; |
| unsigned long long start, end, size; |
| int type, total = 0; |
| u32 i; |
| |
| sb = sfi_search_mmap(); |
| if (!sb) |
| return 0; |
| |
| sfi_for_each_mentry(i, sb, mentry) { |
| start = mentry->phys_start; |
| size = mentry->pages << 12; |
| end = start + size; |
| |
| if (start > end) |
| continue; |
| |
| /* translate SFI mmap type to E820 map type */ |
| switch (mentry->type) { |
| case SFI_MEM_CONV: |
| type = E820_RAM; |
| break; |
| case SFI_MEM_UNUSABLE: |
| case SFI_RUNTIME_SERVICE_DATA: |
| continue; |
| default: |
| type = E820_RESERVED; |
| } |
| |
| if (total == E820MAX) |
| break; |
| entries[total].addr = start; |
| entries[total].size = size; |
| entries[total].type = type; |
| |
| total++; |
| } |
| |
| return total; |
| } |
| |
| static int sfi_get_bank_size(void) |
| { |
| struct sfi_table_simple *sb; |
| struct sfi_mem_entry *mentry; |
| int bank = 0; |
| u32 i; |
| |
| sb = sfi_search_mmap(); |
| if (!sb) |
| return 0; |
| |
| sfi_for_each_mentry(i, sb, mentry) { |
| if (mentry->type != SFI_MEM_CONV) |
| continue; |
| |
| gd->bd->bi_dram[bank].start = mentry->phys_start; |
| gd->bd->bi_dram[bank].size = mentry->pages << 12; |
| bank++; |
| } |
| |
| return bank; |
| } |
| |
| static phys_size_t sfi_get_ram_size(void) |
| { |
| struct sfi_table_simple *sb; |
| struct sfi_mem_entry *mentry; |
| phys_size_t ram = 0; |
| u32 i; |
| |
| sb = sfi_search_mmap(); |
| if (!sb) |
| return 0; |
| |
| sfi_for_each_mentry(i, sb, mentry) { |
| if (mentry->type != SFI_MEM_CONV) |
| continue; |
| |
| ram += mentry->pages << 12; |
| } |
| |
| debug("sfi: RAM size %llu\n", ram); |
| return ram; |
| } |
| |
| unsigned int install_e820_map(unsigned int max_entries, |
| struct e820_entry *entries) |
| { |
| return sfi_setup_e820(max_entries, entries); |
| } |
| |
| /* |
| * This function looks for the highest region of memory lower than 2GB which |
| * has enough space for U-Boot where U-Boot is aligned on a page boundary. It |
| * overrides the default implementation found elsewhere which simply picks the |
| * end of RAM, wherever that may be. The location of the stack, the relocation |
| * address, and how far U-Boot is moved by relocation are set in the global |
| * data structure. |
| */ |
| phys_addr_t board_get_usable_ram_top(phys_size_t total_size) |
| { |
| struct sfi_table_simple *sb; |
| struct sfi_mem_entry *mentry; |
| ulong dest_addr = 0; |
| u32 i; |
| |
| sb = sfi_search_mmap(); |
| if (!sb) |
| panic("No available memory found for relocation"); |
| |
| sfi_for_each_mentry(i, sb, mentry) { |
| unsigned long long start, end; |
| |
| if (mentry->type != SFI_MEM_CONV) |
| continue; |
| |
| start = mentry->phys_start; |
| end = start + (mentry->pages << 12); |
| |
| /* Filter memory over 2GB. */ |
| if (end > 0x7fffffffULL) |
| end = 0x80000000ULL; |
| /* Skip this region if it's too small. */ |
| if (end - start < total_size) |
| continue; |
| |
| /* Use this address if it's the largest so far. */ |
| if (end > dest_addr) |
| dest_addr = end; |
| } |
| |
| return dest_addr; |
| } |
| |
| int dram_init_banksize(void) |
| { |
| sfi_get_bank_size(); |
| return 0; |
| } |
| |
| int dram_init(void) |
| { |
| gd->ram_size = sfi_get_ram_size(); |
| return 0; |
| } |