| /* |
| * Copyright (c) 2015-2016, 2020, 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 <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <linux/io.h> |
| #include <linux/seq_file.h> |
| #include <asm/setup.h> |
| #include <linux/mtd/partitions.h> |
| #include <linux/proc_fs.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/module.h> |
| #include <linux/version.h> |
| #include <linux/genhd.h> |
| #include <linux/major.h> |
| #include <linux/mtd/blktrans.h> |
| #include <linux/mtd/mtd.h> |
| #include <linux/types.h> |
| #include <linux/blkdev.h> |
| #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, |
| }; |
| |
| #ifdef CONFIG_MTD |
| 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; |
| } |
| #endif |
| |
| #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 = NULL; |
| |
| /* |
| * 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. |
| */ |
| #ifdef CONFIG_MTD |
| 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_OR_NULL(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); |
| } |
| #else |
| flash_type_emmc = 1; |
| #endif |
| #ifdef CONFIG_MMC |
| if (IS_ERR_OR_NULL(mtd) && 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"); |