diff options
Diffstat (limited to 'drivers/firmware')
| -rw-r--r-- | drivers/firmware/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/firmware/Makefile | 1 | ||||
| -rw-r--r-- | drivers/firmware/psci/psci.c | 9 | ||||
| -rw-r--r-- | drivers/firmware/raspberrypi.c | 149 | ||||
| -rw-r--r-- | drivers/firmware/rp1.c | 308 |
5 files changed, 473 insertions, 3 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index bbd2155d8483..37359a5dedc2 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -119,6 +119,15 @@ config RASPBERRYPI_FIRMWARE This option enables support for communicating with the firmware on the Raspberry Pi. +config FIRMWARE_RP1 + tristate "RP1 Firmware Driver" + depends on MBOX_RP1 + help + The Raspberry Pi RP1 processor presents a firmware + interface using shared memory and a mailbox. To enable + the driver that communicates with it, say Y. Otherwise, + say N. + config FW_CFG_SYSFS tristate "QEMU fw_cfg device support in sysfs" depends on SYSFS && (ARM || ARM64 || PARISC || PPC_PMAC || RISCV || SPARC || X86) diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 4ddec2820c96..3d3fc417e497 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o obj-$(CONFIG_MTK_ADSP_IPC) += mtk-adsp-ipc.o obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o +obj-$(CONFIG_FIRMWARE_RP1) += rp1.o obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o obj-$(CONFIG_SYSFB) += sysfb.o obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c index 38ca190d4a22..386a9647e8c0 100644 --- a/drivers/firmware/psci/psci.c +++ b/drivers/firmware/psci/psci.c @@ -316,7 +316,14 @@ static int psci_sys_reset(struct notifier_block *nb, unsigned long action, * reset_type[30:0] = 0 (SYSTEM_WARM_RESET) * cookie = 0 (ignored by the implementation) */ - invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, 0, 0); + // Allow extra arguments separated by spaces after + // the partition number. + unsigned long val; + u8 partition = 0; + + if (data && sscanf(data, "%lu", &val) == 1 && val < 63) + partition = val; + invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, partition, 0); } else { invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); } diff --git a/drivers/firmware/raspberrypi.c b/drivers/firmware/raspberrypi.c index 7ecde6921a0a..c8e8df969867 100644 --- a/drivers/firmware/raspberrypi.c +++ b/drivers/firmware/raspberrypi.c @@ -14,6 +14,7 @@ #include <linux/of.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/reboot.h> #include <linux/slab.h> #include <soc/bcm2835/raspberrypi-firmware.h> @@ -32,8 +33,11 @@ struct rpi_firmware { u32 enabled; struct kref consumers; + u32 get_throttled; }; +static struct platform_device *g_pdev; + static DEFINE_MUTEX(transaction_lock); static void response_callback(struct mbox_client *cl, void *msg) @@ -176,15 +180,92 @@ int rpi_firmware_property(struct rpi_firmware *fw, kfree(data); + if ((tag == RPI_FIRMWARE_GET_THROTTLED) && + memcmp(&fw->get_throttled, tag_data, sizeof(fw->get_throttled))) { + memcpy(&fw->get_throttled, tag_data, sizeof(fw->get_throttled)); + sysfs_notify(&fw->cl.dev->kobj, NULL, "get_throttled"); + } + return ret; } EXPORT_SYMBOL_GPL(rpi_firmware_property); +static int rpi_firmware_notify_reboot(struct notifier_block *nb, + unsigned long action, + void *data) +{ + struct rpi_firmware *fw; + struct platform_device *pdev = g_pdev; + u32 reboot_flags = 0; + + if (!pdev) + return 0; + + fw = platform_get_drvdata(pdev); + if (!fw) + return 0; + + // The partition id is the first parameter followed by zero or + // more flags separated by spaces indicating the reason for the reboot. + // + // 'tryboot': Sets a one-shot flag which is cleared upon reboot and + // causes the tryboot.txt to be loaded instead of config.txt + // by the bootloader and the start.elf firmware. + // + // This is intended to allow automatic fallback to a known + // good image if an OS/FW upgrade fails. + // + // N.B. The firmware mechanism for storing reboot flags may vary + // on different Raspberry Pi models. + if (data && strstr(data, " tryboot")) + reboot_flags |= 0x1; + + // The mailbox might have been called earlier, directly via vcmailbox + // so only overwrite if reboot flags are passed to the reboot command. + if (reboot_flags) + (void)rpi_firmware_property(fw, RPI_FIRMWARE_SET_REBOOT_FLAGS, + &reboot_flags, sizeof(reboot_flags)); + + (void)rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_REBOOT, NULL, 0); + + return 0; +} + +static ssize_t get_throttled_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rpi_firmware *fw = dev_get_drvdata(dev); + + WARN_ONCE(1, "deprecated, use hwmon sysfs instead\n"); + + return sprintf(buf, "%x\n", fw->get_throttled); +} + +static DEVICE_ATTR_RO(get_throttled); + +static struct attribute *rpi_firmware_dev_attrs[] = { + &dev_attr_get_throttled.attr, + NULL, +}; + +static const struct attribute_group rpi_firmware_dev_group = { + .attrs = rpi_firmware_dev_attrs, +}; + static void rpi_firmware_print_firmware_revision(struct rpi_firmware *fw) { time64_t date_and_time; u32 packet; + static const char * const variant_strs[] = { + "unknown", + "start", + "start_x", + "start_db", + "start_cd", + }; + const char *variant_str = "cmd unsupported"; + u32 variant; int ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_FIRMWARE_REVISION, &packet, sizeof(packet)); @@ -194,7 +275,35 @@ rpi_firmware_print_firmware_revision(struct rpi_firmware *fw) /* This is not compatible with y2038 */ date_and_time = packet; - dev_info(fw->cl.dev, "Attached to firmware from %ptT\n", &date_and_time); + + ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_FIRMWARE_VARIANT, + &variant, sizeof(variant)); + + if (!ret) { + if (variant >= ARRAY_SIZE(variant_strs)) + variant = 0; + variant_str = variant_strs[variant]; + } + + dev_info(fw->cl.dev, + "Attached to firmware from %ptT, variant %s\n", + &date_and_time, variant_str); +} + +static void +rpi_firmware_print_firmware_hash(struct rpi_firmware *fw) +{ + u32 hash[5]; + int ret = rpi_firmware_property(fw, + RPI_FIRMWARE_GET_FIRMWARE_HASH, + hash, sizeof(hash)); + + if (ret) + return; + + dev_info(fw->cl.dev, + "Firmware hash is %08x%08x%08x%08x%08x\n", + hash[0], hash[1], hash[2], hash[3], hash[4]); } static void @@ -209,6 +318,11 @@ rpi_register_hwmon_driver(struct device *dev, struct rpi_firmware *fw) rpi_hwmon = platform_device_register_data(dev, "raspberrypi-hwmon", -1, NULL, 0); + + if (!IS_ERR_OR_NULL(rpi_hwmon)) { + if (devm_device_add_group(dev, &rpi_firmware_dev_group)) + dev_err(dev, "Failed to create get_trottled attr\n"); + } } static void rpi_register_clk_driver(struct device *dev) @@ -301,8 +415,10 @@ static int rpi_firmware_probe(struct platform_device *pdev) kref_init(&fw->consumers); platform_set_drvdata(pdev, fw); + g_pdev = pdev; rpi_firmware_print_firmware_revision(fw); + rpi_firmware_print_firmware_hash(fw); rpi_register_hwmon_driver(dev, fw); rpi_register_clk_driver(dev); @@ -329,6 +445,7 @@ static void rpi_firmware_remove(struct platform_device *pdev) rpi_clk = NULL; rpi_firmware_put(fw); + g_pdev = NULL; } static const struct of_device_id rpi_firmware_of_match[] = { @@ -408,7 +525,35 @@ static struct platform_driver rpi_firmware_driver = { .shutdown = rpi_firmware_shutdown, .remove = rpi_firmware_remove, }; -module_platform_driver(rpi_firmware_driver); + +static struct notifier_block rpi_firmware_reboot_notifier = { + .notifier_call = rpi_firmware_notify_reboot, +}; + +static int __init rpi_firmware_init(void) +{ + int ret = register_reboot_notifier(&rpi_firmware_reboot_notifier); + if (ret) + goto out1; + ret = platform_driver_register(&rpi_firmware_driver); + if (ret) + goto out2; + + return 0; + +out2: + unregister_reboot_notifier(&rpi_firmware_reboot_notifier); +out1: + return ret; +} +core_initcall(rpi_firmware_init); + +static void __init rpi_firmware_exit(void) +{ + platform_driver_unregister(&rpi_firmware_driver); + unregister_reboot_notifier(&rpi_firmware_reboot_notifier); +} +module_exit(rpi_firmware_exit); MODULE_AUTHOR("Eric Anholt <[email protected]>"); MODULE_DESCRIPTION("Raspberry Pi firmware driver"); diff --git a/drivers/firmware/rp1.c b/drivers/firmware/rp1.c new file mode 100644 index 000000000000..7afea388d7c5 --- /dev/null +++ b/drivers/firmware/rp1.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-24 Raspberry Pi Ltd. + * + * Parts of this driver are based on: + * - raspberrypi.c, by Eric Anholt <[email protected]> + * Copyright (C) 2015 Broadcom + */ + +#include <linux/dma-mapping.h> +#include <linux/kref.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/rp1-firmware.h> + +#define RP1_MAILBOX_FIRMWARE 0 + +enum rp1_firmware_ops { + MBOX_SUCCESS = 0x0000, + GET_FIRMWARE_VERSION = 0x0001, // na -> 160-bit version + GET_FEATURE = 0x0002, // FOURCC -> op base (0 == unsupported), op count + + COMMON_COUNT +}; + +struct rp1_firmware { + struct mbox_client cl; + struct mbox_chan *chan; /* The doorbell channel */ + uint32_t __iomem *buf; /* The shared buffer */ + u32 buf_size; /* The size of the shared buffer */ + struct completion c; + + struct kref consumers; +}; + +struct rp1_get_feature_resp { + uint32_t op_base; + uint32_t op_count; +}; + +static DEFINE_MUTEX(transaction_lock); + +static const struct of_device_id rp1_firmware_of_match[] = { + { .compatible = "raspberrypi,rp1-firmware", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rp1_firmware_of_match); + +static void response_callback(struct mbox_client *cl, void *msg) +{ + struct rp1_firmware *fw = container_of(cl, struct rp1_firmware, cl); + + complete(&fw->c); +} + +/* + * Sends a request to the RP1 firmware and synchronously waits for the reply. + * Returns zero or a positive count of response bytes on success, negative on + * error. + */ + +int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op, + const void *data, unsigned int data_len, + void *resp, unsigned int resp_space) +{ + int ret; + u32 rc; + + if (data_len + 4 > fw->buf_size) + return -EINVAL; + + mutex_lock(&transaction_lock); + + memcpy_toio(&fw->buf[1], data, data_len); + writel((op << 16) | data_len, fw->buf); + + reinit_completion(&fw->c); + ret = mbox_send_message(fw->chan, NULL); + if (ret >= 0) { + if (wait_for_completion_timeout(&fw->c, HZ)) + ret = 0; + else + ret = -ETIMEDOUT; + } else { + dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret); + } + + if (ret == 0) { + rc = readl(fw->buf); + if (rc & 0x80000000) { + ret = (int32_t)rc; + } else { + ret = min(rc, resp_space); + memcpy_fromio(resp, &fw->buf[1], ret); + } + } + + mutex_unlock(&transaction_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(rp1_firmware_message); + +static void rp1_firmware_delete(struct kref *kref) +{ + struct rp1_firmware *fw = container_of(kref, struct rp1_firmware, consumers); + + mbox_free_channel(fw->chan); + kfree(fw); +} + +void rp1_firmware_put(struct rp1_firmware *fw) +{ + kref_put(&fw->consumers, rp1_firmware_delete); +} +EXPORT_SYMBOL_GPL(rp1_firmware_put); + +int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc, + uint32_t *op_base, uint32_t *op_count) +{ + struct rp1_get_feature_resp resp; + int ret; + + memset(&resp, 0, sizeof(resp)); + ret = rp1_firmware_message(fw, GET_FEATURE, + &fourcc, sizeof(fourcc), + &resp, sizeof(resp)); + *op_base = resp.op_base; + *op_count = resp.op_count; + if (ret < 0) + return ret; + if (ret < sizeof(resp) || !resp.op_base) + return -EOPNOTSUPP; + return 0; +} +EXPORT_SYMBOL_GPL(rp1_firmware_get_feature); + +static void devm_rp1_firmware_put(void *data) +{ + struct rp1_firmware *fw = data; + + rp1_firmware_put(fw); +} + +/** + * rp1_firmware_get - Get pointer to rp1_firmware structure. + * + * The reference to rp1_firmware has to be released with rp1_firmware_put(). + * + * Returns an error pointer on failure. + */ +struct rp1_firmware *rp1_firmware_get(struct device_node *client) +{ + const char *match = rp1_firmware_of_match[0].compatible; + struct platform_device *pdev; + struct device_node *fwnode; + struct rp1_firmware *fw; + + if (!client) + return NULL; + fwnode = of_parse_phandle(client, "firmware", 0); + if (!fwnode) + return NULL; + if (!of_device_is_compatible(fwnode, match)) { + of_node_put(fwnode); + return NULL; + } + + pdev = of_find_device_by_node(fwnode); + of_node_put(fwnode); + + if (!pdev) + goto err_exit; + + fw = platform_get_drvdata(pdev); + if (!fw) + goto err_exit; + + if (!kref_get_unless_zero(&fw->consumers)) + goto err_exit; + + put_device(&pdev->dev); + + return fw; + +err_exit: + put_device(&pdev->dev); + return NULL; +} +EXPORT_SYMBOL_GPL(rp1_firmware_get); + +/** + * devm_rp1_firmware_get - Get pointer to rp1_firmware structure. + * @firmware_node: Pointer to the firmware Device Tree node. + * + * Returns NULL is the firmware device is not ready. + */ +struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *client) +{ + struct rp1_firmware *fw; + int ret; + + fw = rp1_firmware_get(client); + if (!fw) + return NULL; + + ret = devm_add_action_or_reset(dev, devm_rp1_firmware_put, fw); + if (ret) + return ERR_PTR(ret); + + return fw; +} +EXPORT_SYMBOL_GPL(devm_rp1_firmware_get); + +static int rp1_firmware_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *shmem; + struct rp1_firmware *fw; + struct resource res; + uint32_t version[5]; + int ret; + + shmem = of_parse_phandle(dev->of_node, "shmem", 0); + if (!of_device_is_compatible(shmem, "raspberrypi,rp1-shmem")) { + of_node_put(shmem); + return -ENXIO; + } + + ret = of_address_to_resource(shmem, 0, &res); + of_node_put(shmem); + if (ret) { + dev_err(dev, "failed to get shared memory (%pOF) - %d\n", shmem, ret); + return ret; + } + + /* + * Memory will be freed by rp1_firmware_delete() once all users have + * released their firmware handles. Don't use devm_kzalloc() here. + */ + fw = kzalloc(sizeof(*fw), GFP_KERNEL); + if (!fw) + return -ENOMEM; + + fw->buf_size = resource_size(&res); + fw->buf = devm_ioremap(dev, res.start, fw->buf_size); + if (!fw->buf) { + dev_err(dev, "failed to ioremap shared memory\n"); + kfree(fw); + return -EADDRNOTAVAIL; + } + + fw->cl.dev = dev; + fw->cl.rx_callback = response_callback; + fw->cl.tx_block = false; + + fw->chan = mbox_request_channel(&fw->cl, RP1_MAILBOX_FIRMWARE); + if (IS_ERR(fw->chan)) { + int ret = PTR_ERR(fw->chan); + + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get mbox channel: %d\n", ret); + kfree(fw); + return ret; + } + + init_completion(&fw->c); + kref_init(&fw->consumers); + + platform_set_drvdata(pdev, fw); + + ret = rp1_firmware_message(fw, GET_FIRMWARE_VERSION, + NULL, 0, &version, sizeof(version)); + if (ret == sizeof(version)) { + dev_info(dev, "RP1 Firmware version %08x%08x%08x%08x%08x\n", + version[0], version[1], version[2], version[3], version[4]); + ret = 0; + } else if (ret >= 0) { + ret = -EIO; + } + + return ret; +} + +static void rp1_firmware_remove(struct platform_device *pdev) +{ + struct rp1_firmware *fw = platform_get_drvdata(pdev); + + rp1_firmware_put(fw); +} + +static struct platform_driver rp1_firmware_driver = { + .driver = { + .name = "rp1-firmware", + .of_match_table = rp1_firmware_of_match, + }, + .probe = rp1_firmware_probe, + .remove = rp1_firmware_remove, +}; + +module_platform_driver(rp1_firmware_driver); + +MODULE_AUTHOR("Phil Elwell <[email protected]>"); +MODULE_DESCRIPTION("RP1 firmware driver"); +MODULE_LICENSE("GPL v2"); |
