xref: /haiku/src/system/boot/platform/efi/devices.cpp (revision 151a4f7f9354583f183b5fe1ec5f4fe83c6d2262)
1 /*
2  * Copyright 2016-2017 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <string.h>
8 
9 #include <boot/partitions.h>
10 #include <boot/platform.h>
11 #include <boot/stage2.h>
12 #include <boot/stdio.h>
13 #include <util/list.h>
14 
15 #include "Header.h"
16 
17 #include "efi_platform.h"
18 #include <efi/protocol/block-io.h>
19 #include <efi/protocol/loaded-image.h>
20 #include <efi/protocol/device-path.h>
21 
22 #include "gpt.h"
23 #include "gpt_known_guids.h"
24 
25 
26 #define DevicePathNodeLength(a)	(((a)->Length[0]) | ((a)->Length[1] << 8))
27 #define NextDevicePathNode(a)	(efi_device_path_protocol*) \
28 								(((uint8_t*)(a)) + DevicePathNodeLength(a))
29 
30 
31 struct device_handle {
32 	list_link					link;
33 	efi_device_path_protocol*	device_path;
34 	efi_handle					handle;
35 };
36 
37 
38 static struct list sMessagingDevices;
39 static struct list sMediaDevices;
40 
41 static efi_guid BlockIoGUID = EFI_BLOCK_IO_PROTOCOL_GUID;
42 static efi_guid LoadedImageGUID = EFI_LOADED_IMAGE_PROTOCOL_GUID;
43 static efi_guid DevicePathGUID = EFI_DEVICE_PATH_PROTOCOL_GUID;
44 
45 
46 static bool
47 device_path_end(efi_device_path_protocol* path)
48 {
49 	if (path == NULL) {
50 		dprintf("ERROR: Unexpected end of device protocol path!\n");
51 		return false;
52 	}
53 
54 	return (path->Type == DEVICE_PATH_END
55 		&& path->SubType == DEVICE_PATH_ENTIRE_END);
56 }
57 
58 
59 static size_t
60 device_path_length(efi_device_path_protocol* path)
61 {
62 	efi_device_path_protocol *node = path;
63 	size_t length = 0;
64 	while (!device_path_end(node)) {
65 		length += DevicePathNodeLength(node);
66 		node = NextDevicePathNode(node);
67 	}
68 
69 	// node now points to the device path end node; add its length as well
70 	return length + DevicePathNodeLength(node);
71 }
72 
73 
74 // If matchSubPath is true, then the second device path can be a sub-path
75 // of the first device path
76 static bool
77 compare_device_paths(efi_device_path_protocol* first,
78 	efi_device_path_protocol* second, bool matchSubPath = false)
79 {
80 	efi_device_path_protocol *firstNode = first;
81 	efi_device_path_protocol *secondNode = second;
82 	while (!device_path_end(firstNode) && !device_path_end(secondNode)) {
83 		size_t firstLength = DevicePathNodeLength(firstNode);
84 		size_t secondLength = DevicePathNodeLength(secondNode);
85 		if (firstLength != secondLength
86 			|| memcmp(firstNode, secondNode, firstLength) != 0) {
87 			return false;
88 		}
89 		firstNode = NextDevicePathNode(firstNode);
90 		secondNode = NextDevicePathNode(secondNode);
91 	}
92 
93 	if (matchSubPath)
94 		return device_path_end(secondNode);
95 
96 	return device_path_end(firstNode) && device_path_end(secondNode);
97 }
98 
99 
100 static bool
101 add_device_path(struct list *list, efi_device_path_protocol* path,
102 	efi_handle handle)
103 {
104 	device_handle *node = NULL;
105 	while ((node = (device_handle*)list_get_next_item(list, node)) != NULL) {
106 		if (compare_device_paths(node->device_path, path))
107 			return false;
108 	}
109 
110 	size_t length = device_path_length(path);
111 	node = (device_handle*)malloc(sizeof(struct device_handle));
112 	node->device_path = (efi_device_path_protocol*)malloc(length);
113 	node->handle = handle;
114 	memcpy(node->device_path, path, length);
115 
116 	list_add_item(list, node);
117 
118 	return true;
119 }
120 
121 
122 class EfiDevice : public Node
123 {
124 	public:
125 		EfiDevice(efi_block_io_protocol *blockIo,
126 			efi_device_path_protocol *devicePath);
127 		virtual ~EfiDevice();
128 
129 		virtual ssize_t ReadAt(void *cookie, off_t pos, void *buffer,
130 			size_t bufferSize);
131 		virtual ssize_t WriteAt(void *cookie, off_t pos, const void *buffer,
132 			size_t bufferSize) { return B_UNSUPPORTED; }
133 		virtual off_t Size() const {
134 			return (fBlockIo->Media->LastBlock + 1) * BlockSize(); }
135 
136 		uint32 BlockSize() const { return fBlockIo->Media->BlockSize; }
137 		bool ReadOnly() const { return fBlockIo->Media->ReadOnly; }
138 		int32 BootMethod() const {
139 			if (fDevicePath->Type == DEVICE_PATH_MEDIA) {
140 				if (fDevicePath->SubType == MEDIA_CDROM_DP)
141 					return BOOT_METHOD_CD;
142 				if (fDevicePath->SubType == MEDIA_HARDDRIVE_DP)
143 					return BOOT_METHOD_HARD_DISK;
144 			}
145 
146 			return BOOT_METHOD_DEFAULT;
147 		}
148 
149 		efi_device_path_protocol* DevicePath() { return fDevicePath; }
150 
151 	private:
152 		efi_block_io_protocol*		fBlockIo;
153 		efi_device_path_protocol*	fDevicePath;
154 };
155 
156 
157 EfiDevice::EfiDevice(efi_block_io_protocol *blockIo,
158 	efi_device_path_protocol *devicePath)
159 	:
160 	fBlockIo(blockIo),
161 	fDevicePath(devicePath)
162 {
163 }
164 
165 
166 EfiDevice::~EfiDevice()
167 {
168 }
169 
170 
171 ssize_t
172 EfiDevice::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize)
173 {
174 	uint32 offset = pos % BlockSize();
175 	pos /= BlockSize();
176 
177 	uint32 numBlocks = (offset + bufferSize + BlockSize()) / BlockSize();
178 	char readBuffer[numBlocks * BlockSize()];
179 
180 	if (fBlockIo->ReadBlocks(fBlockIo, fBlockIo->Media->MediaId,
181 		pos, sizeof(readBuffer), readBuffer) != EFI_SUCCESS)
182 		return B_ERROR;
183 
184 	memcpy(buffer, readBuffer + offset, bufferSize);
185 
186 	return bufferSize;
187 }
188 
189 
190 static status_t
191 build_device_handles()
192 {
193 	efi_guid blockIoGuid = EFI_BLOCK_IO_PROTOCOL_GUID;
194 	efi_guid devicePathGuid = EFI_DEVICE_PATH_PROTOCOL_GUID;
195 
196 	efi_device_path_protocol *devicePath, *node;
197 	efi_handle *handles = NULL;
198 	efi_status status;
199 	size_t size = 0;
200 
201 	status = kBootServices->LocateHandle(ByProtocol, &blockIoGuid, 0, &size, 0);
202 	if (status != EFI_BUFFER_TOO_SMALL)
203 		return B_ENTRY_NOT_FOUND;
204 
205 	handles = (efi_handle*)malloc(size);
206 	status = kBootServices->LocateHandle(ByProtocol, &blockIoGuid, 0, &size,
207 		handles);
208 	if (status != EFI_SUCCESS) {
209 		free(handles);
210 		return B_ENTRY_NOT_FOUND;
211 	}
212 
213 	for (size_t n = 0; n < (size / sizeof(efi_handle)); n++) {
214 		status = kBootServices->HandleProtocol(handles[n], &devicePathGuid,
215 			(void**)&devicePath);
216 		if (status != EFI_SUCCESS)
217 			continue;
218 
219 		node = devicePath;
220 		while (!device_path_end(NextDevicePathNode(node)))
221 			node = NextDevicePathNode(node);
222 
223 		if (node->Type == DEVICE_PATH_MEDIA)
224 			add_device_path(&sMediaDevices, devicePath, handles[n]);
225 		else if (node->Type == DEVICE_PATH_MESSAGING)
226 			add_device_path(&sMessagingDevices, devicePath, handles[n]);
227 	}
228 
229 	return B_OK;
230 }
231 
232 
233 static off_t
234 get_next_check_sum_offset(int32 index, off_t maxSize)
235 {
236 	if (index < 2)
237 		return index * 512;
238 
239 	if (index < 4)
240 		return (maxSize >> 10) + index * 2048;
241 
242 	return ((system_time() + index) % (maxSize >> 9)) * 512;
243 }
244 
245 
246 static uint32
247 compute_check_sum(Node *device, off_t offset)
248 {
249 	char buffer[512];
250 	ssize_t bytesRead = device->ReadAt(NULL, offset, buffer, sizeof(buffer));
251 	if (bytesRead < B_OK)
252 		return 0;
253 
254 	if (bytesRead < (ssize_t)sizeof(buffer))
255 		memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
256 
257 	uint32 *array = (uint32*)buffer;
258 	uint32 sum = 0;
259 
260 	for (uint32 i = 0;
261 		i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) {
262 		sum += array[i];
263 	}
264 
265 	return sum;
266 }
267 
268 
269 static device_handle*
270 get_messaging_device_for_media_device(device_handle *media_device)
271 {
272 	device_handle *device = NULL;
273 	while ((device = (device_handle*)list_get_next_item(&sMessagingDevices,
274 				device)) != NULL) {
275 		if (compare_device_paths(media_device->device_path,
276 				device->device_path, true))
277 			return device;
278 	}
279 
280 	return NULL;
281 }
282 
283 
284 static bool
285 get_boot_uuid(void)
286 {
287 	return false;
288 }
289 
290 
291 static status_t
292 add_boot_device(NodeList *devicesList)
293 {
294 	return B_ENTRY_NOT_FOUND;
295 }
296 
297 
298 static status_t
299 add_boot_device_for_image(NodeList *devicesList)
300 {
301 	efi_loaded_image_protocol *loadedImage;
302 	if (kBootServices->HandleProtocol(kImage, &LoadedImageGUID,
303 			(void**)&loadedImage) != EFI_SUCCESS)
304 		return B_ERROR;
305 
306 	efi_device_path_protocol *devicePath, *node;
307 	if (kBootServices->HandleProtocol(loadedImage->DeviceHandle,
308 			&DevicePathGUID, (void**)&devicePath) != EFI_SUCCESS)
309 		return B_ERROR;
310 
311 	for (node = devicePath; node->Type != DEVICE_PATH_MESSAGING;
312 			node = NextDevicePathNode(node)) {
313 		if (device_path_end(node))
314 			return B_ERROR;
315 	}
316 
317 	size_t length = device_path_length(devicePath);
318 	efi_device_path_protocol *savedDevicePath
319 		= (efi_device_path_protocol*)malloc(length);
320 	memcpy(savedDevicePath, devicePath, length);
321 
322 	efi_handle handle;
323 	if (kBootServices->LocateDevicePath(&BlockIoGUID, &devicePath, &handle)
324 			!= EFI_SUCCESS)
325 		return B_ERROR;
326 
327 	if (!device_path_end(devicePath))
328 		return B_ERROR;
329 
330 	efi_block_io_protocol *blockIo;
331 	if (kBootServices->HandleProtocol(handle, &BlockIoGUID, (void**)&blockIo)
332 			!= EFI_SUCCESS)
333 		return B_ERROR;
334 
335 	if (!blockIo->Media->MediaPresent)
336 		return B_ERROR;
337 
338 	EfiDevice *device = new(std::nothrow)EfiDevice(blockIo, savedDevicePath);
339 	if (device == NULL)
340 		return B_ERROR;
341 
342 	add_device_path(&sMessagingDevices, savedDevicePath, handle);
343 	devicesList->Insert(device);
344 
345 	return B_OK;
346 }
347 
348 
349 static status_t
350 add_cd_devices(NodeList *devicesList)
351 {
352 	device_handle *handle = NULL;
353 	while ((handle = (device_handle*)list_get_next_item(&sMediaDevices, handle))
354 			 != NULL) {
355 		efi_device_path_protocol *node = handle->device_path;
356 		while (!device_path_end(NextDevicePathNode(node)))
357 			node = NextDevicePathNode(node);
358 
359 		if (node->Type != DEVICE_PATH_MEDIA)
360 			continue;
361 
362 		if (node->SubType != MEDIA_CDROM_DP)
363 			continue;
364 
365 		device_handle *messaging_device
366 			= get_messaging_device_for_media_device(handle);
367 		if (messaging_device == NULL)
368 			continue;
369 
370 		efi_block_io_protocol *blockIo;
371 		efi_guid blockIoGuid = EFI_BLOCK_IO_PROTOCOL_GUID;
372 		efi_status status = kBootServices->HandleProtocol(messaging_device->handle,
373 			&blockIoGuid, (void**)&blockIo);
374 		if (status != EFI_SUCCESS)
375 			continue;
376 
377 		if (!blockIo->Media->MediaPresent)
378 			continue;
379 
380 		EfiDevice *device = new(std::nothrow)EfiDevice(blockIo,
381 			handle->device_path);
382 
383 		if (device == NULL)
384 			continue;
385 
386 		devicesList->Insert(device);
387 	}
388 
389 	return devicesList->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND;
390 }
391 
392 
393 static status_t
394 add_remaining_devices(NodeList *devicesList)
395 {
396 	device_handle *node = NULL;
397 	while ((node = (device_handle*)list_get_next_item(&sMessagingDevices, node))
398 		!= NULL) {
399 		NodeIterator it = devicesList->GetIterator();
400 		bool found = false;
401 		while (it.HasNext()) {
402 			EfiDevice *device = (EfiDevice*)it.Next();
403 			// device->DevicePath() is a Media Device Path instance
404 			if (compare_device_paths(device->DevicePath(),
405 				node->device_path, true)) {
406 				found = true;
407 				break;
408 			}
409 		}
410 
411 		if (!found) {
412 			efi_block_io_protocol *blockIo;
413 			efi_guid blockIoGuid = EFI_BLOCK_IO_PROTOCOL_GUID;
414 			efi_status status = kBootServices->HandleProtocol(node->handle,
415 				&blockIoGuid, (void**)&blockIo);
416 			if (status != EFI_SUCCESS)
417 				continue;
418 
419 			if (!blockIo->Media->MediaPresent)
420 				continue;
421 
422 			EfiDevice *device = new(std::nothrow)EfiDevice(blockIo,
423 				node->device_path);
424 
425 			if (device == NULL)
426 				continue;
427 
428 			devicesList->Insert(device);
429 		}
430 	}
431 
432 	return B_OK;
433 }
434 
435 
436 static bool
437 device_contains_partition(EfiDevice *device, boot::Partition *partition)
438 {
439 	EFI::Header *header = (EFI::Header*)partition->content_cookie;
440 	if (header != NULL && header->InitCheck() == B_OK) {
441 		// check if device is GPT, and contains partition entry
442 		uint32 blockSize = device->BlockSize();
443 		gpt_table_header *deviceHeader =
444 			(gpt_table_header*)malloc(blockSize);
445 		ssize_t bytesRead = device->ReadAt(NULL, blockSize, deviceHeader,
446 			blockSize);
447 		if (bytesRead != blockSize)
448 			return false;
449 
450 		if (memcmp(deviceHeader, &header->TableHeader(),
451 				sizeof(gpt_table_header)) != 0)
452 			return false;
453 
454 		// partition->cookie == int partition entry index
455 		uint32 index = (uint32)(addr_t)partition->cookie;
456 		uint32 size = sizeof(gpt_partition_entry) * (index + 1);
457 		gpt_partition_entry *entries = (gpt_partition_entry*)malloc(size);
458 		bytesRead = device->ReadAt(NULL,
459 			deviceHeader->entries_block * blockSize, entries, size);
460 		if (bytesRead != size)
461 			return false;
462 
463 		if (memcmp(&entries[index], &header->EntryAt(index),
464 				sizeof(gpt_partition_entry)) != 0)
465 			return false;
466 
467 		for (size_t i = 0; i < sizeof(kTypeMap) / sizeof(struct type_map); ++i)
468 			if (strcmp(kTypeMap[i].type, BFS_NAME) == 0)
469 				if (kTypeMap[i].guid == header->EntryAt(index).partition_type)
470 					return true;
471 
472 		// Our partition has an EFI header, but we couldn't find one, so bail
473 		return false;
474 	}
475 
476 	if ((partition->offset + partition->size) <= device->Size())
477 		return true;
478 
479 	return false;
480 }
481 
482 
483 status_t
484 platform_add_boot_device(struct stage2_args *args, NodeList *devicesList)
485 {
486 	// This is the first entry point, so init the lists here
487 	list_init(&sMessagingDevices);
488 	list_init(&sMediaDevices);
489 
490 	build_device_handles();
491 
492 	if (get_boot_uuid()) {
493 		// If we have the UUID, add the boot device containing that partition
494 		return add_boot_device(devicesList);
495 	} else {
496 		// If we don't have a UUID, add all CD devices with media, and the
497 		// device that haiku_loader.efi is located on
498 		add_boot_device_for_image(devicesList);
499 			// We do this first, so that booting from CD is the fallback
500 		add_cd_devices(devicesList);
501 		if (devicesList->Count() > 0)
502 			return B_OK;
503 	}
504 
505 	// Otherwise, we don't know what the boot device is; defer to
506 	// platform_add_block_devices() to add the rest
507 	return B_ENTRY_NOT_FOUND;
508 }
509 
510 
511 status_t
512 platform_add_block_devices(struct stage2_args *args, NodeList *devicesList)
513 {
514 	return add_remaining_devices(devicesList);
515 }
516 
517 
518 status_t
519 platform_get_boot_partitions(struct stage2_args *args, Node *bootDevice,
520 		NodeList *partitions, NodeList *bootPartitions)
521 {
522 	NodeIterator iterator = partitions->GetIterator();
523 	boot::Partition *partition = NULL;
524 	while ((partition = (boot::Partition*)iterator.Next()) != NULL) {
525 		if (device_contains_partition((EfiDevice*)bootDevice, partition)) {
526 			iterator.Remove();
527 			bootPartitions->Insert(partition);
528 		}
529 	}
530 
531 	return bootPartitions->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND;
532 }
533 
534 
535 status_t
536 platform_register_boot_device(Node *device)
537 {
538 	EfiDevice *efiDevice = (EfiDevice *)device;
539 	disk_identifier identifier;
540 
541 	// TODO: Setup using device path
542 	identifier.bus_type = UNKNOWN_BUS;
543 	identifier.device_type = UNKNOWN_DEVICE;
544 	identifier.device.unknown.size = device->Size();
545 
546 	for (uint32 i = 0; i < NUM_DISK_CHECK_SUMS; ++i) {
547 		off_t offset = get_next_check_sum_offset(i, device->Size());
548 		identifier.device.unknown.check_sums[i].offset = offset;
549 		identifier.device.unknown.check_sums[i].sum
550 			= compute_check_sum(device, offset);
551 	}
552 
553 	gBootVolume.SetInt32(BOOT_METHOD, efiDevice->BootMethod());
554 	gBootVolume.SetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, efiDevice->ReadOnly());
555 	gBootVolume.SetData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE,
556 		&identifier, sizeof(disk_identifier));
557 
558 	return B_OK;
559 }
560 
561 
562 void
563 platform_cleanup_devices()
564 {
565 }
566