Skip to content

Commit 3555574

Browse files
pelwellpopcornmix
authored andcommitted
firmware: Add an RP1 firmware interface over mbox
The RP1 firmware runs a simple communications channel over some shared memory and a mailbox. This driver provides access to that channel. Signed-off-by: Phil Elwell <[email protected]> firmware: rp1: Simplify rp1_firmware_get Simplify the implementation of rp1_firmware_get, requiring its clients to have a valid 'firmware' property. Also make it return NULL on error. Link: #6593 Signed-off-by: Phil Elwell <[email protected]> firmware: rp1: Linger on firmware failure To avoid pointless retries, let the probe function succeed if the firmware interface is configured correctly but the firmware is incompatible. The value of the private drvdata field holds the outcome. Link: #6642 Signed-off-by: Phil Elwell <[email protected]>
1 parent a9dc4da commit 3555574

File tree

4 files changed

+371
-0
lines changed

4 files changed

+371
-0
lines changed

Diff for: drivers/firmware/Kconfig

+9
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ config RASPBERRYPI_FIRMWARE
120120
This option enables support for communicating with the firmware on the
121121
Raspberry Pi.
122122

123+
config FIRMWARE_RP1
124+
tristate "RP1 Firmware Driver"
125+
depends on MBOX_RP1
126+
help
127+
The Raspberry Pi RP1 processor presents a firmware
128+
interface using shared memory and a mailbox. To enable
129+
the driver that communicates with it, say Y. Otherwise,
130+
say N.
131+
123132
config FW_CFG_SYSFS
124133
tristate "QEMU fw_cfg device support in sysfs"
125134
depends on SYSFS && (ARM || ARM64 || PARISC || PPC_PMAC || RISCV || SPARC || X86)

Diff for: drivers/firmware/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o
1515
obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o
1616
obj-$(CONFIG_MTK_ADSP_IPC) += mtk-adsp-ipc.o
1717
obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
18+
obj-$(CONFIG_FIRMWARE_RP1) += rp1.o
1819
obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
1920
obj-$(CONFIG_SYSFB) += sysfb.o
2021
obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o

Diff for: drivers/firmware/rp1.c

