/* * Copyright (c) 2019 AVM GmbH . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "offdp.h" #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Paul Hüber"); MODULE_DESCRIPTION("AVM offload datapath"); static char *vep; module_param(vep, charp, 0660); struct kobject *offdp_kobj; int offdp_l2fwd_init(void); void offdp_l2fwd_exit(void); enum offdp_rv offdp_l2fwd_unregister(const struct net_device *dev); enum offdp_rv offdp_virtualep_init(void); void offdp_virtualep_exit(void); static struct errstat_domain_storage edom_storage; struct errstat_domain *edom; const char *offdp_rv_names[] = { [OFFDP_SUCCESS] = "success", [OFFDP_ERR_BACKEND_INIT] = "backend init failed", [OFFDP_ERR_MEM] = "out of memory", [OFFDP_ERR_DEV_RESOLVE] = "netdevice lookup failure", [OFFDP_ERR_CONFLICT] = "conflicting session", [OFFDP_ERR_NETLINK] = "netlink error", [OFFDP_ERR_SWAPI] = "switch_api error", [OFFDP_ERR_NOAVAIL] = "resource not available", [OFFDP_ERR_FAST_RCV] = "VEP rx path failed", [OFFDP_ERR_VEP_LIMIT] = "VEP limit reached", [OFFDP_ERR_VEP_NOT_VIRTUAL] = "endpoint not virtual", [OFFDP_ERR_VEP_EXISTS] = "VEP already registered", [OFFDP_ERR_KOBJ] = "kobject error", [OFFDP_ERR_INPUT] = "called with invalid input", }; atomic_t offdp_rv_counters[ARRAY_SIZE(offdp_rv_names)]; atomic_t l2fwd_enabled = ATOMIC_INIT(1); static ssize_t l2fwd_enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%s\n", atomic_read(&l2fwd_enabled) ? "enabled" : "disabled"); } static ssize_t l2fwd_enable_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { bool enable; if (kstrtobool(buf, &enable) || enable == !!atomic_xchg(&l2fwd_enabled, enable)) return count; if (enable) offdp_l2fwd_init(); else offdp_l2fwd_exit(); return count; } static int notifier_fn(struct notifier_block *nb, unsigned long action, void *data) { struct netdev_notifier_info *info = data; switch (action) { case NETDEV_UNREGISTER: offdp_vep_unregister(info->dev); offdp_l2fwd_unregister(info->dev); return NOTIFY_OK; default: return NOTIFY_DONE; } } struct notifier_block notifier = { .notifier_call = notifier_fn }; static struct kobj_attribute l2fwd_enable = __ATTR_RW(l2fwd_enable); int __init offdp_init(void) { enum offdp_rv rv; edom = errstat_domain_init(&edom_storage, offdp_rv_names, offdp_rv_counters, ARRAY_SIZE(offdp_rv_counters)); offdp_kobj = kobject_create_and_add(THIS_MODULE->name, kernel_kobj); if (!offdp_kobj) goto err; sysfs_create_file(offdp_kobj, &l2fwd_enable.attr); errstat_sysfs_attach(edom, offdp_kobj, "errors"); rv = offdp_backend_init(); if (OFFDP_IS_ERR(rv)) goto err; rv = offdp_virtualep_init(); if (OFFDP_IS_ERR(rv)) goto err; if (vep) offdp_vep_register(dev_get_by_name(&init_net, vep)); rv = offdp_l2fwd_init(); if (OFFDP_IS_ERR(rv)) goto err; offdp_vep_tracepoint_hook(); register_netdevice_notifier(¬ifier); return 0; err: pr_debug("%s err %d\n", __func__, rv); return -1; } void __exit offdp_exit(void) { unregister_netdevice_notifier(¬ifier); if (atomic_read(&l2fwd_enabled)) offdp_l2fwd_exit(); offdp_backend_exit(); errstat_sysfs_detach(edom); kobject_put(offdp_kobj); } module_init(offdp_init); module_exit(offdp_exit);