/* * Copyright (C) 2015 Texas Instruments Incorporated * Authors: Reece Pollack * WingMan Kwok * * 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 version 2. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any * kind, whether express or implied; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "netcp.h" #define QOS_MODULE_NAME "netcp-qos" #define QOS_TXHOOK_ORDER 20 struct qos_subqueue { struct netcp_tx_pipe tx_pipe; struct kobject kobj; }; struct qos_priv { struct netcp_device *netcp_device; struct device *dev; const char *dma_chan_name; struct kobject kobj; }; struct qos_intf { struct qos_priv *qos_priv; struct net_device *ndev; struct device *dev; struct kobject kobj; int max_subqueues; struct qos_subqueue subqueues[1]; /* NB: tx-subqueues are allocated dynamically */ }; /* Sysfs stuff related to QoS Subqueues */ #define kobj_to_subqueue(kobj) container_of(kobj, struct qos_subqueue, kobj) static ssize_t qos_txqueue_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct qos_subqueue *subq = kobj_to_subqueue(kobj); return snprintf(buf, PAGE_SIZE, "%u\n", subq->tx_pipe.dma_queue_id); } struct kobj_attribute qos_txqueue_attr = __ATTR(tx_queue, S_IRUGO, qos_txqueue_show, NULL); static ssize_t qos_txchan_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct qos_subqueue *subq = kobj_to_subqueue(kobj); return snprintf(buf, PAGE_SIZE, "%s\n", subq->tx_pipe.dma_chan_name); } struct kobj_attribute qos_txchan_attr = __ATTR(tx_channel, S_IRUGO, qos_txchan_show, NULL); static struct attribute *qos_subqueue_attrs[] = { &qos_txqueue_attr.attr, &qos_txchan_attr.attr, NULL }; static struct kobj_type qos_subqueue_ktype = { .sysfs_ops = &kobj_sysfs_ops, .default_attrs = qos_subqueue_attrs, }; /* Sysfs stuff related to QoS Interfaces */ static struct kobj_type qos_intf_ktype = { .sysfs_ops = &kobj_sysfs_ops, }; static struct kobj_type qos_inst_ktype = { .sysfs_ops = &kobj_sysfs_ops, }; static int qos_tx_hook(int order, void *data, struct netcp_packet *p_info) { struct qos_intf *qos_intf = data; struct sk_buff *skb = p_info->skb; u16 queue_mapping = skb_get_queue_mapping(skb); dev_dbg(qos_intf->dev, "priority: %u, queue_mapping: %04x\n", skb->priority, queue_mapping); if ((queue_mapping < qos_intf->max_subqueues) && qos_intf->subqueues[queue_mapping].tx_pipe.dma_queue) p_info->tx_pipe = &qos_intf->subqueues[queue_mapping].tx_pipe; else { dev_warn(qos_intf->dev, "queue mapping %hu invalid, QoS bypassed\n", queue_mapping); } return 0; } static int qos_close(void *intf_priv, struct net_device *ndev) { struct qos_intf *qos_intf = intf_priv; struct netcp_intf *netcp_intf = netdev_priv(ndev); int i; netcp_unregister_txhook(netcp_intf, QOS_TXHOOK_ORDER, qos_tx_hook, qos_intf); for (i = 0; i < qos_intf->max_subqueues; ++i) { struct qos_subqueue *subq = &qos_intf->subqueues[i]; if (subq->tx_pipe.dma_chan_name) netcp_txpipe_close(&subq->tx_pipe); } return 0; } static int qos_open(void *intf_priv, struct net_device *ndev) { struct qos_intf *qos_intf = intf_priv; struct netcp_intf *netcp_intf = netdev_priv(ndev); int ret; int i; /* Open the QoS input queues */ for (i = 0; i < qos_intf->max_subqueues; ++i) { struct qos_subqueue *subq = &qos_intf->subqueues[i]; if (subq->tx_pipe.dma_chan_name) { ret = netcp_txpipe_open(&subq->tx_pipe); if (ret) goto fail; } } netcp_register_txhook(netcp_intf, QOS_TXHOOK_ORDER, qos_tx_hook, qos_intf); return 0; fail: qos_close(intf_priv, ndev); return ret; } static int init_tx_subqueue(struct qos_intf *qos_intf, int index, unsigned int tx_queue_id) { struct netcp_intf *netcp = netdev_priv(qos_intf->ndev); struct qos_subqueue *subq = &qos_intf->subqueues[index]; u8 name[32]; int ret; netcp_txpipe_init(&subq->tx_pipe, netcp->netcp_device, qos_intf->qos_priv->dma_chan_name, tx_queue_id); /* Create the per-subqueue entry and attributes */ snprintf(name, sizeof(name), "subqueue-%d", index); ret = kobject_init_and_add(&subq->kobj, &qos_subqueue_ktype, kobject_get(&qos_intf->kobj), name); if (ret) { dev_err(qos_intf->dev, "failed to create %s/%s/%s sysfs entry\n", qos_intf->qos_priv->kobj.name, qos_intf->kobj.name, name); kobject_put(&subq->kobj); kobject_put(&qos_intf->kobj); return ret; } return 0; } static int qos_attach(void *inst_priv, struct net_device *ndev, struct device_node *node, void **intf_priv) { struct netcp_intf *netcp = netdev_priv(ndev); struct qos_priv *qos_priv = inst_priv; struct qos_intf *qos_intf; int temp[NETCP_MAX_SUBQUEUES], size, ret, tx_queues, i; tx_queues = netcp->ndev->num_tx_queues; size = sizeof(struct qos_intf) + ((tx_queues - 1) * sizeof(struct qos_subqueue)); qos_intf = devm_kzalloc(qos_priv->dev, size, GFP_KERNEL); if (!qos_intf) { dev_err(qos_priv->dev, "qos interface memory allocation failed\n"); return -ENOMEM; } qos_intf->max_subqueues = tx_queues; qos_intf->qos_priv = qos_priv; qos_intf->ndev = ndev; qos_intf->dev = qos_priv->dev; *intf_priv = qos_intf; /* Create the per-interface sysfs entry */ ret = kobject_init_and_add(&qos_intf->kobj, &qos_intf_ktype, kobject_get(&qos_intf->qos_priv->kobj), node->name); if (ret) { dev_err(qos_intf->dev, "failed to create %s/%s sysfs entry\n", qos_intf->qos_priv->kobj.name, node->name); kobject_put(&qos_intf->kobj); kobject_put(&qos_intf->qos_priv->kobj); goto exit; } if (!of_get_property(node, "tx-queues", &tx_queues)) { dev_err(qos_priv->dev, "missing \"tx-queues\" parameter\n"); return -ENODEV; } /* makes sure queues defined not exceeding intf can support */ tx_queues = min(tx_queues, qos_intf->max_subqueues); ret = of_property_read_u32_array(node, "tx-queues", temp, tx_queues); if (ret < 0) { dev_err(qos_priv->dev, "error in \"tx-queues\" parameter\n"); tx_queues = 1; } for (i = 0; i < tx_queues; i++) { if (temp[i] < 0) { dev_dbg(qos_intf->dev, "no tx-queue defined for mapping %d\n", i); continue; } dev_dbg(qos_intf->dev, "mapping %d as %d\n", temp[i], i); ret = init_tx_subqueue(qos_intf, i, temp[i]); if (ret) { dev_err(qos_intf->dev, "failed to initialize subqueue %d\n", temp[i]); goto exit; } } return 0; exit: devm_kfree(qos_priv->dev, qos_intf); return ret; } static int qos_release(void *intf_priv) { struct qos_intf *qos_intf = intf_priv; devm_kfree(qos_intf->dev, qos_intf); return 0; } static int qos_remove(struct netcp_device *netcp_device, void *inst_priv) { struct qos_priv *qos_priv = inst_priv; devm_kfree(qos_priv->dev, qos_priv); return 0; } static int qos_probe(struct netcp_device *netcp_device, struct device *dev, struct device_node *node, void **inst_priv) { struct qos_priv *qos_priv; int ret = 0; if (!node) { dev_err(dev, "device tree info unavailable\n"); return -ENODEV; } qos_priv = devm_kzalloc(dev, sizeof(struct qos_priv), GFP_KERNEL); if (!qos_priv) return -ENOMEM; *inst_priv = qos_priv; qos_priv->netcp_device = netcp_device; qos_priv->dev = dev; ret = of_property_read_string(node, "tx-channel", &qos_priv->dma_chan_name); if (ret < 0) { dev_err(dev, "missing \"tx-channel\" parameter\n"); ret = -ENODEV; goto exit; } /* Create the per-instance sysfs entry */ ret = kobject_init_and_add(&qos_priv->kobj, &qos_inst_ktype, kobject_get(&dev->kobj), node->name); if (ret) { dev_err(dev, "failed to create %s sysfs entry\n", node->name); kobject_put(&qos_priv->kobj); kobject_put(&dev->kobj); goto exit; } return 0; exit: qos_remove(netcp_device, qos_priv); *inst_priv = NULL; return ret; } static struct netcp_module qos_module = { .name = QOS_MODULE_NAME, .owner = THIS_MODULE, .probe = qos_probe, .open = qos_open, .close = qos_close, .remove = qos_remove, .attach = qos_attach, .release = qos_release, }; static int __init keystone_qos_init(void) { return netcp_register_module(&qos_module); } module_init(keystone_qos_init); static void __exit keystone_qos_exit(void) { netcp_unregister_module(&qos_module); } module_exit(keystone_qos_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Reece Pollack "); MODULE_DESCRIPTION("Quality of Service driver for Keystone devices");