+308
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (C) 2023-24 Raspberry Pi Ltd.
4+
*
5+
* Parts of this driver are based on:
6+
* - raspberrypi.c, by Eric Anholt <[email protected]>
7+
* Copyright (C) 2015 Broadcom
8+
*/
9+
10+
#include <linux/dma-mapping.h>
11+
#include <linux/kref.h>
12+
#include <linux/mailbox_client.h>
13+
#include <linux/module.h>
14+
#include <linux/of_address.h>
15+
#include <linux/of_platform.h>
16+
#include <linux/platform_device.h>
17+
#include <linux/rp1-firmware.h>
18+
19+
#define RP1_MAILBOX_FIRMWARE 0
20+
21+
enum rp1_firmware_ops {
22+
MBOX_SUCCESS = 0x0000,
23+
GET_FIRMWARE_VERSION = 0x0001, // na -> 160-bit version
24+
GET_FEATURE = 0x0002, // FOURCC -> op base (0 == unsupported), op count
25+
26+
COMMON_COUNT
27+
};
28+
29+
struct rp1_firmware {
30+
struct mbox_client cl;
31+
struct mbox_chan *chan; /* The doorbell channel */
32+
uint32_t __iomem *buf; /* The shared buffer */
33+
u32 buf_size; /* The size of the shared buffer */
34+
struct completion c;
35+
36+
struct kref consumers;
37+
};
38+
39+
struct rp1_get_feature_resp {
40+
uint32_t op_base;
41+
uint32_t op_count;
42+
};
43+
44+
static DEFINE_MUTEX(transaction_lock);
45+
46+
static const struct of_device_id rp1_firmware_of_match[] = {
47+
{ .compatible = "raspberrypi,rp1-firmware", },
48+
{},
49+
};
50+
MODULE_DEVICE_TABLE(of, rp1_firmware_of_match);
51+
52+
static void response_callback(struct mbox_client *cl, void *msg)
53+
{
54+
struct rp1_firmware *fw = container_of(cl, struct rp1_firmware, cl);
55+
56+
complete(&fw->c);
57+
}
58+
59+
/*
60+
* Sends a request to the RP1 firmware and synchronously waits for the reply.
61+
* Returns zero or a positive count of response bytes on success, negative on
62+
* error.
63+
*/
64+
65+
int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
66+
const void *data, unsigned int data_len,
67+
void *resp, unsigned int resp_space)
68+
{
69+
int ret;
70+
u32 rc;
71+
72+
if (data_len + 4 > fw->buf_size)
73+
return -EINVAL;
74+
75+
mutex_lock(&transaction_lock);
76+
77+
memcpy_toio(&fw->buf[1], data, data_len);
78+
writel((op << 16) | data_len, fw->buf);
79+
80+
reinit_completion(&fw->c);
81+
ret = mbox_send_message(fw->chan, NULL);
82+
if (ret >= 0) {
83+
if (wait_for_completion_timeout(&fw->c, HZ))
84+
ret = 0;
85+
else
86+
ret = -ETIMEDOUT;
87+
} else {
88+
dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret);
89+
}
90+
91+
if (ret == 0) {
92+
rc = readl(fw->buf);
93+
if (rc & 0x80000000) {
94+
ret = (int32_t)rc;
95+
} else {
96+
ret = min(rc, resp_space);
97+
memcpy_fromio(resp, &fw->buf[1], ret);
98+
}
99+
}
100+
101+
mutex_unlock(&transaction_lock);
102+
103+
return ret;
104+
}
105+
EXPORT_SYMBOL_GPL(rp1_firmware_message);
106+
107+
static void rp1_firmware_delete(struct kref *kref)
108+
{
109+
struct rp1_firmware *fw = container_of(kref, struct rp1_firmware, consumers);
110+
111+
mbox_free_channel(fw->chan);
112+
kfree(fw);
113+
}
114+
115+
void rp1_firmware_put(struct rp1_firmware *fw)
116+
{
117+
if (!IS_ERR_OR_NULL(fw))
118+
kref_put(&fw->consumers, rp1_firmware_delete);
119+
}
120+
EXPORT_SYMBOL_GPL(rp1_firmware_put);
121+
122+
int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
123+
uint32_t *op_base, uint32_t *op_count)
124+
{
125+
struct rp1_get_feature_resp resp;
126+
int ret;
127+
128+
memset(&resp, 0, sizeof(resp));
129+
ret = rp1_firmware_message(fw, GET_FEATURE,
130+
&fourcc, sizeof(fourcc),
131+
&resp, sizeof(resp));
132+
*op_base = resp.op_base;
133+
*op_count = resp.op_count;
134+
if (ret < 0)
135+
return ret;
136+
if (ret < sizeof(resp) || !resp.op_base)
137+
return -EOPNOTSUPP;
138+
return 0;
139+
}
140+
EXPORT_SYMBOL_GPL(rp1_firmware_get_feature);
141+
142+
static void devm_rp1_firmware_put(void *data)
143+
{
144+
struct rp1_firmware *fw = data;
145+
146+
rp1_firmware_put(fw);
147+
}
148+
149+
/**
150+
* rp1_firmware_get - Get pointer to rp1_firmware structure.
151+
*
152+
* The reference to rp1_firmware has to be released with rp1_firmware_put().
153+
*
154+
* Returns an error pointer on failure.
155+
*/
156+
struct rp1_firmware *rp1_firmware_get(struct device_node *client)
157+
{
158+
const char *match = rp1_firmware_of_match[0].compatible;
159+
struct platform_device *pdev;
160+
struct device_node *fwnode;
161+
struct rp1_firmware *fw = NULL;
162+
163+
if (!client)
164+
return NULL;
165+
fwnode = of_parse_phandle(client, "firmware", 0);
166+
if (!fwnode)
167+
return NULL;
168+
if (!of_device_is_compatible(fwnode, match)) {
169+
of_node_put(fwnode);
170+
return ERR_PTR(-ENXIO);
171+
}
172+
173+
pdev = of_find_device_by_node(fwnode);
174+
of_node_put(fwnode);
175+
176+
if (!pdev)
177+
return ERR_PTR(-ENXIO);
178+
179+
fw = platform_get_drvdata(pdev);
180+
if (IS_ERR_OR_NULL(fw))
181+
goto err_exit;
182+
183+
if (!kref_get_unless_zero(&fw->consumers))
184+
goto err_exit;
185+
186+
put_device(&pdev->dev);
187+
188+
return fw;
189+
190+
err_exit:
191+
put_device(&pdev->dev);
192+
return fw;
193+
}
194+
EXPORT_SYMBOL_GPL(rp1_firmware_get);
195+
196+
/**
197+
* devm_rp1_firmware_get - Get pointer to rp1_firmware structure.
198+
* @firmware_node: Pointer to the firmware Device Tree node.
199+
*
200+
* Returns NULL is the firmware device is not ready.
201+
*/
202+
struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *client)
203+
{
204+
struct rp1_firmware *fw;
205+
int ret;
206+
207+
fw = rp1_firmware_get(client);
208+
if (IS_ERR_OR_NULL(fw))
209+
return fw;
210+
211+
ret = devm_add_action_or_reset(dev, devm_rp1_firmware_put, fw);
212+
if (ret)
213+
return ERR_PTR(ret);
214+
215+
return fw;
216+
}
217+
EXPORT_SYMBOL_GPL(devm_rp1_firmware_get);
218+
219+
static int rp1_firmware_probe(struct platform_device *pdev)
220+
{
221+
struct device *dev = &pdev->dev;
222+
struct device_node *shmem;
223+
struct rp1_firmware *fw;
224+
struct resource res;
225+
uint32_t version[5];
226+
int ret;
227+
228+
shmem = of_parse_phandle(dev->of_node, "shmem", 0);
229+
if (!of_device_is_compatible(shmem, "raspberrypi,rp1-shmem")) {
230+
of_node_put(shmem);
231+
return -ENXIO;
232+
}
233+
234+
ret = of_address_to_resource(shmem, 0, &res);
235+
of_node_put(shmem);
236+
if (ret) {
237+
dev_err(dev, "failed to get shared memory (%pOF) - %d\n", shmem, ret);
238+
return ret;
239+
}
240+
241+
/*
242+
* Memory will be freed by rp1_firmware_delete() once all users have
243+
* released their firmware handles. Don't use devm_kzalloc() here.
244+
*/
245+
fw = kzalloc(sizeof(*fw), GFP_KERNEL);
246+
if (!fw)
247+
return -ENOMEM;
248+
249+
fw->buf_size = resource_size(&res);
250+
fw->buf = devm_ioremap(dev, res.start, fw->buf_size);
251+
if (!fw->buf) {
252+
dev_err(dev, "failed to ioremap shared memory\n");
253+
kfree(fw);
254+
return -EADDRNOTAVAIL;
255+
}
256+
257+
fw->cl.dev = dev;
258+
fw->cl.rx_callback = response_callback;
259+
fw->cl.tx_block = false;
260+
261+
fw->chan = mbox_request_channel(&fw->cl, RP1_MAILBOX_FIRMWARE);
262+
if (IS_ERR(fw->chan)) {
263+
int ret = PTR_ERR(fw->chan);
264+
265+
if (ret != -EPROBE_DEFER)
266+
dev_err(dev, "Failed to get mbox channel: %d\n", ret);
267+
kfree(fw);
268+
return ret;
269+
}
270+
271+
init_completion(&fw->c);
272+
kref_init(&fw->consumers);
273+
274+
ret = rp1_firmware_message(fw, GET_FIRMWARE_VERSION,
275+
NULL, 0, &version, sizeof(version));
276+
if (ret == sizeof(version)) {
277+
dev_info(dev, "RP1 Firmware version %08x%08x%08x%08x%08x\n",
278+
version[0], version[1], version[2], version[3], version[4]);
279+
platform_set_drvdata(pdev, fw);
280+
} else {
281+
rp1_firmware_put(fw);
282+
platform_set_drvdata(pdev, ERR_PTR(-ENOENT));
283+
}
284+
285+
return 0;
286+
}
287+
288+
static void rp1_firmware_remove(struct platform_device *pdev)
289+
{
290+
struct rp1_firmware *fw = platform_get_drvdata(pdev);
291+
292+
rp1_firmware_put(fw);
293+
}
294+
295+
static struct platform_driver rp1_firmware_driver = {
296+
.driver = {
297+
.name = "rp1-firmware",
298+
.of_match_table = rp1_firmware_of_match,
299+
},
300+
.probe = rp1_firmware_probe,
301+
.remove = rp1_firmware_remove,
302+
};
303+
304+
module_platform_driver(rp1_firmware_driver);
305+
306+
MODULE_AUTHOR("Phil Elwell <[email protected]>");
307+
MODULE_DESCRIPTION("RP1 firmware driver");
308+
MODULE_LICENSE("GPL v2");

