diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile index eff13aa7..e650ca04 100644 --- a/drivers/platform/tegra/Makefile +++ b/drivers/platform/tegra/Makefile @@ -3,6 +3,9 @@ obj-m += bad.o +obj-m += firmwares-class.o +obj-m += firmwares-inventory.o + tegra-bootloader-debug-objs := tegra_bootloader_debug.o tegra-bootloader-debug-objs += tegra_bootloader_debug_init.o obj-m += tegra-bootloader-debug.o diff --git a/drivers/platform/tegra/firmwares-class.c b/drivers/platform/tegra/firmwares-class.c new file mode 100644 index 00000000..e266d2a1 --- /dev/null +++ b/drivers/platform/tegra/firmwares-class.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, NVIDIA CORPORATION, All rights reserved. + +#include +#include +#include +#include +#include +#include + +struct tegrafw_data { + u32 flags; + ssize_t (*reader)(struct device *dev, char *data, size_t size); + char *saved_string; + struct mutex lock; +}; + +void tegrafw_invalidate(struct device *fwdev) +{ + struct tegrafw_data *tfw = fwdev->platform_data; + + if (!tfw->reader) { + dev_warn(fwdev, "attempt to invalidate when reader is NULL\n"); + return; + } + mutex_lock(&tfw->lock); + if (tfw->saved_string) + devm_kfree(fwdev, tfw->saved_string); + tfw->saved_string = NULL; + mutex_unlock(&tfw->lock); +} + +static ssize_t version_show(struct device *fwdev, + struct device_attribute *attr, char *buf) +{ + struct tegrafw_data *tfw = fwdev->platform_data; + char *version_string; + char reader_string[TFW_VERSION_MAX_SIZE]; + ssize_t r; + + mutex_lock(&tfw->lock); + if (tfw->saved_string) { + version_string = tfw->saved_string; + } else { + tfw->reader(fwdev->parent, + reader_string, sizeof(reader_string)); + version_string = reader_string; + if ((tfw->flags & TFW_DONT_CACHE) == 0) { + if (tfw->saved_string) + devm_kfree(fwdev, tfw->saved_string); + tfw->saved_string = devm_kstrdup(fwdev, + version_string, GFP_KERNEL); + } + } + r = snprintf(buf, PAGE_SIZE, "%s: %s\n", + dev_name(fwdev), version_string); + mutex_unlock(&tfw->lock); + return r; +} + +static int versions_show_one(struct device *fwdev, void *buf) +{ + if (version_show(fwdev, NULL, buf + strlen(buf)) > 0) + return 0; + else + return -ENOMEM; +} + +static ssize_t versions_show(struct class *cls, + struct class_attribute *attr, char *buf) +{ + buf[0] = '\0'; + class_for_each_device(cls, NULL, buf, versions_show_one); + return strlen(buf); +} + +static DEVICE_ATTR_RO(version); + +static struct attribute *tegrafw_attrs[] = { + &dev_attr_version.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(tegrafw); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +/* + * old kernels, deprecated + */ +static struct class_attribute tegrafw_class_attrs[] = { + __ATTR(versions, S_IRUGO, versions_show, NULL), + __ATTR_NULL, +}; +#else +static CLASS_ATTR_RO(versions); + +static struct attribute *tegrafw_class_attrs[] = { + &class_attr_versions.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(tegrafw_class); +#endif + +static void tegrafw_release(struct device *dev) +{ + kfree(dev); +} + +static bool tegrafw_class_registered; + +static struct class tegrafw_class = { + .name = "tegra-firmware", + .owner = THIS_MODULE, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) + /* old kernels... */ + .class_attrs = tegrafw_class_attrs, +#else + .class_groups = tegrafw_class_groups, +#endif +}; + + +/** + * tegrafw_register - register the firmware object + * + * @name: the firmware symbolic name, will be created under tegra-firmwares + * @flags: only %TFW_DONT_CACHE is supported for now - always call @reader + * @reader: function to print version info + * @string: predefined string to return as a version + * + * Return value: error code if IS_ERR(), otherwise valid firmware object. + * Needs to be deallocated with call to tegrafw_unregister() + */ +struct device *tegrafw_register(const char *name, + const u32 flags, + ssize_t (*reader)(struct device *, char *, size_t), + const char *string) +{ + struct device *fwdev = NULL; + int err; + struct tegrafw_data *tfw; + + if (!name) { + pr_err("%s: attempt to register unnamed firmware", + __func__); + err = -EINVAL; + goto out; + } + if ((flags & TFW_DONT_CACHE) && !reader) { + pr_err("%s: don't cache and no reader?\n", + name); + err = -EINVAL; + goto out; + } + if (reader && string) + pr_info("%s: string will be used instead of calls to reader", + name); + + fwdev = kzalloc(sizeof(*fwdev) + sizeof(*tfw), GFP_KERNEL); + if (!fwdev) { + err = -ENOMEM; + goto out; + } + + if (!tegrafw_class_registered) { + class_register(&tegrafw_class); + tegrafw_class_registered = true; + } + + device_initialize(fwdev); + dev_set_name(fwdev, "%s", name); + tfw = (struct tegrafw_data *)(fwdev + 1); + mutex_init(&tfw->lock); + tfw->flags = flags; + tfw->reader = reader; + tfw->saved_string = string ? + devm_kstrdup(fwdev, string, GFP_KERNEL) : NULL; + fwdev->platform_data = tfw; + fwdev->release = tegrafw_release; + fwdev->class = &tegrafw_class; + fwdev->groups = tegrafw_groups; + + err = device_add(fwdev); + if (err < 0) + goto out; + + return fwdev; +out: + put_device(fwdev); + return ERR_PTR(err); +} +EXPORT_SYMBOL(tegrafw_register); + +/** + * tegrafw_unregister - release the firmware object + * + * @dev: device object returned by previous call to tegrafw_register() + */ +void tegrafw_unregister(struct device *fwdev) +{ + if (!IS_ERR_OR_NULL(fwdev)) + device_unregister(fwdev); +} +EXPORT_SYMBOL(tegrafw_unregister); + +/* + * devm_tegrafw_match - used by the devres framework to search devres + * that matches the data + * For tegrafw devres, resource holds the pointer to the data, so + * simple *res = data will work. + * Neither res nor *res should be NULL, in this case we return 0 to indicate + * mismatch + */ +static int devm_tegrafw_match(struct device *dev, void *res, void *data) +{ + struct device **p = res; + + if (WARN_ON(!p || !*p)) + return 0; + return *p == data; +} + +static void devm_tegrafw_release(struct device *dev, void *res) +{ + tegrafw_unregister(*(struct device **)res); +} + +/** + * devm_tegrafw_regsiter - register "managed" firmware + * + * @dev: owner of the firmware + * @name: symbolic name. If %NULL, device name will be used + * @flags: only %TFW_DONT_CACHE is supported right now - always call @reader + * @reader: function to print version info + * @string: predefined string to return as a version + * + * Return: struct device or appropriate error code wrapped in ERR_PTR() + */ +struct device *devm_tegrafw_register(struct device *dev, const char *name, + const u32 flags, + ssize_t (*reader)(struct device *, char *, size_t), + const char *string) +{ + struct device **ptr, *fwdev; + + if (dev == NULL) + return tegrafw_register(name, flags, reader, string); + + ptr = devres_alloc(devm_tegrafw_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + fwdev = tegrafw_register(name ?: dev_name(dev), flags, reader, string); + if (!IS_ERR(fwdev)) { + *ptr = fwdev; + devres_add(dev, ptr); + /* set device parent */ + device_move(fwdev, dev, DPM_ORDER_NONE); + } else { + devres_free(ptr); + } + return fwdev; +} +EXPORT_SYMBOL(devm_tegrafw_register); + +/** + * devm_tegrafw_unregister - unregister "managed" firmware + * @dev: owner of the firmware + * @fwdev: object returned by devm_tegrafw_register() + * + * One usually does not need it, as unregistering will take place automatically + */ +void devm_tegrafw_unregister(struct device *dev, struct device *fwdev) +{ + WARN_ON(devres_release(dev, devm_tegrafw_release, + devm_tegrafw_match, fwdev)); +} +EXPORT_SYMBOL(devm_tegrafw_unregister); + +struct device *devm_tegrafw_register_dt_string(struct device *dev, + const char *name, + const char *path, + const char *property) +{ + struct device_node *dn; + const char *data; + + dn = of_find_node_by_path(path); + if (!dn) + return ERR_PTR(-ENODEV); + if (of_property_read_string(dn, property, &data)) + return ERR_PTR(-ENOENT); + of_node_put(dn); + return devm_tegrafw_register(dev, name, TFW_NORMAL, NULL, data); +} +EXPORT_SYMBOL(devm_tegrafw_register_dt_string); + +static void tegrafw_exit(void) +{ + if (tegrafw_class_registered) + class_unregister(&tegrafw_class); +} + +module_exit(tegrafw_exit); +MODULE_AUTHOR("dmitry pervushin "); +MODULE_DESCRIPTION("Tegra firmwares inventory"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:tegra-firmwares"); diff --git a/drivers/platform/tegra/firmwares-inventory.c b/drivers/platform/tegra/firmwares-inventory.c new file mode 100644 index 00000000..5f81d1e7 --- /dev/null +++ b/drivers/platform/tegra/firmwares-inventory.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, NVIDIA CORPORATION, All rights reserved. + +#include +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_TRUSTY) +#include + +static ssize_t tegrafw_read_trusty(struct device *dev, + char *data, size_t size) +{ + const struct of_device_id trusty_of_match[] = { + {.compatible = "android,trusty-smc-v1", }, + {}, + }; + struct device_node *dn; + struct platform_device *pdev; + + for_each_matching_node(dn, trusty_of_match) { + pdev = of_find_device_by_node(dn); + if (pdev == NULL) + continue; + return snprintf(data, size, "%s", + trusty_version_str_get(&pdev->dev)); + } + snprintf(data, size, "NULL"); + return 0; +} +#endif + +/* TBD: cpu_data is define in arch/arm64/kernel/cpuinfo.c + * It is not exported and hence defining locally for the + * build success. + * Denver FW need to be deprecated. + */ +DEFINE_PER_CPU(struct cpuinfo_arm64, cpu_data); +static ssize_t tegrafw_read_denver(struct device *dev, + char *version, size_t size) +{ + char *v = version; + int i; + size_t printed = 0; + + for_each_online_cpu(i) { + struct cpuinfo_arm64 *cpuinfo = &per_cpu(cpu_data, i); + u32 midr = cpuinfo->reg_midr; + u32 aidr; + + if (MIDR_IMPLEMENTOR(midr) == ARM_CPU_IMP_NVIDIA) { + asm volatile("mrs %0, AIDR_EL1" : "=r" (aidr) : ); + printed = snprintf(v, size, "CPU%d: %u(0x%x) ", + i, aidr, aidr); + size -= printed; + v += printed; + } + } + + return v - version; +} + +#define FIRMWARES_SIZE 10 +static struct device *firmwares[FIRMWARES_SIZE]; +#define check_out_of_bounds(dev, err) \ + if ((dev) - firmwares >= ARRAY_SIZE(firmwares)) { \ + pr_err("Cannot register 'legacy' firmware info: increase " \ + "firmwares array size"); \ + return (err); \ + } + +static int __init tegra_firmwares_init(void) +{ + struct device **dev = firmwares; + char *versions[] = { "mb1", "mb2", "mb1-bct", "qb", "osl" }; + int v; + + check_out_of_bounds(dev, 0); + *dev++ = tegrafw_register("MTS", TFW_NORMAL, tegrafw_read_denver, NULL); + +#if IS_ENABLED(CONFIG_TRUSTY) + check_out_of_bounds(dev, 0); + *dev++ = tegrafw_register("trusty", TFW_DONT_CACHE, + tegrafw_read_trusty, NULL); +#endif + + for (v = 0; v < ARRAY_SIZE(versions); v++) { + check_out_of_bounds(dev, 0); + *dev++ = tegrafw_register_dt_string(versions[v], + "/tegra-firmwares", versions[v]); + } + return 0; +} + +static void __exit tegra_firmwares_exit(void) +{ + struct device **dev = firmwares; + + while (dev - firmwares < ARRAY_SIZE(firmwares)) + tegrafw_unregister(*dev++); +} +module_init(tegra_firmwares_init); +module_exit(tegra_firmwares_exit); + +MODULE_DESCRIPTION("Firmware info drivers"); +MODULE_AUTHOR("dmitry pervushin "); +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/tegra-firmwares.h b/include/linux/tegra-firmwares.h new file mode 100644 index 00000000..28310960 --- /dev/null +++ b/include/linux/tegra-firmwares.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, NVIDIA CORPORATION, All rights reserved. + +#ifndef __TEGRA_FIRMWARES_H +#define __TEGRA_FIRMWARES_H + +/* + * max size of version string + */ +#define TFW_VERSION_MAX_SIZE 256 + +struct device *tegrafw_register(const char *name, + const u32 flags, + ssize_t (*reader)(struct device *dev, char *, size_t), + const char *string); +void tegrafw_unregister(struct device *fwdev); +struct device *devm_tegrafw_register(struct device *dev, + const char *name, + const u32 flags, + ssize_t (*reader)(struct device *dev, char *, size_t), + const char *string); +void devm_tegrafw_unregister(struct device *dev, struct device *fwdev); +void tegrafw_invalidate(struct device *fwdev); +struct device *devm_tegrafw_register_dt_string(struct device *dev, + const char *name, + const char *path, + const char *property); +enum { + TFW_NORMAL = 0x0000, + TFW_DONT_CACHE = 0x0001, + TFW_MAX = 0xFFFF, +}; + +static inline struct device *tegrafw_register_string(const char *name, + const char *string) +{ + return tegrafw_register(name, TFW_NORMAL, NULL, string); +} + +static inline struct device *devm_tegrafw_register_string(struct device *dev, + const char *name, + const char *string) +{ + return devm_tegrafw_register(dev, name, TFW_NORMAL, NULL, string); +} + +static inline struct device *tegrafw_register_dt_string(const char *name, + const char *path, + const char *property) +{ + return devm_tegrafw_register_dt_string(NULL, name, path, property); +} + +#endif /* __TEGRA_FIRMWARES_CLASS */