1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* a mtdsplit driver for IIJ SEIL devices */
4 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
6 #include <linux/module.h>
7 #include <linux/kernel.h>
8 #include <linux/slab.h>
9 #include <linux/vmalloc.h>
11 #include <linux/mtd/mtd.h>
12 #include <linux/mtd/partitions.h>
13 #include <linux/byteorder/generic.h>
19 #define LDR_ENV_PART_NAME "bootloader-env"
20 #define LDR_ENV_KEY_BOOTDEV "BOOTDEV"
23 uint64_t id
; /* Identifier */
24 uint8_t copy
[80]; /* Copyright */
25 uint32_t dcrc
; /* Data CRC Checksum */
26 uint32_t vfmt
; /* Image Version Format */
27 uint32_t vmjr
; /* Image Version Major */
28 uint32_t vmnr
; /* Image Version Minor */
29 uint8_t vrel
[32]; /* Image Version Release */
30 uint32_t dxor
; /* xor value for Data? */
31 uint32_t dlen
; /* Data Length */
35 * check whether the current mtd device is active or not
37 * example of BOOTDEV value (IIJ SA-W2):
38 * - "flash" : primary image on flash
39 * - "rescue" : secondary image on flash
40 * - "usb" : usb storage
41 * - "lan0/1" : network
43 static bool seil_bootdev_is_active(struct device_node
*np
)
45 struct mtd_info
*env_mtd
;
46 char *buf
, *var
, *value
, *eq
;
52 * read bootdev name of the partition
53 * if doesn't exist, return true and skip checking of active device
55 ret
= of_property_read_string(np
, "iij,bootdev-name", &devnm
);
61 env_mtd
= get_mtd_device_nm(LDR_ENV_PART_NAME
);
62 if (IS_ERR(env_mtd
)) {
63 pr_err("failed to get mtd device \"%s\"", LDR_ENV_PART_NAME
);
67 buf
= vmalloc(env_mtd
->size
);
71 ret
= mtd_read(env_mtd
, 0, env_mtd
->size
, &rdlen
, buf
);
72 if (ret
|| rdlen
!= env_mtd
->size
) {
73 pr_err("failed to read from mtd (%d)\n", ret
);
78 for (var
= buf
, ret
= false;
79 var
< buf
+ env_mtd
->size
&& *var
;
80 var
= value
+ strlen(value
) + 1) {
81 eq
= strchr(var
, '=');
87 pr_debug("ENV: %s=%s\n", var
, value
);
88 if (!strcmp(var
, LDR_ENV_KEY_BOOTDEV
)) {
89 ret
= !strcmp(devnm
, value
);
100 static int mtdsplit_parse_seil_fw(struct mtd_info
*master
,
101 const struct mtd_partition
**pparts
,
102 struct mtd_part_parser_data
*data
)
104 struct device_node
*np
= mtd_get_of_node(master
);
105 struct mtd_partition
*parts
;
106 struct seil_header header
;
107 size_t image_size
= 0;
108 size_t rootfs_offset
;
109 size_t hdrlen
= sizeof(header
);
114 if (!seil_bootdev_is_active(np
))
117 ret
= of_property_read_u64(np
, "iij,seil-id", &id
);
119 pr_err("failed to get iij,seil-id from dt\n");
122 pr_debug("got seil-id=0x%016llx from dt\n", id
);
124 parts
= kcalloc(NR_PARTS
, sizeof(*parts
), GFP_KERNEL
);
128 ret
= mtd_read(master
, 0, hdrlen
, &retlen
, (void *)&header
);
132 if (retlen
!= hdrlen
) {
137 if (be64_to_cpu(header
.id
) != id
||
138 be32_to_cpu(header
.vfmt
) != SEIL_VFMT
) {
139 pr_debug("no valid seil image found in \"%s\"\n", master
->name
);
144 image_size
= hdrlen
+ be32_to_cpu(header
.dlen
);
145 if (image_size
> master
->size
) {
146 pr_err("seil image exceeds MTD device \"%s\"\n", master
->name
);
151 /* find the roots after the seil image */
152 ret
= mtd_find_rootfs_from(master
, image_size
,
153 master
->size
, &rootfs_offset
, NULL
);
154 if (ret
|| (master
->size
- rootfs_offset
) == 0) {
155 pr_debug("no rootfs after seil image in \"%s\"\n",
161 parts
[0].name
= KERNEL_PART_NAME
;
163 parts
[0].size
= rootfs_offset
;
165 parts
[1].name
= ROOTFS_PART_NAME
;
166 parts
[1].offset
= rootfs_offset
;
167 parts
[1].size
= master
->size
- rootfs_offset
;
177 static const struct of_device_id mtdsplit_seil_fw_of_match_table
[] = {
178 { .compatible
= "iij,seil-firmware" },
181 MODULE_DEVICE_TABLE(of
, mtdsplit_seil_fw_of_match_table
);
183 static struct mtd_part_parser mtdsplit_seil_fw_parser
= {
184 .owner
= THIS_MODULE
,
186 .of_match_table
= mtdsplit_seil_fw_of_match_table
,
187 .parse_fn
= mtdsplit_parse_seil_fw
,
188 .type
= MTD_PARSER_TYPE_FIRMWARE
,
191 module_mtd_part_parser(mtdsplit_seil_fw_parser
);