1 /*- 2 * Broadcom NetXtreme-C/E network driver. 3 * 4 * Copyright (c) 2024 Broadcom, All Rights Reserved. 5 * The term Broadcom refers to Broadcom Limited and/or its subsidiaries 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS' 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 26 * THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <linux/device.h> 30 #include <linux/slab.h> 31 #include <linux/string.h> 32 #include <linux/types.h> 33 #include <linux/list.h> 34 #include <linux/delay.h> 35 36 #include "bnxt_auxbus_compat.h" 37 38 static struct list_head bnxt_aux_bus_dev_list = LINUX_LIST_HEAD_INIT(bnxt_aux_bus_dev_list); 39 static struct list_head bnxt_aux_bus_drv_list = LINUX_LIST_HEAD_INIT(bnxt_aux_bus_drv_list); 40 static DEFINE_MUTEX(bnxt_auxbus_lock); 41 42 static const struct auxiliary_device_id *auxiliary_match_id(const struct auxiliary_device_id *id, 43 const struct auxiliary_device *auxdev) 44 { 45 for (; id->name[0]; id++) { 46 const char *p = strrchr(dev_name(&auxdev->dev), '.'); 47 int match_size; 48 49 if (!p) 50 continue; 51 match_size = p - dev_name(&auxdev->dev); 52 53 if (strlen(id->name) == match_size && 54 !strncmp(dev_name(&auxdev->dev), id->name, match_size)) 55 return id; 56 } 57 return NULL; 58 } 59 60 int auxiliary_device_init(struct auxiliary_device *auxdev) 61 { 62 struct device *dev = &auxdev->dev; 63 char *modname = KBUILD_MODNAME; 64 int ret; 65 66 if (!dev->parent) { 67 pr_err("auxiliary_device has a NULL dev->parent\n"); 68 return -EINVAL; 69 } 70 71 if (!auxdev->name) { 72 pr_err("auxiliary_device has a NULL name\n"); 73 return -EINVAL; 74 } 75 76 ret = dev_set_name(dev, "%s.%s.%d", modname, auxdev->name, auxdev->id); 77 if (ret) { 78 dev_err(dev, "auxiliary device dev_set_name failed: %d\n", ret); 79 return ret; 80 } 81 82 return 0; 83 } 84 85 int auxiliary_device_add(struct auxiliary_device *auxdev) 86 { 87 const struct auxiliary_device_id *id; 88 struct auxiliary_driver *auxdrv = NULL; 89 bool found = true; 90 int ret = 0; 91 92 mutex_lock(&bnxt_auxbus_lock); 93 list_for_each_entry(auxdrv, &bnxt_aux_bus_drv_list, list) { 94 if (auxdrv) { 95 msleep(2 * 1000); 96 97 id = auxiliary_match_id(auxdrv->id_table, auxdev); 98 if (id) { 99 ret = auxdrv->probe(auxdev, id); 100 if (!ret) 101 auxdev->dev.driver = &auxdrv->driver; 102 else 103 found = false; 104 break; 105 } 106 } 107 } 108 109 if (found) 110 list_add_tail(&auxdev->list, &bnxt_aux_bus_dev_list); 111 mutex_unlock(&bnxt_auxbus_lock); 112 113 return ret; 114 } 115 116 void auxiliary_device_uninit(struct auxiliary_device *auxdev) 117 { 118 return; 119 } 120 121 void auxiliary_device_delete(struct auxiliary_device *auxdev) 122 { 123 struct auxiliary_driver *auxdrv; 124 125 mutex_lock(&bnxt_auxbus_lock); 126 list_for_each_entry(auxdrv, &bnxt_aux_bus_drv_list, list) { 127 if (auxdev->dev.driver != &auxdrv->driver) 128 continue; 129 if (auxdrv->remove) 130 auxdrv->remove(auxdev); 131 auxdev->dev.driver = NULL; 132 } 133 list_del(&auxdev->list); 134 mutex_unlock(&bnxt_auxbus_lock); 135 } 136 137 int auxiliary_driver_register(struct auxiliary_driver *auxdrv) 138 { 139 const struct auxiliary_device_id *id; 140 struct auxiliary_device *auxdev; 141 int ret = 0; 142 143 if (WARN_ON(!auxdrv->probe) || WARN_ON(!auxdrv->id_table)) 144 return -EINVAL; 145 146 if (auxdrv->name) 147 auxdrv->driver.name = kasprintf(GFP_KERNEL, "%s.%s", KBUILD_MODNAME, 148 auxdrv->name); 149 else 150 auxdrv->driver.name = kasprintf(GFP_KERNEL, "%s", KBUILD_MODNAME); 151 if (!auxdrv->driver.name) 152 return -ENOMEM; 153 154 mutex_lock(&bnxt_auxbus_lock); 155 list_for_each_entry(auxdev, &bnxt_aux_bus_dev_list, list) { 156 if (auxdev->dev.driver) 157 continue; 158 159 id = auxiliary_match_id(auxdrv->id_table, auxdev); 160 if (id) { 161 ret = auxdrv->probe(auxdev, id); 162 if (ret) 163 continue; 164 auxdev->dev.driver = &auxdrv->driver; 165 } 166 } 167 list_add_tail(&auxdrv->list, &bnxt_aux_bus_drv_list); 168 mutex_unlock(&bnxt_auxbus_lock); 169 return 0; 170 } 171 EXPORT_SYMBOL(auxiliary_driver_register); 172 173 void auxiliary_driver_unregister(struct auxiliary_driver *auxdrv) 174 { 175 struct auxiliary_device *auxdev; 176 177 /* PF auxiliary devices are added to the list first and then VF devices. 178 * If we remove PF aux device driver first, it causes failures while 179 * removing VF driver. 180 * We need to remove VF auxiliary drivers first, so walk backwards. 181 */ 182 mutex_lock(&bnxt_auxbus_lock); 183 list_for_each_entry_reverse(auxdev, &bnxt_aux_bus_dev_list, list) { 184 if (auxdev->dev.driver != &auxdrv->driver) 185 continue; 186 if (auxdrv->remove) 187 auxdrv->remove(auxdev); 188 auxdev->dev.driver = NULL; 189 } 190 kfree(auxdrv->driver.name); 191 list_del(&auxdrv->list); 192 mutex_unlock(&bnxt_auxbus_lock); 193 } 194 EXPORT_SYMBOL(auxiliary_driver_unregister); 195