1/*
2 * Copyright 2016-2020 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <boot/partitions.h>
8#include <boot/platform.h>
9#include <boot/stage2.h>
10
11#include "Header.h"
12
13#include "efi_platform.h"
14#include <efi/protocol/block-io.h>
15
16#include "gpt.h"
17#include "gpt_known_guids.h"
18
19
20//#define TRACE_DEVICES
21#ifdef TRACE_DEVICES
22#   define TRACE(x...) dprintf("efi/devices: " x)
23#else
24#   define TRACE(x...) ;
25#endif
26
27
28static efi_guid BlockIoGUID = EFI_BLOCK_IO_PROTOCOL_GUID;
29
30
31class EfiDevice : public Node
32{
33	public:
34		EfiDevice(efi_block_io_protocol *blockIo);
35		virtual ~EfiDevice();
36
37		virtual ssize_t ReadAt(void *cookie, off_t pos, void *buffer,
38			size_t bufferSize);
39		virtual ssize_t WriteAt(void *cookie, off_t pos, const void *buffer,
40			size_t bufferSize) { return B_UNSUPPORTED; }
41		virtual off_t Size() const {
42			return (fBlockIo->Media->LastBlock + 1) * BlockSize(); }
43
44		uint32 BlockSize() const { return fBlockIo->Media->BlockSize; }
45	private:
46		efi_block_io_protocol*		fBlockIo;
47};
48
49
50EfiDevice::EfiDevice(efi_block_io_protocol *blockIo)
51	:
52	fBlockIo(blockIo)
53{
54}
55
56
57EfiDevice::~EfiDevice()
58{
59}
60
61
62ssize_t
63EfiDevice::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize)
64{
65	TRACE("%s called. pos: %" B_PRIdOFF ", %p, %" B_PRIuSIZE "\n", __func__,
66		pos, buffer, bufferSize);
67
68	off_t offset = pos % BlockSize();
69	pos /= BlockSize();
70
71	uint32 numBlocks = (offset + bufferSize + BlockSize() - 1) / BlockSize();
72
73	// TODO: We really should implement memalign and align all requests to
74	// fBlockIo->Media->IoAlign. This static alignment is large enough though
75	// to catch most required alignments.
76	char readBuffer[numBlocks * BlockSize()]
77		__attribute__((aligned(2048)));
78
79	if (fBlockIo->ReadBlocks(fBlockIo, fBlockIo->Media->MediaId,
80		pos, sizeof(readBuffer), readBuffer) != EFI_SUCCESS) {
81		dprintf("%s: blockIo error reading from device!\n", __func__);
82		return B_ERROR;
83	}
84
85	memcpy(buffer, readBuffer + offset, bufferSize);
86
87	return bufferSize;
88}
89
90
91static off_t
92get_next_check_sum_offset(int32 index, off_t maxSize)
93{
94	TRACE("%s: called\n", __func__);
95
96	if (index < 2)
97		return index * 512;
98
99	if (index < 4)
100		return (maxSize >> 10) + index * 2048;
101
102	return ((system_time() + index) % (maxSize >> 9)) * 512;
103}
104
105
106static uint32
107compute_check_sum(Node *device, off_t offset)
108{
109	TRACE("%s: called\n", __func__);
110
111	char buffer[512];
112	ssize_t bytesRead = device->ReadAt(NULL, offset, buffer, sizeof(buffer));
113	if (bytesRead < B_OK)
114		return 0;
115
116	if (bytesRead < (ssize_t)sizeof(buffer))
117		memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
118
119	uint32 *array = (uint32*)buffer;
120	uint32 sum = 0;
121
122	for (uint32 i = 0; i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++)
123		sum += array[i];
124
125	return sum;
126}
127
128
129static bool
130device_contains_partition(EfiDevice *device, boot::Partition *partition)
131{
132	EFI::Header *header = (EFI::Header*)partition->content_cookie;
133	if (header != NULL && header->InitCheck() == B_OK) {
134		// check if device is GPT, and contains partition entry
135		uint32 blockSize = device->BlockSize();
136		gpt_table_header *deviceHeader =
137			(gpt_table_header*)malloc(blockSize);
138		ssize_t bytesRead = device->ReadAt(NULL, blockSize, deviceHeader,
139			blockSize);
140		if (bytesRead != (ssize_t)blockSize)
141			return false;
142
143		if (memcmp(deviceHeader, &header->TableHeader(),
144				sizeof(gpt_table_header)) != 0)
145			return false;
146
147		// partition->cookie == int partition entry index
148		uint32 index = (uint32)(addr_t)partition->cookie;
149		uint32 size = sizeof(gpt_partition_entry) * (index + 1);
150		gpt_partition_entry *entries = (gpt_partition_entry*)malloc(size);
151		bytesRead = device->ReadAt(NULL,
152			deviceHeader->entries_block * blockSize, entries, size);
153		if (bytesRead != (ssize_t)size)
154			return false;
155
156		if (memcmp(&entries[index], &header->EntryAt(index),
157				sizeof(gpt_partition_entry)) != 0)
158			return false;
159
160		for (size_t i = 0; i < sizeof(kTypeMap) / sizeof(struct type_map); ++i)
161			if (strcmp(kTypeMap[i].type, BFS_NAME) == 0)
162				if (kTypeMap[i].guid == header->EntryAt(index).partition_type)
163					return true;
164
165		// Our partition has an EFI header, but we couldn't find one, so bail
166		return false;
167	}
168
169	if ((partition->offset + partition->size) <= device->Size())
170			return true;
171
172	return false;
173}
174
175
176status_t
177platform_add_boot_device(struct stage2_args *args, NodeList *devicesList)
178{
179	TRACE("%s: called\n", __func__);
180
181	efi_block_io_protocol *blockIo;
182	size_t memSize = 0;
183
184	// Read to zero sized buffer to get memory needed for handles
185	if (kBootServices->LocateHandle(ByProtocol, &BlockIoGUID, 0, &memSize, 0)
186			!= EFI_BUFFER_TOO_SMALL)
187		panic("Cannot read size of block device handles!");
188
189	uint32 noOfHandles = memSize / sizeof(efi_handle);
190
191	efi_handle handles[noOfHandles];
192	if (kBootServices->LocateHandle(ByProtocol, &BlockIoGUID, 0, &memSize,
193			handles) != EFI_SUCCESS)
194		panic("Failed to locate block devices!");
195
196	// All block devices has one for the disk and one per partition
197	// There is a special case for a device with one fixed partition
198	// But we probably do not care about booting on that kind of device
199	// So find all disk block devices and let Haiku do partition scan
200	for (uint32 n = 0; n < noOfHandles; n++) {
201		if (kBootServices->HandleProtocol(handles[n], &BlockIoGUID,
202				(void**)&blockIo) != EFI_SUCCESS)
203			panic("Cannot get block device handle!");
204
205		TRACE("%s: %p: present: %s, logical: %s, removeable: %s, "
206			"blocksize: %" PRIu32 ", lastblock: %" PRIu64 "\n",
207			__func__, blockIo,
208			blockIo->Media->MediaPresent ? "true" : "false",
209			blockIo->Media->LogicalPartition ? "true" : "false",
210			blockIo->Media->RemovableMedia ? "true" : "false",
211			blockIo->Media->BlockSize, blockIo->Media->LastBlock);
212
213		if (!blockIo->Media->MediaPresent || blockIo->Media->LogicalPartition)
214			continue;
215
216		// The qemu flash device with a 256K block sizes sometime show up
217		// in edk2. If flash is unconfigured, bad things happen on arm.
218		// edk2 bug: https://bugzilla.tianocore.org/show_bug.cgi?id=2856
219		// We're not ready for flash devices in efi, so skip anything odd.
220		if (blockIo->Media->BlockSize > 8192)
221			continue;
222
223		EfiDevice *device = new(std::nothrow)EfiDevice(blockIo);
224		if (device == NULL)
225			panic("Can't allocate memory for block devices!");
226		devicesList->Insert(device);
227	}
228	return devicesList->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND;
229}
230
231
232status_t
233platform_add_block_devices(struct stage2_args *args, NodeList *devicesList)
234{
235	TRACE("%s: called\n", __func__);
236
237	//TODO: Currently we add all in platform_add_boot_device
238	return devicesList->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND;
239}
240
241
242status_t
243platform_get_boot_partitions(struct stage2_args *args, Node *bootDevice,
244		NodeList *partitions, NodeList *bootPartitions)
245{
246	NodeIterator iterator = partitions->GetIterator();
247	boot::Partition *partition = NULL;
248	while ((partition = (boot::Partition*)iterator.Next()) != NULL) {
249		if (device_contains_partition((EfiDevice*)bootDevice, partition)) {
250			bootPartitions->Insert(partition);
251		}
252	}
253
254	return bootPartitions->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND;
255}
256
257
258status_t
259platform_register_boot_device(Node *device)
260{
261	TRACE("%s: called\n", __func__);
262
263	disk_identifier identifier;
264
265	identifier.bus_type = UNKNOWN_BUS;
266	identifier.device_type = UNKNOWN_DEVICE;
267	identifier.device.unknown.size = device->Size();
268
269	for (uint32 i = 0; i < NUM_DISK_CHECK_SUMS; ++i) {
270		off_t offset = get_next_check_sum_offset(i, device->Size());
271		identifier.device.unknown.check_sums[i].offset = offset;
272		identifier.device.unknown.check_sums[i].sum = compute_check_sum(device,
273			offset);
274	}
275
276	// ...HARD_DISK, as we pick partition and have checksum (no need to use _CD)
277	gBootVolume.SetInt32(BOOT_METHOD, BOOT_METHOD_HARD_DISK);
278	gBootVolume.SetData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE,
279		&identifier, sizeof(disk_identifier));
280
281	return B_OK;
282}
283
284
285void
286platform_cleanup_devices()
287{
288}
289