[FREELDR] Add support for loading Linux in x64 FreeLdr. Part 2/2: C code.
[reactos.git] / boot / freeldr / freeldr / linuxboot.c
1 /*
2 * FreeLoader
3 * Copyright (C) 1998-2003 Brian Palmer <brianp@sginet.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 /*
21 * The x86 Linux Boot Protocol is explained at:
22 * https://www.kernel.org/doc/Documentation/x86/boot.txt
23 */
24
25 #if defined(_M_IX86) || defined(_M_AMD64)
26
27 /* INCLUDES *******************************************************************/
28
29 #include <freeldr.h>
30
31 #include <debug.h>
32 DBG_DEFAULT_CHANNEL(LINUX);
33
34 /* GLOBALS ********************************************************************/
35
36 #define LINUX_READ_CHUNK_SIZE 0x20000 // Read 128k at a time
37
38 PLINUX_BOOTSECTOR LinuxBootSector = NULL;
39 PLINUX_SETUPSECTOR LinuxSetupSector = NULL;
40 ULONG SetupSectorSize = 0;
41 BOOLEAN NewStyleLinuxKernel = FALSE;
42 ULONG LinuxKernelSize = 0;
43 ULONG LinuxInitrdSize = 0;
44 PCSTR LinuxKernelName = NULL;
45 PCSTR LinuxInitrdName = NULL;
46 PSTR LinuxCommandLine = NULL;
47 ULONG LinuxCommandLineSize = 0;
48 PVOID LinuxKernelLoadAddress = NULL;
49 PVOID LinuxInitrdLoadAddress = NULL;
50 CHAR LinuxBootDescription[80];
51
52 /* FUNCTIONS ******************************************************************/
53
54 static BOOLEAN LinuxReadBootSector(ULONG LinuxKernelFile);
55 static BOOLEAN LinuxReadSetupSector(ULONG LinuxKernelFile);
56 static BOOLEAN LinuxReadKernel(ULONG LinuxKernelFile);
57 static BOOLEAN LinuxCheckKernelVersion(VOID);
58 static BOOLEAN LinuxReadInitrd(ULONG LinuxInitrdFile);
59
60 static VOID
61 RemoveQuotes(
62 IN OUT PSTR QuotedString)
63 {
64 PCHAR p;
65 PSTR Start;
66 SIZE_T Size;
67
68 /* Skip spaces up to " */
69 p = QuotedString;
70 while (*p == ' ' || *p == '\t' || *p == '"')
71 ++p;
72 Start = p;
73
74 /* Go up to next " */
75 while (*p != ANSI_NULL && *p != '"')
76 ++p;
77 /* NULL-terminate */
78 *p = ANSI_NULL;
79
80 /* Move the NULL-terminated string back into 'QuotedString' in place */
81 Size = (strlen(Start) + 1) * sizeof(CHAR);
82 memmove(QuotedString, Start, Size);
83 }
84
85 ARC_STATUS
86 LoadAndBootLinux(
87 IN ULONG Argc,
88 IN PCHAR Argv[],
89 IN PCHAR Envp[])
90 {
91 ARC_STATUS Status;
92 PCSTR Description;
93 PCSTR ArgValue;
94 PCSTR BootPath;
95 UCHAR DriveNumber = 0;
96 ULONG PartitionNumber = 0;
97 ULONG LinuxKernel = 0;
98 ULONG LinuxInitrdFile = 0;
99 FILEINFORMATION FileInfo;
100 CHAR ArcPath[MAX_PATH];
101
102 Description = GetArgumentValue(Argc, Argv, "LoadIdentifier");
103 if (Description && *Description)
104 RtlStringCbPrintfA(LinuxBootDescription, sizeof(LinuxBootDescription), "Loading %s...", Description);
105 else
106 strcpy(LinuxBootDescription, "Loading Linux...");
107
108 UiDrawBackdrop();
109 UiDrawStatusText(LinuxBootDescription);
110 UiDrawProgressBarCenter(0, 100, LinuxBootDescription);
111
112 /* Find all the message box settings and run them */
113 UiShowMessageBoxesInArgv(Argc, Argv);
114
115 /*
116 * Check whether we have a "BootPath" value (takes precedence
117 * over both "BootDrive" and "BootPartition").
118 */
119 BootPath = GetArgumentValue(Argc, Argv, "BootPath");
120 if (!BootPath || !*BootPath)
121 {
122 /* We don't have one, check whether we use "BootDrive" and "BootPartition" */
123
124 /* Retrieve the boot drive (optional, fall back to using default path otherwise) */
125 ArgValue = GetArgumentValue(Argc, Argv, "BootDrive");
126 if (ArgValue && *ArgValue)
127 {
128 DriveNumber = DriveMapGetBiosDriveNumber(ArgValue);
129
130 /* Retrieve the boot partition (not optional and cannot be zero) */
131 PartitionNumber = 0;
132 ArgValue = GetArgumentValue(Argc, Argv, "BootPartition");
133 if (ArgValue && *ArgValue)
134 PartitionNumber = atoi(ArgValue);
135 if (PartitionNumber == 0)
136 {
137 UiMessageBox("Boot partition cannot be 0!");
138 goto LinuxBootFailed;
139 // return EINVAL;
140 }
141
142 /* Construct the corresponding ARC path */
143 ConstructArcPath(ArcPath, "", DriveNumber, PartitionNumber);
144 *strrchr(ArcPath, '\\') = ANSI_NULL; // Trim the trailing path separator.
145
146 BootPath = ArcPath;
147 }
148 else
149 {
150 /* Fall back to using the system partition as default path */
151 BootPath = GetArgumentValue(Argc, Argv, "SystemPartition");
152 }
153 }
154
155 /* If we haven't retrieved the BIOS drive and partition numbers above, do it now */
156 if (PartitionNumber == 0)
157 {
158 /* Retrieve the BIOS drive and partition numbers */
159 if (!DissectArcPath(BootPath, NULL, &DriveNumber, &PartitionNumber))
160 {
161 /* This is not a fatal failure, but just an inconvenience: display a message */
162 TRACE("DissectArcPath(%s) failed to retrieve BIOS drive and partition numbers.\n", BootPath);
163 }
164 }
165
166 /* Get the kernel name */
167 LinuxKernelName = GetArgumentValue(Argc, Argv, "Kernel");
168 if (!LinuxKernelName || !*LinuxKernelName)
169 {
170 UiMessageBox("Linux kernel filename not specified for selected OS!");
171 goto LinuxBootFailed;
172 }
173
174 /* Get the initrd name (optional) */
175 LinuxInitrdName = GetArgumentValue(Argc, Argv, "Initrd");
176
177 /* Get the command line (optional) */
178 LinuxCommandLineSize = 0;
179 LinuxCommandLine = GetArgumentValue(Argc, Argv, "CommandLine");
180 if (LinuxCommandLine && *LinuxCommandLine)
181 {
182 RemoveQuotes(LinuxCommandLine);
183 LinuxCommandLineSize = (ULONG)strlen(LinuxCommandLine) + 1;
184 LinuxCommandLineSize = min(LinuxCommandLineSize, 260);
185 }
186
187 /* Open the kernel */
188 Status = FsOpenFile(LinuxKernelName, BootPath, OpenReadOnly, &LinuxKernel);
189 if (Status != ESUCCESS)
190 {
191 UiMessageBox("Linux kernel '%s' not found.", LinuxKernelName);
192 goto LinuxBootFailed;
193 }
194
195 /* Open the initrd file image (if necessary) */
196 if (LinuxInitrdName)
197 {
198 Status = FsOpenFile(LinuxInitrdName, BootPath, OpenReadOnly, &LinuxInitrdFile);
199 if (Status != ESUCCESS)
200 {
201 UiMessageBox("Linux initrd image '%s' not found.", LinuxInitrdName);
202 goto LinuxBootFailed;
203 }
204 }
205
206 /* Load the boot sector */
207 if (!LinuxReadBootSector(LinuxKernel))
208 goto LinuxBootFailed;
209
210 /* Load the setup sector */
211 if (!LinuxReadSetupSector(LinuxKernel))
212 goto LinuxBootFailed;
213
214 /* Calc kernel size */
215 Status = ArcGetFileInformation(LinuxKernel, &FileInfo);
216 if (Status != ESUCCESS || FileInfo.EndingAddress.HighPart != 0)
217 LinuxKernelSize = 0;
218 else
219 LinuxKernelSize = FileInfo.EndingAddress.LowPart - (512 + SetupSectorSize);
220
221 /* Get the initrd file image (if necessary) */
222 LinuxInitrdSize = 0;
223 if (LinuxInitrdName)
224 {
225 Status = ArcGetFileInformation(LinuxInitrdFile, &FileInfo);
226 if (Status != ESUCCESS || FileInfo.EndingAddress.HighPart != 0)
227 LinuxInitrdSize = 0;
228 else
229 LinuxInitrdSize = FileInfo.EndingAddress.LowPart;
230 }
231
232 /* Load the kernel */
233 if (!LinuxReadKernel(LinuxKernel))
234 goto LinuxBootFailed;
235
236 /* Load the initrd (if necessary) */
237 if (LinuxInitrdName)
238 {
239 if (!LinuxReadInitrd(LinuxInitrdFile))
240 goto LinuxBootFailed;
241 }
242
243 // If the default root device is set to FLOPPY (0000h), change to /dev/fd0 (0200h)
244 if (LinuxBootSector->RootDevice == 0x0000)
245 LinuxBootSector->RootDevice = 0x0200;
246
247 if (LinuxSetupSector->Version >= 0x0202)
248 {
249 LinuxSetupSector->CommandLinePointer = 0x99000;
250 }
251 else
252 {
253 LinuxBootSector->CommandLineMagic = LINUX_COMMAND_LINE_MAGIC;
254 LinuxBootSector->CommandLineOffset = 0x9000;
255 }
256
257 if (NewStyleLinuxKernel)
258 LinuxSetupSector->TypeOfLoader = LINUX_LOADER_TYPE_FREELOADER;
259 else
260 LinuxSetupSector->LoadFlags = 0;
261
262 RtlCopyMemory((PVOID)0x90000, LinuxBootSector, 512);
263 RtlCopyMemory((PVOID)0x90200, LinuxSetupSector, SetupSectorSize);
264 RtlCopyMemory((PVOID)0x99000,
265 LinuxCommandLine ? LinuxCommandLine : "",
266 LinuxCommandLine ? LinuxCommandLineSize : sizeof(ANSI_NULL));
267
268 UiUnInitialize("Booting Linux...");
269 IniCleanup();
270
271 BootLinuxKernel(LinuxKernelSize, LinuxKernelLoadAddress,
272 (LinuxSetupSector->LoadFlags & LINUX_FLAG_LOAD_HIGH)
273 ? (PVOID)LINUX_KERNEL_LOAD_ADDRESS /* == 0x100000 */
274 : (PVOID)0x10000,
275 DriveNumber, PartitionNumber);
276 /* Must not return! */
277 return ESUCCESS;
278
279 LinuxBootFailed:
280
281 if (LinuxKernel)
282 ArcClose(LinuxKernel);
283
284 if (LinuxInitrdFile)
285 ArcClose(LinuxInitrdFile);
286
287 if (LinuxBootSector != NULL)
288 MmFreeMemory(LinuxBootSector);
289
290 if (LinuxSetupSector != NULL)
291 MmFreeMemory(LinuxSetupSector);
292
293 if (LinuxKernelLoadAddress != NULL)
294 MmFreeMemory(LinuxKernelLoadAddress);
295
296 if (LinuxInitrdLoadAddress != NULL)
297 MmFreeMemory(LinuxInitrdLoadAddress);
298
299 LinuxBootSector = NULL;
300 LinuxSetupSector = NULL;
301 SetupSectorSize = 0;
302 NewStyleLinuxKernel = FALSE;
303 LinuxKernelSize = 0;
304 LinuxInitrdSize = 0;
305 LinuxKernelName = NULL;
306 LinuxInitrdName = NULL;
307 LinuxCommandLine = NULL;
308 LinuxCommandLineSize = 0;
309 LinuxKernelLoadAddress = NULL;
310 LinuxInitrdLoadAddress = NULL;
311 *LinuxBootDescription = ANSI_NULL;
312
313 return ENOEXEC;
314 }
315
316 static BOOLEAN LinuxReadBootSector(ULONG LinuxKernelFile)
317 {
318 LARGE_INTEGER Position;
319
320 /* Allocate memory for boot sector */
321 LinuxBootSector = MmAllocateMemoryWithType(512, LoaderSystemCode);
322 if (LinuxBootSector == NULL)
323 return FALSE;
324
325 /* Load the linux boot sector */
326 Position.QuadPart = 0;
327 if (ArcSeek(LinuxKernelFile, &Position, SeekAbsolute) != ESUCCESS)
328 return FALSE;
329 if (ArcRead(LinuxKernelFile, LinuxBootSector, 512, NULL) != ESUCCESS)
330 return FALSE;
331
332 /* Check for validity */
333 if (LinuxBootSector->BootFlag != LINUX_BOOT_SECTOR_MAGIC)
334 {
335 UiMessageBox("Invalid boot sector magic (0xaa55)");
336 return FALSE;
337 }
338
339 // DbgDumpBuffer(DPRINT_LINUX, LinuxBootSector, 512);
340
341 TRACE("SetupSectors: %d\n" , LinuxBootSector->SetupSectors);
342 TRACE("RootFlags: 0x%x\n", LinuxBootSector->RootFlags);
343 TRACE("SystemSize: 0x%x\n", LinuxBootSector->SystemSize);
344 TRACE("SwapDevice: 0x%x\n", LinuxBootSector->SwapDevice);
345 TRACE("RamSize: 0x%x\n", LinuxBootSector->RamSize);
346 TRACE("VideoMode: 0x%x\n", LinuxBootSector->VideoMode);
347 TRACE("RootDevice: 0x%x\n", LinuxBootSector->RootDevice);
348 TRACE("BootFlag: 0x%x\n", LinuxBootSector->BootFlag);
349
350 return TRUE;
351 }
352
353 static BOOLEAN LinuxReadSetupSector(ULONG LinuxKernelFile)
354 {
355 LARGE_INTEGER Position;
356 UCHAR TempLinuxSetupSector[512];
357
358 /* Load the first linux setup sector */
359 Position.QuadPart = 512;
360 if (ArcSeek(LinuxKernelFile, &Position, SeekAbsolute) != ESUCCESS)
361 return FALSE;
362 if (ArcRead(LinuxKernelFile, TempLinuxSetupSector, 512, NULL) != ESUCCESS)
363 return FALSE;
364
365 /* Check the kernel version */
366 LinuxSetupSector = (PLINUX_SETUPSECTOR)TempLinuxSetupSector;
367 if (!LinuxCheckKernelVersion())
368 return FALSE;
369
370 if (NewStyleLinuxKernel)
371 SetupSectorSize = 512 * LinuxBootSector->SetupSectors;
372 else
373 SetupSectorSize = 512 * 4; // Always 4 setup sectors
374
375 /* Allocate memory for setup sectors */
376 LinuxSetupSector = MmAllocateMemoryWithType(SetupSectorSize, LoaderSystemCode);
377 if (LinuxSetupSector == NULL)
378 return FALSE;
379
380 /* Copy over first setup sector */
381 RtlCopyMemory(LinuxSetupSector, TempLinuxSetupSector, 512);
382
383 /* Load the rest of the linux setup sectors */
384 Position.QuadPart = 1024;
385 if (ArcSeek(LinuxKernelFile, &Position, SeekAbsolute) != ESUCCESS)
386 return FALSE;
387 if (ArcRead(LinuxKernelFile, (PVOID)((ULONG_PTR)LinuxSetupSector + 512), SetupSectorSize - 512, NULL) != ESUCCESS)
388 return FALSE;
389
390 // DbgDumpBuffer(DPRINT_LINUX, LinuxSetupSector, SetupSectorSize);
391
392 TRACE("SetupHeaderSignature: 0x%x (HdrS)\n", LinuxSetupSector->SetupHeaderSignature);
393 TRACE("Version: 0x%x\n", LinuxSetupSector->Version);
394 TRACE("RealModeSwitch: 0x%x\n", LinuxSetupSector->RealModeSwitch);
395 TRACE("SetupSeg: 0x%x\n", LinuxSetupSector->SetupSeg);
396 TRACE("StartSystemSeg: 0x%x\n", LinuxSetupSector->StartSystemSeg);
397 TRACE("KernelVersion: 0x%x\n", LinuxSetupSector->KernelVersion);
398 TRACE("TypeOfLoader: 0x%x\n", LinuxSetupSector->TypeOfLoader);
399 TRACE("LoadFlags: 0x%x\n", LinuxSetupSector->LoadFlags);
400 TRACE("SetupMoveSize: 0x%x\n", LinuxSetupSector->SetupMoveSize);
401 TRACE("Code32Start: 0x%x\n", LinuxSetupSector->Code32Start);
402 TRACE("RamdiskAddress: 0x%x\n", LinuxSetupSector->RamdiskAddress);
403 TRACE("RamdiskSize: 0x%x\n", LinuxSetupSector->RamdiskSize);
404 TRACE("BootSectKludgeOffset: 0x%x\n", LinuxSetupSector->BootSectKludgeOffset);
405 TRACE("BootSectKludgeSegment: 0x%x\n", LinuxSetupSector->BootSectKludgeSegment);
406 TRACE("HeapEnd: 0x%x\n", LinuxSetupSector->HeapEnd);
407
408 return TRUE;
409 }
410
411 static BOOLEAN LinuxReadKernel(ULONG LinuxKernelFile)
412 {
413 PVOID LoadAddress;
414 LARGE_INTEGER Position;
415 ULONG BytesLoaded;
416 CHAR StatusText[260];
417
418 RtlStringCbPrintfA(StatusText, sizeof(StatusText), "Loading %s", LinuxKernelName);
419 UiDrawStatusText(StatusText);
420
421 /* Try to allocate memory for the Linux kernel; if it fails, allocate somewhere else */
422 LinuxKernelLoadAddress = MmAllocateMemoryAtAddress(LinuxKernelSize, (PVOID)LINUX_KERNEL_LOAD_ADDRESS, LoaderSystemCode);
423 if (LinuxKernelLoadAddress != (PVOID)LINUX_KERNEL_LOAD_ADDRESS)
424 {
425 /* It's OK, let's allocate again somewhere else */
426 LinuxKernelLoadAddress = MmAllocateMemoryWithType(LinuxKernelSize, LoaderSystemCode);
427 if (LinuxKernelLoadAddress == NULL)
428 {
429 TRACE("Failed to allocate 0x%lx bytes for the kernel image.\n", LinuxKernelSize);
430 return FALSE;
431 }
432 }
433
434 LoadAddress = LinuxKernelLoadAddress;
435
436 /* Load the linux kernel at 0x100000 (1mb) */
437 Position.QuadPart = 512 + SetupSectorSize;
438 if (ArcSeek(LinuxKernelFile, &Position, SeekAbsolute) != ESUCCESS)
439 return FALSE;
440 for (BytesLoaded = 0; BytesLoaded < LinuxKernelSize; )
441 {
442 if (ArcRead(LinuxKernelFile, LoadAddress, LINUX_READ_CHUNK_SIZE, NULL) != ESUCCESS)
443 return FALSE;
444
445 BytesLoaded += LINUX_READ_CHUNK_SIZE;
446 LoadAddress = (PVOID)((ULONG_PTR)LoadAddress + LINUX_READ_CHUNK_SIZE);
447
448 UiDrawProgressBarCenter(BytesLoaded, LinuxKernelSize + LinuxInitrdSize, LinuxBootDescription);
449 }
450
451 return TRUE;
452 }
453
454 static BOOLEAN LinuxCheckKernelVersion(VOID)
455 {
456 /* Just assume old kernel until we find otherwise */
457 NewStyleLinuxKernel = FALSE;
458
459 /* Check for new style setup header */
460 if (LinuxSetupSector->SetupHeaderSignature != LINUX_SETUP_HEADER_ID)
461 {
462 NewStyleLinuxKernel = FALSE;
463 }
464 /* Check for version below 2.0 */
465 else if (LinuxSetupSector->Version < 0x0200)
466 {
467 NewStyleLinuxKernel = FALSE;
468 }
469 /* Check for version 2.0 */
470 else if (LinuxSetupSector->Version == 0x0200)
471 {
472 NewStyleLinuxKernel = TRUE;
473 }
474 /* Check for version 2.01+ */
475 else if (LinuxSetupSector->Version >= 0x0201)
476 {
477 NewStyleLinuxKernel = TRUE;
478 LinuxSetupSector->HeapEnd = 0x9000;
479 LinuxSetupSector->LoadFlags |= LINUX_FLAG_CAN_USE_HEAP;
480 }
481
482 if ((NewStyleLinuxKernel == FALSE) && (LinuxInitrdName))
483 {
484 UiMessageBox("Error: Cannot load a ramdisk (initrd) with an old kernel image.");
485 return FALSE;
486 }
487
488 return TRUE;
489 }
490
491 static BOOLEAN LinuxReadInitrd(ULONG LinuxInitrdFile)
492 {
493 ULONG BytesLoaded;
494 CHAR StatusText[260];
495
496 RtlStringCbPrintfA(StatusText, sizeof(StatusText), "Loading %s", LinuxInitrdName);
497 UiDrawStatusText(StatusText);
498
499 /* Allocate memory for the ramdisk, below 4GB */
500 // LinuxInitrdLoadAddress = MmAllocateMemory(LinuxInitrdSize);
501 /* Try to align it at the next MB boundary after the kernel */
502 // LinuxInitrdLoadAddress = MmAllocateMemoryAtAddress(LinuxInitrdSize, (PVOID)ROUND_UP((LINUX_KERNEL_LOAD_ADDRESS + LinuxKernelSize), 0x100000));
503 if (LinuxSetupSector->Version <= 0x0202)
504 {
505 #ifdef _M_AMD64
506 C_ASSERT(LINUX_MAX_INITRD_ADDRESS < 0x100000000);
507 #endif
508 LinuxInitrdLoadAddress = MmAllocateHighestMemoryBelowAddress(LinuxInitrdSize, (PVOID)LINUX_MAX_INITRD_ADDRESS, LoaderSystemCode);
509 }
510 else
511 {
512 LinuxInitrdLoadAddress = MmAllocateHighestMemoryBelowAddress(LinuxInitrdSize, UlongToPtr(LinuxSetupSector->InitrdAddressMax), LoaderSystemCode);
513 }
514 if (LinuxInitrdLoadAddress == NULL)
515 {
516 return FALSE;
517 }
518 #ifdef _M_AMD64
519 ASSERT((ULONG_PTR)LinuxInitrdLoadAddress < 0x100000000);
520 #endif
521
522 /* Set the information in the setup struct */
523 LinuxSetupSector->RamdiskAddress = PtrToUlong(LinuxInitrdLoadAddress);
524 LinuxSetupSector->RamdiskSize = LinuxInitrdSize;
525
526 TRACE("RamdiskAddress: 0x%x\n", LinuxSetupSector->RamdiskAddress);
527 TRACE("RamdiskSize: 0x%x\n", LinuxSetupSector->RamdiskSize);
528
529 if (LinuxSetupSector->Version >= 0x0203)
530 {
531 TRACE("InitrdAddressMax: 0x%x\n", LinuxSetupSector->InitrdAddressMax);
532 }
533
534 /* Load the ramdisk */
535 for (BytesLoaded = 0; BytesLoaded < LinuxInitrdSize; )
536 {
537 if (ArcRead(LinuxInitrdFile, LinuxInitrdLoadAddress, LINUX_READ_CHUNK_SIZE, NULL) != ESUCCESS)
538 return FALSE;
539
540 BytesLoaded += LINUX_READ_CHUNK_SIZE;
541 LinuxInitrdLoadAddress = (PVOID)((ULONG_PTR)LinuxInitrdLoadAddress + LINUX_READ_CHUNK_SIZE);
542
543 UiDrawProgressBarCenter(BytesLoaded + LinuxKernelSize, LinuxInitrdSize + LinuxKernelSize, LinuxBootDescription);
544 }
545
546 return TRUE;
547 }
548
549 #endif /* _M_IX86 || _M_AMD64 */