| // 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; |
| len -= csize; |
| if (str) |
| 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); |