1/* 2 * BCM63XX CFE image tag parser 3 * 4 * Copyright �� 2006-2008 Florian Fainelli <florian@openwrt.org> 5 * Mike Albon <malbon@openwrt.org> 6 * Copyright �� 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net> 7 * Copyright �� 2011-2013 Jonas Gorski <jonas.gorski@gmail.com> 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 * 23 */ 24 25#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 26 27#include <linux/crc32.h> 28#include <linux/module.h> 29#include <linux/kernel.h> 30#include <linux/sizes.h> 31#include <linux/slab.h> 32#include <linux/vmalloc.h> 33#include <linux/mtd/mtd.h> 34#include <linux/mtd/partitions.h> 35 36#include <asm/mach-bcm63xx/bcm63xx_nvram.h> 37#include <asm/mach-bcm63xx/bcm963xx_tag.h> 38#include <asm/mach-bcm63xx/board_bcm963xx.h> 39 40#define BCM63XX_EXTENDED_SIZE 0xBFC00000 /* Extended flash address */ 41 42#define BCM63XX_CFE_BLOCK_SIZE SZ_64K /* always at least 64KiB */ 43 44#define BCM63XX_CFE_MAGIC_OFFSET 0x4e0 45 46static int bcm63xx_detect_cfe(struct mtd_info *master) 47{ 48 char buf[9]; 49 int ret; 50 size_t retlen; 51 52 ret = mtd_read(master, BCM963XX_CFE_VERSION_OFFSET, 5, &retlen, 53 (void *)buf); 54 buf[retlen] = 0; 55 56 if (ret) 57 return ret; 58 59 if (strncmp("cfe-v", buf, 5) == 0) 60 return 0; 61 62 /* very old CFE's do not have the cfe-v string, so check for magic */ 63 ret = mtd_read(master, BCM63XX_CFE_MAGIC_OFFSET, 8, &retlen, 64 (void *)buf); 65 buf[retlen] = 0; 66 67 return strncmp("CFE1CFE1", buf, 8); 68} 69 70static int bcm63xx_parse_cfe_partitions(struct mtd_info *master, 71 struct mtd_partition **pparts, 72 struct mtd_part_parser_data *data) 73{ 74 /* CFE, NVRAM and global Linux are always present */ 75 int nrparts = 3, curpart = 0; 76 struct bcm_tag *buf; 77 struct mtd_partition *parts; 78 int ret; 79 size_t retlen; 80 unsigned int rootfsaddr, kerneladdr, spareaddr; 81 unsigned int rootfslen, kernellen, sparelen, totallen; 82 unsigned int cfelen, nvramlen; 83 unsigned int cfe_erasesize; 84 int i; 85 u32 computed_crc; 86 bool rootfs_first = false; 87 88 if (bcm63xx_detect_cfe(master)) 89 return -EINVAL; 90 91 cfe_erasesize = max_t(uint32_t, master->erasesize, 92 BCM63XX_CFE_BLOCK_SIZE); 93 94 cfelen = cfe_erasesize; 95 nvramlen = bcm63xx_nvram_get_psi_size() * SZ_1K; 96 nvramlen = roundup(nvramlen, cfe_erasesize); 97 98 /* Allocate memory for buffer */ 99 buf = vmalloc(sizeof(struct bcm_tag)); 100 if (!buf) 101 return -ENOMEM; 102 103 /* Get the tag */ 104 ret = mtd_read(master, cfelen, sizeof(struct bcm_tag), &retlen, 105 (void *)buf); 106 107 if (retlen != sizeof(struct bcm_tag)) { 108 vfree(buf); 109 return -EIO; 110 } 111 112 computed_crc = crc32_le(IMAGETAG_CRC_START, (u8 *)buf, 113 offsetof(struct bcm_tag, header_crc)); 114 if (computed_crc == buf->header_crc) { 115 char *boardid = &(buf->board_id[0]); 116 char *tagversion = &(buf->tag_version[0]); 117 118 sscanf(buf->flash_image_start, "%u", &rootfsaddr); 119 sscanf(buf->kernel_address, "%u", &kerneladdr); 120 sscanf(buf->kernel_length, "%u", &kernellen); 121 sscanf(buf->total_length, "%u", &totallen); 122 123 pr_info("CFE boot tag found with version %s and board type %s\n", 124 tagversion, boardid); 125 126 kerneladdr = kerneladdr - BCM63XX_EXTENDED_SIZE; 127 rootfsaddr = rootfsaddr - BCM63XX_EXTENDED_SIZE; 128 spareaddr = roundup(totallen, master->erasesize) + cfelen; 129 130 if (rootfsaddr < kerneladdr) { 131 /* default Broadcom layout */ 132 rootfslen = kerneladdr - rootfsaddr; 133 rootfs_first = true; 134 } else { 135 /* OpenWrt layout */ 136 rootfsaddr = kerneladdr + kernellen; 137 rootfslen = spareaddr - rootfsaddr; 138 } 139 } else { 140 pr_warn("CFE boot tag CRC invalid (expected %08x, actual %08x)\n", 141 buf->header_crc, computed_crc); 142 kernellen = 0; 143 rootfslen = 0; 144 rootfsaddr = 0; 145 spareaddr = cfelen; 146 } 147 sparelen = master->size - spareaddr - nvramlen; 148 149 /* Determine number of partitions */ 150 if (rootfslen > 0) 151 nrparts++; 152 153 if (kernellen > 0) 154 nrparts++; 155 156 /* Ask kernel for more memory */ 157 parts = kzalloc(sizeof(*parts) * nrparts + 10 * nrparts, GFP_KERNEL); 158 if (!parts) { 159 vfree(buf); 160 return -ENOMEM; 161 } 162 163 /* Start building partition list */ 164 parts[curpart].name = "CFE"; 165 parts[curpart].offset = 0; 166 parts[curpart].size = cfelen; 167 curpart++; 168 169 if (kernellen > 0) { 170 int kernelpart = curpart; 171 172 if (rootfslen > 0 && rootfs_first) 173 kernelpart++; 174 parts[kernelpart].name = "kernel"; 175 parts[kernelpart].offset = kerneladdr; 176 parts[kernelpart].size = kernellen; 177 curpart++; 178 } 179 180 if (rootfslen > 0) { 181 int rootfspart = curpart; 182 183 if (kernellen > 0 && rootfs_first) 184 rootfspart--; 185 parts[rootfspart].name = "rootfs"; 186 parts[rootfspart].offset = rootfsaddr; 187 parts[rootfspart].size = rootfslen; 188 if (sparelen > 0 && !rootfs_first) 189 parts[rootfspart].size += sparelen; 190 curpart++; 191 } 192 193 parts[curpart].name = "nvram"; 194 parts[curpart].offset = master->size - nvramlen; 195 parts[curpart].size = nvramlen; 196 curpart++; 197 198 /* Global partition "linux" to make easy firmware upgrade */ 199 parts[curpart].name = "linux"; 200 parts[curpart].offset = cfelen; 201 parts[curpart].size = master->size - cfelen - nvramlen; 202 203 for (i = 0; i < nrparts; i++) 204 pr_info("Partition %d is %s offset %llx and length %llx\n", i, 205 parts[i].name, parts[i].offset, parts[i].size); 206 207 pr_info("Spare partition is offset %x and length %x\n", spareaddr, 208 sparelen); 209 210 *pparts = parts; 211 vfree(buf); 212 213 return nrparts; 214}; 215 216static struct mtd_part_parser bcm63xx_cfe_parser = { 217 .owner = THIS_MODULE, 218 .parse_fn = bcm63xx_parse_cfe_partitions, 219 .name = "bcm63xxpart", 220}; 221 222static int __init bcm63xx_cfe_parser_init(void) 223{ 224 register_mtd_parser(&bcm63xx_cfe_parser); 225 return 0; 226} 227 228static void __exit bcm63xx_cfe_parser_exit(void) 229{ 230 deregister_mtd_parser(&bcm63xx_cfe_parser); 231} 232 233module_init(bcm63xx_cfe_parser_init); 234module_exit(bcm63xx_cfe_parser_exit); 235 236MODULE_LICENSE("GPL"); 237MODULE_AUTHOR("Daniel Dickinson <openwrt@cshore.neomailbox.net>"); 238MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); 239MODULE_AUTHOR("Mike Albon <malbon@openwrt.org>"); 240MODULE_AUTHOR("Jonas Gorski <jonas.gorski@gmail.com"); 241MODULE_DESCRIPTION("MTD partitioning for BCM63XX CFE bootloaders"); 242