xref: /qemu/pc-bios/optionrom/linuxboot_dma.c (revision 3e29da9fd81002a0c03041aaa26dea6d9dd9bd65)
1b2a575a1SMarc Marí /*
2b2a575a1SMarc Marí  * Linux Boot Option ROM for fw_cfg DMA
3b2a575a1SMarc Marí  *
4b2a575a1SMarc Marí  * This program is free software; you can redistribute it and/or modify
5b2a575a1SMarc Marí  * it under the terms of the GNU General Public License as published by
6b2a575a1SMarc Marí  * the Free Software Foundation; either version 2 of the License, or
7b2a575a1SMarc Marí  * (at your option) any later version.
8b2a575a1SMarc Marí  *
9b2a575a1SMarc Marí  * This program is distributed in the hope that it will be useful,
10b2a575a1SMarc Marí  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11b2a575a1SMarc Marí  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12b2a575a1SMarc Marí  * GNU General Public License for more details.
13b2a575a1SMarc Marí  *
14b2a575a1SMarc Marí  * You should have received a copy of the GNU General Public License
15b2a575a1SMarc Marí  * along with this program; if not, see <http://www.gnu.org/licenses/>.
16b2a575a1SMarc Marí  *
17b2a575a1SMarc Marí  * Copyright (c) 2015-2016 Red Hat Inc.
18b2a575a1SMarc Marí  *   Authors:
19b2a575a1SMarc Marí  *     Marc Marí <marc.mari.barcelo@gmail.com>
20b2a575a1SMarc Marí  *     Richard W.M. Jones <rjones@redhat.com>
21b2a575a1SMarc Marí  */
22b2a575a1SMarc Marí 
23b2a575a1SMarc Marí asm(
24b2a575a1SMarc Marí ".text\n"
25b2a575a1SMarc Marí ".global _start\n"
26b2a575a1SMarc Marí "_start:\n"
27b2a575a1SMarc Marí "   .short 0xaa55\n"
287f256924SPaolo Bonzini "   .byte 3\n" /* desired size in 512 units; signrom.py adds padding */
29b2a575a1SMarc Marí "   .byte 0xcb\n" /* far return without prefix */
30b2a575a1SMarc Marí "   .org 0x18\n"
31b2a575a1SMarc Marí "   .short 0\n"
32b2a575a1SMarc Marí "   .short _pnph\n"
33b2a575a1SMarc Marí "_pnph:\n"
34b2a575a1SMarc Marí "   .ascii \"$PnP\"\n"
35b2a575a1SMarc Marí "   .byte 0x01\n"
36b2a575a1SMarc Marí "   .byte (_pnph_len / 16)\n"
37b2a575a1SMarc Marí "   .short 0x0000\n"
38b2a575a1SMarc Marí "   .byte 0x00\n"
39b2a575a1SMarc Marí "   .byte 0x00\n"
40b2a575a1SMarc Marí "   .long 0x00000000\n"
41b2a575a1SMarc Marí "   .short _manufacturer\n"
42b2a575a1SMarc Marí "   .short _product\n"
43b2a575a1SMarc Marí "   .long 0x00000000\n"
44b2a575a1SMarc Marí "   .short 0x0000\n"
45b2a575a1SMarc Marí "   .short 0x0000\n"
46b2a575a1SMarc Marí "   .short _bev\n"
47b2a575a1SMarc Marí "   .short 0x0000\n"
48b2a575a1SMarc Marí "   .short 0x0000\n"
49b2a575a1SMarc Marí "   .equ _pnph_len, . - _pnph\n"
50b2a575a1SMarc Marí "_manufacturer:\n"
51b2a575a1SMarc Marí "   .asciz \"QEMU\"\n"
52b2a575a1SMarc Marí "_product:\n"
53b2a575a1SMarc Marí "   .asciz \"Linux loader DMA\"\n"
54b2a575a1SMarc Marí "   .align 4, 0\n"
55b2a575a1SMarc Marí "_bev:\n"
56b2a575a1SMarc Marí "   cli\n"
57b2a575a1SMarc Marí "   cld\n"
58b2a575a1SMarc Marí "   jmp load_kernel\n"
59b2a575a1SMarc Marí );
60b2a575a1SMarc Marí 
61*6dfa0143SStefano Garzarella /*
62*6dfa0143SStefano Garzarella  * The includes of C headers must be after the asm block to avoid compiler
63*6dfa0143SStefano Garzarella  * errors.
64*6dfa0143SStefano Garzarella  */
65*6dfa0143SStefano Garzarella #include <stdint.h>
66*6dfa0143SStefano Garzarella #include "optrom.h"
67*6dfa0143SStefano Garzarella #include "optrom_fw_cfg.h"
68b2a575a1SMarc Marí 
set_es(void * addr)69b2a575a1SMarc Marí static inline void set_es(void *addr)
70b2a575a1SMarc Marí {
71b2a575a1SMarc Marí     uint32_t seg = (uint32_t)addr >> 4;
72b2a575a1SMarc Marí     asm("movl %0, %%es" : : "r"(seg));
73b2a575a1SMarc Marí }
74b2a575a1SMarc Marí 
readw_es(uint16_t offset)75b2a575a1SMarc Marí static inline uint16_t readw_es(uint16_t offset)
76b2a575a1SMarc Marí {
77b2a575a1SMarc Marí     uint16_t val;
78b2a575a1SMarc Marí     asm(ADDR32 "movw %%es:(%1), %0" : "=r"(val) : "r"((uint32_t)offset));
79b2a575a1SMarc Marí     barrier();
80b2a575a1SMarc Marí     return val;
81b2a575a1SMarc Marí }
82b2a575a1SMarc Marí 
readl_es(uint16_t offset)83b2a575a1SMarc Marí static inline uint32_t readl_es(uint16_t offset)
84b2a575a1SMarc Marí {
85b2a575a1SMarc Marí     uint32_t val;
86b2a575a1SMarc Marí     asm(ADDR32 "movl %%es:(%1), %0" : "=r"(val) : "r"((uint32_t)offset));
87b2a575a1SMarc Marí     barrier();
88b2a575a1SMarc Marí     return val;
89b2a575a1SMarc Marí }
90b2a575a1SMarc Marí 
writel_es(uint16_t offset,uint32_t val)91b2a575a1SMarc Marí static inline void writel_es(uint16_t offset, uint32_t val)
92b2a575a1SMarc Marí {
93b2a575a1SMarc Marí     barrier();
94b2a575a1SMarc Marí     asm(ADDR32 "movl %0, %%es:(%1)" : : "r"(val), "r"((uint32_t)offset));
95b2a575a1SMarc Marí }
96b2a575a1SMarc Marí 
97b2a575a1SMarc Marí /* Return top of memory using BIOS function E801. */
get_e801_addr(void)98b2a575a1SMarc Marí static uint32_t get_e801_addr(void)
99b2a575a1SMarc Marí {
100b2a575a1SMarc Marí     uint16_t ax, bx, cx, dx;
101b2a575a1SMarc Marí     uint32_t ret;
102b2a575a1SMarc Marí 
103b2a575a1SMarc Marí     asm("int $0x15\n"
104b2a575a1SMarc Marí         : "=a"(ax), "=b"(bx), "=c"(cx), "=d"(dx)
105b2a575a1SMarc Marí         : "a"(0xe801), "b"(0), "c"(0), "d"(0));
106b2a575a1SMarc Marí 
107b2a575a1SMarc Marí     /* Not SeaBIOS, but in theory a BIOS could return CX=DX=0 in which
108b2a575a1SMarc Marí      * case we need to use the result from AX & BX instead.
109b2a575a1SMarc Marí      */
110b2a575a1SMarc Marí     if (cx == 0 && dx == 0) {
111b2a575a1SMarc Marí         cx = ax;
112b2a575a1SMarc Marí         dx = bx;
113b2a575a1SMarc Marí     }
114b2a575a1SMarc Marí 
115b2a575a1SMarc Marí     if (dx) {
116b2a575a1SMarc Marí         /* DX = extended memory above 16M, in 64K units.
117b2a575a1SMarc Marí          * Convert it to bytes and return.
118b2a575a1SMarc Marí          */
119b2a575a1SMarc Marí         ret = ((uint32_t)dx + 256 /* 16M in 64K units */) << 16;
120b2a575a1SMarc Marí     } else {
121b2a575a1SMarc Marí         /* This is a fallback path for machines with <= 16MB of RAM,
122b2a575a1SMarc Marí          * which probably would never be the case, but deal with it
123b2a575a1SMarc Marí          * anyway.
124b2a575a1SMarc Marí          *
125b2a575a1SMarc Marí          * CX = extended memory between 1M and 16M, in kilobytes
126b2a575a1SMarc Marí          * Convert it to bytes and return.
127b2a575a1SMarc Marí          */
128b2a575a1SMarc Marí         ret = ((uint32_t)cx + 1024 /* 1M in K */) << 10;
129b2a575a1SMarc Marí     }
130b2a575a1SMarc Marí 
131b2a575a1SMarc Marí     return ret;
132b2a575a1SMarc Marí }
133b2a575a1SMarc Marí 
134b2a575a1SMarc Marí /* Force the asm name without leading underscore, even on Win32. */
135b2a575a1SMarc Marí extern void load_kernel(void) asm("load_kernel");
136b2a575a1SMarc Marí 
load_kernel(void)137b2a575a1SMarc Marí void load_kernel(void)
138b2a575a1SMarc Marí {
139b2a575a1SMarc Marí     void *setup_addr;
140b2a575a1SMarc Marí     void *initrd_addr;
141b2a575a1SMarc Marí     void *kernel_addr;
142b2a575a1SMarc Marí     void *cmdline_addr;
143b2a575a1SMarc Marí     uint32_t setup_size;
144b2a575a1SMarc Marí     uint32_t initrd_size;
145b2a575a1SMarc Marí     uint32_t kernel_size;
146b2a575a1SMarc Marí     uint32_t cmdline_size;
147b2a575a1SMarc Marí     uint32_t initrd_end_page, max_allowed_page;
148b2a575a1SMarc Marí     uint32_t segment_addr, stack_addr;
149b2a575a1SMarc Marí 
150*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(&setup_addr, FW_CFG_SETUP_ADDR, 4);
151*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(&setup_size, FW_CFG_SETUP_SIZE, 4);
152*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(setup_addr, FW_CFG_SETUP_DATA, setup_size);
153b2a575a1SMarc Marí 
154b2a575a1SMarc Marí     set_es(setup_addr);
155b2a575a1SMarc Marí 
156b2a575a1SMarc Marí     /* For protocol < 0x203 we don't have initrd_max ... */
157b2a575a1SMarc Marí     if (readw_es(0x206) < 0x203) {
158b2a575a1SMarc Marí         /* ... so we assume initrd_max = 0x37ffffff. */
159b2a575a1SMarc Marí         writel_es(0x22c, 0x37ffffff);
160b2a575a1SMarc Marí     }
161b2a575a1SMarc Marí 
162*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(&initrd_addr, FW_CFG_INITRD_ADDR, 4);
163*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(&initrd_size, FW_CFG_INITRD_SIZE, 4);
164b2a575a1SMarc Marí 
165b2a575a1SMarc Marí     initrd_end_page = ((uint32_t)(initrd_addr + initrd_size) & -4096);
166b2a575a1SMarc Marí     max_allowed_page = (readl_es(0x22c) & -4096);
167b2a575a1SMarc Marí 
168b2a575a1SMarc Marí     if (initrd_end_page != 0 && max_allowed_page != 0 &&
169b2a575a1SMarc Marí         initrd_end_page != max_allowed_page) {
170b2a575a1SMarc Marí         /* Initrd at the end of memory. Compute better initrd address
171b2a575a1SMarc Marí          * based on e801 data
172b2a575a1SMarc Marí          */
173b2a575a1SMarc Marí         initrd_addr = (void *)((get_e801_addr() - initrd_size) & -4096);
174b2a575a1SMarc Marí         writel_es(0x218, (uint32_t)initrd_addr);
175b2a575a1SMarc Marí 
176b2a575a1SMarc Marí     }
177b2a575a1SMarc Marí 
178*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(initrd_addr, FW_CFG_INITRD_DATA, initrd_size);
179b2a575a1SMarc Marí 
180*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(&kernel_addr, FW_CFG_KERNEL_ADDR, 4);
181*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(&kernel_size, FW_CFG_KERNEL_SIZE, 4);
182*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(kernel_addr, FW_CFG_KERNEL_DATA, kernel_size);
183b2a575a1SMarc Marí 
184*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(&cmdline_addr, FW_CFG_CMDLINE_ADDR, 4);
185*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(&cmdline_size, FW_CFG_CMDLINE_SIZE, 4);
186*6dfa0143SStefano Garzarella     bios_cfg_read_entry_dma(cmdline_addr, FW_CFG_CMDLINE_DATA, cmdline_size);
187b2a575a1SMarc Marí 
188b2a575a1SMarc Marí     /* Boot linux */
189b2a575a1SMarc Marí     segment_addr = ((uint32_t)setup_addr >> 4);
190b2a575a1SMarc Marí     stack_addr = (uint32_t)(cmdline_addr - setup_addr - 16);
191b2a575a1SMarc Marí 
192b2a575a1SMarc Marí     /* As we are changing critical registers, we cannot leave freedom to the
193b2a575a1SMarc Marí      * compiler.
194b2a575a1SMarc Marí      */
195b2a575a1SMarc Marí     asm("movw %%ax, %%ds\n"
196b2a575a1SMarc Marí         "movw %%ax, %%es\n"
197b2a575a1SMarc Marí         "movw %%ax, %%fs\n"
198b2a575a1SMarc Marí         "movw %%ax, %%gs\n"
199b2a575a1SMarc Marí         "movw %%ax, %%ss\n"
200b2a575a1SMarc Marí         "movl %%ebx, %%esp\n"
201b2a575a1SMarc Marí         "addw $0x20, %%ax\n"
202b2a575a1SMarc Marí         "pushw %%ax\n" /* CS */
203b2a575a1SMarc Marí         "pushw $0\n" /* IP */
204b2a575a1SMarc Marí         /* Clear registers and jump to Linux */
205b2a575a1SMarc Marí         "xor %%ebx, %%ebx\n"
206b2a575a1SMarc Marí         "xor %%ecx, %%ecx\n"
207b2a575a1SMarc Marí         "xor %%edx, %%edx\n"
208b2a575a1SMarc Marí         "xor %%edi, %%edi\n"
209b2a575a1SMarc Marí         "xor %%ebp, %%ebp\n"
210b2a575a1SMarc Marí         "lretw\n"
211b2a575a1SMarc Marí         : : "a"(segment_addr), "b"(stack_addr));
212b2a575a1SMarc Marí }
213