On Fri, 29 Jun 2012 12:36:30 -0700 Andrew Boie <andrew.p.boie@xxxxxxxxx> wrote: > Android userspace tells the kernel that it wants to boot into recovery > or some other non-default OS environment by passing a string argument > to reboot(). It is left to the OEM to hook this up to their specific > bootloader. > > This driver uses the bootloader control block (BCB) in the 'misc' > partition, which is the AOSP mechanism used to communicate with > the bootloader. Writing 'bootonce-NNN' to the command field > will cause the bootloader to do a oneshot boot into an alternate > boot label NNN if it exists. The device and partition number are > passed in via kernel command line. > > It is the bootloader's job to guard against this area being uninitialzed > or containing an invalid boot label, and just boot normally if that > is the case. The bootloader will always populate a magic signature in > the BCB; the driver will refuse to update it if not present. > > Signed-off-by: Andrew Boie <andrew.p.boie@xxxxxxxxx> Maybe I'm missing something obvious, but why does this need to be implemented in the kernel? Can't some user-space process just write to that partition using open/read/seek/write/close before calling 'reboot'. Thanks, NeilBrown > --- > drivers/staging/android/Kconfig | 11 ++ > drivers/staging/android/Makefile | 1 + > drivers/staging/android/bcb.c | 232 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 244 insertions(+) > create mode 100644 drivers/staging/android/bcb.c > > diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig > index 9a99238..c30fd20 100644 > --- a/drivers/staging/android/Kconfig > +++ b/drivers/staging/android/Kconfig > @@ -78,6 +78,17 @@ config ANDROID_INTF_ALARM_DEV > elapsed realtime, and a non-wakeup alarm on the monotonic clock. > Also exports the alarm interface to user-space. > > +config BOOTLOADER_CONTROL > + tristate "Bootloader Control Block module" > + default n > + help > + This driver installs a reboot hook, such that if reboot() is invoked > + with a string argument NNN, "bootonce-NNN" is copied to the command > + field in the Bootloader Control Block on the /misc partition, to be > + read by the bootloader. If the string matches one of the boot labels > + defined in its configuration, it will boot once into that label. The > + device and partition number are specified on the kernel command line. > + > endif # if ANDROID > > endmenu > diff --git a/drivers/staging/android/Makefile b/drivers/staging/android/Makefile > index 8e18d4e..a27707f 100644 > --- a/drivers/staging/android/Makefile > +++ b/drivers/staging/android/Makefile > @@ -9,5 +9,6 @@ obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o > obj-$(CONFIG_ANDROID_SWITCH) += switch/ > obj-$(CONFIG_ANDROID_INTF_ALARM_DEV) += alarm-dev.o > obj-$(CONFIG_PERSISTENT_TRACER) += trace_persistent.o > +obj-$(CONFIG_BOOTLOADER_CONTROL) += bcb.o > > CFLAGS_REMOVE_trace_persistent.o = -pg > diff --git a/drivers/staging/android/bcb.c b/drivers/staging/android/bcb.c > new file mode 100644 > index 0000000..af75257 > --- /dev/null > +++ b/drivers/staging/android/bcb.c > @@ -0,0 +1,232 @@ > +/* > + * bcb.c: Reboot hook to write bootloader commands to > + * the Android bootloader control block > + * > + * (C) Copyright 2012 Intel Corporation > + * Author: Andrew Boie <andrew.p.boie@xxxxxxxxx> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * as published by the Free Software Foundation; version 2 > + * of the License. > + */ > + > +#include <linux/module.h> > +#include <linux/moduleparam.h> > +#include <linux/blkdev.h> > +#include <linux/reboot.h> > + > +#define BCB_MAGIC 0xFEEDFACE > + > +/* Persistent area written by Android recovery console and Linux bcb driver > + * reboot hook for communication with the bootloader. Bootloader must > + * gracefully handle this area being unitinitailzed */ > +struct bootloader_message { > + /* Directive to the bootloader on what it needs to do next. > + * Possible values: > + * boot-NNN - Automatically boot into label NNN > + * bootonce-NNN - Automatically boot into label NNN, clearing this > + * field afterwards > + * anything else / garbage - Boot default label */ > + char command[32]; > + > + /* Storage area for error codes when using the BCB to boot into special > + * boot targets, e.g. for baseband update. Not used here. */ > + char status[32]; > + > + /* Area for recovery console to stash its command line arguments > + * in case it is reset and the cache command file is erased. > + * Not used here. */ > + char recovery[1024]; > + > + /* Magic sentinel value written by the bootloader; don't update this > + * if not equalto to BCB_MAGIC */ > + uint32_t magic; > +}; > + > +/* TODO: device names/partition numbers are unstable. Add support for looking > + * by GPT partition UUIDs */ > +static char *bootdev = "sda"; > +module_param(bootdev, charp, S_IRUGO); > +MODULE_PARM_DESC(bootdev, "Block device for bootloader communication"); > + > +static int partno; > +module_param(partno, int, S_IRUGO); > +MODULE_PARM_DESC(partno, "Partition number for bootloader communication"); > + > +static int device_match(struct device *dev, void *data) > +{ > + if (strcmp(dev_name(dev), bootdev) == 0) > + return 1; > + return 0; > +} > + > +static struct block_device *get_emmc_bdev(void) > +{ > + struct block_device *bdev; > + struct device *disk_device; > + > + disk_device = class_find_device(&block_class, NULL, NULL, device_match); > + if (!disk_device) { > + pr_err("bcb: device %s not found!\n", bootdev); > + return NULL; > + } > + bdev = bdget_disk(dev_to_disk(disk_device), partno); > + if (!bdev) { > + dev_err(disk_device, "bcb: unable to get disk (%s,%d)\n", > + bootdev, partno); > + return NULL; > + } > + /* Note: this bdev ref will be freed after first > + bdev_get/bdev_put cycle */ > + return bdev; > +} > + > +static u64 last_lba(struct block_device *bdev) > +{ > + if (!bdev || !bdev->bd_inode) > + return 0; > + return div_u64(bdev->bd_inode->i_size, > + bdev_logical_block_size(bdev)) - 1ULL; > +} > + > +static size_t read_lba(struct block_device *bdev, > + u64 lba, u8 *buffer, size_t count) > +{ > + size_t totalreadcount = 0; > + sector_t n = lba * (bdev_logical_block_size(bdev) / 512); > + > + if (!buffer || lba > last_lba(bdev)) > + return 0; > + > + while (count) { > + int copied = 512; > + Sector sect; > + unsigned char *data = read_dev_sector(bdev, n++, §); > + if (!data) > + break; > + if (copied > count) > + copied = count; > + memcpy(buffer, data, copied); > + put_dev_sector(sect); > + buffer += copied; > + totalreadcount += copied; > + count -= copied; > + } > + return totalreadcount; > +} > + > +static size_t write_lba(struct block_device *bdev, > + u64 lba, u8 *buffer, size_t count) > +{ > + size_t totalwritecount = 0; > + sector_t n = lba * (bdev_logical_block_size(bdev) / 512); > + > + if (!buffer || lba > last_lba(bdev)) > + return 0; > + > + while (count) { > + int copied = 512; > + Sector sect; > + unsigned char *data = read_dev_sector(bdev, n++, §); > + if (!data) > + break; > + if (copied > count) > + copied = count; > + memcpy(data, buffer, copied); > + set_page_dirty(sect.v); > + unlock_page(sect.v); > + put_dev_sector(sect); > + buffer += copied; > + totalwritecount += copied; > + count -= copied; > + } > + sync_blockdev(bdev); > + return totalwritecount; > +} > + > +static int bcb_reboot_notifier_call( > + struct notifier_block *notifier, > + unsigned long what, void *data) > +{ > + int ret = NOTIFY_DONE; > + char *cmd = (char *)data; > + struct block_device *bdev = NULL; > + struct bootloader_message *bcb = NULL; > + > + if (what != SYS_RESTART || !data) > + goto out; > + > + bdev = get_emmc_bdev(); > + if (!bdev) > + goto out; > + > + /* make sure the block device is open rw */ > + if (blkdev_get(bdev, FMODE_READ | FMODE_WRITE, NULL) < 0) { > + pr_err("bcb: blk_dev_get failed!\n"); > + goto out; > + } > + > + bcb = kmalloc(sizeof(*bcb), GFP_KERNEL); > + if (!bcb) { > + pr_err("bcb: out of memory\n"); > + goto out; > + } > + > + if (read_lba(bdev, 0, (u8 *)bcb, sizeof(*bcb)) != sizeof(*bcb)) { > + pr_err("bcb: couldn't read bootloader control block\n"); > + goto out; > + } > + > + if (bcb->magic != BCB_MAGIC) { > + pr_err("bcb: bootloader control block corrupted, not writing\n"); > + goto out; > + } > + > + /* When the bootloader reads this area, it will null-terminate it > + * and check if it matches any existing boot labels */ > + snprintf(bcb->command, sizeof(bcb->command), "bootonce-%s", cmd); > + > + if (write_lba(bdev, 0, (u8 *)bcb, sizeof(*bcb)) != sizeof(*bcb)) { > + pr_err("bcb: couldn't write bootloader control block\n"); > + goto out; > + } > + > + ret = NOTIFY_OK; > +out: > + kfree(bcb); > + if (bdev) > + blkdev_put(bdev, FMODE_READ | FMODE_WRITE); > + > + return ret; > +} > + > +static struct notifier_block bcb_reboot_notifier = { > + .notifier_call = bcb_reboot_notifier_call, > +}; > + > +static int __init bcb_init(void) > +{ > + if (partno < 1) { > + pr_err("bcb: partition number not specified\n"); > + return -1; > + } > + if (register_reboot_notifier(&bcb_reboot_notifier)) { > + pr_err("bcb: unable to register reboot notifier\n"); > + return -1; > + } > + pr_info("bcb: writing commands to (%s,%d)\n", > + bootdev, partno); > + return 0; > +} > +module_init(bcb_init); > + > +static void __exit bcb_exit(void) > +{ > + unregister_reboot_notifier(&bcb_reboot_notifier); > +} > +module_exit(bcb_exit); > + > +MODULE_AUTHOR("Andrew Boie <andrew.p.boie@xxxxxxxxx>"); > +MODULE_DESCRIPTION("bootloader communication module"); > +MODULE_LICENSE("GPL v2");
Attachment:
signature.asc
Description: PGP signature
_______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/devel