| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019, Linaro Limited |
| */ |
| #include "nvmem.h" |
| |
| 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", |
| }; |
| |
| #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 |
| dev = container_of(kobj, struct device, kobj); |
| nvmem = to_nvmem_device(dev); |
| |
| /* Stop the user from reading */ |
| if (pos >= nvmem->size) |
| return 0; |
| |
| 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; |
| |
| rc = nvmem->reg_read(nvmem->priv, pos, buf, count); |
| |
| 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 |
| dev = container_of(kobj, struct device, kobj); |
| nvmem = to_nvmem_device(dev); |
| |
| /* Stop the user from writing */ |
| if (pos >= nvmem->size) |
| return -EFBIG; |
| |
| 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; |
| |
| rc = nvmem->reg_write(nvmem->priv, pos, buf, count); |
| |
| if (rc) |
| return rc; |
| |
| return count; |
| } |
| |
| static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, |
| struct bin_attribute *attr, int i) |
| { |
| struct device *dev = container_of(kobj, struct device, kobj); |
| struct nvmem_device *nvmem = to_nvmem_device(dev); |
| 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; |
| } |
| |
| /* 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, |
| }; |
| |
| static const struct attribute_group *nvmem_dev_groups[] = { |
| &nvmem_bin_group, |
| NULL, |
| }; |
| |
| /* read only permission */ |
| static struct bin_attribute bin_attr_ro_nvmem = { |
| .attr = { |
| .name = "nvmem", |
| .mode = 0444, |
| }, |
| .read = bin_attr_nvmem_read, |
| }; |
| |
| /* default read/write permissions, root only */ |
| static struct bin_attribute bin_attr_rw_root_nvmem = { |
| .attr = { |
| .name = "nvmem", |
| .mode = 0600, |
| }, |
| .read = bin_attr_nvmem_read, |
| .write = bin_attr_nvmem_write, |
| }; |
| |
| /* read only permission, root only */ |
| static struct bin_attribute bin_attr_ro_root_nvmem = { |
| .attr = { |
| .name = "nvmem", |
| .mode = 0400, |
| }, |
| .read = bin_attr_nvmem_read, |
| }; |
| |
| const struct attribute_group **nvmem_sysfs_get_groups(void) |
| { |
| return nvmem_dev_groups; |
| } |
| |
| /* |
| * nvmem_setup_compat() - Create an additional binary entry in |
| * drivers sys directory, to be backwards compatible with the older |
| * drivers/misc/eeprom drivers. |
| */ |
| 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; |
| |
| if (nvmem->read_only) { |
| if (config->root_only) |
| nvmem->eeprom = bin_attr_ro_root_nvmem; |
| else |
| nvmem->eeprom = bin_attr_ro_nvmem; |
| } else { |
| if (config->root_only) |
| nvmem->eeprom = bin_attr_rw_root_nvmem; |
| else |
| nvmem->eeprom = bin_attr_rw_nvmem; |
| } |
| nvmem->eeprom.attr.name = "eeprom"; |
| 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; |
| } |
| |
| 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); |
| } |