18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* net/sched/sch_atm.c - ATM VC selection "queueing discipline" */ 38c2ecf20Sopenharmony_ci 48c2ecf20Sopenharmony_ci/* Written 1998-2000 by Werner Almesberger, EPFL ICA */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/module.h> 78c2ecf20Sopenharmony_ci#include <linux/slab.h> 88c2ecf20Sopenharmony_ci#include <linux/init.h> 98c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 108c2ecf20Sopenharmony_ci#include <linux/string.h> 118c2ecf20Sopenharmony_ci#include <linux/errno.h> 128c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 138c2ecf20Sopenharmony_ci#include <linux/atmdev.h> 148c2ecf20Sopenharmony_ci#include <linux/atmclip.h> 158c2ecf20Sopenharmony_ci#include <linux/rtnetlink.h> 168c2ecf20Sopenharmony_ci#include <linux/file.h> /* for fput */ 178c2ecf20Sopenharmony_ci#include <net/netlink.h> 188c2ecf20Sopenharmony_ci#include <net/pkt_sched.h> 198c2ecf20Sopenharmony_ci#include <net/pkt_cls.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* 228c2ecf20Sopenharmony_ci * The ATM queuing discipline provides a framework for invoking classifiers 238c2ecf20Sopenharmony_ci * (aka "filters"), which in turn select classes of this queuing discipline. 248c2ecf20Sopenharmony_ci * Each class maps the flow(s) it is handling to a given VC. Multiple classes 258c2ecf20Sopenharmony_ci * may share the same VC. 268c2ecf20Sopenharmony_ci * 278c2ecf20Sopenharmony_ci * When creating a class, VCs are specified by passing the number of the open 288c2ecf20Sopenharmony_ci * socket descriptor by which the calling process references the VC. The kernel 298c2ecf20Sopenharmony_ci * keeps the VC open at least until all classes using it are removed. 308c2ecf20Sopenharmony_ci * 318c2ecf20Sopenharmony_ci * In this file, most functions are named atm_tc_* to avoid confusion with all 328c2ecf20Sopenharmony_ci * the atm_* in net/atm. This naming convention differs from what's used in the 338c2ecf20Sopenharmony_ci * rest of net/sched. 348c2ecf20Sopenharmony_ci * 358c2ecf20Sopenharmony_ci * Known bugs: 368c2ecf20Sopenharmony_ci * - sometimes messes up the IP stack 378c2ecf20Sopenharmony_ci * - any manipulations besides the few operations described in the README, are 388c2ecf20Sopenharmony_ci * untested and likely to crash the system 398c2ecf20Sopenharmony_ci * - should lock the flow while there is data in the queue (?) 408c2ecf20Sopenharmony_ci */ 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define VCC2FLOW(vcc) ((struct atm_flow_data *) ((vcc)->user_back)) 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct atm_flow_data { 458c2ecf20Sopenharmony_ci struct Qdisc_class_common common; 468c2ecf20Sopenharmony_ci struct Qdisc *q; /* FIFO, TBF, etc. */ 478c2ecf20Sopenharmony_ci struct tcf_proto __rcu *filter_list; 488c2ecf20Sopenharmony_ci struct tcf_block *block; 498c2ecf20Sopenharmony_ci struct atm_vcc *vcc; /* VCC; NULL if VCC is closed */ 508c2ecf20Sopenharmony_ci void (*old_pop)(struct atm_vcc *vcc, 518c2ecf20Sopenharmony_ci struct sk_buff *skb); /* chaining */ 528c2ecf20Sopenharmony_ci struct atm_qdisc_data *parent; /* parent qdisc */ 538c2ecf20Sopenharmony_ci struct socket *sock; /* for closing */ 548c2ecf20Sopenharmony_ci int ref; /* reference count */ 558c2ecf20Sopenharmony_ci struct gnet_stats_basic_packed bstats; 568c2ecf20Sopenharmony_ci struct gnet_stats_queue qstats; 578c2ecf20Sopenharmony_ci struct list_head list; 588c2ecf20Sopenharmony_ci struct atm_flow_data *excess; /* flow for excess traffic; 598c2ecf20Sopenharmony_ci NULL to set CLP instead */ 608c2ecf20Sopenharmony_ci int hdr_len; 618c2ecf20Sopenharmony_ci unsigned char hdr[]; /* header data; MUST BE LAST */ 628c2ecf20Sopenharmony_ci}; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistruct atm_qdisc_data { 658c2ecf20Sopenharmony_ci struct atm_flow_data link; /* unclassified skbs go here */ 668c2ecf20Sopenharmony_ci struct list_head flows; /* NB: "link" is also on this 678c2ecf20Sopenharmony_ci list */ 688c2ecf20Sopenharmony_ci struct tasklet_struct task; /* dequeue tasklet */ 698c2ecf20Sopenharmony_ci}; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci/* ------------------------- Class/flow operations ------------------------- */ 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic inline struct atm_flow_data *lookup_flow(struct Qdisc *sch, u32 classid) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 768c2ecf20Sopenharmony_ci struct atm_flow_data *flow; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci list_for_each_entry(flow, &p->flows, list) { 798c2ecf20Sopenharmony_ci if (flow->common.classid == classid) 808c2ecf20Sopenharmony_ci return flow; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci return NULL; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic int atm_tc_graft(struct Qdisc *sch, unsigned long arg, 868c2ecf20Sopenharmony_ci struct Qdisc *new, struct Qdisc **old, 878c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 908c2ecf20Sopenharmony_ci struct atm_flow_data *flow = (struct atm_flow_data *)arg; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci pr_debug("atm_tc_graft(sch %p,[qdisc %p],flow %p,new %p,old %p)\n", 938c2ecf20Sopenharmony_ci sch, p, flow, new, old); 948c2ecf20Sopenharmony_ci if (list_empty(&flow->list)) 958c2ecf20Sopenharmony_ci return -EINVAL; 968c2ecf20Sopenharmony_ci if (!new) 978c2ecf20Sopenharmony_ci new = &noop_qdisc; 988c2ecf20Sopenharmony_ci *old = flow->q; 998c2ecf20Sopenharmony_ci flow->q = new; 1008c2ecf20Sopenharmony_ci if (*old) 1018c2ecf20Sopenharmony_ci qdisc_reset(*old); 1028c2ecf20Sopenharmony_ci return 0; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic struct Qdisc *atm_tc_leaf(struct Qdisc *sch, unsigned long cl) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci struct atm_flow_data *flow = (struct atm_flow_data *)cl; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci pr_debug("atm_tc_leaf(sch %p,flow %p)\n", sch, flow); 1108c2ecf20Sopenharmony_ci return flow ? flow->q : NULL; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic unsigned long atm_tc_find(struct Qdisc *sch, u32 classid) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct atm_qdisc_data *p __maybe_unused = qdisc_priv(sch); 1168c2ecf20Sopenharmony_ci struct atm_flow_data *flow; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci pr_debug("%s(sch %p,[qdisc %p],classid %x)\n", __func__, sch, p, classid); 1198c2ecf20Sopenharmony_ci flow = lookup_flow(sch, classid); 1208c2ecf20Sopenharmony_ci pr_debug("%s: flow %p\n", __func__, flow); 1218c2ecf20Sopenharmony_ci return (unsigned long)flow; 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic unsigned long atm_tc_bind_filter(struct Qdisc *sch, 1258c2ecf20Sopenharmony_ci unsigned long parent, u32 classid) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct atm_qdisc_data *p __maybe_unused = qdisc_priv(sch); 1288c2ecf20Sopenharmony_ci struct atm_flow_data *flow; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci pr_debug("%s(sch %p,[qdisc %p],classid %x)\n", __func__, sch, p, classid); 1318c2ecf20Sopenharmony_ci flow = lookup_flow(sch, classid); 1328c2ecf20Sopenharmony_ci if (flow) 1338c2ecf20Sopenharmony_ci flow->ref++; 1348c2ecf20Sopenharmony_ci pr_debug("%s: flow %p\n", __func__, flow); 1358c2ecf20Sopenharmony_ci return (unsigned long)flow; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci/* 1398c2ecf20Sopenharmony_ci * atm_tc_put handles all destructions, including the ones that are explicitly 1408c2ecf20Sopenharmony_ci * requested (atm_tc_destroy, etc.). The assumption here is that we never drop 1418c2ecf20Sopenharmony_ci * anything that still seems to be in use. 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_cistatic void atm_tc_put(struct Qdisc *sch, unsigned long cl) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 1468c2ecf20Sopenharmony_ci struct atm_flow_data *flow = (struct atm_flow_data *)cl; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci pr_debug("atm_tc_put(sch %p,[qdisc %p],flow %p)\n", sch, p, flow); 1498c2ecf20Sopenharmony_ci if (--flow->ref) 1508c2ecf20Sopenharmony_ci return; 1518c2ecf20Sopenharmony_ci pr_debug("atm_tc_put: destroying\n"); 1528c2ecf20Sopenharmony_ci list_del_init(&flow->list); 1538c2ecf20Sopenharmony_ci pr_debug("atm_tc_put: qdisc %p\n", flow->q); 1548c2ecf20Sopenharmony_ci qdisc_put(flow->q); 1558c2ecf20Sopenharmony_ci tcf_block_put(flow->block); 1568c2ecf20Sopenharmony_ci if (flow->sock) { 1578c2ecf20Sopenharmony_ci pr_debug("atm_tc_put: f_count %ld\n", 1588c2ecf20Sopenharmony_ci file_count(flow->sock->file)); 1598c2ecf20Sopenharmony_ci flow->vcc->pop = flow->old_pop; 1608c2ecf20Sopenharmony_ci sockfd_put(flow->sock); 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci if (flow->excess) 1638c2ecf20Sopenharmony_ci atm_tc_put(sch, (unsigned long)flow->excess); 1648c2ecf20Sopenharmony_ci if (flow != &p->link) 1658c2ecf20Sopenharmony_ci kfree(flow); 1668c2ecf20Sopenharmony_ci /* 1678c2ecf20Sopenharmony_ci * If flow == &p->link, the qdisc no longer works at this point and 1688c2ecf20Sopenharmony_ci * needs to be removed. (By the caller of atm_tc_put.) 1698c2ecf20Sopenharmony_ci */ 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic void sch_atm_pop(struct atm_vcc *vcc, struct sk_buff *skb) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = VCC2FLOW(vcc)->parent; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci pr_debug("sch_atm_pop(vcc %p,skb %p,[qdisc %p])\n", vcc, skb, p); 1778c2ecf20Sopenharmony_ci VCC2FLOW(vcc)->old_pop(vcc, skb); 1788c2ecf20Sopenharmony_ci tasklet_schedule(&p->task); 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic const u8 llc_oui_ip[] = { 1828c2ecf20Sopenharmony_ci 0xaa, /* DSAP: non-ISO */ 1838c2ecf20Sopenharmony_ci 0xaa, /* SSAP: non-ISO */ 1848c2ecf20Sopenharmony_ci 0x03, /* Ctrl: Unnumbered Information Command PDU */ 1858c2ecf20Sopenharmony_ci 0x00, /* OUI: EtherType */ 1868c2ecf20Sopenharmony_ci 0x00, 0x00, 1878c2ecf20Sopenharmony_ci 0x08, 0x00 1888c2ecf20Sopenharmony_ci}; /* Ethertype IP (0800) */ 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic const struct nla_policy atm_policy[TCA_ATM_MAX + 1] = { 1918c2ecf20Sopenharmony_ci [TCA_ATM_FD] = { .type = NLA_U32 }, 1928c2ecf20Sopenharmony_ci [TCA_ATM_EXCESS] = { .type = NLA_U32 }, 1938c2ecf20Sopenharmony_ci}; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent, 1968c2ecf20Sopenharmony_ci struct nlattr **tca, unsigned long *arg, 1978c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 2008c2ecf20Sopenharmony_ci struct atm_flow_data *flow = (struct atm_flow_data *)*arg; 2018c2ecf20Sopenharmony_ci struct atm_flow_data *excess = NULL; 2028c2ecf20Sopenharmony_ci struct nlattr *opt = tca[TCA_OPTIONS]; 2038c2ecf20Sopenharmony_ci struct nlattr *tb[TCA_ATM_MAX + 1]; 2048c2ecf20Sopenharmony_ci struct socket *sock; 2058c2ecf20Sopenharmony_ci int fd, error, hdr_len; 2068c2ecf20Sopenharmony_ci void *hdr; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci pr_debug("atm_tc_change(sch %p,[qdisc %p],classid %x,parent %x," 2098c2ecf20Sopenharmony_ci "flow %p,opt %p)\n", sch, p, classid, parent, flow, opt); 2108c2ecf20Sopenharmony_ci /* 2118c2ecf20Sopenharmony_ci * The concept of parents doesn't apply for this qdisc. 2128c2ecf20Sopenharmony_ci */ 2138c2ecf20Sopenharmony_ci if (parent && parent != TC_H_ROOT && parent != sch->handle) 2148c2ecf20Sopenharmony_ci return -EINVAL; 2158c2ecf20Sopenharmony_ci /* 2168c2ecf20Sopenharmony_ci * ATM classes cannot be changed. In order to change properties of the 2178c2ecf20Sopenharmony_ci * ATM connection, that socket needs to be modified directly (via the 2188c2ecf20Sopenharmony_ci * native ATM API. In order to send a flow to a different VC, the old 2198c2ecf20Sopenharmony_ci * class needs to be removed and a new one added. (This may be changed 2208c2ecf20Sopenharmony_ci * later.) 2218c2ecf20Sopenharmony_ci */ 2228c2ecf20Sopenharmony_ci if (flow) 2238c2ecf20Sopenharmony_ci return -EBUSY; 2248c2ecf20Sopenharmony_ci if (opt == NULL) 2258c2ecf20Sopenharmony_ci return -EINVAL; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci error = nla_parse_nested_deprecated(tb, TCA_ATM_MAX, opt, atm_policy, 2288c2ecf20Sopenharmony_ci NULL); 2298c2ecf20Sopenharmony_ci if (error < 0) 2308c2ecf20Sopenharmony_ci return error; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (!tb[TCA_ATM_FD]) 2338c2ecf20Sopenharmony_ci return -EINVAL; 2348c2ecf20Sopenharmony_ci fd = nla_get_u32(tb[TCA_ATM_FD]); 2358c2ecf20Sopenharmony_ci pr_debug("atm_tc_change: fd %d\n", fd); 2368c2ecf20Sopenharmony_ci if (tb[TCA_ATM_HDR]) { 2378c2ecf20Sopenharmony_ci hdr_len = nla_len(tb[TCA_ATM_HDR]); 2388c2ecf20Sopenharmony_ci hdr = nla_data(tb[TCA_ATM_HDR]); 2398c2ecf20Sopenharmony_ci } else { 2408c2ecf20Sopenharmony_ci hdr_len = RFC1483LLC_LEN; 2418c2ecf20Sopenharmony_ci hdr = NULL; /* default LLC/SNAP for IP */ 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci if (!tb[TCA_ATM_EXCESS]) 2448c2ecf20Sopenharmony_ci excess = NULL; 2458c2ecf20Sopenharmony_ci else { 2468c2ecf20Sopenharmony_ci excess = (struct atm_flow_data *) 2478c2ecf20Sopenharmony_ci atm_tc_find(sch, nla_get_u32(tb[TCA_ATM_EXCESS])); 2488c2ecf20Sopenharmony_ci if (!excess) 2498c2ecf20Sopenharmony_ci return -ENOENT; 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci pr_debug("atm_tc_change: type %d, payload %d, hdr_len %d\n", 2528c2ecf20Sopenharmony_ci opt->nla_type, nla_len(opt), hdr_len); 2538c2ecf20Sopenharmony_ci sock = sockfd_lookup(fd, &error); 2548c2ecf20Sopenharmony_ci if (!sock) 2558c2ecf20Sopenharmony_ci return error; /* f_count++ */ 2568c2ecf20Sopenharmony_ci pr_debug("atm_tc_change: f_count %ld\n", file_count(sock->file)); 2578c2ecf20Sopenharmony_ci if (sock->ops->family != PF_ATMSVC && sock->ops->family != PF_ATMPVC) { 2588c2ecf20Sopenharmony_ci error = -EPROTOTYPE; 2598c2ecf20Sopenharmony_ci goto err_out; 2608c2ecf20Sopenharmony_ci } 2618c2ecf20Sopenharmony_ci /* @@@ should check if the socket is really operational or we'll crash 2628c2ecf20Sopenharmony_ci on vcc->send */ 2638c2ecf20Sopenharmony_ci if (classid) { 2648c2ecf20Sopenharmony_ci if (TC_H_MAJ(classid ^ sch->handle)) { 2658c2ecf20Sopenharmony_ci pr_debug("atm_tc_change: classid mismatch\n"); 2668c2ecf20Sopenharmony_ci error = -EINVAL; 2678c2ecf20Sopenharmony_ci goto err_out; 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci } else { 2708c2ecf20Sopenharmony_ci int i; 2718c2ecf20Sopenharmony_ci unsigned long cl; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci for (i = 1; i < 0x8000; i++) { 2748c2ecf20Sopenharmony_ci classid = TC_H_MAKE(sch->handle, 0x8000 | i); 2758c2ecf20Sopenharmony_ci cl = atm_tc_find(sch, classid); 2768c2ecf20Sopenharmony_ci if (!cl) 2778c2ecf20Sopenharmony_ci break; 2788c2ecf20Sopenharmony_ci } 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci pr_debug("atm_tc_change: new id %x\n", classid); 2818c2ecf20Sopenharmony_ci flow = kzalloc(sizeof(struct atm_flow_data) + hdr_len, GFP_KERNEL); 2828c2ecf20Sopenharmony_ci pr_debug("atm_tc_change: flow %p\n", flow); 2838c2ecf20Sopenharmony_ci if (!flow) { 2848c2ecf20Sopenharmony_ci error = -ENOBUFS; 2858c2ecf20Sopenharmony_ci goto err_out; 2868c2ecf20Sopenharmony_ci } 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci error = tcf_block_get(&flow->block, &flow->filter_list, sch, 2898c2ecf20Sopenharmony_ci extack); 2908c2ecf20Sopenharmony_ci if (error) { 2918c2ecf20Sopenharmony_ci kfree(flow); 2928c2ecf20Sopenharmony_ci goto err_out; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci flow->q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, classid, 2968c2ecf20Sopenharmony_ci extack); 2978c2ecf20Sopenharmony_ci if (!flow->q) 2988c2ecf20Sopenharmony_ci flow->q = &noop_qdisc; 2998c2ecf20Sopenharmony_ci pr_debug("atm_tc_change: qdisc %p\n", flow->q); 3008c2ecf20Sopenharmony_ci flow->sock = sock; 3018c2ecf20Sopenharmony_ci flow->vcc = ATM_SD(sock); /* speedup */ 3028c2ecf20Sopenharmony_ci flow->vcc->user_back = flow; 3038c2ecf20Sopenharmony_ci pr_debug("atm_tc_change: vcc %p\n", flow->vcc); 3048c2ecf20Sopenharmony_ci flow->old_pop = flow->vcc->pop; 3058c2ecf20Sopenharmony_ci flow->parent = p; 3068c2ecf20Sopenharmony_ci flow->vcc->pop = sch_atm_pop; 3078c2ecf20Sopenharmony_ci flow->common.classid = classid; 3088c2ecf20Sopenharmony_ci flow->ref = 1; 3098c2ecf20Sopenharmony_ci flow->excess = excess; 3108c2ecf20Sopenharmony_ci list_add(&flow->list, &p->link.list); 3118c2ecf20Sopenharmony_ci flow->hdr_len = hdr_len; 3128c2ecf20Sopenharmony_ci if (hdr) 3138c2ecf20Sopenharmony_ci memcpy(flow->hdr, hdr, hdr_len); 3148c2ecf20Sopenharmony_ci else 3158c2ecf20Sopenharmony_ci memcpy(flow->hdr, llc_oui_ip, sizeof(llc_oui_ip)); 3168c2ecf20Sopenharmony_ci *arg = (unsigned long)flow; 3178c2ecf20Sopenharmony_ci return 0; 3188c2ecf20Sopenharmony_cierr_out: 3198c2ecf20Sopenharmony_ci sockfd_put(sock); 3208c2ecf20Sopenharmony_ci return error; 3218c2ecf20Sopenharmony_ci} 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_cistatic int atm_tc_delete(struct Qdisc *sch, unsigned long arg) 3248c2ecf20Sopenharmony_ci{ 3258c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 3268c2ecf20Sopenharmony_ci struct atm_flow_data *flow = (struct atm_flow_data *)arg; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci pr_debug("atm_tc_delete(sch %p,[qdisc %p],flow %p)\n", sch, p, flow); 3298c2ecf20Sopenharmony_ci if (list_empty(&flow->list)) 3308c2ecf20Sopenharmony_ci return -EINVAL; 3318c2ecf20Sopenharmony_ci if (rcu_access_pointer(flow->filter_list) || flow == &p->link) 3328c2ecf20Sopenharmony_ci return -EBUSY; 3338c2ecf20Sopenharmony_ci /* 3348c2ecf20Sopenharmony_ci * Reference count must be 2: one for "keepalive" (set at class 3358c2ecf20Sopenharmony_ci * creation), and one for the reference held when calling delete. 3368c2ecf20Sopenharmony_ci */ 3378c2ecf20Sopenharmony_ci if (flow->ref < 2) { 3388c2ecf20Sopenharmony_ci pr_err("atm_tc_delete: flow->ref == %d\n", flow->ref); 3398c2ecf20Sopenharmony_ci return -EINVAL; 3408c2ecf20Sopenharmony_ci } 3418c2ecf20Sopenharmony_ci if (flow->ref > 2) 3428c2ecf20Sopenharmony_ci return -EBUSY; /* catch references via excess, etc. */ 3438c2ecf20Sopenharmony_ci atm_tc_put(sch, arg); 3448c2ecf20Sopenharmony_ci return 0; 3458c2ecf20Sopenharmony_ci} 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_cistatic void atm_tc_walk(struct Qdisc *sch, struct qdisc_walker *walker) 3488c2ecf20Sopenharmony_ci{ 3498c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 3508c2ecf20Sopenharmony_ci struct atm_flow_data *flow; 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci pr_debug("atm_tc_walk(sch %p,[qdisc %p],walker %p)\n", sch, p, walker); 3538c2ecf20Sopenharmony_ci if (walker->stop) 3548c2ecf20Sopenharmony_ci return; 3558c2ecf20Sopenharmony_ci list_for_each_entry(flow, &p->flows, list) { 3568c2ecf20Sopenharmony_ci if (walker->count >= walker->skip && 3578c2ecf20Sopenharmony_ci walker->fn(sch, (unsigned long)flow, walker) < 0) { 3588c2ecf20Sopenharmony_ci walker->stop = 1; 3598c2ecf20Sopenharmony_ci break; 3608c2ecf20Sopenharmony_ci } 3618c2ecf20Sopenharmony_ci walker->count++; 3628c2ecf20Sopenharmony_ci } 3638c2ecf20Sopenharmony_ci} 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_cistatic struct tcf_block *atm_tc_tcf_block(struct Qdisc *sch, unsigned long cl, 3668c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 3678c2ecf20Sopenharmony_ci{ 3688c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 3698c2ecf20Sopenharmony_ci struct atm_flow_data *flow = (struct atm_flow_data *)cl; 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci pr_debug("atm_tc_find_tcf(sch %p,[qdisc %p],flow %p)\n", sch, p, flow); 3728c2ecf20Sopenharmony_ci return flow ? flow->block : p->link.block; 3738c2ecf20Sopenharmony_ci} 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci/* --------------------------- Qdisc operations ---------------------------- */ 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_cistatic int atm_tc_enqueue(struct sk_buff *skb, struct Qdisc *sch, 3788c2ecf20Sopenharmony_ci struct sk_buff **to_free) 3798c2ecf20Sopenharmony_ci{ 3808c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 3818c2ecf20Sopenharmony_ci struct atm_flow_data *flow; 3828c2ecf20Sopenharmony_ci struct tcf_result res; 3838c2ecf20Sopenharmony_ci int result; 3848c2ecf20Sopenharmony_ci int ret = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_ci pr_debug("atm_tc_enqueue(skb %p,sch %p,[qdisc %p])\n", skb, sch, p); 3878c2ecf20Sopenharmony_ci result = TC_ACT_OK; /* be nice to gcc */ 3888c2ecf20Sopenharmony_ci flow = NULL; 3898c2ecf20Sopenharmony_ci if (TC_H_MAJ(skb->priority) != sch->handle || 3908c2ecf20Sopenharmony_ci !(flow = (struct atm_flow_data *)atm_tc_find(sch, skb->priority))) { 3918c2ecf20Sopenharmony_ci struct tcf_proto *fl; 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci list_for_each_entry(flow, &p->flows, list) { 3948c2ecf20Sopenharmony_ci fl = rcu_dereference_bh(flow->filter_list); 3958c2ecf20Sopenharmony_ci if (fl) { 3968c2ecf20Sopenharmony_ci result = tcf_classify(skb, fl, &res, true); 3978c2ecf20Sopenharmony_ci if (result < 0) 3988c2ecf20Sopenharmony_ci continue; 3998c2ecf20Sopenharmony_ci if (result == TC_ACT_SHOT) 4008c2ecf20Sopenharmony_ci goto done; 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci flow = (struct atm_flow_data *)res.class; 4038c2ecf20Sopenharmony_ci if (!flow) 4048c2ecf20Sopenharmony_ci flow = lookup_flow(sch, res.classid); 4058c2ecf20Sopenharmony_ci goto drop; 4068c2ecf20Sopenharmony_ci } 4078c2ecf20Sopenharmony_ci } 4088c2ecf20Sopenharmony_ci flow = NULL; 4098c2ecf20Sopenharmony_cidone: 4108c2ecf20Sopenharmony_ci ; 4118c2ecf20Sopenharmony_ci } 4128c2ecf20Sopenharmony_ci if (!flow) { 4138c2ecf20Sopenharmony_ci flow = &p->link; 4148c2ecf20Sopenharmony_ci } else { 4158c2ecf20Sopenharmony_ci if (flow->vcc) 4168c2ecf20Sopenharmony_ci ATM_SKB(skb)->atm_options = flow->vcc->atm_options; 4178c2ecf20Sopenharmony_ci /*@@@ looks good ... but it's not supposed to work :-) */ 4188c2ecf20Sopenharmony_ci#ifdef CONFIG_NET_CLS_ACT 4198c2ecf20Sopenharmony_ci switch (result) { 4208c2ecf20Sopenharmony_ci case TC_ACT_QUEUED: 4218c2ecf20Sopenharmony_ci case TC_ACT_STOLEN: 4228c2ecf20Sopenharmony_ci case TC_ACT_TRAP: 4238c2ecf20Sopenharmony_ci __qdisc_drop(skb, to_free); 4248c2ecf20Sopenharmony_ci return NET_XMIT_SUCCESS | __NET_XMIT_STOLEN; 4258c2ecf20Sopenharmony_ci case TC_ACT_SHOT: 4268c2ecf20Sopenharmony_ci __qdisc_drop(skb, to_free); 4278c2ecf20Sopenharmony_ci goto drop; 4288c2ecf20Sopenharmony_ci case TC_ACT_RECLASSIFY: 4298c2ecf20Sopenharmony_ci if (flow->excess) 4308c2ecf20Sopenharmony_ci flow = flow->excess; 4318c2ecf20Sopenharmony_ci else 4328c2ecf20Sopenharmony_ci ATM_SKB(skb)->atm_options |= ATM_ATMOPT_CLP; 4338c2ecf20Sopenharmony_ci break; 4348c2ecf20Sopenharmony_ci } 4358c2ecf20Sopenharmony_ci#endif 4368c2ecf20Sopenharmony_ci } 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci ret = qdisc_enqueue(skb, flow->q, to_free); 4398c2ecf20Sopenharmony_ci if (ret != NET_XMIT_SUCCESS) { 4408c2ecf20Sopenharmony_cidrop: __maybe_unused 4418c2ecf20Sopenharmony_ci if (net_xmit_drop_count(ret)) { 4428c2ecf20Sopenharmony_ci qdisc_qstats_drop(sch); 4438c2ecf20Sopenharmony_ci if (flow) 4448c2ecf20Sopenharmony_ci flow->qstats.drops++; 4458c2ecf20Sopenharmony_ci } 4468c2ecf20Sopenharmony_ci return ret; 4478c2ecf20Sopenharmony_ci } 4488c2ecf20Sopenharmony_ci /* 4498c2ecf20Sopenharmony_ci * Okay, this may seem weird. We pretend we've dropped the packet if 4508c2ecf20Sopenharmony_ci * it goes via ATM. The reason for this is that the outer qdisc 4518c2ecf20Sopenharmony_ci * expects to be able to q->dequeue the packet later on if we return 4528c2ecf20Sopenharmony_ci * success at this place. Also, sch->q.qdisc needs to reflect whether 4538c2ecf20Sopenharmony_ci * there is a packet egligible for dequeuing or not. Note that the 4548c2ecf20Sopenharmony_ci * statistics of the outer qdisc are necessarily wrong because of all 4558c2ecf20Sopenharmony_ci * this. There's currently no correct solution for this. 4568c2ecf20Sopenharmony_ci */ 4578c2ecf20Sopenharmony_ci if (flow == &p->link) { 4588c2ecf20Sopenharmony_ci sch->q.qlen++; 4598c2ecf20Sopenharmony_ci return NET_XMIT_SUCCESS; 4608c2ecf20Sopenharmony_ci } 4618c2ecf20Sopenharmony_ci tasklet_schedule(&p->task); 4628c2ecf20Sopenharmony_ci return NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; 4638c2ecf20Sopenharmony_ci} 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci/* 4668c2ecf20Sopenharmony_ci * Dequeue packets and send them over ATM. Note that we quite deliberately 4678c2ecf20Sopenharmony_ci * avoid checking net_device's flow control here, simply because sch_atm 4688c2ecf20Sopenharmony_ci * uses its own channels, which have nothing to do with any CLIP/LANE/or 4698c2ecf20Sopenharmony_ci * non-ATM interfaces. 4708c2ecf20Sopenharmony_ci */ 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_cistatic void sch_atm_dequeue(unsigned long data) 4738c2ecf20Sopenharmony_ci{ 4748c2ecf20Sopenharmony_ci struct Qdisc *sch = (struct Qdisc *)data; 4758c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 4768c2ecf20Sopenharmony_ci struct atm_flow_data *flow; 4778c2ecf20Sopenharmony_ci struct sk_buff *skb; 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci pr_debug("sch_atm_dequeue(sch %p,[qdisc %p])\n", sch, p); 4808c2ecf20Sopenharmony_ci list_for_each_entry(flow, &p->flows, list) { 4818c2ecf20Sopenharmony_ci if (flow == &p->link) 4828c2ecf20Sopenharmony_ci continue; 4838c2ecf20Sopenharmony_ci /* 4848c2ecf20Sopenharmony_ci * If traffic is properly shaped, this won't generate nasty 4858c2ecf20Sopenharmony_ci * little bursts. Otherwise, it may ... (but that's okay) 4868c2ecf20Sopenharmony_ci */ 4878c2ecf20Sopenharmony_ci while ((skb = flow->q->ops->peek(flow->q))) { 4888c2ecf20Sopenharmony_ci if (!atm_may_send(flow->vcc, skb->truesize)) 4898c2ecf20Sopenharmony_ci break; 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci skb = qdisc_dequeue_peeked(flow->q); 4928c2ecf20Sopenharmony_ci if (unlikely(!skb)) 4938c2ecf20Sopenharmony_ci break; 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci qdisc_bstats_update(sch, skb); 4968c2ecf20Sopenharmony_ci bstats_update(&flow->bstats, skb); 4978c2ecf20Sopenharmony_ci pr_debug("atm_tc_dequeue: sending on class %p\n", flow); 4988c2ecf20Sopenharmony_ci /* remove any LL header somebody else has attached */ 4998c2ecf20Sopenharmony_ci skb_pull(skb, skb_network_offset(skb)); 5008c2ecf20Sopenharmony_ci if (skb_headroom(skb) < flow->hdr_len) { 5018c2ecf20Sopenharmony_ci struct sk_buff *new; 5028c2ecf20Sopenharmony_ci 5038c2ecf20Sopenharmony_ci new = skb_realloc_headroom(skb, flow->hdr_len); 5048c2ecf20Sopenharmony_ci dev_kfree_skb(skb); 5058c2ecf20Sopenharmony_ci if (!new) 5068c2ecf20Sopenharmony_ci continue; 5078c2ecf20Sopenharmony_ci skb = new; 5088c2ecf20Sopenharmony_ci } 5098c2ecf20Sopenharmony_ci pr_debug("sch_atm_dequeue: ip %p, data %p\n", 5108c2ecf20Sopenharmony_ci skb_network_header(skb), skb->data); 5118c2ecf20Sopenharmony_ci ATM_SKB(skb)->vcc = flow->vcc; 5128c2ecf20Sopenharmony_ci memcpy(skb_push(skb, flow->hdr_len), flow->hdr, 5138c2ecf20Sopenharmony_ci flow->hdr_len); 5148c2ecf20Sopenharmony_ci refcount_add(skb->truesize, 5158c2ecf20Sopenharmony_ci &sk_atm(flow->vcc)->sk_wmem_alloc); 5168c2ecf20Sopenharmony_ci /* atm.atm_options are already set by atm_tc_enqueue */ 5178c2ecf20Sopenharmony_ci flow->vcc->send(flow->vcc, skb); 5188c2ecf20Sopenharmony_ci } 5198c2ecf20Sopenharmony_ci } 5208c2ecf20Sopenharmony_ci} 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_cistatic struct sk_buff *atm_tc_dequeue(struct Qdisc *sch) 5238c2ecf20Sopenharmony_ci{ 5248c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 5258c2ecf20Sopenharmony_ci struct sk_buff *skb; 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ci pr_debug("atm_tc_dequeue(sch %p,[qdisc %p])\n", sch, p); 5288c2ecf20Sopenharmony_ci tasklet_schedule(&p->task); 5298c2ecf20Sopenharmony_ci skb = qdisc_dequeue_peeked(p->link.q); 5308c2ecf20Sopenharmony_ci if (skb) 5318c2ecf20Sopenharmony_ci sch->q.qlen--; 5328c2ecf20Sopenharmony_ci return skb; 5338c2ecf20Sopenharmony_ci} 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_cistatic struct sk_buff *atm_tc_peek(struct Qdisc *sch) 5368c2ecf20Sopenharmony_ci{ 5378c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci pr_debug("atm_tc_peek(sch %p,[qdisc %p])\n", sch, p); 5408c2ecf20Sopenharmony_ci 5418c2ecf20Sopenharmony_ci return p->link.q->ops->peek(p->link.q); 5428c2ecf20Sopenharmony_ci} 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_cistatic int atm_tc_init(struct Qdisc *sch, struct nlattr *opt, 5458c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 5468c2ecf20Sopenharmony_ci{ 5478c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 5488c2ecf20Sopenharmony_ci int err; 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci pr_debug("atm_tc_init(sch %p,[qdisc %p],opt %p)\n", sch, p, opt); 5518c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&p->flows); 5528c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&p->link.list); 5538c2ecf20Sopenharmony_ci list_add(&p->link.list, &p->flows); 5548c2ecf20Sopenharmony_ci p->link.q = qdisc_create_dflt(sch->dev_queue, 5558c2ecf20Sopenharmony_ci &pfifo_qdisc_ops, sch->handle, extack); 5568c2ecf20Sopenharmony_ci if (!p->link.q) 5578c2ecf20Sopenharmony_ci p->link.q = &noop_qdisc; 5588c2ecf20Sopenharmony_ci pr_debug("atm_tc_init: link (%p) qdisc %p\n", &p->link, p->link.q); 5598c2ecf20Sopenharmony_ci p->link.vcc = NULL; 5608c2ecf20Sopenharmony_ci p->link.sock = NULL; 5618c2ecf20Sopenharmony_ci p->link.common.classid = sch->handle; 5628c2ecf20Sopenharmony_ci p->link.ref = 1; 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci err = tcf_block_get(&p->link.block, &p->link.filter_list, sch, 5658c2ecf20Sopenharmony_ci extack); 5668c2ecf20Sopenharmony_ci if (err) 5678c2ecf20Sopenharmony_ci return err; 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_ci tasklet_init(&p->task, sch_atm_dequeue, (unsigned long)sch); 5708c2ecf20Sopenharmony_ci return 0; 5718c2ecf20Sopenharmony_ci} 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_cistatic void atm_tc_reset(struct Qdisc *sch) 5748c2ecf20Sopenharmony_ci{ 5758c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 5768c2ecf20Sopenharmony_ci struct atm_flow_data *flow; 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci pr_debug("atm_tc_reset(sch %p,[qdisc %p])\n", sch, p); 5798c2ecf20Sopenharmony_ci list_for_each_entry(flow, &p->flows, list) 5808c2ecf20Sopenharmony_ci qdisc_reset(flow->q); 5818c2ecf20Sopenharmony_ci} 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_cistatic void atm_tc_destroy(struct Qdisc *sch) 5848c2ecf20Sopenharmony_ci{ 5858c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 5868c2ecf20Sopenharmony_ci struct atm_flow_data *flow, *tmp; 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci pr_debug("atm_tc_destroy(sch %p,[qdisc %p])\n", sch, p); 5898c2ecf20Sopenharmony_ci list_for_each_entry(flow, &p->flows, list) { 5908c2ecf20Sopenharmony_ci tcf_block_put(flow->block); 5918c2ecf20Sopenharmony_ci flow->block = NULL; 5928c2ecf20Sopenharmony_ci } 5938c2ecf20Sopenharmony_ci 5948c2ecf20Sopenharmony_ci list_for_each_entry_safe(flow, tmp, &p->flows, list) { 5958c2ecf20Sopenharmony_ci if (flow->ref > 1) 5968c2ecf20Sopenharmony_ci pr_err("atm_destroy: %p->ref = %d\n", flow, flow->ref); 5978c2ecf20Sopenharmony_ci atm_tc_put(sch, (unsigned long)flow); 5988c2ecf20Sopenharmony_ci } 5998c2ecf20Sopenharmony_ci tasklet_kill(&p->task); 6008c2ecf20Sopenharmony_ci} 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_cistatic int atm_tc_dump_class(struct Qdisc *sch, unsigned long cl, 6038c2ecf20Sopenharmony_ci struct sk_buff *skb, struct tcmsg *tcm) 6048c2ecf20Sopenharmony_ci{ 6058c2ecf20Sopenharmony_ci struct atm_qdisc_data *p = qdisc_priv(sch); 6068c2ecf20Sopenharmony_ci struct atm_flow_data *flow = (struct atm_flow_data *)cl; 6078c2ecf20Sopenharmony_ci struct nlattr *nest; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci pr_debug("atm_tc_dump_class(sch %p,[qdisc %p],flow %p,skb %p,tcm %p)\n", 6108c2ecf20Sopenharmony_ci sch, p, flow, skb, tcm); 6118c2ecf20Sopenharmony_ci if (list_empty(&flow->list)) 6128c2ecf20Sopenharmony_ci return -EINVAL; 6138c2ecf20Sopenharmony_ci tcm->tcm_handle = flow->common.classid; 6148c2ecf20Sopenharmony_ci tcm->tcm_info = flow->q->handle; 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_ci nest = nla_nest_start_noflag(skb, TCA_OPTIONS); 6178c2ecf20Sopenharmony_ci if (nest == NULL) 6188c2ecf20Sopenharmony_ci goto nla_put_failure; 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_ci if (nla_put(skb, TCA_ATM_HDR, flow->hdr_len, flow->hdr)) 6218c2ecf20Sopenharmony_ci goto nla_put_failure; 6228c2ecf20Sopenharmony_ci if (flow->vcc) { 6238c2ecf20Sopenharmony_ci struct sockaddr_atmpvc pvc; 6248c2ecf20Sopenharmony_ci int state; 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci memset(&pvc, 0, sizeof(pvc)); 6278c2ecf20Sopenharmony_ci pvc.sap_family = AF_ATMPVC; 6288c2ecf20Sopenharmony_ci pvc.sap_addr.itf = flow->vcc->dev ? flow->vcc->dev->number : -1; 6298c2ecf20Sopenharmony_ci pvc.sap_addr.vpi = flow->vcc->vpi; 6308c2ecf20Sopenharmony_ci pvc.sap_addr.vci = flow->vcc->vci; 6318c2ecf20Sopenharmony_ci if (nla_put(skb, TCA_ATM_ADDR, sizeof(pvc), &pvc)) 6328c2ecf20Sopenharmony_ci goto nla_put_failure; 6338c2ecf20Sopenharmony_ci state = ATM_VF2VS(flow->vcc->flags); 6348c2ecf20Sopenharmony_ci if (nla_put_u32(skb, TCA_ATM_STATE, state)) 6358c2ecf20Sopenharmony_ci goto nla_put_failure; 6368c2ecf20Sopenharmony_ci } 6378c2ecf20Sopenharmony_ci if (flow->excess) { 6388c2ecf20Sopenharmony_ci if (nla_put_u32(skb, TCA_ATM_EXCESS, flow->common.classid)) 6398c2ecf20Sopenharmony_ci goto nla_put_failure; 6408c2ecf20Sopenharmony_ci } else { 6418c2ecf20Sopenharmony_ci if (nla_put_u32(skb, TCA_ATM_EXCESS, 0)) 6428c2ecf20Sopenharmony_ci goto nla_put_failure; 6438c2ecf20Sopenharmony_ci } 6448c2ecf20Sopenharmony_ci return nla_nest_end(skb, nest); 6458c2ecf20Sopenharmony_ci 6468c2ecf20Sopenharmony_cinla_put_failure: 6478c2ecf20Sopenharmony_ci nla_nest_cancel(skb, nest); 6488c2ecf20Sopenharmony_ci return -1; 6498c2ecf20Sopenharmony_ci} 6508c2ecf20Sopenharmony_cistatic int 6518c2ecf20Sopenharmony_ciatm_tc_dump_class_stats(struct Qdisc *sch, unsigned long arg, 6528c2ecf20Sopenharmony_ci struct gnet_dump *d) 6538c2ecf20Sopenharmony_ci{ 6548c2ecf20Sopenharmony_ci struct atm_flow_data *flow = (struct atm_flow_data *)arg; 6558c2ecf20Sopenharmony_ci 6568c2ecf20Sopenharmony_ci if (gnet_stats_copy_basic(qdisc_root_sleeping_running(sch), 6578c2ecf20Sopenharmony_ci d, NULL, &flow->bstats) < 0 || 6588c2ecf20Sopenharmony_ci gnet_stats_copy_queue(d, NULL, &flow->qstats, flow->q->q.qlen) < 0) 6598c2ecf20Sopenharmony_ci return -1; 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci return 0; 6628c2ecf20Sopenharmony_ci} 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_cistatic int atm_tc_dump(struct Qdisc *sch, struct sk_buff *skb) 6658c2ecf20Sopenharmony_ci{ 6668c2ecf20Sopenharmony_ci return 0; 6678c2ecf20Sopenharmony_ci} 6688c2ecf20Sopenharmony_ci 6698c2ecf20Sopenharmony_cistatic const struct Qdisc_class_ops atm_class_ops = { 6708c2ecf20Sopenharmony_ci .graft = atm_tc_graft, 6718c2ecf20Sopenharmony_ci .leaf = atm_tc_leaf, 6728c2ecf20Sopenharmony_ci .find = atm_tc_find, 6738c2ecf20Sopenharmony_ci .change = atm_tc_change, 6748c2ecf20Sopenharmony_ci .delete = atm_tc_delete, 6758c2ecf20Sopenharmony_ci .walk = atm_tc_walk, 6768c2ecf20Sopenharmony_ci .tcf_block = atm_tc_tcf_block, 6778c2ecf20Sopenharmony_ci .bind_tcf = atm_tc_bind_filter, 6788c2ecf20Sopenharmony_ci .unbind_tcf = atm_tc_put, 6798c2ecf20Sopenharmony_ci .dump = atm_tc_dump_class, 6808c2ecf20Sopenharmony_ci .dump_stats = atm_tc_dump_class_stats, 6818c2ecf20Sopenharmony_ci}; 6828c2ecf20Sopenharmony_ci 6838c2ecf20Sopenharmony_cistatic struct Qdisc_ops atm_qdisc_ops __read_mostly = { 6848c2ecf20Sopenharmony_ci .cl_ops = &atm_class_ops, 6858c2ecf20Sopenharmony_ci .id = "atm", 6868c2ecf20Sopenharmony_ci .priv_size = sizeof(struct atm_qdisc_data), 6878c2ecf20Sopenharmony_ci .enqueue = atm_tc_enqueue, 6888c2ecf20Sopenharmony_ci .dequeue = atm_tc_dequeue, 6898c2ecf20Sopenharmony_ci .peek = atm_tc_peek, 6908c2ecf20Sopenharmony_ci .init = atm_tc_init, 6918c2ecf20Sopenharmony_ci .reset = atm_tc_reset, 6928c2ecf20Sopenharmony_ci .destroy = atm_tc_destroy, 6938c2ecf20Sopenharmony_ci .dump = atm_tc_dump, 6948c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 6958c2ecf20Sopenharmony_ci}; 6968c2ecf20Sopenharmony_ci 6978c2ecf20Sopenharmony_cistatic int __init atm_init(void) 6988c2ecf20Sopenharmony_ci{ 6998c2ecf20Sopenharmony_ci return register_qdisc(&atm_qdisc_ops); 7008c2ecf20Sopenharmony_ci} 7018c2ecf20Sopenharmony_ci 7028c2ecf20Sopenharmony_cistatic void __exit atm_exit(void) 7038c2ecf20Sopenharmony_ci{ 7048c2ecf20Sopenharmony_ci unregister_qdisc(&atm_qdisc_ops); 7058c2ecf20Sopenharmony_ci} 7068c2ecf20Sopenharmony_ci 7078c2ecf20Sopenharmony_cimodule_init(atm_init) 7088c2ecf20Sopenharmony_cimodule_exit(atm_exit) 7098c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 710