/* * termios_dumper.c * * genarate a list of current states for acitve ttys. * * Copyright (C) 2008, 2009 Pylone, Inc. * Author: MINAMI Hirokazu * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. */ #include #include #include #include #include #include #include #include /* interprit termios bits */ #define TERMOIS_BIT(NAME) {#NAME, NAME, NULL} #define TERMOIS_MAP(NAME, ...) \ {#NAME, NAME, (struct termios_masks []) __VA_ARGS__} struct termios_masks{ const char *name; unsigned int mask; const struct termios_masks *submap; }; static const struct termios_masks iflags[] = { TERMOIS_BIT(IGNBRK), TERMOIS_BIT(BRKINT), TERMOIS_BIT(IGNPAR), TERMOIS_BIT(PARMRK), TERMOIS_BIT(INPCK), TERMOIS_BIT(ISTRIP), TERMOIS_BIT(INLCR), TERMOIS_BIT(IGNCR), TERMOIS_BIT(ICRNL), TERMOIS_BIT(IUCLC), TERMOIS_BIT(IXON), TERMOIS_BIT(IXANY), TERMOIS_BIT(IXOFF), TERMOIS_BIT(IMAXBEL), TERMOIS_BIT(IUTF8), {NULL} }; static const struct termios_masks oflags[] = { TERMOIS_BIT(OPOST), TERMOIS_BIT(OLCUC), TERMOIS_BIT(ONLCR), TERMOIS_BIT(OCRNL), TERMOIS_BIT(ONOCR), TERMOIS_BIT(ONLRET), TERMOIS_BIT(OFILL), TERMOIS_BIT(OFDEL), TERMOIS_MAP(NLDLY,{ TERMOIS_BIT( NL0), TERMOIS_BIT( NL1), {NULL}}), TERMOIS_MAP(CRDLY,{ TERMOIS_BIT( CR0), TERMOIS_BIT( CR1), TERMOIS_BIT( CR2), TERMOIS_BIT( CR3), {NULL}}), TERMOIS_MAP(TABDLY,{ TERMOIS_BIT( TAB0), TERMOIS_BIT( TAB1), TERMOIS_BIT( TAB2), TERMOIS_BIT( TAB3), TERMOIS_BIT( XTABS), {NULL}}), TERMOIS_MAP(BSDLY,{ TERMOIS_BIT( BS0), TERMOIS_BIT( BS1), {NULL}}), TERMOIS_MAP(VTDLY,{ TERMOIS_BIT( VT0), TERMOIS_BIT( VT1), {NULL}}), TERMOIS_MAP(FFDLY,{ TERMOIS_BIT( FF0), TERMOIS_BIT( FF1), {NULL}}), {NULL} }; static const struct termios_masks cflags[] = { TERMOIS_MAP(CBAUD,{ TERMOIS_BIT( B0), TERMOIS_BIT( B50), TERMOIS_BIT( B75), TERMOIS_BIT( B110), TERMOIS_BIT( B134), TERMOIS_BIT( B150), TERMOIS_BIT( B200), TERMOIS_BIT( B300), TERMOIS_BIT( B600), TERMOIS_BIT( B1200), TERMOIS_BIT( B1800), TERMOIS_BIT( B2400), TERMOIS_BIT( B4800), TERMOIS_BIT( B9600), TERMOIS_BIT( B19200), TERMOIS_BIT( B38400), {NULL}}), TERMOIS_MAP(CSIZE,{ TERMOIS_BIT( CS5), TERMOIS_BIT( CS6), TERMOIS_BIT( CS7), TERMOIS_BIT( CS8), {NULL}}), TERMOIS_BIT(CSTOPB), TERMOIS_BIT(CREAD), TERMOIS_BIT(PARENB), TERMOIS_BIT(PARODD), TERMOIS_BIT(HUPCL), TERMOIS_BIT(CLOCAL), TERMOIS_MAP(CBAUDEX,{ TERMOIS_BIT( BOTHER), TERMOIS_BIT( B57600), TERMOIS_BIT( B115200), TERMOIS_BIT( B230400), TERMOIS_BIT( B460800), TERMOIS_BIT( B500000), TERMOIS_BIT( B576000), TERMOIS_BIT( B921600), TERMOIS_BIT( B1000000), TERMOIS_BIT( B1152000), TERMOIS_BIT( B1500000), TERMOIS_BIT( B2000000), TERMOIS_BIT( B2500000), TERMOIS_BIT( B3000000), TERMOIS_BIT( B3500000), TERMOIS_BIT( B4000000), {NULL}}), TERMOIS_BIT(CIBAUD), TERMOIS_BIT(CMSPAR), TERMOIS_BIT(CRTSCTS), {NULL} }; static const struct termios_masks lflags[] = { TERMOIS_BIT(ISIG), TERMOIS_BIT(ICANON), TERMOIS_BIT(XCASE), TERMOIS_BIT(ECHO), TERMOIS_BIT(ECHOE), TERMOIS_BIT(ECHOK), TERMOIS_BIT(ECHONL), TERMOIS_BIT(NOFLSH), TERMOIS_BIT(TOSTOP), TERMOIS_BIT(ECHOCTL), TERMOIS_BIT(ECHOPRT), TERMOIS_BIT(ECHOKE), TERMOIS_BIT(FLUSHO), TERMOIS_BIT(PENDIN), TERMOIS_BIT(IEXTEN), {NULL} }; static const struct termios_masks *template[] = { iflags, oflags, cflags, lflags, }; /* refer 'tty_driver' in tty core which is not exported. */ static struct list_head *tty_drivers_head; /* try to locate the true head of list by its magic number. * NOTE: may fail if a memory block pointed by list_entry() * accidentaly contains TTY_DRIVER_MAGIC. * (i.e. may fail without valid reson in 1 of 2^32 times) */ static int register_tty_drivers_head() { int ret = 0; mutex_lock(&tty_mutex); do{/* traverse list with tty_mutex */ struct tty_struct *tty; struct tty_driver *driver; if(! (tty = get_current_tty()) ){ ret = -ENODEV; break; } list_for_each_entry(driver, &tty->driver->tty_drivers, tty_drivers) { if(driver->magic != TTY_DRIVER_MAGIC){ tty_drivers_head = &driver->tty_drivers; break; } } }while(0); mutex_unlock(&tty_mutex); if( !tty_drivers_head ){ printk(KERN_INFO "%s:failed to discover tty_drivers\n", __func__); ret = -ENODEV; } return ret; } /* check whether 'tty_index'th tty of the tty driver is active */ static int is_valid_tty(struct tty_driver *driver, int tty_index) { if(driver && (driver->magic == TTY_DRIVER_MAGIC) && (driver->ttys) && (tty_index >= 0) && (driver->num > tty_index) && (driver->ttys[tty_index]) ){ return 1; } return 0; } /* seq_file iterator */ struct seq_iter{ struct tty_driver *driver; int driver_index; struct tty_struct *tty; int tty_index; enum { TERMIO_I = 0, TERMIO_O = 1, TERMIO_C = 2, TERMIO_L = 3, } mask_type; int pos; int pos_offset; }; /* switch the tty pointed by iterator to new one */ static int get_next_tty(struct seq_iter *iter) { int i = 0; struct tty_driver *driver; if( is_valid_tty(iter->driver, iter->tty_index+1) ){ iter->tty_index++; iter->tty = iter->driver->ttys[iter->tty_index]; return 0; } /* try to get next device from another driver */ list_for_each_entry(driver, tty_drivers_head, tty_drivers) { if( (iter->driver_index < i) && is_valid_tty(driver, 0) ){ iter->driver = driver; iter->driver_index = i; iter->tty = driver->ttys[0]; iter->tty_index = 0; return 0; } i++; } /* no more tty driver */ return -1; } /* advance iterator till (*pos)th element. */ static void *update_iterator(struct seq_iter *iter, loff_t *pos) { int to_walk; if(!iter->tty){ /* called for the fisrst time */ if(get_next_tty(iter) < 0){ return NULL; } iter->mask_type = TERMIO_I; iter->pos = 0; iter->pos_offset = 0; } rescan: to_walk = (*pos) - iter->pos - iter->pos_offset; if(to_walk < 0){ printk(KERN_INFO "** %s : invalid location %d\n", __func__, (int)(*pos)); return NULL; } while(to_walk){ iter->pos++; if(!template[iter->mask_type][iter->pos].name){ iter->pos_offset += iter->pos; iter->pos = 0; if(iter->mask_type >= TERMIO_L){ break; } iter->mask_type++; } to_walk--; } if(to_walk > 0){ if(get_next_tty(iter) < 0){ return NULL; } iter->mask_type = TERMIO_I; goto rescan; } return iter; } static void *s_next(struct seq_file *m, void *p, loff_t *pos) { ++(*pos); if(!(update_iterator(p, pos))){ kfree(p); return NULL; } return p; } static void *s_start(struct seq_file *m, loff_t *pos) { struct seq_iter *iter; /* mutex_lock(&tty_mutex); */ if( !(iter = kmalloc(sizeof(*iter), GFP_KERNEL)) ) return NULL; iter->driver = NULL; iter->driver_index = -1; iter->tty = NULL; iter->tty_index = 0; iter->mask_type = TERMIO_I; iter->pos = 0; iter->pos_offset = 0; if(!(update_iterator(iter, pos))){ kfree(iter); return NULL; } return iter; } static void s_stop(struct seq_file *m, void *p) { struct seq_iter *iter = p; if(iter){ kfree(iter); } /* mutex_unlock(&tty_mutex); */ } static int s_show(struct seq_file *m, void *p) { unsigned int state; const struct termios_masks *cur; struct seq_iter *iter = p; char ftype = '?'; const char *readable_value; if(!iter || !iter->tty){ return SEQ_SKIP; } cur = template[iter->mask_type] + iter->pos; switch(iter->mask_type){ case TERMIO_I: ftype = 'I'; state = (iter->tty)->termios->c_iflag & (cur->mask); break; case TERMIO_O: ftype = 'O'; state = (iter->tty)->termios->c_oflag & (cur->mask); break; case TERMIO_C: ftype = 'C'; state = (iter->tty)->termios->c_cflag & (cur->mask); break; case TERMIO_L: ftype = 'L'; state = (iter->tty)->termios->c_lflag & (cur->mask); break; default: return -EINVAL; } if(cur->submap){ int i = 0; readable_value = "???"; while(cur->submap[i].name){ if(cur->submap[i].mask == state){ readable_value = cur->submap[i].name; break; } i++; } }else{ readable_value = (state) ? "On":"Off"; } return seq_printf(m, "%s:%s:%c:%-8s:%-8s[%08x]\n", iter->driver->name, iter->tty->name, ftype, cur->name, readable_value, state); } static const struct seq_operations seqfile_ops = { .start = s_start, .next = s_next, .stop = s_stop, .show = s_show }; static int termios_dumper_open(struct inode *inode, struct file *file) { return seq_open(file, &seqfile_ops); } /************************************************************/ static struct dentry *fs_root; static struct dentry *fs_file; static const struct file_operations fops = { .open = termios_dumper_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int __init termios_dumper_init(void) { if(IS_ERR(fs_root = debugfs_create_dir(KBUILD_BASENAME, NULL))) return PTR_ERR(fs_root); if(IS_ERR(fs_file = debugfs_create_file("state", 0444, fs_root, NULL, &fops))) return PTR_ERR(fs_file); if(register_tty_drivers_head() < 0) return -ENODEV; printk(KERN_INFO "*** %s\n", __func__); return 0; } static void __exit termios_dumper_exit(void){ printk(KERN_INFO "*** %s\n", __func__); debugfs_remove(fs_file); debugfs_remove(fs_root); } module_init(termios_dumper_init) module_exit(termios_dumper_exit) MODULE_LICENSE("GPL");