Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Documentation/devicetree/bindings/mmc/mmc-card.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ patternProperties:
contains:
const: fixed-partitions

nvmem-layout:
$ref: /schemas/nvmem/layouts/nvmem-layout.yaml

required:
- compatible
- reg
Expand Down Expand Up @@ -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>;
};
};
};
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
34 changes: 34 additions & 0 deletions arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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";
};
};

Expand Down Expand Up @@ -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";
};

Expand Down
9 changes: 9 additions & 0 deletions block/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions block/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
188 changes: 188 additions & 0 deletions block/blk-nvmem.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* block device NVMEM provider
*
* Copyright (c) 2024 Daniel Golle <daniel@makrotopia.org>
*
* Useful on devices using a partition on an eMMC for MAC addresses or
* Wi-Fi calibration EEPROM data.
*/

#include "blk.h"
#include <linux/nvmem-provider.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/pagemap.h>
#include <linux/property.h>

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);
5 changes: 4 additions & 1 deletion drivers/bluetooth/btqca.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
13 changes: 13 additions & 0 deletions drivers/nvmem/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
6 changes: 6 additions & 0 deletions include/linux/nvmem-consumer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Loading