/* SPDX-License-Identifier: LGPL-2.1-only */ /* * Copyright (c) 2003-2008 Thomas Graf */ /** * @ingroup link * @defgroup link_API Link Modules API * @brief API for modules implementing specific link types/semantics. * * @par 1) Registering/Unregistering a new link info type * @code * static struct rtnl_link_info_ops vlan_info_ops = { * .io_name = "vlan", * .io_alloc = vlan_alloc, * .io_parse = vlan_parse, * .io_dump[NL_DUMP_BRIEF] = vlan_dump_brief, * .io_dump[NL_DUMP_FULL] = vlan_dump_full, * .io_free = vlan_free, * }; * * static void __init vlan_init(void) * { * rtnl_link_register_info(&vlan_info_ops); * } * * static void __exit vlan_exit(void) * { * rtnl_link_unregister_info(&vlan_info_ops); * } * @endcode * * @{ */ #include #include #include #include #include static NL_LIST_HEAD(info_ops); /* lock protecting info_ops and af_ops */ static NL_RW_LOCK(info_lock); static struct rtnl_link_info_ops *__rtnl_link_info_ops_lookup(const char *name) { struct rtnl_link_info_ops *ops; nl_list_for_each_entry(ops, &info_ops, io_list) if (!strcmp(ops->io_name, name)) return ops; return NULL; } /** * @name Link Info Modules * @{ */ /** * Return operations of a specific link info type * @arg name Name of link info type. * * @note The returned pointer must be given back using rtnl_link_info_ops_put() * * @return Pointer to operations or NULL if unavailable. */ struct rtnl_link_info_ops *rtnl_link_info_ops_lookup(const char *name) { struct rtnl_link_info_ops *ops; nl_write_lock(&info_lock); if ((ops = __rtnl_link_info_ops_lookup(name))) ops->io_refcnt++; nl_write_unlock(&info_lock); return ops; } /** * Take reference to a set of operations. * @arg ops Link info operations. */ void rtnl_link_info_ops_get(struct rtnl_link_info_ops *ops) { if (!ops) return; nl_write_lock(&info_lock); ops->io_refcnt++; nl_write_unlock(&info_lock); } /** * Give back reference to a set of operations. * @arg ops Link info operations. */ void rtnl_link_info_ops_put(struct rtnl_link_info_ops *ops) { if (!ops) return; nl_write_lock(&info_lock); _nl_assert(ops->io_refcnt > 0); ops->io_refcnt--; nl_write_unlock(&info_lock); } /** * Register operations for a link info type * @arg ops Link info operations * * This function must be called by modules implementing a specific link * info type. It will make the operations implemented by the module * available for everyone else. * * @return 0 on success or a negative error code. * @return -NLE_INVAL Link info name not specified. * @return -NLE_EXIST Operations for address family already registered. */ int rtnl_link_register_info(struct rtnl_link_info_ops *ops) { int err = 0; if (ops->io_name == NULL) return -NLE_INVAL; nl_write_lock(&info_lock); if (__rtnl_link_info_ops_lookup(ops->io_name)) { err = -NLE_EXIST; goto errout; } NL_DBG(1, "Registered link info operations %s\n", ops->io_name); nl_list_add_tail(&ops->io_list, &info_ops); errout: nl_write_unlock(&info_lock); return err; } /** * Unregister operations for a link info type * @arg ops Link info operations * * This function must be called if a module implementing a specific link * info type is unloaded or becomes unavailable. It must provide a * set of operations which have previously been registered using * rtnl_link_register_info(). * * @return 0 on success or a negative error code * @return _NLE_OPNOTSUPP Link info operations not registered. * @return -NLE_BUSY Link info operations still in use. */ int rtnl_link_unregister_info(struct rtnl_link_info_ops *ops) { struct rtnl_link_info_ops *t; int err = -NLE_OPNOTSUPP; nl_write_lock(&info_lock); nl_list_for_each_entry(t, &info_ops, io_list) { if (t == ops) { _nl_assert(t->io_refcnt >= 0); if (t->io_refcnt > 0) { err = -NLE_BUSY; goto errout; } nl_list_del(&t->io_list); NL_DBG(1, "Unregistered link info operations %s\n", ops->io_name); err = 0; goto errout; } } errout: nl_write_unlock(&info_lock); return err; } /** @} */ /** * @name Link Address Family Modules * @{ */ static struct rtnl_link_af_ops *af_ops[AF_MAX]; /** * Return operations of a specific link address family * @arg family Address family * * @note The returned pointer must be given back using rtnl_link_af_ops_put() * * @return Pointer to operations or NULL if unavailable. */ struct rtnl_link_af_ops *rtnl_link_af_ops_lookup(const unsigned int family) { if (family == AF_UNSPEC || family >= AF_MAX) return NULL; nl_write_lock(&info_lock); if (af_ops[family]) af_ops[family]->ao_refcnt++; nl_write_unlock(&info_lock); return af_ops[family]; } /** * Give back reference to a set of operations. * @arg ops Address family operations. */ void rtnl_link_af_ops_put(struct rtnl_link_af_ops *ops) { if (ops) { nl_write_lock(&info_lock); ops->ao_refcnt--; nl_write_unlock(&info_lock); } } /** * Allocate and return data buffer for link address family modules * @arg link Link object * @arg ops Address family operations * * This function must be called by link address family modules in all * cases where the API does not provide the data buffer as argument * already. This typically includes set functions the module provides. * Calling this function is strictly required to ensure proper allocation * of the buffer upon first use. Link objects will NOT proactively * allocate a data buffer for each registered link address family. * * @return Pointer to data buffer or NULL on error. */ void *rtnl_link_af_alloc(struct rtnl_link *link, const struct rtnl_link_af_ops *ops) { int family; if (!link || !ops) BUG(); family = ops->ao_family; if (!link->l_af_data[family]) { if (!ops->ao_alloc) BUG(); link->l_af_data[family] = ops->ao_alloc(link); if (!link->l_af_data[family]) return NULL; } return link->l_af_data[family]; } /** * Return data buffer for link address family modules * @arg link Link object * @arg ops Address family operations * * This function returns a pointer to the data buffer for the specified link * address family module or NULL if the buffer was not allocated yet. This * function is typically used by get functions of modules which are not * interested in having the data buffer allocated if no values have been set * yet. * * @return Pointer to data buffer or NULL on error. */ void *rtnl_link_af_data(const struct rtnl_link *link, const struct rtnl_link_af_ops *ops) { if (!link || !ops) BUG(); return link->l_af_data[ops->ao_family]; } /** * Register operations for a link address family * @arg ops Address family operations * * This function must be called by modules implementing a specific link * address family. It will make the operations implemented by the module * available for everyone else. * * @return 0 on success or a negative error code. * @return -NLE_INVAL Address family is out of range (0..AF_MAX) * @return -NLE_EXIST Operations for address family already registered. */ int rtnl_link_af_register(struct rtnl_link_af_ops *ops) { int err = 0; if (ops->ao_family == AF_UNSPEC || ops->ao_family >= AF_MAX) return -NLE_INVAL; nl_write_lock(&info_lock); if (af_ops[ops->ao_family]) { err = -NLE_EXIST; goto errout; } ops->ao_refcnt = 0; af_ops[ops->ao_family] = ops; NL_DBG(1, "Registered link address family operations %u\n", ops->ao_family); errout: nl_write_unlock(&info_lock); return err; } /** * Unregister operations for a link address family * @arg ops Address family operations * * This function must be called if a module implementing a specific link * address family is unloaded or becomes unavailable. It must provide a * set of operations which have previously been registered using * rtnl_link_af_register(). * * @return 0 on success or a negative error code * @return -NLE_INVAL ops is NULL * @return -NLE_OBJ_NOTFOUND Address family operations not registered. * @return -NLE_BUSY Address family operations still in use. */ int rtnl_link_af_unregister(struct rtnl_link_af_ops *ops) { int err = -NLE_INVAL; if (!ops) return err; nl_write_lock(&info_lock); if (!af_ops[ops->ao_family]) { err = -NLE_OBJ_NOTFOUND; goto errout; } if (ops->ao_refcnt > 0) { err = -NLE_BUSY; goto errout; } af_ops[ops->ao_family] = NULL; NL_DBG(1, "Unregistered link address family operations %u\n", ops->ao_family); errout: nl_write_unlock(&info_lock); return err; } /** * Compare af data for a link address family * @arg a Link object a * @arg b Link object b * @arg family af data family * * This function will compare af_data between two links * a and b of family given by arg family * * @return 0 if address family specific data matches or is not present * or != 0 if it mismatches. */ int rtnl_link_af_data_compare(struct rtnl_link *a, struct rtnl_link *b, int family) { struct rtnl_link_af_ops *af_ops; int ret = 0; if (!a->l_af_data[family] && !b->l_af_data[family]) return 0; if (!a->l_af_data[family] || !b->l_af_data[family]) return ~0; af_ops = rtnl_link_af_ops_lookup(family); if (!af_ops) return ~0; if (af_ops->ao_compare == NULL) { ret = ~0; goto out; } ret = af_ops->ao_compare(a, b, family, ~0, 0); out: rtnl_link_af_ops_put(af_ops); return ret; } /** * Compare link info data * @arg a Link object a * @arg b Link object b * * This function will compare link_info data between two links * a and b * * @return 0 if link_info data matches or is not present * or != 0 if it mismatches. */ int rtnl_link_info_data_compare(struct rtnl_link *a, struct rtnl_link *b, int flags) { if (a->l_info_ops != b->l_info_ops) return ~0; if (!a->l_info_ops || !a->l_info_ops->io_compare) return 0; return a->l_info_ops->io_compare(a, b, flags); } /** @} */ /** @} */