Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+217
View File
@@ -0,0 +1,217 @@
# SPDX-License-Identifier: GPL-2.0-only
config MTD_BCM47XX_PARTS
tristate "BCM47XX partitioning parser"
depends on BCM47XX || ARCH_BCM_5301X
help
This provides partitions parser for devices based on BCM47xx
boards.
config MTD_BCM63XX_PARTS
bool "BCM63XX CFE partitioning parser"
depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST
select CRC32
select MTD_PARSER_IMAGETAG
help
This provides partition parsing for BCM63xx devices with CFE
bootloaders.
config MTD_BRCM_U_BOOT
tristate "Broadcom's U-Boot partition parser"
depends on ARCH_BCMBCA || COMPILE_TEST
help
Broadcom uses a custom way of storing U-Boot environment variables.
They are placed inside U-Boot partition itself at unspecified offset.
It's possible to locate them by looking for a custom header with a
magic value. This driver does that and creates subpartitions for
each found environment variables block.
config MTD_CMDLINE_PARTS
tristate "Command line partition table parsing"
depends on MTD
help
Allow generic configuration of the MTD partition tables via the kernel
command line. Multiple flash resources are supported for hardware where
different kinds of flash memory are available.
You will still need the parsing functions to be called by the driver
for your particular device. It won't happen automatically. The
SA1100 map driver (CONFIG_MTD_SA1100) has an option for this, for
example.
The format for the command line is as follows:
mtdparts=<mtddef>[;<mtddef]
<mtddef> := <mtd-id>:<partdef>[,<partdef>]
<partdef> := <size>[@offset][<name>][ro]
<mtd-id> := unique id used in mapping driver/device
<size> := standard linux memsize OR "-" to denote all
remaining space
<name> := (NAME)
Due to the way Linux handles the command line, no spaces are
allowed in the partition definition, including mtd id's and partition
names.
Examples:
1 flash resource (mtd-id "sa1100"), with 1 single writable partition:
mtdparts=sa1100:-
Same flash, but 2 named partitions, the first one being read-only:
mtdparts=sa1100:256k(ARMboot)ro,-(root)
If unsure, say 'N'.
config MTD_OF_PARTS
tristate "OpenFirmware (device tree) partitioning parser"
default y
depends on OF
help
This provides a open firmware device tree partition parser
which derives the partition map from the children of the
flash memory node, as described in
Documentation/devicetree/bindings/mtd/mtd.yaml.
config MTD_OF_PARTS_BCM4908
bool "BCM4908 partitioning support"
depends on MTD_OF_PARTS && (ARCH_BCMBCA || COMPILE_TEST)
default ARCH_BCMBCA
help
This provides partitions parser for BCM4908 family devices
that can have multiple "firmware" partitions. It takes care of
finding currently used one and backup ones.
config MTD_OF_PARTS_LINKSYS_NS
bool "Linksys Northstar partitioning support"
depends on MTD_OF_PARTS && (ARCH_BCM_5301X || ARCH_BCMBCA || COMPILE_TEST)
default ARCH_BCM_5301X
help
This provides partitions parser for Linksys devices based on Broadcom
Northstar architecture. Linksys commonly uses fixed flash layout with
two "firmware" partitions. Currently used firmware has to be detected
using CFE environment variable.
config MTD_PARSER_IMAGETAG
tristate "Parser for BCM963XX Image Tag format partitions"
depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST
select CRC32
help
Image Tag is the firmware header used by broadcom on their xDSL line
of devices. It is used to describe the offsets and lengths of kernel
and rootfs partitions.
This driver adds support for parsing a partition with an Image Tag
header and creates up to two partitions, kernel and rootfs.
config MTD_AFS_PARTS
tristate "ARM Firmware Suite partition parsing"
depends on (ARM || ARM64)
help
The ARM Firmware Suite allows the user to divide flash devices into
multiple 'images'. Each such image has a header containing its name
and offset/size etc.
If you need code which can detect and parse these tables, and
register MTD 'partitions' corresponding to each image detected,
enable this option.
You will still need the parsing functions to be called by the driver
for your particular device. It won't happen automatically. The
'physmap' map driver (CONFIG_MTD_PHYSMAP) does this, for example.
config MTD_PARSER_TPLINK_SAFELOADER
tristate "TP-Link Safeloader partitions parser"
depends on MTD && (ARCH_BCM_5301X || ATH79 || SOC_MT7620 || SOC_MT7621 || COMPILE_TEST)
help
TP-Link home routers use flash partitions to store various data. Info
about flash space layout is stored in a partitions table using a
custom ASCII-based format.
That format was first found in devices with SafeLoader bootloader and
was named after it. Later it was adapted to CFE and U-Boot
bootloaders.
This driver reads partitions table, parses it and creates MTD
partitions.
config MTD_PARSER_TRX
tristate "Parser for TRX format partitions"
depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
help
TRX is a firmware format used by Broadcom on their devices. It
may contain up to 3/4 partitions (depending on the version).
This driver will parse TRX header and report at least two partitions:
kernel and rootfs.
config MTD_SHARPSL_PARTS
tristate "Sharp SL Series NAND flash partition parser"
depends on MTD_NAND_SHARPSL || COMPILE_TEST
help
This provides the read-only FTL logic necessary to read the partition
table from the NAND flash of Sharp SL Series (Zaurus) and the MTD
partition parser using this code.
config MTD_REDBOOT_PARTS
tristate "RedBoot partition table parsing"
help
RedBoot is a ROM monitor and bootloader which deals with multiple
'images' in flash devices by putting a table one of the erase
blocks on the device, similar to a partition table, which gives
the offsets, lengths and names of all the images stored in the
flash.
If you need code which can detect and parse this table, and register
MTD 'partitions' corresponding to each image in the table, enable
this option.
You will still need the parsing functions to be called by the driver
for your particular device. It won't happen automatically. The
SA1100 map driver (CONFIG_MTD_SA1100) has an option for this, for
example.
if MTD_REDBOOT_PARTS
config MTD_REDBOOT_DIRECTORY_BLOCK
int "Location of RedBoot partition table"
default "-1"
help
This option is the Linux counterpart to the
CYGNUM_REDBOOT_FIS_DIRECTORY_BLOCK RedBoot compile time
option.
The option specifies which Flash sectors holds the RedBoot
partition table. A zero or positive value gives an absolute
erase block number. A negative value specifies a number of
sectors before the end of the device.
For example "2" means block number 2, "-1" means the last
block and "-2" means the penultimate block.
config MTD_REDBOOT_PARTS_UNALLOCATED
bool "Include unallocated flash regions"
help
If you need to register each unallocated flash region as a MTD
'partition', enable this option.
config MTD_REDBOOT_PARTS_READONLY
bool "Force read-only for RedBoot system images"
help
If you need to force read-only for 'RedBoot', 'RedBoot Config' and
'FIS directory' images, enable this option.
endif # MTD_REDBOOT_PARTS
config MTD_QCOMSMEM_PARTS
tristate "Qualcomm SMEM flash partition parser"
depends on QCOM_SMEM
help
This provides support for parsing partitions from Shared Memory (SMEM)
for NAND and SPI flash on Qualcomm platforms.
config MTD_SERCOMM_PARTS
tristate "Sercomm partition table parser"
depends on MTD && RALINK
help
This provides partitions table parser for devices with Sercomm
partition map. This partition table contains real partition
offsets, which may differ from device to device depending on the
number and location of bad blocks on NAND.
+17
View File
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o
obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o
obj-$(CONFIG_MTD_BRCM_U_BOOT) += brcm_u-boot.o
obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o
obj-$(CONFIG_MTD_OF_PARTS) += ofpart.o
ofpart-y += ofpart_core.o
ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908) += ofpart_bcm4908.o
ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o
obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o
obj-$(CONFIG_MTD_AFS_PARTS) += afs.o
obj-$(CONFIG_MTD_PARSER_TPLINK_SAFELOADER) += tplink_safeloader.o
obj-$(CONFIG_MTD_PARSER_TRX) += parser_trx.o
obj-$(CONFIG_MTD_SERCOMM_PARTS) += scpart.o
obj-$(CONFIG_MTD_SHARPSL_PARTS) += sharpslpart.o
obj-$(CONFIG_MTD_REDBOOT_PARTS) += redboot.o
obj-$(CONFIG_MTD_QCOMSMEM_PARTS) += qcomsmempart.o
+395
View File
@@ -0,0 +1,395 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*======================================================================
drivers/mtd/afs.c: ARM Flash Layout/Partitioning
Copyright © 2000 ARM Limited
Copyright (C) 2019 Linus Walleij
This is access code for flashes using ARM's flash partitioning
standards.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>
#define AFSV1_FOOTER_MAGIC 0xA0FFFF9F
#define AFSV2_FOOTER_MAGIC1 0x464C5348 /* "FLSH" */
#define AFSV2_FOOTER_MAGIC2 0x464F4F54 /* "FOOT" */
struct footer_v1 {
u32 image_info_base; /* Address of first word of ImageFooter */
u32 image_start; /* Start of area reserved by this footer */
u32 signature; /* 'Magic' number proves it's a footer */
u32 type; /* Area type: ARM Image, SIB, customer */
u32 checksum; /* Just this structure */
};
struct image_info_v1 {
u32 bootFlags; /* Boot flags, compression etc. */
u32 imageNumber; /* Unique number, selects for boot etc. */
u32 loadAddress; /* Address program should be loaded to */
u32 length; /* Actual size of image */
u32 address; /* Image is executed from here */
char name[16]; /* Null terminated */
u32 headerBase; /* Flash Address of any stripped header */
u32 header_length; /* Length of header in memory */
u32 headerType; /* AIF, RLF, s-record etc. */
u32 checksum; /* Image checksum (inc. this struct) */
};
static u32 word_sum(void *words, int num)
{
u32 *p = words;
u32 sum = 0;
while (num--)
sum += *p++;
return sum;
}
static u32 word_sum_v2(u32 *p, u32 num)
{
u32 sum = 0;
int i;
for (i = 0; i < num; i++) {
u32 val;
val = p[i];
if (val > ~sum)
sum++;
sum += val;
}
return ~sum;
}
static bool afs_is_v1(struct mtd_info *mtd, u_int off)
{
/* The magic is 12 bytes from the end of the erase block */
u_int ptr = off + mtd->erasesize - 12;
u32 magic;
size_t sz;
int ret;
ret = mtd_read(mtd, ptr, 4, &sz, (u_char *)&magic);
if (ret < 0) {
printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n",
ptr, ret);
return false;
}
if (ret >= 0 && sz != 4)
return false;
return (magic == AFSV1_FOOTER_MAGIC);
}
static bool afs_is_v2(struct mtd_info *mtd, u_int off)
{
/* The magic is the 8 last bytes of the erase block */
u_int ptr = off + mtd->erasesize - 8;
u32 foot[2];
size_t sz;
int ret;
ret = mtd_read(mtd, ptr, 8, &sz, (u_char *)foot);
if (ret < 0) {
printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n",
ptr, ret);
return false;
}
if (ret >= 0 && sz != 8)
return false;
return (foot[0] == AFSV2_FOOTER_MAGIC1 &&
foot[1] == AFSV2_FOOTER_MAGIC2);
}
static int afs_parse_v1_partition(struct mtd_info *mtd,
u_int off, struct mtd_partition *part)
{
struct footer_v1 fs;
struct image_info_v1 iis;
u_int mask;
/*
* Static checks cannot see that we bail out if we have an error
* reading the footer.
*/
u_int iis_ptr;
u_int img_ptr;
u_int ptr;
size_t sz;
int ret;
int i;
/*
* This is the address mask; we use this to mask off out of
* range address bits.
*/
mask = mtd->size - 1;
ptr = off + mtd->erasesize - sizeof(fs);
ret = mtd_read(mtd, ptr, sizeof(fs), &sz, (u_char *)&fs);
if (ret >= 0 && sz != sizeof(fs))
ret = -EINVAL;
if (ret < 0) {
printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n",
ptr, ret);
return ret;
}
/*
* Check the checksum.
*/
if (word_sum(&fs, sizeof(fs) / sizeof(u32)) != 0xffffffff)
return -EINVAL;
/*
* Hide the SIB (System Information Block)
*/
if (fs.type == 2)
return 0;
iis_ptr = fs.image_info_base & mask;
img_ptr = fs.image_start & mask;
/*
* Check the image info base. This can not
* be located after the footer structure.
*/
if (iis_ptr >= ptr)
return 0;
/*
* Check the start of this image. The image
* data can not be located after this block.
*/
if (img_ptr > off)
return 0;
/* Read the image info block */
memset(&iis, 0, sizeof(iis));
ret = mtd_read(mtd, iis_ptr, sizeof(iis), &sz, (u_char *)&iis);
if (ret < 0) {
printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n",
iis_ptr, ret);
return -EINVAL;
}
if (sz != sizeof(iis))
return -EINVAL;
/*
* Validate the name - it must be NUL terminated.
*/
for (i = 0; i < sizeof(iis.name); i++)
if (iis.name[i] == '\0')
break;
if (i > sizeof(iis.name))
return -EINVAL;
part->name = kstrdup(iis.name, GFP_KERNEL);
if (!part->name)
return -ENOMEM;
part->size = (iis.length + mtd->erasesize - 1) & ~(mtd->erasesize - 1);
part->offset = img_ptr;
part->mask_flags = 0;
printk(" mtd: at 0x%08x, %5lluKiB, %8u, %s\n",
img_ptr, part->size / 1024,
iis.imageNumber, part->name);
return 0;
}
static int afs_parse_v2_partition(struct mtd_info *mtd,
u_int off, struct mtd_partition *part)
{
u_int ptr;
u32 footer[12];
u32 imginfo[36];
char *name;
u32 version;
u32 entrypoint;
u32 attributes;
u32 region_count;
u32 block_start;
u32 block_end;
u32 crc;
size_t sz;
int ret;
int i;
int pad = 0;
pr_debug("Parsing v2 partition @%08x-%08x\n",
off, off + mtd->erasesize);
/* First read the footer */
ptr = off + mtd->erasesize - sizeof(footer);
ret = mtd_read(mtd, ptr, sizeof(footer), &sz, (u_char *)footer);
if ((ret < 0) || (ret >= 0 && sz != sizeof(footer))) {
pr_err("AFS: mtd read failed at 0x%x: %d\n",
ptr, ret);
return -EIO;
}
name = (char *) &footer[0];
version = footer[9];
ptr = off + mtd->erasesize - sizeof(footer) - footer[8];
pr_debug("found image \"%s\", version %08x, info @%08x\n",
name, version, ptr);
/* Then read the image information */
ret = mtd_read(mtd, ptr, sizeof(imginfo), &sz, (u_char *)imginfo);
if ((ret < 0) || (ret >= 0 && sz != sizeof(imginfo))) {
pr_err("AFS: mtd read failed at 0x%x: %d\n",
ptr, ret);
return -EIO;
}
/* 32bit platforms have 4 bytes padding */
crc = word_sum_v2(&imginfo[1], 34);
if (!crc) {
pr_debug("Padding 1 word (4 bytes)\n");
pad = 1;
} else {
/* 64bit platforms have 8 bytes padding */
crc = word_sum_v2(&imginfo[2], 34);
if (!crc) {
pr_debug("Padding 2 words (8 bytes)\n");
pad = 2;
}
}
if (crc) {
pr_err("AFS: bad checksum on v2 image info: %08x\n", crc);
return -EINVAL;
}
entrypoint = imginfo[pad];
attributes = imginfo[pad+1];
region_count = imginfo[pad+2];
block_start = imginfo[20];
block_end = imginfo[21];
pr_debug("image entry=%08x, attr=%08x, regions=%08x, "
"bs=%08x, be=%08x\n",
entrypoint, attributes, region_count,
block_start, block_end);
for (i = 0; i < region_count; i++) {
u32 region_load_addr = imginfo[pad + 3 + i*4];
u32 region_size = imginfo[pad + 4 + i*4];
u32 region_offset = imginfo[pad + 5 + i*4];
u32 region_start;
u32 region_end;
pr_debug(" region %d: address: %08x, size: %08x, "
"offset: %08x\n",
i,
region_load_addr,
region_size,
region_offset);
region_start = off + region_offset;
region_end = region_start + region_size;
/* Align partition to end of erase block */
region_end += (mtd->erasesize - 1);
region_end &= ~(mtd->erasesize -1);
pr_debug(" partition start = %08x, partition end = %08x\n",
region_start, region_end);
/* Create one partition per region */
part->name = kstrdup(name, GFP_KERNEL);
if (!part->name)
return -ENOMEM;
part->offset = region_start;
part->size = region_end - region_start;
part->mask_flags = 0;
}
return 0;
}
static int parse_afs_partitions(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
u_int off, sz;
int ret = 0;
int i;
/* Count the partitions by looping over all erase blocks */
for (i = off = sz = 0; off < mtd->size; off += mtd->erasesize) {
if (afs_is_v1(mtd, off)) {
sz += sizeof(struct mtd_partition);
i += 1;
}
if (afs_is_v2(mtd, off)) {
sz += sizeof(struct mtd_partition);
i += 1;
}
}
if (!i)
return 0;
parts = kzalloc(sz, GFP_KERNEL);
if (!parts)
return -ENOMEM;
/*
* Identify the partitions
*/
for (i = off = 0; off < mtd->size; off += mtd->erasesize) {
if (afs_is_v1(mtd, off)) {
ret = afs_parse_v1_partition(mtd, off, &parts[i]);
if (ret)
goto out_free_parts;
i++;
}
if (afs_is_v2(mtd, off)) {
ret = afs_parse_v2_partition(mtd, off, &parts[i]);
if (ret)
goto out_free_parts;
i++;
}
}
*pparts = parts;
return i;
out_free_parts:
while (--i >= 0)
kfree(parts[i].name);
kfree(parts);
*pparts = NULL;
return ret;
}
static const struct of_device_id mtd_parser_afs_of_match_table[] = {
{ .compatible = "arm,arm-firmware-suite" },
{},
};
MODULE_DEVICE_TABLE(of, mtd_parser_afs_of_match_table);
static struct mtd_part_parser afs_parser = {
.parse_fn = parse_afs_partitions,
.name = "afs",
.of_match_table = mtd_parser_afs_of_match_table,
};
module_mtd_part_parser(afs_parser);
MODULE_AUTHOR("ARM Ltd");
MODULE_DESCRIPTION("ARM Firmware Suite partition parser");
MODULE_LICENSE("GPL");
@@ -0,0 +1,316 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* BCM47XX MTD partitioning
*
* Copyright © 2012 Rafał Miłecki <zajec5@gmail.com>
*/
#include <linux/bcm47xx_nvram.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <uapi/linux/magic.h>
/*
* NAND flash on Netgear R6250 was verified to contain 15 partitions.
* This will result in allocating too big array for some old devices, but the
* memory will be freed soon anyway (see mtd_device_parse_register).
*/
#define BCM47XXPART_MAX_PARTS 20
/*
* Amount of bytes we read when analyzing each block of flash memory.
* Set it big enough to allow detecting partition and reading important data.
*/
#define BCM47XXPART_BYTES_TO_READ 0x4e8
/* Magics */
#define BOARD_DATA_MAGIC 0x5246504D /* MPFR */
#define BOARD_DATA_MAGIC2 0xBD0D0BBD
#define CFE_MAGIC 0x43464531 /* 1EFC */
#define FACTORY_MAGIC 0x59544346 /* FCTY */
#define NVRAM_HEADER 0x48534C46 /* FLSH */
#define POT_MAGIC1 0x54544f50 /* POTT */
#define POT_MAGIC2 0x504f /* OP */
#define ML_MAGIC1 0x39685a42
#define ML_MAGIC2 0x26594131
#define TRX_MAGIC 0x30524448
#define SHSQ_MAGIC 0x71736873 /* shsq (weird ZTE H218N endianness) */
static const char * const trx_types[] = { "trx", NULL };
struct trx_header {
uint32_t magic;
uint32_t length;
uint32_t crc32;
uint16_t flags;
uint16_t version;
uint32_t offset[3];
} __packed;
static void bcm47xxpart_add_part(struct mtd_partition *part, const char *name,
u64 offset, uint32_t mask_flags)
{
part->name = name;
part->offset = offset;
part->mask_flags = mask_flags;
}
/**
* bcm47xxpart_bootpartition - gets index of TRX partition used by bootloader
*
* Some devices may have more than one TRX partition. In such case one of them
* is the main one and another a failsafe one. Bootloader may fallback to the
* failsafe firmware if it detects corruption of the main image.
*
* This function provides info about currently used TRX partition. It's the one
* containing kernel started by the bootloader.
*/
static int bcm47xxpart_bootpartition(void)
{
char buf[4];
int bootpartition;
/* Check CFE environment variable */
if (bcm47xx_nvram_getenv("bootpartition", buf, sizeof(buf)) > 0) {
if (!kstrtoint(buf, 0, &bootpartition))
return bootpartition;
}
return 0;
}
static int bcm47xxpart_parse(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
uint8_t i, curr_part = 0;
uint32_t *buf;
size_t bytes_read;
uint32_t offset;
uint32_t blocksize = master->erasesize;
int trx_parts[2]; /* Array with indexes of TRX partitions */
int trx_num = 0; /* Number of found TRX partitions */
static const int possible_nvram_sizes[] = { 0x8000, 0xF000, 0x10000, };
int err;
/*
* Some really old flashes (like AT45DB*) had smaller erasesize-s, but
* partitions were aligned to at least 0x1000 anyway.
*/
if (blocksize < 0x1000)
blocksize = 0x1000;
/* Alloc */
parts = kzalloc_objs(struct mtd_partition, BCM47XXPART_MAX_PARTS);
if (!parts)
return -ENOMEM;
buf = kzalloc(BCM47XXPART_BYTES_TO_READ, GFP_KERNEL);
if (!buf) {
kfree(parts);
return -ENOMEM;
}
/* Parse block by block looking for magics */
for (offset = 0; offset <= master->size - blocksize;
offset += blocksize) {
/* Nothing more in higher memory on BCM47XX (MIPS) */
if (IS_ENABLED(CONFIG_BCM47XX) && offset >= 0x2000000)
break;
if (curr_part >= BCM47XXPART_MAX_PARTS) {
pr_warn("Reached maximum number of partitions, scanning stopped!\n");
break;
}
/* Read beginning of the block */
err = mtd_read(master, offset, BCM47XXPART_BYTES_TO_READ,
&bytes_read, (uint8_t *)buf);
if (err && !mtd_is_bitflip(err)) {
pr_err("mtd_read error while parsing (offset: 0x%X): %d\n",
offset, err);
continue;
}
/* Magic or small NVRAM at 0x400 */
if ((buf[0x4e0 / 4] == CFE_MAGIC && buf[0x4e4 / 4] == CFE_MAGIC) ||
(buf[0x400 / 4] == NVRAM_HEADER)) {
bcm47xxpart_add_part(&parts[curr_part++], "boot",
offset, MTD_WRITEABLE);
continue;
}
/*
* board_data starts with board_id which differs across boards,
* but we can use 'MPFR' (hopefully) magic at 0x100
*/
if (buf[0x100 / 4] == BOARD_DATA_MAGIC) {
bcm47xxpart_add_part(&parts[curr_part++], "board_data",
offset, MTD_WRITEABLE);
continue;
}
/* Found on Huawei E970 */
if (buf[0x000 / 4] == FACTORY_MAGIC) {
bcm47xxpart_add_part(&parts[curr_part++], "factory",
offset, MTD_WRITEABLE);
continue;
}
/* POT(TOP) */
if (buf[0x000 / 4] == POT_MAGIC1 &&
(buf[0x004 / 4] & 0xFFFF) == POT_MAGIC2) {
bcm47xxpart_add_part(&parts[curr_part++], "POT", offset,
MTD_WRITEABLE);
continue;
}
/* ML */
if (buf[0x010 / 4] == ML_MAGIC1 &&
buf[0x014 / 4] == ML_MAGIC2) {
bcm47xxpart_add_part(&parts[curr_part++], "ML", offset,
MTD_WRITEABLE);
continue;
}
/* TRX */
if (buf[0x000 / 4] == TRX_MAGIC) {
struct trx_header *trx;
uint32_t last_subpart;
uint32_t trx_size;
if (trx_num >= ARRAY_SIZE(trx_parts))
pr_warn("No enough space to store another TRX found at 0x%X\n",
offset);
else
trx_parts[trx_num++] = curr_part;
bcm47xxpart_add_part(&parts[curr_part++], "firmware",
offset, 0);
/*
* Try to find TRX size. The "length" field isn't fully
* reliable as it could be decreased to make CRC32 cover
* only part of TRX data. It's commonly used as checksum
* can't cover e.g. ever-changing rootfs partition.
* Use offsets as helpers for assuming min TRX size.
*/
trx = (struct trx_header *)buf;
last_subpart = max3(trx->offset[0], trx->offset[1],
trx->offset[2]);
trx_size = max(trx->length, last_subpart + blocksize);
/*
* Skip the TRX data. Decrease offset by block size as
* the next loop iteration will increase it.
*/
offset += roundup(trx_size, blocksize) - blocksize;
continue;
}
/* Squashfs on devices not using TRX */
if (le32_to_cpu(buf[0x000 / 4]) == SQUASHFS_MAGIC ||
buf[0x000 / 4] == SHSQ_MAGIC) {
bcm47xxpart_add_part(&parts[curr_part++], "rootfs",
offset, 0);
continue;
}
/*
* New (ARM?) devices may have NVRAM in some middle block. Last
* block will be checked later, so skip it.
*/
if (offset != master->size - blocksize &&
buf[0x000 / 4] == NVRAM_HEADER) {
bcm47xxpart_add_part(&parts[curr_part++], "nvram",
offset, 0);
continue;
}
/* Read middle of the block */
err = mtd_read(master, offset + (blocksize / 2), 0x4, &bytes_read,
(uint8_t *)buf);
if (err && !mtd_is_bitflip(err)) {
pr_err("mtd_read error while parsing (offset: 0x%X): %d\n",
offset + (blocksize / 2), err);
continue;
}
/* Some devices (ex. WNDR3700v3) don't have a standard 'MPFR' */
if (buf[0x000 / 4] == BOARD_DATA_MAGIC2) {
bcm47xxpart_add_part(&parts[curr_part++], "board_data",
offset, MTD_WRITEABLE);
continue;
}
}
/* Look for NVRAM at the end of the last block. */
for (i = 0; i < ARRAY_SIZE(possible_nvram_sizes); i++) {
if (curr_part >= BCM47XXPART_MAX_PARTS) {
pr_warn("Reached maximum number of partitions, scanning stopped!\n");
break;
}
offset = master->size - possible_nvram_sizes[i];
err = mtd_read(master, offset, 0x4, &bytes_read,
(uint8_t *)buf);
if (err && !mtd_is_bitflip(err)) {
pr_err("mtd_read error while reading (offset 0x%X): %d\n",
offset, err);
continue;
}
/* Standard NVRAM */
if (buf[0] == NVRAM_HEADER) {
bcm47xxpart_add_part(&parts[curr_part++], "nvram",
master->size - blocksize, 0);
break;
}
}
kfree(buf);
/*
* Assume that partitions end at the beginning of the one they are
* followed by.
*/
for (i = 0; i < curr_part; i++) {
u64 next_part_offset = (i < curr_part - 1) ?
parts[i + 1].offset : master->size;
parts[i].size = next_part_offset - parts[i].offset;
}
/* If there was TRX parse it now */
for (i = 0; i < trx_num; i++) {
struct mtd_partition *trx = &parts[trx_parts[i]];
if (i == bcm47xxpart_bootpartition())
trx->types = trx_types;
else
trx->name = "failsafe";
}
*pparts = parts;
return curr_part;
};
static const struct of_device_id bcm47xxpart_of_match_table[] = {
{ .compatible = "brcm,bcm947xx-cfe-partitions" },
{},
};
MODULE_DEVICE_TABLE(of, bcm47xxpart_of_match_table);
static struct mtd_part_parser bcm47xxpart_mtd_parser = {
.parse_fn = bcm47xxpart_parse,
.name = "bcm47xxpart",
.of_match_table = bcm47xxpart_of_match_table,
};
module_mtd_part_parser(bcm47xxpart_mtd_parser);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MTD partitioning for BCM47XX flash memories");
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* BCM63XX CFE image tag parser
*
* Copyright © 2006-2008 Florian Fainelli <florian@openwrt.org>
* Mike Albon <malbon@openwrt.org>
* Copyright © 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net>
* Copyright © 2011-2013 Jonas Gorski <jonas.gorski@gmail.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bcm963xx_nvram.h>
#include <linux/bcm963xx_tag.h>
#include <linux/crc32.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#ifdef CONFIG_MIPS
#include <asm/bootinfo.h>
#include <asm/fw/cfe/cfe_api.h>
#endif /* CONFIG_MIPS */
#define BCM963XX_CFE_BLOCK_SIZE SZ_64K /* always at least 64KiB */
#define BCM963XX_CFE_MAGIC_OFFSET 0x4e0
#define BCM963XX_CFE_VERSION_OFFSET 0x570
#define BCM963XX_NVRAM_OFFSET 0x580
/* Ensure strings read from flash structs are null terminated */
#define STR_NULL_TERMINATE(x) \
do { char *_str = (x); _str[sizeof(x) - 1] = 0; } while (0)
static inline int bcm63xx_detect_cfe(void)
{
int ret = 0;
#ifdef CONFIG_MIPS
ret = (fw_arg3 == CFE_EPTSEAL);
#endif /* CONFIG_MIPS */
return ret;
}
static int bcm63xx_read_nvram(struct mtd_info *master,
struct bcm963xx_nvram *nvram)
{
u32 actual_crc, expected_crc;
size_t retlen;
int ret;
/* extract nvram data */
ret = mtd_read(master, BCM963XX_NVRAM_OFFSET, BCM963XX_NVRAM_V5_SIZE,
&retlen, (void *)nvram);
if (ret)
return ret;
ret = bcm963xx_nvram_checksum(nvram, &expected_crc, &actual_crc);
if (ret)
pr_warn("nvram checksum failed, contents may be invalid (expected %08x, got %08x)\n",
expected_crc, actual_crc);
if (!nvram->psi_size)
nvram->psi_size = BCM963XX_DEFAULT_PSI_SIZE;
return 0;
}
static const char * const bcm63xx_cfe_part_types[] = {
"bcm963xx-imagetag",
NULL,
};
static int bcm63xx_parse_cfe_nor_partitions(struct mtd_info *master,
const struct mtd_partition **pparts, struct bcm963xx_nvram *nvram)
{
struct mtd_partition *parts;
int nrparts = 3, curpart = 0;
unsigned int cfelen, nvramlen;
unsigned int cfe_erasesize;
int i;
cfe_erasesize = max_t(uint32_t, master->erasesize,
BCM963XX_CFE_BLOCK_SIZE);
cfelen = cfe_erasesize;
nvramlen = nvram->psi_size * SZ_1K;
nvramlen = roundup(nvramlen, cfe_erasesize);
parts = kzalloc(sizeof(*parts) * nrparts + 10 * nrparts, GFP_KERNEL);
if (!parts)
return -ENOMEM;
/* Start building partition list */
parts[curpart].name = "CFE";
parts[curpart].offset = 0;
parts[curpart].size = cfelen;
curpart++;
parts[curpart].name = "nvram";
parts[curpart].offset = master->size - nvramlen;
parts[curpart].size = nvramlen;
curpart++;
/* Global partition "linux" to make easy firmware upgrade */
parts[curpart].name = "linux";
parts[curpart].offset = cfelen;
parts[curpart].size = master->size - cfelen - nvramlen;
parts[curpart].types = bcm63xx_cfe_part_types;
for (i = 0; i < nrparts; i++)
pr_info("Partition %d is %s offset %llx and length %llx\n", i,
parts[i].name, parts[i].offset, parts[i].size);
*pparts = parts;
return nrparts;
}
static int bcm63xx_parse_cfe_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct bcm963xx_nvram *nvram = NULL;
int ret;
if (!bcm63xx_detect_cfe())
return -EINVAL;
nvram = vzalloc(sizeof(*nvram));
if (!nvram)
return -ENOMEM;
ret = bcm63xx_read_nvram(master, nvram);
if (ret)
goto out;
if (!mtd_type_is_nand(master))
ret = bcm63xx_parse_cfe_nor_partitions(master, pparts, nvram);
else
ret = -EINVAL;
out:
vfree(nvram);
return ret;
};
static const struct of_device_id parse_bcm63xx_cfe_match_table[] = {
{ .compatible = "brcm,bcm963xx-cfe-nor-partitions" },
{},
};
MODULE_DEVICE_TABLE(of, parse_bcm63xx_cfe_match_table);
static struct mtd_part_parser bcm63xx_cfe_parser = {
.parse_fn = bcm63xx_parse_cfe_partitions,
.name = "bcm63xxpart",
.of_match_table = parse_bcm63xx_cfe_match_table,
};
module_mtd_part_parser(bcm63xx_cfe_parser);
MODULE_AUTHOR("Daniel Dickinson <openwrt@cshore.neomailbox.net>");
MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
MODULE_AUTHOR("Mike Albon <malbon@openwrt.org>");
MODULE_AUTHOR("Jonas Gorski <jonas.gorski@gmail.com");
MODULE_DESCRIPTION("MTD partitioning for BCM63XX CFE bootloaders");
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright © 2022 Rafał Miłecki <rafal@milecki.pl>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#define BRCM_U_BOOT_MAX_OFFSET 0x200000
#define BRCM_U_BOOT_STEP 0x1000
#define BRCM_U_BOOT_MAX_PARTS 2
#define BRCM_U_BOOT_MAGIC 0x75456e76 /* uEnv */
struct brcm_u_boot_header {
__le32 magic;
__le32 length;
} __packed;
static const char *names[BRCM_U_BOOT_MAX_PARTS] = {
"u-boot-env",
"u-boot-env-backup",
};
static int brcm_u_boot_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct brcm_u_boot_header header;
struct mtd_partition *parts;
size_t bytes_read;
size_t offset;
int err;
int i = 0;
parts = kzalloc_objs(*parts, BRCM_U_BOOT_MAX_PARTS);
if (!parts)
return -ENOMEM;
for (offset = 0;
offset < min_t(size_t, mtd->size, BRCM_U_BOOT_MAX_OFFSET);
offset += BRCM_U_BOOT_STEP) {
err = mtd_read(mtd, offset, sizeof(header), &bytes_read, (uint8_t *)&header);
if (err && !mtd_is_bitflip(err)) {
pr_err("Failed to read from %s at 0x%zx: %d\n", mtd->name, offset, err);
continue;
}
if (le32_to_cpu(header.magic) != BRCM_U_BOOT_MAGIC)
continue;
parts[i].name = names[i];
parts[i].offset = offset;
parts[i].size = sizeof(header) + le32_to_cpu(header.length);
i++;
pr_info("offset:0x%zx magic:0x%08x BINGO\n", offset, header.magic);
if (i == BRCM_U_BOOT_MAX_PARTS)
break;
}
*pparts = parts;
return i;
};
static const struct of_device_id brcm_u_boot_of_match_table[] = {
{ .compatible = "brcm,u-boot" },
{},
};
MODULE_DEVICE_TABLE(of, brcm_u_boot_of_match_table);
static struct mtd_part_parser brcm_u_boot_mtd_parser = {
.parse_fn = brcm_u_boot_parse,
.name = "brcm_u-boot",
.of_match_table = brcm_u_boot_of_match_table,
};
module_mtd_part_parser(brcm_u_boot_mtd_parser);
MODULE_DESCRIPTION("Broadcom's U-Boot partition parser");
MODULE_LICENSE("GPL");
@@ -0,0 +1,428 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Read flash partition table from command line
*
* Copyright © 2002 SYSGO Real-Time Solutions GmbH
* Copyright © 2002-2010 David Woodhouse <dwmw2@infradead.org>
*
* The format for the command line is as follows:
*
* mtdparts=<mtddef>[;<mtddef]
* <mtddef> := <mtd-id>:<partdef>[,<partdef>]
* <partdef> := <size>[@<offset>][<name>][ro][lk][slc]
* <mtd-id> := unique name used in mapping driver/device (mtd->name)
* <size> := standard linux memsize OR "-" to denote all remaining space
* size is automatically truncated at end of device
* if specified or truncated size is 0 the part is skipped
* <offset> := standard linux memsize
* if omitted the part will immediately follow the previous part
* or 0 if the first part
* <name> := '(' NAME ')'
* NAME will appear in /proc/mtd
*
* <size> and <offset> can be specified such that the parts are out of order
* in physical memory and may even overlap.
*
* The parts are assigned MTD numbers in the order they are specified in the
* command line regardless of their order in physical memory.
*
* Examples:
*
* 1 NOR Flash, with 1 single writable partition:
* edb7312-nor:-
*
* 1 NOR Flash with 2 partitions, 1 NAND with one
* edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)
*/
#define pr_fmt(fmt) "mtd: " fmt
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/module.h>
#include <linux/err.h>
/* special size referring to all the remaining space in a partition */
#define SIZE_REMAINING ULLONG_MAX
#define OFFSET_CONTINUOUS ULLONG_MAX
struct cmdline_mtd_partition {
struct cmdline_mtd_partition *next;
int num_parts;
struct mtd_partition *parts;
char mtd_id[];
};
/* mtdpart_setup() parses into here */
static struct cmdline_mtd_partition *partitions;
/* the command line passed to mtdpart_setup() */
static char *mtdparts;
static char *cmdline;
static int cmdline_parsed;
/*
* Parse one partition definition for an MTD. Since there can be many
* comma separated partition definitions, this function calls itself
* recursively until no more partition definitions are found. Nice side
* effect: the memory to keep the mtd_partition structs and the names
* is allocated upon the last definition being found. At that point the
* syntax has been verified ok.
*/
static struct mtd_partition * newpart(char *s,
char **retptr,
int *num_parts,
int this_part,
unsigned char **extra_mem_ptr,
int extra_mem_size)
{
struct mtd_partition *parts;
unsigned long long size, offset = OFFSET_CONTINUOUS;
char *name;
int name_len;
unsigned char *extra_mem;
char delim;
unsigned int mask_flags, add_flags;
/* fetch the partition size */
if (*s == '-') {
/* assign all remaining space to this partition */
size = SIZE_REMAINING;
s++;
} else {
size = memparse(s, &s);
if (!size) {
pr_err("partition has size 0\n");
return ERR_PTR(-EINVAL);
}
}
/* fetch partition name and flags */
mask_flags = 0; /* this is going to be a regular partition */
add_flags = 0;
delim = 0;
/* check for offset */
if (*s == '@') {
s++;
offset = memparse(s, &s);
}
/* now look for name */
if (*s == '(')
delim = ')';
if (delim) {
char *p;
name = ++s;
p = strchr(name, delim);
if (!p) {
pr_err("no closing %c found in partition name\n", delim);
return ERR_PTR(-EINVAL);
}
name_len = p - name;
s = p + 1;
} else {
name = NULL;
name_len = 13; /* Partition_000 */
}
/* record name length for memory allocation later */
extra_mem_size += name_len + 1;
/* test for options */
if (strncmp(s, "ro", 2) == 0) {
mask_flags |= MTD_WRITEABLE;
s += 2;
}
/* if lk is found do NOT unlock the MTD partition*/
if (strncmp(s, "lk", 2) == 0) {
mask_flags |= MTD_POWERUP_LOCK;
s += 2;
}
/* if slc is found use emulated SLC mode on this partition*/
if (!strncmp(s, "slc", 3)) {
add_flags |= MTD_SLC_ON_MLC_EMULATION;
s += 3;
}
/* test if more partitions are following */
if (*s == ',') {
if (size == SIZE_REMAINING) {
pr_err("no partitions allowed after a fill-up partition\n");
return ERR_PTR(-EINVAL);
}
/* more partitions follow, parse them */
parts = newpart(s + 1, &s, num_parts, this_part + 1,
&extra_mem, extra_mem_size);
if (IS_ERR(parts))
return parts;
} else {
/* this is the last partition: allocate space for all */
int alloc_size;
*num_parts = this_part + 1;
alloc_size = *num_parts * sizeof(struct mtd_partition) +
extra_mem_size;
parts = kzalloc(alloc_size, GFP_KERNEL);
if (!parts)
return ERR_PTR(-ENOMEM);
extra_mem = (unsigned char *)(parts + *num_parts);
}
/*
* enter this partition (offset will be calculated later if it is
* OFFSET_CONTINUOUS at this point)
*/
parts[this_part].size = size;
parts[this_part].offset = offset;
parts[this_part].mask_flags = mask_flags;
parts[this_part].add_flags = add_flags;
if (name)
strscpy(extra_mem, name, name_len + 1);
else
sprintf(extra_mem, "Partition_%03d", this_part);
parts[this_part].name = extra_mem;
extra_mem += name_len + 1;
pr_debug("partition %d: name <%s>, offset %llx, size %llx, mask flags %x\n",
this_part, parts[this_part].name, parts[this_part].offset,
parts[this_part].size, parts[this_part].mask_flags);
/* return (updated) pointer to extra_mem memory */
if (extra_mem_ptr)
*extra_mem_ptr = extra_mem;
/* return (updated) pointer command line string */
*retptr = s;
/* return partition table */
return parts;
}
/*
* Parse the command line.
*/
static int mtdpart_setup_real(char *s)
{
cmdline_parsed = 1;
for( ; s != NULL; )
{
struct cmdline_mtd_partition *this_mtd;
struct mtd_partition *parts;
int mtd_id_len, num_parts;
char *p, *mtd_id, *semicol, *open_parenth;
/*
* Replace the first ';' by a NULL char so strrchr can work
* properly.
*/
semicol = strchr(s, ';');
if (semicol)
*semicol = '\0';
/*
* make sure that part-names with ":" will not be handled as
* part of the mtd-id with an ":"
*/
open_parenth = strchr(s, '(');
if (open_parenth)
*open_parenth = '\0';
mtd_id = s;
/*
* fetch <mtd-id>. We use strrchr to ignore all ':' that could
* be present in the MTD name, only the last one is interpreted
* as an <mtd-id>/<part-definition> separator.
*/
p = strrchr(s, ':');
/* Restore the '(' now. */
if (open_parenth)
*open_parenth = '(';
/* Restore the ';' now. */
if (semicol)
*semicol = ';';
if (!p) {
pr_err("no mtd-id\n");
return -EINVAL;
}
mtd_id_len = p - mtd_id;
pr_debug("parsing <%s>\n", p+1);
/*
* parse one mtd. have it reserve memory for the
* struct cmdline_mtd_partition and the mtd-id string.
*/
parts = newpart(p + 1, /* cmdline */
&s, /* out: updated cmdline ptr */
&num_parts, /* out: number of parts */
0, /* first partition */
(unsigned char**)&this_mtd, /* out: extra mem */
mtd_id_len + 1 + sizeof(*this_mtd) +
sizeof(void*)-1 /*alignment*/);
if (IS_ERR(parts)) {
/*
* An error occurred. We're either:
* a) out of memory, or
* b) in the middle of the partition spec
* Either way, this mtd is hosed and we're
* unlikely to succeed in parsing any more
*/
return PTR_ERR(parts);
}
/* align this_mtd */
this_mtd = (struct cmdline_mtd_partition *)
ALIGN((unsigned long)this_mtd, sizeof(void *));
/* enter results */
this_mtd->parts = parts;
this_mtd->num_parts = num_parts;
strscpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1);
/* link into chain */
this_mtd->next = partitions;
partitions = this_mtd;
pr_debug("mtdid=<%s> num_parts=<%d>\n",
this_mtd->mtd_id, this_mtd->num_parts);
/* EOS - we're done */
if (*s == 0)
break;
/* does another spec follow? */
if (*s != ';') {
pr_err("bad character after partition (%c)\n", *s);
return -EINVAL;
}
s++;
}
return 0;
}
/*
* Main function to be called from the MTD mapping driver/device to
* obtain the partitioning information. At this point the command line
* arguments will actually be parsed and turned to struct mtd_partition
* information. It returns partitions for the requested mtd device, or
* the first one in the chain if a NULL mtd_id is passed in.
*/
static int parse_cmdline_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
unsigned long long offset;
int i, err;
struct cmdline_mtd_partition *part;
const char *mtd_id = master->name;
/* parse command line */
if (!cmdline_parsed) {
err = mtdpart_setup_real(cmdline);
if (err)
return err;
}
/*
* Search for the partition definition matching master->name.
* If master->name is not set, stop at first partition definition.
*/
for (part = partitions; part; part = part->next) {
if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))
break;
}
if (!part)
return 0;
for (i = 0, offset = 0; i < part->num_parts; i++) {
if (part->parts[i].offset == OFFSET_CONTINUOUS)
part->parts[i].offset = offset;
else
offset = part->parts[i].offset;
if (part->parts[i].size == SIZE_REMAINING)
part->parts[i].size = master->size - offset;
if (offset + part->parts[i].size > master->size) {
pr_warn("%s: partitioning exceeds flash size, truncating\n",
part->mtd_id);
part->parts[i].size = master->size - offset;
}
offset += part->parts[i].size;
if (part->parts[i].size == 0) {
pr_warn("%s: skipping zero sized partition\n",
part->mtd_id);
part->num_parts--;
memmove(&part->parts[i], &part->parts[i + 1],
sizeof(*part->parts) * (part->num_parts - i));
i--;
}
}
*pparts = kmemdup(part->parts, sizeof(*part->parts) * part->num_parts,
GFP_KERNEL);
if (!*pparts)
return -ENOMEM;
return part->num_parts;
}
/*
* This is the handler for our kernel parameter, called from
* main.c::checksetup(). Note that we can not yet kmalloc() anything,
* so we only save the commandline for later processing.
*
* This function needs to be visible for bootloaders.
*/
static int __init mtdpart_setup(char *s)
{
cmdline = s;
return 1;
}
__setup("mtdparts=", mtdpart_setup);
static struct mtd_part_parser cmdline_parser = {
.parse_fn = parse_cmdline_partitions,
.name = "cmdlinepart",
};
static int __init cmdline_parser_init(void)
{
if (mtdparts)
mtdpart_setup(mtdparts);
register_mtd_parser(&cmdline_parser);
return 0;
}
static void __exit cmdline_parser_exit(void)
{
deregister_mtd_parser(&cmdline_parser);
}
module_init(cmdline_parser_init);
module_exit(cmdline_parser_exit);
MODULE_PARM_DESC(mtdparts, "Partitioning specification");
module_param(mtdparts, charp, 0);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>");
MODULE_DESCRIPTION("Command line configuration of MTD partitions");
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/mtd/partitions.h>
#include "ofpart_bcm4908.h"
#define BLPARAMS_FW_OFFSET "NAND_RFS_OFS"
static long long bcm4908_partitions_fw_offset(void)
{
struct device_node *root;
struct property *prop;
const char *s;
root = of_find_node_by_path("/");
if (!root)
return -ENOENT;
of_property_for_each_string(root, "brcm_blparms", prop, s) {
size_t len = strlen(BLPARAMS_FW_OFFSET);
unsigned long offset;
int err;
if (strncmp(s, BLPARAMS_FW_OFFSET, len) || s[len] != '=')
continue;
err = kstrtoul(s + len + 1, 0, &offset);
if (err) {
pr_err("failed to parse %s\n", s + len + 1);
of_node_put(root);
return err;
}
of_node_put(root);
return offset << 10;
}
of_node_put(root);
return -ENOENT;
}
int bcm4908_partitions_post_parse(struct mtd_info *mtd, struct mtd_partition *parts, int nr_parts)
{
long long fw_offset;
int i;
fw_offset = bcm4908_partitions_fw_offset();
for (i = 0; i < nr_parts; i++) {
if (of_device_is_compatible(parts[i].of_node, "brcm,bcm4908-firmware")) {
if (fw_offset < 0 || parts[i].offset == fw_offset)
parts[i].name = "firmware";
else
parts[i].name = "backup";
}
}
return 0;
}
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __BCM4908_PARTITIONS_H
#define __BCM4908_PARTITIONS_H
#ifdef CONFIG_MTD_OF_PARTS_BCM4908
int bcm4908_partitions_post_parse(struct mtd_info *mtd, struct mtd_partition *parts, int nr_parts);
#endif
#endif
@@ -0,0 +1,312 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Flash partitions described by the OF (or flattened) device tree
*
* Copyright © 2006 MontaVista Software Inc.
* Author: Vitaly Wool <vwool@ru.mvista.com>
*
* Revised to handle newer style flash binding by:
* Copyright © 2007 David Gibson, IBM Corporation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/mtd/partitions.h>
#include "ofpart_bcm4908.h"
#include "ofpart_linksys_ns.h"
struct fixed_partitions_quirks {
int (*post_parse)(struct mtd_info *mtd, struct mtd_partition *parts, int nr_parts);
};
#ifdef CONFIG_MTD_OF_PARTS_BCM4908
static struct fixed_partitions_quirks bcm4908_partitions_quirks = {
.post_parse = bcm4908_partitions_post_parse,
};
#endif
#ifdef CONFIG_MTD_OF_PARTS_LINKSYS_NS
static struct fixed_partitions_quirks linksys_ns_partitions_quirks = {
.post_parse = linksys_ns_partitions_post_parse,
};
#endif
static const struct of_device_id parse_ofpart_match_table[];
static bool node_has_compatible(struct device_node *pp)
{
return of_get_property(pp, "compatible", NULL);
}
static int parse_fixed_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
const struct fixed_partitions_quirks *quirks;
const struct of_device_id *of_id;
struct mtd_partition *parts;
struct device_node *mtd_node;
struct device_node *ofpart_node;
const char *partname;
struct device_node *pp;
int nr_parts, i, ret = 0;
bool dedicated = true;
/* Pull of_node from the master device node */
mtd_node = mtd_get_of_node(master);
if (!mtd_node)
return 0;
if (!master->parent) { /* Master */
ofpart_node = of_get_child_by_name(mtd_node, "partitions");
if (!ofpart_node) {
/*
* We might get here even when ofpart isn't used at all (e.g.,
* when using another parser), so don't be louder than
* KERN_DEBUG
*/
pr_debug("%s: 'partitions' subnode not found on %pOF. Trying to parse direct subnodes as partitions.\n",
master->name, mtd_node);
ofpart_node = mtd_node;
dedicated = false;
}
} else { /* Partition */
ofpart_node = of_node_get(mtd_node);
}
of_id = of_match_node(parse_ofpart_match_table, ofpart_node);
if (dedicated && !of_id) {
/* The 'partitions' subnode might be used by another parser */
of_node_put(ofpart_node);
return 0;
}
quirks = of_id ? of_id->data : NULL;
/* First count the subnodes */
nr_parts = 0;
for_each_child_of_node(ofpart_node, pp) {
if (!dedicated && node_has_compatible(pp))
continue;
nr_parts++;
}
if (nr_parts == 0) {
if (dedicated)
of_node_put(ofpart_node);
return 0;
}
parts = kzalloc_objs(*parts, nr_parts);
if (!parts) {
if (dedicated)
of_node_put(ofpart_node);
return -ENOMEM;
}
i = 0;
for_each_child_of_node(ofpart_node, pp) {
const __be32 *reg;
int len;
int a_cells, s_cells;
if (!dedicated && node_has_compatible(pp))
continue;
reg = of_get_property(pp, "reg", &len);
if (!reg) {
if (dedicated) {
pr_debug("%s: ofpart partition %pOF (%pOF) missing reg property.\n",
master->name, pp,
mtd_node);
goto ofpart_fail;
} else {
nr_parts--;
continue;
}
}
a_cells = of_n_addr_cells(pp);
s_cells = of_n_size_cells(pp);
if (!dedicated && s_cells == 0) {
/*
* This is a ugly workaround to not create
* regression on devices that are still creating
* partitions as direct children of the nand controller.
* This can happen in case the nand controller node has
* #size-cells equal to 0 and the firmware (e.g.
* U-Boot) just add the partitions there assuming
* 32-bit addressing.
*
* If you get this warning your firmware and/or DTS
* should be really fixed.
*
* This is working only for devices smaller than 4GiB.
*/
pr_warn("%s: ofpart partition %pOF (%pOF) #size-cells is wrongly set to <0>, assuming <1> for parsing partitions.\n",
master->name, pp, mtd_node);
s_cells = 1;
}
if (len / 4 != a_cells + s_cells) {
pr_debug("%s: ofpart partition %pOF (%pOF) error parsing reg property.\n",
master->name, pp,
mtd_node);
goto ofpart_fail;
}
parts[i].offset = of_read_number(reg, a_cells);
parts[i].size = of_read_number(reg + a_cells, s_cells);
parts[i].of_node = pp;
partname = of_get_property(pp, "label", &len);
if (!partname)
partname = of_get_property(pp, "name", &len);
parts[i].name = partname;
if (of_property_read_bool(pp, "read-only"))
parts[i].mask_flags |= MTD_WRITEABLE;
if (of_property_read_bool(pp, "lock"))
parts[i].mask_flags |= MTD_POWERUP_LOCK;
if (of_property_read_bool(pp, "slc-mode"))
parts[i].add_flags |= MTD_SLC_ON_MLC_EMULATION;
i++;
}
if (!nr_parts)
goto ofpart_none;
if (quirks && quirks->post_parse)
quirks->post_parse(master, parts, nr_parts);
if (dedicated)
of_node_put(ofpart_node);
*pparts = parts;
return nr_parts;
ofpart_fail:
pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n",
master->name, pp, mtd_node);
of_node_put(pp);
ret = -EINVAL;
ofpart_none:
if (dedicated)
of_node_put(ofpart_node);
kfree(parts);
return ret;
}
static const struct of_device_id parse_ofpart_match_table[] = {
/* Generic */
{ .compatible = "fixed-partitions" },
/* Customized */
#ifdef CONFIG_MTD_OF_PARTS_BCM4908
{ .compatible = "brcm,bcm4908-partitions", .data = &bcm4908_partitions_quirks, },
#endif
#ifdef CONFIG_MTD_OF_PARTS_LINKSYS_NS
{ .compatible = "linksys,ns-partitions", .data = &linksys_ns_partitions_quirks, },
#endif
{},
};
MODULE_DEVICE_TABLE(of, parse_ofpart_match_table);
static struct mtd_part_parser ofpart_parser = {
.parse_fn = parse_fixed_partitions,
.name = "fixed-partitions",
.of_match_table = parse_ofpart_match_table,
};
static int parse_ofoldpart_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
struct device_node *dp;
int i, plen, nr_parts;
const struct {
__be32 offset, len;
} *part;
const char *names;
/* Pull of_node from the master device node */
dp = mtd_get_of_node(master);
if (!dp)
return 0;
part = of_get_property(dp, "partitions", &plen);
if (!part)
return 0; /* No partitions found */
pr_warn("Device tree uses obsolete partition map binding: %pOF\n", dp);
nr_parts = plen / sizeof(part[0]);
parts = kzalloc_objs(*parts, nr_parts);
if (!parts)
return -ENOMEM;
names = of_get_property(dp, "partition-names", &plen);
for (i = 0; i < nr_parts; i++) {
parts[i].offset = be32_to_cpu(part->offset);
parts[i].size = be32_to_cpu(part->len) & ~1;
/* bit 0 set signifies read only partition */
if (be32_to_cpu(part->len) & 1)
parts[i].mask_flags = MTD_WRITEABLE;
if (names && (plen > 0)) {
int len = strlen(names) + 1;
parts[i].name = names;
plen -= len;
names += len;
} else {
parts[i].name = "unnamed";
}
part++;
}
*pparts = parts;
return nr_parts;
}
static struct mtd_part_parser ofoldpart_parser = {
.parse_fn = parse_ofoldpart_partitions,
.name = "ofoldpart",
};
static int __init ofpart_parser_init(void)
{
register_mtd_parser(&ofpart_parser);
register_mtd_parser(&ofoldpart_parser);
return 0;
}
static void __exit ofpart_parser_exit(void)
{
deregister_mtd_parser(&ofpart_parser);
deregister_mtd_parser(&ofoldpart_parser);
}
module_init(ofpart_parser_init);
module_exit(ofpart_parser_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Parser for MTD partitioning information in device tree");
MODULE_AUTHOR("Vitaly Wool, David Gibson");
/*
* When MTD core cannot find the requested parser, it tries to load the module
* with the same name. Since we provide the ofoldpart parser, we should have
* the corresponding alias.
*/
MODULE_ALIAS("fixed-partitions");
MODULE_ALIAS("ofoldpart");
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
*/
#include <linux/bcm47xx_nvram.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include "ofpart_linksys_ns.h"
#define NVRAM_BOOT_PART "bootpartition"
static int ofpart_linksys_ns_bootpartition(void)
{
char buf[4];
int bootpartition;
/* Check CFE environment variable */
if (bcm47xx_nvram_getenv(NVRAM_BOOT_PART, buf, sizeof(buf)) > 0) {
if (!kstrtoint(buf, 0, &bootpartition))
return bootpartition;
pr_warn("Failed to parse %s value \"%s\"\n", NVRAM_BOOT_PART,
buf);
} else {
pr_warn("Failed to get NVRAM \"%s\"\n", NVRAM_BOOT_PART);
}
return 0;
}
int linksys_ns_partitions_post_parse(struct mtd_info *mtd,
struct mtd_partition *parts,
int nr_parts)
{
int bootpartition = ofpart_linksys_ns_bootpartition();
int trx_idx = 0;
int i;
for (i = 0; i < nr_parts; i++) {
if (of_device_is_compatible(parts[i].of_node, "linksys,ns-firmware")) {
if (trx_idx++ == bootpartition)
parts[i].name = "firmware";
else
parts[i].name = "backup";
}
}
return 0;
}
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __OFPART_LINKSYS_NS_H
#define __OFPART_LINKSYS_NS_H
#ifdef CONFIG_MTD_OF_PARTS_LINKSYS_NS
int linksys_ns_partitions_post_parse(struct mtd_info *mtd,
struct mtd_partition *parts,
int nr_parts);
#endif
#endif
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* BCM63XX CFE image tag parser
*
* Copyright © 2006-2008 Florian Fainelli <florian@openwrt.org>
* Mike Albon <malbon@openwrt.org>
* Copyright © 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net>
* Copyright © 2011-2013 Jonas Gorski <jonas.gorski@gmail.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bcm963xx_tag.h>
#include <linux/crc32.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
/* Ensure strings read from flash structs are null terminated */
#define STR_NULL_TERMINATE(x) \
do { char *_str = (x); _str[sizeof(x) - 1] = 0; } while (0)
static int bcm963xx_read_imagetag(struct mtd_info *master, const char *name,
loff_t tag_offset, struct bcm_tag *buf)
{
int ret;
size_t retlen;
u32 computed_crc;
ret = mtd_read(master, tag_offset, sizeof(*buf), &retlen, (void *)buf);
if (ret)
return ret;
if (retlen != sizeof(*buf))
return -EIO;
computed_crc = crc32_le(IMAGETAG_CRC_START, (u8 *)buf,
offsetof(struct bcm_tag, header_crc));
if (computed_crc == buf->header_crc) {
STR_NULL_TERMINATE(buf->board_id);
STR_NULL_TERMINATE(buf->tag_version);
pr_info("%s: CFE image tag found at 0x%llx with version %s, board type %s\n",
name, tag_offset, buf->tag_version, buf->board_id);
return 0;
}
pr_warn("%s: CFE image tag at 0x%llx CRC invalid (expected %08x, actual %08x)\n",
name, tag_offset, buf->header_crc, computed_crc);
return -EINVAL;
}
static int bcm963xx_parse_imagetag_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
/* CFE, NVRAM and global Linux are always present */
int nrparts = 0, curpart = 0;
struct bcm_tag *buf = NULL;
struct mtd_partition *parts;
int ret;
unsigned int rootfsaddr, kerneladdr, spareaddr, offset;
unsigned int rootfslen, kernellen, sparelen, totallen;
int i;
bool rootfs_first = false;
buf = vmalloc(sizeof(struct bcm_tag));
if (!buf)
return -ENOMEM;
/* Get the tag */
ret = bcm963xx_read_imagetag(master, "rootfs", 0, buf);
if (!ret) {
STR_NULL_TERMINATE(buf->flash_image_start);
if (kstrtouint(buf->flash_image_start, 10, &rootfsaddr) ||
rootfsaddr < BCM963XX_EXTENDED_SIZE) {
pr_err("invalid rootfs address: %*ph\n",
(int)sizeof(buf->flash_image_start),
buf->flash_image_start);
ret = -EINVAL;
goto out;
}
STR_NULL_TERMINATE(buf->kernel_address);
if (kstrtouint(buf->kernel_address, 10, &kerneladdr) ||
kerneladdr < BCM963XX_EXTENDED_SIZE) {
pr_err("invalid kernel address: %*ph\n",
(int)sizeof(buf->kernel_address),
buf->kernel_address);
ret = -EINVAL;
goto out;
}
STR_NULL_TERMINATE(buf->kernel_length);
if (kstrtouint(buf->kernel_length, 10, &kernellen)) {
pr_err("invalid kernel length: %*ph\n",
(int)sizeof(buf->kernel_length),
buf->kernel_length);
ret = -EINVAL;
goto out;
}
STR_NULL_TERMINATE(buf->total_length);
if (kstrtouint(buf->total_length, 10, &totallen)) {
pr_err("invalid total length: %*ph\n",
(int)sizeof(buf->total_length),
buf->total_length);
ret = -EINVAL;
goto out;
}
/*
* Addresses are flash absolute, so convert to partition
* relative addresses. Assume either kernel or rootfs will
* directly follow the image tag.
*/
if (rootfsaddr < kerneladdr)
offset = rootfsaddr - sizeof(struct bcm_tag);
else
offset = kerneladdr - sizeof(struct bcm_tag);
kerneladdr = kerneladdr - offset;
rootfsaddr = rootfsaddr - offset;
spareaddr = roundup(totallen, master->erasesize);
if (rootfsaddr < kerneladdr) {
/* default Broadcom layout */
rootfslen = kerneladdr - rootfsaddr;
rootfs_first = true;
} else {
/* OpenWrt layout */
rootfsaddr = kerneladdr + kernellen;
rootfslen = spareaddr - rootfsaddr;
}
} else {
goto out;
}
sparelen = master->size - spareaddr;
/* Determine number of partitions */
if (rootfslen > 0)
nrparts++;
if (kernellen > 0)
nrparts++;
parts = kzalloc(sizeof(*parts) * nrparts + 10 * nrparts, GFP_KERNEL);
if (!parts) {
ret = -ENOMEM;
goto out;
}
/* Start building partition list */
if (kernellen > 0) {
int kernelpart = curpart;
if (rootfslen > 0 && rootfs_first)
kernelpart++;
parts[kernelpart].name = "kernel";
parts[kernelpart].offset = kerneladdr;
parts[kernelpart].size = kernellen;
curpart++;
}
if (rootfslen > 0) {
int rootfspart = curpart;
if (kernellen > 0 && rootfs_first)
rootfspart--;
parts[rootfspart].name = "rootfs";
parts[rootfspart].offset = rootfsaddr;
parts[rootfspart].size = rootfslen;
if (sparelen > 0 && !rootfs_first)
parts[rootfspart].size += sparelen;
curpart++;
}
for (i = 0; i < nrparts; i++)
pr_info("Partition %d is %s offset %llx and length %llx\n", i,
parts[i].name, parts[i].offset, parts[i].size);
pr_info("Spare partition is offset %x and length %x\n", spareaddr,
sparelen);
*pparts = parts;
ret = 0;
out:
vfree(buf);
if (ret)
return ret;
return nrparts;
}
static const struct of_device_id parse_bcm963xx_imagetag_match_table[] = {
{ .compatible = "brcm,bcm963xx-imagetag" },
{},
};
MODULE_DEVICE_TABLE(of, parse_bcm963xx_imagetag_match_table);
static struct mtd_part_parser bcm963xx_imagetag_parser = {
.parse_fn = bcm963xx_parse_imagetag_partitions,
.name = "bcm963xx-imagetag",
.of_match_table = parse_bcm963xx_imagetag_match_table,
};
module_mtd_part_parser(bcm963xx_imagetag_parser);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Daniel Dickinson <openwrt@cshore.neomailbox.net>");
MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
MODULE_AUTHOR("Mike Albon <malbon@openwrt.org>");
MODULE_AUTHOR("Jonas Gorski <jonas.gorski@gmail.com>");
MODULE_DESCRIPTION("MTD parser for BCM963XX CFE Image Tag partitions");
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Parser for TRX format partitions
*
* Copyright (C) 2012 - 2017 Rafał Miłecki <rafal@milecki.pl>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#define TRX_PARSER_MAX_PARTS 4
/* Magics */
#define TRX_MAGIC 0x30524448
#define UBI_EC_MAGIC 0x23494255 /* UBI# */
struct trx_header {
uint32_t magic;
uint32_t length;
uint32_t crc32;
uint16_t flags;
uint16_t version;
uint32_t offset[3];
} __packed;
static const char *parser_trx_data_part_name(struct mtd_info *master,
size_t offset)
{
uint32_t buf;
size_t bytes_read;
int err;
err = mtd_read(master, offset, sizeof(buf), &bytes_read,
(uint8_t *)&buf);
if (err && !mtd_is_bitflip(err)) {
pr_err("mtd_read error while parsing (offset: 0x%zX): %d\n",
offset, err);
goto out_default;
}
if (buf == UBI_EC_MAGIC)
return "ubi";
out_default:
return "rootfs";
}
static int parser_trx_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *np = mtd_get_of_node(mtd);
struct mtd_partition *parts;
struct mtd_partition *part;
struct trx_header trx;
size_t bytes_read;
uint8_t curr_part = 0, i = 0;
uint32_t trx_magic = TRX_MAGIC;
int err;
/* Get different magic from device tree if specified */
err = of_property_read_u32(np, "brcm,trx-magic", &trx_magic);
if (err != 0 && err != -EINVAL)
pr_err("failed to parse \"brcm,trx-magic\" DT attribute, using default: %d\n", err);
parts = kzalloc_objs(struct mtd_partition, TRX_PARSER_MAX_PARTS);
if (!parts)
return -ENOMEM;
err = mtd_read(mtd, 0, sizeof(trx), &bytes_read, (uint8_t *)&trx);
if (err) {
pr_err("MTD reading error: %d\n", err);
kfree(parts);
return err;
}
if (trx.magic != trx_magic) {
kfree(parts);
return -ENOENT;
}
/* We have LZMA loader if there is address in offset[2] */
if (trx.offset[2]) {
part = &parts[curr_part++];
part->name = "loader";
part->offset = trx.offset[i];
i++;
}
if (trx.offset[i]) {
part = &parts[curr_part++];
part->name = "linux";
part->offset = trx.offset[i];
i++;
}
if (trx.offset[i]) {
part = &parts[curr_part++];
part->name = parser_trx_data_part_name(mtd, trx.offset[i]);
part->offset = trx.offset[i];
i++;
}
/*
* Assume that every partition ends at the beginning of the one it is
* followed by.
*/
for (i = 0; i < curr_part; i++) {
u64 next_part_offset = (i < curr_part - 1) ?
parts[i + 1].offset : mtd->size;
parts[i].size = next_part_offset - parts[i].offset;
}
*pparts = parts;
return i;
};
static const struct of_device_id mtd_parser_trx_of_match_table[] = {
{ .compatible = "brcm,trx" },
{},
};
MODULE_DEVICE_TABLE(of, mtd_parser_trx_of_match_table);
static struct mtd_part_parser mtd_parser_trx = {
.parse_fn = parser_trx_parse,
.name = "trx",
.of_match_table = mtd_parser_trx_of_match_table,
};
module_mtd_part_parser(mtd_parser_trx);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Parser for TRX format partitions");
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm SMEM NAND flash partition parser
*
* Copyright (C) 2020, Linaro Ltd.
*/
#include <linux/ctype.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smem.h>
#define SMEM_AARM_PARTITION_TABLE 9
#define SMEM_APPS 0
#define SMEM_FLASH_PART_MAGIC1 0x55ee73aa
#define SMEM_FLASH_PART_MAGIC2 0xe35ebddb
#define SMEM_FLASH_PTABLE_V3 3
#define SMEM_FLASH_PTABLE_V4 4
#define SMEM_FLASH_PTABLE_MAX_PARTS_V3 16
#define SMEM_FLASH_PTABLE_MAX_PARTS_V4 48
#define SMEM_FLASH_PTABLE_HDR_LEN (4 * sizeof(u32))
#define SMEM_FLASH_PTABLE_NAME_SIZE 16
/**
* struct smem_flash_pentry - SMEM Flash partition entry
* @name: Name of the partition
* @offset: Offset in blocks
* @length: Length of the partition in blocks
* @attr: Flags for this partition
*/
struct smem_flash_pentry {
char name[SMEM_FLASH_PTABLE_NAME_SIZE];
__le32 offset;
__le32 length;
u8 attr;
} __packed __aligned(4);
/**
* struct smem_flash_ptable - SMEM Flash partition table
* @magic1: Partition table Magic 1
* @magic2: Partition table Magic 2
* @version: Partition table version
* @numparts: Number of partitions in this ptable
* @pentry: Flash partition entries belonging to this ptable
*/
struct smem_flash_ptable {
__le32 magic1;
__le32 magic2;
__le32 version;
__le32 numparts;
struct smem_flash_pentry pentry[SMEM_FLASH_PTABLE_MAX_PARTS_V4];
} __packed __aligned(4);
static int parse_qcomsmem_part(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
size_t len = SMEM_FLASH_PTABLE_HDR_LEN;
int ret, i, j, tmpparts, numparts = 0;
struct smem_flash_pentry *pentry;
struct smem_flash_ptable *ptable;
struct mtd_partition *parts;
char *name, *c;
if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_4K_SECTORS)
&& mtd->type == MTD_NORFLASH) {
pr_err("%s: SMEM partition parser is incompatible with 4K sectors\n",
mtd->name);
return -EINVAL;
}
pr_debug("Parsing partition table info from SMEM\n");
ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, &len);
if (IS_ERR(ptable)) {
if (PTR_ERR(ptable) != -EPROBE_DEFER)
pr_err("Error reading partition table header\n");
return PTR_ERR(ptable);
}
/* Verify ptable magic */
if (le32_to_cpu(ptable->magic1) != SMEM_FLASH_PART_MAGIC1 ||
le32_to_cpu(ptable->magic2) != SMEM_FLASH_PART_MAGIC2) {
pr_err("Partition table magic verification failed\n");
return -EINVAL;
}
/* Ensure that # of partitions is less than the max we have allocated */
tmpparts = le32_to_cpu(ptable->numparts);
if (tmpparts > SMEM_FLASH_PTABLE_MAX_PARTS_V4) {
pr_err("Partition numbers exceed the max limit\n");
return -EINVAL;
}
/* Find out length of partition data based on table version */
if (le32_to_cpu(ptable->version) <= SMEM_FLASH_PTABLE_V3) {
len = SMEM_FLASH_PTABLE_HDR_LEN + SMEM_FLASH_PTABLE_MAX_PARTS_V3 *
sizeof(struct smem_flash_pentry);
} else if (le32_to_cpu(ptable->version) == SMEM_FLASH_PTABLE_V4) {
len = SMEM_FLASH_PTABLE_HDR_LEN + SMEM_FLASH_PTABLE_MAX_PARTS_V4 *
sizeof(struct smem_flash_pentry);
} else {
pr_err("Unknown ptable version (%d)", le32_to_cpu(ptable->version));
return -EINVAL;
}
/*
* Now that the partition table header has been parsed, verified
* and the length of the partition table calculated, read the
* complete partition table
*/
ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, &len);
if (IS_ERR(ptable)) {
pr_err("Error reading partition table\n");
return PTR_ERR(ptable);
}
for (i = 0; i < tmpparts; i++) {
pentry = &ptable->pentry[i];
if (pentry->name[0] != '\0')
numparts++;
}
parts = kzalloc_objs(*parts, numparts);
if (!parts)
return -ENOMEM;
for (i = 0, j = 0; i < tmpparts; i++) {
pentry = &ptable->pentry[i];
if (pentry->name[0] == '\0')
continue;
name = kstrdup(pentry->name, GFP_KERNEL);
if (!name) {
ret = -ENOMEM;
goto out_free_parts;
}
/* Convert name to lower case */
for (c = name; *c != '\0'; c++)
*c = tolower(*c);
parts[j].name = name;
parts[j].offset = le32_to_cpu(pentry->offset) * mtd->erasesize;
parts[j].mask_flags = pentry->attr;
parts[j].size = le32_to_cpu(pentry->length) * mtd->erasesize;
pr_debug("%d: %s offs=0x%08x size=0x%08x attr:0x%08x\n",
i, pentry->name, le32_to_cpu(pentry->offset),
le32_to_cpu(pentry->length), pentry->attr);
j++;
}
pr_debug("SMEM partition table found: ver: %d len: %d\n",
le32_to_cpu(ptable->version), tmpparts);
*pparts = parts;
return numparts;
out_free_parts:
while (--j >= 0)
kfree(parts[j].name);
kfree(parts);
*pparts = NULL;
return ret;
}
static void parse_qcomsmem_cleanup(const struct mtd_partition *pparts,
int nr_parts)
{
int i;
for (i = 0; i < nr_parts; i++)
kfree(pparts[i].name);
kfree(pparts);
}
static const struct of_device_id qcomsmem_of_match_table[] = {
{ .compatible = "qcom,smem-part" },
{},
};
MODULE_DEVICE_TABLE(of, qcomsmem_of_match_table);
static struct mtd_part_parser mtd_parser_qcomsmem = {
.parse_fn = parse_qcomsmem_part,
.cleanup = parse_qcomsmem_cleanup,
.name = "qcomsmem",
.of_match_table = qcomsmem_of_match_table,
};
module_mtd_part_parser(mtd_parser_qcomsmem);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
MODULE_DESCRIPTION("Qualcomm SMEM NAND flash partition parser");
+323
View File
@@ -0,0 +1,323 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Parse RedBoot-style Flash Image System (FIS) tables and
* produce a Linux partition array to match.
*
* Copyright © 2001 Red Hat UK Limited
* Copyright © 2001-2010 David Woodhouse <dwmw2@infradead.org>
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/of.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/module.h>
struct fis_image_desc {
unsigned char name[16]; // Null terminated name
u32 flash_base; // Address within FLASH of image
u32 mem_base; // Address in memory where it executes
u32 size; // Length of image
u32 entry_point; // Execution entry point
u32 data_length; // Length of actual data
unsigned char _pad[256 - (16 + 7 * sizeof(u32))];
u32 desc_cksum; // Checksum over image descriptor
u32 file_cksum; // Checksum over image data
};
struct fis_list {
struct fis_image_desc *img;
struct fis_list *next;
};
static int directory = CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK;
module_param(directory, int, 0);
static inline int redboot_checksum(struct fis_image_desc *img)
{
/* RedBoot doesn't actually write the desc_cksum field yet AFAICT */
return 1;
}
static void parse_redboot_of(struct mtd_info *master)
{
struct device_node *np;
struct device_node *npart;
u32 dirblock;
int ret;
np = mtd_get_of_node(master);
if (!np)
return;
npart = of_get_child_by_name(np, "partitions");
if (!npart)
return;
ret = of_property_read_u32(npart, "fis-index-block", &dirblock);
of_node_put(npart);
if (ret)
return;
/*
* Assign the block found in the device tree to the local
* directory block pointer.
*/
directory = dirblock;
}
static int parse_redboot_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
int nrparts = 0;
struct fis_image_desc *buf;
struct mtd_partition *parts;
struct fis_list *fl = NULL, *tmp_fl;
int ret, i;
size_t retlen;
char *names;
char *nullname;
int namelen = 0;
int nulllen = 0;
int numslots;
unsigned long offset;
#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
static char nullstring[] = "unallocated";
#endif
parse_redboot_of(master);
if (directory < 0) {
offset = master->size + directory * master->erasesize;
while (mtd_block_isbad(master, offset)) {
if (!offset) {
nogood:
pr_notice("Failed to find a non-bad block to check for RedBoot partition table\n");
return -EIO;
}
offset -= master->erasesize;
}
} else {
offset = (unsigned long) directory * master->erasesize;
while (mtd_block_isbad(master, offset)) {
offset += master->erasesize;
if (offset == master->size)
goto nogood;
}
}
buf = vmalloc(master->erasesize);
if (!buf)
return -ENOMEM;
pr_notice("Searching for RedBoot partition table in %s at offset 0x%lx\n",
master->name, offset);
ret = mtd_read(master, offset, master->erasesize, &retlen,
(void *)buf);
if (ret)
goto out;
if (retlen != master->erasesize) {
ret = -EIO;
goto out;
}
numslots = (master->erasesize / sizeof(struct fis_image_desc));
for (i = 0; i < numslots; i++) {
if (!memcmp(buf[i].name, "FIS directory", 14)) {
/* This is apparently the FIS directory entry for the
* FIS directory itself. The FIS directory size is
* one erase block; if the buf[i].size field is
* swab32(erasesize) then we know we are looking at
* a byte swapped FIS directory - swap all the entries!
* (NOTE: this is 'size' not 'data_length'; size is
* the full size of the entry.)
*/
/* RedBoot can combine the FIS directory and
config partitions into a single eraseblock;
we assume wrong-endian if either the swapped
'size' matches the eraseblock size precisely,
or if the swapped size actually fits in an
eraseblock while the unswapped size doesn't. */
if (swab32(buf[i].size) == master->erasesize ||
(buf[i].size > master->erasesize
&& swab32(buf[i].size) < master->erasesize)) {
int j;
/* Update numslots based on actual FIS directory size */
numslots = swab32(buf[i].size) / sizeof(struct fis_image_desc);
for (j = 0; j < numslots; ++j) {
/* A single 0xff denotes a deleted entry.
* Two of them in a row is the end of the table.
*/
if (buf[j].name[0] == 0xff) {
if (buf[j].name[1] == 0xff) {
break;
} else {
continue;
}
}
/* The unsigned long fields were written with the
* wrong byte sex, name and pad have no byte sex.
*/
swab32s(&buf[j].flash_base);
swab32s(&buf[j].mem_base);
swab32s(&buf[j].size);
swab32s(&buf[j].entry_point);
swab32s(&buf[j].data_length);
swab32s(&buf[j].desc_cksum);
swab32s(&buf[j].file_cksum);
}
} else if (buf[i].size < master->erasesize) {
/* Update numslots based on actual FIS directory size */
numslots = buf[i].size / sizeof(struct fis_image_desc);
}
break;
}
}
if (i == numslots) {
/* Didn't find it */
pr_notice("No RedBoot partition table detected in %s\n",
master->name);
ret = 0;
goto out;
}
for (i = 0; i < numslots; i++) {
struct fis_list *new_fl, **prev;
if (buf[i].name[0] == 0xff) {
if (buf[i].name[1] == 0xff) {
break;
} else {
continue;
}
}
if (!redboot_checksum(&buf[i]))
break;
new_fl = kmalloc_obj(struct fis_list);
namelen += strlen(buf[i].name) + 1;
if (!new_fl) {
ret = -ENOMEM;
goto out;
}
new_fl->img = &buf[i];
if (data && data->origin)
buf[i].flash_base -= data->origin;
else
buf[i].flash_base &= master->size - 1;
/* I'm sure the JFFS2 code has done me permanent damage.
* I now think the following is _normal_
*/
prev = &fl;
while (*prev && (*prev)->img->flash_base < new_fl->img->flash_base)
prev = &(*prev)->next;
new_fl->next = *prev;
*prev = new_fl;
nrparts++;
}
#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
if (fl->img->flash_base) {
nrparts++;
nulllen = sizeof(nullstring);
}
for (tmp_fl = fl; tmp_fl->next; tmp_fl = tmp_fl->next) {
if (tmp_fl->img->flash_base + tmp_fl->img->size + master->erasesize <= tmp_fl->next->img->flash_base) {
nrparts++;
nulllen = sizeof(nullstring);
}
}
#endif
parts = kzalloc(sizeof(*parts) * nrparts + nulllen + namelen, GFP_KERNEL);
if (!parts) {
ret = -ENOMEM;
goto out;
}
nullname = (char *)&parts[nrparts];
#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
if (nulllen > 0)
strcpy(nullname, nullstring);
#endif
names = nullname + nulllen;
i = 0;
#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
if (fl->img->flash_base) {
parts[0].name = nullname;
parts[0].size = fl->img->flash_base;
parts[0].offset = 0;
i++;
}
#endif
for ( ; i < nrparts; i++) {
parts[i].size = fl->img->size;
parts[i].offset = fl->img->flash_base;
parts[i].name = names;
strcpy(names, fl->img->name);
#ifdef CONFIG_MTD_REDBOOT_PARTS_READONLY
if (!strcmp(names, "RedBoot") ||
!strcmp(names, "RedBoot config") ||
!strcmp(names, "FIS directory")) {
parts[i].mask_flags = MTD_WRITEABLE;
}
#endif
names += strlen(names) + 1;
#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
if (fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) {
i++;
parts[i].offset = parts[i - 1].size + parts[i - 1].offset;
parts[i].size = fl->next->img->flash_base - parts[i].offset;
parts[i].name = nullname;
}
#endif
tmp_fl = fl;
fl = fl->next;
kfree(tmp_fl);
}
ret = nrparts;
*pparts = parts;
out:
while (fl) {
struct fis_list *old = fl;
fl = fl->next;
kfree(old);
}
vfree(buf);
return ret;
}
static const struct of_device_id mtd_parser_redboot_of_match_table[] = {
{ .compatible = "redboot-fis" },
{},
};
MODULE_DEVICE_TABLE(of, mtd_parser_redboot_of_match_table);
static struct mtd_part_parser redboot_parser = {
.parse_fn = parse_redboot_partitions,
.name = "RedBoot",
.of_match_table = mtd_parser_redboot_of_match_table,
};
module_mtd_part_parser(redboot_parser);
/* mtd parsers will request the module by parser name */
MODULE_ALIAS("RedBoot");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
MODULE_DESCRIPTION("Parsing code for RedBoot Flash Image System (FIS) tables");
+248
View File
@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* drivers/mtd/scpart.c: Sercomm Partition Parser
*
* Copyright (C) 2018 NOGUCHI Hiroshi
* Copyright (C) 2022 Mikhail Zhilkin
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/module.h>
#define MOD_NAME "scpart"
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) MOD_NAME ": " fmt
#define ID_ALREADY_FOUND 0xffffffffUL
#define MAP_OFFS_IN_BLK 0x800
#define MAP_MIRROR_NUM 2
static const char sc_part_magic[] = {
'S', 'C', 'F', 'L', 'M', 'A', 'P', 'O', 'K', '\0',
};
#define PART_MAGIC_LEN sizeof(sc_part_magic)
/* assumes that all fields are set by CPU native endian */
struct sc_part_desc {
uint32_t part_id;
uint32_t part_offs;
uint32_t part_bytes;
};
static uint32_t scpart_desc_is_valid(struct sc_part_desc *pdesc)
{
return ((pdesc->part_id != 0xffffffffUL) &&
(pdesc->part_offs != 0xffffffffUL) &&
(pdesc->part_bytes != 0xffffffffUL));
}
static int scpart_scan_partmap(struct mtd_info *master, loff_t partmap_offs,
struct sc_part_desc **ppdesc)
{
int cnt = 0;
int res = 0;
int res2;
uint32_t offs;
size_t retlen;
struct sc_part_desc *pdesc = NULL;
struct sc_part_desc *tmpdesc;
uint8_t *buf;
buf = kzalloc(master->erasesize, GFP_KERNEL);
if (!buf) {
res = -ENOMEM;
goto out;
}
res2 = mtd_read(master, partmap_offs, master->erasesize, &retlen, buf);
if (res2 || retlen != master->erasesize) {
res = -EIO;
goto free;
}
for (offs = MAP_OFFS_IN_BLK;
offs < master->erasesize - sizeof(*tmpdesc);
offs += sizeof(*tmpdesc)) {
tmpdesc = (struct sc_part_desc *)&buf[offs];
if (!scpart_desc_is_valid(tmpdesc))
break;
cnt++;
}
if (cnt > 0) {
int bytes = cnt * sizeof(*pdesc);
pdesc = kzalloc_objs(*pdesc, cnt);
if (!pdesc) {
res = -ENOMEM;
goto free;
}
memcpy(pdesc, &(buf[MAP_OFFS_IN_BLK]), bytes);
*ppdesc = pdesc;
res = cnt;
}
free:
kfree(buf);
out:
return res;
}
static int scpart_find_partmap(struct mtd_info *master,
struct sc_part_desc **ppdesc)
{
int magic_found = 0;
int res = 0;
int res2;
loff_t offs = 0;
size_t retlen;
uint8_t rdbuf[PART_MAGIC_LEN];
while ((magic_found < MAP_MIRROR_NUM) &&
(offs < master->size) &&
!mtd_block_isbad(master, offs)) {
res2 = mtd_read(master, offs, PART_MAGIC_LEN, &retlen, rdbuf);
if (res2 || retlen != PART_MAGIC_LEN) {
res = -EIO;
goto out;
}
if (!memcmp(rdbuf, sc_part_magic, PART_MAGIC_LEN)) {
pr_debug("Signature found at 0x%llx\n", offs);
magic_found++;
res = scpart_scan_partmap(master, offs, ppdesc);
if (res > 0)
goto out;
}
offs += master->erasesize;
}
out:
if (res > 0)
pr_info("Valid 'SC PART MAP' (%d partitions) found at 0x%llx\n", res, offs);
else
pr_info("No valid 'SC PART MAP' was found\n");
return res;
}
static int scpart_parse(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
const char *partname;
int n;
int nr_scparts;
int nr_parts = 0;
int res = 0;
struct sc_part_desc *scpart_map = NULL;
struct mtd_partition *parts = NULL;
struct device_node *mtd_node;
struct device_node *ofpart_node;
struct device_node *pp;
mtd_node = mtd_get_of_node(master);
if (!mtd_node) {
res = -ENOENT;
goto out;
}
ofpart_node = of_get_child_by_name(mtd_node, "partitions");
if (!ofpart_node) {
pr_info("%s: 'partitions' subnode not found on %pOF.\n",
master->name, mtd_node);
res = -ENOENT;
goto out;
}
nr_scparts = scpart_find_partmap(master, &scpart_map);
if (nr_scparts <= 0) {
pr_info("No any partitions was found in 'SC PART MAP'.\n");
res = -ENOENT;
goto free;
}
parts = kzalloc_objs(*parts, of_get_child_count(ofpart_node));
if (!parts) {
res = -ENOMEM;
goto free;
}
for_each_child_of_node(ofpart_node, pp) {
u32 scpart_id;
if (of_property_read_u32(pp, "sercomm,scpart-id", &scpart_id))
continue;
for (n = 0 ; n < nr_scparts ; n++)
if ((scpart_map[n].part_id != ID_ALREADY_FOUND) &&
(scpart_id == scpart_map[n].part_id))
break;
if (n >= nr_scparts)
/* not match */
continue;
/* add the partition found in OF into MTD partition array */
parts[nr_parts].offset = scpart_map[n].part_offs;
parts[nr_parts].size = scpart_map[n].part_bytes;
parts[nr_parts].of_node = pp;
if (!of_property_read_string(pp, "label", &partname))
parts[nr_parts].name = partname;
if (of_property_read_bool(pp, "read-only"))
parts[nr_parts].mask_flags |= MTD_WRITEABLE;
if (of_property_read_bool(pp, "lock"))
parts[nr_parts].mask_flags |= MTD_POWERUP_LOCK;
/* mark as 'done' */
scpart_map[n].part_id = ID_ALREADY_FOUND;
nr_parts++;
}
if (nr_parts > 0) {
*pparts = parts;
res = nr_parts;
} else
pr_info("No partition in OF matches partition ID with 'SC PART MAP'.\n");
of_node_put(pp);
free:
of_node_put(ofpart_node);
kfree(scpart_map);
if (res <= 0)
kfree(parts);
out:
return res;
}
static const struct of_device_id scpart_parser_of_match_table[] = {
{ .compatible = "sercomm,sc-partitions" },
{},
};
MODULE_DEVICE_TABLE(of, scpart_parser_of_match_table);
static struct mtd_part_parser scpart_parser = {
.parse_fn = scpart_parse,
.name = "scpart",
.of_match_table = scpart_parser_of_match_table,
};
module_mtd_part_parser(scpart_parser);
/* mtd parsers will request the module by parser name */
MODULE_ALIAS("scpart");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NOGUCHI Hiroshi <drvlabo@gmail.com>");
MODULE_AUTHOR("Mikhail Zhilkin <csharper2005@gmail.com>");
MODULE_DESCRIPTION("Sercomm partition parser");
@@ -0,0 +1,398 @@
/*
* sharpslpart.c - MTD partition parser for NAND flash using the SHARP FTL
* for logical addressing, as used on the PXA models of the SHARP SL Series.
*
* Copyright (C) 2017 Andrea Adami <andrea.adami@gmail.com>
*
* Based on SHARP GPL 2.4 sources:
* http://support.ezaurus.com/developer/source/source_dl.asp
* drivers/mtd/nand/sharp_sl_logical.c
* linux/include/asm-arm/sharp_nand_logical.h
*
* Copyright (C) 2002 SHARP
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/bitops.h>
#include <linux/sizes.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
/* oob structure */
#define NAND_NOOB_LOGADDR_00 8
#define NAND_NOOB_LOGADDR_01 9
#define NAND_NOOB_LOGADDR_10 10
#define NAND_NOOB_LOGADDR_11 11
#define NAND_NOOB_LOGADDR_20 12
#define NAND_NOOB_LOGADDR_21 13
#define BLOCK_IS_RESERVED 0xffff
#define BLOCK_UNMASK_COMPLEMENT 1
/* factory defaults */
#define SHARPSL_NAND_PARTS 3
#define SHARPSL_FTL_PART_SIZE (7 * SZ_1M)
#define SHARPSL_PARTINFO1_LADDR 0x00060000
#define SHARPSL_PARTINFO2_LADDR 0x00064000
#define BOOT_MAGIC 0x424f4f54
#define FSRO_MAGIC 0x4653524f
#define FSRW_MAGIC 0x46535257
/**
* struct sharpsl_ftl - Sharp FTL Logical Table
* @logmax: number of logical blocks
* @log2phy: the logical-to-physical table
*
* Structure containing the logical-to-physical translation table
* used by the SHARP SL FTL.
*/
struct sharpsl_ftl {
unsigned int logmax;
unsigned int *log2phy;
};
/* verify that the OOB bytes 8 to 15 are free and available for the FTL */
static int sharpsl_nand_check_ooblayout(struct mtd_info *mtd)
{
u8 freebytes = 0;
int section = 0;
while (true) {
struct mtd_oob_region oobfree = { };
int ret, i;
ret = mtd_ooblayout_free(mtd, section++, &oobfree);
if (ret)
break;
if (!oobfree.length || oobfree.offset > 15 ||
(oobfree.offset + oobfree.length) < 8)
continue;
i = oobfree.offset >= 8 ? oobfree.offset : 8;
for (; i < oobfree.offset + oobfree.length && i < 16; i++)
freebytes |= BIT(i - 8);
if (freebytes == 0xff)
return 0;
}
return -ENOTSUPP;
}
static int sharpsl_nand_read_oob(struct mtd_info *mtd, loff_t offs, u8 *buf)
{
struct mtd_oob_ops ops = { };
int ret;
ops.mode = MTD_OPS_PLACE_OOB;
ops.ooblen = mtd->oobsize;
ops.oobbuf = buf;
ret = mtd_read_oob(mtd, offs, &ops);
if (ret != 0 || mtd->oobsize != ops.oobretlen)
return -1;
return 0;
}
/*
* The logical block number assigned to a physical block is stored in the OOB
* of the first page, in 3 16-bit copies with the following layout:
*
* 01234567 89abcdef
* -------- --------
* ECC BB xyxyxy
*
* When reading we check that the first two copies agree.
* In case of error, matching is tried using the following pairs.
* Reserved values 0xffff mean the block is kept for wear leveling.
*
* 01234567 89abcdef
* -------- --------
* ECC BB xyxy oob[8]==oob[10] && oob[9]==oob[11] -> byte0=8 byte1=9
* ECC BB xyxy oob[10]==oob[12] && oob[11]==oob[13] -> byte0=10 byte1=11
* ECC BB xy xy oob[12]==oob[8] && oob[13]==oob[9] -> byte0=12 byte1=13
*/
static int sharpsl_nand_get_logical_num(u8 *oob)
{
u16 us;
int good0, good1;
if (oob[NAND_NOOB_LOGADDR_00] == oob[NAND_NOOB_LOGADDR_10] &&
oob[NAND_NOOB_LOGADDR_01] == oob[NAND_NOOB_LOGADDR_11]) {
good0 = NAND_NOOB_LOGADDR_00;
good1 = NAND_NOOB_LOGADDR_01;
} else if (oob[NAND_NOOB_LOGADDR_10] == oob[NAND_NOOB_LOGADDR_20] &&
oob[NAND_NOOB_LOGADDR_11] == oob[NAND_NOOB_LOGADDR_21]) {
good0 = NAND_NOOB_LOGADDR_10;
good1 = NAND_NOOB_LOGADDR_11;
} else if (oob[NAND_NOOB_LOGADDR_20] == oob[NAND_NOOB_LOGADDR_00] &&
oob[NAND_NOOB_LOGADDR_21] == oob[NAND_NOOB_LOGADDR_01]) {
good0 = NAND_NOOB_LOGADDR_20;
good1 = NAND_NOOB_LOGADDR_21;
} else {
return -EINVAL;
}
us = oob[good0] | oob[good1] << 8;
/* parity check */
if (hweight16(us) & BLOCK_UNMASK_COMPLEMENT)
return -EINVAL;
/* reserved */
if (us == BLOCK_IS_RESERVED)
return BLOCK_IS_RESERVED;
return (us >> 1) & GENMASK(9, 0);
}
static int sharpsl_nand_init_ftl(struct mtd_info *mtd, struct sharpsl_ftl *ftl)
{
unsigned int block_num, phymax;
int i, ret, log_num;
loff_t block_adr;
u8 *oob;
oob = kzalloc(mtd->oobsize, GFP_KERNEL);
if (!oob)
return -ENOMEM;
phymax = mtd_div_by_eb(SHARPSL_FTL_PART_SIZE, mtd);
/* FTL reserves 5% of the blocks + 1 spare */
ftl->logmax = ((phymax * 95) / 100) - 1;
ftl->log2phy = kmalloc_array(ftl->logmax, sizeof(*ftl->log2phy),
GFP_KERNEL);
if (!ftl->log2phy) {
ret = -ENOMEM;
goto exit;
}
/* initialize ftl->log2phy */
for (i = 0; i < ftl->logmax; i++)
ftl->log2phy[i] = UINT_MAX;
/* create physical-logical table */
for (block_num = 0; block_num < phymax; block_num++) {
block_adr = (loff_t)block_num * mtd->erasesize;
if (mtd_block_isbad(mtd, block_adr))
continue;
if (sharpsl_nand_read_oob(mtd, block_adr, oob))
continue;
/* get logical block */
log_num = sharpsl_nand_get_logical_num(oob);
/* cut-off errors and skip the out-of-range values */
if (log_num > 0 && log_num < ftl->logmax) {
if (ftl->log2phy[log_num] == UINT_MAX)
ftl->log2phy[log_num] = block_num;
}
}
pr_info("Sharp SL FTL: %d blocks used (%d logical, %d reserved)\n",
phymax, ftl->logmax, phymax - ftl->logmax);
ret = 0;
exit:
kfree(oob);
return ret;
}
static void sharpsl_nand_cleanup_ftl(struct sharpsl_ftl *ftl)
{
kfree(ftl->log2phy);
}
static int sharpsl_nand_read_laddr(struct mtd_info *mtd,
loff_t from,
size_t len,
void *buf,
struct sharpsl_ftl *ftl)
{
unsigned int log_num, final_log_num;
unsigned int block_num;
loff_t block_adr;
loff_t block_ofs;
size_t retlen;
int err;
log_num = mtd_div_by_eb((u32)from, mtd);
final_log_num = mtd_div_by_eb(((u32)from + len - 1), mtd);
if (len <= 0 || log_num >= ftl->logmax || final_log_num > log_num)
return -EINVAL;
block_num = ftl->log2phy[log_num];
block_adr = (loff_t)block_num * mtd->erasesize;
block_ofs = mtd_mod_by_eb((u32)from, mtd);
err = mtd_read(mtd, block_adr + block_ofs, len, &retlen, buf);
/* Ignore corrected ECC errors */
if (mtd_is_bitflip(err))
err = 0;
if (!err && retlen != len)
err = -EIO;
if (err)
pr_err("sharpslpart: error, read failed at %#llx\n",
block_adr + block_ofs);
return err;
}
/*
* MTD Partition Parser
*
* Sample values read from SL-C860
*
* # cat /proc/mtd
* dev: size erasesize name
* mtd0: 006d0000 00020000 "Filesystem"
* mtd1: 00700000 00004000 "smf"
* mtd2: 03500000 00004000 "root"
* mtd3: 04400000 00004000 "home"
*
* PARTITIONINFO1
* 0x00060000: 00 00 00 00 00 00 70 00 42 4f 4f 54 00 00 00 00 ......p.BOOT....
* 0x00060010: 00 00 70 00 00 00 c0 03 46 53 52 4f 00 00 00 00 ..p.....FSRO....
* 0x00060020: 00 00 c0 03 00 00 00 04 46 53 52 57 00 00 00 00 ........FSRW....
*/
struct sharpsl_nand_partinfo {
__le32 start;
__le32 end;
__be32 magic;
u32 reserved;
};
static int sharpsl_nand_read_partinfo(struct mtd_info *master,
loff_t from,
size_t len,
struct sharpsl_nand_partinfo *buf,
struct sharpsl_ftl *ftl)
{
int ret;
ret = sharpsl_nand_read_laddr(master, from, len, buf, ftl);
if (ret)
return ret;
/* check for magics */
if (be32_to_cpu(buf[0].magic) != BOOT_MAGIC ||
be32_to_cpu(buf[1].magic) != FSRO_MAGIC ||
be32_to_cpu(buf[2].magic) != FSRW_MAGIC) {
pr_err("sharpslpart: magic values mismatch\n");
return -EINVAL;
}
/* fixup for hardcoded value 64 MiB (for older models) */
buf[2].end = cpu_to_le32(master->size);
/* extra sanity check */
if (le32_to_cpu(buf[0].end) <= le32_to_cpu(buf[0].start) ||
le32_to_cpu(buf[1].start) < le32_to_cpu(buf[0].end) ||
le32_to_cpu(buf[1].end) <= le32_to_cpu(buf[1].start) ||
le32_to_cpu(buf[2].start) < le32_to_cpu(buf[1].end) ||
le32_to_cpu(buf[2].end) <= le32_to_cpu(buf[2].start)) {
pr_err("sharpslpart: partition sizes mismatch\n");
return -EINVAL;
}
return 0;
}
static int sharpsl_parse_mtd_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct sharpsl_ftl ftl;
struct sharpsl_nand_partinfo buf[SHARPSL_NAND_PARTS];
struct mtd_partition *sharpsl_nand_parts;
int err;
/* check that OOB bytes 8 to 15 used by the FTL are actually free */
err = sharpsl_nand_check_ooblayout(master);
if (err)
return err;
/* init logical mgmt (FTL) */
err = sharpsl_nand_init_ftl(master, &ftl);
if (err)
return err;
/* read and validate first partition table */
pr_info("sharpslpart: try reading first partition table\n");
err = sharpsl_nand_read_partinfo(master,
SHARPSL_PARTINFO1_LADDR,
sizeof(buf), buf, &ftl);
if (err) {
/* fallback: read second partition table */
pr_warn("sharpslpart: first partition table is invalid, retry using the second\n");
err = sharpsl_nand_read_partinfo(master,
SHARPSL_PARTINFO2_LADDR,
sizeof(buf), buf, &ftl);
}
/* cleanup logical mgmt (FTL) */
sharpsl_nand_cleanup_ftl(&ftl);
if (err) {
pr_err("sharpslpart: both partition tables are invalid\n");
return err;
}
sharpsl_nand_parts = kzalloc_objs(*sharpsl_nand_parts,
SHARPSL_NAND_PARTS);
if (!sharpsl_nand_parts)
return -ENOMEM;
/* original names */
sharpsl_nand_parts[0].name = "smf";
sharpsl_nand_parts[0].offset = le32_to_cpu(buf[0].start);
sharpsl_nand_parts[0].size = le32_to_cpu(buf[0].end) -
le32_to_cpu(buf[0].start);
sharpsl_nand_parts[1].name = "root";
sharpsl_nand_parts[1].offset = le32_to_cpu(buf[1].start);
sharpsl_nand_parts[1].size = le32_to_cpu(buf[1].end) -
le32_to_cpu(buf[1].start);
sharpsl_nand_parts[2].name = "home";
sharpsl_nand_parts[2].offset = le32_to_cpu(buf[2].start);
sharpsl_nand_parts[2].size = le32_to_cpu(buf[2].end) -
le32_to_cpu(buf[2].start);
*pparts = sharpsl_nand_parts;
return SHARPSL_NAND_PARTS;
}
static struct mtd_part_parser sharpsl_mtd_parser = {
.parse_fn = sharpsl_parse_mtd_partitions,
.name = "sharpslpart",
};
module_mtd_part_parser(sharpsl_mtd_parser);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andrea Adami <andrea.adami@gmail.com>");
MODULE_DESCRIPTION("MTD partitioning for NAND flash on Sharp SL Series");
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright © 2022 Rafał Miłecki <rafal@milecki.pl>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/slab.h>
#define TPLINK_SAFELOADER_DATA_OFFSET 4
#define TPLINK_SAFELOADER_MAX_PARTS 32
struct safeloader_cmn_header {
__be32 size;
uint32_t unused;
} __packed;
static void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd)
{
struct safeloader_cmn_header hdr;
struct device_node *np;
size_t bytes_read;
size_t size;
u32 offset;
char *buf;
int err;
np = mtd_get_of_node(mtd);
if (mtd_is_partition(mtd))
of_node_get(np);
else
np = of_get_child_by_name(np, "partitions");
if (of_property_read_u32(np, "partitions-table-offset", &offset)) {
pr_err("Failed to get partitions table offset\n");
goto err_put;
}
err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr);
if (err && !mtd_is_bitflip(err)) {
pr_err("Failed to read from %s at 0x%x\n", mtd->name, offset);
goto err_put;
}
size = be32_to_cpu(hdr.size);
buf = kmalloc(size + 1, GFP_KERNEL);
if (!buf)
goto err_put;
err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf);
if (err && !mtd_is_bitflip(err)) {
pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr));
goto err_kfree;
}
buf[size] = '\0';
of_node_put(np);
return buf;
err_kfree:
kfree(buf);
err_put:
of_node_put(np);
return NULL;
}
static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
char name[65];
size_t offset;
size_t bytes;
char *buf;
int idx;
int err;
parts = kzalloc_objs(*parts, TPLINK_SAFELOADER_MAX_PARTS);
if (!parts) {
err = -ENOMEM;
goto err_out;
}
buf = mtd_parser_tplink_safeloader_read_table(mtd);
if (!buf) {
err = -ENOENT;
goto err_free_parts;
}
for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET;
idx < TPLINK_SAFELOADER_MAX_PARTS &&
sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n",
name, &parts[idx].offset, &parts[idx].size, &bytes) == 3;
idx++, offset += bytes + 1) {
parts[idx].name = kstrdup(name, GFP_KERNEL);
if (!parts[idx].name) {
err = -ENOMEM;
goto err_free;
}
}
if (idx == TPLINK_SAFELOADER_MAX_PARTS)
pr_warn("Reached maximum number of partitions!\n");
kfree(buf);
*pparts = parts;
return idx;
err_free:
kfree(buf);
for (idx -= 1; idx >= 0; idx--)
kfree(parts[idx].name);
err_free_parts:
kfree(parts);
err_out:
return err;
};
static void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts,
int nr_parts)
{
int i;
for (i = 0; i < nr_parts; i++)
kfree(pparts[i].name);
kfree(pparts);
}
static const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = {
{ .compatible = "tplink,safeloader-partitions" },
{},
};
MODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table);
static struct mtd_part_parser mtd_parser_tplink_safeloader = {
.parse_fn = mtd_parser_tplink_safeloader_parse,
.cleanup = mtd_parser_tplink_safeloader_cleanup,
.name = "tplink-safeloader",
.of_match_table = mtd_parser_tplink_safeloader_of_match_table,
};
module_mtd_part_parser(mtd_parser_tplink_safeloader);
MODULE_DESCRIPTION("TP-Link Safeloader partitions parser");
MODULE_LICENSE("GPL");