/* * Copyright (c) 2015-2016 The Linux Foundation. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bootconfig.h" static struct proc_dir_entry *boot_info_dir; static struct proc_dir_entry *partname_dir[CONFIG_NUM_ALT_PARTITION]; static unsigned int num_parts; static unsigned int flash_type_emmc; struct sbl_if_dualboot_info_type_v2 *bootconfig1; struct sbl_if_dualboot_info_type_v2 *bootconfig2; static int getbinary_show(struct seq_file *m, void *v) { struct sbl_if_dualboot_info_type_v2 *sbl_info_v2; sbl_info_v2 = m->private; memcpy(m->buf + m->count, sbl_info_v2, sizeof(struct sbl_if_dualboot_info_type_v2)); m->count += sizeof(struct sbl_if_dualboot_info_type_v2); return 0; } static int getbinary_open(struct inode *inode, struct file *file) { return single_open(file, getbinary_show, PDE_DATA(inode)); } static const struct file_operations getbinary_ops = { .open = getbinary_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int part_upgradepartition_show(struct seq_file *m, void *v) { struct per_part_info *part_info_t = m->private; /* * In case of NOR\NAND, SBLs change the names of paritions in * such a way that the partition to upgrade is always suffixed * by _1. This is not the case in eMMC as paritions are read * from GPT and we have no control on it. So for eMMC we need * to check and generate the name wheres for NOR\NAND it is * always _1 SBLs should be modified not to change partition * names so that it is consistent with GPT. Till that is done * we will take care of it here. */ if (flash_type_emmc && (part_info_t->primaryboot)) seq_printf(m, "%s\n", part_info_t->name); else seq_printf(m, "%s_1\n", part_info_t->name); return 0; } static int part_upgradepartition_open(struct inode *inode, struct file *file) { return single_open(file, part_upgradepartition_show, PDE_DATA(inode)); } static const struct file_operations upgradepartition_ops = { .open = part_upgradepartition_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static ssize_t part_primaryboot_write(struct file *file, const char __user *user, size_t count, loff_t *data) { int ret; char optstr[64]; struct per_part_info *part_entry; unsigned long val; part_entry = PDE_DATA(file_inode(file)); if (count == 0 || count > sizeof(optstr)) return -EINVAL; ret = copy_from_user(optstr, user, count); if (ret) return ret; optstr[count - 1] = '\0'; ret = kstrtoul(optstr, 0, &val); if (ret) return ret; part_entry->primaryboot = val; return count; } static int part_primaryboot_show(struct seq_file *m, void *v) { struct per_part_info *part_entry; part_entry = m->private; seq_printf(m, "%x\n", part_entry->primaryboot); return 0; } static int part_primaryboot_open(struct inode *inode, struct file *file) { return single_open(file, part_primaryboot_show, PDE_DATA(inode)); } static const struct file_operations primaryboot_ops = { .open = part_primaryboot_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = part_primaryboot_write, }; struct sbl_if_dualboot_info_type_v2 *read_bootconfig_mtd( struct mtd_info *master, uint64_t offset) { size_t retlen = 0; struct sbl_if_dualboot_info_type_v2 *bootconfig_mtd; int ret; while (mtd_block_isbad(master, offset)) { offset += master->erasesize; if (offset >= master->size) { pr_alert("Bad blocks occurred while reading from \"%s\"\n", master->name); return NULL; } } bootconfig_mtd = kmalloc(sizeof(struct sbl_if_dualboot_info_type_v2), GFP_ATOMIC); if (!bootconfig_mtd) return NULL; ret = mtd_read(master, offset, sizeof(struct sbl_if_dualboot_info_type_v2), &retlen, (void *)bootconfig_mtd); if (ret < 0) { pr_alert("error occured while reading from \"%s\"\n", master->name); bootconfig_mtd = NULL; kfree(bootconfig_mtd); return NULL; } if (bootconfig_mtd->magic_start != SMEM_DUAL_BOOTINFO_MAGIC_START) { pr_alert("Magic not found in \"%s\"\n", master->name); kfree(bootconfig_mtd); return NULL; } return bootconfig_mtd; } #ifdef CONFIG_MMC struct sbl_if_dualboot_info_type_v2 *read_bootconfig_emmc(struct gendisk *disk, struct hd_struct *part) { sector_t n; Sector sect; int ret; unsigned char *data; struct sbl_if_dualboot_info_type_v2 *bootconfig_emmc; unsigned ssz; struct block_device *bdev = NULL; bdev = bdget_disk(disk, 0); if (!bdev) return NULL; bdev->bd_invalidated = 1; ret = blkdev_get(bdev, FMODE_READ , NULL); if (ret) return NULL; ssz = bdev_logical_block_size(bdev); bootconfig_emmc = kmalloc(ssz, GFP_ATOMIC); if (!bootconfig_emmc) return NULL; n = part->start_sect * (bdev_logical_block_size(bdev) / 512); data = read_dev_sector(bdev, n, §); put_dev_sector(sect); blkdev_put(bdev, FMODE_READ); if (!data) { kfree(bootconfig_emmc); return NULL; } memcpy(bootconfig_emmc, data, 512); if (bootconfig_emmc->magic_start != SMEM_DUAL_BOOTINFO_MAGIC_START) { pr_alert("Magic not found\n"); kfree(bootconfig_emmc); return NULL; } return bootconfig_emmc; } #endif #define BOOTCONFIG_PARTITION "0:BOOTCONFIG" #define BOOTCONFIG_PARTITION1 "0:BOOTCONFIG1" #define ROOTFS_PARTITION "rootfs" #define MAX_MMC_DEVICE 2 static int __init bootconfig_partition_init(void) { struct per_part_info *part_info; int i; #ifdef CONFIG_MMC struct gendisk *disk = NULL; struct disk_part_iter piter; struct hd_struct *part; int partno; #endif struct mtd_info *mtd; /* * In case of NOR\NAND boot, there is a chance that emmc * might have bootconfig paritions. This will try to read * the bootconfig partitions and create a proc entry which * is not correct since it is not booting from emmc. */ mtd = get_mtd_device_nm(ROOTFS_PARTITION); if (IS_ERR(mtd)) flash_type_emmc = 1; mtd = get_mtd_device_nm(BOOTCONFIG_PARTITION); if (!IS_ERR(mtd)) { bootconfig1 = read_bootconfig_mtd(mtd, 0); mtd = get_mtd_device_nm(BOOTCONFIG_PARTITION1); if (IS_ERR(mtd)) { pr_alert("%s: " BOOTCONFIG_PARTITION1 " not found\n", __func__); return 0; } bootconfig2 = read_bootconfig_mtd(mtd, 0); } #ifdef CONFIG_MMC else if (flash_type_emmc == 1) { flash_type_emmc = 0; for (i = 0; i < MAX_MMC_DEVICE; i++) { disk = get_gendisk(MKDEV(MMC_BLOCK_MAJOR, i*CONFIG_MMC_BLOCK_MINORS), &partno); if (!disk) return 0; disk_part_iter_init(&piter, disk, DISK_PITER_INCL_PART0); while ((part = disk_part_iter_next(&piter))) { if (part->info) { if (!strcmp((char *)part->info->volname, BOOTCONFIG_PARTITION)) { bootconfig1 = read_bootconfig_emmc(disk, part); } if (!strcmp((char *)part->info->volname, BOOTCONFIG_PARTITION1)) { bootconfig2 = read_bootconfig_emmc(disk, part); flash_type_emmc = 1; } } } disk_part_iter_exit(&piter); if (bootconfig1 || bootconfig2) break; } } #endif if (!bootconfig1) { if (bootconfig2) bootconfig1 = bootconfig2; else return 0; } if (!bootconfig2) { if (bootconfig1) bootconfig2 = bootconfig1; else return 0; } /* * The following check is to handle the case when an image without * apps upgrade support is upgraded to the image that supports APPS * upgrade. Earlier, the bootconfig file will be chosen based on age, * but now bootconfig1 only is considered and bootconfig2 is a backup. * When bootconfig2 is active in the older image and sysupgrade * is done to it, we copy the bootconfig2 to bootconfig1 so that the * failsafe parameters can be retained. */ if (bootconfig2->age > bootconfig1->age) bootconfig1 = bootconfig2; num_parts = bootconfig1->numaltpart; bootconfig1->age++; part_info = (struct per_part_info *)bootconfig1->per_part_entry; boot_info_dir = proc_mkdir("boot_info", NULL); if (!boot_info_dir) return 0; for (i = 0; i < num_parts; i++) { if (!flash_type_emmc && (strncmp(part_info[i].name, "kernel", ALT_PART_NAME_LENGTH) == 0)) continue; partname_dir[i] = proc_mkdir(part_info[i].name, boot_info_dir); if (partname_dir != NULL) { proc_create_data("primaryboot", S_IRUGO, partname_dir[i], &primaryboot_ops, part_info + i); proc_create_data("upgradepartition", S_IRUGO, partname_dir[i], &upgradepartition_ops, part_info + i); } } proc_create_data("getbinary_bootconfig", S_IRUGO, boot_info_dir, &getbinary_ops, bootconfig1); proc_create_data("getbinary_bootconfig1", S_IRUGO, boot_info_dir, &getbinary_ops, bootconfig1); return 0; } module_init(bootconfig_partition_init); static void __exit bootconfig_partition_exit(void) { struct per_part_info *part_info; int i; if (!bootconfig1) return; if (!bootconfig2) return; part_info = (struct per_part_info *)bootconfig1->per_part_entry; for (i = 0; i < num_parts; i++) { if (!flash_type_emmc && (strncmp(part_info[i].name, "kernel", ALT_PART_NAME_LENGTH) == 0)) continue; remove_proc_entry("primaryboot", partname_dir[i]); remove_proc_entry("upgradepartition", partname_dir[i]); remove_proc_entry(part_info[i].name, boot_info_dir); } remove_proc_entry("getbinary_bootconfig", boot_info_dir); remove_proc_entry("getbinary_bootconfig1", boot_info_dir); remove_proc_entry("boot_info", NULL); kfree(bootconfig1); kfree(bootconfig2); } module_exit(bootconfig_partition_exit); MODULE_LICENSE("Dual BSD/GPL");