diff --git a/Documentation/devicetree/bindings/mmc/mmc-card.yaml b/Documentation/devicetree/bindings/mmc/mmc-card.yaml index a61d6c96df759..b21426a49cf1d 100644 --- a/Documentation/devicetree/bindings/mmc/mmc-card.yaml +++ b/Documentation/devicetree/bindings/mmc/mmc-card.yaml @@ -40,6 +40,9 @@ patternProperties: contains: const: fixed-partitions + nvmem-layout: + $ref: /schemas/nvmem/layouts/nvmem-layout.yaml + required: - compatible - reg @@ -86,6 +89,27 @@ examples: read-only; }; }; + + partitions-boot2 { + nvmem-layout { + compatible = "fixed-layout"; + + #address-cells = <1>; + #size-cells = <1>; + + mac-addr@4400 { + compatible = "mac-base"; + reg = <0x4400 0x6>; + #nvmem-cell-cells = <1>; + }; + + bd-addr@5400 { + compatible = "mac-base"; + reg = <0x5400 0x6>; + #nvmem-cell-cells = <1>; + }; + }; + }; }; }; diff --git a/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml b/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml index c8e9c55c1afb4..7cb28f30c9af0 100644 --- a/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml +++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml @@ -22,4 +22,13 @@ properties: description: boot firmware is incorrectly passing the address in big-endian order + nvmem-cells: + maxItems: 1 + description: + Nvmem data cell that contains a 6 byte BD address with the most + significant byte first (big-endian). + + nvmem-cell-names: + const: local-bd-address + additionalProperties: true diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml index c21d66c7cd558..96e025cd1e3ac 100644 --- a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml +++ b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml @@ -92,6 +92,15 @@ properties: ieee80211-freq-limit: true + nvmem-cells: + maxItems: 1 + description: + Nvmem data cell that contains a 6 byte MAC address with the most + significant byte first (big-endian). + + nvmem-cell-names: + const: mac-address + qcom,calibration-data: $ref: /schemas/types.yaml#/definitions/uint8-array description: diff --git a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts index bf088fa9807f0..6ed91cccae2fb 100644 --- a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts +++ b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts @@ -409,7 +409,35 @@ no-sdio; no-sd; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + card@0 { + compatible = "mmc-card"; + reg = <0>; + + partitions-boot1 { + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + wifi_mac_addr: mac-addr@4400 { + compatible = "mac-base"; + reg = <0x4400 0x6>; + #nvmem-cell-cells = <1>; + }; + + bd_addr: bd-addr@5400 { + compatible = "mac-base"; + reg = <0x5400 0x6>; + #nvmem-cell-cells = <1>; + }; + }; + }; + }; }; &spi5 { @@ -512,6 +540,9 @@ vddch0-supply = <&pm4125_l22>; enable-gpios = <&tlmm 87 GPIO_ACTIVE_HIGH>; max-speed = <3000000>; + + nvmem-cells = <&bd_addr 0>; + nvmem-cell-names = "local-bd-address"; }; }; @@ -557,6 +588,9 @@ qcom,ath10k-calibration-variant = "ArduinoImola"; firmware-name = "qcm2290"; + nvmem-cells = <&wifi_mac_addr 0>; + nvmem-cell-names = "mac-address"; + status = "okay"; }; diff --git a/block/Kconfig b/block/Kconfig index 15027963472d7..0b33747e16dc3 100644 --- a/block/Kconfig +++ b/block/Kconfig @@ -209,6 +209,15 @@ config BLK_INLINE_ENCRYPTION_FALLBACK by falling back to the kernel crypto API when inline encryption hardware is not present. +config BLK_NVMEM + bool "Block device NVMEM provider" + depends on OF + depends on NVMEM + help + Allow block devices (or partitions) to act as NVMEM providers, + typically used with eMMC to store MAC addresses or Wi-Fi + calibration data on embedded devices. + source "block/partitions/Kconfig" config BLK_PM diff --git a/block/Makefile b/block/Makefile index 7dce2e44276c4..d7ac389e71902 100644 --- a/block/Makefile +++ b/block/Makefile @@ -36,3 +36,4 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \ blk-crypto-sysfs.o obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED) += holder.o +obj-$(CONFIG_BLK_NVMEM) += blk-nvmem.o diff --git a/block/blk-nvmem.c b/block/blk-nvmem.c new file mode 100644 index 0000000000000..96c0ffc51b186 --- /dev/null +++ b/block/blk-nvmem.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * block device NVMEM provider + * + * Copyright (c) 2024 Daniel Golle + * + * Useful on devices using a partition on an eMMC for MAC addresses or + * Wi-Fi calibration EEPROM data. + */ + +#include "blk.h" +#include +#include +#include +#include +#include + +static void blk_nvmem_free(void *data) +{ + kfree(data); +} + +/* List of all NVMEM devices */ +static LIST_HEAD(nvmem_devices); +static DEFINE_MUTEX(devices_mutex); + +struct blk_nvmem { + struct nvmem_device *nvmem; + dev_t devt; + bool removed; + struct list_head list; +}; + +static int blk_nvmem_reg_read(void *priv, unsigned int from, + void *val, size_t bytes) +{ + blk_mode_t mode = BLK_OPEN_READ | BLK_OPEN_RESTRICT_WRITES; + unsigned long offs = from & ~PAGE_MASK, to_read; + pgoff_t f_index = from >> PAGE_SHIFT; + struct blk_nvmem *bnv = priv; + size_t bytes_left = bytes; + struct file *bdev_file; + struct folio *folio; + void *p; + int ret = 0; + + if (bnv->removed) + return -ENODEV; + + bdev_file = bdev_file_open_by_dev(bnv->devt, mode, priv, NULL); + if (!bdev_file) + return -ENODEV; + + if (IS_ERR(bdev_file)) + return PTR_ERR(bdev_file); + + while (bytes_left) { + folio = read_mapping_folio(bdev_file->f_mapping, f_index++, NULL); + if (IS_ERR(folio)) { + ret = PTR_ERR(folio); + goto err_release_bdev; + } + to_read = min_t(unsigned long, bytes_left, PAGE_SIZE - offs); + p = folio_address(folio) + offset_in_folio(folio, offs); + memcpy(val, p, to_read); + offs = 0; + bytes_left -= to_read; + val += to_read; + folio_put(folio); + } + +err_release_bdev: + fput(bdev_file); + + return ret; +} + +static int blk_nvmem_register(struct device *dev) +{ + struct device_node *np = dev_of_node(dev); + struct block_device *bdev = dev_to_bdev(dev); + struct nvmem_config config = {}; + struct blk_nvmem *bnv; + + /* skip devices which do not have a device tree node */ + if (!np) + return 0; + + /* skip devices without an nvmem layout defined */ + if (!of_get_child_by_name(np, "nvmem-layout")) + return 0; + + /* + * skip block device too large to be represented as NVMEM devices + * which are using an 'int' as address + */ + if (bdev_nr_bytes(bdev) > INT_MAX) + return -EFBIG; + + bnv = kzalloc_obj(*bnv); + if (!bnv) + return -ENOMEM; + + config.id = NVMEM_DEVID_NONE; + config.dev = &bdev->bd_device; + config.name = dev_name(&bdev->bd_device); + config.owner = THIS_MODULE; + config.priv = bnv; + config.reg_read = blk_nvmem_reg_read; + config.size = bdev_nr_bytes(bdev); + config.word_size = 1; + config.stride = 1; + config.read_only = true; + config.root_only = true; + config.ignore_wp = true; + config.of_node = to_of_node(dev->fwnode); + + bnv->devt = bdev->bd_device.devt; + bnv->nvmem = nvmem_register(&config); + if (IS_ERR(bnv->nvmem)) { + dev_err_probe(&bdev->bd_device, PTR_ERR(bnv->nvmem), + "Failed to register NVMEM device\n"); + + kfree(bnv); + return PTR_ERR(bnv->nvmem); + } + + /* + * Free bnv only when the nvmem device is fully released (i.e. when + * its kref hits zero), not at unregister time. This prevents a + * use-after-free if a consumer still holds an nvmem_cell reference + * when the block device is removed: nvmem_unregister() only does a + * kref_put(), so reg_read could still be called with bnv as priv + * until the last consumer drops its cell. + */ + if (devm_add_action(nvmem_dev(bnv->nvmem), blk_nvmem_free, bnv)) { + nvmem_unregister(bnv->nvmem); + kfree(bnv); + return -ENOMEM; + } + + mutex_lock(&devices_mutex); + list_add_tail(&bnv->list, &nvmem_devices); + mutex_unlock(&devices_mutex); + + return 0; +} + +static void blk_nvmem_unregister(struct device *dev) +{ + struct blk_nvmem *bnv_c, *bnv = NULL; + + mutex_lock(&devices_mutex); + list_for_each_entry(bnv_c, &nvmem_devices, list) { + if (bnv_c->devt == dev_to_bdev(dev)->bd_device.devt) { + bnv = bnv_c; + break; + } + } + + if (!bnv) { + mutex_unlock(&devices_mutex); + return; + } + + list_del(&bnv->list); + mutex_unlock(&devices_mutex); + bnv->removed = true; + nvmem_unregister(bnv->nvmem); +} + +static struct class_interface blk_nvmem_bus_interface __refdata = { + .class = &block_class, + .add_dev = &blk_nvmem_register, + .remove_dev = &blk_nvmem_unregister, +}; + +static int __init blk_nvmem_init(void) +{ + int ret; + + ret = class_interface_register(&blk_nvmem_bus_interface); + if (ret) + return ret; + + return 0; +} +device_initcall(blk_nvmem_init); diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index dda76365726f0..df33eacfd29fa 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -721,8 +721,11 @@ static int qca_check_bdaddr(struct hci_dev *hdev, const struct qca_fw_config *co } bda = (struct hci_rp_read_bd_addr *)skb->data; - if (!bacmp(&bda->bdaddr, &config->bdaddr)) + if (!bacmp(&bda->bdaddr, &config->bdaddr)) { hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY); + hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM); + hci_set_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE); + } kfree_skb(skb); diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 311cb2e5a5c02..ee3481229c20b 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -2154,6 +2154,19 @@ const char *nvmem_dev_name(struct nvmem_device *nvmem) } EXPORT_SYMBOL_GPL(nvmem_dev_name); +/** + * nvmem_dev() - Get the struct device of a given nvmem device. + * + * @nvmem: nvmem device. + * + * Return: pointer to the struct device of the nvmem device. + */ +struct device *nvmem_dev(struct nvmem_device *nvmem) +{ + return &nvmem->dev; +} +EXPORT_SYMBOL_GPL(nvmem_dev); + /** * nvmem_dev_size() - Get the size of a given nvmem device. * diff --git a/include/linux/nvmem-consumer.h b/include/linux/nvmem-consumer.h index 34c0e58dfa266..ce387c89dc8e4 100644 --- a/include/linux/nvmem-consumer.h +++ b/include/linux/nvmem-consumer.h @@ -82,6 +82,7 @@ int nvmem_device_cell_write(struct nvmem_device *nvmem, const char *nvmem_dev_name(struct nvmem_device *nvmem); size_t nvmem_dev_size(struct nvmem_device *nvmem); +struct device *nvmem_dev(struct nvmem_device *nvmem); void nvmem_add_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries); @@ -220,6 +221,11 @@ static inline const char *nvmem_dev_name(struct nvmem_device *nvmem) return NULL; } +static inline struct device *nvmem_dev(struct nvmem_device *nvmem) +{ + return NULL; +} + static inline void nvmem_add_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries) {} static inline void diff --git a/include/linux/of_net.h b/include/linux/of_net.h index d88715a0b3a52..7854ba555d9a5 100644 --- a/include/linux/of_net.h +++ b/include/linux/of_net.h @@ -15,6 +15,7 @@ struct net_device; extern int of_get_phy_mode(struct device_node *np, phy_interface_t *interface); extern int of_get_mac_address(struct device_node *np, u8 *mac); extern int of_get_mac_address_nvmem(struct device_node *np, u8 *mac); +int of_get_nvmem_eui48(struct device_node *np, const char *cell_name, u8 *addr); int of_get_ethdev_address(struct device_node *np, struct net_device *dev); extern struct net_device *of_find_net_device_by_node(struct device_node *np); #else @@ -34,6 +35,12 @@ static inline int of_get_mac_address_nvmem(struct device_node *np, u8 *mac) return -ENODEV; } +static inline int of_get_nvmem_eui48(struct device_node *np, + const char *cell_name, u8 *addr) +{ + return -ENODEV; +} + static inline int of_get_ethdev_address(struct device_node *np, struct net_device *dev) { return -ENODEV; diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 572b1c620c5d6..7686466d11092 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -164,6 +164,24 @@ enum { */ HCI_QUIRK_BDADDR_PROPERTY_BROKEN, + /* When this quirk is set, the public Bluetooth address + * initially reported by HCI Read BD Address command + * is considered invalid. The public BD Address can be + * retrieved via a 'local-bd-address' NVMEM cell. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_USE_BDADDR_NVMEM, + + /* When this quirk is set, the Bluetooth Device Address provided by + * the 'local-bd-address' NVMEM is stored in big-endian order. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_BDADDR_NVMEM_BE, + /* When this quirk is set, the duplicate filtering during * scanning is based on Bluetooth devices addresses. To allow * RSSI based updates, restart scanning if needed. diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index fd3aacdea512a..589ccdfa26c12 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -7,6 +7,7 @@ */ #include +#include #include #include @@ -3588,6 +3589,37 @@ int hci_powered_update_sync(struct hci_dev *hdev) return 0; } +/** + * hci_dev_get_bd_addr_from_nvmem - Get the Bluetooth Device Address + * (BD_ADDR) for a HCI device from + * an NVMEM cell. + * @hdev: The HCI device + * + * Search for 'local-bd-address' NVMEM cell in the device firmware node. + * + * All-zero BD addresses are rejected (unprovisioned). + */ +static int hci_dev_get_bd_addr_from_nvmem(struct hci_dev *hdev) +{ + struct device_node *np = dev_of_node(hdev->dev.parent); + u8 ba[sizeof(bdaddr_t)]; + int err; + + if (!np) + return -ENODEV; + + err = of_get_nvmem_eui48(np, "local-bd-address", ba); + if (err) + return err; + + if (hci_test_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE)) + baswap(&hdev->public_addr, (bdaddr_t *)ba); + else + bacpy(&hdev->public_addr, (bdaddr_t *)ba); + + return 0; +} + /** * hci_dev_get_bd_addr_from_property - Get the Bluetooth Device Address * (BD_ADDR) for a HCI device from @@ -5042,12 +5074,17 @@ static int hci_dev_setup_sync(struct hci_dev *hdev) * its setup callback. */ invalid_bdaddr = hci_test_quirk(hdev, HCI_QUIRK_INVALID_BDADDR) || - hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY); + hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY) || + hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM); if (!ret) { if (hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY) && !bacmp(&hdev->public_addr, BDADDR_ANY)) hci_dev_get_bd_addr_from_property(hdev); + if (hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM) && + !bacmp(&hdev->public_addr, BDADDR_ANY)) + hci_dev_get_bd_addr_from_nvmem(hdev); + if (invalid_bdaddr && bacmp(&hdev->public_addr, BDADDR_ANY) && hdev->set_bdaddr) { ret = hdev->set_bdaddr(hdev, &hdev->public_addr); diff --git a/net/core/of_net.c b/net/core/of_net.c index 93ea425b9248a..79b289de0f16a 100644 --- a/net/core/of_net.c +++ b/net/core/of_net.c @@ -61,9 +61,6 @@ static int of_get_mac_addr(struct device_node *np, const char *name, u8 *addr) int of_get_mac_address_nvmem(struct device_node *np, u8 *addr) { struct platform_device *pdev = of_find_device_by_node(np); - struct nvmem_cell *cell; - const void *mac; - size_t len; int ret; /* Try lookup by device first, there might be a nvmem_cell_lookup @@ -75,27 +72,53 @@ int of_get_mac_address_nvmem(struct device_node *np, u8 *addr) return ret; } - cell = of_nvmem_cell_get(np, "mac-address"); + ret = of_get_nvmem_eui48(np, "mac-address", addr); + if (ret) + return ret; + + if (!is_valid_ether_addr(addr)) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL(of_get_mac_address_nvmem); + +/** + * of_get_nvmem_eui48 - Read a 6-byte EUI-48 address from a named NVMEM cell. + * @np: Device node to look up the NVMEM cell from. + * @cell_name: Name of the NVMEM cell (e.g. "mac-address", "local-bd-address"). + * @addr: Output buffer for the 6-byte address. + * + * Reads the named NVMEM cell and validates that it contains a non-zero 6-byte + * address. Returns 0 on success, negative errno on failure. + */ +int of_get_nvmem_eui48(struct device_node *np, const char *cell_name, u8 *addr) +{ + struct nvmem_cell *cell; + const void *eui48; + size_t len; + + cell = of_nvmem_cell_get(np, cell_name); if (IS_ERR(cell)) return PTR_ERR(cell); - mac = nvmem_cell_read(cell, &len); + eui48 = nvmem_cell_read(cell, &len); nvmem_cell_put(cell); - if (IS_ERR(mac)) - return PTR_ERR(mac); + if (IS_ERR(eui48)) + return PTR_ERR(eui48); - if (len != ETH_ALEN || !is_valid_ether_addr(mac)) { - kfree(mac); + if (len != ETH_ALEN || !memchr_inv(eui48, 0, ETH_ALEN)) { + kfree(eui48); return -EINVAL; } - memcpy(addr, mac, ETH_ALEN); - kfree(mac); + memcpy(addr, eui48, ETH_ALEN); + kfree(eui48); return 0; } -EXPORT_SYMBOL(of_get_mac_address_nvmem); +EXPORT_SYMBOL_GPL(of_get_nvmem_eui48); /** * of_get_mac_address()