efiblock.c revision 1.7 1 /* $NetBSD: efiblock.c,v 1.7 2019/09/27 20:10:42 jakllsch Exp $ */
2
3 /*-
4 * Copyright (c) 2016 Kimihiro Nonaka <nonaka (at) netbsd.org>
5 * Copyright (c) 2018 Jared McNeill <jmcneill (at) invisible.ca>
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30 #define FSTYPENAMES
31
32 #include <sys/param.h>
33 #include <sys/md5.h>
34 #include <sys/uuid.h>
35
36 #include "efiboot.h"
37 #include "efiblock.h"
38
39 static EFI_HANDLE *efi_block;
40 static UINTN efi_nblock;
41 static struct efi_block_part *efi_block_booted = NULL;
42
43 static TAILQ_HEAD(, efi_block_dev) efi_block_devs = TAILQ_HEAD_INITIALIZER(efi_block_devs);
44
45 static int
46 efi_block_parse(const char *fname, struct efi_block_part **pbpart, char **pfile)
47 {
48 struct efi_block_dev *bdev;
49 struct efi_block_part *bpart;
50 char pathbuf[PATH_MAX], *default_device, *ep = NULL;
51 const char *full_path;
52 intmax_t dev;
53 int part;
54
55 default_device = get_default_device();
56 if (strchr(fname, ':') == NULL) {
57 if (strlen(default_device) > 0) {
58 snprintf(pathbuf, sizeof(pathbuf), "%s:%s", default_device, fname);
59 full_path = pathbuf;
60 *pfile = __UNCONST(fname);
61 } else {
62 return EINVAL;
63 }
64 } else {
65 full_path = fname;
66 *pfile = strchr(fname, ':') + 1;
67 }
68
69 if (strncasecmp(full_path, "hd", 2) != 0)
70 return EINVAL;
71 dev = strtoimax(full_path + 2, &ep, 10);
72 if (dev < 0 || dev >= efi_nblock)
73 return ENXIO;
74 if (ep[0] < 'a' || ep[0] >= 'a' + MAXPARTITIONS || ep[1] != ':')
75 return EINVAL;
76 part = ep[0] - 'a';
77 TAILQ_FOREACH(bdev, &efi_block_devs, entries) {
78 if (bdev->index == dev) {
79 TAILQ_FOREACH(bpart, &bdev->partitions, entries) {
80 if (bpart->index == part) {
81 *pbpart = bpart;
82 return 0;
83 }
84 }
85 }
86 }
87
88 return ENOENT;
89 }
90
91 static void
92 efi_block_generate_hash_mbr(struct efi_block_part *bpart, struct mbr_sector *mbr)
93 {
94 MD5_CTX md5ctx;
95
96 MD5Init(&md5ctx);
97 MD5Update(&md5ctx, (void *)mbr, sizeof(*mbr));
98 MD5Final(bpart->hash, &md5ctx);
99 }
100
101 static void *
102 efi_block_allocate_device_buffer(struct efi_block_dev *bdev, UINTN size,
103 void **buf_start)
104 {
105 void *buf;
106
107 if (bdev->bio->Media->IoAlign <= 1)
108 *buf_start = buf = AllocatePool(size);
109 else {
110 buf = AllocatePool(size + bdev->bio->Media->IoAlign - 1);
111 *buf_start = (buf == NULL) ? NULL :
112 (void *)roundup2((intptr_t)buf, bdev->bio->Media->IoAlign);
113 }
114
115 return buf;
116 }
117
118 static int
119 efi_block_find_partitions_disklabel(struct efi_block_dev *bdev, struct mbr_sector *mbr, uint32_t start, uint32_t size)
120 {
121 struct efi_block_part *bpart;
122 struct disklabel d;
123 struct partition *p;
124 EFI_STATUS status;
125 EFI_LBA lba;
126 void *buf, *buf_start;
127 UINT32 sz;
128 int n;
129
130 sz = __MAX(sizeof(d), bdev->bio->Media->BlockSize);
131 sz = roundup(sz, bdev->bio->Media->BlockSize);
132 if ((buf = efi_block_allocate_device_buffer(bdev, sz, &buf_start)) == NULL)
133 return ENOMEM;
134
135 lba = (((EFI_LBA)start + LABELSECTOR) * DEV_BSIZE) / bdev->bio->Media->BlockSize;
136 status = uefi_call_wrapper(bdev->bio->ReadBlocks, 5, bdev->bio, bdev->media_id,
137 lba, sz, buf_start);
138 if (EFI_ERROR(status) || getdisklabel(buf_start, &d) != NULL) {
139 FreePool(buf);
140 return EIO;
141 }
142 FreePool(buf);
143
144 if (le32toh(d.d_magic) != DISKMAGIC || le32toh(d.d_magic2) != DISKMAGIC)
145 return EINVAL;
146 if (le16toh(d.d_npartitions) > MAXPARTITIONS)
147 return EINVAL;
148
149 for (n = 0; n < le16toh(d.d_npartitions); n++) {
150 p = &d.d_partitions[n];
151 switch (p->p_fstype) {
152 case FS_BSDFFS:
153 case FS_MSDOS:
154 case FS_BSDLFS:
155 break;
156 default:
157 continue;
158 }
159
160 bpart = alloc(sizeof(*bpart));
161 bpart->index = n;
162 bpart->bdev = bdev;
163 bpart->type = EFI_BLOCK_PART_DISKLABEL;
164 bpart->disklabel.secsize = le32toh(d.d_secsize);
165 bpart->disklabel.part = *p;
166 efi_block_generate_hash_mbr(bpart, mbr);
167 TAILQ_INSERT_TAIL(&bdev->partitions, bpart, entries);
168 }
169
170 return 0;
171 }
172
173 static int
174 efi_block_find_partitions_mbr(struct efi_block_dev *bdev)
175 {
176 struct mbr_sector mbr;
177 struct mbr_partition *mbr_part;
178 EFI_STATUS status;
179 void *buf, *buf_start;
180 UINT32 sz;
181 int n;
182
183 sz = __MAX(sizeof(mbr), bdev->bio->Media->BlockSize);
184 sz = roundup(sz, bdev->bio->Media->BlockSize);
185 if ((buf = efi_block_allocate_device_buffer(bdev, sz, &buf_start)) == NULL)
186 return ENOMEM;
187
188 status = uefi_call_wrapper(bdev->bio->ReadBlocks, 5, bdev->bio, bdev->media_id,
189 0, sz, buf_start);
190 if (EFI_ERROR(status)) {
191 FreePool(buf);
192 return EIO;
193 }
194 memcpy(&mbr, buf_start, sizeof(mbr));
195 FreePool(buf);
196
197 if (le32toh(mbr.mbr_magic) != MBR_MAGIC)
198 return ENOENT;
199
200 for (n = 0; n < MBR_PART_COUNT; n++) {
201 mbr_part = &mbr.mbr_parts[n];
202 if (le32toh(mbr_part->mbrp_size) == 0)
203 continue;
204 if (mbr_part->mbrp_type == MBR_PTYPE_NETBSD) {
205 efi_block_find_partitions_disklabel(bdev, &mbr, le32toh(mbr_part->mbrp_start), le32toh(mbr_part->mbrp_size));
206 break;
207 }
208 }
209
210 return 0;
211 }
212
213 static const struct {
214 struct uuid guid;
215 uint8_t fstype;
216 } gpt_guid_to_str[] = {
217 { GPT_ENT_TYPE_NETBSD_FFS, FS_BSDFFS },
218 { GPT_ENT_TYPE_NETBSD_LFS, FS_BSDLFS },
219 { GPT_ENT_TYPE_NETBSD_RAIDFRAME, FS_RAID },
220 { GPT_ENT_TYPE_NETBSD_CCD, FS_CCD },
221 { GPT_ENT_TYPE_NETBSD_CGD, FS_CGD },
222 { GPT_ENT_TYPE_MS_BASIC_DATA, FS_MSDOS }, /* or NTFS? ambiguous */
223 };
224
225 static int
226 efi_block_find_partitions_gpt_entry(struct efi_block_dev *bdev, struct gpt_hdr *hdr, struct gpt_ent *ent, UINT32 index)
227 {
228 struct efi_block_part *bpart;
229 uint8_t fstype = FS_UNUSED;
230 struct uuid uuid;
231 int n;
232
233 memcpy(&uuid, ent->ent_type, sizeof(uuid));
234 for (n = 0; n < __arraycount(gpt_guid_to_str); n++)
235 if (memcmp(ent->ent_type, &gpt_guid_to_str[n].guid, sizeof(ent->ent_type)) == 0) {
236 fstype = gpt_guid_to_str[n].fstype;
237 break;
238 }
239 if (fstype == FS_UNUSED)
240 return 0;
241
242 bpart = alloc(sizeof(*bpart));
243 bpart->index = index;
244 bpart->bdev = bdev;
245 bpart->type = EFI_BLOCK_PART_GPT;
246 bpart->gpt.fstype = fstype;
247 bpart->gpt.ent = *ent;
248 memcpy(bpart->hash, ent->ent_guid, sizeof(bpart->hash));
249 TAILQ_INSERT_TAIL(&bdev->partitions, bpart, entries);
250
251 return 0;
252 }
253
254 static int
255 efi_block_find_partitions_gpt(struct efi_block_dev *bdev)
256 {
257 struct gpt_hdr hdr;
258 struct gpt_ent ent;
259 EFI_STATUS status;
260 void *buf, *buf_start;
261 UINT32 sz, entry;
262
263 sz = __MAX(sizeof(hdr), bdev->bio->Media->BlockSize);
264 sz = roundup(sz, bdev->bio->Media->BlockSize);
265 if ((buf = efi_block_allocate_device_buffer(bdev, sz, &buf_start)) == NULL)
266 return ENOMEM;
267
268 status = uefi_call_wrapper(bdev->bio->ReadBlocks, 5, bdev->bio, bdev->media_id,
269 GPT_HDR_BLKNO, sz, buf_start);
270 if (EFI_ERROR(status)) {
271 FreePool(buf);
272 return EIO;
273 }
274 memcpy(&hdr, buf_start, sizeof(hdr));
275 FreePool(buf);
276
277 if (memcmp(hdr.hdr_sig, GPT_HDR_SIG, sizeof(hdr.hdr_sig)) != 0)
278 return ENOENT;
279 if (le32toh(hdr.hdr_entsz) < sizeof(ent))
280 return EINVAL;
281
282 sz = __MAX(le32toh(hdr.hdr_entsz) * le32toh(hdr.hdr_entries), bdev->bio->Media->BlockSize);
283 sz = roundup(sz, bdev->bio->Media->BlockSize);
284 if ((buf = efi_block_allocate_device_buffer(bdev, sz, &buf_start)) == NULL)
285 return ENOMEM;
286
287 status = uefi_call_wrapper(bdev->bio->ReadBlocks, 5, bdev->bio, bdev->media_id,
288 le64toh(hdr.hdr_lba_table), sz, buf_start);
289 if (EFI_ERROR(status)) {
290 FreePool(buf);
291 return EIO;
292 }
293
294 for (entry = 0; entry < le32toh(hdr.hdr_entries); entry++) {
295 memcpy(&ent, buf_start + (entry * le32toh(hdr.hdr_entsz)),
296 sizeof(ent));
297 efi_block_find_partitions_gpt_entry(bdev, &hdr, &ent, entry);
298 }
299
300 FreePool(buf);
301
302 return 0;
303 }
304
305 static int
306 efi_block_find_partitions(struct efi_block_dev *bdev)
307 {
308 int error;
309
310 error = efi_block_find_partitions_gpt(bdev);
311 if (error)
312 error = efi_block_find_partitions_mbr(bdev);
313
314 return error;
315 }
316
317 void
318 efi_block_probe(void)
319 {
320 struct efi_block_dev *bdev;
321 struct efi_block_part *bpart;
322 EFI_BLOCK_IO *bio;
323 EFI_STATUS status;
324 uint16_t devindex = 0;
325 int depth = -1;
326 int n;
327
328 status = LibLocateHandle(ByProtocol, &BlockIoProtocol, NULL, &efi_nblock, &efi_block);
329 if (EFI_ERROR(status))
330 return;
331
332 if (efi_bootdp) {
333 depth = efi_device_path_depth(efi_bootdp, MEDIA_DEVICE_PATH);
334 if (depth == 0)
335 depth = 1;
336 else if (depth == -1)
337 depth = 2;
338 }
339
340 for (n = 0; n < efi_nblock; n++) {
341 status = uefi_call_wrapper(BS->HandleProtocol, 3, efi_block[n], &BlockIoProtocol, (void **)&bio);
342 if (EFI_ERROR(status) || !bio->Media->MediaPresent)
343 continue;
344
345 if (bio->Media->LogicalPartition)
346 continue;
347
348 bdev = alloc(sizeof(*bdev));
349 bdev->index = devindex++;
350 bdev->bio = bio;
351 bdev->media_id = bio->Media->MediaId;
352 bdev->path = DevicePathFromHandle(efi_block[n]);
353 TAILQ_INIT(&bdev->partitions);
354 TAILQ_INSERT_TAIL(&efi_block_devs, bdev, entries);
355
356 efi_block_find_partitions(bdev);
357
358 if (depth > 0 && efi_device_path_ncmp(efi_bootdp, DevicePathFromHandle(efi_block[n]), depth) == 0) {
359 TAILQ_FOREACH(bpart, &bdev->partitions, entries) {
360 uint8_t fstype = FS_UNUSED;
361 switch (bpart->type) {
362 case EFI_BLOCK_PART_DISKLABEL:
363 fstype = bpart->disklabel.part.p_fstype;
364 break;
365 case EFI_BLOCK_PART_GPT:
366 fstype = bpart->gpt.fstype;
367 break;
368 }
369 if (fstype == FS_BSDFFS) {
370 char devname[9];
371 snprintf(devname, sizeof(devname), "hd%u%c", bdev->index, bpart->index + 'a');
372 set_default_device(devname);
373 break;
374 }
375 }
376 }
377 }
378 }
379
380 static void
381 print_guid(const uint8_t *guid)
382 {
383 const int index[] = { 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15 };
384 int i;
385
386 for (i = 0; i < 16; i++) {
387 printf("%02x", guid[index[i]]);
388 if (i == 3 || i == 5 || i == 7 || i == 9)
389 printf("-");
390 }
391 }
392
393 void
394 efi_block_show(void)
395 {
396 struct efi_block_dev *bdev;
397 struct efi_block_part *bpart;
398 uint64_t size;
399 CHAR16 *path;
400
401 TAILQ_FOREACH(bdev, &efi_block_devs, entries) {
402 printf("hd%u (", bdev->index);
403
404 /* Size in MB */
405 size = ((bdev->bio->Media->LastBlock + 1) * bdev->bio->Media->BlockSize) / (1024 * 1024);
406 if (size >= 10000)
407 printf("%"PRIu64" GB", size / 1024);
408 else
409 printf("%"PRIu64" MB", size);
410 printf("): ");
411
412 path = DevicePathToStr(bdev->path);
413 Print(L"%s", path);
414 FreePool(path);
415
416 printf("\n");
417
418 TAILQ_FOREACH(bpart, &bdev->partitions, entries) {
419 switch (bpart->type) {
420 case EFI_BLOCK_PART_DISKLABEL:
421 printf(" hd%u%c (", bdev->index, bpart->index + 'a');
422
423 /* Size in MB */
424 size = ((uint64_t)bpart->disklabel.secsize * bpart->disklabel.part.p_size) / (1024 * 1024);
425 if (size >= 10000)
426 printf("%"PRIu64" GB", size / 1024);
427 else
428 printf("%"PRIu64" MB", size);
429 printf("): ");
430
431 printf("%s\n", fstypenames[bpart->disklabel.part.p_fstype]);
432 break;
433 case EFI_BLOCK_PART_GPT:
434 printf(" hd%u%c ", bdev->index, bpart->index + 'a');
435
436 if (bpart->gpt.ent.ent_name[0] == 0x0000) {
437 printf("\"");
438 print_guid(bpart->gpt.ent.ent_guid);
439 printf("\"");
440 } else {
441 Print(L"\"%s\"", bpart->gpt.ent.ent_name);
442 }
443
444 /* Size in MB */
445 size = (le64toh(bpart->gpt.ent.ent_lba_end) - le64toh(bpart->gpt.ent.ent_lba_start)) * bdev->bio->Media->BlockSize;
446 size /= (1024 * 1024);
447 if (size >= 10000)
448 printf(" (%"PRIu64" GB): ", size / 1024);
449 else
450 printf(" (%"PRIu64" MB): ", size);
451
452 printf("%s\n", fstypenames[bpart->gpt.fstype]);
453 break;
454 default:
455 break;
456 }
457 }
458 }
459 }
460
461 struct efi_block_part *
462 efi_block_boot_part(void)
463 {
464 return efi_block_booted;
465 }
466
467 int
468 efi_block_open(struct open_file *f, ...)
469 {
470 struct efi_block_part *bpart;
471 const char *fname;
472 char **file;
473 char *path;
474 va_list ap;
475 int rv, n;
476
477 va_start(ap, f);
478 fname = va_arg(ap, const char *);
479 file = va_arg(ap, char **);
480 va_end(ap);
481
482 rv = efi_block_parse(fname, &bpart, &path);
483 if (rv != 0)
484 return rv;
485
486 for (n = 0; n < ndevs; n++)
487 if (strcmp(DEV_NAME(&devsw[n]), "efiblock") == 0) {
488 f->f_dev = &devsw[n];
489 break;
490 }
491 if (n == ndevs)
492 return ENXIO;
493
494 f->f_devdata = bpart;
495
496 *file = path;
497
498 efi_block_booted = bpart;
499
500 return 0;
501 }
502
503 int
504 efi_block_close(struct open_file *f)
505 {
506 return 0;
507 }
508
509 int
510 efi_block_strategy(void *devdata, int rw, daddr_t dblk, size_t size, void *buf, size_t *rsize)
511 {
512 struct efi_block_part *bpart = devdata;
513 EFI_STATUS status;
514 void *allocated_buf, *aligned_buf;
515
516 if (rw != F_READ)
517 return EROFS;
518
519 switch (bpart->type) {
520 case EFI_BLOCK_PART_DISKLABEL:
521 if (bpart->bdev->bio->Media->BlockSize != bpart->disklabel.secsize) {
522 printf("%s: unsupported block size %d (expected %d)\n", __func__,
523 bpart->bdev->bio->Media->BlockSize, bpart->disklabel.secsize);
524 return EIO;
525 }
526 dblk += bpart->disklabel.part.p_offset;
527 break;
528 case EFI_BLOCK_PART_GPT:
529 if (bpart->bdev->bio->Media->BlockSize != DEV_BSIZE) {
530 printf("%s: unsupported block size %d (expected %d)\n", __func__,
531 bpart->bdev->bio->Media->BlockSize, DEV_BSIZE);
532 return EIO;
533 }
534 dblk += le64toh(bpart->gpt.ent.ent_lba_start);
535 break;
536 default:
537 return EINVAL;
538 }
539
540 if ((bpart->bdev->bio->Media->IoAlign <= 1) ||
541 ((intptr_t)buf & (bpart->bdev->bio->Media->IoAlign - 1)) == 0) {
542 allocated_buf = NULL;
543 aligned_buf = buf;
544 } else if ((allocated_buf = efi_block_allocate_device_buffer(bpart->bdev,
545 size, &aligned_buf)) == NULL)
546 return ENOMEM;
547
548 status = uefi_call_wrapper(bpart->bdev->bio->ReadBlocks, 5,
549 bpart->bdev->bio, bpart->bdev->media_id, dblk, size, aligned_buf);
550 if (EFI_ERROR(status)) {
551 if (allocated_buf != NULL)
552 FreePool(allocated_buf);
553 return EIO;
554 }
555 if (allocated_buf != NULL) {
556 memcpy(buf, aligned_buf, size);
557 FreePool(allocated_buf);
558 }
559
560 *rsize = size;
561
562 return 0;
563 }
564