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)
brcmf_fwvid_request_module(enum brcmf_fwvendor fwvid)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
brcmf_fwvid_register_vendor(enum brcmf_fwvendor fwvid,struct module * vmod,const struct brcmf_fwvid_ops * vops)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 WARN_ON(!vops->alloc_fweh_info))
94 return -EINVAL;
95
96 if (WARN_ON(fwvid_list[fwvid].vmod))
97 return -EEXIST;
98
99 brcmf_dbg(TRACE, "mod=%s: enter\n", fwvid_list[fwvid].name);
100
101 mutex_lock(&fwvid_list_lock);
102
103 fwvid_list[fwvid].vmod = vmod;
104 fwvid_list[fwvid].vops = vops;
105
106 mutex_unlock(&fwvid_list_lock);
107
108 complete_all(&fwvid_list[fwvid].reg_done);
109
110 return 0;
111 }
112 BRCMF_EXPORT_SYMBOL_GPL(brcmf_fwvid_register_vendor);
113
brcmf_fwvid_unregister_vendor(enum brcmf_fwvendor fwvid,struct module * mod)114 int brcmf_fwvid_unregister_vendor(enum brcmf_fwvendor fwvid, struct module *mod)
115 {
116 struct brcmf_bus *bus, *tmp;
117
118 if (fwvid >= BRCMF_FWVENDOR_NUM)
119 return -ERANGE;
120
121 if (WARN_ON(fwvid_list[fwvid].vmod != mod))
122 return -ENOENT;
123
124 mutex_lock(&fwvid_list_lock);
125
126 list_for_each_entry_safe(bus, tmp, &fwvid_list[fwvid].drvr_list, list) {
127 mutex_unlock(&fwvid_list_lock);
128
129 brcmf_dbg(INFO, "mod=%s: removing %s\n", fwvid_list[fwvid].name,
130 dev_name(bus->dev));
131 brcmf_bus_remove(bus);
132
133 mutex_lock(&fwvid_list_lock);
134 }
135
136 fwvid_list[fwvid].vmod = NULL;
137 fwvid_list[fwvid].vops = NULL;
138 reinit_completion(&fwvid_list[fwvid].reg_done);
139
140 brcmf_dbg(TRACE, "mod=%s: exit\n", fwvid_list[fwvid].name);
141 mutex_unlock(&fwvid_list_lock);
142
143 return 0;
144 }
145 BRCMF_EXPORT_SYMBOL_GPL(brcmf_fwvid_unregister_vendor);
146 #else
brcmf_fwvid_request_module(enum brcmf_fwvendor fwvid)147 static inline int brcmf_fwvid_request_module(enum brcmf_fwvendor fwvid)
148 {
149 return 0;
150 }
151 #endif
152
brcmf_fwvid_attach(struct brcmf_pub * drvr)153 int brcmf_fwvid_attach(struct brcmf_pub *drvr)
154 {
155 enum brcmf_fwvendor fwvid = drvr->bus_if->fwvid;
156 int ret;
157
158 if (fwvid >= ARRAY_SIZE(fwvid_list))
159 return -ERANGE;
160
161 brcmf_dbg(TRACE, "mod=%s: enter: dev %s\n", fwvid_list[fwvid].name,
162 dev_name(drvr->bus_if->dev));
163
164 mutex_lock(&fwvid_list_lock);
165
166 ret = brcmf_fwvid_request_module(fwvid);
167 if (ret)
168 return ret;
169
170 drvr->vops = fwvid_list[fwvid].vops;
171 list_add(&drvr->bus_if->list, &fwvid_list[fwvid].drvr_list);
172
173 mutex_unlock(&fwvid_list_lock);
174
175 return ret;
176 }
177
brcmf_fwvid_detach(struct brcmf_pub * drvr)178 void brcmf_fwvid_detach(struct brcmf_pub *drvr)
179 {
180 enum brcmf_fwvendor fwvid = drvr->bus_if->fwvid;
181
182 if (fwvid >= ARRAY_SIZE(fwvid_list))
183 return;
184
185 brcmf_dbg(TRACE, "mod=%s: enter: dev %s\n", fwvid_list[fwvid].name,
186 dev_name(drvr->bus_if->dev));
187
188 mutex_lock(&fwvid_list_lock);
189
190 if (drvr->vops) {
191 drvr->vops = NULL;
192 list_del(&drvr->bus_if->list);
193 }
194 mutex_unlock(&fwvid_list_lock);
195 }
196
brcmf_fwvid_vendor_name(struct brcmf_pub * drvr)197 const char *brcmf_fwvid_vendor_name(struct brcmf_pub *drvr)
198 {
199 return fwvid_list[drvr->bus_if->fwvid].name;
200 }
201