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 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 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 147 static inline int brcmf_fwvid_request_module(enum brcmf_fwvendor fwvid) 148 { 149 return 0; 150 } 151 #endif 152 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 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 197 const char *brcmf_fwvid_vendor_name(struct brcmf_pub *drvr) 198 { 199 return fwvid_list[drvr->bus_if->fwvid].name; 200 } 201