#include #include #include #include #include #if defined (BIOS) # include #elif defined (UEFI) # include # include #endif #include #include #include #include #include #define DEFAULT_FASTEST_XFER_SIZE 64 #define MAX_FASTEST_XFER_SIZE 512 #define MAX_VOLUMES 64 #if defined (BIOS) struct bios_drive_params { uint16_t buf_size; uint16_t info_flags; uint32_t cyl; uint32_t heads; uint32_t sects; uint64_t lba_count; uint16_t bytes_per_sect; uint32_t edd; } __attribute__((packed)); struct dap { uint16_t size; uint16_t count; uint16_t offset; uint16_t segment; uint64_t lba; }; #define XFER_BUF_SIZE (xfer_sizes[SIZEOF_ARRAY(xfer_sizes) - 1] * 512) static const size_t xfer_sizes[] = { 1, 2, 4, 8, 16, 24, 32, 48, 64 }; static uint8_t *xfer_buf = NULL; static size_t fastest_xfer_size(struct volume *volume) { struct dap dap = {0}; if (xfer_buf == NULL) xfer_buf = conv_mem_alloc(XFER_BUF_SIZE); size_t fastest_size = 1; uint64_t last_speed = (uint64_t)-1; for (size_t i = 0; i < SIZEOF_ARRAY(xfer_sizes); i++) { if (xfer_sizes[i] * volume->sector_size > XFER_BUF_SIZE) { break; } dap.size = 16; dap.count = xfer_sizes[i]; dap.segment = rm_seg(xfer_buf); dap.offset = rm_off(xfer_buf); dap.lba = 0; uint64_t start_timestamp = rdtsc(); for (size_t j = 0; j < XFER_BUF_SIZE / 512; j += xfer_sizes[i]) { struct rm_regs r = {0}; r.eax = 0x4200; r.edx = volume->drive; r.esi = (uint32_t)rm_off(&dap); r.ds = rm_seg(&dap); rm_int(0x13, &r, &r); if (r.eflags & EFLAGS_CF) { int ah = (r.eax >> 8) & 0xff; print("Disk error %x. Drive %x", ah, volume->drive); return 8; } dap.lba += xfer_sizes[i]; } uint64_t end_timestamp = rdtsc(); uint64_t speed = end_timestamp - start_timestamp; if (speed < last_speed) { last_speed = speed; fastest_size = xfer_sizes[i]; } } return fastest_size; } int disk_read_sectors(struct volume *volume, void *buf, uint64_t block, size_t count) { struct dap dap = {0}; if (count * volume->sector_size > XFER_BUF_SIZE) panic(false, "XFER"); if (xfer_buf == NULL) xfer_buf = conv_mem_alloc(XFER_BUF_SIZE); dap.size = 16; dap.count = count; dap.segment = rm_seg(xfer_buf); dap.offset = rm_off(xfer_buf); dap.lba = block; struct rm_regs r = {0}; r.eax = 0x4200; r.edx = volume->drive; r.esi = (uint32_t)rm_off(&dap); r.ds = rm_seg(&dap); rm_int(0x13, &r, &r); if (r.eflags & EFLAGS_CF) { return DISK_FAILURE; } if (buf != NULL) memcpy(buf, xfer_buf, count * volume->sector_size); return DISK_SUCCESS; } static int disk_write_sectors(struct volume *volume, void *buf, uint64_t block, size_t count) { struct dap dap = {0}; if (count * volume->sector_size > XFER_BUF_SIZE) panic(false, "XFER"); if (xfer_buf == NULL) xfer_buf = conv_mem_alloc(XFER_BUF_SIZE); dap.size = 16; dap.count = count; dap.segment = rm_seg(xfer_buf); dap.offset = rm_off(xfer_buf); dap.lba = block; struct rm_regs r = {0}; r.eax = 0x4301; r.edx = volume->drive; r.esi = (uint32_t)rm_off(&dap); r.ds = rm_seg(&dap); if (buf != NULL) memcpy(xfer_buf, buf, count * volume->sector_size); rm_int(0x13, &r, &r); if (r.eflags & EFLAGS_CF) { return DISK_FAILURE; } return DISK_SUCCESS; } static bool detect_sector_size(struct volume *volume) { struct dap dap = {0}; if (xfer_buf == NULL) xfer_buf = conv_mem_alloc(XFER_BUF_SIZE); dap.size = 16; dap.count = 1; dap.segment = rm_seg(xfer_buf); dap.offset = rm_off(xfer_buf); dap.lba = 0; struct rm_regs r = {0}; r.eax = 0x4200; r.edx = volume->drive; r.esi = (uint32_t)rm_off(&dap); r.ds = rm_seg(&dap); struct rm_regs r_copy = r; struct dap dap_copy = dap; memset(xfer_buf, 0, XFER_BUF_SIZE); rm_int(0x13, &r, &r); if (r.eflags & EFLAGS_CF) { return false; } size_t sector_size_a = 0; for (long i = XFER_BUF_SIZE - 1; i >= 0; i--) { if (xfer_buf[i] != 0) { sector_size_a = i + 1; break; } } r = r_copy; dap = dap_copy; memset(xfer_buf, 0xff, XFER_BUF_SIZE); rm_int(0x13, &r, &r); if (r.eflags & EFLAGS_CF) { return false; } size_t sector_size_b = 0; for (long i = XFER_BUF_SIZE - 1; i >= 0; i--) { if (xfer_buf[i] != 0xff) { sector_size_b = i + 1; break; } } volume->sector_size = sector_size_a > sector_size_b ? sector_size_a : sector_size_b; return true; } void disk_create_index(void) { volume_index = ext_mem_alloc(sizeof(struct volume) * MAX_VOLUMES); int optical_indices = 1, hdd_indices = 1; for (uint8_t drive = 0x80; drive < 0xf0; drive++) { if (volume_index_i == MAX_VOLUMES) { print("WARNING: TOO MANY VOLUMES!"); break; } struct rm_regs r = {0}; struct bios_drive_params drive_params; r.eax = 0x4800; r.edx = drive; r.ds = rm_seg(&drive_params); r.esi = rm_off(&drive_params); drive_params.buf_size = sizeof(struct bios_drive_params); rm_int(0x13, &r, &r); if (r.eflags & EFLAGS_CF) { continue; } struct volume *block = ext_mem_alloc(sizeof(struct volume)); block->drive = drive; block->partition = 0; block->first_sect = 0; block->sect_count = drive_params.lba_count; block->max_partition = -1; if (!detect_sector_size(block)) { continue; } if (disk_read_sectors(block, xfer_buf, 0, 1) != DISK_SUCCESS) { continue; } block->is_optical = disk_write_sectors(block, xfer_buf, 0, 1) != DISK_SUCCESS; if (block->is_optical) { block->index = optical_indices++; } else { block->index = hdd_indices++; } block->fastest_xfer_size = fastest_xfer_size(block); if (gpt_get_guid(&block->guid, block)) { block->guid_valid = true; } volume_index[volume_index_i++] = block; for (int part = 0; ; part++) { struct volume *p = ext_mem_alloc(sizeof(struct volume)); int ret = part_get(p, block, part); if (ret == END_OF_TABLE || ret == INVALID_TABLE) break; if (ret == NO_PARTITION) continue; volume_index[volume_index_i++] = p; block->max_partition++; } } } #endif #if defined (UEFI) int disk_read_sectors(struct volume *volume, void *buf, uint64_t block, size_t count) { EFI_STATUS status; status = volume->block_io->ReadBlocks(volume->block_io, volume->block_io->Media->MediaId, block, count * volume->sector_size, buf); switch (status) { case EFI_SUCCESS: return DISK_SUCCESS; case EFI_NO_MEDIA: return DISK_NO_MEDIA; default: return DISK_FAILURE; } } static alignas(4096) uint8_t unique_sector_pool[4096]; struct volume *disk_volume_from_efi_handle(EFI_HANDLE efi_handle) { EFI_STATUS status; EFI_GUID block_io_guid = BLOCK_IO_PROTOCOL; EFI_BLOCK_IO *block_io = NULL; status = gBS->HandleProtocol(efi_handle, &block_io_guid, (void **)&block_io); if (status) { return NULL; } block_io->Media->WriteCaching = false; for (size_t i = 0; i < volume_index_i; i++) { if (volume_index[i]->unique_sector_valid == false) { continue; } if (volume_index[i]->unique_sector % block_io->Media->BlockSize) { continue; } size_t unique_sector = volume_index[i]->unique_sector / block_io->Media->BlockSize; status = block_io->ReadBlocks(block_io, block_io->Media->MediaId, unique_sector, 4096, unique_sector_pool); if (status != 0) { continue; } uint8_t b2b[BLAKE2B_OUT_BYTES]; blake2b(b2b, unique_sector_pool, 4096); if (memcmp(b2b, volume_index[i]->unique_sector_b2b, BLAKE2B_OUT_BYTES) == 0) { return volume_index[i]; } } // Fallback to read-back method EFI_GUID disk_io_guid = DISK_IO_PROTOCOL; EFI_DISK_IO *disk_io = NULL; status = gBS->HandleProtocol(efi_handle, &disk_io_guid, (void **)&disk_io); if (status) { disk_io = NULL; } uint64_t signature = rand64(); uint64_t new_signature; do { new_signature = rand64(); } while (new_signature == signature); uint64_t orig; if (disk_io != NULL) { status = disk_io->ReadDisk(disk_io, block_io->Media->MediaId, 0, sizeof(uint64_t), &orig); } else { status = block_io->ReadBlocks(block_io, block_io->Media->MediaId, 0, 4096, unique_sector_pool); orig = *(uint64_t *)unique_sector_pool; } if (status) { return NULL; } if (disk_io != NULL) { status = disk_io->WriteDisk(disk_io, block_io->Media->MediaId, 0, sizeof(uint64_t), &signature); } else { *(uint64_t *)unique_sector_pool = signature; status = block_io->WriteBlocks(block_io, block_io->Media->MediaId, 0, 4096, unique_sector_pool); } if (status) { return NULL; } struct volume *ret = NULL; for (size_t i = 0; i < volume_index_i; i++) { uint64_t compare; EFI_DISK_IO *cur_disk_io = NULL; status = gBS->HandleProtocol(volume_index[i]->efi_handle, &disk_io_guid, (void **)&cur_disk_io); if (status) { cur_disk_io = NULL; } if (cur_disk_io != NULL) { status = cur_disk_io->ReadDisk(cur_disk_io, volume_index[i]->block_io->Media->MediaId, volume_index[i]->first_sect * 512, sizeof(uint64_t), &compare); } else { status = volume_index[i]->block_io->ReadBlocks(volume_index[i]->block_io, volume_index[i]->block_io->Media->MediaId, (volume_index[i]->first_sect * 512) / volume_index[i]->sector_size, 4096, unique_sector_pool); compare = *(uint64_t *)unique_sector_pool; } if (status) { continue; } if (compare == signature) { // Double check if (disk_io != NULL) { status = disk_io->WriteDisk(disk_io, block_io->Media->MediaId, 0, sizeof(uint64_t), &new_signature); } else { *(uint64_t *)unique_sector_pool = new_signature; status = block_io->WriteBlocks(block_io, block_io->Media->MediaId, 0, 4096, unique_sector_pool); } if (status) { break; } if (cur_disk_io != NULL) { status = cur_disk_io->ReadDisk(cur_disk_io, volume_index[i]->block_io->Media->MediaId, volume_index[i]->first_sect * 512, sizeof(uint64_t), &compare); } else { status = volume_index[i]->block_io->ReadBlocks(volume_index[i]->block_io, volume_index[i]->block_io->Media->MediaId, (volume_index[i]->first_sect * 512) / volume_index[i]->sector_size, 4096, unique_sector_pool); compare = *(uint64_t *)unique_sector_pool; } if (status) { continue; } if (compare == new_signature) { ret = volume_index[i]; break; } if (disk_io != NULL) { status = disk_io->WriteDisk(disk_io, block_io->Media->MediaId, 0, sizeof(uint64_t), &signature); } else { *(uint64_t *)unique_sector_pool = signature; status = block_io->WriteBlocks(block_io, block_io->Media->MediaId, 0, 4096, unique_sector_pool); } if (status) { break; } } } if (disk_io != NULL) { status = disk_io->WriteDisk(disk_io, block_io->Media->MediaId, 0, sizeof(uint64_t), &orig); } else { *(uint64_t *)unique_sector_pool = orig; status = block_io->WriteBlocks(block_io, block_io->Media->MediaId, 0, 4096, unique_sector_pool); } if (status) { return NULL; } if (ret != NULL) { return ret; } return NULL; } static struct volume *volume_by_unique_sector(uint64_t sect, void *b2b) { for (size_t i = 0; i < volume_index_i; i++) { if (volume_index[i]->unique_sector_valid == false) { continue; } if (volume_index[i]->unique_sector == sect && memcmp(volume_index[i]->unique_sector_b2b, b2b, BLAKE2B_OUT_BYTES) == 0) { return volume_index[i]; } } return NULL; } #define UNIQUE_SECT_MAX_SEARCH_RANGE 0x1000 static void find_unique_sectors(void) { EFI_STATUS status; for (size_t i = 0; i < volume_index_i; i++) { for (size_t j = 0; j < UNIQUE_SECT_MAX_SEARCH_RANGE; j++) { if ((volume_index[i]->first_sect * 512) % volume_index[i]->block_io->Media->BlockSize) { break; } size_t first_sect = (volume_index[i]->first_sect * 512) / volume_index[i]->block_io->Media->BlockSize; status = volume_index[i]->block_io->ReadBlocks( volume_index[i]->block_io, volume_index[i]->block_io->Media->MediaId, first_sect + j, 4096, unique_sector_pool); if (status != 0) { break; } uint8_t b2b[BLAKE2B_OUT_BYTES]; blake2b(b2b, unique_sector_pool, 4096); uint64_t uniq = (uint64_t)j * volume_index[i]->block_io->Media->BlockSize; if (volume_by_unique_sector(uniq, b2b) == NULL) { volume_index[i]->unique_sector_valid = true; volume_index[i]->unique_sector = uniq; memcpy(volume_index[i]->unique_sector_b2b, b2b, BLAKE2B_OUT_BYTES); break; } } } } static void find_part_handles(EFI_HANDLE *handles, size_t handle_count) { for (size_t i = 0; i < handle_count; i++) { struct volume *vol = disk_volume_from_efi_handle(handles[i]); if (vol == NULL) { continue; } vol->efi_part_handle = handles[i]; } } void disk_create_index(void) { EFI_STATUS status; EFI_HANDLE tmp_handles[1]; EFI_GUID block_io_guid = BLOCK_IO_PROTOCOL; EFI_HANDLE *handles = tmp_handles; UINTN handles_size = sizeof(EFI_HANDLE); status = gBS->LocateHandle(ByProtocol, &block_io_guid, NULL, &handles_size, handles); if (status != EFI_BUFFER_TOO_SMALL && status != EFI_SUCCESS) { goto fail; } handles = ext_mem_alloc(handles_size); status = gBS->LocateHandle(ByProtocol, &block_io_guid, NULL, &handles_size, handles); if (status != EFI_SUCCESS) { fail: panic(false, "LocateHandle for BLOCK_IO_PROTOCOL failed. Machine not supported by Limine UEFI."); } volume_index = ext_mem_alloc(sizeof(struct volume) * MAX_VOLUMES); int optical_indices = 1, hdd_indices = 1; size_t handle_count = handles_size / sizeof(EFI_HANDLE); for (size_t i = 0; i < handle_count; i++) { if (volume_index_i == MAX_VOLUMES) { print("WARNING: TOO MANY VOLUMES!"); break; } EFI_GUID disk_io_guid = DISK_IO_PROTOCOL; EFI_DISK_IO *disk_io = NULL; status = gBS->HandleProtocol(handles[i], &disk_io_guid, (void **)&disk_io); if (status) { disk_io = NULL; } EFI_BLOCK_IO *drive = NULL; status = gBS->HandleProtocol(handles[i], &block_io_guid, (void **)&drive); if (status != 0 || drive == NULL || drive->Media->LastBlock == 0) continue; if (drive->Media->LogicalPartition) continue; drive->Media->WriteCaching = false; uint64_t orig; if (disk_io != NULL) { status = disk_io->ReadDisk(disk_io, drive->Media->MediaId, 0, sizeof(uint64_t), &orig); } else { status = drive->ReadBlocks(drive, drive->Media->MediaId, 0, 4096, unique_sector_pool); } if (status) { continue; } if (disk_io != NULL) { status = disk_io->WriteDisk(disk_io, drive->Media->MediaId, 0, sizeof(uint64_t), &orig); } else { status = drive->WriteBlocks(drive, drive->Media->MediaId, 0, 4096, unique_sector_pool); } struct volume *block = ext_mem_alloc(sizeof(struct volume)); if (status || drive->Media->ReadOnly) { block->index = optical_indices++; block->is_optical = true; } else { block->index = hdd_indices++; } block->efi_handle = handles[i]; block->block_io = drive; block->partition = 0; block->sector_size = drive->Media->BlockSize; block->first_sect = 0; block->sect_count = drive->Media->LastBlock + 1; block->max_partition = -1; if (drive->Revision >= EFI_BLOCK_IO_PROTOCOL_REVISION3) { block->fastest_xfer_size = drive->Media->OptimalTransferLengthGranularity; } if (block->fastest_xfer_size == 0) { block->fastest_xfer_size = DEFAULT_FASTEST_XFER_SIZE; } else if (block->fastest_xfer_size >= MAX_FASTEST_XFER_SIZE) { block->fastest_xfer_size = MAX_FASTEST_XFER_SIZE; } if (gpt_get_guid(&block->guid, block)) { block->guid_valid = true; } volume_index[volume_index_i++] = block; for (int part = 0; ; part++) { struct volume *p = ext_mem_alloc(sizeof(struct volume)); int ret = part_get(p, block, part); if (ret == END_OF_TABLE || ret == INVALID_TABLE) break; if (ret == NO_PARTITION) continue; volume_index[volume_index_i++] = p; block->max_partition++; } } find_unique_sectors(); find_part_handles(handles, handle_count); pmm_free(handles, handles_size); } #endif