blob: 2d73d0761eda0ae5e8d0b056ce8d4ebc32a7ec73 [file] [log] [blame]
/*
* 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, &sect);
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");