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