diff options
Diffstat (limited to 'linux/dev/drivers/block/genhd.c')
-rw-r--r-- | linux/dev/drivers/block/genhd.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/linux/dev/drivers/block/genhd.c b/linux/dev/drivers/block/genhd.c index f38654b6..23c1e120 100644 --- a/linux/dev/drivers/block/genhd.c +++ b/linux/dev/drivers/block/genhd.c @@ -29,6 +29,11 @@ #endif #include <linux/hdreg.h> #include <alloca.h> +#ifdef CONFIG_GPT_DISKLABEL +#include <linux/blkdev.h> +#include <kern/kalloc.h> +#include <stddef.h> +#endif #include <asm/system.h> @@ -276,6 +281,246 @@ static void bsd_disklabel_partition(struct gendisk *hd, kdev_t dev) } #endif +#ifdef CONFIG_GPT_DISKLABEL +/* + * Compute a CRC32 but treat some range as if it were zeros. + * + * Straight copy of ether_crc_le() from linux/pcmcia-cs/include/linux/crc32.h, except for the first if/else + */ +static inline unsigned ether_crc_le_hole(int length, unsigned char *data, unsigned int skip_offset, unsigned int skip_length) +{ + static unsigned const ethernet_polynomial_le = 0xedb88320U; + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + if(skip_offset == 0 && skip_length-- != 0) + current_octet = 0; + else + --skip_offset; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + +/* + * Read in a full GPT array into a contiguous chunk, allocates *PP_S bytes into *PP. + * + * An attempt to do as few round-trips as possible is made by reading a PAGE_SIZE at a time, + * since that's the bread() maximum. + */ +static int gpt_read_part_table(void **pp, vm_size_t *pp_s, kdev_t dev, int bsize, __u64 first_sector, struct gpt_disklabel_header *h) +{ + __u64 lba = first_sector + h->h_part_table_lba; + __u32 bytes_left = *pp_s = h->h_part_table_len * h->h_part_table_entry_size; + struct buffer_head *bh; + void *cur = *pp = (void *)kalloc(*pp_s); + if (!cur) { + printk(" unable to allocate GPT partition table buffer"); + return -2; + } + + while (bytes_left) { + unsigned bytes_to_read = MIN(bytes_left, PAGE_SIZE); + if(!(bh = bread(dev, lba, bytes_to_read))) { + printk(" unable to read partition table array"); + return -3; + } + + memcpy(cur, bh->b_data, bytes_to_read); + cur += bytes_to_read; + bytes_left -= bytes_to_read; + lba += PAGE_SIZE / bsize; + + brelse(bh); + } + + return 0; +} + +/* + * Sequence from section 5.3.2 of spec 2.8A: + * signature, CRC, lba_current matches, partition table CRC, primary: check backup for validity + */ +static int gpt_verify_header(void **pp, vm_size_t *pp_s, kdev_t dev, int bsize, __u64 first_sector, __u64 lba, struct gpt_disklabel_header *h) +{ + int res; + __u32 crc; + + if (memcmp(h->h_signature, GPT_SIGNATURE, strlen(GPT_SIGNATURE)) != 0) { + printk(" bad GPT signature \"%c%c%c%c%c%c%c%c\";", + h->h_signature[0], h->h_signature[1], h->h_signature[2], h->h_signature[3], + h->h_signature[4], h->h_signature[5], h->h_signature[6], h->h_signature[7]); + return 1; + } + + crc = ether_crc_le_hole(h->h_header_size, (void *)h, + offsetof(struct gpt_disklabel_header, h_header_crc), sizeof(h->h_header_crc)) ^ ~0; + if (crc != h->h_header_crc) { + printk(" bad header CRC: %x != %x;", crc, h->h_header_crc); + return 2; + } + + if (h->h_lba_current != lba) { + printk(" current LBA mismatch: %lld != %lld;", h->h_lba_current, lba); + return 3; + } + + if (*pp) { + kfree((vm_offset_t)*pp, *pp_s); + *pp = NULL; + } + if ((res = gpt_read_part_table(pp, pp_s, dev, bsize, first_sector, h))) + return res; + + crc = ether_crc_le_hole(*pp_s, *pp, 0, 0) ^ ~0; + if (crc != h->h_part_table_crc) { + printk(" bad partition table CRC: %x != %x;", crc, h->h_part_table_crc); + return 4; + } + + for (int i = h->h_header_size; i < bsize; ++i) + res |= ((char*)h)[i]; + if (res) { + printk(" rest of GPT block dirty;"); + return 5; + } + + return 0; +} + +static void gpt_print_part_name(struct gpt_disklabel_part *p) +{ + for(int n = 0; n < sizeof(p->p_name) / sizeof(*p->p_name) && p->p_name[n]; ++n) + if(p->p_name[n] & ~0xFF) + printk("?"); /* Can't support all of Unicode, but don't print garbage at least... */ + else + printk("%c", p->p_name[n]); +} + +#ifdef DEBUG +static void gpt_print_guid(struct gpt_guid *guid) +{ + printk("%08X-%04X-%04X-%02X%02X-", guid->g_time_low, guid->g_time_mid, guid->g_time_high_version, guid->g_clock_sec_high, guid->g_clock_sec_low); + for (int i = 0; i < sizeof(guid->g_node_id); ++i) + printk("%02X", guid->g_node_id[i]); +} + +static void gpt_dump_header(struct gpt_disklabel_header *h) +{ + printk(" [h_signature: \"%c%c%c%c%c%c%c%c\"; ", + h->h_signature[0], h->h_signature[1], h->h_signature[2], h->h_signature[3], + h->h_signature[4], h->h_signature[5], h->h_signature[6], h->h_signature[7]); + printk("h_revision: %x; ", h->h_revision); + printk("h_header_size: %u; ", h->h_header_size); + printk("h_header_crc: %x; ", h->h_header_crc); + printk("h_reserved: %u; ", h->h_reserved); + printk("h_lba_current: %llu; ", h->h_lba_current); + printk("h_lba_backup: %llu; ", h->h_lba_backup); + printk("h_lba_usable_first: %llu; ", h->h_lba_usable_first); + printk("h_lba_usable_last: %llu; ", h->h_lba_usable_last); + printk("h_guid: "); gpt_print_guid(&h->h_guid); printk("; "); + printk("h_part_table_lba: %llu; ", h->h_part_table_lba); + printk("h_part_table_len: %u; ", h->h_part_table_len); + printk("h_part_table_crc: %x]", h->h_part_table_crc); +} + +static void gpt_dump_part(struct gpt_disklabel_part *p, int i) +{ + printk(" part#%d:[", i); + printk("p_type: "); gpt_print_guid(&p->p_type); + printk("; p_guid:"); gpt_print_guid(&p->p_guid); + printk("; p_lba_first: %llu", p->p_lba_first); + printk("; p_lba_last: %llu", p->p_lba_last); + printk("; p_attrs: %llx", p->p_attrs); + printk("; p_name: \""); gpt_print_part_name(p); printk("\"]"); +} +#else +static void gpt_dump_header(struct gpt_disklabel_header *h) {} +static void gpt_dump_part(struct gpt_disklabel_part *p, int i) {} +#endif + +static int gpt_partition(struct gendisk *hd, kdev_t dev, __u64 first_sector, int minor) +{ + struct buffer_head *bh; + struct gpt_disklabel_header *h; + void *pp = NULL; vm_size_t pp_s = 0; + int res, bsize = 512; + /* Note: this must be set by the driver; SCSI does -- + * only, in practice, it always sets this to 512, see sd_init() in sd.c */ + if (hardsect_size[MAJOR(dev)] && hardsect_size[MAJOR(dev)][MINOR(dev)]) + bsize = hardsect_size[MAJOR(dev)][MINOR(dev)]; + set_blocksize(dev,bsize); /* Must override read block size since GPT has pointers, stolen from amiga_partition(). */ + if (!(bh = bread(dev, first_sector + 1, bsize))) { + printk("unable to read GPT"); + res = -1; + goto done; + } + + h = (struct gpt_disklabel_header *)bh->b_data; + gpt_dump_header(h); + + res = gpt_verify_header(&pp, &pp_s, dev, bsize, first_sector, 1, h); + if (res < 0) + goto done; + else if (res > 0) { + printk(" main GPT dirty, trying backup at %llu;", h->h_lba_backup); + __u64 lba = h->h_lba_backup; + brelse(bh); + + if (!(bh = bread(dev, first_sector + lba, bsize))) { + printk("unable to read backup GPT"); + res = -4; + goto done; + } + + h = (struct gpt_disklabel_header *)bh->b_data; + gpt_dump_header(h); + + res = gpt_verify_header(&pp, &pp_s, dev, bsize, first_sector, lba, h); + if (res < 0) + goto done; + else if (res > 0) { + printk(" backup GPT dirty as well; cowardly refusing to continue"); + res = -5; + goto done; + } + } + + /* At least one good GPT+array */ + + for(int i = 0; i < h->h_part_table_len; ++i, ++minor) { + struct gpt_disklabel_part *p = + (struct gpt_disklabel_part *) (pp + i * h->h_part_table_entry_size); + if(memcmp(&p->p_type, &GPT_GUID_TYPE_UNUSED, sizeof(struct gpt_guid)) == 0) + continue; + gpt_dump_part(p, i); + + if (minor > hd->max_nr * hd->max_p) { + printk(" [ignoring GPT partition %d \"", i); gpt_print_part_name(p); printk("\": too many partitions (max %d)]", hd->max_p); + } else { + add_partition(hd, minor, first_sector + p->p_lba_first, p->p_lba_last - p->p_lba_first + 1); + if(p->p_name[0]) { + printk(" ("); gpt_print_part_name(p); printk(")"); + } + } + } + +done: + brelse(bh); + set_blocksize(dev,BLOCK_SIZE); + kfree((vm_offset_t)pp, pp_s); + printk("\n"); + return !res; +} +#endif + static int msdos_partition(struct gendisk *hd, kdev_t dev, unsigned long first_sector) { int i, minor = current_minor; @@ -381,6 +626,12 @@ check_table: for (i=1 ; i<=4 ; minor++,i++,p++) { if (!NR_SECTS(p)) continue; +#ifdef CONFIG_GPT_DISKLABEL + if (SYS_IND(p) == GPT_PARTITION) { + brelse(bh); + return gpt_partition(hd, dev, first_sector, minor); + } else +#endif add_partition(hd, minor, first_sector+START_SECT(p), NR_SECTS(p)); if (is_extended_partition(p)) { printk(" <"); |