/* net/atm/proc.c - ATM /proc interface */ /* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ /* * The mechanism used here isn't designed for speed but rather for convenience * of implementation. We only return one entry per read system call, so we can * be reasonably sure not to overrun the page and race conditions may lead to * the addition or omission of some lines but never to any corruption of a * line's internal structure. * * Making the whole thing slightly more efficient is left as an exercise to the * reader. (Suggestions: wrapper which loops to get several entries per system * call; or make --left slightly more clever to avoid O(n^2) characteristics.) * I find it fast enough on my unloaded 266 MHz Pentium 2 :-) */ #include #include /* for EXPORT_SYMBOL */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for __init */ #include #include #include /* for HZ */ #include "resources.h" #include "common.h" /* atm_proc_init prototype */ #include "signaling.h" /* to get sigd - ugly too */ #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) #include #include "ipcommon.h" #endif #if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) #include "lec.h" #include "lec_arpc.h" #endif static ssize_t proc_dev_atm_read(struct file *file,char *buf,size_t count, loff_t *pos); static ssize_t proc_spec_atm_read(struct file *file,char *buf,size_t count, loff_t *pos); static struct file_operations proc_dev_atm_operations = { read: proc_dev_atm_read, }; static struct file_operations proc_spec_atm_operations = { read: proc_spec_atm_read, }; static void add_stats(char *buf,const char *aal, const struct k_atm_aal_stats *stats) { sprintf(strchr(buf,0),"%s ( %d %d %d %d %d )",aal, atomic_read(&stats->tx),atomic_read(&stats->tx_err), atomic_read(&stats->rx),atomic_read(&stats->rx_err), atomic_read(&stats->rx_drop)); } static void dev_info(const struct atm_dev *dev,char *buf) { int off,i; off = sprintf(buf,"%3d %-8s",dev->number,dev->type); for (i = 0; i < ESI_LEN; i++) off += sprintf(buf+off,"%02x",dev->esi[i]); strcat(buf," "); add_stats(buf,"0",&dev->stats.aal0); strcat(buf," "); add_stats(buf,"5",&dev->stats.aal5); sprintf(strchr(buf,0), "\t[%d]", atomic_read(&dev->refcnt)); strcat(buf,"\n"); } #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) #define SEQ_NO_VCC_TOKEN ((void *) 2) static void svc_addr(struct seq_file *seq, struct sockaddr_atmsvc *addr) { static int code[] = { 1,2,10,6,1,0 }; static int e164[] = { 1,8,4,6,1,0 }; if (*addr->sas_addr.pub) { seq_printf(seq, "%s", addr->sas_addr.pub); if (*addr->sas_addr.prv) seq_putc(seq, '+'); } else if (!*addr->sas_addr.prv) { seq_printf(seq, "%s", "(none)"); return; } if (*addr->sas_addr.prv) { unsigned char *prv = addr->sas_addr.prv; int *fields; int i, j; fields = *prv == ATM_AFI_E164 ? e164 : code; for (i = 0; fields[i]; i++) { for (j = fields[i]; j; j--) seq_printf(seq, "%02X", *prv++); if (fields[i+1]) seq_putc(seq, '.'); } } } static void atmarp_info(struct seq_file *seq, struct net_device *dev,struct atmarp_entry *entry, struct clip_vcc *clip_vcc) { unsigned long exp; char buf[17]; int svc, llc, off; svc = ((clip_vcc == SEQ_NO_VCC_TOKEN) || (clip_vcc->vcc->sk->family == AF_ATMSVC)); llc = ((clip_vcc == SEQ_NO_VCC_TOKEN) || (clip_vcc->encap)); if (clip_vcc == SEQ_NO_VCC_TOKEN) exp = entry->neigh->used; else exp = clip_vcc->last_use; exp = (jiffies - exp) / HZ; seq_printf(seq, "%-6s%-4s%-4s%5ld ", dev->name, svc ? "SVC" : "PVC", llc ? "LLC" : "NULL", exp); off = snprintf(buf, sizeof(buf)-1, "%d.%d.%d.%d", NIPQUAD(entry->ip)); while (off < 16) buf[off++] = ' '; buf[off] = '\0'; seq_printf(seq, "%s", buf); if (clip_vcc == SEQ_NO_VCC_TOKEN) { if (time_before(jiffies, entry->expires)) seq_printf(seq, "(resolving)\n"); else seq_printf(seq, "(expired, ref %d)\n", atomic_read(&entry->neigh->refcnt)); } else if (!svc) { seq_printf(seq, "%d.%d.%d\n", clip_vcc->vcc->dev->number, clip_vcc->vcc->vpi, clip_vcc->vcc->vci); } else { svc_addr(seq, &clip_vcc->vcc->remote); seq_putc(seq, '\n'); } } struct clip_seq_state { /* This member must be first. */ struct neigh_seq_state ns; /* Local to clip specific iteration. */ struct clip_vcc *vcc; }; static struct clip_vcc *clip_seq_next_vcc(struct atmarp_entry *e, struct clip_vcc *curr) { if (!curr) { curr = e->vccs; if (!curr) return SEQ_NO_VCC_TOKEN; return curr; } if (curr == SEQ_NO_VCC_TOKEN) return NULL; curr = curr->next; return curr; } static void *clip_seq_vcc_walk(struct clip_seq_state *state, struct atmarp_entry *e, loff_t *pos) { struct clip_vcc *vcc = state->vcc; vcc = clip_seq_next_vcc(e, vcc); if (vcc && pos != NULL) { while (*pos) { vcc = clip_seq_next_vcc(e, vcc); if (!vcc) break; --(*pos); } } state->vcc = vcc; return vcc; } static void *clip_seq_sub_iter(struct neigh_seq_state *_state, struct neighbour *n, loff_t *pos) { struct clip_seq_state *state = (struct clip_seq_state *) _state; return clip_seq_vcc_walk(state, NEIGH2ENTRY(n), pos); } static void *clip_seq_start(struct seq_file *seq, loff_t *pos) { return neigh_seq_start(seq, pos, clip_tbl_hook, NEIGH_SEQ_NEIGH_ONLY); } static int clip_seq_show(struct seq_file *seq, void *v) { static char atm_arp_banner[] = "IPitf TypeEncp Idle IP address ATM address\n"; if (v == SEQ_START_TOKEN) { seq_puts(seq, atm_arp_banner); } else { struct clip_seq_state *state = seq->private; struct neighbour *n = v; struct clip_vcc *vcc = state->vcc; atmarp_info(seq, n->dev, NEIGH2ENTRY(n), vcc); } return 0; } static struct seq_operations arp_seq_ops = { .start = clip_seq_start, .next = neigh_seq_next, .stop = neigh_seq_stop, .show = clip_seq_show, }; static int arp_seq_open(struct inode *inode, struct file *file) { struct clip_seq_state *state; struct seq_file *seq; int rc = -EAGAIN; if (!clip_tbl_hook) goto out; state = kmalloc(sizeof(*state), GFP_KERNEL); if (!state) { rc = -ENOMEM; goto out_kfree; } memset(state, 0, sizeof(*state)); state->ns.neigh_sub_iter = clip_seq_sub_iter; rc = seq_open(file, &arp_seq_ops); if (rc) goto out_kfree; seq = file->private_data; seq->private = state; out: return rc; out_kfree: kfree(state); goto out; } static struct file_operations arp_seq_fops = { .open = arp_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release_private, .owner = THIS_MODULE, }; #endif static void pvc_info(struct atm_vcc *vcc, char *buf, int clip_info) { static const char *class_name[] = { "off","UBR","CBR","VBR","ABR" }; static const char *aal_name[] = { "---", "1", "2", "3/4", /* 0- 3 */ "???", "5", "???", "???", /* 4- 7 */ "???", "???", "???", "???", /* 8-11 */ "???", "0", "???", "???"}; /* 12-15 */ int off; off = sprintf(buf,"%3d %3d %5d %-3s %7d %-5s %7d %-6s", vcc->dev->number,vcc->vpi,vcc->vci, vcc->qos.aal >= sizeof(aal_name)/sizeof(aal_name[0]) ? "err" : aal_name[vcc->qos.aal],vcc->qos.rxtp.min_pcr, class_name[vcc->qos.rxtp.traffic_class],vcc->qos.txtp.min_pcr, class_name[vcc->qos.txtp.traffic_class]); #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) if (clip_info && (vcc->push == atm_clip_ops->clip_push)) { struct clip_vcc *clip_vcc = CLIP_VCC(vcc); struct net_device *dev; dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev : NULL; off += sprintf(buf+off,"CLIP, Itf:%s, Encap:", dev ? dev->name : "none?"); if (clip_vcc->encap) off += sprintf(buf+off,"LLC/SNAP"); else off += sprintf(buf+off,"None"); } #endif strcpy(buf+off,"\n"); } static const char *vcc_state(struct atm_vcc *vcc) { static const char *map[] = { ATM_VS2TXT_MAP }; return map[ATM_VF2VS(vcc->flags)]; } static void vc_info(struct atm_vcc *vcc,char *buf) { char *here; here = buf+sprintf(buf,"%p ",vcc); if (!vcc->dev) here += sprintf(here,"Unassigned "); else here += sprintf(here,"%3d %3d %5d ",vcc->dev->number,vcc->vpi, vcc->vci); switch (vcc->sk->family) { case AF_ATMPVC: here += sprintf(here,"PVC"); break; case AF_ATMSVC: here += sprintf(here,"SVC"); break; default: here += sprintf(here,"%3d",vcc->sk->family); } here += sprintf(here," %04lx %5d %7d/%7d %7d/%7d\n",vcc->flags.bits, vcc->reply, atomic_read(&vcc->sk->wmem_alloc),vcc->sk->sndbuf, atomic_read(&vcc->sk->rmem_alloc),vcc->sk->rcvbuf); } static void svc_info(struct atm_vcc *vcc,char *buf) { char *here; int i; if (!vcc->dev) sprintf(buf,sizeof(void *) == 4 ? "N/A@%p%10s" : "N/A@%p%2s", vcc,""); else sprintf(buf,"%3d %3d %5d ",vcc->dev->number,vcc->vpi, vcc->vci); here = strchr(buf,0); here += sprintf(here,"%-10s ",vcc_state(vcc)); here += sprintf(here,"%s%s",vcc->remote.sas_addr.pub, *vcc->remote.sas_addr.pub && *vcc->remote.sas_addr.prv ? "+" : ""); if (*vcc->remote.sas_addr.prv) for (i = 0; i < ATM_ESA_LEN; i++) here += sprintf(here,"%02x", vcc->remote.sas_addr.prv[i]); strcat(here,"\n"); } #if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) static char* lec_arp_get_status_string(unsigned char status) { switch(status) { case ESI_UNKNOWN: return "ESI_UNKNOWN "; case ESI_ARP_PENDING: return "ESI_ARP_PENDING "; case ESI_VC_PENDING: return "ESI_VC_PENDING "; case ESI_FLUSH_PENDING: return "ESI_FLUSH_PENDING "; case ESI_FORWARD_DIRECT: return "ESI_FORWARD_DIRECT"; default: return " "; } } static void lec_info(struct lec_arp_table *entry, char *buf) { int j, offset=0; for(j=0;jmac_addr[j]); } offset+=sprintf(buf+offset, " "); for(j=0;jatm_addr[j]); } offset+=sprintf(buf+offset, " %s %4.4x", lec_arp_get_status_string(entry->status), entry->flags&0xffff); if (entry->vcc) { offset+=sprintf(buf+offset, "%3d %3d ", entry->vcc->vpi, entry->vcc->vci); } else offset+=sprintf(buf+offset, " "); if (entry->recv_vcc) { offset+=sprintf(buf+offset, " %3d %3d", entry->recv_vcc->vpi, entry->recv_vcc->vci); } sprintf(buf+offset,"\n"); } #endif static int atm_devices_info(loff_t pos,char *buf) { struct atm_dev *dev; struct list_head *p; int left; if (!pos) { return sprintf(buf,"Itf Type ESI/\"MAC\"addr " "AAL(TX,err,RX,err,drop) ... [refcnt]\n"); } left = pos-1; spin_lock(&atm_dev_lock); list_for_each(p, &atm_devs) { dev = list_entry(p, struct atm_dev, dev_list); if (left-- == 0) { dev_info(dev,buf); spin_unlock(&atm_dev_lock); return strlen(buf); } } spin_unlock(&atm_dev_lock); return 0; } /* * FIXME: it isn't safe to walk the VCC list without turning off interrupts. * What is really needed is some lock on the devices. Ditto for ATMARP. */ static int atm_pvc_info(loff_t pos,char *buf) { struct sock *s; struct atm_vcc *vcc; int left, clip_info = 0; if (!pos) { return sprintf(buf,"Itf VPI VCI AAL RX(PCR,Class) " "TX(PCR,Class)\n"); } left = pos-1; #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) if (try_atm_clip_ops()) clip_info = 1; #endif read_lock(&vcc_sklist_lock); for(s = vcc_sklist; s; s = s->next) { vcc = s->protinfo.af_atm; if (vcc->sk->family == PF_ATMPVC && vcc->dev && !left--) { pvc_info(vcc,buf,clip_info); read_unlock(&vcc_sklist_lock); #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) if (clip_info && atm_clip_ops->owner) __MOD_DEC_USE_COUNT(atm_clip_ops->owner); #endif return strlen(buf); } } read_unlock(&vcc_sklist_lock); #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) if (clip_info && atm_clip_ops->owner) __MOD_DEC_USE_COUNT(atm_clip_ops->owner); #endif return 0; } static int atm_vc_info(loff_t pos,char *buf) { struct atm_vcc *vcc; struct sock *s; int left; if (!pos) return sprintf(buf,sizeof(void *) == 4 ? "%-8s%s" : "%-16s%s", "Address"," Itf VPI VCI Fam Flags Reply Send buffer" " Recv buffer\n"); left = pos-1; read_lock(&vcc_sklist_lock); for(s = vcc_sklist; s; s = s->next) { vcc = s->protinfo.af_atm; if (!left--) { vc_info(vcc,buf); read_unlock(&vcc_sklist_lock); return strlen(buf); } } read_unlock(&vcc_sklist_lock); return 0; } static int atm_svc_info(loff_t pos,char *buf) { struct sock *s; struct atm_vcc *vcc; int left; if (!pos) return sprintf(buf,"Itf VPI VCI State Remote\n"); left = pos-1; read_lock(&vcc_sklist_lock); for(s = vcc_sklist; s; s = s->next) { vcc = s->protinfo.af_atm; if (vcc->sk->family == PF_ATMSVC && !left--) { svc_info(vcc,buf); read_unlock(&vcc_sklist_lock); return strlen(buf); } } read_unlock(&vcc_sklist_lock); return 0; } #if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) static int atm_lec_info(loff_t pos,char *buf) { unsigned long flags; struct lec_priv *priv; struct lec_arp_table *entry; int i, count, d, e; struct net_device *dev; if (!pos) { return sprintf(buf,"Itf MAC ATM destination" " Status Flags " "VPI/VCI Recv VPI/VCI\n"); } if (!try_atm_lane_ops()) return 0; /* the lane module is not there yet */ count = pos; for(d = 0; d < MAX_LEC_ITF; d++) { dev = atm_lane_ops->get_lec(d); if (!dev || !(priv = (struct lec_priv *) dev->priv)) continue; spin_lock_irqsave(&priv->lec_arp_lock, flags); for(i = 0; i < LEC_ARP_TABLE_SIZE; i++) { for(entry = priv->lec_arp_tables[i]; entry; entry = entry->next) { if (--count) continue; e = sprintf(buf,"%s ", dev->name); lec_info(entry, buf+e); spin_unlock_irqrestore(&priv->lec_arp_lock, flags); dev_put(dev); if (atm_lane_ops->owner) __MOD_DEC_USE_COUNT(atm_lane_ops->owner); return strlen(buf); } } for(entry = priv->lec_arp_empty_ones; entry; entry = entry->next) { if (--count) continue; e = sprintf(buf,"%s ", dev->name); lec_info(entry, buf+e); spin_unlock_irqrestore(&priv->lec_arp_lock, flags); dev_put(dev); if (atm_lane_ops->owner) __MOD_DEC_USE_COUNT(atm_lane_ops->owner); return strlen(buf); } for(entry = priv->lec_no_forward; entry; entry=entry->next) { if (--count) continue; e = sprintf(buf,"%s ", dev->name); lec_info(entry, buf+e); spin_unlock_irqrestore(&priv->lec_arp_lock, flags); dev_put(dev); if (atm_lane_ops->owner) __MOD_DEC_USE_COUNT(atm_lane_ops->owner); return strlen(buf); } for(entry = priv->mcast_fwds; entry; entry = entry->next) { if (--count) continue; e = sprintf(buf,"%s ", dev->name); lec_info(entry, buf+e); spin_unlock_irqrestore(&priv->lec_arp_lock, flags); dev_put(dev); if (atm_lane_ops->owner) __MOD_DEC_USE_COUNT(atm_lane_ops->owner); return strlen(buf); } spin_unlock_irqrestore(&priv->lec_arp_lock, flags); dev_put(dev); } if (atm_lane_ops->owner) __MOD_DEC_USE_COUNT(atm_lane_ops->owner); return 0; } #endif static ssize_t proc_dev_atm_read(struct file *file,char *buf,size_t count, loff_t *pos) { struct atm_dev *dev; unsigned long page; int length; if (count == 0) return 0; page = get_free_page(GFP_KERNEL); if (!page) return -ENOMEM; dev = ((struct proc_dir_entry *) file->f_dentry->d_inode->u.generic_ip) ->data; if (!dev->ops->proc_read) length = -EINVAL; else { length = dev->ops->proc_read(dev,pos,(char *) page); if (length > count) length = -EINVAL; } if (length >= 0) { if (copy_to_user(buf,(char *) page,length)) length = -EFAULT; (*pos)++; } free_page(page); return length; } static ssize_t proc_spec_atm_read(struct file *file,char *buf,size_t count, loff_t *pos) { unsigned long page; int length; int (*info)(loff_t,char *); info = ((struct proc_dir_entry *) file->f_dentry->d_inode->u.generic_ip) ->data; if (count == 0) return 0; page = get_free_page(GFP_KERNEL); if (!page) return -ENOMEM; length = (*info)(*pos,(char *) page); if (length > count) length = -EINVAL; if (length >= 0) { if (copy_to_user(buf,(char *) page,length)) length = -EFAULT; (*pos)++; } free_page(page); return length; } struct proc_dir_entry *atm_proc_root; EXPORT_SYMBOL(atm_proc_root); int atm_proc_dev_register(struct atm_dev *dev) { int digits,num; int error; error = -ENOMEM; digits = 0; for (num = dev->number; num; num /= 10) digits++; if (!digits) digits++; dev->proc_name = kmalloc(strlen(dev->type) + digits + 2, GFP_ATOMIC); if (!dev->proc_name) goto fail1; sprintf(dev->proc_name,"%s:%d",dev->type, dev->number); dev->proc_entry = create_proc_entry(dev->proc_name, 0, atm_proc_root); if (!dev->proc_entry) goto fail0; dev->proc_entry->data = dev; dev->proc_entry->proc_fops = &proc_dev_atm_operations; dev->proc_entry->owner = THIS_MODULE; return 0; fail0: kfree(dev->proc_name); fail1: return error; } void atm_proc_dev_deregister(struct atm_dev *dev) { remove_proc_entry(dev->proc_name, atm_proc_root); kfree(dev->proc_name); } #define CREATE_ENTRY(name) \ name = create_proc_entry(#name,0,atm_proc_root); \ if (!name) goto cleanup; \ name->data = atm_##name##_info; \ name->proc_fops = &proc_spec_atm_operations; \ name->owner = THIS_MODULE static struct proc_dir_entry *devices = NULL, *pvc = NULL, *svc = NULL, *arp = NULL, *lec = NULL, *vc = NULL; static void atm_proc_cleanup(void) { if (devices) remove_proc_entry("devices",atm_proc_root); if (pvc) remove_proc_entry("pvc",atm_proc_root); if (svc) remove_proc_entry("svc",atm_proc_root); if (arp) remove_proc_entry("arp",atm_proc_root); if (lec) remove_proc_entry("lec",atm_proc_root); if (vc) remove_proc_entry("vc",atm_proc_root); remove_proc_entry("net/atm",NULL); } int atm_proc_init(void) { atm_proc_root = proc_mkdir("net/atm",NULL); if (!atm_proc_root) return -ENOMEM; CREATE_ENTRY(devices); CREATE_ENTRY(pvc); CREATE_ENTRY(svc); CREATE_ENTRY(vc); #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE) arp = create_proc_entry("arp", S_IRUGO, atm_proc_root); if (!arp) goto cleanup; arp->proc_fops = &arp_seq_fops; #endif #if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) CREATE_ENTRY(lec); #endif return 0; cleanup: atm_proc_cleanup(); return -ENOMEM; } void atm_proc_exit(void) { atm_proc_cleanup(); }