[PATCH] bcb: Android bootloader control block driver

Andrew Boie andrew.p.boie at intel.com
Fri Jun 29 19:36:30 UTC 2012


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 at intel.com>
---
 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 at intel.com>
+ *
+ * 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++, &sect);
+		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++, &sect);
+		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 at intel.com>");
+MODULE_DESCRIPTION("bootloader communication module");
+MODULE_LICENSE("GPL v2");
-- 
1.7.9.5




More information about the devel mailing list