1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter.git synced 2025-03-09 15:40:20 +00:00

Update 6.12 kernel patches

This commit is contained in:
Ycarus (Yannick Chabanois) 2024-12-26 18:19:04 +01:00
parent bdb9b0046f
commit 9d83c70ced
247 changed files with 53301 additions and 589 deletions

View file

@ -0,0 +1,112 @@
config MTD_SPLIT
def_bool n
help
Generic MTD split support.
config MTD_SPLIT_SUPPORT
def_bool MTD = y
comment "Rootfs partition parsers"
config MTD_SPLIT_SQUASHFS_ROOT
bool "Squashfs based root partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
help
This provides a parsing function which allows to detect the
offset and size of the unused portion of a rootfs partition
containing a squashfs.
comment "Firmware partition parsers"
config MTD_SPLIT_BCM63XX_FW
bool "BCM63xx firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_BCM_WFI_FW
bool "Broadcom Whole Flash Image parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_CFE_BOOTFS
bool "Parser finding rootfs appended to the CFE bootfs"
depends on MTD_SPLIT_SUPPORT && (ARCH_BCM4908 || ARCH_BCMBCA)
select MTD_SPLIT
help
cferom on BCM4908 (and bcm63xx) uses JFFS2 bootfs partition
for storing kernel, cferam and some device specific files.
There isn't any straight way of storing rootfs so it gets
appended to the JFFS2 bootfs partition. Kernel needs to find
it and run init from it. This parser is responsible for
finding appended rootfs.
config MTD_SPLIT_SEAMA_FW
bool "Seama firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_WRGG_FW
bool "WRGG firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_UIMAGE_FW
bool "uImage based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_FIT_FW
bool "FIT based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_LZMA_FW
bool "LZMA compressed kernel based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_TPLINK_FW
bool "TP-Link firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_TRX_FW
bool "TRX image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_BRNIMAGE_FW
bool "brnImage (brnboot image) firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_EVA_FW
bool "EVA image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_MINOR_FW
bool "Mikrotik NOR image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_JIMAGE_FW
bool "JBOOT Image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_ELF_FW
bool "ELF loader firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_H3C_VFS
bool "Parser finding rootfs appended to H3C VFS"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_SEIL_FW
bool "IIJ SEIL firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT

View file

@ -0,0 +1,19 @@
obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o
obj-$(CONFIG_MTD_SPLIT_BCM63XX_FW) += mtdsplit_bcm63xx.o
obj-$(CONFIG_MTD_SPLIT_BCM_WFI_FW) += mtdsplit_bcm_wfi.o
obj-$(CONFIG_MTD_SPLIT_CFE_BOOTFS) += mtdsplit_cfe_bootfs.o
obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o
obj-$(CONFIG_MTD_SPLIT_SEIL_FW) += mtdsplit_seil.o
obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o
obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o
obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o
obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o
obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o
obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o
obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o
obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o
obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o
obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o
obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o
obj-$(CONFIG_MTD_SPLIT_ELF_FW) += mtdsplit_elf.o
obj-$(CONFIG_MTD_SPLIT_H3C_VFS) += mtdsplit_h3c_vfs.o

View file

@ -0,0 +1,130 @@
/*
* Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) "mtdsplit: " fmt
#include <linux/export.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/magic.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
#define UBI_EC_MAGIC 0x55424923 /* UBI# */
struct squashfs_super_block {
__le32 s_magic;
__le32 pad0[9];
__le64 bytes_used;
};
int mtd_get_squashfs_len(struct mtd_info *master,
size_t offset,
size_t *squashfs_len)
{
struct squashfs_super_block sb;
size_t retlen;
int err;
err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb);
if (err || (retlen != sizeof(sb))) {
pr_alert("error occured while reading from \"%s\"\n",
master->name);
return -EIO;
}
if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) {
pr_alert("no squashfs found in \"%s\"\n", master->name);
return -EINVAL;
}
retlen = le64_to_cpu(sb.bytes_used);
if (retlen <= 0) {
pr_alert("squashfs is empty in \"%s\"\n", master->name);
return -ENODEV;
}
if (offset + retlen > master->size) {
pr_alert("squashfs has invalid size in \"%s\"\n",
master->name);
return -EINVAL;
}
*squashfs_len = retlen;
return 0;
}
EXPORT_SYMBOL_GPL(mtd_get_squashfs_len);
static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset)
{
return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize;
}
int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
enum mtdsplit_part_type *type)
{
u32 magic;
size_t retlen;
int ret;
ret = mtd_read(mtd, offset, sizeof(magic), &retlen,
(unsigned char *) &magic);
if (ret)
return ret;
if (retlen != sizeof(magic))
return -EIO;
if (le32_to_cpu(magic) == SQUASHFS_MAGIC) {
if (type)
*type = MTDSPLIT_PART_TYPE_SQUASHFS;
return 0;
} else if (magic == 0x19852003) {
if (type)
*type = MTDSPLIT_PART_TYPE_JFFS2;
return 0;
} else if (be32_to_cpu(magic) == UBI_EC_MAGIC) {
if (type)
*type = MTDSPLIT_PART_TYPE_UBI;
return 0;
}
return -EINVAL;
}
EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic);
int mtd_find_rootfs_from(struct mtd_info *mtd,
size_t from,
size_t limit,
size_t *ret_offset,
enum mtdsplit_part_type *type)
{
size_t offset;
int err;
for (offset = from; offset < limit;
offset = mtd_next_eb(mtd, offset)) {
err = mtd_check_rootfs_magic(mtd, offset, type);
if (err)
continue;
*ret_offset = offset;
return 0;
}
return -ENODEV;
}
EXPORT_SYMBOL_GPL(mtd_find_rootfs_from);

View file

@ -0,0 +1,67 @@
/*
* Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#ifndef _MTDSPLIT_H
#define _MTDSPLIT_H
#define KERNEL_PART_NAME "kernel"
#define ROOTFS_PART_NAME "rootfs"
#define UBI_PART_NAME "ubi"
#define ROOTFS_SPLIT_NAME "rootfs_data"
enum mtdsplit_part_type {
MTDSPLIT_PART_TYPE_UNK = 0,
MTDSPLIT_PART_TYPE_SQUASHFS,
MTDSPLIT_PART_TYPE_JFFS2,
MTDSPLIT_PART_TYPE_UBI,
};
#ifdef CONFIG_MTD_SPLIT
int mtd_get_squashfs_len(struct mtd_info *master,
size_t offset,
size_t *squashfs_len);
int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
enum mtdsplit_part_type *type);
int mtd_find_rootfs_from(struct mtd_info *mtd,
size_t from,
size_t limit,
size_t *ret_offset,
enum mtdsplit_part_type *type);
#else
static inline int mtd_get_squashfs_len(struct mtd_info *master,
size_t offset,
size_t *squashfs_len)
{
return -ENODEV;
}
static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
enum mtdsplit_part_type *type)
{
return -EINVAL;
}
static inline int mtd_find_rootfs_from(struct mtd_info *mtd,
size_t from,
size_t limit,
size_t *ret_offset,
enum mtdsplit_part_type *type)
{
return -ENODEV;
}
#endif /* CONFIG_MTD_SPLIT */
#endif /* _MTDSPLIT_H */

View file

