450-11-block-implement-NVMEM-provider.patch 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. From b9936aa8a3775c2027f655d91a206d0e6e1c7ec0 Mon Sep 17 00:00:00 2001
  2. From: Daniel Golle <[email protected]>
  3. Date: Tue, 11 Jul 2023 00:17:31 +0100
  4. Subject: [PATCH 11/15] block: implement NVMEM provider
  5. On embedded devices using an eMMC it is common that one or more partitions
  6. on the eMMC are used to store MAC addresses and Wi-Fi calibration EEPROM
  7. data. Allow referencing the partition in device tree for the kernel and
  8. Wi-Fi drivers accessing it via the NVMEM layer.
  9. Signed-off-by: Daniel Golle <[email protected]>
  10. ---
  11. block/Kconfig | 9 +++
  12. block/Makefile | 1 +
  13. block/blk-nvmem.c | 186 ++++++++++++++++++++++++++++++++++++++++++++++
  14. 3 files changed, 196 insertions(+)
  15. create mode 100644 block/blk-nvmem.c
  16. --- a/block/Kconfig
  17. +++ b/block/Kconfig
  18. @@ -208,6 +208,15 @@ config BLK_INLINE_ENCRYPTION_FALLBACK
  19. by falling back to the kernel crypto API when inline
  20. encryption hardware is not present.
  21. +config BLK_NVMEM
  22. + bool "Block device NVMEM provider"
  23. + depends on OF
  24. + depends on NVMEM
  25. + help
  26. + Allow block devices (or partitions) to act as NVMEM prodivers,
  27. + typically used with eMMC to store MAC addresses or Wi-Fi
  28. + calibration data on embedded devices.
  29. +
  30. source "block/partitions/Kconfig"
  31. config BLK_MQ_PCI
  32. --- a/block/Makefile
  33. +++ b/block/Makefile
  34. @@ -34,6 +34,7 @@ obj-$(CONFIG_BLK_DEV_ZONED) += blk-zoned
  35. obj-$(CONFIG_BLK_WBT) += blk-wbt.o
  36. obj-$(CONFIG_BLK_DEBUG_FS) += blk-mq-debugfs.o
  37. obj-$(CONFIG_BLK_DEBUG_FS_ZONED)+= blk-mq-debugfs-zoned.o
  38. +obj-$(CONFIG_BLK_NVMEM) += blk-nvmem.o
  39. obj-$(CONFIG_BLK_SED_OPAL) += sed-opal.o
  40. obj-$(CONFIG_BLK_PM) += blk-pm.o
  41. obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \
  42. --- /dev/null
  43. +++ b/block/blk-nvmem.c
  44. @@ -0,0 +1,186 @@
  45. +// SPDX-License-Identifier: GPL-2.0-or-later
  46. +/*
  47. + * block device NVMEM provider
  48. + *
  49. + * Copyright (c) 2023 Daniel Golle <[email protected]>
  50. + *
  51. + * Useful on devices using a partition on an eMMC for MAC addresses or
  52. + * Wi-Fi calibration EEPROM data.
  53. + */
  54. +
  55. +#include "blk.h"
  56. +#include <linux/nvmem-provider.h>
  57. +#include <linux/of.h>
  58. +#include <linux/pagemap.h>
  59. +#include <linux/property.h>
  60. +
  61. +/* List of all NVMEM devices */
  62. +static LIST_HEAD(nvmem_devices);
  63. +static DEFINE_MUTEX(devices_mutex);
  64. +
  65. +struct blk_nvmem {
  66. + struct nvmem_device *nvmem;
  67. + struct block_device *bdev;
  68. + struct list_head list;
  69. +};
  70. +
  71. +static int blk_nvmem_reg_read(void *priv, unsigned int from,
  72. + void *val, size_t bytes)
  73. +{
  74. + unsigned long offs = from & ~PAGE_MASK, to_read;
  75. + pgoff_t f_index = from >> PAGE_SHIFT;
  76. + struct address_space *mapping;
  77. + struct blk_nvmem *bnv = priv;
  78. + size_t bytes_left = bytes;
  79. + struct folio *folio;
  80. + void *p;
  81. + int ret;
  82. +
  83. + if (!bnv->bdev)
  84. + return -ENODEV;
  85. +
  86. + if (!bnv->bdev->bd_disk)
  87. + return -EINVAL;
  88. +
  89. + if (!bnv->bdev->bd_disk->fops)
  90. + return -EIO;
  91. +
  92. + if (!bnv->bdev->bd_disk->fops->open)
  93. + return -EIO;
  94. +
  95. + ret = bnv->bdev->bd_disk->fops->open(bnv->bdev->bd_disk, BLK_OPEN_READ);
  96. + if (ret)
  97. + return ret;
  98. +
  99. + mapping = bnv->bdev->bd_inode->i_mapping;
  100. +
  101. + while (bytes_left) {
  102. + folio = read_mapping_folio(mapping, f_index++, NULL);
  103. + if (IS_ERR(folio)) {
  104. + ret = PTR_ERR(folio);
  105. + goto err_release_bdev;
  106. + }
  107. + to_read = min_t(unsigned long, bytes_left, PAGE_SIZE - offs);
  108. + p = folio_address(folio) + offset_in_folio(folio, offs);
  109. + memcpy(val, p, to_read);
  110. + offs = 0;
  111. + bytes_left -= to_read;
  112. + val += to_read;
  113. + folio_put(folio);
  114. + }
  115. +
  116. +err_release_bdev:
  117. + bnv->bdev->bd_disk->fops->release(bnv->bdev->bd_disk);
  118. +
  119. + return ret;
  120. +}
  121. +
  122. +static int blk_nvmem_register(struct device *dev)
  123. +{
  124. + struct device_node *np = dev_of_node(dev);
  125. + struct block_device *bdev = dev_to_bdev(dev);
  126. + struct nvmem_config config = {};
  127. + struct blk_nvmem *bnv;
  128. +
  129. + /* skip devices which do not have a device tree node */
  130. + if (!np)
  131. + return 0;
  132. +
  133. + /* skip devices without an nvmem layout defined */
  134. + if (!of_get_child_by_name(np, "nvmem-layout"))
  135. + return 0;
  136. +
  137. + /*
  138. + * skip devices which don't have GENHD_FL_NVMEM set
  139. + *
  140. + * This flag is used for mtdblock and ubiblock devices because
  141. + * both, MTD and UBI already implement their own NVMEM provider.
  142. + * To avoid registering multiple NVMEM providers for the same
  143. + * device node, don't register the block NVMEM provider for them.
  144. + */
  145. + if (!(bdev->bd_disk->flags & GENHD_FL_NVMEM))
  146. + return 0;
  147. +
  148. + /*
  149. + * skip block device too large to be represented as NVMEM devices
  150. + * which are using an 'int' as address
  151. + */
  152. + if (bdev_nr_bytes(bdev) > INT_MAX)
  153. + return -EFBIG;
  154. +
  155. + bnv = kzalloc(sizeof(struct blk_nvmem), GFP_KERNEL);
  156. + if (!bnv)
  157. + return -ENOMEM;
  158. +
  159. + config.id = NVMEM_DEVID_NONE;
  160. + config.dev = &bdev->bd_device;
  161. + config.name = dev_name(&bdev->bd_device);
  162. + config.owner = THIS_MODULE;
  163. + config.priv = bnv;
  164. + config.reg_read = blk_nvmem_reg_read;
  165. + config.size = bdev_nr_bytes(bdev);
  166. + config.word_size = 1;
  167. + config.stride = 1;
  168. + config.read_only = true;
  169. + config.root_only = true;
  170. + config.ignore_wp = true;
  171. + config.of_node = to_of_node(dev->fwnode);
  172. +
  173. + bnv->bdev = bdev;
  174. + bnv->nvmem = nvmem_register(&config);
  175. + if (IS_ERR(bnv->nvmem)) {
  176. + dev_err_probe(&bdev->bd_device, PTR_ERR(bnv->nvmem),
  177. + "Failed to register NVMEM device\n");
  178. +
  179. + kfree(bnv);
  180. + return PTR_ERR(bnv->nvmem);
  181. + }
  182. +
  183. + mutex_lock(&devices_mutex);
  184. + list_add_tail(&bnv->list, &nvmem_devices);
  185. + mutex_unlock(&devices_mutex);
  186. +
  187. + return 0;
  188. +}
  189. +
  190. +static void blk_nvmem_unregister(struct device *dev)
  191. +{
  192. + struct block_device *bdev = dev_to_bdev(dev);
  193. + struct blk_nvmem *bnv_c, *bnv = NULL;
  194. +
  195. + mutex_lock(&devices_mutex);
  196. + list_for_each_entry(bnv_c, &nvmem_devices, list) {
  197. + if (bnv_c->bdev == bdev) {
  198. + bnv = bnv_c;
  199. + break;
  200. + }
  201. + }
  202. +
  203. + if (!bnv) {
  204. + mutex_unlock(&devices_mutex);
  205. + return;
  206. + }
  207. +
  208. + list_del(&bnv->list);
  209. + mutex_unlock(&devices_mutex);
  210. + nvmem_unregister(bnv->nvmem);
  211. + kfree(bnv);
  212. +}
  213. +
  214. +static struct class_interface blk_nvmem_bus_interface __refdata = {
  215. + .class = &block_class,
  216. + .add_dev = &blk_nvmem_register,
  217. + .remove_dev = &blk_nvmem_unregister,
  218. +};
  219. +
  220. +static int __init blk_nvmem_init(void)
  221. +{
  222. + int ret;
  223. +
  224. + ret = class_interface_register(&blk_nvmem_bus_interface);
  225. + if (ret)
  226. + return ret;
  227. +
  228. + return 0;
  229. +}
  230. +device_initcall(blk_nvmem_init);