/*- * Broadcom NetXtreme-C/E network driver. * * Copyright (c) 2024 Broadcom, All Rights Reserved. * The term Broadcom refers to Broadcom Limited and/or its subsidiaries * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "bnxt_auxbus_compat.h" static struct list_head bnxt_aux_bus_dev_list = LINUX_LIST_HEAD_INIT(bnxt_aux_bus_dev_list); static struct list_head bnxt_aux_bus_drv_list = LINUX_LIST_HEAD_INIT(bnxt_aux_bus_drv_list); static DEFINE_MUTEX(bnxt_auxbus_lock); static const struct auxiliary_device_id *auxiliary_match_id(const struct auxiliary_device_id *id, const struct auxiliary_device *auxdev) { for (; id->name[0]; id++) { const char *p = strrchr(dev_name(&auxdev->dev), '.'); int match_size; if (!p) continue; match_size = p - dev_name(&auxdev->dev); if (strlen(id->name) == match_size && !strncmp(dev_name(&auxdev->dev), id->name, match_size)) return id; } return NULL; } int auxiliary_device_init(struct auxiliary_device *auxdev) { struct device *dev = &auxdev->dev; char *modname = KBUILD_MODNAME; int ret; if (!dev->parent) { pr_err("auxiliary_device has a NULL dev->parent\n"); return -EINVAL; } if (!auxdev->name) { pr_err("auxiliary_device has a NULL name\n"); return -EINVAL; } ret = dev_set_name(dev, "%s.%s.%d", modname, auxdev->name, auxdev->id); if (ret) { dev_err(dev, "auxiliary device dev_set_name failed: %d\n", ret); return ret; } return 0; } int auxiliary_device_add(struct auxiliary_device *auxdev) { const struct auxiliary_device_id *id; struct auxiliary_driver *auxdrv = NULL; bool found = true; int ret = 0; mutex_lock(&bnxt_auxbus_lock); list_for_each_entry(auxdrv, &bnxt_aux_bus_drv_list, list) { if (auxdrv) { msleep(2 * 1000); id = auxiliary_match_id(auxdrv->id_table, auxdev); if (id) { ret = auxdrv->probe(auxdev, id); if (!ret) auxdev->dev.driver = &auxdrv->driver; else found = false; break; } } } if (found) list_add_tail(&auxdev->list, &bnxt_aux_bus_dev_list); mutex_unlock(&bnxt_auxbus_lock); return ret; } void auxiliary_device_uninit(struct auxiliary_device *auxdev) { return; } void auxiliary_device_delete(struct auxiliary_device *auxdev) { struct auxiliary_driver *auxdrv; mutex_lock(&bnxt_auxbus_lock); list_for_each_entry(auxdrv, &bnxt_aux_bus_drv_list, list) { if (auxdev->dev.driver != &auxdrv->driver) continue; if (auxdrv->remove) auxdrv->remove(auxdev); auxdev->dev.driver = NULL; } list_del(&auxdev->list); mutex_unlock(&bnxt_auxbus_lock); } int auxiliary_driver_register(struct auxiliary_driver *auxdrv) { const struct auxiliary_device_id *id; struct auxiliary_device *auxdev; int ret = 0; if (WARN_ON(!auxdrv->probe) || WARN_ON(!auxdrv->id_table)) return -EINVAL; if (auxdrv->name) auxdrv->driver.name = kasprintf(GFP_KERNEL, "%s.%s", KBUILD_MODNAME, auxdrv->name); else auxdrv->driver.name = kasprintf(GFP_KERNEL, "%s", KBUILD_MODNAME); if (!auxdrv->driver.name) return -ENOMEM; mutex_lock(&bnxt_auxbus_lock); list_for_each_entry(auxdev, &bnxt_aux_bus_dev_list, list) { if (auxdev->dev.driver) continue; id = auxiliary_match_id(auxdrv->id_table, auxdev); if (id) { ret = auxdrv->probe(auxdev, id); if (ret) continue; auxdev->dev.driver = &auxdrv->driver; } } list_add_tail(&auxdrv->list, &bnxt_aux_bus_drv_list); mutex_unlock(&bnxt_auxbus_lock); return 0; } EXPORT_SYMBOL(auxiliary_driver_register); void auxiliary_driver_unregister(struct auxiliary_driver *auxdrv) { struct auxiliary_device *auxdev; /* PF auxiliary devices are added to the list first and then VF devices. * If we remove PF aux device driver first, it causes failures while * removing VF driver. * We need to remove VF auxiliary drivers first, so walk backwards. */ mutex_lock(&bnxt_auxbus_lock); list_for_each_entry_reverse(auxdev, &bnxt_aux_bus_dev_list, list) { if (auxdev->dev.driver != &auxdrv->driver) continue; if (auxdrv->remove) auxdrv->remove(auxdev); auxdev->dev.driver = NULL; } kfree(auxdrv->driver.name); list_del(&auxdrv->list); mutex_unlock(&bnxt_auxbus_lock); } EXPORT_SYMBOL(auxiliary_driver_unregister);