Signed-off-by: Gerd Hoffmann kraxel@redhat.com --- arch/x86/Kconfig | 12 +++ arch/x86/kernel/Makefile | 1 + arch/x86/kernel/coreboot.c | 232 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 arch/x86/kernel/coreboot.c
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 778178f..3aeb038 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2372,6 +2372,18 @@ config X86_SYSFB
If unsure, say Y.
+config COREBOOT + bool "coreboot" + depends on X86_SYSFB + depends on FB_SIMPLE + help + Add coreboot framebuffer support to the linux kernel. Say Y here + if you want the linux kernel find and use the firmware framebuffer + initialized by coreboot. Useful when using the linux kernel as + coreboot payload. + + If unsure, or if you don't know what coreboot is, say N. + endmenu
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index ada2e2d..4b30b0e 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -103,6 +103,7 @@ obj-$(CONFIG_UPROBES) += uprobes.o obj-y += sysfb.o obj-$(CONFIG_X86_SYSFB) += sysfb_simplefb.o obj-$(CONFIG_EFI) += sysfb_efi.o +obj-$(CONFIG_COREBOOT) += coreboot.o
obj-$(CONFIG_PERF_EVENTS) += perf_regs.o obj-$(CONFIG_TRACING) += tracepoint.o diff --git a/arch/x86/kernel/coreboot.c b/arch/x86/kernel/coreboot.c new file mode 100644 index 0000000..44ed3fa --- /dev/null +++ b/arch/x86/kernel/coreboot.c @@ -0,0 +1,232 @@ +/* + * Find and parse coreboot tables. Register framebuffer if present. + * See src/include/boot/coreboot_tables.h in coreboot source tree. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/screen_info.h> +#include <linux/platform_device.h> +#include <linux/platform_data/simplefb.h> +#include <video/vga.h> + +#include <asm/checksum.h> +#include <asm/io.h> +#include <asm/sysfb.h> + +struct cb_header { + u32 signature; + u32 header_bytes; + u32 header_checksum; + u32 table_bytes; + u32 table_checksum; + u32 table_entries; +}; + +#define CB_TAG_MAINBOARD 0x0003 +#define CB_TAG_VERSION 0x0004 +#define CB_TAG_FORWARD 0x0011 +#define CB_TAG_FRAMEBUFFER 0x0012 + +struct cb_mainboard { + u8 vendor_idx; + u8 part_idx; + char strings[0]; +}; + +struct cb_framebuffer { + u64 physical_address; + u32 x_resolution; + u32 y_resolution; + u32 bytes_per_line; + u8 bits_per_pixel; + u8 red_mask_pos; + u8 red_mask_size; + u8 green_mask_pos; + u8 green_mask_size; + u8 blue_mask_pos; + u8 blue_mask_size; + u8 reserved_mask_pos; + u8 reserved_mask_size; +}; + +struct cb_entry { + u32 tag; + u32 size; + union { + char string[0]; + u64 forward; + struct cb_mainboard mb; + struct cb_framebuffer fb; + } u; +}; + +#define CB_SIGNATURE 0x4f49424C /* "LBIO" */ + +/* Try to locate the coreboot header in a given address range. */ +static __init struct cb_header *coreboot_find_header(u32 addr, int len) +{ + struct cb_header *cbh, *copy; + int offset; + void *map; + + map = ioremap(addr, len); + for (offset = 0; offset < len; offset += 16) { + cbh = map + offset; + if (!cbh) + continue; + if (cbh->signature != CB_SIGNATURE) + continue; + if (!cbh->table_bytes) + continue; + if (ip_compute_csum(cbh, sizeof(*cbh)) != 0) + continue; + if (ip_compute_csum(cbh + 1, cbh->table_bytes) + != cbh->table_checksum) + continue; + pr_debug("coreboot: header found at 0x%x\n", + addr + offset); + copy = kzalloc(sizeof(*cbh) + cbh->table_bytes, GFP_KERNEL); + memcpy(copy, cbh, sizeof(*cbh) + cbh->table_bytes); + iounmap(map); + return copy; + } + iounmap(map); + return NULL; +} + +static __init bool check_vga_text_mode(void) +{ + uint8_t reg; + + if (screen_info.orig_video_isVGA != 1) + return false; + + reg = inb(VGA_GFX_I); + if (reg == 0xff) + /* no vga present */ + return false; + + outb(VGA_GFX_MISC, VGA_GFX_I); + reg = inb(VGA_GFX_D); + if (reg & 0x01) + /* vga is in gfx mode */ + return false; + + return true; +} + +static __init void coreboot_fb_init(struct cb_framebuffer *fb) +{ + const char *reason = ""; + struct simplefb_platform_data mode; + struct screen_info si; + struct resource res; + + pr_info("coreboot: framebuffer: %dx%d, %d bpp, at %llx\n", + fb->x_resolution, + fb->y_resolution, + fb->bits_per_pixel, + fb->physical_address); + + if (screen_info.orig_video_isVGA == VIDEO_TYPE_VLFB) { + reason = "have vesa lfb"; + goto skip; + } + if (screen_info.orig_video_isVGA == VIDEO_TYPE_EFI) { + reason = "have efi gop"; + goto skip; + } + if (check_vga_text_mode()) { + reason = "vga is in text mode"; + goto skip; + } + + memset(&si, 0, sizeof(si)); + si.orig_video_isVGA = VIDEO_TYPE_VLFB; + si.lfb_width = fb->x_resolution; + si.lfb_height = fb->y_resolution; + si.lfb_depth = fb->bits_per_pixel; + si.lfb_base = fb->physical_address; + si.lfb_linelength = fb->bytes_per_line; + si.lfb_size = + (fb->y_resolution * fb->bytes_per_line + 65535) / 65536; + si.pages = 1; + si.red_size = fb->red_mask_size; + si.red_pos = fb->red_mask_pos; + si.green_size = fb->green_mask_size; + si.green_pos = fb->green_mask_pos; + si.blue_size = fb->blue_mask_size; + si.blue_pos = fb->blue_mask_pos; + si.rsvd_size = fb->reserved_mask_size; + si.rsvd_pos = fb->reserved_mask_pos; + if (!parse_mode(&si, &mode)) { + reason = "mode not compatible"; + goto skip; + } + + memset(&res, 0, sizeof(res)); + res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + res.name = "coreboot-fb"; + res.start = si.lfb_base; + res.end = si.lfb_base + si.lfb_size * 65536 - 1; + + pr_info("coreboot: setting up simplefb\n"); + platform_device_register_resndata(NULL, "simple-framebuffer", 0, + &res, 1, &mode, sizeof(mode)); + return; + +skip: + pr_info("coreboot: skipping framebuffer setup (%s)\n", reason); + return; +} + +static __init int coreboot_detect(void) +{ + struct cb_header *cbh; + struct cb_entry *cbe; + u64 addr = 0; + void *next; + int i; + +newheader: + cbh = coreboot_find_header(addr, 0x1000); + if (!cbh) + return 0; + + next = cbh + 1; + for (i = 0; i < cbh->table_entries; i++) { + cbe = next; + next += cbe->size; + switch (cbe->tag) { + case CB_TAG_MAINBOARD: + pr_info("coreboot: mainboard: %s / %s\n", + cbe->u.mb.strings + cbe->u.mb.vendor_idx, + cbe->u.mb.strings + cbe->u.mb.part_idx); + break; + case CB_TAG_VERSION: + pr_info("coreboot: version: %s\n", + cbe->u.string); + break; + case CB_TAG_FORWARD: + pr_debug("coreboot: forward to 0x%llx\n", + cbe->u.forward); + addr = cbe->u.forward; + kfree(cbh); + goto newheader; + case CB_TAG_FRAMEBUFFER: + coreboot_fb_init(&cbe->u.fb); + break; + default: + pr_debug("%s: unhandled tag 0x%x (size %d)\n", + __func__, cbe->tag, cbe->size); + break; + } + } + + kfree(cbh); + return 0; +} + +device_initcall(coreboot_detect);