From: Ke Chen chenke54@huawei.com
driver inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/I5WKYW
-----------------------------------------------------------------------
Add ROH device driver support, include: 1. roh core initialization 2. provide a registration framework for roh device. 3. etc.
Signed-off-by: Ke Chen chenke54@huawei.com Reviewed-by: Gang Zhang gang.zhang@huawei.com Reviewed-by: Yefeng Yan yanyefeng@huawei.com Reviewed-by: Jingchao Dai daijingchao1@huawei.com --- drivers/Kconfig | 3 + drivers/Makefile | 1 + drivers/roh/Kconfig | 16 ++ drivers/roh/Makefile | 6 + drivers/roh/core/Makefile | 7 + drivers/roh/core/core.c | 481 +++++++++++++++++++++++++++++++++++ drivers/roh/core/core.h | 103 ++++++++ drivers/roh/core/core_priv.h | 21 ++ drivers/roh/core/main.c | 28 ++ 9 files changed, 666 insertions(+) create mode 100644 drivers/roh/Kconfig create mode 100644 drivers/roh/Makefile create mode 100644 drivers/roh/core/Makefile create mode 100644 drivers/roh/core/core.c create mode 100644 drivers/roh/core/core.h create mode 100644 drivers/roh/core/core_priv.h create mode 100644 drivers/roh/core/main.c
diff --git a/drivers/Kconfig b/drivers/Kconfig index 9310808ee385..b1b3d958f065 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -237,4 +237,7 @@ source "drivers/interconnect/Kconfig" source "drivers/counter/Kconfig"
source "drivers/most/Kconfig" + +source "drivers/roh/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index fd9c0b3da5f1..57e3773affcd 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -191,3 +191,4 @@ obj-$(CONFIG_GNSS) += gnss/ obj-$(CONFIG_INTERCONNECT) += interconnect/ obj-$(CONFIG_COUNTER) += counter/ obj-$(CONFIG_MOST) += most/ +obj-$(CONFIG_ROH) += roh/ diff --git a/drivers/roh/Kconfig b/drivers/roh/Kconfig new file mode 100644 index 000000000000..0ff2de261827 --- /dev/null +++ b/drivers/roh/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0+ + +menuconfig ROH + tristate "ROH support" + depends on HAS_IOMEM && HAS_DMA + depends on NET + depends on INET + depends on m + select IRQ_POLL + select DIMLIB + help + Core support for ROH. Make sure to also select + any protocols you wish to use as well as drivers + for your ROH hardware. + + To compile ROH core as module, choose M here. diff --git a/drivers/roh/Makefile b/drivers/roh/Makefile new file mode 100644 index 000000000000..e5ea16bd6b21 --- /dev/null +++ b/drivers/roh/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile for the Linux kernel ROH device drivers. +# + +obj-$(CONFIG_ROH) += core/ diff --git a/drivers/roh/core/Makefile b/drivers/roh/core/Makefile new file mode 100644 index 000000000000..1c5ab1fc08ea --- /dev/null +++ b/drivers/roh/core/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile for the Linux kernel ROH Core drivers. +# + +roh_core-objs := main.o core.o +obj-$(CONFIG_ROH) += roh_core.o diff --git a/drivers/roh/core/core.c b/drivers/roh/core/core.c new file mode 100644 index 000000000000..39c1404e0d97 --- /dev/null +++ b/drivers/roh/core/core.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright (c) 2022 Hisilicon Limited. + +#include <linux/pci.h> +#include <net/rtnetlink.h> + +#include "core.h" +#include "core_priv.h" + +static DEFINE_XARRAY_FLAGS(devices, XA_FLAGS_ALLOC); +static DECLARE_RWSEM(devices_rwsem); +#define DEVICE_REGISTERED XA_MARK_1 + +static u32 highest_client_id; +#define CLIENT_REGISTERED XA_MARK_1 +static DEFINE_XARRAY_FLAGS(clients, XA_FLAGS_ALLOC); +static DECLARE_RWSEM(clients_rwsem); + +static void roh_client_put(struct roh_client *client) +{ + if (refcount_dec_and_test(&client->uses)) + complete(&client->uses_zero); +} + +#define CLIENT_DATA_REGISTERED XA_MARK_1 + +static int add_client_context(struct roh_device *device, + struct roh_client *client); +static void remove_client_context(struct roh_device *device, + unsigned int client_id); +static void __roh_unregister_device(struct roh_device *device); + +static void roh_device_release(struct device *device) +{ + struct roh_device *dev = container_of(device, struct roh_device, dev); + + WARN_ON(refcount_read(&dev->refcount)); + + mutex_destroy(&dev->unregistration_lock); + xa_destroy(&dev->client_data); + kfree(dev); +} + +static int roh_device_uevent(struct device *device, struct kobj_uevent_env *env) +{ + struct roh_device *dev = container_of(device, struct roh_device, dev); + + if (add_uevent_var(env, "NAME=%s", dev->name)) { + pr_err("failed to do add_uevent_var.\n"); + return -ENOMEM; + } + + return 0; +} + +static struct class roh_class = { + .name = "roh", + .dev_release = roh_device_release, + .dev_uevent = roh_device_uevent, +}; + +struct roh_device *roh_alloc_device(size_t size) +{ + struct roh_device *device; + + if (WARN_ON(size < sizeof(struct roh_device))) + return NULL; + + device = kzalloc(size, GFP_KERNEL); + if (!device) + return NULL; + + device->dev.class = &roh_class; + + device_initialize(&device->dev); + dev_set_drvdata(&device->dev, device); + + mutex_init(&device->unregistration_lock); + + xa_init_flags(&device->client_data, XA_FLAGS_ALLOC); + init_rwsem(&device->client_data_rwsem); + init_completion(&device->unreg_completion); + + return device; +} +EXPORT_SYMBOL(roh_alloc_device); + +void roh_dealloc_device(struct roh_device *device) +{ + down_write(&devices_rwsem); + if (xa_load(&devices, device->index) == device) + xa_erase(&devices, device->index); + up_write(&devices_rwsem); + + WARN_ON(!xa_empty(&device->client_data)); + WARN_ON(refcount_read(&device->refcount)); + + put_device(&device->dev); +} +EXPORT_SYMBOL(roh_dealloc_device); + +static int alloc_name(struct roh_device *device) +{ + struct roh_device *dev; + unsigned long index; + struct ida inuse; + int rc; + int i; + + lockdep_assert_held_write(&devices_rwsem); + + ida_init(&inuse); + xa_for_each(&devices, index, dev) { + char buf[ROH_DEVICE_NAME_MAX]; + + if (sscanf(dev_name(&dev->dev), device->name, &i) != 1) + continue; + if (i < 0 || i >= INT_MAX) + continue; + rc = snprintf(buf, sizeof(buf), device->name, i); + if (rc >= sizeof(buf) || rc < 0) { + ida_destroy(&inuse); + pr_err("device name is too long.\n"); + return -EINVAL; + } + + if (strcmp(buf, dev_name(&dev->dev)) != 0) + continue; + + rc = ida_alloc_range(&inuse, i, i, GFP_KERNEL); + if (rc < 0) + goto out; + } + + rc = ida_alloc(&inuse, GFP_KERNEL); + if (rc < 0) + goto out; + + rc = dev_set_name(&device->dev, device->name, rc); + +out: + ida_destroy(&inuse); + return rc; +} + +static void roh_device_put(struct roh_device *device) +{ + if (refcount_dec_and_test(&device->refcount)) + complete(&device->unreg_completion); +} + +struct roh_device *__roh_device_get_by_name(const char *name) +{ + struct roh_device *device; + unsigned long index; + + xa_for_each(&devices, index, device) + if (!strcmp(name, dev_name(&device->dev))) + return device; + + return NULL; +} + +static int assign_name(struct roh_device *device) +{ + static u32 last_id; + int ret; + + down_write(&devices_rwsem); + if (strchr(device->name, '%')) + ret = alloc_name(device); + else + ret = dev_set_name(&device->dev, device->name); + + if (ret) + goto out; + + if (__roh_device_get_by_name(dev_name(&device->dev))) { + ret = -ENFILE; + goto out; + } + + strlcpy(device->name, dev_name(&device->dev), ROH_DEVICE_NAME_MAX); + ret = xa_alloc_cyclic(&devices, &device->index, device, xa_limit_31b, + &last_id, GFP_KERNEL); + if (ret > 0) + ret = 0; + +out: + up_write(&devices_rwsem); + return ret; +} + +static void disable_device(struct roh_device *device) +{ + u32 cid; + + WARN_ON(!refcount_read(&device->refcount)); + + down_write(&devices_rwsem); + xa_clear_mark(&devices, device->index, DEVICE_REGISTERED); + up_write(&devices_rwsem); + + down_read(&clients_rwsem); + cid = highest_client_id; + up_read(&clients_rwsem); + while (cid) { + cid--; + remove_client_context(device, cid); + } + + roh_device_put(device); + wait_for_completion(&device->unreg_completion); +} + +static int enable_device_and_get(struct roh_device *device) +{ + struct roh_client *client; + unsigned long index; + int ret = 0; + + refcount_set(&device->refcount, MAX_DEVICE_REFCOUNT); + down_write(&devices_rwsem); + xa_set_mark(&devices, device->index, DEVICE_REGISTERED); + + downgrade_write(&devices_rwsem); + down_read(&clients_rwsem); + xa_for_each_marked(&clients, index, client, CLIENT_REGISTERED) { + ret = add_client_context(device, client); + if (ret) + break; + } + up_read(&clients_rwsem); + + up_read(&devices_rwsem); + + return ret; +} + +int roh_register_device(struct roh_device *device) +{ + int ret; + + ret = assign_name(device); + if (ret) { + pr_err("roh_core: failed to assigne name, ret = %d\n", ret); + return ret; + } + + dev_set_uevent_suppress(&device->dev, true); + ret = device_add(&device->dev); + if (ret) { + pr_err("roh_core: failed to add device, ret = %d\n", ret); + goto out; + } + + ret = enable_device_and_get(device); + dev_set_uevent_suppress(&device->dev, false); + kobject_uevent(&device->dev.kobj, KOBJ_ADD); + if (ret) { + roh_device_put(device); + __roh_unregister_device(device); + return ret; + } + + roh_device_put(device); + + return 0; +out: + dev_set_uevent_suppress(&device->dev, false); + return ret; +} +EXPORT_SYMBOL(roh_register_device); + +static void __roh_unregister_device(struct roh_device *device) +{ + mutex_lock(&device->unregistration_lock); + if (!refcount_read(&device->refcount)) + goto out; + + disable_device(device); + device_del(&device->dev); + +out: + mutex_unlock(&device->unregistration_lock); +} + +void roh_unregister_device(struct roh_device *device) +{ + get_device(&device->dev); + + __roh_unregister_device(device); + put_device(&device->dev); +} +EXPORT_SYMBOL(roh_unregister_device); + +void roh_set_client_data(struct roh_device *device, struct roh_client *client, + void *data) +{ + void *rc; + + if (WARN_ON(IS_ERR(data))) + data = NULL; + + rc = xa_store(&device->client_data, client->client_id, data, + GFP_KERNEL); + WARN_ON(xa_is_err(rc)); +} + +static void remove_client_context(struct roh_device *device, + unsigned int client_id) +{ + struct roh_client *client; + void *client_data; + + down_write(&device->client_data_rwsem); + if (!xa_get_mark(&device->client_data, client_id, + CLIENT_DATA_REGISTERED)) { + up_write(&device->client_data_rwsem); + return; + } + client_data = xa_load(&device->client_data, client_id); + xa_clear_mark(&device->client_data, client_id, CLIENT_DATA_REGISTERED); + client = xa_load(&clients, client_id); + up_write(&device->client_data_rwsem); + + if (client->remove) + client->remove(device, client_data); + + xa_erase(&device->client_data, client_id); + roh_device_put(device); + roh_client_put(client); +} + +static int add_client_context(struct roh_device *device, + struct roh_client *client) +{ + int ret = 0; + + down_write(&device->client_data_rwsem); + if (!refcount_inc_not_zero(&client->uses)) + goto out_unlock; + refcount_inc(&device->refcount); + + if (xa_get_mark(&device->client_data, client->client_id, + CLIENT_DATA_REGISTERED)) + goto out; + + ret = xa_err(xa_store(&device->client_data, client->client_id, NULL, + GFP_KERNEL)); + if (ret) + goto out; + + downgrade_write(&device->client_data_rwsem); + if (client->add) { + if (client->add(device)) { + xa_erase(&device->client_data, client->client_id); + up_read(&device->client_data_rwsem); + roh_device_put(device); + roh_client_put(client); + return 0; + } + } + + xa_set_mark(&device->client_data, client->client_id, + CLIENT_DATA_REGISTERED); + up_read(&device->client_data_rwsem); + + return 0; + +out: + roh_device_put(device); + roh_client_put(client); +out_unlock: + up_write(&device->client_data_rwsem); + return ret; +} + +static int assign_client_id(struct roh_client *client) +{ + int ret; + + down_write(&clients_rwsem); + + client->client_id = highest_client_id; + ret = xa_insert(&clients, client->client_id, client, GFP_KERNEL); + if (ret) + goto out; + + highest_client_id++; + xa_set_mark(&clients, client->client_id, CLIENT_REGISTERED); + +out: + up_write(&clients_rwsem); + return ret; +} + +static void remove_client_id(struct roh_client *client) +{ + down_write(&clients_rwsem); + xa_erase(&clients, client->client_id); + for (; highest_client_id; highest_client_id--) + if (xa_load(&clients, highest_client_id - 1)) + break; + up_write(&clients_rwsem); +} + +int roh_register_client(struct roh_client *client) +{ + struct roh_device *device; + unsigned long index; + int ret; + + refcount_set(&client->uses, 1); + init_completion(&client->uses_zero); + ret = assign_client_id(client); + if (ret) + return ret; + + down_read(&devices_rwsem); + xa_for_each_marked(&devices, index, device, DEVICE_REGISTERED) { + ret = add_client_context(device, client); + if (ret) { + up_read(&devices_rwsem); + roh_unregister_client(client); + return ret; + } + } + up_read(&devices_rwsem); + + return 0; +} + +void roh_unregister_client(struct roh_client *client) +{ + struct roh_device *device; + unsigned long index; + + down_write(&clients_rwsem); + roh_client_put(client); + xa_clear_mark(&clients, client->client_id, CLIENT_REGISTERED); + up_write(&clients_rwsem); + + rcu_read_lock(); + xa_for_each(&devices, index, device) { + if (!roh_device_try_get(device)) + continue; + rcu_read_unlock(); + + remove_client_context(device, client->client_id); + + roh_device_put(device); + rcu_read_lock(); + } + rcu_read_unlock(); + + wait_for_completion(&client->uses_zero); + remove_client_id(client); +} + +int roh_core_init(void) +{ + int ret; + + ret = class_register(&roh_class); + if (ret) { + pr_err("roh_core: couldn't create roh device class.\n"); + return ret; + } + + return 0; +} + +void roh_core_cleanup(void) +{ + class_unregister(&roh_class); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Huawei Tech. Co., Ltd."); +MODULE_DESCRIPTION("ROH Core Driver"); diff --git a/drivers/roh/core/core.h b/drivers/roh/core/core.h new file mode 100644 index 000000000000..85f676a7a075 --- /dev/null +++ b/drivers/roh/core/core.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +// Copyright (c) 2022 Hisilicon Limited. + +#ifndef __CORE_H__ +#define __CORE_H__ + +#include <linux/types.h> +#include <linux/workqueue.h> +#include <linux/netdevice.h> +#include <net/bonding.h> + +#define ROH_DEVICE_NAME_MAX 64 +#define MAX_DEVICE_REFCOUNT 2 + +enum roh_dev_tx { + ROHDEV_TX_MIN = INT_MIN, /* make sure enum is signed */ + ROHDEV_TX_OK = 0x00, /* driver took care of packet */ + ROHDEV_TX_BUSY = 0x10, /* driver tx path was busy */ + ROHDEV_TX_LOCKED = 0x20 /* driver tx lock was already taken */ +}; + +enum roh_mib_type { ROH_MIB_PUBLIC = 0, ROH_MIB_PRIVATE }; + +static inline void convert_eid_to_mac(u8 mac[6], u32 eid) +{ + mac[0] = 0; + mac[1] = 0; + mac[2] = 0; + mac[3] = (eid >> 16) & 0xff; + mac[4] = (eid >> 8) & 0xff; + mac[5] = eid & 0xff; +} + +struct roh_eid_attr { + u32 base; + u32 num; +}; + +struct roh_guid_attr { + u8 data[16]; +}; + +struct roh_mib_stats { + struct mutex lock; /* Protect values[] */ + const char * const *names; + u32 num_counters; + u64 value[]; +}; + +struct roh_device; +struct roh_device_ops { + int (*query_guid)(struct roh_device *device, struct roh_guid_attr *attr); + int (*set_eid)(struct roh_device *device, struct roh_eid_attr *attr); + struct sk_buff *(*pkt_create)(struct net_device *ndev, + u8 *dest_mac, u8 *src_mac, int ptype, + struct roh_guid_attr *guid, u16 eid_nums); + int (*pkt_parse)(struct sk_buff *skb, struct roh_eid_attr *eid_attr, int ptype); + enum roh_dev_tx (*xmit_pkt)(struct roh_device *device, struct sk_buff *skb); + struct roh_mib_stats *(*alloc_hw_stats)(struct roh_device *device, enum roh_mib_type); + int (*get_hw_stats)(struct roh_device *device, + struct roh_mib_stats *stats, enum roh_mib_type); +}; + +struct roh_device { + struct device dev; + char name[ROH_DEVICE_NAME_MAX]; + struct roh_device_ops ops; + u32 abi_ver; + + struct rcu_head rcu_head; + struct rw_semaphore client_data_rwsem; + struct xarray client_data; + + struct module *owner; + struct net_device *netdev; + + u32 index; + /* + * Positive refcount indicates that the device is currently + * registered and cannot be unregistered. + */ + refcount_t refcount; + struct completion unreg_completion; + struct mutex unregistration_lock; /* lock for unregiste */ +}; + +static inline bool roh_device_try_get(struct roh_device *device) +{ + return refcount_inc_not_zero(&device->refcount); +} + +struct roh_device *__roh_device_get_by_name(const char *name); + +struct roh_device *roh_alloc_device(size_t size); +void roh_dealloc_device(struct roh_device *device); + +int roh_register_device(struct roh_device *device); +void roh_unregister_device(struct roh_device *device); + +int roh_core_init(void); +void roh_core_cleanup(void); + +#endif /* __CORE_H__ */ diff --git a/drivers/roh/core/core_priv.h b/drivers/roh/core/core_priv.h new file mode 100644 index 000000000000..7e21cc807044 --- /dev/null +++ b/drivers/roh/core/core_priv.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +// Copyright (c) 2022 Hisilicon Limited. + +#ifndef __CORE_PRIV_H__ +#define __CORE_PRIV_H__ + +struct roh_client { + char *name; + int (*add)(struct roh_device *device); + void (*remove)(struct roh_device *device, void *client_data); + refcount_t uses; + u32 client_id; + struct completion uses_zero; +}; + +int roh_register_client(struct roh_client *client); +void roh_unregister_client(struct roh_client *client); +void roh_set_client_data(struct roh_device *device, + struct roh_client *client, void *data); + +#endif /* __CORE_PRIV_H__ */ diff --git a/drivers/roh/core/main.c b/drivers/roh/core/main.c new file mode 100644 index 000000000000..0f766f85eaf5 --- /dev/null +++ b/drivers/roh/core/main.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright (c) 2022 Hisilicon Limited. + +#include <linux/init.h> +#include <linux/module.h> + +#include "core.h" + +static int __init roh_init(void) +{ + int ret; + + ret = roh_core_init(); + if (ret) { + pr_err("roh_core: roh core init failed.\n"); + return ret; + } + + return 0; +} + +static void __exit roh_cleanup(void) +{ + roh_core_cleanup(); +} + +module_init(roh_init); +module_exit(roh_cleanup);