2018-09-21 21:40:20 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2015-07-27 19:13:19 +08:00
|
|
|
/*
|
|
|
|
* nvmem framework core.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
|
|
|
* Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/export.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/idr.h>
|
|
|
|
#include <linux/init.h>
|
2018-09-21 21:40:08 +08:00
|
|
|
#include <linux/kref.h>
|
2015-07-27 19:13:19 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/nvmem-consumer.h>
|
|
|
|
#include <linux/nvmem-provider.h>
|
2020-01-07 17:29:19 +08:00
|
|
|
#include <linux/gpio/consumer.h>
|
2015-07-27 19:13:19 +08:00
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/slab.h>
|
2020-03-25 21:19:51 +08:00
|
|
|
|
2023-12-15 19:15:29 +08:00
|
|
|
#include "internals.h"
|
2020-03-25 21:19:51 +08:00
|
|
|
|
|
|
|
#define to_nvmem_device(d) container_of(d, struct nvmem_device, dev)
|
|
|
|
|
|
|
|
#define FLAG_COMPAT BIT(0)
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry {
|
2015-07-27 19:13:19 +08:00
|
|
|
const char *name;
|
|
|
|
int offset;
|
2023-04-05 01:21:42 +08:00
|
|
|
size_t raw_len;
|
2015-07-27 19:13:19 +08:00
|
|
|
int bytes;
|
|
|
|
int bit_offset;
|
|
|
|
int nbits;
|
2023-04-05 01:21:24 +08:00
|
|
|
nvmem_cell_post_process_t read_post_process;
|
2023-04-05 01:21:28 +08:00
|
|
|
void *priv;
|
2018-11-06 23:41:41 +08:00
|
|
|
struct device_node *np;
|
2015-07-27 19:13:19 +08:00
|
|
|
struct nvmem_device *nvmem;
|
|
|
|
struct list_head node;
|
|
|
|
};
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell {
|
|
|
|
struct nvmem_cell_entry *entry;
|
|
|
|
const char *id;
|
2023-02-06 21:43:46 +08:00
|
|
|
int index;
|
2021-10-13 21:19:55 +08:00
|
|
|
};
|
|
|
|
|
2015-07-27 19:13:19 +08:00
|
|
|
static DEFINE_MUTEX(nvmem_mutex);
|
|
|
|
static DEFINE_IDA(nvmem_ida);
|
|
|
|
|
2018-09-21 21:40:15 +08:00
|
|
|
static DEFINE_MUTEX(nvmem_cell_mutex);
|
|
|
|
static LIST_HEAD(nvmem_cell_tables);
|
|
|
|
|
2018-09-21 21:40:17 +08:00
|
|
|
static DEFINE_MUTEX(nvmem_lookup_mutex);
|
|
|
|
static LIST_HEAD(nvmem_lookup_list);
|
|
|
|
|
2018-09-21 21:40:19 +08:00
|
|
|
static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
|
|
|
|
|
2020-11-27 18:28:34 +08:00
|
|
|
static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
|
void *val, size_t bytes)
|
2020-05-11 22:50:41 +08:00
|
|
|
{
|
|
|
|
if (nvmem->reg_read)
|
|
|
|
return nvmem->reg_read(nvmem->priv, offset, val, bytes);
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2020-11-27 18:28:34 +08:00
|
|
|
static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
|
void *val, size_t bytes)
|
2020-05-11 22:50:41 +08:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (nvmem->reg_write) {
|
|
|
|
gpiod_set_value_cansleep(nvmem->wp_gpio, 0);
|
|
|
|
ret = nvmem->reg_write(nvmem->priv, offset, val, bytes);
|
|
|
|
gpiod_set_value_cansleep(nvmem->wp_gpio, 1);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2020-11-27 18:28:34 +08:00
|
|
|
static int nvmem_access_with_keepouts(struct nvmem_device *nvmem,
|
|
|
|
unsigned int offset, void *val,
|
|
|
|
size_t bytes, int write)
|
|
|
|
{
|
|
|
|
|
|
|
|
unsigned int end = offset + bytes;
|
|
|
|
unsigned int kend, ksize;
|
|
|
|
const struct nvmem_keepout *keepout = nvmem->keepout;
|
|
|
|
const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Skip all keepouts before the range being accessed.
|
|
|
|
* Keepouts are sorted.
|
|
|
|
*/
|
|
|
|
while ((keepout < keepoutend) && (keepout->end <= offset))
|
|
|
|
keepout++;
|
|
|
|
|
|
|
|
while ((offset < end) && (keepout < keepoutend)) {
|
|
|
|
/* Access the valid portion before the keepout. */
|
|
|
|
if (offset < keepout->start) {
|
|
|
|
kend = min(end, keepout->start);
|
|
|
|
ksize = kend - offset;
|
|
|
|
if (write)
|
|
|
|
rc = __nvmem_reg_write(nvmem, offset, val, ksize);
|
|
|
|
else
|
|
|
|
rc = __nvmem_reg_read(nvmem, offset, val, ksize);
|
|
|
|
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
offset += ksize;
|
|
|
|
val += ksize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now we're aligned to the start of this keepout zone. Go
|
|
|
|
* through it.
|
|
|
|
*/
|
|
|
|
kend = min(end, keepout->end);
|
|
|
|
ksize = kend - offset;
|
|
|
|
if (!write)
|
|
|
|
memset(val, keepout->value, ksize);
|
|
|
|
|
|
|
|
val += ksize;
|
|
|
|
offset += ksize;
|
|
|
|
keepout++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we ran out of keepouts but there's still stuff to do, send it
|
|
|
|
* down directly
|
|
|
|
*/
|
|
|
|
if (offset < end) {
|
|
|
|
ksize = end - offset;
|
|
|
|
if (write)
|
|
|
|
return __nvmem_reg_write(nvmem, offset, val, ksize);
|
|
|
|
else
|
|
|
|
return __nvmem_reg_read(nvmem, offset, val, ksize);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
|
void *val, size_t bytes)
|
|
|
|
{
|
|
|
|
if (!nvmem->nkeepout)
|
|
|
|
return __nvmem_reg_read(nvmem, offset, val, bytes);
|
|
|
|
|
|
|
|
return nvmem_access_with_keepouts(nvmem, offset, val, bytes, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
|
|
|
|
void *val, size_t bytes)
|
|
|
|
{
|
|
|
|
if (!nvmem->nkeepout)
|
|
|
|
return __nvmem_reg_write(nvmem, offset, val, bytes);
|
|
|
|
|
|
|
|
return nvmem_access_with_keepouts(nvmem, offset, val, bytes, true);
|
|
|
|
}
|
|
|
|
|
2020-03-25 21:19:51 +08:00
|
|
|
#ifdef CONFIG_NVMEM_SYSFS
|
|
|
|
static const char * const nvmem_type_str[] = {
|
|
|
|
[NVMEM_TYPE_UNKNOWN] = "Unknown",
|
|
|
|
[NVMEM_TYPE_EEPROM] = "EEPROM",
|
|
|
|
[NVMEM_TYPE_OTP] = "OTP",
|
|
|
|
[NVMEM_TYPE_BATTERY_BACKED] = "Battery backed",
|
2021-06-11 17:45:58 +08:00
|
|
|
[NVMEM_TYPE_FRAM] = "FRAM",
|
2020-03-25 21:19:51 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
|
|
static struct lock_class_key eeprom_lock_key;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static ssize_t type_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct nvmem_device *nvmem = to_nvmem_device(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", nvmem_type_str[nvmem->type]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR_RO(type);
|
|
|
|
|
|
|
|
static struct attribute *nvmem_attrs[] = {
|
|
|
|
&dev_attr_type.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf,
|
|
|
|
loff_t pos, size_t count)
|
|
|
|
{
|
|
|
|
struct device *dev;
|
|
|
|
struct nvmem_device *nvmem;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (attr->private)
|
|
|
|
dev = attr->private;
|
|
|
|
else
|
2020-09-17 21:44:35 +08:00
|
|
|
dev = kobj_to_dev(kobj);
|
2020-03-25 21:19:51 +08:00
|
|
|
nvmem = to_nvmem_device(dev);
|
|
|
|
|
|
|
|
/* Stop the user from reading */
|
|
|
|
if (pos >= nvmem->size)
|
|
|
|
return 0;
|
|
|
|
|
2020-07-22 18:06:54 +08:00
|
|
|
if (!IS_ALIGNED(pos, nvmem->stride))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2020-03-25 21:19:51 +08:00
|
|
|
if (count < nvmem->word_size)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (pos + count > nvmem->size)
|
|
|
|
count = nvmem->size - pos;
|
|
|
|
|
|
|
|
count = round_down(count, nvmem->word_size);
|
|
|
|
|
|
|
|
if (!nvmem->reg_read)
|
|
|
|
return -EPERM;
|
|
|
|
|
2020-05-11 22:50:41 +08:00
|
|
|
rc = nvmem_reg_read(nvmem, pos, buf, count);
|
2020-03-25 21:19:51 +08:00
|
|
|
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf,
|
|
|
|
loff_t pos, size_t count)
|
|
|
|
{
|
|
|
|
struct device *dev;
|
|
|
|
struct nvmem_device *nvmem;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (attr->private)
|
|
|
|
dev = attr->private;
|
|
|
|
else
|
2020-09-17 21:44:35 +08:00
|
|
|
dev = kobj_to_dev(kobj);
|
2020-03-25 21:19:51 +08:00
|
|
|
nvmem = to_nvmem_device(dev);
|
|
|
|
|
|
|
|
/* Stop the user from writing */
|
|
|
|
if (pos >= nvmem->size)
|
|
|
|
return -EFBIG;
|
|
|
|
|
2020-07-22 18:06:54 +08:00
|
|
|
if (!IS_ALIGNED(pos, nvmem->stride))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2020-03-25 21:19:51 +08:00
|
|
|
if (count < nvmem->word_size)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (pos + count > nvmem->size)
|
|
|
|
count = nvmem->size - pos;
|
|
|
|
|
|
|
|
count = round_down(count, nvmem->word_size);
|
|
|
|
|
|
|
|
if (!nvmem->reg_write)
|
|
|
|
return -EPERM;
|
|
|
|
|
2020-05-11 22:50:41 +08:00
|
|
|
rc = nvmem_reg_write(nvmem, pos, buf, count);
|
2020-03-25 21:19:51 +08:00
|
|
|
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2020-04-17 20:13:06 +08:00
|
|
|
static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem)
|
2020-03-25 21:19:51 +08:00
|
|
|
{
|
|
|
|
umode_t mode = 0400;
|
|
|
|
|
|
|
|
if (!nvmem->root_only)
|
|
|
|
mode |= 0044;
|
|
|
|
|
|
|
|
if (!nvmem->read_only)
|
|
|
|
mode |= 0200;
|
|
|
|
|
|
|
|
if (!nvmem->reg_write)
|
|
|
|
mode &= ~0200;
|
|
|
|
|
|
|
|
if (!nvmem->reg_read)
|
|
|
|
mode &= ~0444;
|
|
|
|
|
|
|
|
return mode;
|
|
|
|
}
|
|
|
|
|
2020-04-17 20:13:06 +08:00
|
|
|
static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, int i)
|
|
|
|
{
|
2020-09-17 21:44:35 +08:00
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
2020-04-17 20:13:06 +08:00
|
|
|
struct nvmem_device *nvmem = to_nvmem_device(dev);
|
|
|
|
|
2021-11-30 21:39:09 +08:00
|
|
|
attr->size = nvmem->size;
|
|
|
|
|
2020-04-17 20:13:06 +08:00
|
|
|
return nvmem_bin_attr_get_umode(nvmem);
|
|
|
|
}
|
|
|
|
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,
|
|
|
|
const char *id, int index);
|
|
|
|
|
|
|
|
static ssize_t nvmem_cell_attr_read(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf,
|
|
|
|
loff_t pos, size_t count)
|
|
|
|
{
|
|
|
|
struct nvmem_cell_entry *entry;
|
|
|
|
struct nvmem_cell *cell = NULL;
|
|
|
|
size_t cell_sz, read_len;
|
|
|
|
void *content;
|
|
|
|
|
|
|
|
entry = attr->private;
|
|
|
|
cell = nvmem_create_cell(entry, entry->name, 0);
|
|
|
|
if (IS_ERR(cell))
|
|
|
|
return PTR_ERR(cell);
|
|
|
|
|
|
|
|
if (!cell)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
content = nvmem_cell_read(cell, &cell_sz);
|
|
|
|
if (IS_ERR(content)) {
|
|
|
|
read_len = PTR_ERR(content);
|
|
|
|
goto destroy_cell;
|
|
|
|
}
|
|
|
|
|
|
|
|
read_len = min_t(unsigned int, cell_sz - pos, count);
|
|
|
|
memcpy(buf, content + pos, read_len);
|
|
|
|
kfree(content);
|
|
|
|
|
|
|
|
destroy_cell:
|
|
|
|
kfree_const(cell->id);
|
|
|
|
kfree(cell);
|
|
|
|
|
|
|
|
return read_len;
|
|
|
|
}
|
|
|
|
|
2020-03-25 21:19:51 +08:00
|
|
|
/* default read/write permissions */
|
|
|
|
static struct bin_attribute bin_attr_rw_nvmem = {
|
|
|
|
.attr = {
|
|
|
|
.name = "nvmem",
|
|
|
|
.mode = 0644,
|
|
|
|
},
|
|
|
|
.read = bin_attr_nvmem_read,
|
|
|
|
.write = bin_attr_nvmem_write,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct bin_attribute *nvmem_bin_attributes[] = {
|
|
|
|
&bin_attr_rw_nvmem,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group nvmem_bin_group = {
|
|
|
|
.bin_attrs = nvmem_bin_attributes,
|
|
|
|
.attrs = nvmem_attrs,
|
|
|
|
.is_bin_visible = nvmem_bin_attr_is_visible,
|
|
|
|
};
|
|
|
|
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
/* Cell attributes will be dynamically allocated */
|
|
|
|
static struct attribute_group nvmem_cells_group = {
|
|
|
|
.name = "cells",
|
|
|
|
};
|
|
|
|
|
2020-03-25 21:19:51 +08:00
|
|
|
static const struct attribute_group *nvmem_dev_groups[] = {
|
|
|
|
&nvmem_bin_group,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
static const struct attribute_group *nvmem_cells_groups[] = {
|
|
|
|
&nvmem_cells_group,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2020-04-17 20:13:06 +08:00
|
|
|
static struct bin_attribute bin_attr_nvmem_eeprom_compat = {
|
2020-03-25 21:19:51 +08:00
|
|
|
.attr = {
|
2020-04-17 20:13:06 +08:00
|
|
|
.name = "eeprom",
|
2020-03-25 21:19:51 +08:00
|
|
|
},
|
|
|
|
.read = bin_attr_nvmem_read,
|
|
|
|
.write = bin_attr_nvmem_write,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* nvmem_setup_compat() - Create an additional binary entry in
|
|
|
|
* drivers sys directory, to be backwards compatible with the older
|
|
|
|
* drivers/misc/eeprom drivers.
|
|
|
|
*/
|
|
|
|
static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem,
|
|
|
|
const struct nvmem_config *config)
|
|
|
|
{
|
|
|
|
int rval;
|
|
|
|
|
|
|
|
if (!config->compat)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!config->base_dev)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2021-06-11 17:45:58 +08:00
|
|
|
if (config->type == NVMEM_TYPE_FRAM)
|
|
|
|
bin_attr_nvmem_eeprom_compat.attr.name = "fram";
|
|
|
|
|
2020-04-17 20:13:06 +08:00
|
|
|
nvmem->eeprom = bin_attr_nvmem_eeprom_compat;
|
|
|
|
nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem);
|
2020-03-25 21:19:51 +08:00
|
|
|
nvmem->eeprom.size = nvmem->size;
|
|
|
|
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
|
|
nvmem->eeprom.attr.key = &eeprom_lock_key;
|
|
|
|
#endif
|
|
|
|
nvmem->eeprom.private = &nvmem->dev;
|
|
|
|
nvmem->base_dev = config->base_dev;
|
|
|
|
|
|
|
|
rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom);
|
|
|
|
if (rval) {
|
|
|
|
dev_err(&nvmem->dev,
|
|
|
|
"Failed to create eeprom binary file %d\n", rval);
|
|
|
|
return rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
nvmem->flags |= FLAG_COMPAT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem,
|
|
|
|
const struct nvmem_config *config)
|
|
|
|
{
|
|
|
|
if (config->compat)
|
|
|
|
device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
|
|
|
|
}
|
|
|
|
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
struct bin_attribute **cells_attrs, *attrs;
|
|
|
|
struct nvmem_cell_entry *entry;
|
|
|
|
unsigned int ncells = 0, i = 0;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&nvmem_mutex);
|
|
|
|
|
|
|
|
if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated) {
|
|
|
|
nvmem_cells_group.bin_attrs = NULL;
|
|
|
|
goto unlock_mutex;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate an array of attributes with a sentinel */
|
|
|
|
ncells = list_count_nodes(&nvmem->cells);
|
|
|
|
cells_attrs = devm_kcalloc(&nvmem->dev, ncells + 1,
|
|
|
|
sizeof(struct bin_attribute *), GFP_KERNEL);
|
|
|
|
if (!cells_attrs) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto unlock_mutex;
|
|
|
|
}
|
|
|
|
|
|
|
|
attrs = devm_kcalloc(&nvmem->dev, ncells, sizeof(struct bin_attribute), GFP_KERNEL);
|
|
|
|
if (!attrs) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto unlock_mutex;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize each attribute to take the name and size of the cell */
|
|
|
|
list_for_each_entry(entry, &nvmem->cells, node) {
|
|
|
|
sysfs_bin_attr_init(&attrs[i]);
|
|
|
|
attrs[i].attr.name = devm_kasprintf(&nvmem->dev, GFP_KERNEL,
|
2024-02-10 00:34:54 +08:00
|
|
|
"%s@%x,%x", entry->name,
|
|
|
|
entry->offset,
|
|
|
|
entry->bit_offset);
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
attrs[i].attr.mode = 0444;
|
|
|
|
attrs[i].size = entry->bytes;
|
|
|
|
attrs[i].read = &nvmem_cell_attr_read;
|
|
|
|
attrs[i].private = entry;
|
|
|
|
if (!attrs[i].attr.name) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto unlock_mutex;
|
|
|
|
}
|
|
|
|
|
|
|
|
cells_attrs[i] = &attrs[i];
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
nvmem_cells_group.bin_attrs = cells_attrs;
|
|
|
|
|
|
|
|
ret = devm_device_add_groups(&nvmem->dev, nvmem_cells_groups);
|
|
|
|
if (ret)
|
|
|
|
goto unlock_mutex;
|
|
|
|
|
|
|
|
nvmem->sysfs_cells_populated = true;
|
|
|
|
|
|
|
|
unlock_mutex:
|
|
|
|
mutex_unlock(&nvmem_mutex);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-03-25 21:19:51 +08:00
|
|
|
#else /* CONFIG_NVMEM_SYSFS */
|
|
|
|
|
|
|
|
static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem,
|
|
|
|
const struct nvmem_config *config)
|
|
|
|
{
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem,
|
|
|
|
const struct nvmem_config *config)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_NVMEM_SYSFS */
|
2016-02-27 03:59:19 +08:00
|
|
|
|
2015-07-27 19:13:19 +08:00
|
|
|
static void nvmem_release(struct device *dev)
|
|
|
|
{
|
|
|
|
struct nvmem_device *nvmem = to_nvmem_device(dev);
|
|
|
|
|
2020-09-17 21:44:36 +08:00
|
|
|
ida_free(&nvmem_ida, nvmem->id);
|
2020-03-10 21:22:50 +08:00
|
|
|
gpiod_put(nvmem->wp_gpio);
|
2015-07-27 19:13:19 +08:00
|
|
|
kfree(nvmem);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct device_type nvmem_provider_type = {
|
|
|
|
.release = nvmem_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct bus_type nvmem_bus_type = {
|
|
|
|
.name = "nvmem",
|
|
|
|
};
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
static void nvmem_cell_entry_drop(struct nvmem_cell_entry *cell)
|
2015-07-27 19:13:19 +08:00
|
|
|
{
|
2018-09-21 21:40:19 +08:00
|
|
|
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell);
|
2018-09-21 21:40:14 +08:00
|
|
|
mutex_lock(&nvmem_mutex);
|
2015-07-27 19:13:19 +08:00
|
|
|
list_del(&cell->node);
|
2018-09-21 21:40:14 +08:00
|
|
|
mutex_unlock(&nvmem_mutex);
|
2018-11-06 23:41:41 +08:00
|
|
|
of_node_put(cell->np);
|
2020-01-09 18:40:17 +08:00
|
|
|
kfree_const(cell->name);
|
2015-07-27 19:13:19 +08:00
|
|
|
kfree(cell);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem)
|
|
|
|
{
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry *cell, *p;
|
2015-07-27 19:13:19 +08:00
|
|
|
|
2018-09-21 21:40:14 +08:00
|
|
|
list_for_each_entry_safe(cell, p, &nvmem->cells, node)
|
2021-10-13 21:19:55 +08:00
|
|
|
nvmem_cell_entry_drop(cell);
|
2015-07-27 19:13:19 +08:00
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell)
|
2015-07-27 19:13:19 +08:00
|
|
|
{
|
2018-09-21 21:40:14 +08:00
|
|
|
mutex_lock(&nvmem_mutex);
|
|
|
|
list_add_tail(&cell->node, &cell->nvmem->cells);
|
|
|
|
mutex_unlock(&nvmem_mutex);
|
2018-09-21 21:40:19 +08:00
|
|
|
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_ADD, cell);
|
2015-07-27 19:13:19 +08:00
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
|
|
|
|
const struct nvmem_cell_info *info,
|
|
|
|
struct nvmem_cell_entry *cell)
|
2015-07-27 19:13:19 +08:00
|
|
|
{
|
|
|
|
cell->nvmem = nvmem;
|
|
|
|
cell->offset = info->offset;
|
2023-04-05 01:21:42 +08:00
|
|
|
cell->raw_len = info->raw_len ?: info->bytes;
|
2015-07-27 19:13:19 +08:00
|
|
|
cell->bytes = info->bytes;
|
2020-09-24 04:44:56 +08:00
|
|
|
cell->name = info->name;
|
2023-04-05 01:21:24 +08:00
|
|
|
cell->read_post_process = info->read_post_process;
|
2023-04-05 01:21:28 +08:00
|
|
|
cell->priv = info->priv;
|
2015-07-27 19:13:19 +08:00
|
|
|
|
|
|
|
cell->bit_offset = info->bit_offset;
|
|
|
|
cell->nbits = info->nbits;
|
2022-04-30 00:26:46 +08:00
|
|
|
cell->np = info->np;
|
2015-07-27 19:13:19 +08:00
|
|
|
|
|
|
|
if (cell->nbits)
|
|
|
|
cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
|
|
|
|
BITS_PER_BYTE);
|
|
|
|
|
|
|
|
if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
|
|
|
|
dev_err(&nvmem->dev,
|
|
|
|
"cell %s unaligned to nvmem stride %d\n",
|
2020-09-24 04:44:56 +08:00
|
|
|
cell->name ?: "<unknown>", nvmem->stride);
|
2015-07-27 19:13:19 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
static int nvmem_cell_info_to_nvmem_cell_entry(struct nvmem_device *nvmem,
|
|
|
|
const struct nvmem_cell_info *info,
|
|
|
|
struct nvmem_cell_entry *cell)
|
2020-09-24 04:44:56 +08:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
err = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, cell);
|
2020-09-24 04:44:56 +08:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
cell->name = kstrdup_const(info->name, GFP_KERNEL);
|
|
|
|
if (!cell->name)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-02-06 21:43:49 +08:00
|
|
|
/**
|
|
|
|
* nvmem_add_one_cell() - Add one cell information to an nvmem device
|
|
|
|
*
|
|
|
|
* @nvmem: nvmem device to add cells to.
|
|
|
|
* @info: nvmem cell info to add to the device
|
|
|
|
*
|
|
|
|
* Return: 0 or negative error code on failure.
|
|
|
|
*/
|
|
|
|
int nvmem_add_one_cell(struct nvmem_device *nvmem,
|
|
|
|
const struct nvmem_cell_info *info)
|
|
|
|
{
|
|
|
|
struct nvmem_cell_entry *cell;
|
|
|
|
int rval;
|
|
|
|
|
|
|
|
cell = kzalloc(sizeof(*cell), GFP_KERNEL);
|
|
|
|
if (!cell)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, info, cell);
|
|
|
|
if (rval) {
|
|
|
|
kfree(cell);
|
|
|
|
return rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
nvmem_cell_entry_add(cell);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_add_one_cell);
|
|
|
|
|
2018-05-11 19:06:56 +08:00
|
|
|
/**
|
|
|
|
* nvmem_add_cells() - Add cell information to an nvmem device
|
|
|
|
*
|
|
|
|
* @nvmem: nvmem device to add cells to.
|
|
|
|
* @info: nvmem cell info to add to the device
|
|
|
|
* @ncells: number of cells in info
|
|
|
|
*
|
|
|
|
* Return: 0 or negative error code on failure.
|
|
|
|
*/
|
2018-09-21 21:40:26 +08:00
|
|
|
static int nvmem_add_cells(struct nvmem_device *nvmem,
|
2018-05-11 19:06:56 +08:00
|
|
|
const struct nvmem_cell_info *info,
|
|
|
|
int ncells)
|
2015-07-27 19:13:19 +08:00
|
|
|
{
|
2023-02-06 21:43:49 +08:00
|
|
|
int i, rval;
|
2015-07-27 19:13:19 +08:00
|
|
|
|
2018-05-11 19:06:56 +08:00
|
|
|
for (i = 0; i < ncells; i++) {
|
2023-02-06 21:43:49 +08:00
|
|
|
rval = nvmem_add_one_cell(nvmem, &info[i]);
|
|
|
|
if (rval)
|
|
|
|
return rval;
|
2015-07-27 19:13:19 +08:00
|
|
|
}
|
|
|
|
|
2023-02-06 21:43:49 +08:00
|
|
|
return 0;
|
2015-07-27 19:13:19 +08:00
|
|
|
}
|
|
|
|
|
2018-09-21 21:40:19 +08:00
|
|
|
/**
|
|
|
|
* nvmem_register_notifier() - Register a notifier block for nvmem events.
|
|
|
|
*
|
|
|
|
* @nb: notifier block to be called on nvmem events.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, negative error number on failure.
|
|
|
|
*/
|
|
|
|
int nvmem_register_notifier(struct notifier_block *nb)
|
|
|
|
{
|
|
|
|
return blocking_notifier_chain_register(&nvmem_notifier, nb);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_register_notifier);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_unregister_notifier() - Unregister a notifier block for nvmem events.
|
|
|
|
*
|
|
|
|
* @nb: notifier block to be unregistered.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, negative error number on failure.
|
|
|
|
*/
|
|
|
|
int nvmem_unregister_notifier(struct notifier_block *nb)
|
|
|
|
{
|
|
|
|
return blocking_notifier_chain_unregister(&nvmem_notifier, nb);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_unregister_notifier);
|
|
|
|
|
2018-09-21 21:40:15 +08:00
|
|
|
static int nvmem_add_cells_from_table(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
const struct nvmem_cell_info *info;
|
|
|
|
struct nvmem_cell_table *table;
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry *cell;
|
2018-09-21 21:40:15 +08:00
|
|
|
int rval = 0, i;
|
|
|
|
|
|
|
|
mutex_lock(&nvmem_cell_mutex);
|
|
|
|
list_for_each_entry(table, &nvmem_cell_tables, node) {
|
|
|
|
if (strcmp(nvmem_dev_name(nvmem), table->nvmem_name) == 0) {
|
|
|
|
for (i = 0; i < table->ncells; i++) {
|
|
|
|
info = &table->cells[i];
|
|
|
|
|
|
|
|
cell = kzalloc(sizeof(*cell), GFP_KERNEL);
|
|
|
|
if (!cell) {
|
|
|
|
rval = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, info, cell);
|
2018-09-21 21:40:15 +08:00
|
|
|
if (rval) {
|
|
|
|
kfree(cell);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
nvmem_cell_entry_add(cell);
|
2018-09-21 21:40:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
mutex_unlock(&nvmem_cell_mutex);
|
|
|
|
return rval;
|
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
static struct nvmem_cell_entry *
|
|
|
|
nvmem_find_cell_entry_by_name(struct nvmem_device *nvmem, const char *cell_id)
|
2018-09-21 21:40:17 +08:00
|
|
|
{
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry *iter, *cell = NULL;
|
2018-09-21 21:40:17 +08:00
|
|
|
|
|
|
|
mutex_lock(&nvmem_mutex);
|
2019-01-28 23:55:02 +08:00
|
|
|
list_for_each_entry(iter, &nvmem->cells, node) {
|
|
|
|
if (strcmp(cell_id, iter->name) == 0) {
|
|
|
|
cell = iter;
|
2018-09-21 21:40:17 +08:00
|
|
|
break;
|
2019-01-28 23:55:02 +08:00
|
|
|
}
|
2018-09-21 21:40:17 +08:00
|
|
|
}
|
|
|
|
mutex_unlock(&nvmem_mutex);
|
|
|
|
|
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
|
2020-11-27 18:28:34 +08:00
|
|
|
static int nvmem_validate_keepouts(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
unsigned int cur = 0;
|
|
|
|
const struct nvmem_keepout *keepout = nvmem->keepout;
|
|
|
|
const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout;
|
|
|
|
|
|
|
|
while (keepout < keepoutend) {
|
|
|
|
/* Ensure keepouts are sorted and don't overlap. */
|
|
|
|
if (keepout->start < cur) {
|
|
|
|
dev_err(&nvmem->dev,
|
|
|
|
"Keepout regions aren't sorted or overlap.\n");
|
|
|
|
|
|
|
|
return -ERANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (keepout->end < keepout->start) {
|
|
|
|
dev_err(&nvmem->dev,
|
|
|
|
"Invalid keepout region.\n");
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate keepouts (and holes between) don't violate
|
|
|
|
* word_size constraints.
|
|
|
|
*/
|
|
|
|
if ((keepout->end - keepout->start < nvmem->word_size) ||
|
|
|
|
((keepout->start != cur) &&
|
|
|
|
(keepout->start - cur < nvmem->word_size))) {
|
|
|
|
|
|
|
|
dev_err(&nvmem->dev,
|
|
|
|
"Keepout regions violate word_size constraints.\n");
|
|
|
|
|
|
|
|
return -ERANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Validate keepouts don't violate stride (alignment). */
|
|
|
|
if (!IS_ALIGNED(keepout->start, nvmem->stride) ||
|
|
|
|
!IS_ALIGNED(keepout->end, nvmem->stride)) {
|
|
|
|
|
|
|
|
dev_err(&nvmem->dev,
|
|
|
|
"Keepout regions violate stride.\n");
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
cur = keepout->end;
|
|
|
|
keepout++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-06-11 22:03:29 +08:00
|
|
|
static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
|
2018-09-21 21:40:16 +08:00
|
|
|
{
|
|
|
|
struct device *dev = &nvmem->dev;
|
2023-02-06 21:43:50 +08:00
|
|
|
struct device_node *child;
|
2018-09-21 21:40:16 +08:00
|
|
|
const __be32 *addr;
|
2023-02-06 21:43:50 +08:00
|
|
|
int len, ret;
|
2018-09-21 21:40:16 +08:00
|
|
|
|
2023-06-11 22:03:29 +08:00
|
|
|
for_each_child_of_node(np, child) {
|
2023-02-06 21:43:50 +08:00
|
|
|
struct nvmem_cell_info info = {0};
|
2018-09-21 21:40:16 +08:00
|
|
|
|
|
|
|
addr = of_get_property(child, "reg", &len);
|
2021-01-30 01:14:30 +08:00
|
|
|
if (!addr)
|
|
|
|
continue;
|
|
|
|
if (len < 2 * sizeof(u32)) {
|
2018-09-21 21:40:16 +08:00
|
|
|
dev_err(dev, "nvmem: invalid reg on %pOF\n", child);
|
2021-06-11 18:23:21 +08:00
|
|
|
of_node_put(child);
|
2018-09-21 21:40:16 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2023-02-06 21:43:50 +08:00
|
|
|
info.offset = be32_to_cpup(addr++);
|
|
|
|
info.bytes = be32_to_cpup(addr);
|
|
|
|
info.name = kasprintf(GFP_KERNEL, "%pOFn", child);
|
2018-09-21 21:40:16 +08:00
|
|
|
|
|
|
|
addr = of_get_property(child, "bits", &len);
|
|
|
|
if (addr && len == (2 * sizeof(u32))) {
|
2023-02-06 21:43:50 +08:00
|
|
|
info.bit_offset = be32_to_cpup(addr++);
|
|
|
|
info.nbits = be32_to_cpup(addr);
|
2024-02-24 19:45:16 +08:00
|
|
|
if (info.bit_offset >= BITS_PER_BYTE || info.nbits < 1) {
|
|
|
|
dev_err(dev, "nvmem: invalid bits on %pOF\n", child);
|
|
|
|
of_node_put(child);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2018-09-21 21:40:16 +08:00
|
|
|
}
|
|
|
|
|
2023-02-06 21:43:50 +08:00
|
|
|
info.np = of_node_get(child);
|
|
|
|
|
2023-12-15 19:15:31 +08:00
|
|
|
if (nvmem->fixup_dt_cell_info)
|
|
|
|
nvmem->fixup_dt_cell_info(nvmem, &info);
|
2023-04-05 01:21:25 +08:00
|
|
|
|
2023-02-06 21:43:50 +08:00
|
|
|
ret = nvmem_add_one_cell(nvmem, &info);
|
|
|
|
kfree(info.name);
|
|
|
|
if (ret) {
|
2021-06-11 18:23:21 +08:00
|
|
|
of_node_put(child);
|
2023-02-06 21:43:50 +08:00
|
|
|
return ret;
|
2018-09-21 21:40:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-06-11 22:03:29 +08:00
|
|
|
static int nvmem_add_cells_from_legacy_of(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
return nvmem_add_cells_from_dt(nvmem, nvmem->dev.of_node);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int nvmem_add_cells_from_fixed_layout(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
struct device_node *layout_np;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
layout_np = of_nvmem_layout_get_container(nvmem);
|
|
|
|
if (!layout_np)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (of_device_is_compatible(layout_np, "fixed-layout"))
|
|
|
|
err = nvmem_add_cells_from_dt(nvmem, layout_np);
|
|
|
|
|
|
|
|
of_node_put(layout_np);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
int nvmem_layout_register(struct nvmem_layout *layout)
|
2023-04-05 01:21:21 +08:00
|
|
|
{
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
int ret;
|
|
|
|
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
if (!layout->add_cells)
|
|
|
|
return -EINVAL;
|
2023-08-23 21:27:44 +08:00
|
|
|
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
/* Populate the cells */
|
2023-12-19 20:01:03 +08:00
|
|
|
ret = layout->add_cells(layout);
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
#ifdef CONFIG_NVMEM_SYSFS
|
|
|
|
ret = nvmem_populate_sysfs_cells(layout->nvmem);
|
|
|
|
if (ret) {
|
|
|
|
nvmem_device_remove_all_cells(layout->nvmem);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
2023-04-05 01:21:21 +08:00
|
|
|
}
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
EXPORT_SYMBOL_GPL(nvmem_layout_register);
|
2023-04-05 01:21:21 +08:00
|
|
|
|
|
|
|
void nvmem_layout_unregister(struct nvmem_layout *layout)
|
|
|
|
{
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
/* Keep the API even with an empty stub in case we need it later */
|
2023-04-05 01:21:21 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
|
|
|
|
|
2015-07-27 19:13:19 +08:00
|
|
|
/**
|
|
|
|
* nvmem_register() - Register a nvmem device for given nvmem_config.
|
2020-07-22 18:06:56 +08:00
|
|
|
* Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
|
2015-07-27 19:13:19 +08:00
|
|
|
*
|
|
|
|
* @config: nvmem device configuration with which nvmem device is created.
|
|
|
|
*
|
|
|
|
* Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device
|
|
|
|
* on success.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct nvmem_device *nvmem_register(const struct nvmem_config *config)
|
|
|
|
{
|
|
|
|
struct nvmem_device *nvmem;
|
|
|
|
int rval;
|
|
|
|
|
|
|
|
if (!config->dev)
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
2020-03-10 21:22:51 +08:00
|
|
|
if (!config->reg_read && !config->reg_write)
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
2015-07-27 19:13:19 +08:00
|
|
|
nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL);
|
|
|
|
if (!nvmem)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
2023-02-06 21:43:41 +08:00
|
|
|
rval = ida_alloc(&nvmem_ida, GFP_KERNEL);
|
2015-07-27 19:13:19 +08:00
|
|
|
if (rval < 0) {
|
|
|
|
kfree(nvmem);
|
|
|
|
return ERR_PTR(rval);
|
|
|
|
}
|
2020-03-10 21:22:48 +08:00
|
|
|
|
2023-01-27 18:40:08 +08:00
|
|
|
nvmem->id = rval;
|
|
|
|
|
2023-01-27 18:40:10 +08:00
|
|
|
nvmem->dev.type = &nvmem_provider_type;
|
|
|
|
nvmem->dev.bus = &nvmem_bus_type;
|
|
|
|
nvmem->dev.parent = config->dev;
|
|
|
|
|
|
|
|
device_initialize(&nvmem->dev);
|
|
|
|
|
2023-01-27 18:40:09 +08:00
|
|
|
if (!config->ignore_wp)
|
2020-01-07 17:29:19 +08:00
|
|
|
nvmem->wp_gpio = gpiod_get_optional(config->dev, "wp",
|
|
|
|
GPIOD_OUT_HIGH);
|
2020-03-10 21:22:49 +08:00
|
|
|
if (IS_ERR(nvmem->wp_gpio)) {
|
|
|
|
rval = PTR_ERR(nvmem->wp_gpio);
|
2023-01-27 18:40:14 +08:00
|
|
|
nvmem->wp_gpio = NULL;
|
2023-01-27 18:40:10 +08:00
|
|
|
goto err_put_device;
|
2020-03-10 21:22:49 +08:00
|
|
|
}
|
2020-01-07 17:29:19 +08:00
|
|
|
|
2018-09-21 21:40:08 +08:00
|
|
|
kref_init(&nvmem->refcnt);
|
2018-09-21 21:40:14 +08:00
|
|
|
INIT_LIST_HEAD(&nvmem->cells);
|
2023-12-15 19:15:31 +08:00
|
|
|
nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info;
|
2018-09-21 21:40:08 +08:00
|
|
|
|
2015-07-27 19:13:19 +08:00
|
|
|
nvmem->owner = config->owner;
|
2017-10-21 00:57:42 +08:00
|
|
|
if (!nvmem->owner && config->dev->driver)
|
|
|
|
nvmem->owner = config->dev->driver->owner;
|
2017-12-15 22:06:05 +08:00
|
|
|
nvmem->stride = config->stride ?: 1;
|
|
|
|
nvmem->word_size = config->word_size ?: 1;
|
2016-04-25 03:28:05 +08:00
|
|
|
nvmem->size = config->size;
|
2020-03-25 20:21:15 +08:00
|
|
|
nvmem->root_only = config->root_only;
|
2016-04-25 03:28:05 +08:00
|
|
|
nvmem->priv = config->priv;
|
2018-11-30 19:53:20 +08:00
|
|
|
nvmem->type = config->type;
|
2016-04-25 03:28:05 +08:00
|
|
|
nvmem->reg_read = config->reg_read;
|
|
|
|
nvmem->reg_write = config->reg_write;
|
2020-11-27 18:28:34 +08:00
|
|
|
nvmem->keepout = config->keepout;
|
|
|
|
nvmem->nkeepout = config->nkeepout;
|
2021-04-24 19:06:04 +08:00
|
|
|
if (config->of_node)
|
|
|
|
nvmem->dev.of_node = config->of_node;
|
2023-10-23 18:27:59 +08:00
|
|
|
else
|
2018-11-30 19:53:25 +08:00
|
|
|
nvmem->dev.of_node = config->dev->of_node;
|
2018-03-09 22:46:56 +08:00
|
|
|
|
2020-07-22 18:06:58 +08:00
|
|
|
switch (config->id) {
|
|
|
|
case NVMEM_DEVID_NONE:
|
2022-09-16 20:20:50 +08:00
|
|
|
rval = dev_set_name(&nvmem->dev, "%s", config->name);
|
2020-07-22 18:06:58 +08:00
|
|
|
break;
|
|
|
|
case NVMEM_DEVID_AUTO:
|
2022-09-16 20:20:50 +08:00
|
|
|
rval = dev_set_name(&nvmem->dev, "%s%d", config->name, nvmem->id);
|
2020-07-22 18:06:58 +08:00
|
|
|
break;
|
|
|
|
default:
|
2022-09-16 20:20:50 +08:00
|
|
|
rval = dev_set_name(&nvmem->dev, "%s%d",
|
2018-03-09 22:46:56 +08:00
|
|
|
config->name ? : "nvmem",
|
|
|
|
config->name ? config->id : nvmem->id);
|
2020-07-22 18:06:58 +08:00
|
|
|
break;
|
2018-03-09 22:46:56 +08:00
|
|
|
}
|
2015-07-27 19:13:19 +08:00
|
|
|
|
2023-01-27 18:40:10 +08:00
|
|
|
if (rval)
|
|
|
|
goto err_put_device;
|
2022-09-16 20:20:50 +08:00
|
|
|
|
2019-01-28 23:55:00 +08:00
|
|
|
nvmem->read_only = device_property_present(config->dev, "read-only") ||
|
|
|
|
config->read_only || !nvmem->reg_write;
|
2015-07-27 19:13:19 +08:00
|
|
|
|
2020-03-25 21:19:51 +08:00
|
|
|
#ifdef CONFIG_NVMEM_SYSFS
|
|
|
|
nvmem->dev.groups = nvmem_dev_groups;
|
|
|
|
#endif
|
2015-07-27 19:13:19 +08:00
|
|
|
|
2022-09-16 20:04:02 +08:00
|
|
|
if (nvmem->nkeepout) {
|
|
|
|
rval = nvmem_validate_keepouts(nvmem);
|
|
|
|
if (rval)
|
2023-01-27 18:40:11 +08:00
|
|
|
goto err_put_device;
|
2022-09-16 20:04:02 +08:00
|
|
|
}
|
|
|
|
|
2016-02-27 03:59:19 +08:00
|
|
|
if (config->compat) {
|
2019-04-16 17:59:24 +08:00
|
|
|
rval = nvmem_sysfs_setup_compat(nvmem, config);
|
2016-02-27 03:59:19 +08:00
|
|
|
if (rval)
|
2023-01-27 18:40:11 +08:00
|
|
|
goto err_put_device;
|
2015-07-27 19:13:19 +08:00
|
|
|
}
|
|
|
|
|
2018-09-21 21:40:07 +08:00
|
|
|
if (config->cells) {
|
|
|
|
rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
|
|
|
|
if (rval)
|
2023-01-27 18:40:13 +08:00
|
|
|
goto err_remove_cells;
|
2018-09-21 21:40:07 +08:00
|
|
|
}
|
2015-07-27 19:13:19 +08:00
|
|
|
|
2018-09-21 21:40:15 +08:00
|
|
|
rval = nvmem_add_cells_from_table(nvmem);
|
|
|
|
if (rval)
|
|
|
|
goto err_remove_cells;
|
|
|
|
|
2023-10-20 18:55:41 +08:00
|
|
|
if (config->add_legacy_fixed_of_cells) {
|
|
|
|
rval = nvmem_add_cells_from_legacy_of(nvmem);
|
|
|
|
if (rval)
|
|
|
|
goto err_remove_cells;
|
|
|
|
}
|
2018-09-21 21:40:16 +08:00
|
|
|
|
2023-08-23 21:27:41 +08:00
|
|
|
rval = nvmem_add_cells_from_fixed_layout(nvmem);
|
2023-01-27 18:40:11 +08:00
|
|
|
if (rval)
|
|
|
|
goto err_remove_cells;
|
|
|
|
|
2023-08-23 21:27:41 +08:00
|
|
|
dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
|
|
|
|
|
|
|
|
rval = device_add(&nvmem->dev);
|
2023-04-05 01:21:21 +08:00
|
|
|
if (rval)
|
|
|
|
goto err_remove_cells;
|
|
|
|
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
rval = nvmem_populate_layout(nvmem);
|
|
|
|
if (rval)
|
|
|
|
goto err_remove_dev;
|
|
|
|
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
#ifdef CONFIG_NVMEM_SYSFS
|
|
|
|
rval = nvmem_populate_sysfs_cells(nvmem);
|
|
|
|
if (rval)
|
|
|
|
goto err_destroy_layout;
|
|
|
|
#endif
|
|
|
|
|
2019-02-15 18:42:59 +08:00
|
|
|
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
|
2018-09-21 21:40:19 +08:00
|
|
|
|
2015-07-27 19:13:19 +08:00
|
|
|
return nvmem;
|
2017-06-09 17:59:07 +08:00
|
|
|
|
nvmem: core: Expose cells through sysfs
The binary content of nvmem devices is available to the user so in the
easiest cases, finding the content of a cell is rather easy as it is
just a matter of looking at a known and fixed offset. However, nvmem
layouts have been recently introduced to cope with more advanced
situations, where the offset and size of the cells is not known in
advance or is dynamic. When using layouts, more advanced parsers are
used by the kernel in order to give direct access to the content of each
cell, regardless of its position/size in the underlying
device. Unfortunately, these information are not accessible by users,
unless by fully re-implementing the parser logic in userland.
Let's expose the cells and their content through sysfs to avoid these
situations. Of course the relevant NVMEM sysfs Kconfig option must be
enabled for this support to be available.
Not all nvmem devices expose cells. Indeed, the .bin_attrs attribute
group member will be filled at runtime only when relevant and will
remain empty otherwise. In this case, as the cells attribute group will
be empty, it will not lead to any additional folder/file creation.
Exposed cells are read-only. There is, in practice, everything in the
core to support a write path, but as I don't see any need for that, I
prefer to keep the interface simple (and probably safer). The interface
is documented as being in the "testing" state which means we can later
add a write attribute if though relevant.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Tested-by: Chen-Yu Tsai <wenst@chromium.org>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-9-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:34 +08:00
|
|
|
#ifdef CONFIG_NVMEM_SYSFS
|
|
|
|
err_destroy_layout:
|
|
|
|
nvmem_destroy_layout(nvmem);
|
|
|
|
#endif
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
err_remove_dev:
|
|
|
|
device_del(&nvmem->dev);
|
2018-09-21 21:40:15 +08:00
|
|
|
err_remove_cells:
|
|
|
|
nvmem_device_remove_all_cells(nvmem);
|
2018-09-21 21:40:07 +08:00
|
|
|
if (config->compat)
|
2019-04-16 17:59:24 +08:00
|
|
|
nvmem_sysfs_remove_compat(nvmem, config);
|
2017-06-09 17:59:07 +08:00
|
|
|
err_put_device:
|
|
|
|
put_device(&nvmem->dev);
|
|
|
|
|
2016-02-27 03:59:19 +08:00
|
|
|
return ERR_PTR(rval);
|
2015-07-27 19:13:19 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_register);
|
|
|
|
|
2018-09-21 21:40:08 +08:00
|
|
|
static void nvmem_device_release(struct kref *kref)
|
|
|
|
{
|
|
|
|
struct nvmem_device *nvmem;
|
|
|
|
|
|
|
|
nvmem = container_of(kref, struct nvmem_device, refcnt);
|
|
|
|
|
2018-09-21 21:40:19 +08:00
|
|
|
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem);
|
|
|
|
|
2018-09-21 21:40:08 +08:00
|
|
|
if (nvmem->flags & FLAG_COMPAT)
|
|
|
|
device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
|
|
|
|
|
|
|
|
nvmem_device_remove_all_cells(nvmem);
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
nvmem_destroy_layout(nvmem);
|
2020-03-25 01:15:58 +08:00
|
|
|
device_unregister(&nvmem->dev);
|
2018-09-21 21:40:08 +08:00
|
|
|
}
|
|
|
|
|
2015-07-27 19:13:19 +08:00
|
|
|
/**
|
|
|
|
* nvmem_unregister() - Unregister previously registered nvmem device
|
|
|
|
*
|
|
|
|
* @nvmem: Pointer to previously registered nvmem device.
|
|
|
|
*/
|
2018-09-21 21:40:13 +08:00
|
|
|
void nvmem_unregister(struct nvmem_device *nvmem)
|
2015-07-27 19:13:19 +08:00
|
|
|
{
|
2022-02-20 23:15:17 +08:00
|
|
|
if (nvmem)
|
|
|
|
kref_put(&nvmem->refcnt, nvmem_device_release);
|
2015-07-27 19:13:19 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_unregister);
|
|
|
|
|
2022-02-20 23:15:16 +08:00
|
|
|
static void devm_nvmem_unregister(void *nvmem)
|
2018-03-09 22:46:57 +08:00
|
|
|
{
|
2022-02-20 23:15:16 +08:00
|
|
|
nvmem_unregister(nvmem);
|
2018-03-09 22:46:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* devm_nvmem_register() - Register a managed nvmem device for given
|
|
|
|
* nvmem_config.
|
2020-07-22 18:06:56 +08:00
|
|
|
* Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
|
2018-03-09 22:46:57 +08:00
|
|
|
*
|
2018-05-11 19:07:02 +08:00
|
|
|
* @dev: Device that uses the nvmem device.
|
2018-03-09 22:46:57 +08:00
|
|
|
* @config: nvmem device configuration with which nvmem device is created.
|
|
|
|
*
|
|
|
|
* Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device
|
|
|
|
* on success.
|
|
|
|
*/
|
|
|
|
struct nvmem_device *devm_nvmem_register(struct device *dev,
|
|
|
|
const struct nvmem_config *config)
|
|
|
|
{
|
2022-02-20 23:15:16 +08:00
|
|
|
struct nvmem_device *nvmem;
|
|
|
|
int ret;
|
2018-03-09 22:46:57 +08:00
|
|
|
|
|
|
|
nvmem = nvmem_register(config);
|
2022-02-20 23:15:16 +08:00
|
|
|
if (IS_ERR(nvmem))
|
|
|
|
return nvmem;
|
2018-03-09 22:46:57 +08:00
|
|
|
|
2022-02-20 23:15:16 +08:00
|
|
|
ret = devm_add_action_or_reset(dev, devm_nvmem_unregister, nvmem);
|
|
|
|
if (ret)
|
|
|
|
return ERR_PTR(ret);
|
2018-03-09 22:46:57 +08:00
|
|
|
|
|
|
|
return nvmem;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(devm_nvmem_register);
|
|
|
|
|
2019-10-03 17:52:29 +08:00
|
|
|
static struct nvmem_device *__nvmem_device_get(void *data,
|
|
|
|
int (*match)(struct device *dev, const void *data))
|
2015-07-27 19:13:34 +08:00
|
|
|
{
|
|
|
|
struct nvmem_device *nvmem = NULL;
|
2019-10-03 17:52:29 +08:00
|
|
|
struct device *dev;
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2018-09-21 21:40:14 +08:00
|
|
|
mutex_lock(&nvmem_mutex);
|
2019-10-03 17:52:29 +08:00
|
|
|
dev = bus_find_device(&nvmem_bus_type, NULL, data, match);
|
|
|
|
if (dev)
|
|
|
|
nvmem = to_nvmem_device(dev);
|
2015-07-27 19:13:34 +08:00
|
|
|
mutex_unlock(&nvmem_mutex);
|
2018-09-21 21:40:14 +08:00
|
|
|
if (!nvmem)
|
|
|
|
return ERR_PTR(-EPROBE_DEFER);
|
2015-07-27 19:13:34 +08:00
|
|
|
|
|
|
|
if (!try_module_get(nvmem->owner)) {
|
|
|
|
dev_err(&nvmem->dev,
|
|
|
|
"could not increase module refcount for cell %s\n",
|
2018-09-21 21:40:04 +08:00
|
|
|
nvmem_dev_name(nvmem));
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2019-01-28 23:55:05 +08:00
|
|
|
put_device(&nvmem->dev);
|
2015-07-27 19:13:34 +08:00
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
}
|
|
|
|
|
2018-09-21 21:40:08 +08:00
|
|
|
kref_get(&nvmem->refcnt);
|
|
|
|
|
2015-07-27 19:13:34 +08:00
|
|
|
return nvmem;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __nvmem_device_put(struct nvmem_device *nvmem)
|
|
|
|
{
|
2019-01-28 23:55:05 +08:00
|
|
|
put_device(&nvmem->dev);
|
2015-07-27 19:13:34 +08:00
|
|
|
module_put(nvmem->owner);
|
2018-09-21 21:40:08 +08:00
|
|
|
kref_put(&nvmem->refcnt, nvmem_device_release);
|
2015-07-27 19:13:34 +08:00
|
|
|
}
|
|
|
|
|
2017-09-11 17:00:14 +08:00
|
|
|
#if IS_ENABLED(CONFIG_OF)
|
2015-07-27 19:13:45 +08:00
|
|
|
/**
|
|
|
|
* of_nvmem_device_get() - Get nvmem device from a given id
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @np: Device tree node that uses the nvmem device.
|
2015-07-27 19:13:45 +08:00
|
|
|
* @id: nvmem name from nvmem-names property.
|
|
|
|
*
|
|
|
|
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
|
|
|
|
* on success.
|
|
|
|
*/
|
|
|
|
struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id)
|
|
|
|
{
|
|
|
|
|
|
|
|
struct device_node *nvmem_np;
|
2020-09-17 21:44:37 +08:00
|
|
|
struct nvmem_device *nvmem;
|
2019-01-28 23:55:03 +08:00
|
|
|
int index = 0;
|
2015-07-27 19:13:45 +08:00
|
|
|
|
2019-01-28 23:55:03 +08:00
|
|
|
if (id)
|
|
|
|
index = of_property_match_string(np, "nvmem-names", id);
|
2015-07-27 19:13:45 +08:00
|
|
|
|
|
|
|
nvmem_np = of_parse_phandle(np, "nvmem", index);
|
|
|
|
if (!nvmem_np)
|
2019-01-28 23:55:03 +08:00
|
|
|
return ERR_PTR(-ENOENT);
|
2015-07-27 19:13:45 +08:00
|
|
|
|
2020-09-17 21:44:37 +08:00
|
|
|
nvmem = __nvmem_device_get(nvmem_np, device_match_of_node);
|
|
|
|
of_node_put(nvmem_np);
|
|
|
|
return nvmem;
|
2015-07-27 19:13:45 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(of_nvmem_device_get);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_device_get() - Get nvmem device from a given id
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @dev: Device that uses the nvmem device.
|
|
|
|
* @dev_name: name of the requested nvmem device.
|
2015-07-27 19:13:45 +08:00
|
|
|
*
|
|
|
|
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
|
|
|
|
* on success.
|
|
|
|
*/
|
|
|
|
struct nvmem_device *nvmem_device_get(struct device *dev, const char *dev_name)
|
|
|
|
{
|
|
|
|
if (dev->of_node) { /* try dt first */
|
|
|
|
struct nvmem_device *nvmem;
|
|
|
|
|
|
|
|
nvmem = of_nvmem_device_get(dev->of_node, dev_name);
|
|
|
|
|
|
|
|
if (!IS_ERR(nvmem) || PTR_ERR(nvmem) == -EPROBE_DEFER)
|
|
|
|
return nvmem;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-10-03 17:52:29 +08:00
|
|
|
return __nvmem_device_get((void *)dev_name, device_match_name);
|
2015-07-27 19:13:45 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_device_get);
|
|
|
|
|
2019-10-03 17:52:29 +08:00
|
|
|
/**
|
|
|
|
* nvmem_device_find() - Find nvmem device with matching function
|
|
|
|
*
|
|
|
|
* @data: Data to pass to match function
|
|
|
|
* @match: Callback function to check device
|
|
|
|
*
|
|
|
|
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
|
|
|
|
* on success.
|
|
|
|
*/
|
|
|
|
struct nvmem_device *nvmem_device_find(void *data,
|
|
|
|
int (*match)(struct device *dev, const void *data))
|
|
|
|
{
|
|
|
|
return __nvmem_device_get(data, match);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_device_find);
|
|
|
|
|
2015-07-27 19:13:45 +08:00
|
|
|
static int devm_nvmem_device_match(struct device *dev, void *res, void *data)
|
|
|
|
{
|
|
|
|
struct nvmem_device **nvmem = res;
|
|
|
|
|
|
|
|
if (WARN_ON(!nvmem || !*nvmem))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return *nvmem == data;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void devm_nvmem_device_release(struct device *dev, void *res)
|
|
|
|
{
|
|
|
|
nvmem_device_put(*(struct nvmem_device **)res);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* devm_nvmem_device_put() - put alredy got nvmem device
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @dev: Device that uses the nvmem device.
|
2015-07-27 19:13:45 +08:00
|
|
|
* @nvmem: pointer to nvmem device allocated by devm_nvmem_cell_get(),
|
|
|
|
* that needs to be released.
|
|
|
|
*/
|
|
|
|
void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = devres_release(dev, devm_nvmem_device_release,
|
|
|
|
devm_nvmem_device_match, nvmem);
|
|
|
|
|
|
|
|
WARN_ON(ret);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(devm_nvmem_device_put);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_device_put() - put alredy got nvmem device
|
|
|
|
*
|
|
|
|
* @nvmem: pointer to nvmem device that needs to be released.
|
|
|
|
*/
|
|
|
|
void nvmem_device_put(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
__nvmem_device_put(nvmem);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_device_put);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* devm_nvmem_device_get() - Get nvmem cell of device form a given id
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @dev: Device that requests the nvmem device.
|
|
|
|
* @id: name id for the requested nvmem device.
|
2015-07-27 19:13:45 +08:00
|
|
|
*
|
|
|
|
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_cell
|
|
|
|
* on success. The nvmem_cell will be freed by the automatically once the
|
|
|
|
* device is freed.
|
|
|
|
*/
|
|
|
|
struct nvmem_device *devm_nvmem_device_get(struct device *dev, const char *id)
|
|
|
|
{
|
|
|
|
struct nvmem_device **ptr, *nvmem;
|
|
|
|
|
|
|
|
ptr = devres_alloc(devm_nvmem_device_release, sizeof(*ptr), GFP_KERNEL);
|
|
|
|
if (!ptr)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
nvmem = nvmem_device_get(dev, id);
|
|
|
|
if (!IS_ERR(nvmem)) {
|
|
|
|
*ptr = nvmem;
|
|
|
|
devres_add(dev, ptr);
|
|
|
|
} else {
|
|
|
|
devres_free(ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return nvmem;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(devm_nvmem_device_get);
|
|
|
|
|
2023-02-06 21:43:46 +08:00
|
|
|
static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,
|
|
|
|
const char *id, int index)
|
2021-10-13 21:19:55 +08:00
|
|
|
{
|
|
|
|
struct nvmem_cell *cell;
|
|
|
|
const char *name = NULL;
|
|
|
|
|
|
|
|
cell = kzalloc(sizeof(*cell), GFP_KERNEL);
|
|
|
|
if (!cell)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
if (id) {
|
|
|
|
name = kstrdup_const(id, GFP_KERNEL);
|
|
|
|
if (!name) {
|
|
|
|
kfree(cell);
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cell->id = name;
|
|
|
|
cell->entry = entry;
|
2023-02-06 21:43:46 +08:00
|
|
|
cell->index = index;
|
2021-10-13 21:19:55 +08:00
|
|
|
|
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
|
2018-09-21 21:40:17 +08:00
|
|
|
static struct nvmem_cell *
|
|
|
|
nvmem_cell_get_from_lookup(struct device *dev, const char *con_id)
|
2015-07-27 19:13:34 +08:00
|
|
|
{
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry *cell_entry;
|
2018-09-21 21:40:17 +08:00
|
|
|
struct nvmem_cell *cell = ERR_PTR(-ENOENT);
|
|
|
|
struct nvmem_cell_lookup *lookup;
|
2015-07-27 19:13:34 +08:00
|
|
|
struct nvmem_device *nvmem;
|
2018-09-21 21:40:17 +08:00
|
|
|
const char *dev_id;
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2018-09-21 21:40:17 +08:00
|
|
|
if (!dev)
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
|
|
|
dev_id = dev_name(dev);
|
|
|
|
|
|
|
|
mutex_lock(&nvmem_lookup_mutex);
|
|
|
|
|
|
|
|
list_for_each_entry(lookup, &nvmem_lookup_list, node) {
|
|
|
|
if ((strcmp(lookup->dev_id, dev_id) == 0) &&
|
|
|
|
(strcmp(lookup->con_id, con_id) == 0)) {
|
|
|
|
/* This is the right entry. */
|
2019-10-03 17:52:29 +08:00
|
|
|
nvmem = __nvmem_device_get((void *)lookup->nvmem_name,
|
|
|
|
device_match_name);
|
2018-10-03 15:31:11 +08:00
|
|
|
if (IS_ERR(nvmem)) {
|
2018-09-21 21:40:17 +08:00
|
|
|
/* Provider may not be registered yet. */
|
2018-10-03 15:31:11 +08:00
|
|
|
cell = ERR_CAST(nvmem);
|
2019-01-28 23:55:06 +08:00
|
|
|
break;
|
2018-09-21 21:40:17 +08:00
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
cell_entry = nvmem_find_cell_entry_by_name(nvmem,
|
|
|
|
lookup->cell_name);
|
|
|
|
if (!cell_entry) {
|
2018-09-21 21:40:17 +08:00
|
|
|
__nvmem_device_put(nvmem);
|
2018-10-03 15:31:11 +08:00
|
|
|
cell = ERR_PTR(-ENOENT);
|
2021-10-13 21:19:55 +08:00
|
|
|
} else {
|
2023-02-06 21:43:46 +08:00
|
|
|
cell = nvmem_create_cell(cell_entry, con_id, 0);
|
2021-10-13 21:19:55 +08:00
|
|
|
if (IS_ERR(cell))
|
|
|
|
__nvmem_device_put(nvmem);
|
2018-09-21 21:40:17 +08:00
|
|
|
}
|
2019-01-28 23:55:06 +08:00
|
|
|
break;
|
2018-09-21 21:40:17 +08:00
|
|
|
}
|
|
|
|
}
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2018-09-21 21:40:17 +08:00
|
|
|
mutex_unlock(&nvmem_lookup_mutex);
|
2015-07-27 19:13:34 +08:00
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
static void nvmem_layout_module_put(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
if (nvmem->layout && nvmem->layout->dev.driver)
|
|
|
|
module_put(nvmem->layout->dev.driver->owner);
|
|
|
|
}
|
|
|
|
|
2017-09-11 17:00:14 +08:00
|
|
|
#if IS_ENABLED(CONFIG_OF)
|
2021-10-13 21:19:55 +08:00
|
|
|
static struct nvmem_cell_entry *
|
|
|
|
nvmem_find_cell_entry_by_node(struct nvmem_device *nvmem, struct device_node *np)
|
2018-10-03 05:11:12 +08:00
|
|
|
{
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry *iter, *cell = NULL;
|
2018-10-03 05:11:12 +08:00
|
|
|
|
|
|
|
mutex_lock(&nvmem_mutex);
|
2019-01-28 23:55:02 +08:00
|
|
|
list_for_each_entry(iter, &nvmem->cells, node) {
|
|
|
|
if (np == iter->np) {
|
|
|
|
cell = iter;
|
2018-10-03 05:11:12 +08:00
|
|
|
break;
|
2019-01-28 23:55:02 +08:00
|
|
|
}
|
2018-10-03 05:11:12 +08:00
|
|
|
}
|
|
|
|
mutex_unlock(&nvmem_mutex);
|
|
|
|
|
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
static int nvmem_layout_module_get_optional(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
if (!nvmem->layout)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!nvmem->layout->dev.driver ||
|
|
|
|
!try_module_get(nvmem->layout->dev.driver->owner))
|
|
|
|
return -EPROBE_DEFER;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-27 19:13:34 +08:00
|
|
|
/**
|
|
|
|
* of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @np: Device tree node that uses the nvmem cell.
|
2018-09-21 21:40:21 +08:00
|
|
|
* @id: nvmem cell name from nvmem-cell-names property, or NULL
|
|
|
|
* for the cell at index 0 (the lone cell with no accompanying
|
|
|
|
* nvmem-cell-names property).
|
2015-07-27 19:13:34 +08:00
|
|
|
*
|
|
|
|
* Return: Will be an ERR_PTR() on error or a valid pointer
|
|
|
|
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
|
|
|
* nvmem_cell_put().
|
|
|
|
*/
|
2018-09-21 21:40:21 +08:00
|
|
|
struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
|
2015-07-27 19:13:34 +08:00
|
|
|
{
|
|
|
|
struct device_node *cell_np, *nvmem_np;
|
|
|
|
struct nvmem_device *nvmem;
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry *cell_entry;
|
2018-09-21 21:40:16 +08:00
|
|
|
struct nvmem_cell *cell;
|
2023-02-06 21:43:46 +08:00
|
|
|
struct of_phandle_args cell_spec;
|
2017-01-23 07:02:40 +08:00
|
|
|
int index = 0;
|
2023-02-06 21:43:46 +08:00
|
|
|
int cell_index = 0;
|
|
|
|
int ret;
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2017-01-23 07:02:40 +08:00
|
|
|
/* if cell name exists, find index to the name */
|
2018-09-21 21:40:21 +08:00
|
|
|
if (id)
|
|
|
|
index = of_property_match_string(np, "nvmem-cell-names", id);
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2023-02-06 21:43:46 +08:00
|
|
|
ret = of_parse_phandle_with_optional_args(np, "nvmem-cells",
|
|
|
|
"#nvmem-cell-cells",
|
|
|
|
index, &cell_spec);
|
|
|
|
if (ret)
|
2023-03-10 17:48:45 +08:00
|
|
|
return ERR_PTR(-ENOENT);
|
2023-02-06 21:43:46 +08:00
|
|
|
|
|
|
|
if (cell_spec.args_count > 1)
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
|
|
|
cell_np = cell_spec.np;
|
|
|
|
if (cell_spec.args_count)
|
|
|
|
cell_index = cell_spec.args[0];
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2023-01-27 18:40:12 +08:00
|
|
|
nvmem_np = of_get_parent(cell_np);
|
|
|
|
if (!nvmem_np) {
|
|
|
|
of_node_put(cell_np);
|
2015-07-27 19:13:34 +08:00
|
|
|
return ERR_PTR(-EINVAL);
|
2023-01-27 18:40:12 +08:00
|
|
|
}
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2023-04-05 01:21:21 +08:00
|
|
|
/* nvmem layouts produce cells within the nvmem-layout container */
|
|
|
|
if (of_node_name_eq(nvmem_np, "nvmem-layout")) {
|
|
|
|
nvmem_np = of_get_next_parent(nvmem_np);
|
|
|
|
if (!nvmem_np) {
|
|
|
|
of_node_put(cell_np);
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-03 17:52:29 +08:00
|
|
|
nvmem = __nvmem_device_get(nvmem_np, device_match_of_node);
|
2017-09-11 17:00:12 +08:00
|
|
|
of_node_put(nvmem_np);
|
2023-01-27 18:40:12 +08:00
|
|
|
if (IS_ERR(nvmem)) {
|
|
|
|
of_node_put(cell_np);
|
2015-07-27 19:13:34 +08:00
|
|
|
return ERR_CAST(nvmem);
|
2023-01-27 18:40:12 +08:00
|
|
|
}
|
2015-07-27 19:13:34 +08:00
|
|
|
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
ret = nvmem_layout_module_get_optional(nvmem);
|
|
|
|
if (ret) {
|
|
|
|
of_node_put(cell_np);
|
|
|
|
__nvmem_device_put(nvmem);
|
|
|
|
return ERR_PTR(ret);
|
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
cell_entry = nvmem_find_cell_entry_by_node(nvmem, cell_np);
|
2023-01-27 18:40:12 +08:00
|
|
|
of_node_put(cell_np);
|
2021-10-13 21:19:55 +08:00
|
|
|
if (!cell_entry) {
|
2018-09-21 21:40:16 +08:00
|
|
|
__nvmem_device_put(nvmem);
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
nvmem_layout_module_put(nvmem);
|
|
|
|
if (nvmem->layout)
|
|
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
|
|
else
|
|
|
|
return ERR_PTR(-ENOENT);
|
2015-07-27 19:13:34 +08:00
|
|
|
}
|
|
|
|
|
2023-02-06 21:43:46 +08:00
|
|
|
cell = nvmem_create_cell(cell_entry, id, cell_index);
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
if (IS_ERR(cell)) {
|
2021-10-13 21:19:55 +08:00
|
|
|
__nvmem_device_put(nvmem);
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
nvmem_layout_module_put(nvmem);
|
|
|
|
}
|
2021-10-13 21:19:55 +08:00
|
|
|
|
2015-07-27 19:13:34 +08:00
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(of_nvmem_cell_get);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_cell_get() - Get nvmem cell of device form a given cell name
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @dev: Device that requests the nvmem cell.
|
2018-09-21 21:40:21 +08:00
|
|
|
* @id: nvmem cell name to get (this corresponds with the name from the
|
|
|
|
* nvmem-cell-names property for DT systems and with the con_id from
|
|
|
|
* the lookup entry for non-DT systems).
|
2015-07-27 19:13:34 +08:00
|
|
|
*
|
|
|
|
* Return: Will be an ERR_PTR() on error or a valid pointer
|
|
|
|
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
|
|
|
* nvmem_cell_put().
|
|
|
|
*/
|
2018-09-21 21:40:21 +08:00
|
|
|
struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *id)
|
2015-07-27 19:13:34 +08:00
|
|
|
{
|
|
|
|
struct nvmem_cell *cell;
|
|
|
|
|
|
|
|
if (dev->of_node) { /* try dt first */
|
2018-09-21 21:40:21 +08:00
|
|
|
cell = of_nvmem_cell_get(dev->of_node, id);
|
2015-07-27 19:13:34 +08:00
|
|
|
if (!IS_ERR(cell) || PTR_ERR(cell) == -EPROBE_DEFER)
|
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
|
2018-09-21 21:40:21 +08:00
|
|
|
/* NULL cell id only allowed for device tree; invalid otherwise */
|
|
|
|
if (!id)
|
2018-06-19 01:30:43 +08:00
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
2018-09-21 21:40:21 +08:00
|
|
|
return nvmem_cell_get_from_lookup(dev, id);
|
2015-07-27 19:13:34 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_get);
|
|
|
|
|
|
|
|
static void devm_nvmem_cell_release(struct device *dev, void *res)
|
|
|
|
{
|
|
|
|
nvmem_cell_put(*(struct nvmem_cell **)res);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* devm_nvmem_cell_get() - Get nvmem cell of device form a given id
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @dev: Device that requests the nvmem cell.
|
|
|
|
* @id: nvmem cell name id to get.
|
2015-07-27 19:13:34 +08:00
|
|
|
*
|
|
|
|
* Return: Will be an ERR_PTR() on error or a valid pointer
|
|
|
|
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
|
|
|
* automatically once the device is freed.
|
|
|
|
*/
|
|
|
|
struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *id)
|
|
|
|
{
|
|
|
|
struct nvmem_cell **ptr, *cell;
|
|
|
|
|
|
|
|
ptr = devres_alloc(devm_nvmem_cell_release, sizeof(*ptr), GFP_KERNEL);
|
|
|
|
if (!ptr)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
cell = nvmem_cell_get(dev, id);
|
|
|
|
if (!IS_ERR(cell)) {
|
|
|
|
*ptr = cell;
|
|
|
|
devres_add(dev, ptr);
|
|
|
|
} else {
|
|
|
|
devres_free(ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(devm_nvmem_cell_get);
|
|
|
|
|
|
|
|
static int devm_nvmem_cell_match(struct device *dev, void *res, void *data)
|
|
|
|
{
|
|
|
|
struct nvmem_cell **c = res;
|
|
|
|
|
|
|
|
if (WARN_ON(!c || !*c))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return *c == data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* devm_nvmem_cell_put() - Release previously allocated nvmem cell
|
|
|
|
* from devm_nvmem_cell_get.
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @dev: Device that requests the nvmem cell.
|
|
|
|
* @cell: Previously allocated nvmem cell by devm_nvmem_cell_get().
|
2015-07-27 19:13:34 +08:00
|
|
|
*/
|
|
|
|
void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = devres_release(dev, devm_nvmem_cell_release,
|
|
|
|
devm_nvmem_cell_match, cell);
|
|
|
|
|
|
|
|
WARN_ON(ret);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(devm_nvmem_cell_put);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_cell_put() - Release previously allocated nvmem cell.
|
|
|
|
*
|
2017-01-23 07:02:39 +08:00
|
|
|
* @cell: Previously allocated nvmem cell by nvmem_cell_get().
|
2015-07-27 19:13:34 +08:00
|
|
|
*/
|
|
|
|
void nvmem_cell_put(struct nvmem_cell *cell)
|
|
|
|
{
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_device *nvmem = cell->entry->nvmem;
|
|
|
|
|
|
|
|
if (cell->id)
|
|
|
|
kfree_const(cell->id);
|
2015-07-27 19:13:34 +08:00
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
kfree(cell);
|
2015-07-27 19:13:34 +08:00
|
|
|
__nvmem_device_put(nvmem);
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
nvmem_layout_module_put(nvmem);
|
2015-07-27 19:13:34 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_put);
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf)
|
2015-07-27 19:13:34 +08:00
|
|
|
{
|
|
|
|
u8 *p, *b;
|
2019-04-13 18:32:58 +08:00
|
|
|
int i, extra, bit_offset = cell->bit_offset;
|
2015-07-27 19:13:34 +08:00
|
|
|
|
|
|
|
p = b = buf;
|
|
|
|
if (bit_offset) {
|
|
|
|
/* First shift */
|
|
|
|
*b++ >>= bit_offset;
|
|
|
|
|
|
|
|
/* setup rest of the bytes if any */
|
|
|
|
for (i = 1; i < cell->bytes; i++) {
|
|
|
|
/* Get bits from next byte and shift them towards msb */
|
|
|
|
*p |= *b << (BITS_PER_BYTE - bit_offset);
|
|
|
|
|
|
|
|
p = b;
|
|
|
|
*b++ >>= bit_offset;
|
|
|
|
}
|
2019-04-13 18:32:58 +08:00
|
|
|
} else {
|
|
|
|
/* point to the msb */
|
|
|
|
p += cell->bytes - 1;
|
2015-07-27 19:13:34 +08:00
|
|
|
}
|
2019-04-13 18:32:58 +08:00
|
|
|
|
|
|
|
/* result fits in less bytes */
|
|
|
|
extra = cell->bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
|
|
|
|
while (--extra >= 0)
|
|
|
|
*p-- = 0;
|
|
|
|
|
2015-07-27 19:13:34 +08:00
|
|
|
/* clear msb bits if any leftover in the last byte */
|
2021-10-13 20:45:11 +08:00
|
|
|
if (cell->nbits % BITS_PER_BYTE)
|
|
|
|
*p &= GENMASK((cell->nbits % BITS_PER_BYTE) - 1, 0);
|
2015-07-27 19:13:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int __nvmem_cell_read(struct nvmem_device *nvmem,
|
2023-02-06 21:43:46 +08:00
|
|
|
struct nvmem_cell_entry *cell,
|
|
|
|
void *buf, size_t *len, const char *id, int index)
|
2015-07-27 19:13:34 +08:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
2023-04-05 01:21:42 +08:00
|
|
|
rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->raw_len);
|
2015-07-27 19:13:34 +08:00
|
|
|
|
remove lots of IS_ERR_VALUE abuses
Most users of IS_ERR_VALUE() in the kernel are wrong, as they
pass an 'int' into a function that takes an 'unsigned long'
argument. This happens to work because the type is sign-extended
on 64-bit architectures before it gets converted into an
unsigned type.
However, anything that passes an 'unsigned short' or 'unsigned int'
argument into IS_ERR_VALUE() is guaranteed to be broken, as are
8-bit integers and types that are wider than 'unsigned long'.
Andrzej Hajda has already fixed a lot of the worst abusers that
were causing actual bugs, but it would be nice to prevent any
users that are not passing 'unsigned long' arguments.
This patch changes all users of IS_ERR_VALUE() that I could find
on 32-bit ARM randconfig builds and x86 allmodconfig. For the
moment, this doesn't change the definition of IS_ERR_VALUE()
because there are probably still architecture specific users
elsewhere.
Almost all the warnings I got are for files that are better off
using 'if (err)' or 'if (err < 0)'.
The only legitimate user I could find that we get a warning for
is the (32-bit only) freescale fman driver, so I did not remove
the IS_ERR_VALUE() there but changed the type to 'unsigned long'.
For 9pfs, I just worked around one user whose calling conventions
are so obscure that I did not dare change the behavior.
I was using this definition for testing:
#define IS_ERR_VALUE(x) ((unsigned long*)NULL == (typeof (x)*)NULL && \
unlikely((unsigned long long)(x) >= (unsigned long long)(typeof(x))-MAX_ERRNO))
which ends up making all 16-bit or wider types work correctly with
the most plausible interpretation of what IS_ERR_VALUE() was supposed
to return according to its users, but also causes a compile-time
warning for any users that do not pass an 'unsigned long' argument.
I suggested this approach earlier this year, but back then we ended
up deciding to just fix the users that are obviously broken. After
the initial warning that caused me to get involved in the discussion
(fs/gfs2/dir.c) showed up again in the mainline kernel, Linus
asked me to send the whole thing again.
[ Updated the 9p parts as per Al Viro - Linus ]
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lkml.org/lkml/2016/1/7/363
Link: https://lkml.org/lkml/2016/5/27/486
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> # For nvmem part
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-05-28 05:23:25 +08:00
|
|
|
if (rc)
|
2015-07-27 19:13:34 +08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
/* shift bits in-place */
|
2015-09-30 20:35:15 +08:00
|
|
|
if (cell->bit_offset || cell->nbits)
|
2015-07-27 19:13:34 +08:00
|
|
|
nvmem_shift_read_buffer_in_place(cell, buf);
|
|
|
|
|
2023-04-05 01:21:24 +08:00
|
|
|
if (cell->read_post_process) {
|
2023-04-05 01:21:28 +08:00
|
|
|
rc = cell->read_post_process(cell->priv, id, index,
|
2023-04-05 01:21:42 +08:00
|
|
|
cell->offset, buf, cell->raw_len);
|
2023-04-05 01:21:24 +08:00
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2017-01-23 07:02:38 +08:00
|
|
|
if (len)
|
|
|
|
*len = cell->bytes;
|
2015-07-27 19:13:34 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_cell_read() - Read a given nvmem cell
|
|
|
|
*
|
|
|
|
* @cell: nvmem cell to be read.
|
2017-01-23 07:02:38 +08:00
|
|
|
* @len: pointer to length of cell which will be populated on successful read;
|
|
|
|
* can be NULL.
|
2015-07-27 19:13:34 +08:00
|
|
|
*
|
2017-01-05 00:18:11 +08:00
|
|
|
* Return: ERR_PTR() on error or a valid pointer to a buffer on success. The
|
|
|
|
* buffer should be freed by the consumer with a kfree().
|
2015-07-27 19:13:34 +08:00
|
|
|
*/
|
|
|
|
void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
|
|
|
|
{
|
2023-04-05 01:21:42 +08:00
|
|
|
struct nvmem_cell_entry *entry = cell->entry;
|
|
|
|
struct nvmem_device *nvmem = entry->nvmem;
|
2015-07-27 19:13:34 +08:00
|
|
|
u8 *buf;
|
|
|
|
int rc;
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
if (!nvmem)
|
2015-07-27 19:13:34 +08:00
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
2023-04-05 01:21:42 +08:00
|
|
|
buf = kzalloc(max_t(size_t, entry->raw_len, entry->bytes), GFP_KERNEL);
|
2015-07-27 19:13:34 +08:00
|
|
|
if (!buf)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
2023-02-06 21:43:46 +08:00
|
|
|
rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id, cell->index);
|
remove lots of IS_ERR_VALUE abuses
Most users of IS_ERR_VALUE() in the kernel are wrong, as they
pass an 'int' into a function that takes an 'unsigned long'
argument. This happens to work because the type is sign-extended
on 64-bit architectures before it gets converted into an
unsigned type.
However, anything that passes an 'unsigned short' or 'unsigned int'
argument into IS_ERR_VALUE() is guaranteed to be broken, as are
8-bit integers and types that are wider than 'unsigned long'.
Andrzej Hajda has already fixed a lot of the worst abusers that
were causing actual bugs, but it would be nice to prevent any
users that are not passing 'unsigned long' arguments.
This patch changes all users of IS_ERR_VALUE() that I could find
on 32-bit ARM randconfig builds and x86 allmodconfig. For the
moment, this doesn't change the definition of IS_ERR_VALUE()
because there are probably still architecture specific users
elsewhere.
Almost all the warnings I got are for files that are better off
using 'if (err)' or 'if (err < 0)'.
The only legitimate user I could find that we get a warning for
is the (32-bit only) freescale fman driver, so I did not remove
the IS_ERR_VALUE() there but changed the type to 'unsigned long'.
For 9pfs, I just worked around one user whose calling conventions
are so obscure that I did not dare change the behavior.
I was using this definition for testing:
#define IS_ERR_VALUE(x) ((unsigned long*)NULL == (typeof (x)*)NULL && \
unlikely((unsigned long long)(x) >= (unsigned long long)(typeof(x))-MAX_ERRNO))
which ends up making all 16-bit or wider types work correctly with
the most plausible interpretation of what IS_ERR_VALUE() was supposed
to return according to its users, but also causes a compile-time
warning for any users that do not pass an 'unsigned long' argument.
I suggested this approach earlier this year, but back then we ended
up deciding to just fix the users that are obviously broken. After
the initial warning that caused me to get involved in the discussion
(fs/gfs2/dir.c) showed up again in the mainline kernel, Linus
asked me to send the whole thing again.
[ Updated the 9p parts as per Al Viro - Linus ]
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lkml.org/lkml/2016/1/7/363
Link: https://lkml.org/lkml/2016/5/27/486
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> # For nvmem part
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-05-28 05:23:25 +08:00
|
|
|
if (rc) {
|
2015-07-27 19:13:34 +08:00
|
|
|
kfree(buf);
|
|
|
|
return ERR_PTR(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_read);
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
static void *nvmem_cell_prepare_write_buffer(struct nvmem_cell_entry *cell,
|
2017-09-11 17:00:13 +08:00
|
|
|
u8 *_buf, int len)
|
2015-07-27 19:13:34 +08:00
|
|
|
{
|
|
|
|
struct nvmem_device *nvmem = cell->nvmem;
|
|
|
|
int i, rc, nbits, bit_offset = cell->bit_offset;
|
|
|
|
u8 v, *p, *buf, *b, pbyte, pbits;
|
|
|
|
|
|
|
|
nbits = cell->nbits;
|
|
|
|
buf = kzalloc(cell->bytes, GFP_KERNEL);
|
|
|
|
if (!buf)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
memcpy(buf, _buf, len);
|
|
|
|
p = b = buf;
|
|
|
|
|
|
|
|
if (bit_offset) {
|
|
|
|
pbyte = *b;
|
|
|
|
*b <<= bit_offset;
|
|
|
|
|
|
|
|
/* setup the first byte with lsb bits from nvmem */
|
2016-04-25 03:28:05 +08:00
|
|
|
rc = nvmem_reg_read(nvmem, cell->offset, &v, 1);
|
2018-05-11 19:07:03 +08:00
|
|
|
if (rc)
|
|
|
|
goto err;
|
2015-07-27 19:13:34 +08:00
|
|
|
*b++ |= GENMASK(bit_offset - 1, 0) & v;
|
|
|
|
|
|
|
|
/* setup rest of the byte if any */
|
|
|
|
for (i = 1; i < cell->bytes; i++) {
|
|
|
|
/* Get last byte bits and shift them towards lsb */
|
|
|
|
pbits = pbyte >> (BITS_PER_BYTE - 1 - bit_offset);
|
|
|
|
pbyte = *b;
|
|
|
|
p = b;
|
|
|
|
*b <<= bit_offset;
|
|
|
|
*b++ |= pbits;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if it's not end on byte boundary */
|
|
|
|
if ((nbits + bit_offset) % BITS_PER_BYTE) {
|
|
|
|
/* setup the last byte with msb bits from nvmem */
|
2016-04-25 03:28:05 +08:00
|
|
|
rc = nvmem_reg_read(nvmem,
|
2015-07-27 19:13:34 +08:00
|
|
|
cell->offset + cell->bytes - 1, &v, 1);
|
2018-05-11 19:07:03 +08:00
|
|
|
if (rc)
|
|
|
|
goto err;
|
2015-07-27 19:13:34 +08:00
|
|
|
*p |= GENMASK(7, (nbits + bit_offset) % BITS_PER_BYTE) & v;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf;
|
2018-05-11 19:07:03 +08:00
|
|
|
err:
|
|
|
|
kfree(buf);
|
|
|
|
return ERR_PTR(rc);
|
2015-07-27 19:13:34 +08:00
|
|
|
}
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, size_t len)
|
2015-07-27 19:13:34 +08:00
|
|
|
{
|
|
|
|
struct nvmem_device *nvmem = cell->nvmem;
|
|
|
|
int rc;
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
if (!nvmem || nvmem->read_only ||
|
2015-07-27 19:13:34 +08:00
|
|
|
(cell->bit_offset == 0 && len != cell->bytes))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2023-04-05 01:21:24 +08:00
|
|
|
/*
|
|
|
|
* Any cells which have a read_post_process hook are read-only because
|
|
|
|
* we cannot reverse the operation and it might affect other cells,
|
|
|
|
* too.
|
|
|
|
*/
|
|
|
|
if (cell->read_post_process)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2015-07-27 19:13:34 +08:00
|
|
|
if (cell->bit_offset || cell->nbits) {
|
|
|
|
buf = nvmem_cell_prepare_write_buffer(cell, buf, len);
|
|
|
|
if (IS_ERR(buf))
|
|
|
|
return PTR_ERR(buf);
|
|
|
|
}
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
rc = nvmem_reg_write(nvmem, cell->offset, buf, cell->bytes);
|
2015-07-27 19:13:34 +08:00
|
|
|
|
|
|
|
/* free the tmp buffer */
|
2015-09-30 20:36:10 +08:00
|
|
|
if (cell->bit_offset || cell->nbits)
|
2015-07-27 19:13:34 +08:00
|
|
|
kfree(buf);
|
|
|
|
|
remove lots of IS_ERR_VALUE abuses
Most users of IS_ERR_VALUE() in the kernel are wrong, as they
pass an 'int' into a function that takes an 'unsigned long'
argument. This happens to work because the type is sign-extended
on 64-bit architectures before it gets converted into an
unsigned type.
However, anything that passes an 'unsigned short' or 'unsigned int'
argument into IS_ERR_VALUE() is guaranteed to be broken, as are
8-bit integers and types that are wider than 'unsigned long'.
Andrzej Hajda has already fixed a lot of the worst abusers that
were causing actual bugs, but it would be nice to prevent any
users that are not passing 'unsigned long' arguments.
This patch changes all users of IS_ERR_VALUE() that I could find
on 32-bit ARM randconfig builds and x86 allmodconfig. For the
moment, this doesn't change the definition of IS_ERR_VALUE()
because there are probably still architecture specific users
elsewhere.
Almost all the warnings I got are for files that are better off
using 'if (err)' or 'if (err < 0)'.
The only legitimate user I could find that we get a warning for
is the (32-bit only) freescale fman driver, so I did not remove
the IS_ERR_VALUE() there but changed the type to 'unsigned long'.
For 9pfs, I just worked around one user whose calling conventions
are so obscure that I did not dare change the behavior.
I was using this definition for testing:
#define IS_ERR_VALUE(x) ((unsigned long*)NULL == (typeof (x)*)NULL && \
unlikely((unsigned long long)(x) >= (unsigned long long)(typeof(x))-MAX_ERRNO))
which ends up making all 16-bit or wider types work correctly with
the most plausible interpretation of what IS_ERR_VALUE() was supposed
to return according to its users, but also causes a compile-time
warning for any users that do not pass an 'unsigned long' argument.
I suggested this approach earlier this year, but back then we ended
up deciding to just fix the users that are obviously broken. After
the initial warning that caused me to get involved in the discussion
(fs/gfs2/dir.c) showed up again in the mainline kernel, Linus
asked me to send the whole thing again.
[ Updated the 9p parts as per Al Viro - Linus ]
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lkml.org/lkml/2016/1/7/363
Link: https://lkml.org/lkml/2016/5/27/486
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> # For nvmem part
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-05-28 05:23:25 +08:00
|
|
|
if (rc)
|
2015-07-27 19:13:34 +08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
2021-10-13 21:19:55 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_cell_write() - Write to a given nvmem cell
|
|
|
|
*
|
|
|
|
* @cell: nvmem cell to be written.
|
|
|
|
* @buf: Buffer to be written.
|
|
|
|
* @len: length of buffer to be written to nvmem cell.
|
|
|
|
*
|
|
|
|
* Return: length of bytes written or negative on failure.
|
|
|
|
*/
|
|
|
|
int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)
|
|
|
|
{
|
|
|
|
return __nvmem_cell_entry_write(cell->entry, buf, len);
|
|
|
|
}
|
|
|
|
|
2015-07-27 19:13:34 +08:00
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_write);
|
|
|
|
|
2020-03-10 21:22:45 +08:00
|
|
|
static int nvmem_cell_read_common(struct device *dev, const char *cell_id,
|
|
|
|
void *val, size_t count)
|
2019-04-13 18:32:57 +08:00
|
|
|
{
|
|
|
|
struct nvmem_cell *cell;
|
|
|
|
void *buf;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
cell = nvmem_cell_get(dev, cell_id);
|
|
|
|
if (IS_ERR(cell))
|
|
|
|
return PTR_ERR(cell);
|
|
|
|
|
|
|
|
buf = nvmem_cell_read(cell, &len);
|
|
|
|
if (IS_ERR(buf)) {
|
|
|
|
nvmem_cell_put(cell);
|
|
|
|
return PTR_ERR(buf);
|
|
|
|
}
|
2020-03-10 21:22:45 +08:00
|
|
|
if (len != count) {
|
2019-04-13 18:32:57 +08:00
|
|
|
kfree(buf);
|
|
|
|
nvmem_cell_put(cell);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2020-03-10 21:22:45 +08:00
|
|
|
memcpy(val, buf, count);
|
2019-04-13 18:32:57 +08:00
|
|
|
kfree(buf);
|
|
|
|
nvmem_cell_put(cell);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-03-10 21:22:45 +08:00
|
|
|
|
2020-07-22 18:06:57 +08:00
|
|
|
/**
|
|
|
|
* nvmem_cell_read_u8() - Read a cell value as a u8
|
|
|
|
*
|
|
|
|
* @dev: Device that requests the nvmem cell.
|
|
|
|
* @cell_id: Name of nvmem cell to read.
|
|
|
|
* @val: pointer to output value.
|
|
|
|
*
|
|
|
|
* Return: 0 on success or negative errno.
|
|
|
|
*/
|
|
|
|
int nvmem_cell_read_u8(struct device *dev, const char *cell_id, u8 *val)
|
|
|
|
{
|
|
|
|
return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val));
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_read_u8);
|
|
|
|
|
2020-03-10 21:22:45 +08:00
|
|
|
/**
|
2020-07-22 18:06:56 +08:00
|
|
|
* nvmem_cell_read_u16() - Read a cell value as a u16
|
2020-03-10 21:22:45 +08:00
|
|
|
*
|
|
|
|
* @dev: Device that requests the nvmem cell.
|
|
|
|
* @cell_id: Name of nvmem cell to read.
|
|
|
|
* @val: pointer to output value.
|
|
|
|
*
|
|
|
|
* Return: 0 on success or negative errno.
|
|
|
|
*/
|
|
|
|
int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val)
|
|
|
|
{
|
|
|
|
return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val));
|
|
|
|
}
|
2019-04-13 18:32:57 +08:00
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_read_u16);
|
|
|
|
|
2017-07-26 17:34:46 +08:00
|
|
|
/**
|
2020-07-22 18:06:56 +08:00
|
|
|
* nvmem_cell_read_u32() - Read a cell value as a u32
|
2017-07-26 17:34:46 +08:00
|
|
|
*
|
|
|
|
* @dev: Device that requests the nvmem cell.
|
|
|
|
* @cell_id: Name of nvmem cell to read.
|
|
|
|
* @val: pointer to output value.
|
|
|
|
*
|
|
|
|
* Return: 0 on success or negative errno.
|
|
|
|
*/
|
|
|
|
int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val)
|
|
|
|
{
|
2020-03-10 21:22:45 +08:00
|
|
|
return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val));
|
2017-07-26 17:34:46 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_read_u32);
|
|
|
|
|
2020-03-10 21:22:46 +08:00
|
|
|
/**
|
2020-07-22 18:06:56 +08:00
|
|
|
* nvmem_cell_read_u64() - Read a cell value as a u64
|
2020-03-10 21:22:46 +08:00
|
|
|
*
|
|
|
|
* @dev: Device that requests the nvmem cell.
|
|
|
|
* @cell_id: Name of nvmem cell to read.
|
|
|
|
* @val: pointer to output value.
|
|
|
|
*
|
|
|
|
* Return: 0 on success or negative errno.
|
|
|
|
*/
|
|
|
|
int nvmem_cell_read_u64(struct device *dev, const char *cell_id, u64 *val)
|
|
|
|
{
|
|
|
|
return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val));
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_read_u64);
|
|
|
|
|
2021-06-11 16:33:47 +08:00
|
|
|
static const void *nvmem_cell_read_variable_common(struct device *dev,
|
|
|
|
const char *cell_id,
|
|
|
|
size_t max_len, size_t *len)
|
2021-03-30 19:12:37 +08:00
|
|
|
{
|
|
|
|
struct nvmem_cell *cell;
|
|
|
|
int nbits;
|
|
|
|
void *buf;
|
|
|
|
|
|
|
|
cell = nvmem_cell_get(dev, cell_id);
|
|
|
|
if (IS_ERR(cell))
|
|
|
|
return cell;
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
nbits = cell->entry->nbits;
|
2021-03-30 19:12:37 +08:00
|
|
|
buf = nvmem_cell_read(cell, len);
|
|
|
|
nvmem_cell_put(cell);
|
|
|
|
if (IS_ERR(buf))
|
|
|
|
return buf;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If nbits is set then nvmem_cell_read() can significantly exaggerate
|
|
|
|
* the length of the real data. Throw away the extra junk.
|
|
|
|
*/
|
|
|
|
if (nbits)
|
|
|
|
*len = DIV_ROUND_UP(nbits, 8);
|
|
|
|
|
|
|
|
if (*len > max_len) {
|
|
|
|
kfree(buf);
|
|
|
|
return ERR_PTR(-ERANGE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_cell_read_variable_le_u32() - Read up to 32-bits of data as a little endian number.
|
|
|
|
*
|
|
|
|
* @dev: Device that requests the nvmem cell.
|
|
|
|
* @cell_id: Name of nvmem cell to read.
|
|
|
|
* @val: pointer to output value.
|
|
|
|
*
|
|
|
|
* Return: 0 on success or negative errno.
|
|
|
|
*/
|
|
|
|
int nvmem_cell_read_variable_le_u32(struct device *dev, const char *cell_id,
|
|
|
|
u32 *val)
|
|
|
|
{
|
|
|
|
size_t len;
|
2021-06-11 16:33:47 +08:00
|
|
|
const u8 *buf;
|
2021-03-30 19:12:37 +08:00
|
|
|
int i;
|
|
|
|
|
|
|
|
buf = nvmem_cell_read_variable_common(dev, cell_id, sizeof(*val), &len);
|
|
|
|
if (IS_ERR(buf))
|
|
|
|
return PTR_ERR(buf);
|
|
|
|
|
|
|
|
/* Copy w/ implicit endian conversion */
|
|
|
|
*val = 0;
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
*val |= buf[i] << (8 * i);
|
|
|
|
|
|
|
|
kfree(buf);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_read_variable_le_u32);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_cell_read_variable_le_u64() - Read up to 64-bits of data as a little endian number.
|
|
|
|
*
|
|
|
|
* @dev: Device that requests the nvmem cell.
|
|
|
|
* @cell_id: Name of nvmem cell to read.
|
|
|
|
* @val: pointer to output value.
|
|
|
|
*
|
|
|
|
* Return: 0 on success or negative errno.
|
|
|
|
*/
|
|
|
|
int nvmem_cell_read_variable_le_u64(struct device *dev, const char *cell_id,
|
|
|
|
u64 *val)
|
|
|
|
{
|
|
|
|
size_t len;
|
2021-06-11 16:33:47 +08:00
|
|
|
const u8 *buf;
|
2021-03-30 19:12:37 +08:00
|
|
|
int i;
|
|
|
|
|
|
|
|
buf = nvmem_cell_read_variable_common(dev, cell_id, sizeof(*val), &len);
|
|
|
|
if (IS_ERR(buf))
|
|
|
|
return PTR_ERR(buf);
|
|
|
|
|
|
|
|
/* Copy w/ implicit endian conversion */
|
|
|
|
*val = 0;
|
|
|
|
for (i = 0; i < len; i++)
|
2021-03-30 19:12:38 +08:00
|
|
|
*val |= (uint64_t)buf[i] << (8 * i);
|
2021-03-30 19:12:37 +08:00
|
|
|
|
|
|
|
kfree(buf);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_cell_read_variable_le_u64);
|
|
|
|
|
2015-07-27 19:13:45 +08:00
|
|
|
/**
|
|
|
|
* nvmem_device_cell_read() - Read a given nvmem device and cell
|
|
|
|
*
|
|
|
|
* @nvmem: nvmem device to read from.
|
|
|
|
* @info: nvmem cell info to be read.
|
|
|
|
* @buf: buffer pointer which will be populated on successful read.
|
|
|
|
*
|
|
|
|
* Return: length of successful bytes read on success and negative
|
|
|
|
* error code on error.
|
|
|
|
*/
|
|
|
|
ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
|
|
|
|
struct nvmem_cell_info *info, void *buf)
|
|
|
|
{
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry cell;
|
2015-07-27 19:13:45 +08:00
|
|
|
int rc;
|
|
|
|
ssize_t len;
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
if (!nvmem)
|
2015-07-27 19:13:45 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
rc = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, &cell);
|
remove lots of IS_ERR_VALUE abuses
Most users of IS_ERR_VALUE() in the kernel are wrong, as they
pass an 'int' into a function that takes an 'unsigned long'
argument. This happens to work because the type is sign-extended
on 64-bit architectures before it gets converted into an
unsigned type.
However, anything that passes an 'unsigned short' or 'unsigned int'
argument into IS_ERR_VALUE() is guaranteed to be broken, as are
8-bit integers and types that are wider than 'unsigned long'.
Andrzej Hajda has already fixed a lot of the worst abusers that
were causing actual bugs, but it would be nice to prevent any
users that are not passing 'unsigned long' arguments.
This patch changes all users of IS_ERR_VALUE() that I could find
on 32-bit ARM randconfig builds and x86 allmodconfig. For the
moment, this doesn't change the definition of IS_ERR_VALUE()
because there are probably still architecture specific users
elsewhere.
Almost all the warnings I got are for files that are better off
using 'if (err)' or 'if (err < 0)'.
The only legitimate user I could find that we get a warning for
is the (32-bit only) freescale fman driver, so I did not remove
the IS_ERR_VALUE() there but changed the type to 'unsigned long'.
For 9pfs, I just worked around one user whose calling conventions
are so obscure that I did not dare change the behavior.
I was using this definition for testing:
#define IS_ERR_VALUE(x) ((unsigned long*)NULL == (typeof (x)*)NULL && \
unlikely((unsigned long long)(x) >= (unsigned long long)(typeof(x))-MAX_ERRNO))
which ends up making all 16-bit or wider types work correctly with
the most plausible interpretation of what IS_ERR_VALUE() was supposed
to return according to its users, but also causes a compile-time
warning for any users that do not pass an 'unsigned long' argument.
I suggested this approach earlier this year, but back then we ended
up deciding to just fix the users that are obviously broken. After
the initial warning that caused me to get involved in the discussion
(fs/gfs2/dir.c) showed up again in the mainline kernel, Linus
asked me to send the whole thing again.
[ Updated the 9p parts as per Al Viro - Linus ]
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lkml.org/lkml/2016/1/7/363
Link: https://lkml.org/lkml/2016/5/27/486
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> # For nvmem part
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-05-28 05:23:25 +08:00
|
|
|
if (rc)
|
2015-07-27 19:13:45 +08:00
|
|
|
return rc;
|
|
|
|
|
2023-02-06 21:43:46 +08:00
|
|
|
rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL, 0);
|
remove lots of IS_ERR_VALUE abuses
Most users of IS_ERR_VALUE() in the kernel are wrong, as they
pass an 'int' into a function that takes an 'unsigned long'
argument. This happens to work because the type is sign-extended
on 64-bit architectures before it gets converted into an
unsigned type.
However, anything that passes an 'unsigned short' or 'unsigned int'
argument into IS_ERR_VALUE() is guaranteed to be broken, as are
8-bit integers and types that are wider than 'unsigned long'.
Andrzej Hajda has already fixed a lot of the worst abusers that
were causing actual bugs, but it would be nice to prevent any
users that are not passing 'unsigned long' arguments.
This patch changes all users of IS_ERR_VALUE() that I could find
on 32-bit ARM randconfig builds and x86 allmodconfig. For the
moment, this doesn't change the definition of IS_ERR_VALUE()
because there are probably still architecture specific users
elsewhere.
Almost all the warnings I got are for files that are better off
using 'if (err)' or 'if (err < 0)'.
The only legitimate user I could find that we get a warning for
is the (32-bit only) freescale fman driver, so I did not remove
the IS_ERR_VALUE() there but changed the type to 'unsigned long'.
For 9pfs, I just worked around one user whose calling conventions
are so obscure that I did not dare change the behavior.
I was using this definition for testing:
#define IS_ERR_VALUE(x) ((unsigned long*)NULL == (typeof (x)*)NULL && \
unlikely((unsigned long long)(x) >= (unsigned long long)(typeof(x))-MAX_ERRNO))
which ends up making all 16-bit or wider types work correctly with
the most plausible interpretation of what IS_ERR_VALUE() was supposed
to return according to its users, but also causes a compile-time
warning for any users that do not pass an 'unsigned long' argument.
I suggested this approach earlier this year, but back then we ended
up deciding to just fix the users that are obviously broken. After
the initial warning that caused me to get involved in the discussion
(fs/gfs2/dir.c) showed up again in the mainline kernel, Linus
asked me to send the whole thing again.
[ Updated the 9p parts as per Al Viro - Linus ]
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lkml.org/lkml/2016/1/7/363
Link: https://lkml.org/lkml/2016/5/27/486
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> # For nvmem part
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-05-28 05:23:25 +08:00
|
|
|
if (rc)
|
2015-07-27 19:13:45 +08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_device_cell_read);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_device_cell_write() - Write cell to a given nvmem device
|
|
|
|
*
|
|
|
|
* @nvmem: nvmem device to be written to.
|
2017-01-23 07:02:39 +08:00
|
|
|
* @info: nvmem cell info to be written.
|
2015-07-27 19:13:45 +08:00
|
|
|
* @buf: buffer to be written to cell.
|
|
|
|
*
|
|
|
|
* Return: length of bytes written or negative error code on failure.
|
2018-09-21 21:40:23 +08:00
|
|
|
*/
|
2015-07-27 19:13:45 +08:00
|
|
|
int nvmem_device_cell_write(struct nvmem_device *nvmem,
|
|
|
|
struct nvmem_cell_info *info, void *buf)
|
|
|
|
{
|
2021-10-13 21:19:55 +08:00
|
|
|
struct nvmem_cell_entry cell;
|
2015-07-27 19:13:45 +08:00
|
|
|
int rc;
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
if (!nvmem)
|
2015-07-27 19:13:45 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
rc = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, &cell);
|
remove lots of IS_ERR_VALUE abuses
Most users of IS_ERR_VALUE() in the kernel are wrong, as they
pass an 'int' into a function that takes an 'unsigned long'
argument. This happens to work because the type is sign-extended
on 64-bit architectures before it gets converted into an
unsigned type.
However, anything that passes an 'unsigned short' or 'unsigned int'
argument into IS_ERR_VALUE() is guaranteed to be broken, as are
8-bit integers and types that are wider than 'unsigned long'.
Andrzej Hajda has already fixed a lot of the worst abusers that
were causing actual bugs, but it would be nice to prevent any
users that are not passing 'unsigned long' arguments.
This patch changes all users of IS_ERR_VALUE() that I could find
on 32-bit ARM randconfig builds and x86 allmodconfig. For the
moment, this doesn't change the definition of IS_ERR_VALUE()
because there are probably still architecture specific users
elsewhere.
Almost all the warnings I got are for files that are better off
using 'if (err)' or 'if (err < 0)'.
The only legitimate user I could find that we get a warning for
is the (32-bit only) freescale fman driver, so I did not remove
the IS_ERR_VALUE() there but changed the type to 'unsigned long'.
For 9pfs, I just worked around one user whose calling conventions
are so obscure that I did not dare change the behavior.
I was using this definition for testing:
#define IS_ERR_VALUE(x) ((unsigned long*)NULL == (typeof (x)*)NULL && \
unlikely((unsigned long long)(x) >= (unsigned long long)(typeof(x))-MAX_ERRNO))
which ends up making all 16-bit or wider types work correctly with
the most plausible interpretation of what IS_ERR_VALUE() was supposed
to return according to its users, but also causes a compile-time
warning for any users that do not pass an 'unsigned long' argument.
I suggested this approach earlier this year, but back then we ended
up deciding to just fix the users that are obviously broken. After
the initial warning that caused me to get involved in the discussion
(fs/gfs2/dir.c) showed up again in the mainline kernel, Linus
asked me to send the whole thing again.
[ Updated the 9p parts as per Al Viro - Linus ]
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lkml.org/lkml/2016/1/7/363
Link: https://lkml.org/lkml/2016/5/27/486
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> # For nvmem part
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-05-28 05:23:25 +08:00
|
|
|
if (rc)
|
2015-07-27 19:13:45 +08:00
|
|
|
return rc;
|
|
|
|
|
2021-10-13 21:19:55 +08:00
|
|
|
return __nvmem_cell_entry_write(&cell, buf, cell.bytes);
|
2015-07-27 19:13:45 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_device_cell_write);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_device_read() - Read from a given nvmem device
|
|
|
|
*
|
|
|
|
* @nvmem: nvmem device to read from.
|
|
|
|
* @offset: offset in nvmem device.
|
|
|
|
* @bytes: number of bytes to read.
|
|
|
|
* @buf: buffer pointer which will be populated on successful read.
|
|
|
|
*
|
|
|
|
* Return: length of successful bytes read on success and negative
|
|
|
|
* error code on error.
|
|
|
|
*/
|
|
|
|
int nvmem_device_read(struct nvmem_device *nvmem,
|
|
|
|
unsigned int offset,
|
|
|
|
size_t bytes, void *buf)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
if (!nvmem)
|
2015-07-27 19:13:45 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
rc = nvmem_reg_read(nvmem, offset, buf, bytes);
|
2015-07-27 19:13:45 +08:00
|
|
|
|
remove lots of IS_ERR_VALUE abuses
Most users of IS_ERR_VALUE() in the kernel are wrong, as they
pass an 'int' into a function that takes an 'unsigned long'
argument. This happens to work because the type is sign-extended
on 64-bit architectures before it gets converted into an
unsigned type.
However, anything that passes an 'unsigned short' or 'unsigned int'
argument into IS_ERR_VALUE() is guaranteed to be broken, as are
8-bit integers and types that are wider than 'unsigned long'.
Andrzej Hajda has already fixed a lot of the worst abusers that
were causing actual bugs, but it would be nice to prevent any
users that are not passing 'unsigned long' arguments.
This patch changes all users of IS_ERR_VALUE() that I could find
on 32-bit ARM randconfig builds and x86 allmodconfig. For the
moment, this doesn't change the definition of IS_ERR_VALUE()
because there are probably still architecture specific users
elsewhere.
Almost all the warnings I got are for files that are better off
using 'if (err)' or 'if (err < 0)'.
The only legitimate user I could find that we get a warning for
is the (32-bit only) freescale fman driver, so I did not remove
the IS_ERR_VALUE() there but changed the type to 'unsigned long'.
For 9pfs, I just worked around one user whose calling conventions
are so obscure that I did not dare change the behavior.
I was using this definition for testing:
#define IS_ERR_VALUE(x) ((unsigned long*)NULL == (typeof (x)*)NULL && \
unlikely((unsigned long long)(x) >= (unsigned long long)(typeof(x))-MAX_ERRNO))
which ends up making all 16-bit or wider types work correctly with
the most plausible interpretation of what IS_ERR_VALUE() was supposed
to return according to its users, but also causes a compile-time
warning for any users that do not pass an 'unsigned long' argument.
I suggested this approach earlier this year, but back then we ended
up deciding to just fix the users that are obviously broken. After
the initial warning that caused me to get involved in the discussion
(fs/gfs2/dir.c) showed up again in the mainline kernel, Linus
asked me to send the whole thing again.
[ Updated the 9p parts as per Al Viro - Linus ]
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lkml.org/lkml/2016/1/7/363
Link: https://lkml.org/lkml/2016/5/27/486
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> # For nvmem part
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-05-28 05:23:25 +08:00
|
|
|
if (rc)
|
2015-07-27 19:13:45 +08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_device_read);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_device_write() - Write cell to a given nvmem device
|
|
|
|
*
|
|
|
|
* @nvmem: nvmem device to be written to.
|
|
|
|
* @offset: offset in nvmem device.
|
|
|
|
* @bytes: number of bytes to write.
|
|
|
|
* @buf: buffer to be written.
|
|
|
|
*
|
|
|
|
* Return: length of bytes written or negative error code on failure.
|
2018-09-21 21:40:23 +08:00
|
|
|
*/
|
2015-07-27 19:13:45 +08:00
|
|
|
int nvmem_device_write(struct nvmem_device *nvmem,
|
|
|
|
unsigned int offset,
|
|
|
|
size_t bytes, void *buf)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
if (!nvmem)
|
2015-07-27 19:13:45 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2016-04-25 03:28:05 +08:00
|
|
|
rc = nvmem_reg_write(nvmem, offset, buf, bytes);
|
2015-07-27 19:13:45 +08:00
|
|
|
|
remove lots of IS_ERR_VALUE abuses
Most users of IS_ERR_VALUE() in the kernel are wrong, as they
pass an 'int' into a function that takes an 'unsigned long'
argument. This happens to work because the type is sign-extended
on 64-bit architectures before it gets converted into an
unsigned type.
However, anything that passes an 'unsigned short' or 'unsigned int'
argument into IS_ERR_VALUE() is guaranteed to be broken, as are
8-bit integers and types that are wider than 'unsigned long'.
Andrzej Hajda has already fixed a lot of the worst abusers that
were causing actual bugs, but it would be nice to prevent any
users that are not passing 'unsigned long' arguments.
This patch changes all users of IS_ERR_VALUE() that I could find
on 32-bit ARM randconfig builds and x86 allmodconfig. For the
moment, this doesn't change the definition of IS_ERR_VALUE()
because there are probably still architecture specific users
elsewhere.
Almost all the warnings I got are for files that are better off
using 'if (err)' or 'if (err < 0)'.
The only legitimate user I could find that we get a warning for
is the (32-bit only) freescale fman driver, so I did not remove
the IS_ERR_VALUE() there but changed the type to 'unsigned long'.
For 9pfs, I just worked around one user whose calling conventions
are so obscure that I did not dare change the behavior.
I was using this definition for testing:
#define IS_ERR_VALUE(x) ((unsigned long*)NULL == (typeof (x)*)NULL && \
unlikely((unsigned long long)(x) >= (unsigned long long)(typeof(x))-MAX_ERRNO))
which ends up making all 16-bit or wider types work correctly with
the most plausible interpretation of what IS_ERR_VALUE() was supposed
to return according to its users, but also causes a compile-time
warning for any users that do not pass an 'unsigned long' argument.
I suggested this approach earlier this year, but back then we ended
up deciding to just fix the users that are obviously broken. After
the initial warning that caused me to get involved in the discussion
(fs/gfs2/dir.c) showed up again in the mainline kernel, Linus
asked me to send the whole thing again.
[ Updated the 9p parts as per Al Viro - Linus ]
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lkml.org/lkml/2016/1/7/363
Link: https://lkml.org/lkml/2016/5/27/486
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> # For nvmem part
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-05-28 05:23:25 +08:00
|
|
|
if (rc)
|
2015-07-27 19:13:45 +08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_device_write);
|
|
|
|
|
2018-09-21 21:40:15 +08:00
|
|
|
/**
|
|
|
|
* nvmem_add_cell_table() - register a table of cell info entries
|
|
|
|
*
|
|
|
|
* @table: table of cell info entries
|
|
|
|
*/
|
|
|
|
void nvmem_add_cell_table(struct nvmem_cell_table *table)
|
|
|
|
{
|
|
|
|
mutex_lock(&nvmem_cell_mutex);
|
|
|
|
list_add_tail(&table->node, &nvmem_cell_tables);
|
|
|
|
mutex_unlock(&nvmem_cell_mutex);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_add_cell_table);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_del_cell_table() - remove a previously registered cell info table
|
|
|
|
*
|
|
|
|
* @table: table of cell info entries
|
|
|
|
*/
|
|
|
|
void nvmem_del_cell_table(struct nvmem_cell_table *table)
|
|
|
|
{
|
|
|
|
mutex_lock(&nvmem_cell_mutex);
|
|
|
|
list_del(&table->node);
|
|
|
|
mutex_unlock(&nvmem_cell_mutex);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_del_cell_table);
|
|
|
|
|
2018-09-21 21:40:17 +08:00
|
|
|
/**
|
|
|
|
* nvmem_add_cell_lookups() - register a list of cell lookup entries
|
|
|
|
*
|
|
|
|
* @entries: array of cell lookup entries
|
|
|
|
* @nentries: number of cell lookup entries in the array
|
|
|
|
*/
|
|
|
|
void nvmem_add_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
mutex_lock(&nvmem_lookup_mutex);
|
|
|
|
for (i = 0; i < nentries; i++)
|
|
|
|
list_add_tail(&entries[i].node, &nvmem_lookup_list);
|
|
|
|
mutex_unlock(&nvmem_lookup_mutex);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_add_cell_lookups);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nvmem_del_cell_lookups() - remove a list of previously added cell lookup
|
|
|
|
* entries
|
|
|
|
*
|
|
|
|
* @entries: array of cell lookup entries
|
|
|
|
* @nentries: number of cell lookup entries in the array
|
|
|
|
*/
|
|
|
|
void nvmem_del_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
mutex_lock(&nvmem_lookup_mutex);
|
|
|
|
for (i = 0; i < nentries; i++)
|
|
|
|
list_del(&entries[i].node);
|
|
|
|
mutex_unlock(&nvmem_lookup_mutex);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_del_cell_lookups);
|
|
|
|
|
2018-09-21 21:40:03 +08:00
|
|
|
/**
|
|
|
|
* nvmem_dev_name() - Get the name of a given nvmem device.
|
|
|
|
*
|
|
|
|
* @nvmem: nvmem device.
|
|
|
|
*
|
|
|
|
* Return: name of the nvmem device.
|
|
|
|
*/
|
|
|
|
const char *nvmem_dev_name(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
return dev_name(&nvmem->dev);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_dev_name);
|
|
|
|
|
2023-12-22 01:34:17 +08:00
|
|
|
/**
|
|
|
|
* nvmem_dev_size() - Get the size of a given nvmem device.
|
|
|
|
*
|
|
|
|
* @nvmem: nvmem device.
|
|
|
|
*
|
|
|
|
* Return: size of the nvmem device.
|
|
|
|
*/
|
|
|
|
size_t nvmem_dev_size(struct nvmem_device *nvmem)
|
|
|
|
{
|
|
|
|
return nvmem->size;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nvmem_dev_size);
|
|
|
|
|
2015-07-27 19:13:19 +08:00
|
|
|
static int __init nvmem_init(void)
|
|
|
|
{
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = bus_register(&nvmem_bus_type);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = nvmem_layout_bus_register();
|
|
|
|
if (ret)
|
|
|
|
bus_unregister(&nvmem_bus_type);
|
|
|
|
|
|
|
|
return ret;
|
2015-07-27 19:13:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit nvmem_exit(void)
|
|
|
|
{
|
nvmem: core: Rework layouts to become regular devices
Current layout support was initially written without modules support in
mind. When the requirement for module support rose, the existing base
was improved to adopt modularization support, but kind of a design flaw
was introduced. With the existing implementation, when a storage device
registers into NVMEM, the core tries to hook a layout (if any) and
populates its cells immediately. This means, if the hardware description
expects a layout to be hooked up, but no driver was provided for that,
the storage medium will fail to probe and try later from
scratch. Even if we consider that the hardware description shall be
correct, we could still probe the storage device (especially if it
contains the rootfs).
One way to overcome this situation is to consider the layouts as
devices, and leverage the native notifier mechanism. When a new NVMEM
device is registered, we can populate its nvmem-layout child, if any,
and wait for the matching to be done in order to get the cells (the
waiting can be easily done with the NVMEM notifiers). If the layout
driver is compiled as a module, it should automatically be loaded. This
way, there is no strong order to enforce, any NVMEM device creation
or NVMEM layout driver insertion will be observed as a new event which
may lead to the creation of additional cells, without disturbing the
probes with costly (and sometimes endless) deferrals.
In order to achieve that goal we create a new bus for the nvmem-layouts
with minimal logic to match nvmem-layout devices with nvmem-layout
drivers. All this infrastructure code is created in the layouts.c file.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Rafał Miłecki <rafal@milecki.pl>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-7-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-12-15 19:15:32 +08:00
|
|
|
nvmem_layout_bus_unregister();
|
2015-07-27 19:13:19 +08:00
|
|
|
bus_unregister(&nvmem_bus_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
subsys_initcall(nvmem_init);
|
|
|
|
module_exit(nvmem_exit);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
|
|
|
|
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com");
|
|
|
|
MODULE_DESCRIPTION("nvmem Driver Core");
|