1 // SPDX-License-Identifier: ISC
2 /*
3  * Copyright (c) 2022 Broadcom Corporation
4  */
5 #include <linux/errno.h>
6 #include <linux/export.h>
7 #include <linux/module.h>
8 #include <linux/kmod.h>
9 #include <linux/list.h>
10 #include <linux/completion.h>
11 #include <linux/mutex.h>
12 #include <linux/printk.h>
13 #include <linux/jiffies.h>
14 #include <linux/workqueue.h>
15 
16 #include "core.h"
17 #include "bus.h"
18 #include "debug.h"
19 #include "fwvid.h"
20 
21 #include "wcc/vops.h"
22 #include "cyw/vops.h"
23 #include "bca/vops.h"
24 
25 struct brcmf_fwvid_entry {
26 	const char *name;
27 	const struct brcmf_fwvid_ops *vops;
28 	struct list_head drvr_list;
29 #if IS_MODULE(CONFIG_BRCMFMAC)
30 	struct module *vmod;
31 	struct completion reg_done;
32 #endif
33 };
34 
35 static DEFINE_MUTEX(fwvid_list_lock);
36 
37 #if IS_MODULE(CONFIG_BRCMFMAC)
38 #define FWVID_ENTRY_INIT(_vid, _name) \
39 	[BRCMF_FWVENDOR_ ## _vid] = { \
40 		.name = #_name, \
41 		.reg_done = COMPLETION_INITIALIZER(fwvid_list[BRCMF_FWVENDOR_ ## _vid].reg_done), \
42 		.drvr_list = LIST_HEAD_INIT(fwvid_list[BRCMF_FWVENDOR_ ## _vid].drvr_list), \
43 	}
44 #else
45 #define FWVID_ENTRY_INIT(_vid, _name) \
46 	[BRCMF_FWVENDOR_ ## _vid] = { \
47 		.name = #_name, \
48 		.drvr_list = LIST_HEAD_INIT(fwvid_list[BRCMF_FWVENDOR_ ## _vid].drvr_list), \
49 		.vops = _vid ## _VOPS \
50 	}
51 #endif /* IS_MODULE(CONFIG_BRCMFMAC) */
52 
53 static struct brcmf_fwvid_entry fwvid_list[BRCMF_FWVENDOR_NUM] = {
54 	FWVID_ENTRY_INIT(WCC, wcc),
55 	FWVID_ENTRY_INIT(CYW, cyw),
56 	FWVID_ENTRY_INIT(BCA, bca),
57 };
58 
59 #if IS_MODULE(CONFIG_BRCMFMAC)
60 static int brcmf_fwvid_request_module(enum brcmf_fwvendor fwvid)
61 {
62 	int ret;
63 
64 	if (!fwvid_list[fwvid].vmod) {
65 		struct completion *reg_done = &fwvid_list[fwvid].reg_done;
66 
67 		mutex_unlock(&fwvid_list_lock);
68 
69 		ret = request_module("brcmfmac-%s", fwvid_list[fwvid].name);
70 		if (ret)
71 			goto fail;
72 
73 		ret = wait_for_completion_interruptible(reg_done);
74 		if (ret)
75 			goto fail;
76 
77 		mutex_lock(&fwvid_list_lock);
78 	}
79 	return 0;
80 
81 fail:
82 	brcmf_err("mod=%s: failed %d\n", fwvid_list[fwvid].name, ret);
83 	return ret;
84 }
85 
86 int brcmf_fwvid_register_vendor(enum brcmf_fwvendor fwvid, struct module *vmod,
87 				const struct brcmf_fwvid_ops *vops)
88 {
89 	if (fwvid >= BRCMF_FWVENDOR_NUM)
90 		return -ERANGE;
91 
92 	if (WARN_ON(!vmod) || WARN_ON(!vops))
93 		return -EINVAL;
94 
95 	if (WARN_ON(fwvid_list[fwvid].vmod))
96 		return -EEXIST;
97 
98 	brcmf_dbg(TRACE, "mod=%s: enter\n", fwvid_list[fwvid].name);
99 
100 	mutex_lock(&fwvid_list_lock);
101 
102 	fwvid_list[fwvid].vmod = vmod;
103 	fwvid_list[fwvid].vops = vops;
104 
105 	mutex_unlock(&fwvid_list_lock);
106 
107 	complete_all(&fwvid_list[fwvid].reg_done);
108 
109 	return 0;
110 }
111 BRCMF_EXPORT_SYMBOL_GPL(brcmf_fwvid_register_vendor);
112 
113 int brcmf_fwvid_unregister_vendor(enum brcmf_fwvendor fwvid, struct module *mod)
114 {
115 	struct brcmf_bus *bus, *tmp;
116 
117 	if (fwvid >= BRCMF_FWVENDOR_NUM)
118 		return -ERANGE;
119 
120 	if (WARN_ON(fwvid_list[fwvid].vmod != mod))
121 		return -ENOENT;
122 
123 	mutex_lock(&fwvid_list_lock);
124 
125 	list_for_each_entry_safe(bus, tmp, &fwvid_list[fwvid].drvr_list, list) {
126 		mutex_unlock(&fwvid_list_lock);
127 
128 		brcmf_dbg(INFO, "mod=%s: removing %s\n", fwvid_list[fwvid].name,
129 			  dev_name(bus->dev));
130 		brcmf_bus_remove(bus);
131 
132 		mutex_lock(&fwvid_list_lock);
133 	}
134 
135 	fwvid_list[fwvid].vmod = NULL;
136 	fwvid_list[fwvid].vops = NULL;
137 	reinit_completion(&fwvid_list[fwvid].reg_done);
138 
139 	brcmf_dbg(TRACE, "mod=%s: exit\n", fwvid_list[fwvid].name);
140 	mutex_unlock(&fwvid_list_lock);
141 
142 	return 0;
143 }
144 BRCMF_EXPORT_SYMBOL_GPL(brcmf_fwvid_unregister_vendor);
145 #else
146 static inline int brcmf_fwvid_request_module(enum brcmf_fwvendor fwvid)
147 {
148 	return 0;
149 }
150 #endif
151 
152 int brcmf_fwvid_attach(struct brcmf_pub *drvr)
153 {
154 	enum brcmf_fwvendor fwvid = drvr->bus_if->fwvid;
155 	int ret;
156 
157 	if (fwvid >= ARRAY_SIZE(fwvid_list))
158 		return -ERANGE;
159 
160 	brcmf_dbg(TRACE, "mod=%s: enter: dev %s\n", fwvid_list[fwvid].name,
161 		  dev_name(drvr->bus_if->dev));
162 
163 	mutex_lock(&fwvid_list_lock);
164 
165 	ret = brcmf_fwvid_request_module(fwvid);
166 	if (ret)
167 		return ret;
168 
169 	drvr->vops = fwvid_list[fwvid].vops;
170 	list_add(&drvr->bus_if->list, &fwvid_list[fwvid].drvr_list);
171 
172 	mutex_unlock(&fwvid_list_lock);
173 
174 	return ret;
175 }
176 
177 void brcmf_fwvid_detach(struct brcmf_pub *drvr)
178 {
179 	enum brcmf_fwvendor fwvid = drvr->bus_if->fwvid;
180 
181 	if (fwvid >= ARRAY_SIZE(fwvid_list))
182 		return;
183 
184 	brcmf_dbg(TRACE, "mod=%s: enter: dev %s\n", fwvid_list[fwvid].name,
185 		  dev_name(drvr->bus_if->dev));
186 
187 	mutex_lock(&fwvid_list_lock);
188 
189 	if (drvr->vops) {
190 		drvr->vops = NULL;
191 		list_del(&drvr->bus_if->list);
192 	}
193 	mutex_unlock(&fwvid_list_lock);
194 }
195 
196 const char *brcmf_fwvid_vendor_name(struct brcmf_pub *drvr)
197 {
198 	return fwvid_list[drvr->bus_if->fwvid].name;
199 }
200