summaryrefslogtreecommitdiffstats
path: root/arch/x86/lib/sfi.c
blob: 3d3658088ad5b640a225125272e146aeca9c6add (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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;
}
OpenPOWER on IntegriCloud