Diff for: include/linux/rp1-firmware.h

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
/*
3+
* Copyright (C) 2023 2023-2024 Raspberry Pi Ltd.
4+
*/
5+
6+
#ifndef __SOC_RP1_FIRMWARE_H__
7+
#define __SOC_RP1_FIRMWARE_H__
8+
9+
#include <linux/types.h>
10+
#include <linux/of_device.h>
11+
12+
#define RP1_FOURCC(s) ((uint32_t)((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0)))
13+
14+
struct rp1_firmware;
15+
16+
#if IS_ENABLED(CONFIG_FIRMWARE_RP1)
17+
int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
18+
const void *data, unsigned int data_len,
19+
void *resp, unsigned int resp_space);
20+
void rp1_firmware_put(struct rp1_firmware *fw);
21+
struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode);
22+
struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *fwnode);
23+
int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
24+
uint32_t *op_base, uint32_t *op_count);
25+
#else
26+
static inline int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
27+
const void *data, unsigned int data_len,
28+
void *resp, unsigned int resp_space)
29+
{
30+
return -EOPNOTSUPP;
31+
}
32+
33+
static inline void rp1_firmware_put(struct rp1_firmware *fw) { }
34+
35+
static inline struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode)
36+
{
37+
return NULL;
38+
}
39+
40+
static inline struct rp1_firmware *devm_rp1_firmware_get(struct device *dev,
41+
struct device_node *fwnode)
42+
{
43+
return NULL;
44+
}
45+
46+
static inline int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
47+
uint32_t *op_base, uint32_t *op_count)
48+
{
49+
return -EOPNOTSUPP;
50+
}
51+
#endif
52+
53+
#endif /* __SOC_RP1_FIRMWARE_H__ */

0 commit comments

Comments
 (0)