// SPDX-License-Identifier: GPL-2.0/* * nvmem framework core. * * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> */ #include <linux/cleanup.h>#include <linux/device.h>#include <linux/export.h>#include <linux/fs.h>#include <linux/idr.h>#include <linux/init.h>#include <linux/kref.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/nvmem-consumer.h>#include <linux/nvmem-provider.h>#include <linux/gpio/consumer.h>#include <linux/of.h>#include <linux/slab.h> #include "internals.h" #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) #define FLAG_COMPAT BIT(0)struct nvmem_cell_entry { const char *name; int offset; size_t raw_len; int bytes; int bit_offset; int nbits; nvmem_cell_post_process_t read_post_process; void *priv; struct device_node *np; struct nvmem_device *nvmem; struct list_head node;}; struct nvmem_cell { struct nvmem_cell_entry *entry; const char *id; int index;}; static DEFINE_MUTEX(nvmem_mutex);static DEFINE_IDA(nvmem_ida); static DEFINE_MUTEX(nvmem_lookup_mutex);static LIST_HEAD(nvmem_lookup_list); static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes){ struct nvmem_operations *ops = nvmem->ops; if (!ops->reg_read) return -EOPNOTSUPP; return ops->reg_read(nvmem->priv, offset, val, bytes);} static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes){ struct nvmem_operations *ops = nvmem->ops; int ret, wr_ok; if (!ops->reg_write) return -EOPNOTSUPP; ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 0); if (ret) return ret; wr_ok = ops->reg_write(nvmem->priv, offset, val, bytes); ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 1); if (ret) return ret; return wr_ok;} 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);} #ifdef CONFIG_NVMEM_SYSFSstatic const char * const nvmem_type_str[] = { [NVMEM_TYPE_UNKNOWN] = "Unknown", [NVMEM_TYPE_EEPROM] = "EEPROM", [NVMEM_TYPE_OTP] = "OTP", [NVMEM_TYPE_BATTERY_BACKED] = "Battery backed", [NVMEM_TYPE_FRAM] = "FRAM",}; #ifdef CONFIG_DEBUG_LOCK_ALLOCstatic 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 sysfs_emit(buf, "%s\n", nvmem_type_str[nvmem->type]);} static DEVICE_ATTR_RO(type); static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr, char *buf){ struct nvmem_device *nvmem = to_nvmem_device(dev); return sysfs_emit(buf, "%d\n", nvmem->read_only);} static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct nvmem_device *nvmem = to_nvmem_device(dev); int ret = kstrtobool(buf, &nvmem->read_only); if (ret < 0) return ret; return count;} static DEVICE_ATTR_RW(force_ro); static struct attribute *nvmem_attrs[] = { &dev_attr_force_ro.attr, &dev_attr_type.attr, NULL,}; static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, const 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 dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; count = round_down(count, nvmem->word_size); rc = nvmem_reg_read(nvmem, pos, buf, count); if (rc) { if (rc == -EOPNOTSUPP) return -EPERM; return rc; } return count;} static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, const 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 dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; count = round_down(count, nvmem->word_size); if (nvmem->read_only) return -EPERM; rc = nvmem_reg_write(nvmem, pos, buf, count); if (rc) { if (rc == -EOPNOTSUPP) return -EPERM; return rc; } return count;} static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem){ struct nvmem_operations *ops = nvmem->ops; umode_t mode = 0400; if (!nvmem->root_only) mode |= 0044; if (!nvmem->read_only) mode |= 0200; if (!ops->reg_write) mode &= ~0200; if (!ops->reg_read) mode &= ~0444; return mode;} static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, const struct bin_attribute *attr, int i){ struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); return nvmem_bin_attr_get_umode(nvmem);} static size_t nvmem_bin_attr_size(struct kobject *kobj, const struct bin_attribute *attr, int i){ struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); return nvmem->size;} static umode_t nvmem_attr_is_visible(struct kobject *kobj, struct attribute *attr, int i){ struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); struct nvmem_operations *ops = nvmem->ops; /* * If the device has no .reg_write operation, do not allow * configuration as read-write. * If the device is set as read-only by configuration, it * can be forced into read-write mode using the 'force_ro' * attribute. */ if (attr == &dev_attr_force_ro.attr && !ops->reg_write) return 0; /* Attribute not visible */ return attr->mode;} 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, const 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;} /* default read/write permissions */static const struct bin_attribute bin_attr_rw_nvmem = { .attr = { .name = "nvmem", .mode = 0644, }, .read = bin_attr_nvmem_read, .write = bin_attr_nvmem_write,}; static const struct bin_attribute *const 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, .bin_size = nvmem_bin_attr_size, .is_visible = nvmem_attr_is_visible,}; static const struct attribute_group *nvmem_dev_groups[] = { &nvmem_bin_group, NULL,}; static const struct bin_attribute bin_attr_nvmem_eeprom_compat = { .attr = { .name = "eeprom", }, .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; nvmem->eeprom = bin_attr_nvmem_eeprom_compat; if (config->type == NVMEM_TYPE_FRAM) nvmem->eeprom.attr.name = "fram"; nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem); 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){ if (nvmem->flags & FLAG_COMPAT) device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);} static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem){ struct attribute_group group = { .name = "cells", }; struct nvmem_cell_entry *entry; const struct bin_attribute **pattrs; struct bin_attribute *attrs; unsigned int ncells = 0, i = 0; int ret; guard(mutex)(&nvmem_mutex); if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated) return 0; /* Allocate an array of attributes with a sentinel */ ncells = list_count_nodes(&nvmem->cells); pattrs = devm_kcalloc(&nvmem->dev, ncells + 1, sizeof(struct bin_attribute *), GFP_KERNEL); if (!pattrs) return -ENOMEM; attrs = devm_kcalloc(&nvmem->dev, ncells, sizeof(struct bin_attribute), GFP_KERNEL); if (!attrs) return -ENOMEM; /* 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, "%s@%x,%x", entry->name,Lines 1–500 of 2179. Load more
// SPDX-License-Identifier: GPL-2.0 /* * nvmem framework core. * * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> */ #include <linux/cleanup.h> #include <linux/device.h> #include <linux/export.h> #include <linux/fs.h> #include <linux/idr.h> #include <linux/init.h> #include <linux/kref.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/nvmem-consumer.h> #include <linux/nvmem-provider.h> #include <linux/gpio/consumer.h> #include <linux/of.h> #include <linux/slab.h> #include "internals.h" #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) #define FLAG_COMPAT BIT(0) struct nvmem_cell_entry { const char *name; int offset; size_t raw_len; int bytes; int bit_offset; int nbits; nvmem_cell_post_process_t read_post_process; void *priv; struct device_node *np; struct nvmem_device *nvmem; struct list_head node; }; struct nvmem_cell { struct nvmem_cell_entry *entry; const char *id; int index; }; static DEFINE_MUTEX(nvmem_mutex); static DEFINE_IDA(nvmem_ida); static DEFINE_MUTEX(nvmem_lookup_mutex); static LIST_HEAD(nvmem_lookup_list); static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { struct nvmem_operations *ops = nvmem->ops; if (!ops->reg_read) return -EOPNOTSUPP; return ops->reg_read(nvmem->priv, offset, val, bytes); } static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { struct nvmem_operations *ops = nvmem->ops; int ret, wr_ok; if (!ops->reg_write) return -EOPNOTSUPP; ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 0); if (ret) return ret; wr_ok = ops->reg_write(nvmem->priv, offset, val, bytes); ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 1); if (ret) return ret; return wr_ok; } 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); } #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", [NVMEM_TYPE_FRAM] = "FRAM", }; #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 sysfs_emit(buf, "%s\n", nvmem_type_str[nvmem->type]); } static DEVICE_ATTR_RO(type); static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr, char *buf) { struct nvmem_device *nvmem = to_nvmem_device(dev); return sysfs_emit(buf, "%d\n", nvmem->read_only); } static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct nvmem_device *nvmem = to_nvmem_device(dev); int ret = kstrtobool(buf, &nvmem->read_only); if (ret < 0) return ret; return count; } static DEVICE_ATTR_RW(force_ro); static struct attribute *nvmem_attrs[] = { &dev_attr_force_ro.attr, &dev_attr_type.attr, NULL, }; static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, const 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 dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; count = round_down(count, nvmem->word_size); rc = nvmem_reg_read(nvmem, pos, buf, count); if (rc) { if (rc == -EOPNOTSUPP) return -EPERM; return rc; } return count; } static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, const 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 dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; count = round_down(count, nvmem->word_size); if (nvmem->read_only) return -EPERM; rc = nvmem_reg_write(nvmem, pos, buf, count); if (rc) { if (rc == -EOPNOTSUPP) return -EPERM; return rc; } return count; } static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem) { struct nvmem_operations *ops = nvmem->ops; umode_t mode = 0400; if (!nvmem->root_only) mode |= 0044; if (!nvmem->read_only) mode |= 0200; if (!ops->reg_write) mode &= ~0200; if (!ops->reg_read) mode &= ~0444; return mode; } static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, const struct bin_attribute *attr, int i) { struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); return nvmem_bin_attr_get_umode(nvmem); } static size_t nvmem_bin_attr_size(struct kobject *kobj, const struct bin_attribute *attr, int i) { struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); return nvmem->size; } static umode_t nvmem_attr_is_visible(struct kobject *kobj, struct attribute *attr, int i) { struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); struct nvmem_operations *ops = nvmem->ops; /* * If the device has no .reg_write operation, do not allow * configuration as read-write. * If the device is set as read-only by configuration, it * can be forced into read-write mode using the 'force_ro' * attribute. */ if (attr == &dev_attr_force_ro.attr && !ops->reg_write) return 0; /* Attribute not visible */ return attr->mode; } 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, const 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; } /* default read/write permissions */ static const struct bin_attribute bin_attr_rw_nvmem = { .attr = { .name = "nvmem", .mode = 0644, }, .read = bin_attr_nvmem_read, .write = bin_attr_nvmem_write, }; static const struct bin_attribute *const 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, .bin_size = nvmem_bin_attr_size, .is_visible = nvmem_attr_is_visible, }; static const struct attribute_group *nvmem_dev_groups[] = { &nvmem_bin_group, NULL, }; static const struct bin_attribute bin_attr_nvmem_eeprom_compat = { .attr = { .name = "eeprom", }, .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; nvmem->eeprom = bin_attr_nvmem_eeprom_compat; if (config->type == NVMEM_TYPE_FRAM) nvmem->eeprom.attr.name = "fram"; nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem); 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) { if (nvmem->flags & FLAG_COMPAT) device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); } static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem) { struct attribute_group group = { .name = "cells", }; struct nvmem_cell_entry *entry; const struct bin_attribute **pattrs; struct bin_attribute *attrs; unsigned int ncells = 0, i = 0; int ret; guard(mutex)(&nvmem_mutex); if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated) return 0; /* Allocate an array of attributes with a sentinel */ ncells = list_count_nodes(&nvmem->cells); pattrs = devm_kcalloc(&nvmem->dev, ncells + 1, sizeof(struct bin_attribute *), GFP_KERNEL); if (!pattrs) return -ENOMEM; attrs = devm_kcalloc(&nvmem->dev, ncells, sizeof(struct bin_attribute), GFP_KERNEL); if (!attrs) return -ENOMEM; /* 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, "%s@%x,%x", entry->name,