routerboot.c 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Driver for MikroTik RouterBoot flash data. Common routines.
  4. *
  5. * Copyright (C) 2020 Thibaut VARÈNE <[email protected]>
  6. *
  7. * This program is free software; you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License version 2 as published
  9. * by the Free Software Foundation.
  10. */
  11. #include <linux/types.h>
  12. #include <linux/module.h>
  13. #include <linux/kernel.h>
  14. #include <linux/sysfs.h>
  15. #include <linux/mtd/mtd.h>
  16. #include "routerboot.h"
  17. static struct kobject *rb_kobj;
  18. /**
  19. * routerboot_tag_find() - Locate a given tag in routerboot config data.
  20. * @bufhead: the buffer to look into. Must start with a tag node.
  21. * @buflen: size of bufhead
  22. * @tag_id: the tag identifier to look for
  23. * @pld_ofs: will be updated with tag payload offset in bufhead, if tag found
  24. * @pld_len: will be updated with tag payload size, if tag found
  25. *
  26. * This incarnation of tag_find() does only that: it finds a specific routerboot
  27. * tag node in the input buffer. Routerboot tag nodes are u32 values:
  28. * - The low nibble is the tag identification number,
  29. * - The high nibble is the tag payload length (node excluded) in bytes.
  30. * The payload immediately follows the tag node. Tag nodes are 32bit-aligned.
  31. * The returned pld_ofs will always be aligned. pld_len may not end on 32bit
  32. * boundary (the only known case is when parsing ERD data).
  33. * The nodes are cpu-endian on the flash media. The payload is cpu-endian when
  34. * applicable. Tag nodes are not ordered (by ID) on flash.
  35. *
  36. * Return: 0 on success (tag found) or errno
  37. */
  38. int routerboot_tag_find(const u8 *bufhead, const size_t buflen, const u16 tag_id,
  39. u16 *pld_ofs, u16 *pld_len)
  40. {
  41. const u32 *datum, *bufend;
  42. u32 node;
  43. u16 id, len;
  44. int ret;
  45. if (!bufhead || !tag_id)
  46. return -EINVAL;
  47. ret = -ENOENT;
  48. datum = (const u32 *)bufhead;
  49. bufend = (const u32 *)(bufhead + buflen);
  50. while (datum < bufend) {
  51. node = *datum++;
  52. /* Tag list ends with null node */
  53. if (!node)
  54. break;
  55. id = node & 0xFFFF;
  56. len = node >> 16;
  57. if (tag_id == id) {
  58. if (datum >= bufend)
  59. break;
  60. if (pld_ofs)
  61. *pld_ofs = (u16)((u8 *)datum - bufhead);
  62. if (pld_len)
  63. *pld_len = len;
  64. ret = 0;
  65. break;
  66. }
  67. /*
  68. * The only known situation where len may not end on 32bit
  69. * boundary is within ERD data. Since we're only extracting
  70. * one tag (the first and only one) from that data, we should
  71. * never need to forcefully ALIGN(). Do it anyway, this is not a
  72. * performance path.
  73. */
  74. len = ALIGN(len, sizeof(*datum));
  75. datum += len / sizeof(*datum);
  76. }
  77. return ret;
  78. }
  79. /**
  80. * routerboot_rle_decode() - Simple RLE (MikroTik variant) decoding routine.
  81. * @in: input buffer to decode
  82. * @inlen: size of in
  83. * @out: output buffer to write decoded data to
  84. * @outlen: pointer to out size when function is called, will be updated with
  85. * size of decoded output on return
  86. *
  87. * MikroTik's variant of RLE operates as follows, considering a signed run byte:
  88. * - positive run => classic RLE
  89. * - negative run => the next -<run> bytes must be copied verbatim
  90. * The API is matched to the lzo1x routines for convenience.
  91. *
  92. * NB: The output buffer cannot overlap with the input buffer.
  93. *
  94. * Return: 0 on success or errno
  95. */
  96. int routerboot_rle_decode(const u8 *in, size_t inlen, u8 *out, size_t *outlen)
  97. {
  98. int ret, run, nbytes; // use native types for speed
  99. u8 byte;
  100. if (!in || (inlen < 2) || !out)
  101. return -EINVAL;
  102. ret = -ENOSPC;
  103. nbytes = 0;
  104. while (inlen >= 2) {
  105. run = *in++;
  106. inlen--;
  107. /* Verbatim copies */
  108. if (run & 0x80) {
  109. /* Invert run byte sign */
  110. run = ~run & 0xFF;
  111. run++;
  112. if (run > inlen)
  113. goto fail;
  114. inlen -= run;
  115. nbytes += run;
  116. if (nbytes > *outlen)
  117. goto fail;
  118. /* Basic memcpy */
  119. while (run-- > 0)
  120. *out++ = *in++;
  121. }
  122. /* Stream of half-words RLE: <run><byte>. run == 0 is ignored */
  123. else {
  124. byte = *in++;
  125. inlen--;
  126. nbytes += run;
  127. if (nbytes > *outlen)
  128. goto fail;
  129. while (run-- > 0)
  130. *out++ = byte;
  131. }
  132. }
  133. ret = 0;
  134. fail:
  135. *outlen = nbytes;
  136. return ret;
  137. }
  138. static void routerboot_mtd_notifier_add(struct mtd_info *mtd)
  139. {
  140. /* Currently routerboot is only known to live on NOR flash */
  141. if (mtd->type != MTD_NORFLASH)
  142. return;
  143. /*
  144. * We ignore the following return values and always register.
  145. * These init() routines are designed so that their failed state is
  146. * always manageable by the corresponding exit() calls.
  147. * Notifier is called with MTD mutex held: use __get/__put variants.
  148. * TODO: allow partition names override
  149. */
  150. if (!strcmp(mtd->name, RB_MTD_HARD_CONFIG))
  151. rb_hardconfig_init(rb_kobj, mtd);
  152. else if (!strcmp(mtd->name, RB_MTD_SOFT_CONFIG))
  153. rb_softconfig_init(rb_kobj, mtd);
  154. }
  155. static void routerboot_mtd_notifier_remove(struct mtd_info *mtd)
  156. {
  157. if (mtd->type != MTD_NORFLASH)
  158. return;
  159. if (!strcmp(mtd->name, RB_MTD_HARD_CONFIG))
  160. rb_hardconfig_exit();
  161. else if (!strcmp(mtd->name, RB_MTD_SOFT_CONFIG))
  162. rb_softconfig_exit();
  163. }
  164. /* Note: using a notifier prevents qualifying init()/exit() functions with __init/__exit */
  165. static struct mtd_notifier routerboot_mtd_notifier = {
  166. .add = routerboot_mtd_notifier_add,
  167. .remove = routerboot_mtd_notifier_remove,
  168. };
  169. static int __init routerboot_init(void)
  170. {
  171. rb_kobj = kobject_create_and_add("mikrotik", firmware_kobj);
  172. if (!rb_kobj)
  173. return -ENOMEM;
  174. register_mtd_user(&routerboot_mtd_notifier);
  175. return 0;
  176. }
  177. static void __exit routerboot_exit(void)
  178. {
  179. unregister_mtd_user(&routerboot_mtd_notifier);
  180. /* Exit routines are idempotent */
  181. rb_softconfig_exit();
  182. rb_hardconfig_exit();
  183. kobject_put(rb_kobj); // recursive afaict
  184. }
  185. /* Common routines */
  186. ssize_t routerboot_tag_show_string(const u8 *pld, u16 pld_len, char *buf)
  187. {
  188. return scnprintf(buf, pld_len+1, "%s\n", pld);
  189. }
  190. ssize_t routerboot_tag_show_u32s(const u8 *pld, u16 pld_len, char *buf)
  191. {
  192. char *out = buf;
  193. u32 *data; // cpu-endian
  194. /* Caller ensures pld_len > 0 */
  195. if (pld_len % sizeof(*data))
  196. return -EINVAL;
  197. data = (u32 *)pld;
  198. do {
  199. out += sprintf(out, "0x%08x\n", *data);
  200. data++;
  201. } while ((pld_len -= sizeof(*data)));
  202. return out - buf;
  203. }
  204. module_init(routerboot_init);
  205. module_exit(routerboot_exit);
  206. MODULE_LICENSE("GPL v2");
  207. MODULE_DESCRIPTION("MikroTik RouterBoot sysfs support");
  208. MODULE_AUTHOR("Thibaut VARENE");