aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware')
-rw-r--r--drivers/firmware/Kconfig9
-rw-r--r--drivers/firmware/Makefile1
-rw-r--r--drivers/firmware/psci/psci.c9
-rw-r--r--drivers/firmware/raspberrypi.c149
-rw-r--r--drivers/firmware/rp1.c308
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");