/* * fs/partitions/aix.c * * Copyright (C) 2012-2013 Philippe De Muyter <phdm@macqel.be> */ #include "check.h" #include "aix.h" struct lvm_rec { char lvm_id[4]; /* "_LVM" */ char reserved4[16]; __be32 lvmarea_len; __be32 vgda_len; __be32 vgda_psn[2]; char reserved36[10]; __be16 pp_size; /* log2(pp_size) */ char reserved46[12]; __be16 version; }; struct vgda { __be32 secs; __be32 usec; char reserved8[16]; __be16 numlvs; __be16 maxlvs; __be16 pp_size; __be16 numpvs; __be16 total_vgdas; __be16 vgda_size; }; struct lvd { __be16 lv_ix; __be16 res2; __be16 res4; __be16 maxsize; __be16 lv_state; __be16 mirror; __be16 mirror_policy; __be16 num_lps; __be16 res10[8]; }; struct lvname { char name[64]; }; struct ppe { __be16 lv_ix; unsigned short res2; unsigned short res4; __be16 lp_ix; unsigned short res8[12]; }; struct pvd { char reserved0[16]; __be16 pp_count; char reserved18[2]; __be32 psn_part1; char reserved24[8]; struct ppe ppe[1016]; }; #define LVM_MAXLVS 256 /** * last_lba(): return number of last logical block of device * @bdev: block device * * Description: Returns last LBA value on success, 0 on error. * This is stored (by sd and ide-geometry) in * the part[0] entry for this disk, and is the number of * physical sectors available on the disk. */ static u64 last_lba(struct block_device *bdev) { if (!bdev || !bdev->bd_inode) return 0; return (bdev->bd_inode->i_size >> 9) - 1ULL; } /** * read_lba(): Read bytes from disk, starting at given LBA * @state * @lba * @buffer * @count * * Description: Reads @count bytes from @state->bdev into @buffer. * Returns number of bytes read on success, 0 on error. */ static size_t read_lba(struct parsed_partitions *state, u64 lba, u8 *buffer, size_t count) { size_t totalreadcount = 0; if (!buffer || lba + count / 512 > last_lba(state->bdev)) return 0; while (count) { int copied = 512; Sector sect; unsigned char *data = read_part_sector(state, lba++, §); if (!data) break; if (copied > count) copied = count; memcpy(buffer, data, copied); put_dev_sector(sect); buffer += copied; totalreadcount += copied; count -= copied; } return totalreadcount; } /** * alloc_pvd(): reads physical volume descriptor * @state * @lba * * Description: Returns pvd on success, NULL on error. * Allocates space for pvd and fill it with disk blocks at @lba * Notes: remember to free pvd when you're done! */ static struct pvd *alloc_pvd(struct parsed_partitions *state, u32 lba) { size_t count = sizeof(struct pvd); struct pvd *p; p = kmalloc(count, GFP_KERNEL); if (!p) return NULL; if (read_lba(state, lba, (u8 *) p, count) < count) { kfree(p); return NULL; } return p; } /** * alloc_lvn(): reads logical volume names * @state * @lba * * Description: Returns lvn on success, NULL on error. * Allocates space for lvn and fill it with disk blocks at @lba * Notes: remember to free lvn when you're done! */ static struct lvname *alloc_lvn(struct parsed_partitions *state, u32 lba) { size_t count = sizeof(struct lvname) * LVM_MAXLVS; struct lvname *p; p = kmalloc(count, GFP_KERNEL); if (!p) return NULL; if (read_lba(state, lba, (u8 *) p, count) < count) { kfree(p); return NULL; } return p; } int aix_partition(struct parsed_partitions *state) { int ret = 0; Sector sect; unsigned char *d; u32 pp_bytes_size; u32 pp_blocks_size = 0; u32 vgda_sector = 0; u32 vgda_len = 0; int numlvs = 0; struct pvd *pvd; struct lv_info { unsigned short pps_per_lv; unsigned short pps_found; unsigned char lv_is_contiguous; } *lvip; struct lvname *n = NULL; d = read_part_sector(state, 7, §); if (d) { struct lvm_rec *p = (struct lvm_rec *)d; u16 lvm_version = be16_to_cpu(p->version); char tmp[64]; if (lvm_version == 1) { int pp_size_log2 = be16_to_cpu(p->pp_size); pp_bytes_size = 1 << pp_size_log2; pp_blocks_size = pp_bytes_size / 512; snprintf(tmp, sizeof(tmp), " AIX LVM header version %u found\n", lvm_version); vgda_len = be32_to_cpu(p->vgda_len); vgda_sector = be32_to_cpu(p->vgda_psn[0]); } else { snprintf(tmp, sizeof(tmp), " unsupported AIX LVM version %d found\n", lvm_version); } strlcat(state->pp_buf, tmp, PAGE_SIZE); put_dev_sector(sect); } if (vgda_sector && (d = read_part_sector(state, vgda_sector, §))) { struct vgda *p = (struct vgda *)d; numlvs = be16_to_cpu(p->numlvs); put_dev_sector(sect); } lvip = kcalloc(state->limit, sizeof(struct lv_info), GFP_KERNEL); if (!lvip) return 0; if (numlvs && (d = read_part_sector(state, vgda_sector + 1, §))) { struct lvd *p = (struct lvd *)d; int i; n = alloc_lvn(state, vgda_sector + vgda_len - 33); if (n) { int foundlvs = 0; for (i = 0; foundlvs < numlvs && i < state->limit; i += 1) { lvip[i].pps_per_lv = be16_to_cpu(p[i].num_lps); if (lvip[i].pps_per_lv) foundlvs += 1; } } put_dev_sector(sect); } pvd = alloc_pvd(state, vgda_sector + 17); if (pvd) { int numpps = be16_to_cpu(pvd->pp_count); int psn_part1 = be32_to_cpu(pvd->psn_part1); int i; int cur_lv_ix = -1; int next_lp_ix = 1; int lp_ix; for (i = 0; i < numpps; i += 1) { struct ppe *p = pvd->ppe + i; unsigned int lv_ix; lp_ix = be16_to_cpu(p->lp_ix); if (!lp_ix) { next_lp_ix = 1; continue; } lv_ix = be16_to_cpu(p->lv_ix) - 1; if (lv_ix >= state->limit) { cur_lv_ix = -1; continue; } lvip[lv_ix].pps_found += 1; if (lp_ix == 1) { cur_lv_ix = lv_ix; next_lp_ix = 1; } else if (lv_ix != cur_lv_ix || lp_ix != next_lp_ix) { next_lp_ix = 1; continue; } if (lp_ix == lvip[lv_ix].pps_per_lv) { char tmp[70]; put_partition(state, lv_ix + 1, (i + 1 - lp_ix) * pp_blocks_size + psn_part1, lvip[lv_ix].pps_per_lv * pp_blocks_size); snprintf(tmp, sizeof(tmp), " <%s>\n", n[lv_ix].name); strlcat(state->pp_buf, tmp, PAGE_SIZE); lvip[lv_ix].lv_is_contiguous = 1; ret = 1; next_lp_ix = 1; } else next_lp_ix += 1; } for (i = 0; i < state->limit; i += 1) if (lvip[i].pps_found && !lvip[i].lv_is_contiguous) pr_warn("partition %s (%u pp's found) is " "not contiguous\n", n[i].name, lvip[i].pps_found); kfree(pvd); } kfree(n); kfree(lvip); return ret; }