mtdsplit_h3c_vfs.c 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Some devices made by H3C use a "VFS" filesystem to store firmware images.
  4. * This parses the start of the filesystem to read the length of the first
  5. * file (the kernel image). It then searches for the rootfs after the end of
  6. * the file data. This driver assumes that the filesystem was generated by
  7. * mkh3cvfs, and only works if the filesystem matches the expected layout,
  8. * which includes the file name of the kernel image.
  9. */
  10. #include <linux/module.h>
  11. #include <linux/kernel.h>
  12. #include <linux/slab.h>
  13. #include <linux/of.h>
  14. #include <linux/types.h>
  15. #include <linux/mtd/mtd.h>
  16. #include <linux/mtd/partitions.h>
  17. #include "mtdsplit.h"
  18. #define VFS_ERASEBLOCK_SIZE 0x10000
  19. #define VFS_BLOCK_SIZE 0x400
  20. #define VFS_BLOCKS_PER_ERASEBLOCK (VFS_ERASEBLOCK_SIZE / VFS_BLOCK_SIZE)
  21. #define FORMAT_FLAG_OFFSET 0x0
  22. #define FORMAT_FLAG (VFS_ERASEBLOCK_SIZE << 12 | VFS_BLOCK_SIZE)
  23. #define FILE_ENTRY_OFFSET 0x800
  24. #define FILE_ENTRY_FLAGS 0x3f
  25. #define FILE_ENTRY_PARENT_BLOCK 0
  26. #define FILE_ENTRY_PARENT_INDEX 0
  27. #define FILE_ENTRY_DATA_BLOCK 2
  28. #define FILE_ENTRY_NAME "openwrt-kernel.bin"
  29. #define NR_PARTS 2
  30. struct file_entry {
  31. uint8_t flags;
  32. uint8_t res0[5];
  33. uint16_t year;
  34. uint8_t month;
  35. uint8_t day;
  36. uint8_t hour;
  37. uint8_t minute;
  38. uint8_t second;
  39. uint8_t res1[3];
  40. uint32_t length;
  41. uint32_t parent_block;
  42. uint16_t parent_index;
  43. uint8_t res2[2];
  44. uint32_t data_block;
  45. char name[96];
  46. } __attribute__ ((packed));
  47. static inline size_t block_offset(int block)
  48. {
  49. return VFS_ERASEBLOCK_SIZE * (block / (VFS_BLOCKS_PER_ERASEBLOCK-1))
  50. + VFS_BLOCK_SIZE * (1 + (block % (VFS_BLOCKS_PER_ERASEBLOCK-1)));
  51. }
  52. static inline int block_count(size_t size)
  53. {
  54. return (size + VFS_BLOCK_SIZE - 1) / VFS_BLOCK_SIZE;
  55. }
  56. static int mtdsplit_h3c_vfs_parse(struct mtd_info *mtd,
  57. const struct mtd_partition **pparts,
  58. struct mtd_part_parser_data *data)
  59. {
  60. struct mtd_partition *parts;
  61. uint32_t format_flag;
  62. struct file_entry file_entry;
  63. size_t retlen;
  64. int err;
  65. size_t kernel_size;
  66. size_t expected_offset;
  67. size_t rootfs_offset;
  68. if (mtd->erasesize != VFS_ERASEBLOCK_SIZE)
  69. return -EINVAL;
  70. /* Check format flag */
  71. err = mtd_read(mtd, FORMAT_FLAG_OFFSET, sizeof(format_flag), &retlen,
  72. (void *) &format_flag);
  73. if (err)
  74. return err;
  75. if (retlen != sizeof(format_flag))
  76. return -EIO;
  77. if (format_flag != FORMAT_FLAG) {
  78. pr_info("mtdsplit_h3c_vfs: unexpected format flag %08x\n",
  79. format_flag);
  80. return 0;
  81. }
  82. /* Check file entry */
  83. err = mtd_read(mtd, FILE_ENTRY_OFFSET, sizeof(file_entry), &retlen,
  84. (void *) &file_entry);
  85. if (err)
  86. return err;
  87. if (retlen != sizeof(file_entry))
  88. return -EIO;
  89. if (file_entry.flags != FILE_ENTRY_FLAGS ||
  90. file_entry.parent_block != FILE_ENTRY_PARENT_BLOCK ||
  91. file_entry.parent_index != FILE_ENTRY_PARENT_INDEX ||
  92. file_entry.data_block != FILE_ENTRY_DATA_BLOCK ||
  93. strncmp(file_entry.name, FILE_ENTRY_NAME, sizeof(file_entry.name)) != 0) {
  94. pr_info("mtdsplit_h3c_vfs: unexpected file entry - OpenWrt probably not installed\n");
  95. return 0;
  96. }
  97. /* Find rootfs offset */
  98. kernel_size = block_offset(file_entry.data_block +
  99. block_count(file_entry.length) - 1) +
  100. VFS_BLOCK_SIZE;
  101. expected_offset = mtd_roundup_to_eb(kernel_size, mtd);
  102. err = mtd_find_rootfs_from(mtd, expected_offset, mtd->size,
  103. &rootfs_offset, NULL);
  104. if (err)
  105. return err;
  106. parts = kzalloc(NR_PARTS * sizeof(*parts), GFP_KERNEL);
  107. if (!parts)
  108. return -ENOMEM;
  109. parts[0].name = KERNEL_PART_NAME;
  110. parts[0].offset = 0;
  111. parts[0].size = rootfs_offset;
  112. parts[1].name = ROOTFS_PART_NAME;
  113. parts[1].offset = rootfs_offset;
  114. parts[1].size = mtd->size - rootfs_offset;
  115. *pparts = parts;
  116. return NR_PARTS;
  117. }
  118. static const struct of_device_id mtdsplit_h3c_vfs_of_match_table[] = {
  119. { .compatible = "h3c,vfs-firmware" },
  120. {},
  121. };
  122. MODULE_DEVICE_TABLE(of, mtdsplit_h3c_vfs_of_match_table);
  123. static struct mtd_part_parser mtdsplit_h3c_vfs_parser = {
  124. .owner = THIS_MODULE,
  125. .name = "h3c-vfs",
  126. .of_match_table = mtdsplit_h3c_vfs_of_match_table,
  127. .parse_fn = mtdsplit_h3c_vfs_parse,
  128. .type = MTD_PARSER_TYPE_FIRMWARE,
  129. };
  130. module_mtd_part_parser(mtdsplit_h3c_vfs_parser);