@ -0,0 +1,186 @@
/*
* Firmware MTD split for BCM63XX, based on bcm63xxpart.c
*
* Copyright (C) 2006-2008 Florian Fainelli <florian@openwrt.org>
* Copyright (C) 2006-2008 Mike Albon <malbon@openwrt.org>
* Copyright (C) 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net>
* Copyright (C) 2011-2013 Jonas Gorski <jonas.gorski@gmail.com>
* Copyright (C) 2015 Simon Arlott <simon@fire.lp0.eu>
* Copyright (C) 2017 Álvaro Fernández Rojas <noltari@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bcm963xx_tag.h>
#include <linux/crc32.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/byteorder/generic.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include "mtdsplit.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)
#define BCM63XX_NR_PARTS 2
static int bcm63xx_read_image_tag(struct mtd_info *master, loff_t offset,
struct bcm_tag *hdr)
{
int ret;
size_t retlen;
u32 computed_crc;
ret = mtd_read(master, offset, sizeof(*hdr), &retlen, (void *) hdr);
if (ret)
return ret;
if (retlen != sizeof(*hdr))
return -EIO;
computed_crc = crc32_le(IMAGETAG_CRC_START, (u8 *)hdr,
offsetof(struct bcm_tag, header_crc));
if (computed_crc == hdr->header_crc) {
STR_NULL_TERMINATE(hdr->board_id);
STR_NULL_TERMINATE(hdr->tag_version);
pr_info("CFE image tag found at 0x%llx with version %s, "
"board type %s\n", offset, hdr->tag_version,
hdr->board_id);
return 0;
} else {
pr_err("CFE image tag at 0x%llx CRC invalid "
"(expected %08x, actual %08x)\n",
offset, hdr->header_crc, computed_crc);
return 1;
}
}
static int bcm63xx_parse_partitions(struct mtd_info *master,
const struct mtd_partition **pparts,
struct bcm_tag *hdr)
{
struct mtd_partition *parts;
unsigned int flash_image_start;
unsigned int kernel_address;
unsigned int kernel_length;
size_t kernel_offset = 0, kernel_size = 0;
size_t rootfs_offset = 0, rootfs_size = 0;
int kernel_part, rootfs_part;
STR_NULL_TERMINATE(hdr->flash_image_start);
if (kstrtouint(hdr->flash_image_start, 10, &flash_image_start) ||
flash_image_start < BCM963XX_EXTENDED_SIZE) {
pr_err("invalid rootfs address: %*ph\n",
(int) sizeof(hdr->flash_image_start),
hdr->flash_image_start);
return -EINVAL;
}
STR_NULL_TERMINATE(hdr->kernel_address);
if (kstrtouint(hdr->kernel_address, 10, &kernel_address) ||
kernel_address < BCM963XX_EXTENDED_SIZE) {
pr_err("invalid kernel address: %*ph\n",
(int) sizeof(hdr->kernel_address), hdr->kernel_address);
return -EINVAL;
}
STR_NULL_TERMINATE(hdr->kernel_length);
if (kstrtouint(hdr->kernel_length, 10, &kernel_length) ||
!kernel_length) {
pr_err("invalid kernel length: %*ph\n",
(int) sizeof(hdr->kernel_length), hdr->kernel_length);
return -EINVAL;
}
kernel_offset = kernel_address - BCM963XX_EXTENDED_SIZE -
mtdpart_get_offset(master);
kernel_size = kernel_length;
if (flash_image_start < kernel_address) {
/* rootfs first */
rootfs_part = 0;
kernel_part = 1;
rootfs_offset = flash_image_start - BCM963XX_EXTENDED_SIZE -
mtdpart_get_offset(master);
rootfs_size = kernel_offset - rootfs_offset;
} else {
/* kernel first */
kernel_part = 0;
rootfs_part = 1;
rootfs_offset = kernel_offset + kernel_size;
rootfs_size = master->size - rootfs_offset;
}
if (mtd_check_rootfs_magic(master, rootfs_offset, NULL))
pr_warn("rootfs magic not found\n");
parts = kzalloc(BCM63XX_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[kernel_part].name = KERNEL_PART_NAME;
parts[kernel_part].offset = kernel_offset;
parts[kernel_part].size = kernel_size;
parts[rootfs_part].name = ROOTFS_PART_NAME;
parts[rootfs_part].offset = rootfs_offset;
parts[rootfs_part].size = rootfs_size;
*pparts = parts;
return BCM63XX_NR_PARTS;
}
static int mtdsplit_parse_bcm63xx(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct bcm_tag hdr;
loff_t offset;
if (mtd_type_is_nand(master))
return -EINVAL;
/* find bcm63xx_cfe image on erase block boundaries */
for (offset = 0; offset < master->size; offset += master->erasesize) {
if (!bcm63xx_read_image_tag(master, offset, (void *) &hdr))
return bcm63xx_parse_partitions(master, pparts,
(void *) &hdr);
}
return -EINVAL;
}
static const struct of_device_id mtdsplit_fit_of_match_table[] = {
{ .compatible = "brcm,bcm963xx-imagetag" },
{ },
};
static struct mtd_part_parser mtdsplit_bcm63xx_parser = {
.owner = THIS_MODULE,
.name = "bcm63xx-fw",
.of_match_table = mtdsplit_fit_of_match_table,
.parse_fn = mtdsplit_parse_bcm63xx,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_bcm63xx_init(void)
{
register_mtd_parser(&mtdsplit_bcm63xx_parser);
return 0;
}
module_init(mtdsplit_bcm63xx_init);

View file

@ -0,0 +1,535 @@
/*
* MTD split for Broadcom Whole Flash Image
*
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define je16_to_cpu(x) ((x).v16)
#define je32_to_cpu(x) ((x).v32)
#include <linux/crc32.h>
#include <linux/init.h>
#include <linux/jffs2.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/byteorder/generic.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include "mtdsplit.h"
#define char_to_num(c) ((c >= '0' && c <= '9') ? (c - '0') : (0))
#define BCM_WFI_PARTS 3
#define BCM_WFI_SPLIT_PARTS 2
#define CFERAM_NAME "cferam"
#define CFERAM_NAME_LEN (sizeof(CFERAM_NAME) - 1)
#define CFERAM_NAME_MAX_LEN 32
#define KERNEL_NAME "vmlinux.lz"
#define KERNEL_NAME_LEN (sizeof(KERNEL_NAME) - 1)
#define OPENWRT_NAME "1-openwrt"
#define OPENWRT_NAME_LEN (sizeof(OPENWRT_NAME) - 1)
#define UBI_MAGIC 0x55424923
#define CFE_MAGIC_PFX "cferam."
#define CFE_MAGIC_PFX_LEN (sizeof(CFE_MAGIC_PFX) - 1)
#define CFE_MAGIC "cferam.000"
#define CFE_MAGIC_LEN (sizeof(CFE_MAGIC) - 1)
#define SERCOMM_MAGIC_PFX "eRcOmM."
#define SERCOMM_MAGIC_PFX_LEN (sizeof(SERCOMM_MAGIC_PFX) - 1)
#define SERCOMM_MAGIC "eRcOmM.000"
#define SERCOMM_MAGIC_LEN (sizeof(SERCOMM_MAGIC) - 1)
#define PART_CFERAM "cferam"
#define PART_FIRMWARE "firmware"
#define PART_IMAGE_1 "img1"
#define PART_IMAGE_2 "img2"
static u32 jffs2_dirent_crc(struct jffs2_raw_dirent *node)
{
return crc32(0, node, sizeof(struct jffs2_raw_dirent) - 8);
}
static bool jffs2_dirent_valid(struct jffs2_raw_dirent *node)
{
return ((je16_to_cpu(node->magic) == JFFS2_MAGIC_BITMASK) &&
(je16_to_cpu(node->nodetype) == JFFS2_NODETYPE_DIRENT) &&
je32_to_cpu(node->ino) &&
je32_to_cpu(node->node_crc) == jffs2_dirent_crc(node));
}
static int jffs2_find_file(struct mtd_info *mtd, uint8_t *buf,
const char *name, size_t name_len,
loff_t *offs, loff_t size,
char **out_name, size_t *out_name_len)
{
const loff_t end = *offs + size;
struct jffs2_raw_dirent *node;
bool valid = false;
size_t retlen;
uint16_t magic;
int rc;
for (; *offs < end; *offs += mtd->erasesize) {
unsigned int block_offs = 0;
/* Skip CFE erased blocks */
rc = mtd_read(mtd, *offs, sizeof(magic), &retlen,
(void *) &magic);
if (rc || retlen != sizeof(magic)) {
continue;
}
/* Skip blocks not starting with JFFS2 magic */
if (magic != JFFS2_MAGIC_BITMASK)
continue;
/* Read full block */
rc = mtd_read(mtd, *offs, mtd->erasesize, &retlen,
(void *) buf);
if (rc)
return rc;
if (retlen != mtd->erasesize)
return -EINVAL;
while (block_offs < mtd->erasesize) {
node = (struct jffs2_raw_dirent *) &buf[block_offs];
if (!jffs2_dirent_valid(node)) {
block_offs += 4;
continue;
}
if (!memcmp(node->name, OPENWRT_NAME,
OPENWRT_NAME_LEN)) {
valid = true;
} else if (!memcmp(node->name, name, name_len)) {
if (!valid)
return -EINVAL;
if (out_name)
*out_name = kstrndup(node->name,
node->nsize,
GFP_KERNEL);
if (out_name_len)
*out_name_len = node->nsize;
return 0;
}
block_offs += je32_to_cpu(node->totlen);
block_offs = (block_offs + 0x3) & ~0x3;
}
}
return -ENOENT;
}
static int ubifs_find(struct mtd_info *mtd, loff_t *offs, loff_t size)
{
const loff_t end = *offs + size;
uint32_t magic;
size_t retlen;
int rc;
for (; *offs < end; *offs += mtd->erasesize) {
rc = mtd_read(mtd, *offs, sizeof(magic), &retlen,
(unsigned char *) &magic);
if (rc || retlen != sizeof(magic))
continue;
if (be32_to_cpu(magic) == UBI_MAGIC)
return 0;
}
return -ENOENT;
}
static int parse_bcm_wfi(struct mtd_info *master,
const struct mtd_partition **pparts,
uint8_t *buf, loff_t off, loff_t size, bool cfe_part)
{
struct device_node *mtd_node;
struct mtd_partition *parts;
loff_t cfe_off, kernel_off, rootfs_off;
unsigned int num_parts = BCM_WFI_PARTS, cur_part = 0;
const char *cferam_name = CFERAM_NAME;
size_t cferam_name_len;
int ret;
mtd_node = mtd_get_of_node(master);
if (mtd_node)
of_property_read_string(mtd_node, "brcm,cferam", &cferam_name);
cferam_name_len = strnlen(cferam_name, CFERAM_NAME_MAX_LEN);
if (cferam_name_len > 0)
cferam_name_len--;
if (cfe_part) {
num_parts++;
cfe_off = off;
ret = jffs2_find_file(master, buf, cferam_name,
cferam_name_len, &cfe_off,
size - (cfe_off - off), NULL, NULL);
if (ret)
return ret;
kernel_off = cfe_off + master->erasesize;
} else {
kernel_off = off;
}
ret = jffs2_find_file(master, buf, KERNEL_NAME, KERNEL_NAME_LEN,
&kernel_off, size - (kernel_off - off),
NULL, NULL);
if (ret)
return ret;
rootfs_off = kernel_off + master->erasesize;
ret = ubifs_find(master, &rootfs_off, size - (rootfs_off - off));
if (ret)
return ret;
parts = kzalloc(num_parts * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
if (cfe_part) {
parts[cur_part].name = PART_CFERAM;
parts[cur_part].mask_flags = MTD_WRITEABLE;
parts[cur_part].offset = cfe_off;
parts[cur_part].size = kernel_off - cfe_off;
cur_part++;
}
parts[cur_part].name = PART_FIRMWARE;
parts[cur_part].offset = kernel_off;
parts[cur_part].size = size - (kernel_off - off);
cur_part++;
parts[cur_part].name = KERNEL_PART_NAME;
parts[cur_part].offset = kernel_off;
parts[cur_part].size = rootfs_off - kernel_off;
cur_part++;
parts[cur_part].name = UBI_PART_NAME;
parts[cur_part].offset = rootfs_off;
parts[cur_part].size = size - (rootfs_off - off);
cur_part++;
*pparts = parts;
return num_parts;
}
static int mtdsplit_parse_bcm_wfi(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *mtd_node;
bool cfe_part = true;
uint8_t *buf;
int ret;
mtd_node = mtd_get_of_node(master);
if (!mtd_node)
return -EINVAL;
buf = kzalloc(master->erasesize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (of_property_read_bool(mtd_node, "brcm,no-cferam"))
cfe_part = false;
ret = parse_bcm_wfi(master, pparts, buf, 0, master->size, cfe_part);
kfree(buf);
return ret;
}
static const struct of_device_id mtdsplit_bcm_wfi_of_match[] = {
{ .compatible = "brcm,wfi" },
{ },
};
static struct mtd_part_parser mtdsplit_bcm_wfi_parser = {
.owner = THIS_MODULE,
.name = "bcm-wfi-fw",
.of_match_table = mtdsplit_bcm_wfi_of_match,
.parse_fn = mtdsplit_parse_bcm_wfi,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int cferam_bootflag_value(const char *name, size_t name_len)
{
int rc = -ENOENT;
if (name &&
(name_len >= CFE_MAGIC_LEN) &&
!memcmp(name, CFE_MAGIC_PFX, CFE_MAGIC_PFX_LEN)) {
rc = char_to_num(name[CFE_MAGIC_PFX_LEN + 0]) * 100;
rc += char_to_num(name[CFE_MAGIC_PFX_LEN + 1]) * 10;
rc += char_to_num(name[CFE_MAGIC_PFX_LEN + 2]) * 1;
}
return rc;
}
static int mtdsplit_parse_bcm_wfi_split(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
loff_t cfe_off;
loff_t img1_off = 0;
loff_t img2_off = master->size / 2;
loff_t img1_size = (img2_off - img1_off);
loff_t img2_size = (master->size - img2_off);
loff_t active_off, inactive_off;
loff_t active_size, inactive_size;
const char *inactive_name;
uint8_t *buf;
char *cfe1_name = NULL, *cfe2_name = NULL;
size_t cfe1_size = 0, cfe2_size = 0;
int ret;
int bf1, bf2;
buf = kzalloc(master->erasesize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
cfe_off = img1_off;
ret = jffs2_find_file(master, buf, CFERAM_NAME, CFERAM_NAME_LEN,
&cfe_off, img1_size, &cfe1_name, &cfe1_size);
cfe_off = img2_off;
ret = jffs2_find_file(master, buf, CFERAM_NAME, CFERAM_NAME_LEN,
&cfe_off, img2_size, &cfe2_name, &cfe2_size);
bf1 = cferam_bootflag_value(cfe1_name, cfe1_size);
if (bf1 >= 0)
printk("cferam: bootflag1=%d\n", bf1);
bf2 = cferam_bootflag_value(cfe2_name, cfe2_size);
if (bf2 >= 0)
printk("cferam: bootflag2=%d\n", bf2);
kfree(cfe1_name);
kfree(cfe2_name);
if (bf1 >= bf2) {
active_off = img1_off;
active_size = img1_size;
inactive_off = img2_off;
inactive_size = img2_size;
inactive_name = PART_IMAGE_2;
} else {
active_off = img2_off;
active_size = img2_size;
inactive_off = img1_off;
inactive_size = img1_size;
inactive_name = PART_IMAGE_1;
}
ret = parse_bcm_wfi(master, pparts, buf, active_off, active_size, true);
kfree(buf);
if (ret > 0) {
parts = kzalloc((ret + 1) * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
memcpy(parts, *pparts, ret * sizeof(*parts));
kfree(*pparts);
parts[ret].name = inactive_name;
parts[ret].offset = inactive_off;
parts[ret].size = inactive_size;
ret++;
*pparts = parts;
} else {
parts = kzalloc(BCM_WFI_SPLIT_PARTS * sizeof(*parts), GFP_KERNEL);
parts[0].name = PART_IMAGE_1;
parts[0].offset = img1_off;
parts[0].size = img1_size;
parts[1].name = PART_IMAGE_2;
parts[1].offset = img2_off;
parts[1].size = img2_size;
*pparts = parts;
}
return ret;
}
static const struct of_device_id mtdsplit_bcm_wfi_split_of_match[] = {
{ .compatible = "brcm,wfi-split" },
{ },
};
static struct mtd_part_parser mtdsplit_bcm_wfi_split_parser = {
.owner = THIS_MODULE,
.name = "bcm-wfi-split-fw",
.of_match_table = mtdsplit_bcm_wfi_split_of_match,
.parse_fn = mtdsplit_parse_bcm_wfi_split,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int sercomm_bootflag_value(struct mtd_info *mtd, uint8_t *buf)
{
size_t retlen;
loff_t offs;
int rc;
for (offs = 0; offs < mtd->size; offs += mtd->erasesize) {
rc = mtd_read(mtd, offs, SERCOMM_MAGIC_LEN, &retlen, buf);
if (rc || retlen != SERCOMM_MAGIC_LEN)
continue;
if (memcmp(buf, SERCOMM_MAGIC_PFX, SERCOMM_MAGIC_PFX_LEN))
continue;
rc = char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 0]) * 100;
rc += char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 1]) * 10;
rc += char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 2]) * 1;
return rc;
}
return -ENOENT;
}
static int mtdsplit_parse_ser_wfi(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
struct mtd_info *mtd_bf1, *mtd_bf2;
loff_t img1_off = 0;
loff_t img2_off = master->size / 2;
loff_t img1_size = (img2_off - img1_off);
loff_t img2_size = (master->size - img2_off);
loff_t active_off, inactive_off;
loff_t active_size, inactive_size;
const char *inactive_name;
uint8_t *buf;
int bf1, bf2;
int ret;
mtd_bf1 = get_mtd_device_nm("bootflag1");
if (IS_ERR(mtd_bf1))
return -ENOENT;
mtd_bf2 = get_mtd_device_nm("bootflag2");
if (IS_ERR(mtd_bf2))
return -ENOENT;
buf = kzalloc(master->erasesize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
bf1 = sercomm_bootflag_value(mtd_bf1, buf);
if (bf1 >= 0)
printk("sercomm: bootflag1=%d\n", bf1);
bf2 = sercomm_bootflag_value(mtd_bf2, buf);
if (bf2 >= 0)
printk("sercomm: bootflag2=%d\n", bf2);
if (bf1 == bf2 && bf2 >= 0) {
struct erase_info bf_erase;
bf2 = -ENOENT;
bf_erase.addr = 0;
bf_erase.len = mtd_bf2->size;
mtd_erase(mtd_bf2, &bf_erase);
}
if (bf1 >= bf2) {
active_off = img1_off;
active_size = img1_size;
inactive_off = img2_off;
inactive_size = img2_size;
inactive_name = PART_IMAGE_2;
} else {
active_off = img2_off;
active_size = img2_size;
inactive_off = img1_off;
inactive_size = img1_size;
inactive_name = PART_IMAGE_1;
}
ret = parse_bcm_wfi(master, pparts, buf, active_off, active_size, false);
kfree(buf);
if (ret > 0) {
parts = kzalloc((ret + 1) * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
memcpy(parts, *pparts, ret * sizeof(*parts));
kfree(*pparts);
parts[ret].name = inactive_name;
parts[ret].offset = inactive_off;
parts[ret].size = inactive_size;
ret++;
*pparts = parts;
} else {
parts = kzalloc(BCM_WFI_SPLIT_PARTS * sizeof(*parts), GFP_KERNEL);
parts[0].name = PART_IMAGE_1;
parts[0].offset = img1_off;
parts[0].size = img1_size;
parts[1].name = PART_IMAGE_2;
parts[1].offset = img2_off;
parts[1].size = img2_size;
*pparts = parts;
}
return ret;
}
static const struct of_device_id mtdsplit_ser_wfi_of_match[] = {
{ .compatible = "sercomm,wfi" },
{ },
};
static struct mtd_part_parser mtdsplit_ser_wfi_parser = {
.owner = THIS_MODULE,
.name = "ser-wfi-fw",
.of_match_table = mtdsplit_ser_wfi_of_match,
.parse_fn = mtdsplit_parse_ser_wfi,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_bcm_wfi_init(void)
{
register_mtd_parser(&mtdsplit_bcm_wfi_parser);
register_mtd_parser(&mtdsplit_bcm_wfi_split_parser);
register_mtd_parser(&mtdsplit_ser_wfi_parser);
return 0;
}
module_init(mtdsplit_bcm_wfi_init);

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2012 John Crispin <blogic@openwrt.org>
* Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
#define BRNIMAGE_NR_PARTS 2
#define BRNIMAGE_ALIGN_BYTES 0x400
#define BRNIMAGE_FOOTER_SIZE 12
#define BRNIMAGE_MIN_OVERHEAD (BRNIMAGE_FOOTER_SIZE)
#define BRNIMAGE_MAX_OVERHEAD (BRNIMAGE_ALIGN_BYTES + BRNIMAGE_FOOTER_SIZE)
static int mtdsplit_parse_brnimage(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
uint32_t buf;
unsigned long rootfs_offset, rootfs_size, kernel_size;
size_t len;
int ret = 0;
for (rootfs_offset = 0; rootfs_offset < master->size;
rootfs_offset += BRNIMAGE_ALIGN_BYTES) {
ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
if (!ret)
break;
}
if (ret)
return ret;
if (rootfs_offset >= master->size)
return -EINVAL;
ret = mtd_read(master, rootfs_offset - BRNIMAGE_FOOTER_SIZE, 4, &len,
(void *)&buf);
if (ret)
return ret;
if (len != 4)
return -EIO;
kernel_size = le32_to_cpu(buf);
if (kernel_size > (rootfs_offset - BRNIMAGE_MIN_OVERHEAD))
return -EINVAL;
if (kernel_size < (rootfs_offset - BRNIMAGE_MAX_OVERHEAD))
return -EINVAL;
/*
* The footer must be untouched as it contains the checksum of the
* original brnImage (kernel + squashfs)!
*/
rootfs_size = master->size - rootfs_offset - BRNIMAGE_FOOTER_SIZE;
parts = kzalloc(BRNIMAGE_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = kernel_size;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = rootfs_size;
*pparts = parts;
return BRNIMAGE_NR_PARTS;
}
static struct mtd_part_parser mtdsplit_brnimage_parser = {
.owner = THIS_MODULE,
.name = "brnimage-fw",
.parse_fn = mtdsplit_parse_brnimage,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_brnimage_init(void)
{
register_mtd_parser(&mtdsplit_brnimage_parser);
return 0;
}
subsys_initcall(mtdsplit_brnimage_init);

View file

@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
*/
#include <linux/init.h>
#include <linux/jffs2.h>
#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>
#include "mtdsplit.h"
#define je16_to_cpu(x) ((x).v16)
#define je32_to_cpu(x) ((x).v32)
#define NR_PARTS 2
static int mtdsplit_cfe_bootfs_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct jffs2_raw_dirent node;
enum mtdsplit_part_type type;
struct mtd_partition *parts;
size_t rootfs_offset;
size_t retlen;
size_t offset;
int err;
/* Don't parse backup partitions */
if (strcmp(mtd->name, "firmware"))
return -EINVAL;
/* Find the end of JFFS2 bootfs partition */
offset = 0;
do {
err = mtd_read(mtd, offset, sizeof(node), &retlen, (void *)&node);
if (err || retlen != sizeof(node))
break;
if (je16_to_cpu(node.magic) != JFFS2_MAGIC_BITMASK)
break;
offset += je32_to_cpu(node.totlen);
offset = (offset + 0x3) & ~0x3;
} while (offset < mtd->size);
/* Find rootfs partition that follows the bootfs */
err = mtd_find_rootfs_from(mtd, mtd->erasesize, mtd->size, &rootfs_offset, &type);
if (err)
return err;
parts = kzalloc(NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = "bootfs";
parts[0].offset = 0;
parts[0].size = rootfs_offset;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[1].name = UBI_PART_NAME;
else
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = mtd->size - rootfs_offset;
*pparts = parts;
return NR_PARTS;
}
static const struct of_device_id mtdsplit_cfe_bootfs_of_match_table[] = {
{ .compatible = "brcm,bcm4908-firmware" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_cfe_bootfs_of_match_table);
static struct mtd_part_parser mtdsplit_cfe_bootfs_parser = {
.owner = THIS_MODULE,
.name = "cfe-bootfs",
.of_match_table = mtdsplit_cfe_bootfs_of_match_table,
.parse_fn = mtdsplit_cfe_bootfs_parse,
};
module_mtd_part_parser(mtdsplit_cfe_bootfs_parser);

View file

@ -0,0 +1,287 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* MTD splitter for ELF loader firmware partitions
*
* Copyright (C) 2020 Sander Vanheule <sander@svanheule.net>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; version 2.
*
* To parse the ELF kernel loader, a small ELF parser is used that can
* handle both ELF32 or ELF64 class loaders. The splitter assumes that the
* kernel is always located before the rootfs, whether it is embedded in the
* loader or not.
*
* The kernel image is preferably embedded inside the ELF loader, so the end
* of the loader equals the end of the kernel partition. This is due to the
* way mtd_find_rootfs_from searches for the the rootfs:
* - if the kernel image is embedded in the loader, the appended rootfs may
* follow the loader immediately, within the same erase block.
* - if the kernel image is not embedded in the loader, but placed at some
* offset behind the loader (OKLI-style loader), the rootfs must be
* aligned to an erase-block after the loader and kernel image.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
#define ELF_NR_PARTS 2
#define ELF_MAGIC 0x7f454c46 /* 0x7f E L F */
#define ELF_CLASS_32 1
#define ELF_CLASS_64 2
struct elf_header_ident {
uint32_t magic;
uint8_t class;
uint8_t data;
uint8_t version;
uint8_t osabi;
uint8_t abiversion;
uint8_t pad[7];
};
struct elf_header_32 {
uint16_t type;
uint16_t machine;
uint32_t version;
uint32_t entry;
uint32_t phoff;
uint32_t shoff;
uint32_t flags;
uint16_t ehsize;
uint16_t phentsize;
uint16_t phnum;
uint16_t shentsize;
uint16_t shnum;
uint16_t shstrndx;
};
struct elf_header_64 {
uint16_t type;
uint16_t machine;
uint32_t version;
uint64_t entry;
uint64_t phoff;
uint64_t shoff;
uint32_t flags;
uint16_t ehsize;
uint16_t phentsize;
uint16_t phnum;
uint16_t shentsize;
uint16_t shnum;
uint16_t shstrndx;
};
struct elf_header {
struct elf_header_ident ident;
union {
struct elf_header_32 elf32;
struct elf_header_64 elf64;
};
};
struct elf_program_header_32 {
uint32_t type;
uint32_t offset;
uint32_t vaddr;
uint32_t paddr;
uint32_t filesize;
uint32_t memsize;
uint32_t flags;
};
struct elf_program_header_64 {
uint32_t type;
uint32_t flags;
uint64_t offset;
uint64_t vaddr;
uint64_t paddr;
uint64_t filesize;
uint64_t memsize;
};
static int mtdsplit_elf_read_mtd(struct mtd_info *mtd, size_t offset,
uint8_t *dst, size_t len)
{
size_t retlen;
int ret;
ret = mtd_read(mtd, offset, len, &retlen, dst);
if (ret) {
pr_debug("read error in \"%s\"\n", mtd->name);
return ret;
}
if (retlen != len) {
pr_debug("short read in \"%s\"\n", mtd->name);
return -EIO;
}
return 0;
}
static int elf32_determine_size(struct mtd_info *mtd, struct elf_header *hdr,
size_t *size)
{
struct elf_header_32 *hdr32 = &(hdr->elf32);
int err;
size_t section_end, ph_table_end, ph_entry;
struct elf_program_header_32 ph;
*size = 0;
if (hdr32->shoff > 0) {
*size = hdr32->shoff + hdr32->shentsize * hdr32->shnum;
return 0;
}
ph_entry = hdr32->phoff;
ph_table_end = hdr32->phoff + hdr32->phentsize * hdr32->phnum;
while (ph_entry < ph_table_end) {
err = mtdsplit_elf_read_mtd(mtd, ph_entry, (uint8_t *)(&ph),
sizeof(ph));
if (err)
return err;
section_end = ph.offset + ph.filesize;
if (section_end > *size)
*size = section_end;
ph_entry += hdr32->phentsize;
}
return 0;
}
static int elf64_determine_size(struct mtd_info *mtd, struct elf_header *hdr,
size_t *size)
{
struct elf_header_64 *hdr64 = &(hdr->elf64);
int err;
size_t section_end, ph_table_end, ph_entry;
struct elf_program_header_64 ph;
*size = 0;
if (hdr64->shoff > 0) {
*size = hdr64->shoff + hdr64->shentsize * hdr64->shnum;
return 0;
}
ph_entry = hdr64->phoff;
ph_table_end = hdr64->phoff + hdr64->phentsize * hdr64->phnum;
while (ph_entry < ph_table_end) {
err = mtdsplit_elf_read_mtd(mtd, ph_entry, (uint8_t *)(&ph),
sizeof(ph));
if (err)
return err;
section_end = ph.offset + ph.filesize;
if (section_end > *size)
*size = section_end;
ph_entry += hdr64->phentsize;
}
return 0;
}
static int mtdsplit_parse_elf(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct elf_header hdr;
size_t loader_size, rootfs_offset;
enum mtdsplit_part_type type;
struct mtd_partition *parts;
int err;
err = mtdsplit_elf_read_mtd(mtd, 0, (uint8_t *)&hdr, sizeof(hdr));
if (err)
return err;
if (be32_to_cpu(hdr.ident.magic) != ELF_MAGIC) {
pr_debug("invalid ELF magic %08x\n",
be32_to_cpu(hdr.ident.magic));
return -EINVAL;
}
switch (hdr.ident.class) {
case ELF_CLASS_32:
err = elf32_determine_size(mtd, &hdr, &loader_size);
break;
case ELF_CLASS_64:
err = elf64_determine_size(mtd, &hdr, &loader_size);
break;
default:
pr_debug("invalid ELF class %i\n", hdr.ident.class);
err = -EINVAL;
}
if (err)
return err;
err = mtd_find_rootfs_from(mtd, loader_size, mtd->size,
&rootfs_offset, &type);
if (err)
return err;
if (rootfs_offset == mtd->size) {
pr_debug("no rootfs found in \"%s\"\n", mtd->name);
return -ENODEV;
}
parts = kzalloc(ELF_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[1].name = UBI_PART_NAME;
else
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = mtd->size - rootfs_offset;
*pparts = parts;
return ELF_NR_PARTS;
}
static const struct of_device_id mtdsplit_elf_of_match_table[] = {
{ .compatible = "openwrt,elf" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_elf_of_match_table);
static struct mtd_part_parser mtdsplit_elf_parser = {
.owner = THIS_MODULE,
.name = "elf-loader-fw",
.of_match_table = mtdsplit_elf_of_match_table,
.parse_fn = mtdsplit_parse_elf,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_elf_init(void)
{
register_mtd_parser(&mtdsplit_elf_parser);
return 0;
}
subsys_initcall(mtdsplit_elf_init);

View file

@ -0,0 +1,103 @@
/*
* Copyright (C) 2012 John Crispin <blogic@openwrt.org>
* Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define EVA_NR_PARTS 2
#define EVA_MAGIC 0xfeed1281
#define EVA_FOOTER_SIZE 0x18
#define EVA_DUMMY_SQUASHFS_SIZE 0x100
struct eva_image_header {
uint32_t magic;
uint32_t size;
};
static int mtdsplit_parse_eva(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
struct eva_image_header hdr;
size_t retlen;
unsigned long kernel_size, rootfs_offset;
int err;
err = mtd_read(master, 0, sizeof(hdr), &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != sizeof(hdr))
return -EIO;
if (le32_to_cpu(hdr.magic) != EVA_MAGIC)
return -EINVAL;
kernel_size = le32_to_cpu(hdr.size) + EVA_FOOTER_SIZE;
/* rootfs starts at the next 0x10000 boundary: */
rootfs_offset = round_up(kernel_size, 0x10000);
/* skip the dummy EVA squashfs partition (with wrong endianness): */
rootfs_offset += EVA_DUMMY_SQUASHFS_SIZE;
if (rootfs_offset >= master->size)
return -EINVAL;
err = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
if (err)
return err;
parts = kzalloc(EVA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = kernel_size;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return EVA_NR_PARTS;
}
static const struct of_device_id mtdsplit_eva_of_match_table[] = {
{ .compatible = "avm,eva-firmware" },
{},
};
static struct mtd_part_parser mtdsplit_eva_parser = {
.owner = THIS_MODULE,
.name = "eva-fw",
.of_match_table = mtdsplit_eva_of_match_table,
.parse_fn = mtdsplit_parse_eva,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_eva_init(void)
{
register_mtd_parser(&mtdsplit_eva_parser);
return 0;
}
subsys_initcall(mtdsplit_eva_init);

View file

@ -0,0 +1,365 @@
/*
* Copyright (c) 2015 The Linux Foundation
* Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/types.h>
#include <linux/byteorder/generic.h>
#include <linux/slab.h>
#include <linux/libfdt.h>
#include <linux/of_fdt.h>
#include "mtdsplit.h"
// string macros from git://git.denx.de/u-boot.git/include/image.h
#define FIT_IMAGES_PATH "/images"
#define FIT_DATA_PROP "data"
#define FIT_DATA_POSITION_PROP "data-position"
#define FIT_DATA_OFFSET_PROP "data-offset"
#define FIT_DATA_SIZE_PROP "data-size"
// functions from git://git.denx.de/u-boot.git/common/image-fit.c
/**
* fit_image_get_data - get data property and its size for a given component image node
* @fit: pointer to the FIT format image header
* @noffset: component image node offset
* @data: double pointer to void, will hold data property's data address
* @size: pointer to size_t, will hold data property's data size
*
* fit_image_get_data() finds data property in a given component image node.
* If the property is found its data start address and size are returned to
* the caller.
*
* returns:
* 0, on success
* -1, on failure
*/
static int fit_image_get_data(const void *fit, int noffset,
const void **data, size_t *size)
{
int len;
*data = fdt_getprop(fit, noffset, FIT_DATA_PROP, &len);
if (*data == NULL) {
*size = 0;
return -1;
}
*size = len;
return 0;
}
/**
* Get 'data-offset' property from a given image node.
*
* @fit: pointer to the FIT image header
* @noffset: component image node offset
* @data_offset: holds the data-offset property
*
* returns:
* 0, on success
* -ENOENT if the property could not be found
*/
static int fit_image_get_data_offset(const void *fit, int noffset, int *data_offset)
{
const fdt32_t *val;
val = fdt_getprop(fit, noffset, FIT_DATA_OFFSET_PROP, NULL);
if (!val)
return -ENOENT;
*data_offset = fdt32_to_cpu(*val);
return 0;
}
/**
* Get 'data-position' property from a given image node.
*
* @fit: pointer to the FIT image header
* @noffset: component image node offset
* @data_position: holds the data-position property
*
* returns:
* 0, on success
* -ENOENT if the property could not be found
*/
static int fit_image_get_data_position(const void *fit, int noffset,
int *data_position)
{
const fdt32_t *val;
val = fdt_getprop(fit, noffset, FIT_DATA_POSITION_PROP, NULL);
if (!val)
return -ENOENT;
*data_position = fdt32_to_cpu(*val);
return 0;
}
/**
* Get 'data-size' property from a given image node.
*
* @fit: pointer to the FIT image header
* @noffset: component image node offset
* @data_size: holds the data-size property
*
* returns:
* 0, on success
* -ENOENT if the property could not be found
*/
static int fit_image_get_data_size(const void *fit, int noffset, int *data_size)
{
const fdt32_t *val;
val = fdt_getprop(fit, noffset, FIT_DATA_SIZE_PROP, NULL);
if (!val)
return -ENOENT;
*data_size = fdt32_to_cpu(*val);
return 0;
}
/**
* fit_image_get_data_and_size - get data and its size including
* both embedded and external data
* @fit: pointer to the FIT format image header
* @noffset: component image node offset
* @data: double pointer to void, will hold data property's data address
* @size: pointer to size_t, will hold data property's data size
*
* fit_image_get_data_and_size() finds data and its size including
* both embedded and external data. If the property is found
* its data start address and size are returned to the caller.
*
* returns:
* 0, on success
* otherwise, on failure
*/
static int fit_image_get_data_and_size(const void *fit, int noffset,
const void **data, size_t *size)
{
bool external_data = false;
int offset;
int len;
int ret;
if (!fit_image_get_data_position(fit, noffset, &offset)) {
external_data = true;
} else if (!fit_image_get_data_offset(fit, noffset, &offset)) {
external_data = true;
/*
* For FIT with external data, figure out where
* the external images start. This is the base
* for the data-offset properties in each image.
*/
offset += ((fdt_totalsize(fit) + 3) & ~3);
}
if (external_data) {
ret = fit_image_get_data_size(fit, noffset, &len);
if (!ret) {
*data = fit + offset;
*size = len;
}
} else {
ret = fit_image_get_data(fit, noffset, data, size);
}
return ret;
}
static int
mtdsplit_fit_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);
const char *cmdline_match = NULL;
struct fdt_header hdr;
size_t hdr_len, retlen;
size_t offset;
u32 offset_start = 0;
size_t fit_offset, fit_size;
size_t rootfs_offset, rootfs_size;
size_t data_size, img_total, max_size = 0;
struct mtd_partition *parts;
int ret, ndepth, noffset, images_noffset;
const void *img_data;
void *fit;
of_property_read_string(np, "openwrt,cmdline-match", &cmdline_match);
if (cmdline_match && !strstr(saved_command_line, cmdline_match))
return -ENODEV;
of_property_read_u32(np, "openwrt,fit-offset", &offset_start);
hdr_len = sizeof(struct fdt_header);
/* Parse the MTD device & search for the FIT image location */
for(offset = 0; offset + hdr_len <= mtd->size; offset += mtd->erasesize) {
ret = mtd_read(mtd, offset + offset_start, hdr_len, &retlen, (void*) &hdr);
if (ret) {
pr_err("read error in \"%s\" at offset 0x%llx\n",
mtd->name, (unsigned long long) offset);
return ret;
}
if (retlen != hdr_len) {
pr_err("short read in \"%s\"\n", mtd->name);
return -EIO;
}
/* Check the magic - see if this is a FIT image */
if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) {
pr_debug("no valid FIT image found in \"%s\" at offset %llx\n",
mtd->name, (unsigned long long) offset);
continue;
}
/* We found a FIT image. Let's keep going */
break;
}
fit_offset = offset;
fit_size = be32_to_cpu(hdr.totalsize);
if (fit_size == 0) {
pr_err("FIT image in \"%s\" at offset %llx has null size\n",
mtd->name, (unsigned long long) fit_offset);
return -ENODEV;
}
/*
* Classic uImage.FIT has all data embedded into the FDT
* data structure. Hence the total size of the image equals
* the total size of the FDT structure.
* Modern uImage.FIT may have only references to data in FDT,
* hence we need to parse FDT structure to find the end of the
* last external data refernced.
*/
if (fit_size > 0x1000) {
enum mtdsplit_part_type type;
/* Search for the rootfs partition after the FIT image */
ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size + offset_start, mtd->size,
&rootfs_offset, &type);
if (ret) {
pr_info("no rootfs found after FIT image in \"%s\"\n",
mtd->name);
return ret;
}
rootfs_size = mtd->size - rootfs_offset;
parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = fit_offset;
parts[0].size = mtd_roundup_to_eb(fit_size + offset_start, mtd);
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[1].name = UBI_PART_NAME;
else
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = rootfs_size;
*pparts = parts;
return 2;
} else {
/* Search for rootfs_data after FIT external data */
fit = kzalloc(fit_size, GFP_KERNEL);
ret = mtd_read(mtd, offset, fit_size + offset_start, &retlen, fit);
if (ret) {
pr_err("read error in \"%s\" at offset 0x%llx\n",
mtd->name, (unsigned long long) offset);
return ret;
}
images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
if (images_noffset < 0) {
pr_err("Can't find images parent node '%s' (%s)\n",
FIT_IMAGES_PATH, fdt_strerror(images_noffset));
return -ENODEV;
}
for (ndepth = 0,
noffset = fdt_next_node(fit, images_noffset, &ndepth);
(noffset >= 0) && (ndepth > 0);
noffset = fdt_next_node(fit, noffset, &ndepth)) {
if (ndepth == 1) {
ret = fit_image_get_data_and_size(fit, noffset, &img_data, &data_size);
if (ret)
return 0;
img_total = data_size + (img_data - fit);
max_size = (max_size > img_total) ? max_size : img_total;
}
}
parts = kzalloc(sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = ROOTFS_SPLIT_NAME;
parts[0].offset = fit_offset + mtd_roundup_to_eb(max_size, mtd);
parts[0].size = mtd->size - parts[0].offset;
*pparts = parts;
kfree(fit);
return 1;
}
}
static const struct of_device_id mtdsplit_fit_of_match_table[] = {
{ .compatible = "denx,fit" },
{},
};
static struct mtd_part_parser uimage_parser = {
.owner = THIS_MODULE,
.name = "fit-fw",
.of_match_table = mtdsplit_fit_of_match_table,
.parse_fn = mtdsplit_fit_parse,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
/**************************************************
* Init
**************************************************/
static int __init mtdsplit_fit_init(void)
{
register_mtd_parser(&uimage_parser);
return 0;
}
module_init(mtdsplit_fit_init);

View file

@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Some devices made by H3C use a "VFS" filesystem to store firmware images.
* This parses the start of the filesystem to read the length of the first
* file (the kernel image). It then searches for the rootfs after the end of
* the file data. This driver assumes that the filesystem was generated by
* mkh3cvfs, and only works if the filesystem matches the expected layout,
* which includes the file name of the kernel image.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/types.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include "mtdsplit.h"
#define VFS_ERASEBLOCK_SIZE 0x10000
#define VFS_BLOCK_SIZE 0x400
#define VFS_BLOCKS_PER_ERASEBLOCK (VFS_ERASEBLOCK_SIZE / VFS_BLOCK_SIZE)
#define FORMAT_FLAG_OFFSET 0x0
#define FORMAT_FLAG (VFS_ERASEBLOCK_SIZE << 12 | VFS_BLOCK_SIZE)
#define FILE_ENTRY_OFFSET 0x800
#define FILE_ENTRY_FLAGS 0x3f
#define FILE_ENTRY_PARENT_BLOCK 0
#define FILE_ENTRY_PARENT_INDEX 0
#define FILE_ENTRY_DATA_BLOCK 2
#define FILE_ENTRY_NAME "openwrt-kernel.bin"
#define NR_PARTS 2
struct file_entry {
uint8_t flags;
uint8_t res0[5];
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t res1[3];
uint32_t length;
uint32_t parent_block;
uint16_t parent_index;
uint8_t res2[2];
uint32_t data_block;
char name[96];
} __attribute__ ((packed));
static inline size_t block_offset(int block)
{
return VFS_ERASEBLOCK_SIZE * (block / (VFS_BLOCKS_PER_ERASEBLOCK-1))
+ VFS_BLOCK_SIZE * (1 + (block % (VFS_BLOCKS_PER_ERASEBLOCK-1)));
}
static inline int block_count(size_t size)
{
return (size + VFS_BLOCK_SIZE - 1) / VFS_BLOCK_SIZE;
}
static int mtdsplit_h3c_vfs_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
uint32_t format_flag;
struct file_entry file_entry;
size_t retlen;
int err;
size_t kernel_size;
size_t expected_offset;
size_t rootfs_offset;
if (mtd->erasesize != VFS_ERASEBLOCK_SIZE)
return -EINVAL;
/* Check format flag */
err = mtd_read(mtd, FORMAT_FLAG_OFFSET, sizeof(format_flag), &retlen,
(void *) &format_flag);
if (err)
return err;
if (retlen != sizeof(format_flag))
return -EIO;
if (format_flag != FORMAT_FLAG)
return -EINVAL;
/* Check file entry */
err = mtd_read(mtd, FILE_ENTRY_OFFSET, sizeof(file_entry), &retlen,
(void *) &file_entry);
if (err)
return err;
if (retlen != sizeof(file_entry))
return -EIO;
if (file_entry.flags != FILE_ENTRY_FLAGS)
return -EINVAL;
if (file_entry.parent_block != FILE_ENTRY_PARENT_BLOCK)
return -EINVAL;
if (file_entry.parent_index != FILE_ENTRY_PARENT_INDEX)
return -EINVAL;
if (file_entry.data_block != FILE_ENTRY_DATA_BLOCK)
return -EINVAL;
if (strncmp(file_entry.name, FILE_ENTRY_NAME, sizeof(file_entry.name)) != 0)
return -EINVAL;
/* Find rootfs offset */
kernel_size = block_offset(file_entry.data_block +
block_count(file_entry.length) - 1) +
VFS_BLOCK_SIZE;
expected_offset = mtd_roundup_to_eb(kernel_size, mtd);
err = mtd_find_rootfs_from(mtd, expected_offset, mtd->size,
&rootfs_offset, NULL);
if (err)
return err;
parts = kzalloc(NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = mtd->size - rootfs_offset;
*pparts = parts;
return NR_PARTS;
}
static const struct of_device_id mtdsplit_h3c_vfs_of_match_table[] = {
{ .compatible = "h3c,vfs-firmware" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_h3c_vfs_of_match_table);
static struct mtd_part_parser mtdsplit_h3c_vfs_parser = {
.owner = THIS_MODULE,
.name = "h3c-vfs",
.of_match_table = mtdsplit_h3c_vfs_of_match_table,
.parse_fn = mtdsplit_h3c_vfs_parse,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
module_mtd_part_parser(mtdsplit_h3c_vfs_parser);

View file

@ -0,0 +1,284 @@
/*
* Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com>
*
* Based on: mtdsplit_uimage.c
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE )
#define STAG_SIZE 16
#define STAG_ID 0x04
#define STAG_MAGIC 0x2B24
#define SCH2_SIZE 40
#define SCH2_MAGIC 0x2124
#define SCH2_VER 0x02
/*
* Jboot image header,
* all data in little endian.
*/
struct jimage_header //stag + sch2 jboot joined headers
{
uint8_t stag_cmark; // in factory 0xFF , in sysupgrade must be the same as stag_id
uint8_t stag_id; // 0x04
uint16_t stag_magic; //magic 0x2B24
uint32_t stag_time_stamp; // timestamp calculated in jboot way
uint32_t stag_image_length; // lentgh of kernel + sch2 header
uint16_t stag_image_checksum; // negated jboot_checksum of sch2 + kernel
uint16_t stag_tag_checksum; // negated jboot_checksum of stag header data
uint16_t sch2_magic; // magic 0x2124
uint8_t sch2_cp_type; // 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma
uint8_t sch2_version; // 0x02 for sch2
uint32_t sch2_ram_addr; // ram entry address
uint32_t sch2_image_len; // kernel image length
uint32_t sch2_image_crc32; // kernel image crc
uint32_t sch2_start_addr; // ram start address
uint32_t sch2_rootfs_addr; // rootfs flash address
uint32_t sch2_rootfs_len; // rootfls length
uint32_t sch2_rootfs_crc32; // rootfs crc32
uint32_t sch2_header_crc32; // sch2 header crc32, durring calculation this area is replaced by zero
uint16_t sch2_header_length; // sch2 header length: 0x28
uint16_t sch2_cmd_line_length; // cmd line length, known zeros
};
static int
read_jimage_header(struct mtd_info *mtd, size_t offset, u_char *buf,
size_t header_len)
{
size_t retlen;
int ret;
ret = mtd_read(mtd, offset, header_len, &retlen, buf);
if (ret) {
pr_debug("read error in \"%s\"\n", mtd->name);
return ret;
}
if (retlen != header_len) {
pr_debug("short read in \"%s\"\n", mtd->name);
return -EIO;
}
return 0;
}
/**
* __mtdsplit_parse_jimage - scan partition and create kernel + rootfs parts
*
* @find_header: function to call for a block of data that will return offset
* of a valid jImage header if found
*/
static int __mtdsplit_parse_jimage(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data,
ssize_t (*find_header)(u_char *buf, size_t len))
{
struct mtd_partition *parts;
u_char *buf;
int nr_parts;
size_t offset;
size_t jimage_offset;
size_t jimage_size = 0;
size_t rootfs_offset;
size_t rootfs_size = 0;
int jimage_part, rf_part;
int ret;
enum mtdsplit_part_type type;
nr_parts = 2;
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
buf = vmalloc(MAX_HEADER_LEN);
if (!buf) {
ret = -ENOMEM;
goto err_free_parts;
}
/* find jImage on erase block boundaries */
for (offset = 0; offset < master->size; offset += master->erasesize) {
struct jimage_header *header;
jimage_size = 0;
ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN);
if (ret)
continue;
ret = find_header(buf, MAX_HEADER_LEN);
if (ret < 0) {
pr_debug("no valid jImage found in \"%s\" at offset %llx\n",
master->name, (unsigned long long) offset);
continue;
}
header = (struct jimage_header *)(buf + ret);
jimage_size = sizeof(*header) + header->sch2_image_len + ret;
if ((offset + jimage_size) > master->size) {
pr_debug("jImage exceeds MTD device \"%s\"\n",
master->name);
continue;
}
break;
}
if (jimage_size == 0) {
pr_debug("no jImage found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_buf;
}
jimage_offset = offset;
if (jimage_offset == 0) {
jimage_part = 0;
rf_part = 1;
/* find the roots after the jImage */
ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size,
master->size, &rootfs_offset, &type);
if (ret) {
pr_debug("no rootfs after jImage in \"%s\"\n",
master->name);
goto err_free_buf;
}
rootfs_size = master->size - rootfs_offset;
jimage_size = rootfs_offset - jimage_offset;
} else {
rf_part = 0;
jimage_part = 1;
/* check rootfs presence at offset 0 */
ret = mtd_check_rootfs_magic(master, 0, &type);
if (ret) {
pr_debug("no rootfs before jImage in \"%s\"\n",
master->name);
goto err_free_buf;
}
rootfs_offset = 0;
rootfs_size = jimage_offset;
}
if (rootfs_size == 0) {
pr_debug("no rootfs found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_buf;
}
parts[jimage_part].name = KERNEL_PART_NAME;
parts[jimage_part].offset = jimage_offset;
parts[jimage_part].size = jimage_size;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[rf_part].name = UBI_PART_NAME;
else
parts[rf_part].name = ROOTFS_PART_NAME;
parts[rf_part].offset = rootfs_offset;
parts[rf_part].size = rootfs_size;
vfree(buf);
*pparts = parts;
return nr_parts;
err_free_buf:
vfree(buf);
err_free_parts:
kfree(parts);
return ret;
}
static ssize_t jimage_verify_default(u_char *buf, size_t len)
{
struct jimage_header *header = (struct jimage_header *)buf;
/* default sanity checks */
if (header->stag_magic != STAG_MAGIC) {
pr_debug("invalid jImage stag header magic: %04x\n",
header->stag_magic);
return -EINVAL;
}
if (header->sch2_magic != SCH2_MAGIC) {
pr_debug("invalid jImage sch2 header magic: %04x\n",
header->stag_magic);
return -EINVAL;
}
if (header->stag_cmark != header->stag_id) {
pr_debug("invalid jImage stag header cmark: %02x\n",
header->stag_magic);
return -EINVAL;
}
if (header->stag_id != STAG_ID) {
pr_debug("invalid jImage stag header id: %02x\n",
header->stag_magic);
return -EINVAL;
}
if (header->sch2_version != SCH2_VER) {
pr_debug("invalid jImage sch2 header version: %02x\n",
header->stag_magic);
return -EINVAL;
}
return 0;
}
static int
mtdsplit_jimage_parse_generic(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
return __mtdsplit_parse_jimage(master, pparts, data,
jimage_verify_default);
}
static const struct of_device_id mtdsplit_jimage_of_match_table[] = {
{ .compatible = "amit,jimage" },
{},
};
static struct mtd_part_parser jimage_generic_parser = {
.owner = THIS_MODULE,
.name = "jimage-fw",
.of_match_table = mtdsplit_jimage_of_match_table,
.parse_fn = mtdsplit_jimage_parse_generic,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
/**************************************************
* Init
**************************************************/
static int __init mtdsplit_jimage_init(void)
{
register_mtd_parser(&jimage_generic_parser);
return 0;
}
module_init(mtdsplit_jimage_init);

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <asm/unaligned.h>
#include "mtdsplit.h"
#define LZMA_NR_PARTS 2
#define LZMA_PROPERTIES_SIZE 5
struct lzma_header {
u8 props[LZMA_PROPERTIES_SIZE];
u8 size_low[4];
u8 size_high[4];
};
static int mtdsplit_parse_lzma(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct lzma_header hdr;
size_t hdr_len, retlen;
size_t rootfs_offset;
u32 t;
struct mtd_partition *parts;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != hdr_len)
return -EIO;
/* verify LZMA properties */
if (hdr.props[0] >= (9 * 5 * 5))
return -EINVAL;
t = get_unaligned_le32(&hdr.props[1]);
if (!is_power_of_2(t))
return -EINVAL;
t = get_unaligned_le32(&hdr.size_high);
if (t)
return -EINVAL;
err = mtd_find_rootfs_from(master, master->erasesize, master->size,
&rootfs_offset, NULL);
if (err)
return err;
parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return LZMA_NR_PARTS;
}
static const struct of_device_id mtdsplit_lzma_of_match_table[] = {
{ .compatible = "lzma" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_lzma_of_match_table);
static struct mtd_part_parser mtdsplit_lzma_parser = {
.owner = THIS_MODULE,
.name = "lzma-fw",
.of_match_table = mtdsplit_lzma_of_match_table,
.parse_fn = mtdsplit_parse_lzma,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_lzma_init(void)
{
register_mtd_parser(&mtdsplit_lzma_parser);
return 0;
}
subsys_initcall(mtdsplit_lzma_init);

View file

@ -0,0 +1,142 @@
/*
* MTD splitter for MikroTik NOR devices
*
* Copyright (C) 2017 Thibaut VARENE <varenet@parisc-linux.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* The rootfs is expected at erase-block boundary due to the use of
* mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header
* for two main reasons:
* - the original header uses weakly defined types (int, enum...) which can
* vary in length depending on build host (and the struct is not packed),
* and the name field can have a different total length depending on
* whether or not the yaffs code was _built_ with unicode support.
* - the only field that could be of real use here (file_size_low) contains
* invalid data in the header generated by kernel2minor, so we cannot use
* it to infer the exact position of the rootfs and do away with
* mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs).
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/string.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define YAFFS_OBJECT_TYPE_FILE 0x1
#define YAFFS_OBJECTID_ROOT 0x1
#define YAFFS_SUM_UNUSED 0xFFFF
#define YAFFS_MAX_NAME_LENGTH 127
#define YAFFS_NAME_KERNEL "kernel"
#define YAFFS_NAME_BOOTIMAGE "bootimage"
#define MINOR_NR_PARTS 2
/*
* This structure is based on yaffs_obj_hdr from yaffs_guts.h
* The weak types match upstream. The fields have cpu-endianness
*/
struct minor_header {
int yaffs_type;
int yaffs_obj_id;
u16 yaffs_sum_unused;
char yaffs_name[YAFFS_MAX_NAME_LENGTH];
};
static int mtdsplit_parse_minor(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct minor_header hdr;
size_t hdr_len, retlen;
size_t rootfs_offset;
struct mtd_partition *parts;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err) {
pr_err("MiNOR mtd_read error: %d\n", err);
return err;
}
if (retlen != hdr_len) {
pr_err("MiNOR mtd_read too short\n");
return -EIO;
}
/* match header */
if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE) {
pr_info("MiNOR YAFFS first type not matched\n");
return 0;
}
if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT) {
pr_info("MiNOR YAFFS first objectid not matched\n");
return 0;
}
if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED) {
pr_info("MiNOR YAFFS first sum not matched\n");
return 0;
}
if ((memcmp(hdr.yaffs_name, YAFFS_NAME_KERNEL, sizeof(YAFFS_NAME_KERNEL))) &&
(memcmp(hdr.yaffs_name, YAFFS_NAME_BOOTIMAGE, sizeof(YAFFS_NAME_BOOTIMAGE)))) {
pr_info("MiNOR YAFFS first name not matched\n");
return 0;
}
err = mtd_find_rootfs_from(master, master->erasesize, master->size,
&rootfs_offset, NULL);
if (err) {
pr_info("MiNOR mtd_find_rootfs_from error: %d\n", err);
return 0;
}
parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return MINOR_NR_PARTS;
}
static const struct of_device_id mtdsplit_minor_of_match_table[] = {
{ .compatible = "mikrotik,minor" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_minor_of_match_table);
static struct mtd_part_parser mtdsplit_minor_parser = {
.owner = THIS_MODULE,
.name = "minor-fw",
.of_match_table = mtdsplit_minor_of_match_table,
.parse_fn = mtdsplit_parse_minor,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_minor_init(void)
{
register_mtd_parser(&mtdsplit_minor_parser);
return 0;
}
subsys_initcall(mtdsplit_minor_init);

View file

@ -0,0 +1,118 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define SEAMA_MAGIC 0x5EA3A417
#define SEAMA_NR_PARTS 2
#define SEAMA_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
struct seama_header {
__be32 magic; /* should always be SEAMA_MAGIC. */
__be16 reserved; /* reserved for */
__be16 metasize; /* size of the META data */
__be32 size; /* size of the image */
u8 md5[16]; /* digest */
};
static int mtdsplit_parse_seama(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct seama_header hdr;
size_t hdr_len, retlen, kernel_ent_size;
size_t rootfs_offset;
struct mtd_partition *parts;
enum mtdsplit_part_type type;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != hdr_len)
return -EIO;
/* sanity checks */
if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC)
return -EINVAL;
kernel_ent_size = hdr_len + be32_to_cpu(hdr.size) +
be16_to_cpu(hdr.metasize);
if (kernel_ent_size > master->size)
return -EINVAL;
/* Check for the rootfs right after Seama entity with a kernel. */
err = mtd_check_rootfs_magic(master, kernel_ent_size, &type);
if (!err) {
rootfs_offset = kernel_ent_size;
} else {
/*
* On some devices firmware entity might contain both: kernel
* and rootfs. We can't determine kernel size so we just have to
* look for rootfs magic.
* Start the search from an arbitrary offset.
*/
err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS,
master->size, &rootfs_offset, &type);
if (err)
return err;
}
parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = sizeof hdr + be16_to_cpu(hdr.metasize);
parts[0].size = rootfs_offset - parts[0].offset;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[1].name = UBI_PART_NAME;
else
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return SEAMA_NR_PARTS;
}
static const struct of_device_id mtdsplit_seama_of_match_table[] = {
{ .compatible = "seama" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_seama_of_match_table);
static struct mtd_part_parser mtdsplit_seama_parser = {
.owner = THIS_MODULE,
.name = "seama-fw",
.of_match_table = mtdsplit_seama_of_match_table,
.parse_fn = mtdsplit_parse_seama,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_seama_init(void)
{
register_mtd_parser(&mtdsplit_seama_parser);
return 0;
}
subsys_initcall(mtdsplit_seama_init);

View file

@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* a mtdsplit driver for IIJ SEIL devices */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/of.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
#define NR_PARTS 2
#define SEIL_VFMT 1
#define LDR_ENV_PART_NAME "bootloader-env"
#define LDR_ENV_KEY_BOOTDEV "BOOTDEV"
struct seil_header {
uint64_t id; /* Identifier */
uint8_t copy[80]; /* Copyright */
uint32_t dcrc; /* Data CRC Checksum */
uint32_t vfmt; /* Image Version Format */
uint32_t vmjr; /* Image Version Major */
uint32_t vmnr; /* Image Version Minor */
uint8_t vrel[32]; /* Image Version Release */
uint32_t dxor; /* xor value for Data? */
uint32_t dlen; /* Data Length */
};
/*
* check whether the current mtd device is active or not
*
* example of BOOTDEV value (IIJ SA-W2):
* - "flash" : primary image on flash
* - "rescue" : secondary image on flash
* - "usb" : usb storage
* - "lan0/1" : network
*/
static bool seil_bootdev_is_active(struct device_node *np)
{
struct mtd_info *env_mtd;
char *buf, *var, *value, *eq;
const char *devnm;
size_t rdlen;
int ret;
/*
* read bootdev name of the partition
* if doesn't exist, return true and skip checking of active device
*/
ret = of_property_read_string(np, "iij,bootdev-name", &devnm);
if (ret == -EINVAL)
return true;
else if (ret < 0)
return false;
env_mtd = get_mtd_device_nm(LDR_ENV_PART_NAME);
if (IS_ERR(env_mtd)) {
pr_err("failed to get mtd device \"%s\"", LDR_ENV_PART_NAME);
return false;
}
buf = vmalloc(env_mtd->size);
if (!buf)
return false;
ret = mtd_read(env_mtd, 0, env_mtd->size, &rdlen, buf);
if (ret || rdlen != env_mtd->size) {
pr_err("failed to read from mtd (%d)\n", ret);
ret = 0;
goto exit_vfree;
}
for (var = buf, ret = false;
var < buf + env_mtd->size && *var;
var = value + strlen(value) + 1) {
eq = strchr(var, '=');
if (!eq)
break;
*eq = '\0';
value = eq + 1;
pr_debug("ENV: %s=%s\n", var, value);
if (!strcmp(var, LDR_ENV_KEY_BOOTDEV)) {
ret = !strcmp(devnm, value);
break;
}
}
exit_vfree:
vfree(buf);
return ret;
}
static int mtdsplit_parse_seil_fw(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *np = mtd_get_of_node(master);
struct mtd_partition *parts;
struct seil_header header;
size_t image_size = 0;
size_t rootfs_offset;
size_t hdrlen = sizeof(header);
size_t retlen;
int ret;
u64 id;
if (!seil_bootdev_is_active(np))
return -ENODEV;
ret = of_property_read_u64(np, "iij,seil-id", &id);
if (ret) {
pr_err("failed to get iij,seil-id from dt\n");
return ret;
}
pr_debug("got seil-id=0x%016llx from dt\n", id);
parts = kcalloc(NR_PARTS, sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
ret = mtd_read(master, 0, hdrlen, &retlen, (void *)&header);
if (ret)
goto err_free_parts;
if (retlen != hdrlen) {
ret = -EIO;
goto err_free_parts;
}
if (be64_to_cpu(header.id) != id ||
be32_to_cpu(header.vfmt) != SEIL_VFMT) {
pr_debug("no valid seil image found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_parts;
}
image_size = hdrlen + be32_to_cpu(header.dlen);
if (image_size > master->size) {
pr_err("seil image exceeds MTD device \"%s\"\n", master->name);
ret = -EINVAL;
goto err_free_parts;
}
/* find the roots after the seil image */
ret = mtd_find_rootfs_from(master, image_size,
master->size, &rootfs_offset, NULL);
if (ret || (master->size - rootfs_offset) == 0) {
pr_debug("no rootfs after seil image in \"%s\"\n",
master->name);
ret = -ENODEV;
goto err_free_parts;
}
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return NR_PARTS;
err_free_parts:
kfree(parts);
return ret;
}
static const struct of_device_id mtdsplit_seil_fw_of_match_table[] = {
{ .compatible = "iij,seil-firmware" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_seil_fw_of_match_table);
static struct mtd_part_parser mtdsplit_seil_fw_parser = {
.owner = THIS_MODULE,
.name = "seil-fw",
.of_match_table = mtdsplit_seil_fw_of_match_table,
.parse_fn = mtdsplit_parse_seil_fw,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
module_mtd_part_parser(mtdsplit_seil_fw_parser);

View file

@ -0,0 +1,72 @@
/*
* Copyright (C) 2013 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/magic.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include "mtdsplit.h"
static int
mtdsplit_parse_squashfs(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *part;
struct mtd_info *parent_mtd;
size_t part_offset;
size_t squashfs_len;
int err;
err = mtd_get_squashfs_len(master, 0, &squashfs_len);
if (err)
return err;
parent_mtd = mtd_get_master(master);
part_offset = mtdpart_get_offset(master);
part = kzalloc(sizeof(*part), GFP_KERNEL);
if (!part) {
pr_alert("unable to allocate memory for \"%s\" partition\n",
ROOTFS_SPLIT_NAME);
return -ENOMEM;
}
part->name = ROOTFS_SPLIT_NAME;
part->offset = mtd_roundup_to_eb(part_offset + squashfs_len,
parent_mtd) - part_offset;
part->size = mtd_rounddown_to_eb(master->size - part->offset, master);
*pparts = part;
return 1;
}
static struct mtd_part_parser mtdsplit_squashfs_parser = {
.owner = THIS_MODULE,
.name = "squashfs-split",
.parse_fn = mtdsplit_parse_squashfs,
.type = MTD_PARSER_TYPE_ROOTFS,
};
static int __init mtdsplit_squashfs_init(void)
{
register_mtd_parser(&mtdsplit_squashfs_parser);
return 0;
}
subsys_initcall(mtdsplit_squashfs_init);

View file

@ -0,0 +1,176 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define TPLINK_NR_PARTS 2
#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
#define MD5SUM_LEN 16
struct fw_v1 {
char vendor_name[24];
char fw_version[36];
uint32_t hw_id; /* hardware id */
uint32_t hw_rev; /* hardware revision */
uint32_t unk1;
uint8_t md5sum1[MD5SUM_LEN];
uint32_t unk2;
uint8_t md5sum2[MD5SUM_LEN];
uint32_t unk3;
uint32_t kernel_la; /* kernel load address */
uint32_t kernel_ep; /* kernel entry point */
uint32_t fw_length; /* total length of the firmware */
uint32_t kernel_ofs; /* kernel data offset */
uint32_t kernel_len; /* kernel data length */
uint32_t rootfs_ofs; /* rootfs data offset */
uint32_t rootfs_len; /* rootfs data length */
uint32_t boot_ofs; /* bootloader data offset */
uint32_t boot_len; /* bootloader data length */
uint8_t pad[360];
} __attribute__ ((packed));
struct fw_v2 {
char fw_version[48]; /* 0x04: fw version string */
uint32_t hw_id; /* 0x34: hardware id */
uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */
uint32_t unk1; /* 0x3c: 0x00000000 */
uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */
uint32_t unk2; /* 0x50: 0x00000000 */
uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */
uint32_t unk3; /* 0x64: 0xffffffff */
uint32_t kernel_la; /* 0x68: kernel load address */
uint32_t kernel_ep; /* 0x6c: kernel entry point */
uint32_t fw_length; /* 0x70: total length of the image */
uint32_t kernel_ofs; /* 0x74: kernel data offset */
uint32_t kernel_len; /* 0x78: kernel data length */
uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */
uint32_t rootfs_len; /* 0x80: rootfs data length */
uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */
uint32_t boot_len; /* 0x88: FIXME: seems to be unused */
uint16_t unk4; /* 0x8c: 0x55aa */
uint8_t sver_hi; /* 0x8e */
uint8_t sver_lo; /* 0x8f */
uint8_t unk5; /* 0x90: magic: 0xa5 */
uint8_t ver_hi; /* 0x91 */
uint8_t ver_mid; /* 0x92 */
uint8_t ver_lo; /* 0x93 */
uint8_t pad[364];
} __attribute__ ((packed));
struct tplink_fw_header {
uint32_t version;
union {
struct fw_v1 v1;
struct fw_v2 v2;
};
};
static int mtdsplit_parse_tplink(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct tplink_fw_header hdr;
size_t hdr_len, retlen, kernel_size;
size_t rootfs_offset;
struct mtd_partition *parts;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != hdr_len)
return -EIO;
switch (le32_to_cpu(hdr.version)) {
case 1:
if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr))
return -EINVAL;
kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len);
rootfs_offset = be32_to_cpu(hdr.v1.rootfs_ofs);
break;
case 2:
case 3:
if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr))
return -EINVAL;
kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len);
rootfs_offset = be32_to_cpu(hdr.v2.rootfs_ofs);
break;
default:
return -EINVAL;
}
if (kernel_size > master->size)
return -EINVAL;
/* Find the rootfs */
err = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
if (err) {
/*
* The size in the header might cover the rootfs as well.
* Start the search from an arbitrary offset.
*/
err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS,
master->size, &rootfs_offset, NULL);
if (err)
return err;
}
parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = kernel_size;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return TPLINK_NR_PARTS;
}
static const struct of_device_id mtdsplit_tplink_of_match_table[] = {
{ .compatible = "tplink,firmware" },
{},
};
static struct mtd_part_parser mtdsplit_tplink_parser = {
.owner = THIS_MODULE,
.name = "tplink-fw",
.of_match_table = mtdsplit_tplink_of_match_table,
.parse_fn = mtdsplit_parse_tplink,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_tplink_init(void)
{
register_mtd_parser(&mtdsplit_tplink_parser);
return 0;
}
subsys_initcall(mtdsplit_tplink_init);

View file

@ -0,0 +1,155 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define TRX_MAGIC 0x30524448 /* "HDR0" */
struct trx_header {
__le32 magic;
__le32 len;
__le32 crc32;
__le32 flag_version;
__le32 offset[4];
};
static int
read_trx_header(struct mtd_info *mtd, size_t offset,
struct trx_header *header)
{
size_t header_len;
size_t retlen;
int ret;
header_len = sizeof(*header);
ret = mtd_read(mtd, offset, header_len, &retlen,
(unsigned char *) header);
if (ret) {
pr_debug("read error in \"%s\"\n", mtd->name);
return ret;
}
if (retlen != header_len) {
pr_debug("short read in \"%s\"\n", mtd->name);
return -EIO;
}
return 0;
}
static int
mtdsplit_parse_trx(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
struct trx_header hdr;
int nr_parts;
size_t offset;
size_t trx_offset;
size_t trx_size = 0;
size_t rootfs_offset;
size_t rootfs_size = 0;
int ret;
nr_parts = 2;
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
/* find trx image on erase block boundaries */
for (offset = 0; offset < master->size; offset += master->erasesize) {
trx_size = 0;
ret = read_trx_header(master, offset, &hdr);
if (ret)
continue;
if (hdr.magic != cpu_to_le32(TRX_MAGIC)) {
pr_debug("no valid trx header found in \"%s\" at offset %llx\n",
master->name, (unsigned long long) offset);
continue;
}
trx_size = le32_to_cpu(hdr.len);
if ((offset + trx_size) > master->size) {
pr_debug("trx image exceeds MTD device \"%s\"\n",
master->name);
continue;
}
break;
}
if (trx_size == 0) {
pr_debug("no trx header found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err;
}
trx_offset = offset + hdr.offset[0];
rootfs_offset = offset + hdr.offset[1];
rootfs_size = master->size - rootfs_offset;
trx_size = rootfs_offset - trx_offset;
if (rootfs_size == 0) {
pr_debug("no rootfs found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err;
}
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = trx_offset;
parts[0].size = trx_size;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = rootfs_size;
*pparts = parts;
return nr_parts;
err:
kfree(parts);
return ret;
}
static const struct of_device_id trx_parser_of_match_table[] = {
{ .compatible = "openwrt,trx" },
{},
};
MODULE_DEVICE_TABLE(of, trx_parser_of_match_table);
static struct mtd_part_parser trx_parser = {
.owner = THIS_MODULE,
.name = "trx-fw",
.of_match_table = trx_parser_of_match_table,
.parse_fn = mtdsplit_parse_trx,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_trx_init(void)
{
register_mtd_parser(&trx_parser);
return 0;
}
module_init(mtdsplit_trx_init);

View file

@ -0,0 +1,282 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/version.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include <dt-bindings/mtd/partitions/uimage.h>
#include "mtdsplit.h"
/*
* Legacy format image header,
* all data in network byte order (aka natural aka bigendian).
*/
struct uimage_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
};
static int
read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf,
size_t header_len)
{
size_t retlen;
int ret;
ret = mtd_read(mtd, offset, header_len, &retlen, buf);
if (ret) {
pr_debug("read error in \"%s\"\n", mtd->name);
return ret;
}
if (retlen != header_len) {
pr_debug("short read in \"%s\"\n", mtd->name);
return -EIO;
}
return 0;
}
static void uimage_parse_dt(struct mtd_info *master, int *extralen,
u32 *ih_magic, u32 *ih_type,
u32 *header_offset, u32 *part_magic)
{
struct device_node *np = mtd_get_of_node(master);
if (!np || !of_device_is_compatible(np, "openwrt,uimage"))
return;
if (!of_property_read_u32(np, "openwrt,padding", extralen))
pr_debug("got openwrt,padding=%d from device-tree\n", *extralen);
if (!of_property_read_u32(np, "openwrt,ih-magic", ih_magic))
pr_debug("got openwrt,ih-magic=%08x from device-tree\n", *ih_magic);
if (!of_property_read_u32(np, "openwrt,ih-type", ih_type))
pr_debug("got openwrt,ih-type=%08x from device-tree\n", *ih_type);
if (!of_property_read_u32(np, "openwrt,offset", header_offset))
pr_debug("got ih-start=%u from device-tree\n", *header_offset);
if (!of_property_read_u32(np, "openwrt,partition-magic", part_magic))
pr_debug("got openwrt,partition-magic=%08x from device-tree\n", *part_magic);
}
static ssize_t uimage_verify_default(u_char *buf, u32 ih_magic, u32 ih_type)
{
struct uimage_header *header = (struct uimage_header *)buf;
/* default sanity checks */
if (be32_to_cpu(header->ih_magic) != ih_magic) {
pr_debug("invalid uImage magic: %08x != %08x\n",
be32_to_cpu(header->ih_magic), ih_magic);
return -EINVAL;
}
if (header->ih_os != IH_OS_LINUX) {
pr_debug("invalid uImage OS: %08x != %08x\n",
be32_to_cpu(header->ih_os), IH_OS_LINUX);
return -EINVAL;
}
if (header->ih_type != ih_type) {
pr_debug("invalid uImage type: %08x != %08x\n",
be32_to_cpu(header->ih_type), ih_type);
return -EINVAL;
}
return 0;
}
/**
* __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts
*
* @find_header: function to call for a block of data that will return offset
* and tail padding length of a valid uImage header if found
*/
static int __mtdsplit_parse_uimage(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_partition *parts;
u_char *buf;
int nr_parts;
size_t offset;
size_t uimage_offset;
size_t uimage_size = 0;
size_t rootfs_offset;
size_t rootfs_size = 0;
size_t buflen;
int uimage_part, rf_part;
int ret;
int extralen = 0;
u32 ih_magic = IH_MAGIC;
u32 ih_type = IH_TYPE_KERNEL;
u32 header_offset = 0;
u32 part_magic = 0;
enum mtdsplit_part_type type;
nr_parts = 2;
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
uimage_parse_dt(master, &extralen, &ih_magic, &ih_type, &header_offset, &part_magic);
buflen = sizeof(struct uimage_header) + header_offset;
buf = vmalloc(buflen);
if (!buf) {
ret = -ENOMEM;
goto err_free_parts;
}
/* find uImage on erase block boundaries */
for (offset = 0; offset < master->size; offset += master->erasesize) {
struct uimage_header *header;
uimage_size = 0;
ret = read_uimage_header(master, offset, buf, buflen);
if (ret)
continue;
/* verify optional partition magic before uimage header */
if (header_offset && part_magic && (be32_to_cpu(*(u32 *)buf) != part_magic))
continue;
ret = uimage_verify_default(buf + header_offset, ih_magic, ih_type);
if (ret < 0) {
pr_debug("no valid uImage found in \"%s\" at offset %llx\n",
master->name, (unsigned long long) offset);
continue;
}
header = (struct uimage_header *)(buf + header_offset);
uimage_size = sizeof(*header) +
be32_to_cpu(header->ih_size) + header_offset + extralen;
if ((offset + uimage_size) > master->size) {
pr_debug("uImage exceeds MTD device \"%s\"\n",
master->name);
continue;
}
break;
}
if (uimage_size == 0) {
pr_debug("no uImage found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_buf;
}
uimage_offset = offset;
if (uimage_offset == 0) {
uimage_part = 0;
rf_part = 1;
/* find the roots after the uImage */
ret = mtd_find_rootfs_from(master, uimage_offset + uimage_size,
master->size, &rootfs_offset, &type);
if (ret) {
pr_debug("no rootfs after uImage in \"%s\"\n",
master->name);
goto err_free_buf;
}
rootfs_size = master->size - rootfs_offset;
uimage_size = rootfs_offset - uimage_offset;
} else {
rf_part = 0;
uimage_part = 1;
/* check rootfs presence at offset 0 */
ret = mtd_check_rootfs_magic(master, 0, &type);
if (ret) {
pr_debug("no rootfs before uImage in \"%s\"\n",
master->name);
goto err_free_buf;
}
rootfs_offset = 0;
rootfs_size = uimage_offset;
}
if (rootfs_size == 0) {
pr_debug("no rootfs found in \"%s\"\n", master->name);
ret = -ENODEV;
goto err_free_buf;
}
parts[uimage_part].name = KERNEL_PART_NAME;
parts[uimage_part].offset = uimage_offset;
parts[uimage_part].size = uimage_size;
if (type == MTDSPLIT_PART_TYPE_UBI)
parts[rf_part].name = UBI_PART_NAME;
else
parts[rf_part].name = ROOTFS_PART_NAME;
parts[rf_part].offset = rootfs_offset;
parts[rf_part].size = rootfs_size;
vfree(buf);
*pparts = parts;
return nr_parts;
err_free_buf:
vfree(buf);
err_free_parts:
kfree(parts);
return ret;
}
static const struct of_device_id mtdsplit_uimage_of_match_table[] = {
{ .compatible = "denx,uimage" },
{ .compatible = "openwrt,uimage" },
{},
};
static struct mtd_part_parser uimage_generic_parser = {
.owner = THIS_MODULE,
.name = "uimage-fw",
.of_match_table = mtdsplit_uimage_of_match_table,
.parse_fn = __mtdsplit_parse_uimage,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
/**************************************************
* Init
**************************************************/
static int __init mtdsplit_uimage_init(void)
{
register_mtd_parser(&uimage_generic_parser);
return 0;
}
module_init(mtdsplit_uimage_init);

View file

@ -0,0 +1,142 @@
/*
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2016 Stijn Tintel <stijn@linux-ipv6.be>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/byteorder/generic.h>
#include <linux/of.h>
#include "mtdsplit.h"
#define WRGG_NR_PARTS 2
#define WRGG_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
#define WRGG03_MAGIC 0x20080321
#define WRG_MAGIC 0x20040220
struct wrgg03_header {
char signature[32];
uint32_t magic1;
uint32_t magic2;
char version[16];
char model[16];
uint32_t flag[2];
uint32_t reserve[2];
char buildno[16];
uint32_t size;
uint32_t offset;
char devname[32];
char digest[16];
} __attribute__ ((packed));
struct wrg_header {
char signature[32];
uint32_t magic1;
uint32_t magic2;
uint32_t size;
uint32_t offset;
char devname[32];
char digest[16];
} __attribute__ ((packed));
static int mtdsplit_parse_wrgg(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct wrgg03_header hdr;
size_t hdr_len, retlen, kernel_ent_size;
size_t rootfs_offset;
struct mtd_partition *parts;
enum mtdsplit_part_type type;
int err;
hdr_len = sizeof(hdr);
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
if (err)
return err;
if (retlen != hdr_len)
return -EIO;
/* sanity checks */
if (le32_to_cpu(hdr.magic1) == WRGG03_MAGIC) {
kernel_ent_size = hdr_len + be32_to_cpu(hdr.size);
/*
* If this becomes silly big it's probably because the
* WRGG image is little-endian.
*/
if (kernel_ent_size > master->size)
kernel_ent_size = hdr_len + le32_to_cpu(hdr.size);
/* Now what ?! It's neither */
if (kernel_ent_size > master->size)
return -EINVAL;
} else if (le32_to_cpu(hdr.magic1) == WRG_MAGIC) {
kernel_ent_size = sizeof(struct wrg_header) + le32_to_cpu(
((struct wrg_header*)&hdr)->size);
} else {
return -EINVAL;
}
if (kernel_ent_size > master->size)
return -EINVAL;
/*
* The size in the header covers the rootfs as well.
* Start the search from an arbitrary offset.
*/
err = mtd_find_rootfs_from(master, WRGG_MIN_ROOTFS_OFFS,
master->size, &rootfs_offset, &type);
if (err)
return err;
parts = kzalloc(WRGG_NR_PARTS * sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
parts[0].name = KERNEL_PART_NAME;
parts[0].offset = 0;
parts[0].size = rootfs_offset;
parts[1].name = ROOTFS_PART_NAME;
parts[1].offset = rootfs_offset;
parts[1].size = master->size - rootfs_offset;
*pparts = parts;
return WRGG_NR_PARTS;
}
static const struct of_device_id mtdsplit_wrgg_of_match_table[] = {
{ .compatible = "wrg" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_wrgg_of_match_table);
static struct mtd_part_parser mtdsplit_wrgg_parser = {
.owner = THIS_MODULE,
.name = "wrgg-fw",
.of_match_table = mtdsplit_wrgg_of_match_table,
.parse_fn = mtdsplit_parse_wrgg,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
static int __init mtdsplit_wrgg_init(void)
{
register_mtd_parser(&mtdsplit_wrgg_parser);
return 0;
}
subsys_initcall(mtdsplit_wrgg_init);

View file

@ -0,0 +1,465 @@
/*
* Copyright (c) 2017 MediaTek Inc.
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/module.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/bits.h>
#include "mtk_bmt.h"
struct bmt_desc bmtd = {};
/* -------- Nand operations wrapper -------- */
int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset)
{
int pages = bmtd.blk_size >> bmtd.pg_shift;
loff_t src = (loff_t)src_blk << bmtd.blk_shift;
loff_t dest = (loff_t)dest_blk << bmtd.blk_shift;
loff_t offset = 0;
uint8_t oob[64];
int i, ret;
for (i = 0; i < pages; i++) {
struct mtd_oob_ops rd_ops = {
.mode = MTD_OPS_PLACE_OOB,
.oobbuf = oob,
.ooblen = min_t(int, bmtd.mtd->oobsize / pages, sizeof(oob)),
.datbuf = bmtd.data_buf,
.len = bmtd.pg_size,
};
struct mtd_oob_ops wr_ops = {
.mode = MTD_OPS_PLACE_OOB,
.oobbuf = oob,
.datbuf = bmtd.data_buf,
.len = bmtd.pg_size,
};
if (offset >= max_offset)
break;
ret = bmtd._read_oob(bmtd.mtd, src + offset, &rd_ops);
if (ret < 0 && !mtd_is_bitflip(ret))
return ret;
if (!rd_ops.retlen)
break;
ret = bmtd._write_oob(bmtd.mtd, dest + offset, &wr_ops);
if (ret < 0)
return ret;
wr_ops.ooblen = rd_ops.oobretlen;
offset += rd_ops.retlen;
}
return 0;
}
/* -------- Bad Blocks Management -------- */
bool mapping_block_in_range(int block, int *start, int *end)
{
const __be32 *cur = bmtd.remap_range;
u32 addr = block << bmtd.blk_shift;
int i;
if (!cur || !bmtd.remap_range_len) {
*start = 0;
*end = bmtd.total_blks;
return true;
}
for (i = 0; i < bmtd.remap_range_len; i++, cur += 2) {
if (addr < be32_to_cpu(cur[0]) || addr >= be32_to_cpu(cur[1]))
continue;
*start = be32_to_cpu(cur[0]);
*end = be32_to_cpu(cur[1]);
return true;
}
return false;
}
static bool
mtk_bmt_remap_block(u32 block, u32 mapped_block, int copy_len)
{
int start, end;
if (!mapping_block_in_range(block, &start, &end))
return false;
return bmtd.ops->remap_block(block, mapped_block, copy_len);
}
static int
mtk_bmt_read(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops)
{
struct mtd_oob_ops cur_ops = *ops;
int retry_count = 0;
loff_t cur_from;
int ret = 0;
int max_bitflips = 0;
ops->retlen = 0;
ops->oobretlen = 0;
while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) {
int cur_ret;
u32 offset = from & (bmtd.blk_size - 1);
u32 block = from >> bmtd.blk_shift;
int cur_block;
cur_block = bmtd.ops->get_mapping_block(block);
if (cur_block < 0)
return -EIO;
cur_from = ((loff_t)cur_block << bmtd.blk_shift) + offset;
cur_ops.oobretlen = 0;
cur_ops.retlen = 0;
cur_ops.len = min_t(u32, mtd->erasesize - offset,
ops->len - ops->retlen);
cur_ret = bmtd._read_oob(mtd, cur_from, &cur_ops);
if (cur_ret < 0)
ret = cur_ret;
else
max_bitflips = max_t(int, max_bitflips, cur_ret);
if (cur_ret < 0 && !mtd_is_bitflip(cur_ret)) {
if (mtk_bmt_remap_block(block, cur_block, mtd->erasesize) &&
retry_count++ < 10)
continue;
goto out;
}
if (mtd->bitflip_threshold && cur_ret >= mtd->bitflip_threshold)
mtk_bmt_remap_block(block, cur_block, mtd->erasesize);
ops->retlen += cur_ops.retlen;
ops->oobretlen += cur_ops.oobretlen;
cur_ops.ooboffs = 0;
cur_ops.datbuf += cur_ops.retlen;
cur_ops.oobbuf += cur_ops.oobretlen;
cur_ops.ooblen -= cur_ops.oobretlen;
if (!cur_ops.len)
cur_ops.len = mtd->erasesize - offset;
from += cur_ops.len;
retry_count = 0;
}
out:
if (ret < 0)
return ret;
return max_bitflips;
}
static int
mtk_bmt_write(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops)
{
struct mtd_oob_ops cur_ops = *ops;
int retry_count = 0;
loff_t cur_to;
int ret;
ops->retlen = 0;
ops->oobretlen = 0;
while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) {
u32 offset = to & (bmtd.blk_size - 1);
u32 block = to >> bmtd.blk_shift;
int cur_block;
cur_block = bmtd.ops->get_mapping_block(block);
if (cur_block < 0)
return -EIO;
cur_to = ((loff_t)cur_block << bmtd.blk_shift) + offset;
cur_ops.oobretlen = 0;
cur_ops.retlen = 0;
cur_ops.len = min_t(u32, bmtd.blk_size - offset,
ops->len - ops->retlen);
ret = bmtd._write_oob(mtd, cur_to, &cur_ops);
if (ret < 0) {
if (mtk_bmt_remap_block(block, cur_block, offset) &&
retry_count++ < 10)
continue;
return ret;
}
ops->retlen += cur_ops.retlen;
ops->oobretlen += cur_ops.oobretlen;
cur_ops.ooboffs = 0;
cur_ops.datbuf += cur_ops.retlen;
cur_ops.oobbuf += cur_ops.oobretlen;
cur_ops.ooblen -= cur_ops.oobretlen;
if (!cur_ops.len)
cur_ops.len = mtd->erasesize - offset;
to += cur_ops.len;
retry_count = 0;
}
return 0;
}
static int
mtk_bmt_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct erase_info mapped_instr = {
.len = bmtd.blk_size,
};
int retry_count = 0;
u64 start_addr, end_addr;
int ret;
u16 orig_block;
int block;
start_addr = instr->addr & (~mtd->erasesize_mask);
end_addr = instr->addr + instr->len;
while (start_addr < end_addr) {
orig_block = start_addr >> bmtd.blk_shift;
block = bmtd.ops->get_mapping_block(orig_block);
if (block < 0)
return -EIO;
mapped_instr.addr = (loff_t)block << bmtd.blk_shift;
ret = bmtd._erase(mtd, &mapped_instr);
if (ret) {
if (mtk_bmt_remap_block(orig_block, block, 0) &&
retry_count++ < 10)
continue;
instr->fail_addr = start_addr;
break;
}
start_addr += mtd->erasesize;
retry_count = 0;
}
return ret;
}
static int
mtk_bmt_block_isbad(struct mtd_info *mtd, loff_t ofs)
{
int retry_count = 0;
u16 orig_block = ofs >> bmtd.blk_shift;
u16 block;
int ret;
retry:
block = bmtd.ops->get_mapping_block(orig_block);
ret = bmtd._block_isbad(mtd, (loff_t)block << bmtd.blk_shift);
if (ret) {
if (mtk_bmt_remap_block(orig_block, block, bmtd.blk_size) &&
retry_count++ < 10)
goto retry;
}
return ret;
}
static int
mtk_bmt_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
u16 orig_block = ofs >> bmtd.blk_shift;
int block;
block = bmtd.ops->get_mapping_block(orig_block);
if (block < 0)
return -EIO;
mtk_bmt_remap_block(orig_block, block, bmtd.blk_size);
return bmtd._block_markbad(mtd, (loff_t)block << bmtd.blk_shift);
}
static void
mtk_bmt_replace_ops(struct mtd_info *mtd)
{
bmtd._read_oob = mtd->_read_oob;
bmtd._write_oob = mtd->_write_oob;
bmtd._erase = mtd->_erase;
bmtd._block_isbad = mtd->_block_isbad;
bmtd._block_markbad = mtd->_block_markbad;
mtd->_read_oob = mtk_bmt_read;
mtd->_write_oob = mtk_bmt_write;
mtd->_erase = mtk_bmt_mtd_erase;
mtd->_block_isbad = mtk_bmt_block_isbad;
mtd->_block_markbad = mtk_bmt_block_markbad;
}
static int mtk_bmt_debug_repair(void *data, u64 val)
{
int block = val >> bmtd.blk_shift;
int prev_block, new_block;
prev_block = bmtd.ops->get_mapping_block(block);
if (prev_block < 0)
return -EIO;
bmtd.ops->unmap_block(block);
new_block = bmtd.ops->get_mapping_block(block);
if (new_block < 0)
return -EIO;
if (prev_block == new_block)
return 0;
bbt_nand_erase(new_block);
bbt_nand_copy(new_block, prev_block, bmtd.blk_size);
return 0;
}
static int mtk_bmt_debug_mark_good(void *data, u64 val)
{
bmtd.ops->unmap_block(val >> bmtd.blk_shift);
return 0;
}
static int mtk_bmt_debug_mark_bad(void *data, u64 val)
{
u32 block = val >> bmtd.blk_shift;
int cur_block;
cur_block = bmtd.ops->get_mapping_block(block);
if (cur_block < 0)
return -EIO;
mtk_bmt_remap_block(block, cur_block, bmtd.blk_size);
return 0;
}
static int mtk_bmt_debug(void *data, u64 val)
{
return bmtd.ops->debug(data, val);
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_repair, NULL, mtk_bmt_debug_repair, "%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_good, NULL, mtk_bmt_debug_mark_good, "%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_bad, NULL, mtk_bmt_debug_mark_bad, "%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(fops_debug, NULL, mtk_bmt_debug, "%llu\n");
static void
mtk_bmt_add_debugfs(void)
{
struct dentry *dir;
dir = bmtd.debugfs_dir = debugfs_create_dir("mtk-bmt", NULL);
if (!dir)
return;
debugfs_create_file_unsafe("repair", S_IWUSR, dir, NULL, &fops_repair);
debugfs_create_file_unsafe("mark_good", S_IWUSR, dir, NULL, &fops_mark_good);
debugfs_create_file_unsafe("mark_bad", S_IWUSR, dir, NULL, &fops_mark_bad);
debugfs_create_file_unsafe("debug", S_IWUSR, dir, NULL, &fops_debug);
}
void mtk_bmt_detach(struct mtd_info *mtd)
{
if (bmtd.mtd != mtd)
return;
if (bmtd.debugfs_dir)
debugfs_remove_recursive(bmtd.debugfs_dir);
bmtd.debugfs_dir = NULL;
kfree(bmtd.bbt_buf);
kfree(bmtd.data_buf);
mtd->_read_oob = bmtd._read_oob;
mtd->_write_oob = bmtd._write_oob;
mtd->_erase = bmtd._erase;
mtd->_block_isbad = bmtd._block_isbad;
mtd->_block_markbad = bmtd._block_markbad;
mtd->size = bmtd.total_blks << bmtd.blk_shift;
memset(&bmtd, 0, sizeof(bmtd));
}
int mtk_bmt_attach(struct mtd_info *mtd)
{
struct device_node *np;
int ret = 0;
if (bmtd.mtd)
return -ENOSPC;
np = mtd_get_of_node(mtd);
if (!np)
return 0;
if (of_property_read_bool(np, "mediatek,bmt-v2"))
bmtd.ops = &mtk_bmt_v2_ops;
else if (of_property_read_bool(np, "mediatek,nmbm"))
bmtd.ops = &mtk_bmt_nmbm_ops;
else if (of_property_read_bool(np, "mediatek,bbt"))
bmtd.ops = &mtk_bmt_bbt_ops;
else
return 0;
bmtd.remap_range = of_get_property(np, "mediatek,bmt-remap-range",
&bmtd.remap_range_len);
bmtd.remap_range_len /= 8;
bmtd.mtd = mtd;
mtk_bmt_replace_ops(mtd);
bmtd.blk_size = mtd->erasesize;
bmtd.blk_shift = ffs(bmtd.blk_size) - 1;
bmtd.pg_size = mtd->writesize;
bmtd.pg_shift = ffs(bmtd.pg_size) - 1;
bmtd.total_blks = mtd->size >> bmtd.blk_shift;
bmtd.data_buf = kzalloc(bmtd.pg_size + bmtd.mtd->oobsize, GFP_KERNEL);
if (!bmtd.data_buf) {
pr_info("nand: FATAL ERR: allocate buffer failed!\n");
ret = -1;
goto error;
}
memset(bmtd.data_buf, 0xff, bmtd.pg_size + bmtd.mtd->oobsize);
ret = bmtd.ops->init(np);
if (ret)
goto error;
mtk_bmt_add_debugfs();
return 0;
error:
mtk_bmt_detach(mtd);
return ret;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Xiangsheng Hou <xiangsheng.hou@mediatek.com>, Felix Fietkau <nbd@nbd.name>");
MODULE_DESCRIPTION("Bad Block mapping management v2 for MediaTek NAND Flash Driver");

View file

@ -0,0 +1,137 @@
#ifndef __MTK_BMT_PRIV_H
#define __MTK_BMT_PRIV_H
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/mtd/mtk_bmt.h>
#include <linux/debugfs.h>
#define MAIN_SIGNATURE_OFFSET 0
#define OOB_SIGNATURE_OFFSET 1
#define BBT_LOG(fmt, ...) pr_debug("[BBT][%s|%d] "fmt"\n", __func__, __LINE__, ##__VA_ARGS__)
struct mtk_bmt_ops {
char *sig;
unsigned int sig_len;
int (*init)(struct device_node *np);
bool (*remap_block)(u16 block, u16 mapped_block, int copy_len);
void (*unmap_block)(u16 block);
int (*get_mapping_block)(int block);
int (*debug)(void *data, u64 val);
};
struct bbbt;
struct nmbm_instance;
struct bmt_desc {
struct mtd_info *mtd;
unsigned char *bbt_buf;
unsigned char *data_buf;
int (*_read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*_write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
const struct mtk_bmt_ops *ops;
union {
struct bbbt *bbt;
struct nmbm_instance *ni;
};
struct dentry *debugfs_dir;
u32 table_size;
u32 pg_size;
u32 blk_size;
u16 pg_shift;
u16 blk_shift;
/* bbt logical address */
u16 pool_lba;
/* bbt physical address */
u16 pool_pba;
/* Maximum count of bad blocks that the vendor guaranteed */
u16 bb_max;
/* Total blocks of the Nand Chip */
u16 total_blks;
/* The block(n) BMT is located at (bmt_tbl[n]) */
u16 bmt_blk_idx;
/* How many pages needs to store 'struct bbbt' */
u32 bmt_pgs;
const __be32 *remap_range;
int remap_range_len;
/* to compensate for driver level remapping */
u8 oob_offset;
};
extern struct bmt_desc bmtd;
extern const struct mtk_bmt_ops mtk_bmt_v2_ops;
extern const struct mtk_bmt_ops mtk_bmt_bbt_ops;
extern const struct mtk_bmt_ops mtk_bmt_nmbm_ops;
static inline u32 blk_pg(u16 block)
{
return (u32)(block << (bmtd.blk_shift - bmtd.pg_shift));
}
static inline int
bbt_nand_read(u32 page, unsigned char *dat, int dat_len,
unsigned char *fdm, int fdm_len)
{
struct mtd_oob_ops ops = {
.mode = MTD_OPS_PLACE_OOB,
.ooboffs = bmtd.oob_offset,
.oobbuf = fdm,
.ooblen = fdm_len,
.datbuf = dat,
.len = dat_len,
};
int ret;
ret = bmtd._read_oob(bmtd.mtd, page << bmtd.pg_shift, &ops);
if (ret < 0)
return ret;
if (ret)
pr_info("%s: %d bitflips\n", __func__, ret);
return 0;
}
static inline int bbt_nand_erase(u16 block)
{
struct mtd_info *mtd = bmtd.mtd;
struct erase_info instr = {
.addr = (loff_t)block << bmtd.blk_shift,
.len = bmtd.blk_size,
};
return bmtd._erase(mtd, &instr);
}
static inline int write_bmt(u16 block, unsigned char *dat)
{
struct mtd_oob_ops ops = {
.mode = MTD_OPS_PLACE_OOB,
.ooboffs = OOB_SIGNATURE_OFFSET + bmtd.oob_offset,
.oobbuf = bmtd.ops->sig,
.ooblen = bmtd.ops->sig_len,
.datbuf = dat,
.len = bmtd.bmt_pgs << bmtd.pg_shift,
};
loff_t addr = (loff_t)block << bmtd.blk_shift;
return bmtd._write_oob(bmtd.mtd, addr, &ops);
}
int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset);
bool mapping_block_in_range(int block, int *start, int *end);
#endif

View file

@ -0,0 +1,203 @@
/*
* Copyright (c) 2017 MediaTek Inc.
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 "mtk_bmt.h"
static bool
bbt_block_is_bad(u16 block)
{
u8 cur = bmtd.bbt_buf[block / 4];
return cur & (3 << ((block % 4) * 2));
}
static void
bbt_set_block_state(u16 block, bool bad)
{
u8 mask = (3 << ((block % 4) * 2));
if (bad)
bmtd.bbt_buf[block / 4] |= mask;
else
bmtd.bbt_buf[block / 4] &= ~mask;
bbt_nand_erase(bmtd.bmt_blk_idx);
write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf);
}
static int
get_mapping_block_index_bbt(int block)
{
int start, end, ofs;
int bad_blocks = 0;
int i;
if (!mapping_block_in_range(block, &start, &end))
return block;
start >>= bmtd.blk_shift;
end >>= bmtd.blk_shift;
/* skip bad blocks within the mapping range */
ofs = block - start;
for (i = start; i < end; i++) {
if (bbt_block_is_bad(i))
bad_blocks++;
else if (ofs)
ofs--;
else
break;
}
if (i < end)
return i;
/* when overflowing, remap remaining blocks to bad ones */
for (i = end - 1; bad_blocks > 0; i--) {
if (!bbt_block_is_bad(i))
continue;
bad_blocks--;
if (bad_blocks <= ofs)
return i;
}
return block;
}
static bool remap_block_bbt(u16 block, u16 mapped_blk, int copy_len)
{
int start, end;
u16 new_blk;
if (!mapping_block_in_range(block, &start, &end))
return false;
bbt_set_block_state(mapped_blk, true);
new_blk = get_mapping_block_index_bbt(block);
bbt_nand_erase(new_blk);
if (copy_len > 0)
bbt_nand_copy(new_blk, mapped_blk, copy_len);
return true;
}
static void
unmap_block_bbt(u16 block)
{
bbt_set_block_state(block, false);
}
static int
mtk_bmt_read_bbt(void)
{
u8 oob_buf[8];
int i;
for (i = bmtd.total_blks - 1; i >= bmtd.total_blks - 5; i--) {
u32 page = i << (bmtd.blk_shift - bmtd.pg_shift);
if (bbt_nand_read(page, bmtd.bbt_buf, bmtd.pg_size,
oob_buf, sizeof(oob_buf))) {
pr_info("read_bbt: could not read block %d\n", i);
continue;
}
if (oob_buf[0] != 0xff) {
pr_info("read_bbt: bad block at %d\n", i);
continue;
}
if (memcmp(&oob_buf[1], "mtknand", 7) != 0) {
pr_info("read_bbt: signature mismatch in block %d\n", i);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, oob_buf, 8, 1);
continue;
}
pr_info("read_bbt: found bbt at block %d\n", i);
bmtd.bmt_blk_idx = i;
return 0;
}
return -EIO;
}
static int
mtk_bmt_init_bbt(struct device_node *np)
{
int buf_size = round_up(bmtd.total_blks >> 2, bmtd.blk_size);
int ret;
bmtd.bbt_buf = kmalloc(buf_size, GFP_KERNEL);
if (!bmtd.bbt_buf)
return -ENOMEM;
memset(bmtd.bbt_buf, 0xff, buf_size);
bmtd.mtd->size -= 4 * bmtd.mtd->erasesize;
ret = mtk_bmt_read_bbt();
if (ret)
return ret;
bmtd.bmt_pgs = buf_size / bmtd.pg_size;
return 0;
}
static int mtk_bmt_debug_bbt(void *data, u64 val)
{
char buf[5];
int i, k;
switch (val) {
case 0:
for (i = 0; i < bmtd.total_blks; i += 4) {
u8 cur = bmtd.bbt_buf[i / 4];
for (k = 0; k < 4; k++, cur >>= 2)
buf[k] = (cur & 3) ? 'B' : '.';
buf[4] = 0;
printk("[%06x] %s\n", i * bmtd.blk_size, buf);
}
break;
case 100:
#if 0
for (i = bmtd.bmt_blk_idx; i < bmtd.total_blks - 1; i++)
bbt_nand_erase(bmtd.bmt_blk_idx);
#endif
bmtd.bmt_blk_idx = bmtd.total_blks - 1;
bbt_nand_erase(bmtd.bmt_blk_idx);
write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf);
break;
default:
break;
}
return 0;
}
const struct mtk_bmt_ops mtk_bmt_bbt_ops = {
.sig = "mtknand",
.sig_len = 7,
.init = mtk_bmt_init_bbt,
.remap_block = remap_block_bbt,
.unmap_block = unmap_block_bbt,
.get_mapping_block = get_mapping_block_index_bbt,
.debug = mtk_bmt_debug_bbt,
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,506 @@
/*
* Copyright (c) 2017 MediaTek Inc.
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 "mtk_bmt.h"
struct bbbt {
char signature[3];
/* This version is used to distinguish the legacy and new algorithm */
#define BBMT_VERSION 2
unsigned char version;
/* Below 2 tables will be written in SLC */
u16 bb_tbl[];
};
struct bbmt {
u16 block;
#define NO_MAPPED 0
#define NORMAL_MAPPED 1
#define BMT_MAPPED 2
u16 mapped;
};
/* Maximum 8k blocks */
#define BBPOOL_RATIO 2
#define BB_TABLE_MAX bmtd.table_size
#define BMT_TABLE_MAX (BB_TABLE_MAX * BBPOOL_RATIO / 100)
#define BMT_TBL_DEF_VAL 0x0
static inline struct bbmt *bmt_tbl(struct bbbt *bbbt)
{
return (struct bbmt *)&bbbt->bb_tbl[bmtd.table_size];
}
static u16 find_valid_block(u16 block)
{
u8 fdm[4];
int ret;
int loop = 0;
retry:
if (block >= bmtd.total_blks)
return 0;
ret = bbt_nand_read(blk_pg(block), bmtd.data_buf, bmtd.pg_size,
fdm, sizeof(fdm));
/* Read the 1st byte of FDM to judge whether it's a bad
* or not
*/
if (ret || fdm[0] != 0xff) {
pr_info("nand: found bad block 0x%x\n", block);
if (loop >= bmtd.bb_max) {
pr_info("nand: FATAL ERR: too many bad blocks!!\n");
return 0;
}
loop++;
block++;
goto retry;
}
return block;
}
/* Find out all bad blocks, and fill in the mapping table */
static int scan_bad_blocks(struct bbbt *bbt)
{
int i;
u16 block = 0;
/* First time download, the block0 MUST NOT be a bad block,
* this is guaranteed by vendor
*/
bbt->bb_tbl[0] = 0;
/*
* Construct the mapping table of Normal data area(non-PMT/BMTPOOL)
* G - Good block; B - Bad block
* ---------------------------
* physical |G|G|B|G|B|B|G|G|G|G|B|G|B|
* ---------------------------
* What bb_tbl[i] looks like:
* physical block(i):
* 0 1 2 3 4 5 6 7 8 9 a b c
* mapped block(bb_tbl[i]):
* 0 1 3 6 7 8 9 b ......
* ATTENTION:
* If new bad block ocurred(n), search bmt_tbl to find
* a available block(x), and fill in the bb_tbl[n] = x;
*/
for (i = 1; i < bmtd.pool_lba; i++) {
bbt->bb_tbl[i] = find_valid_block(bbt->bb_tbl[i - 1] + 1);
BBT_LOG("bb_tbl[0x%x] = 0x%x", i, bbt->bb_tbl[i]);
if (bbt->bb_tbl[i] == 0)
return -1;
}
/* Physical Block start Address of BMT pool */
bmtd.pool_pba = bbt->bb_tbl[i - 1] + 1;
if (bmtd.pool_pba >= bmtd.total_blks - 2) {
pr_info("nand: FATAL ERR: Too many bad blocks!!\n");
return -1;
}
BBT_LOG("pool_pba=0x%x", bmtd.pool_pba);
i = 0;
block = bmtd.pool_pba;
/*
* The bmt table is used for runtime bad block mapping
* G - Good block; B - Bad block
* ---------------------------
* physical |G|G|B|G|B|B|G|G|G|G|B|G|B|
* ---------------------------
* block: 0 1 2 3 4 5 6 7 8 9 a b c
* What bmt_tbl[i] looks like in initial state:
* i:
* 0 1 2 3 4 5 6 7
* bmt_tbl[i].block:
* 0 1 3 6 7 8 9 b
* bmt_tbl[i].mapped:
* N N N N N N N B
* N - Not mapped(Available)
* M - Mapped
* B - BMT
* ATTENTION:
* BMT always in the last valid block in pool
*/
while ((block = find_valid_block(block)) != 0) {
bmt_tbl(bbt)[i].block = block;
bmt_tbl(bbt)[i].mapped = NO_MAPPED;
BBT_LOG("bmt_tbl[%d].block = 0x%x", i, block);
block++;
i++;
}
/* i - How many available blocks in pool, which is the length of bmt_tbl[]
* bmtd.bmt_blk_idx - bmt_tbl[bmtd.bmt_blk_idx].block => the BMT block
*/
bmtd.bmt_blk_idx = i - 1;
bmt_tbl(bbt)[bmtd.bmt_blk_idx].mapped = BMT_MAPPED;
if (i < 1) {
pr_info("nand: FATAL ERR: no space to store BMT!!\n");
return -1;
}
pr_info("[BBT] %d available blocks in BMT pool\n", i);
return 0;
}
static bool is_valid_bmt(unsigned char *buf, unsigned char *fdm)
{
struct bbbt *bbt = (struct bbbt *)buf;
u8 *sig = (u8*)bbt->signature + MAIN_SIGNATURE_OFFSET;
if (memcmp(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3) == 0 &&
memcmp(fdm + OOB_SIGNATURE_OFFSET, "bmt", 3) == 0) {
if (bbt->version == BBMT_VERSION)
return true;
}
BBT_LOG("[BBT] BMT Version not match,upgrage preloader and uboot please! sig=%02x%02x%02x, fdm=%02x%02x%02x",
sig[0], sig[1], sig[2],
fdm[1], fdm[2], fdm[3]);
return false;
}
static u16 get_bmt_index(struct bbmt *bmt)
{
int i = 0;
while (bmt[i].block != BMT_TBL_DEF_VAL) {
if (bmt[i].mapped == BMT_MAPPED)
return i;
i++;
}
return 0;
}
/* Write the Burner Bad Block Table to Nand Flash
* n - write BMT to bmt_tbl[n]
*/
static u16 upload_bmt(struct bbbt *bbt, int n)
{
u16 block;
retry:
if (n < 0 || bmt_tbl(bbt)[n].mapped == NORMAL_MAPPED) {
pr_info("nand: FATAL ERR: no space to store BMT!\n");
return (u16)-1;
}
block = bmt_tbl(bbt)[n].block;
BBT_LOG("n = 0x%x, block = 0x%x", n, block);
if (bbt_nand_erase(block)) {
bmt_tbl(bbt)[n].block = 0;
/* erase failed, try the previous block: bmt_tbl[n - 1].block */
n--;
goto retry;
}
/* The signature offset is fixed set to 0,
* oob signature offset is fixed set to 1
*/
memcpy(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3);
bbt->version = BBMT_VERSION;
if (write_bmt(block, (unsigned char *)bbt)) {
bmt_tbl(bbt)[n].block = 0;
/* write failed, try the previous block in bmt_tbl[n - 1] */
n--;
goto retry;
}
/* Return the current index(n) of BMT pool (bmt_tbl[n]) */
return n;
}
static u16 find_valid_block_in_pool(struct bbbt *bbt)
{
int i;
if (bmtd.bmt_blk_idx == 0)
goto error;
for (i = 0; i < bmtd.bmt_blk_idx; i++) {
if (bmt_tbl(bbt)[i].block != 0 && bmt_tbl(bbt)[i].mapped == NO_MAPPED) {
bmt_tbl(bbt)[i].mapped = NORMAL_MAPPED;
return bmt_tbl(bbt)[i].block;
}
}
error:
pr_info("nand: FATAL ERR: BMT pool is run out!\n");
return 0;
}
/* We met a bad block, mark it as bad and map it to a valid block in pool,
* if it's a write failure, we need to write the data to mapped block
*/
static bool remap_block_v2(u16 block, u16 mapped_block, int copy_len)
{
u16 new_block;
struct bbbt *bbt;
bbt = bmtd.bbt;
new_block = find_valid_block_in_pool(bbt);
if (new_block == 0)
return false;
/* Map new bad block to available block in pool */
bbt->bb_tbl[block] = new_block;
/* Erase new block */
bbt_nand_erase(new_block);
if (copy_len > 0)
bbt_nand_copy(new_block, mapped_block, copy_len);
bmtd.bmt_blk_idx = upload_bmt(bbt, bmtd.bmt_blk_idx);
return true;
}
static int get_mapping_block_index_v2(int block)
{
int start, end;
if (block >= bmtd.pool_lba)
return block;
if (!mapping_block_in_range(block, &start, &end))
return block;
return bmtd.bbt->bb_tbl[block];
}
static void
unmap_block_v2(u16 block)
{
bmtd.bbt->bb_tbl[block] = block;
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
}
static unsigned long *
mtk_bmt_get_mapping_mask(void)
{
struct bbmt *bbmt = bmt_tbl(bmtd.bbt);
int main_blocks = bmtd.mtd->size >> bmtd.blk_shift;
unsigned long *used;
int i, k;
used = kcalloc(sizeof(unsigned long), BIT_WORD(bmtd.bmt_blk_idx) + 1, GFP_KERNEL);
if (!used)
return NULL;
for (i = 1; i < main_blocks; i++) {
if (bmtd.bbt->bb_tbl[i] == i)
continue;
for (k = 0; k < bmtd.bmt_blk_idx; k++) {
if (bmtd.bbt->bb_tbl[i] != bbmt[k].block)
continue;
set_bit(k, used);
break;
}
}
return used;
}
static int mtk_bmt_debug_v2(void *data, u64 val)
{
struct bbmt *bbmt = bmt_tbl(bmtd.bbt);
struct mtd_info *mtd = bmtd.mtd;
unsigned long *used;
int main_blocks = mtd->size >> bmtd.blk_shift;
int n_remap = 0;
int i;
used = mtk_bmt_get_mapping_mask();
if (!used)
return -ENOMEM;
switch (val) {
case 0:
for (i = 1; i < main_blocks; i++) {
if (bmtd.bbt->bb_tbl[i] == i)
continue;
printk("remap [%x->%x]\n", i, bmtd.bbt->bb_tbl[i]);
n_remap++;
}
for (i = 0; i <= bmtd.bmt_blk_idx; i++) {
char c;
switch (bbmt[i].mapped) {
case NO_MAPPED:
continue;
case NORMAL_MAPPED:
c = 'm';
if (test_bit(i, used))
c = 'M';
break;
case BMT_MAPPED:
c = 'B';
break;
default:
c = 'X';
break;
}
printk("[%x:%c] = 0x%x\n", i, c, bbmt[i].block);
}
break;
case 100:
for (i = 0; i <= bmtd.bmt_blk_idx; i++) {
if (bbmt[i].mapped != NORMAL_MAPPED)
continue;
if (test_bit(i, used))
continue;
n_remap++;
bbmt[i].mapped = NO_MAPPED;
printk("free block [%d:%x]\n", i, bbmt[i].block);
}
if (n_remap)
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
break;
}
kfree(used);
return 0;
}
static int mtk_bmt_init_v2(struct device_node *np)
{
u32 bmt_pool_size, bmt_table_size;
u32 bufsz, block;
u16 pmt_block;
if (of_property_read_u32(np, "mediatek,bmt-pool-size",
&bmt_pool_size) != 0)
bmt_pool_size = 80;
if (of_property_read_u8(np, "mediatek,bmt-oob-offset",
&bmtd.oob_offset) != 0)
bmtd.oob_offset = 0;
if (of_property_read_u32(np, "mediatek,bmt-table-size",
&bmt_table_size) != 0)
bmt_table_size = 0x2000U;
bmtd.table_size = bmt_table_size;
pmt_block = bmtd.total_blks - bmt_pool_size - 2;
bmtd.mtd->size = pmt_block << bmtd.blk_shift;
/*
* ---------------------------------------
* | PMT(2blks) | BMT POOL(totalblks * 2%) |
* ---------------------------------------
* ^ ^
* | |
* pmt_block pmt_block + 2blocks(pool_lba)
*
* ATTETION!!!!!!
* The blocks ahead of the boundary block are stored in bb_tbl
* and blocks behind are stored in bmt_tbl
*/
bmtd.pool_lba = (u16)(pmt_block + 2);
bmtd.bb_max = bmtd.total_blks * BBPOOL_RATIO / 100;
bufsz = round_up(sizeof(struct bbbt) +
bmt_table_size * sizeof(struct bbmt), bmtd.pg_size);
bmtd.bmt_pgs = bufsz >> bmtd.pg_shift;
bmtd.bbt_buf = kzalloc(bufsz, GFP_KERNEL);
if (!bmtd.bbt_buf)
return -ENOMEM;
memset(bmtd.bbt_buf, 0xff, bufsz);
/* Scanning start from the first page of the last block
* of whole flash
*/
bmtd.bbt = NULL;
for (u16 block = bmtd.total_blks - 1; !bmtd.bbt && block >= bmtd.pool_lba; block--) {
u8 fdm[4];
if (bbt_nand_read(blk_pg(block), bmtd.bbt_buf, bufsz, fdm, sizeof(fdm))) {
/* Read failed, try the previous block */
continue;
}
if (!is_valid_bmt(bmtd.bbt_buf, fdm)) {
/* No valid BMT found, try the previous block */
continue;
}
bmtd.bmt_blk_idx = get_bmt_index(bmt_tbl((struct bbbt *)bmtd.bbt_buf));
if (bmtd.bmt_blk_idx == 0) {
pr_info("[BBT] FATAL ERR: bmt block index is wrong!\n");
break;
}
pr_info("[BBT] BMT.v2 is found at 0x%x\n", block);
bmtd.bbt = (struct bbbt *)bmtd.bbt_buf;
}
if (!bmtd.bbt) {
/* BMT not found */
if (bmtd.total_blks > BB_TABLE_MAX + BMT_TABLE_MAX) {
pr_info("nand: FATAL: Too many blocks, can not support!\n");
return -1;
}
bmtd.bbt = (struct bbbt *)bmtd.bbt_buf;
memset(bmt_tbl(bmtd.bbt), BMT_TBL_DEF_VAL,
bmtd.table_size * sizeof(struct bbmt));
if (scan_bad_blocks(bmtd.bbt))
return -1;
/* BMT always in the last valid block in pool */
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
block = bmt_tbl(bmtd.bbt)[bmtd.bmt_blk_idx].block;
pr_notice("[BBT] BMT.v2 is written into PBA:0x%x\n", block);
if (bmtd.bmt_blk_idx == 0)
pr_info("nand: Warning: no available block in BMT pool!\n");
else if (bmtd.bmt_blk_idx == (u16)-1)
return -1;
}
return 0;
}
const struct mtk_bmt_ops mtk_bmt_v2_ops = {
.sig = "bmt",
.sig_len = 3,
.init = mtk_bmt_init_v2,
.remap_block = remap_block_v2,
.unmap_block = unmap_block_v2,
.get_mapping_block = get_mapping_block_index_v2,
.debug = mtk_bmt_debug_v2,
};

View file

@ -0,0 +1,365 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Parser for MikroTik RouterBoot partitions.
*
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This parser builds from the "fixed-partitions" one (see ofpart.c), but it can
* handle dynamic partitions as found on routerboot devices.
*
* DTS nodes are defined as follows:
* For fixed partitions:
* node-name@unit-address {
* reg = <prop-encoded-array>;
* label = <string>;
* read-only;
* lock;
* };
*
* reg property is mandatory; other properties are optional.
* reg format is <address length>. length can be 0 if the next partition is
* another fixed partition or a "well-known" partition as defined below: in that
* case the partition will extend up to the next one.
*
* For dynamic partitions:
* node-name {
* size = <prop-encoded-array>;
* label = <string>;
* read-only;
* lock;
* };
*
* size property is normally mandatory. It can only be omitted (or set to 0) if:
* - the partition is a "well-known" one (as defined below), in which case
* the partition size will be automatically adjusted; or
* - the next partition is a fixed one or a "well-known" one, in which case
* the current partition will extend up to the next one.
* Other properties are optional.
* size format is <length>.
* By default dynamic partitions are appended after the preceding one, except
* for "well-known" ones which are automatically located on flash.
*
* Well-known partitions (matched via label or node-name):
* - "hard_config"
* - "soft_config"
* - "dtb_config"
*
* Note: this parser will happily register 0-sized partitions if misused.
*
* This parser requires the DTS to list partitions in ascending order as
* expected on the MTD device.
*
* Since only the "hard_config" and "soft_config" partitions are used in OpenWRT,
* a minimal working DTS could define only these two partitions dynamically (in
* the right order, usually hard_config then soft_config).
*
* Note: some mips RB devices encode the hard_config offset and length in two
* consecutive u32 located at offset 0x14 (for ramips) or 0x24 (for ath79) on
* the SPI NOR flash. Unfortunately this seems inconsistent across machines and
* does not apply to e.g. ipq-based ones, so we ignore that information.
*
* Note: To find well-known partitions, this parser will go through the entire
* top mtd partition parsed, _before_ the DTS nodes are processed. This works
* well in the current state of affairs, and is a simpler implementation than
* searching for known partitions in the "holes" left between fixed-partition,
* _after_ processing DTS nodes.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/libfdt_env.h>
#include <linux/string.h>
#define RB_MAGIC_HARD (('H') | ('a' << 8) | ('r' << 16) | ('d' << 24))
#define RB_MAGIC_SOFT (('S') | ('o' << 8) | ('f' << 16) | ('t' << 24))
#define RB_BLOCK_SIZE 0x1000
struct routerboot_dynpart {
const char * const name;
const u32 magic;
int (* const size_fixup)(struct mtd_info *, struct routerboot_dynpart *);
size_t offset;
size_t size;
bool found;
};
static int routerboot_dtbsfixup(struct mtd_info *, struct routerboot_dynpart *);
static struct routerboot_dynpart rb_dynparts[] = {
{
.name = "hard_config",
.magic = RB_MAGIC_HARD, // stored in CPU-endianness on flash
.size_fixup = NULL,
.offset = 0x0,
.size = RB_BLOCK_SIZE,
.found = false,
}, {
.name = "soft_config",
.magic = RB_MAGIC_SOFT, // stored in CPU-endianness on flash
.size_fixup = NULL,
.offset = 0x0,
.size = RB_BLOCK_SIZE,
.found = false,
}, {
.name = "dtb_config",
.magic = fdt32_to_cpu(OF_DT_HEADER), // stored BE on flash
.size_fixup = routerboot_dtbsfixup,
.offset = 0x0,
.size = 0x0,
.found = false,
}
};
static int routerboot_dtbsfixup(struct mtd_info *master, struct routerboot_dynpart *rbdpart)
{
int err;
size_t bytes_read, psize;
struct {
fdt32_t magic;
fdt32_t totalsize;
fdt32_t off_dt_struct;
fdt32_t off_dt_strings;
fdt32_t off_mem_rsvmap;
fdt32_t version;
fdt32_t last_comp_version;
fdt32_t boot_cpuid_phys;
fdt32_t size_dt_strings;
fdt32_t size_dt_struct;
} fdt_header;
err = mtd_read(master, rbdpart->offset, sizeof(fdt_header),
&bytes_read, (u8 *)&fdt_header);
if (err)
return err;
if (bytes_read != sizeof(fdt_header))
return -EIO;
psize = fdt32_to_cpu(fdt_header.totalsize);
if (!psize)
return -EINVAL;
rbdpart->size = psize;
return 0;
}
static void routerboot_find_dynparts(struct mtd_info *master)
{
size_t bytes_read, offset;
bool allfound;
int err, i;
u32 buf;
/*
* Dynamic RouterBoot partitions offsets are aligned to RB_BLOCK_SIZE:
* read the whole partition at RB_BLOCK_SIZE intervals to find sigs.
* Skip partition content when possible.
*/
offset = 0;
while (offset < master->size) {
err = mtd_read(master, offset, sizeof(buf), &bytes_read, (u8 *)&buf);
if (err) {
pr_err("%s: mtd_read error while parsing (offset: 0x%zX): %d\n",
master->name, offset, err);
continue;
}
allfound = true;
for (i = 0; i < ARRAY_SIZE(rb_dynparts); i++) {
if (rb_dynparts[i].found)
continue;
allfound = false;
if (rb_dynparts[i].magic == buf) {
rb_dynparts[i].offset = offset;
if (rb_dynparts[i].size_fixup) {
err = rb_dynparts[i].size_fixup(master, &rb_dynparts[i]);
if (err) {
pr_err("%s: size fixup error while parsing \"%s\": %d\n",
master->name, rb_dynparts[i].name, err);
continue;
}
}
rb_dynparts[i].found = true;
/*
* move offset to skip the whole partition on
* next iteration if size > RB_BLOCK_SIZE.
*/
if (rb_dynparts[i].size > RB_BLOCK_SIZE)
offset += ALIGN_DOWN((rb_dynparts[i].size - RB_BLOCK_SIZE), RB_BLOCK_SIZE);
break;
}
}
offset += RB_BLOCK_SIZE;
if (allfound)
break;
}
}
static int routerboot_partitions_parse(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *rbpart_node, *pp;
struct mtd_partition *parts;
const char *partname;
size_t master_ofs;
int np;
/* Pull of_node from the master device node */
rbpart_node = mtd_get_of_node(master);
if (!rbpart_node)
return 0;
/* First count the subnodes */
np = 0;
for_each_child_of_node(rbpart_node, pp)
np++;
if (!np)
return 0;
parts = kcalloc(np, sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
/* Preemptively look for known parts in flash */
routerboot_find_dynparts(master);
np = 0;
master_ofs = 0;
for_each_child_of_node(rbpart_node, pp) {
const __be32 *reg, *sz;
size_t offset, size;
int i, len, a_cells, s_cells;
partname = of_get_property(pp, "label", &len);
/* Allow deprecated use of "name" instead of "label" */
if (!partname)
partname = of_get_property(pp, "name", &len);
/* Fallback to node name per spec if all else fails: partname is always set */
if (!partname)
partname = pp->name;
parts[np].name = partname;
reg = of_get_property(pp, "reg", &len);
if (reg) {
/* Fixed partition */
a_cells = of_n_addr_cells(pp);
s_cells = of_n_size_cells(pp);
if ((len / 4) != (a_cells + s_cells)) {
pr_debug("%s: routerboot partition %pOF (%pOF) error parsing reg property.\n",
master->name, pp, rbpart_node);
goto rbpart_fail;
}
offset = of_read_number(reg, a_cells);
size = of_read_number(reg + a_cells, s_cells);
} else {
/* Dynamic partition */
/* Default: part starts at current offset, 0 size */
offset = master_ofs;
size = 0;
/* Check if well-known partition */
for (i = 0; i < ARRAY_SIZE(rb_dynparts); i++) {
if (!strcmp(partname, rb_dynparts[i].name) && rb_dynparts[i].found) {
offset = rb_dynparts[i].offset;
size = rb_dynparts[i].size;
break;
}
}
/* Standalone 'size' property? Override size */
sz = of_get_property(pp, "size", &len);
if (sz) {
s_cells = of_n_size_cells(pp);
if ((len / 4) != s_cells) {
pr_debug("%s: routerboot partition %pOF (%pOF) error parsing size property.\n",
master->name, pp, rbpart_node);
goto rbpart_fail;
}
size = of_read_number(sz, s_cells);
}
}
if (np > 0) {
/* Minor sanity check for overlaps */
if (offset < (parts[np-1].offset + parts[np-1].size)) {
pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" overlaps with previous partition \"%s\".\n",
master->name, pp, rbpart_node,
partname, parts[np-1].name);
goto rbpart_fail;
}
/* Fixup end of previous partition if necessary */
if (!parts[np-1].size)
parts[np-1].size = (offset - parts[np-1].offset);
}
if ((offset + size) > master->size) {
pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" extends past end of segment.\n",
master->name, pp, rbpart_node, partname);
goto rbpart_fail;
}
parts[np].offset = offset;
parts[np].size = size;
parts[np].of_node = pp;
if (of_get_property(pp, "read-only", &len))
parts[np].mask_flags |= MTD_WRITEABLE;
if (of_get_property(pp, "lock", &len))
parts[np].mask_flags |= MTD_POWERUP_LOCK;
/* Keep master offset aligned to RB_BLOCK_SIZE */
master_ofs = ALIGN(offset + size, RB_BLOCK_SIZE);
np++;
}
*pparts = parts;
return np;
rbpart_fail:
pr_err("%s: error parsing routerboot partition %pOF (%pOF)\n",
master->name, pp, rbpart_node);
of_node_put(pp);
kfree(parts);
return -EINVAL;
}
static const struct of_device_id parse_routerbootpart_match_table[] = {
{ .compatible = "mikrotik,routerboot-partitions" },
{},
};
MODULE_DEVICE_TABLE(of, parse_routerbootpart_match_table);
static struct mtd_part_parser routerbootpart_parser = {
.parse_fn = routerboot_partitions_parse,
.name = "routerbootpart",
.of_match_table = parse_routerbootpart_match_table,
};
module_mtd_part_parser(routerbootpart_parser);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MTD partitioning for RouterBoot");
MODULE_AUTHOR("Thibaut VARENE");