summaryrefslogtreecommitdiffstats
path: root/drivers/of/of_firmware.c
blob: 80feb3b90dbac34ffb48abc759f179c6758630c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de>
 */
#include <common.h>
#include <firmware.h>
#include <of.h>

static struct firmware_mgr *of_node_get_mgr(struct device_node *np)
{
	struct device_node *mgr_node;

	do {
		mgr_node = of_parse_phandle(np, "fpga-mgr", 0);
		if (mgr_node)
			return firmwaremgr_find_by_node(mgr_node);
	} while ((np = of_get_parent(np)) != NULL);

	return NULL;
}

struct fw_load_entry {
	struct firmware_mgr *mgr;
	char *firmware;
	struct list_head list;
};

static LIST_HEAD(fw_load_list);

static int load_firmware(struct device_node *target,
			 struct device_node *fragment, void *unused)
{
	const char *firmware_name;
	int err;
	struct firmware_mgr *mgr;
	struct fw_load_entry *fle;

	err = of_property_read_string(fragment,
				      "firmware-name", &firmware_name);
	/* Nothing to do if property does not exist. */
	if (err == -EINVAL)
		return 0;
	else if (err)
		return -EINVAL;

	if (!target)
		return -EINVAL;

	if (!of_device_is_compatible(target, "fpga-region"))
		return 0;

	mgr = of_node_get_mgr(target);
	if (!mgr)
		return -EINVAL;

	fle = xzalloc(sizeof(*fle));
	fle->mgr = mgr;
	fle->firmware = xstrdup(firmware_name);

	list_add_tail(&fle->list, &fw_load_list);

	return 0;
}

/*
 * The dt overlay API says that a "firmware-name" property found in an overlay
 * node compatible to "fpga-region" triggers loading of the firmware with the
 * name given in the "firmware-name" property.
 *
 * barebox applies overlays to the Kernel device tree as part of booting the
 * Kernel. When a firmware is needed for an overlay then it shall be loaded,
 * so that the Kernel already finds the firmware loaded.
 * 
 * In barebox overlays are applied as a of_fixup to the desired tree. The fixups
 * might be executed multiple times not only as part of booting the Kernel, but
 * also during of_diff command execution and other actions. It's not desired
 * that we (re-)load all firmwares each time this happens, so the process is
 * splitted up. During application of an overlay the needed firmwares are only
 * collected to a list, but not actually loaded. Only once it's clear we want to
 * boot with that device tree the firmwares are loaded by explicitly calling
 * of_overlay_load_firmware().
 */

/**
 * of_overlay_pre_load_firmware() - check overlay node for firmware to load
 * @root: The device tree to apply the overlay to
 * @overlay: The overlay
 *
 * This function checks the given overlay for firmware to load. If a firmware
 * is needed then it is not directly loaded, but instead added to a list of
 * firmware to be loaded. The firmware files on this list can then be loaded
 * with of_overlay_load_firmware().
 *
 * Return: 0 for success or negative error code otherwise
 */
int of_overlay_pre_load_firmware(struct device_node *root, struct device_node *overlay)
{
	return of_process_overlay(root, overlay, load_firmware, NULL);
}

/**
 * of_overlay_load_firmware() - load all firmware files
 *
 * This function loads all firmware files previously collected in
 * of_overlay_pre_load_firmware().
 *
 * Return: 0 when all firmware files could be loaded, negative error code
 * otherwise.
 */
int of_overlay_load_firmware(void)
{
	struct fw_load_entry *fle;
	int ret;

	list_for_each_entry(fle, &fw_load_list, list) {
		ret = firmwaremgr_load_file(fle->mgr, fle->firmware);
		if (ret)
			return ret;
	}

	return 0;
}

/**
 * of_overlay_load_firmware_clear() - Clear list of firmware files
 *
 * This function clears the list of firmware files.
 */
void of_overlay_load_firmware_clear(void)
{
	struct fw_load_entry *fle, *tmp;

	list_for_each_entry_safe(fle, tmp, &fw_load_list, list) {
		list_del(&fle->list);
		free(fle->firmware);
		free(fle);
	}
}