1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * rfd_ftl.c -- resident flash disk (flash translation layer) 4 * 5 * Copyright © 2005 Sean Young <sean@mess.org> 6 * 7 * This type of flash translation layer (FTL) is used by the Embedded BIOS 8 * by General Software. It is known as the Resident Flash Disk (RFD), see: 9 * 10 * http://www.gensw.com/pages/prod/bios/rfd.htm 11 * 12 * based on ftl.c 13 */ 14 15 #include <linux/hdreg.h> 16 #include <linux/init.h> 17 #include <linux/mtd/blktrans.h> 18 #include <linux/mtd/mtd.h> 19 #include <linux/vmalloc.h> 20 #include <linux/slab.h> 21 #include <linux/jiffies.h> 22 #include <linux/module.h> 23 24 #include <asm/types.h> 25 26 static int block_size = 0; 27 module_param(block_size, int, 0); 28 MODULE_PARM_DESC(block_size, "Block size to use by RFD, defaults to erase unit size"); 29 30 #define PREFIX "rfd_ftl: " 31 32 /* This major has been assigned by device@lanana.org */ 33 #ifndef RFD_FTL_MAJOR 34 #define RFD_FTL_MAJOR 256 35 #endif 36 37 /* Maximum number of partitions in an FTL region */ 38 #define PART_BITS 4 39 40 /* An erase unit should start with this value */ 41 #define RFD_MAGIC 0x9193 42 43 /* the second value is 0xffff or 0xffc8; function unknown */ 44 45 /* the third value is always 0xffff, ignored */ 46 47 /* next is an array of mapping for each corresponding sector */ 48 #define HEADER_MAP_OFFSET 3 49 #define SECTOR_DELETED 0x0000 50 #define SECTOR_ZERO 0xfffe 51 #define SECTOR_FREE 0xffff 52 53 #define SECTOR_SIZE 512 54 55 #define SECTORS_PER_TRACK 63 56 57 struct block { 58 enum { 59 BLOCK_OK, 60 BLOCK_ERASING, 61 BLOCK_ERASED, 62 BLOCK_UNUSED, 63 BLOCK_FAILED 64 } state; 65 int free_sectors; 66 int used_sectors; 67 int erases; 68 u_long offset; 69 }; 70 71 struct partition { 72 struct mtd_blktrans_dev mbd; 73 74 u_int block_size; /* size of erase unit */ 75 u_int total_blocks; /* number of erase units */ 76 u_int header_sectors_per_block; /* header sectors in erase unit */ 77 u_int data_sectors_per_block; /* data sectors in erase unit */ 78 u_int sector_count; /* sectors in translated disk */ 79 u_int header_size; /* bytes in header sector */ 80 int reserved_block; /* block next up for reclaim */ 81 int current_block; /* block to write to */ 82 u16 *header_cache; /* cached header */ 83 84 int is_reclaiming; 85 int cylinders; 86 int errors; 87 u_long *sector_map; 88 struct block *blocks; 89 }; 90 91 static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf); 92 93 static int build_block_map(struct partition *part, int block_no) 94 { 95 struct block *block = &part->blocks[block_no]; 96 int i; 97 98 block->offset = part->block_size * block_no; 99 100 if (le16_to_cpu(part->header_cache[0]) != RFD_MAGIC) { 101 block->state = BLOCK_UNUSED; 102 return -ENOENT; 103 } 104 105 block->state = BLOCK_OK; 106 107 for (i=0; i<part->data_sectors_per_block; i++) { 108 u16 entry; 109 110 entry = le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]); 111 112 if (entry == SECTOR_DELETED) 113 continue; 114 115 if (entry == SECTOR_FREE) { 116 block->free_sectors++; 117 continue; 118 } 119 120 if (entry == SECTOR_ZERO) 121 entry = 0; 122 123 if (entry >= part->sector_count) { 124 printk(KERN_WARNING PREFIX 125 "'%s': unit #%d: entry %d corrupt, " 126 "sector %d out of range\n", 127 part->mbd.mtd->name, block_no, i, entry); 128 continue; 129 } 130 131 if (part->sector_map[entry] != -1) { 132 printk(KERN_WARNING PREFIX 133 "'%s': more than one entry for sector %d\n", 134 part->mbd.mtd->name, entry); 135 part->errors = 1; 136 continue; 137 } 138 139 part->sector_map[entry] = block->offset + 140 (i + part->header_sectors_per_block) * SECTOR_SIZE; 141 142 block->used_sectors++; 143 } 144 145 if (block->free_sectors == part->data_sectors_per_block) 146 part->reserved_block = block_no; 147 148 return 0; 149 } 150 151 static int scan_header(struct partition *part) 152 { 153 int sectors_per_block; 154 int i, rc = -ENOMEM; 155 int blocks_found; 156 size_t retlen; 157 158 sectors_per_block = part->block_size / SECTOR_SIZE; 159 part->total_blocks = (u32)part->mbd.mtd->size / part->block_size; 160 161 if (part->total_blocks < 2) 162 return -ENOENT; 163 164 /* each erase block has three bytes header, followed by the map */ 165 part->header_sectors_per_block = 166 ((HEADER_MAP_OFFSET + sectors_per_block) * 167 sizeof(u16) + SECTOR_SIZE - 1) / SECTOR_SIZE; 168 169 part->data_sectors_per_block = sectors_per_block - 170 part->header_sectors_per_block; 171 172 part->header_size = (HEADER_MAP_OFFSET + 173 part->data_sectors_per_block) * sizeof(u16); 174 175 part->cylinders = (part->data_sectors_per_block * 176 (part->total_blocks - 1) - 1) / SECTORS_PER_TRACK; 177 178 part->sector_count = part->cylinders * SECTORS_PER_TRACK; 179 180 part->current_block = -1; 181 part->reserved_block = -1; 182 part->is_reclaiming = 0; 183 184 part->header_cache = kmalloc(part->header_size, GFP_KERNEL); 185 if (!part->header_cache) 186 goto err; 187 188 part->blocks = kzalloc_objs(struct block, part->total_blocks); 189 if (!part->blocks) 190 goto err; 191 192 part->sector_map = vmalloc_array(part->sector_count, 193 sizeof(u_long)); 194 if (!part->sector_map) 195 goto err; 196 197 for (i=0; i<part->sector_count; i++) 198 part->sector_map[i] = -1; 199 200 for (i=0, blocks_found=0; i<part->total_blocks; i++) { 201 rc = mtd_read(part->mbd.mtd, i * part->block_size, 202 part->header_size, &retlen, 203 (u_char *)part->header_cache); 204 205 if (!rc && retlen != part->header_size) 206 rc = -EIO; 207 208 if (rc) 209 goto err; 210 211 if (!build_block_map(part, i)) 212 blocks_found++; 213 } 214 215 if (blocks_found == 0) { 216 printk(KERN_NOTICE PREFIX "no RFD magic found in '%s'\n", 217 part->mbd.mtd->name); 218 rc = -ENOENT; 219 goto err; 220 } 221 222 if (part->reserved_block == -1) { 223 printk(KERN_WARNING PREFIX "'%s': no empty erase unit found\n", 224 part->mbd.mtd->name); 225 226 part->errors = 1; 227 } 228 229 return 0; 230 231 err: 232 vfree(part->sector_map); 233 kfree(part->header_cache); 234 kfree(part->blocks); 235 236 return rc; 237 } 238 239 static int rfd_ftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *buf) 240 { 241 struct partition *part = container_of(dev, struct partition, mbd); 242 u_long addr; 243 size_t retlen; 244 int rc; 245 246 if (sector >= part->sector_count) 247 return -EIO; 248 249 addr = part->sector_map[sector]; 250 if (addr != -1) { 251 rc = mtd_read(part->mbd.mtd, addr, SECTOR_SIZE, &retlen, 252 (u_char *)buf); 253 if (!rc && retlen != SECTOR_SIZE) 254 rc = -EIO; 255 256 if (rc) { 257 printk(KERN_WARNING PREFIX "error reading '%s' at " 258 "0x%lx\n", part->mbd.mtd->name, addr); 259 return rc; 260 } 261 } else 262 memset(buf, 0, SECTOR_SIZE); 263 264 return 0; 265 } 266 267 static int erase_block(struct partition *part, int block) 268 { 269 struct erase_info *erase; 270 int rc; 271 272 erase = kmalloc_obj(struct erase_info); 273 if (!erase) 274 return -ENOMEM; 275 276 erase->addr = part->blocks[block].offset; 277 erase->len = part->block_size; 278 279 part->blocks[block].state = BLOCK_ERASING; 280 part->blocks[block].free_sectors = 0; 281 282 rc = mtd_erase(part->mbd.mtd, erase); 283 if (rc) { 284 printk(KERN_ERR PREFIX "erase of region %llx,%llx on '%s' " 285 "failed\n", (unsigned long long)erase->addr, 286 (unsigned long long)erase->len, part->mbd.mtd->name); 287 part->blocks[block].state = BLOCK_FAILED; 288 part->blocks[block].free_sectors = 0; 289 part->blocks[block].used_sectors = 0; 290 } else { 291 u16 magic = cpu_to_le16(RFD_MAGIC); 292 size_t retlen; 293 294 part->blocks[block].state = BLOCK_ERASED; 295 part->blocks[block].free_sectors = part->data_sectors_per_block; 296 part->blocks[block].used_sectors = 0; 297 part->blocks[block].erases++; 298 299 rc = mtd_write(part->mbd.mtd, part->blocks[block].offset, 300 sizeof(magic), &retlen, (u_char *)&magic); 301 if (!rc && retlen != sizeof(magic)) 302 rc = -EIO; 303 304 if (rc) { 305 pr_err(PREFIX "'%s': unable to write RFD header at 0x%lx\n", 306 part->mbd.mtd->name, part->blocks[block].offset); 307 part->blocks[block].state = BLOCK_FAILED; 308 } else { 309 part->blocks[block].state = BLOCK_OK; 310 } 311 } 312 313 kfree(erase); 314 315 return rc; 316 } 317 318 static int move_block_contents(struct partition *part, int block_no, u_long *old_sector) 319 { 320 void *sector_data; 321 u16 *map; 322 size_t retlen; 323 int i, rc = -ENOMEM; 324 325 part->is_reclaiming = 1; 326 327 sector_data = kmalloc(SECTOR_SIZE, GFP_KERNEL); 328 if (!sector_data) 329 goto err3; 330 331 map = kmalloc(part->header_size, GFP_KERNEL); 332 if (!map) 333 goto err2; 334 335 rc = mtd_read(part->mbd.mtd, part->blocks[block_no].offset, 336 part->header_size, &retlen, (u_char *)map); 337 338 if (!rc && retlen != part->header_size) 339 rc = -EIO; 340 341 if (rc) { 342 printk(KERN_ERR PREFIX "error reading '%s' at " 343 "0x%lx\n", part->mbd.mtd->name, 344 part->blocks[block_no].offset); 345 346 goto err; 347 } 348 349 for (i=0; i<part->data_sectors_per_block; i++) { 350 u16 entry = le16_to_cpu(map[HEADER_MAP_OFFSET + i]); 351 u_long addr; 352 353 354 if (entry == SECTOR_FREE || entry == SECTOR_DELETED) 355 continue; 356 357 if (entry == SECTOR_ZERO) 358 entry = 0; 359 360 /* already warned about and ignored in build_block_map() */ 361 if (entry >= part->sector_count) 362 continue; 363 364 addr = part->blocks[block_no].offset + 365 (i + part->header_sectors_per_block) * SECTOR_SIZE; 366 367 if (*old_sector == addr) { 368 *old_sector = -1; 369 if (!part->blocks[block_no].used_sectors--) { 370 rc = erase_block(part, block_no); 371 break; 372 } 373 continue; 374 } 375 rc = mtd_read(part->mbd.mtd, addr, SECTOR_SIZE, &retlen, 376 sector_data); 377 378 if (!rc && retlen != SECTOR_SIZE) 379 rc = -EIO; 380 381 if (rc) { 382 printk(KERN_ERR PREFIX "'%s': Unable to " 383 "read sector for relocation\n", 384 part->mbd.mtd->name); 385 386 goto err; 387 } 388 389 rc = rfd_ftl_writesect((struct mtd_blktrans_dev*)part, 390 entry, sector_data); 391 392 if (rc) 393 goto err; 394 } 395 396 err: 397 kfree(map); 398 err2: 399 kfree(sector_data); 400 err3: 401 part->is_reclaiming = 0; 402 403 return rc; 404 } 405 406 static int reclaim_block(struct partition *part, u_long *old_sector) 407 { 408 int block, best_block, score, old_sector_block; 409 int rc; 410 411 /* we have a race if sync doesn't exist */ 412 mtd_sync(part->mbd.mtd); 413 414 score = 0x7fffffff; /* MAX_INT */ 415 best_block = -1; 416 if (*old_sector != -1) 417 old_sector_block = *old_sector / part->block_size; 418 else 419 old_sector_block = -1; 420 421 for (block=0; block<part->total_blocks; block++) { 422 int this_score; 423 424 if (block == part->reserved_block) 425 continue; 426 427 /* 428 * Postpone reclaiming if there is a free sector as 429 * more removed sectors is more efficient (have to move 430 * less). 431 */ 432 if (part->blocks[block].free_sectors) 433 return 0; 434 435 this_score = part->blocks[block].used_sectors; 436 437 if (block == old_sector_block) 438 this_score--; 439 else { 440 /* no point in moving a full block */ 441 if (part->blocks[block].used_sectors == 442 part->data_sectors_per_block) 443 continue; 444 } 445 446 this_score += part->blocks[block].erases; 447 448 if (this_score < score) { 449 best_block = block; 450 score = this_score; 451 } 452 } 453 454 if (best_block == -1) 455 return -ENOSPC; 456 457 part->current_block = -1; 458 part->reserved_block = best_block; 459 460 pr_debug("reclaim_block: reclaiming block #%d with %d used " 461 "%d free sectors\n", best_block, 462 part->blocks[best_block].used_sectors, 463 part->blocks[best_block].free_sectors); 464 465 if (part->blocks[best_block].used_sectors) 466 rc = move_block_contents(part, best_block, old_sector); 467 else 468 rc = erase_block(part, best_block); 469 470 return rc; 471 } 472 473 /* 474 * IMPROVE: It would be best to choose the block with the most deleted sectors, 475 * because if we fill that one up first it'll have the most chance of having 476 * the least live sectors at reclaim. 477 */ 478 static int find_free_block(struct partition *part) 479 { 480 int block, stop; 481 482 block = part->current_block == -1 ? 483 jiffies % part->total_blocks : part->current_block; 484 stop = block; 485 486 do { 487 if (part->blocks[block].free_sectors && 488 block != part->reserved_block) 489 return block; 490 491 if (part->blocks[block].state == BLOCK_UNUSED) 492 erase_block(part, block); 493 494 if (++block >= part->total_blocks) 495 block = 0; 496 497 } while (block != stop); 498 499 return -1; 500 } 501 502 static int find_writable_block(struct partition *part, u_long *old_sector) 503 { 504 int rc, block; 505 size_t retlen; 506 507 block = find_free_block(part); 508 509 if (block == -1) { 510 if (!part->is_reclaiming) { 511 rc = reclaim_block(part, old_sector); 512 if (rc) 513 goto err; 514 515 block = find_free_block(part); 516 } 517 518 if (block == -1) { 519 rc = -ENOSPC; 520 goto err; 521 } 522 } 523 524 rc = mtd_read(part->mbd.mtd, part->blocks[block].offset, 525 part->header_size, &retlen, 526 (u_char *)part->header_cache); 527 528 if (!rc && retlen != part->header_size) 529 rc = -EIO; 530 531 if (rc) { 532 printk(KERN_ERR PREFIX "'%s': unable to read header at " 533 "0x%lx\n", part->mbd.mtd->name, 534 part->blocks[block].offset); 535 goto err; 536 } 537 538 part->current_block = block; 539 540 err: 541 return rc; 542 } 543 544 static int mark_sector_deleted(struct partition *part, u_long old_addr) 545 { 546 int block, offset, rc; 547 u_long addr; 548 size_t retlen; 549 u16 del = cpu_to_le16(SECTOR_DELETED); 550 551 block = old_addr / part->block_size; 552 offset = (old_addr % part->block_size) / SECTOR_SIZE - 553 part->header_sectors_per_block; 554 555 addr = part->blocks[block].offset + 556 (HEADER_MAP_OFFSET + offset) * sizeof(u16); 557 rc = mtd_write(part->mbd.mtd, addr, sizeof(del), &retlen, 558 (u_char *)&del); 559 560 if (!rc && retlen != sizeof(del)) 561 rc = -EIO; 562 563 if (rc) { 564 printk(KERN_ERR PREFIX "error writing '%s' at " 565 "0x%lx\n", part->mbd.mtd->name, addr); 566 goto err; 567 } 568 if (block == part->current_block) 569 part->header_cache[offset + HEADER_MAP_OFFSET] = del; 570 571 part->blocks[block].used_sectors--; 572 573 if (!part->blocks[block].used_sectors && 574 !part->blocks[block].free_sectors) 575 rc = erase_block(part, block); 576 577 err: 578 return rc; 579 } 580 581 static int find_free_sector(const struct partition *part, const struct block *block) 582 { 583 int i, stop; 584 585 i = stop = part->data_sectors_per_block - block->free_sectors; 586 587 do { 588 if (le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]) 589 == SECTOR_FREE) 590 return i; 591 592 if (++i == part->data_sectors_per_block) 593 i = 0; 594 } 595 while(i != stop); 596 597 return -1; 598 } 599 600 static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, ulong *old_addr) 601 { 602 struct partition *part = container_of(dev, struct partition, mbd); 603 struct block *block; 604 u_long addr; 605 int i; 606 int rc; 607 size_t retlen; 608 u16 entry; 609 610 if (part->current_block == -1 || 611 !part->blocks[part->current_block].free_sectors) { 612 613 rc = find_writable_block(part, old_addr); 614 if (rc) 615 goto err; 616 } 617 618 block = &part->blocks[part->current_block]; 619 620 i = find_free_sector(part, block); 621 622 if (i < 0) { 623 rc = -ENOSPC; 624 goto err; 625 } 626 627 addr = (i + part->header_sectors_per_block) * SECTOR_SIZE + 628 block->offset; 629 rc = mtd_write(part->mbd.mtd, addr, SECTOR_SIZE, &retlen, 630 (u_char *)buf); 631 632 if (!rc && retlen != SECTOR_SIZE) 633 rc = -EIO; 634 635 if (rc) { 636 printk(KERN_ERR PREFIX "error writing '%s' at 0x%lx\n", 637 part->mbd.mtd->name, addr); 638 goto err; 639 } 640 641 part->sector_map[sector] = addr; 642 643 entry = cpu_to_le16(sector == 0 ? SECTOR_ZERO : sector); 644 645 part->header_cache[i + HEADER_MAP_OFFSET] = entry; 646 647 addr = block->offset + (HEADER_MAP_OFFSET + i) * sizeof(u16); 648 rc = mtd_write(part->mbd.mtd, addr, sizeof(entry), &retlen, 649 (u_char *)&entry); 650 651 if (!rc && retlen != sizeof(entry)) 652 rc = -EIO; 653 654 if (rc) { 655 printk(KERN_ERR PREFIX "error writing '%s' at 0x%lx\n", 656 part->mbd.mtd->name, addr); 657 goto err; 658 } 659 block->used_sectors++; 660 block->free_sectors--; 661 662 err: 663 return rc; 664 } 665 666 static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf) 667 { 668 struct partition *part = container_of(dev, struct partition, mbd); 669 u_long old_addr; 670 int i; 671 int rc = 0; 672 673 pr_debug("rfd_ftl_writesect(sector=0x%lx)\n", sector); 674 675 if (part->reserved_block == -1) { 676 rc = -EACCES; 677 goto err; 678 } 679 680 if (sector >= part->sector_count) { 681 rc = -EIO; 682 goto err; 683 } 684 685 old_addr = part->sector_map[sector]; 686 687 for (i=0; i<SECTOR_SIZE; i++) { 688 if (!buf[i]) 689 continue; 690 691 rc = do_writesect(dev, sector, buf, &old_addr); 692 if (rc) 693 goto err; 694 break; 695 } 696 697 if (i == SECTOR_SIZE) 698 part->sector_map[sector] = -1; 699 700 if (old_addr != -1) 701 rc = mark_sector_deleted(part, old_addr); 702 703 err: 704 return rc; 705 } 706 707 static int rfd_ftl_discardsect(struct mtd_blktrans_dev *dev, 708 unsigned long sector, unsigned int nr_sects) 709 { 710 struct partition *part = container_of(dev, struct partition, mbd); 711 u_long addr; 712 int rc; 713 714 while (nr_sects) { 715 if (sector >= part->sector_count) 716 return -EIO; 717 718 addr = part->sector_map[sector]; 719 720 if (addr != -1) { 721 rc = mark_sector_deleted(part, addr); 722 if (rc) 723 return rc; 724 725 part->sector_map[sector] = -1; 726 } 727 728 sector++; 729 nr_sects--; 730 } 731 732 return 0; 733 } 734 735 static int rfd_ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo) 736 { 737 struct partition *part = container_of(dev, struct partition, mbd); 738 739 geo->heads = 1; 740 geo->sectors = SECTORS_PER_TRACK; 741 geo->cylinders = part->cylinders; 742 743 return 0; 744 } 745 746 static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) 747 { 748 struct partition *part; 749 750 if ((mtd->type != MTD_NORFLASH && mtd->type != MTD_RAM) || 751 mtd->size > UINT_MAX) 752 return; 753 754 part = kzalloc_obj(struct partition); 755 if (!part) 756 return; 757 758 part->mbd.mtd = mtd; 759 760 if (block_size) 761 part->block_size = block_size; 762 else { 763 if (!mtd->erasesize) { 764 printk(KERN_WARNING PREFIX "please provide block_size"); 765 goto out; 766 } else 767 part->block_size = mtd->erasesize; 768 } 769 770 if (scan_header(part) == 0) { 771 part->mbd.size = part->sector_count; 772 part->mbd.tr = tr; 773 part->mbd.devnum = -1; 774 if (!(mtd->flags & MTD_WRITEABLE)) 775 part->mbd.readonly = 1; 776 else if (part->errors) { 777 printk(KERN_WARNING PREFIX "'%s': errors found, " 778 "setting read-only\n", mtd->name); 779 part->mbd.readonly = 1; 780 } 781 782 printk(KERN_INFO PREFIX "name: '%s' type: %d flags %x\n", 783 mtd->name, mtd->type, mtd->flags); 784 785 if (!add_mtd_blktrans_dev(&part->mbd)) 786 return; 787 } 788 out: 789 kfree(part); 790 } 791 792 static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev) 793 { 794 struct partition *part = container_of(dev, struct partition, mbd); 795 int i; 796 797 for (i=0; i<part->total_blocks; i++) { 798 pr_debug("rfd_ftl_remove_dev:'%s': erase unit #%02d: %d erases\n", 799 part->mbd.mtd->name, i, part->blocks[i].erases); 800 } 801 802 vfree(part->sector_map); 803 kfree(part->header_cache); 804 kfree(part->blocks); 805 del_mtd_blktrans_dev(&part->mbd); 806 } 807 808 static struct mtd_blktrans_ops rfd_ftl_tr = { 809 .name = "rfd", 810 .major = RFD_FTL_MAJOR, 811 .part_bits = PART_BITS, 812 .blksize = SECTOR_SIZE, 813 814 .readsect = rfd_ftl_readsect, 815 .writesect = rfd_ftl_writesect, 816 .discard = rfd_ftl_discardsect, 817 .getgeo = rfd_ftl_getgeo, 818 .add_mtd = rfd_ftl_add_mtd, 819 .remove_dev = rfd_ftl_remove_dev, 820 .owner = THIS_MODULE, 821 }; 822 823 module_mtd_blktrans(rfd_ftl_tr); 824 825 MODULE_LICENSE("GPL"); 826 MODULE_AUTHOR("Sean Young <sean@mess.org>"); 827 MODULE_DESCRIPTION("Support code for RFD Flash Translation Layer, " 828 "used by General Software's Embedded BIOS"); 829 830