summaryrefslogtreecommitdiff
path: root/drivers/of/module.c
blob: 780fd82a7ecc58c796fccba37883aa938481eb69 (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
// SPDX-License-Identifier: GPL-2.0
/*
 * Linux kernel module helpers.
 */

#include <linux/of.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>

ssize_t of_modalias(const struct device_node *np, char *str, ssize_t len)
{
	const char *compat;
	char *c;
	struct property *p;
	ssize_t csize;
	ssize_t tsize;

	/*
	 * Prevent a kernel oops in vsnprintf() -- it only allows passing a
	 * NULL ptr when the length is also 0. Also filter out the negative
	 * lengths...
	 */
	if ((len > 0 && !str) || len < 0)
		return -EINVAL;

	/* Name & Type */
	/* %p eats all alphanum characters, so %c must be used here */
	csize = snprintf(str, len, "of:N%pOFn%c%s", np, 'T',
			 of_node_get_device_type(np));
	tsize = csize;
	if (csize >= len)
		csize = len > 0 ? len - 1 : 0;
	len -= csize;
	str += csize;

	of_property_for_each_string(np, "compatible", p, compat) {
		csize = strlen(compat) + 1;
		tsize += csize;
		if (csize >= len)
			continue;

		csize = snprintf(str, len, "C%s", compat);
		for (c = str; c; ) {
			c = strchr(c, ' ');
			if (c)
				*c++ = '_';
		}
		len -= csize;
		str += csize;
	}

	return tsize;
}

int of_request_module(const struct device_node *np)
{
	char *str;
	ssize_t size;
	int ret;

	if (!np)
		return -ENODEV;

	size = of_modalias(np, NULL, 0);
	if (size < 0)
		return size;

	/* Reserve an additional byte for the trailing '\0' */
	size++;

	str = kmalloc(size, GFP_KERNEL);
	if (!str)
		return -ENOMEM;

	of_modalias(np, str, size);
	str[size - 1] = '\0';
	ret = request_module(str);
	kfree(str);

	return ret;
}
EXPORT_SYMBOL_GPL(of_request_module);