Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1422821AbXBUTWe (ORCPT ); Wed, 21 Feb 2007 14:22:34 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1422824AbXBUTWe (ORCPT ); Wed, 21 Feb 2007 14:22:34 -0500 Received: from csclub1.cs.uwaterloo.ca ([129.97.7.226]:38409 "EHLO caffeine.csclub.uwaterloo.ca" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1422821AbXBUTWd (ORCPT ); Wed, 21 Feb 2007 14:22:33 -0500 X-Greylist: delayed 2965 seconds by postgrey-1.27 at vger.kernel.org; Wed, 21 Feb 2007 14:22:32 EST Date: Wed, 21 Feb 2007 10:15:54 -0500 To: Mockern Cc: linux-kernel@vger.kernel.org Subject: Re: cat problem in tiny_tty driver (the source included) Message-ID: <20070221151554.GA22466@csclub.uwaterloo.ca> References: <45DC5F34.000008.04893@tide.yandex.ru> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <45DC5F34.000008.04893@tide.yandex.ru> User-Agent: Mutt/1.5.9i From: lsorense@csclub.uwaterloo.ca (Lennart Sorensen) Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 19941 Lines: 671 On Wed, Feb 21, 2007 at 06:03:16PM +0300, Mockern wrote: > I tried to check cat operations for tiny_tty driver from LDD book. > > What is wrong with cat operation here? > > Here is the output from strace cat hello > /dev/my_tty1 > > root@andy:/home# strace cat hello > /dev/my_tty1 > execve("/bin/cat", ["cat", "hello"], [/* 12 vars */]) = 0 > brk(0) = 0x7d000 > open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) > open("/etc/ld.so.cache", O_RDONLY) = 3 > fstat64(3, {st_mode=S_IFREG|0644, st_size=5664, ...}) = 0 > old_mmap(NULL, 5664, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40017000 > close(3) = 0 > open("/lib/libm.so.6", O_RDONLY) = 3 > read(3, "\177ELF\1\1\1a\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\250B\0\000"..., 512) = 51 > 2 > fstat64(3, {st_mode=S_IFREG|0755, st_size=480324, ...}) = 0 > old_mmap(NULL, 506412, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x40020000 > mprotect(0x40093000, 35372, PROT_NONE) = 0 > old_mmap(0x40098000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x70 > 000) = 0x40098000 > close(3) = 0 > open("/lib/libcrypt.so.1", O_RDONLY) = 3 > read(3, "\177ELF\1\1\1a\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\260\10\0"..., 512) = 512 > fstat64(3, {st_mode=S_IFREG|0755, st_size=19940, ...}) = 0 > old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0 > x40019000 > old_mmap(NULL, 211220, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x4009c000 > mprotect(0x400a1000, 190740, PROT_NONE) = 0 > old_mmap(0x400a4000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0) = > 0x400a4000 > old_mmap(0x400a9000, 157972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANO > NYMOUS, -1, 0) = 0x400a9000 > close(3) = 0 > open("/lib/libc.so.6", O_RDONLY) = 3 > read(3, "\177ELF\1\1\1a\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\330p\1\000"..., 512) = 51 > 2 > fstat64(3, {st_mode=S_IFREG|0755, st_size=1240024, ...}) = 0 > old_mmap(NULL, 1257088, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x400d0000 > mprotect(0x401f5000, 56960, PROT_NONE) = 0 > old_mmap(0x401f8000, 36864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x12 > 0000) = 0x401f8000 > old_mmap(0x40201000, 7808, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONY > MOUS, -1, 0) = 0x40201000 > close(3) = 0 > munmap(0x40017000, 5664) = 0 > getuid32() = 0 > getgid32() = 0 > setgid32(0) = 0 > setuid32(0) = 0 > brk(0) = 0x7d000 > brk(0x9e000) = 0x9e000 > brk(0) = 0x9e000 > open("hello", O_RDONLY) = 3 > read(3, "123456789", 8192) = 9 > write(1, "123456789", 9) = -1 EINVAL (Invalid argument)//?????????? > write(2, "cat: ", 5cat: ) = 5 > write(2, "Write Error", 11Write Error) = 11 > write(2, ": Invalid argument\n", 19: Invalid argument > ) = 19 > close(3) = 0 > io_submit(0, 0x40200164, 0 > Process 1432 detached > root@andy:/home# > > ------------------------------------------------------------------------------------ > > /* > * Tiny TTY driver > * > * Copyright (C) 2002-2004 Greg Kroah-Hartman (greg@kroah.com) > * > * 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 of the License. > * > * This driver shows how to create a minimal tty driver. It does not rely on > * any backing hardware, but creates a timer that emulates data being received > * from some kind of hardware. > */ > > #include > #include > #include > #include > #include > #include > #include > #include > #include > #include > #include > #include > > > #define DRIVER_VERSION "v2.0" > #define DRIVER_AUTHOR "Greg Kroah-Hartman " > #define DRIVER_DESC "Tiny TTY driver" > > /* Module information */ > MODULE_AUTHOR( DRIVER_AUTHOR ); > MODULE_DESCRIPTION( DRIVER_DESC ); > MODULE_LICENSE("GPL"); > > #define DELAY_TIME HZ * 2 /* 2 seconds per character */ > #define TINY_DATA_CHARACTER 't' > > #define TINY_TTY_MAJOR 240 /* experimental range */ > #define TINY_TTY_MINORS 4 /* only have 4 devices */ > > struct tiny_serial { > struct tty_struct *tty; /* pointer to the tty for this device */ > int open_count; /* number of times this port has been opened */ > struct semaphore sem; /* locks this structure */ > struct timer_list *timer; > > /* for tiocmget and tiocmset functions */ > int msr; /* MSR shadow */ > int mcr; /* MCR shadow */ > > /* for ioctl fun */ > struct serial_struct serial; > wait_queue_head_t wait; > struct async_icount icount; > }; > > static struct tiny_serial *tiny_table[TINY_TTY_MINORS]; /* initially all NULL */ > > > static void tiny_timer(unsigned long timer_data) > { > struct tiny_serial *tiny = (struct tiny_serial *)timer_data; > struct tty_struct *tty; > int i; > char data[1] = {TINY_DATA_CHARACTER}; > int data_size = 1; > > if (!tiny) > return; > > tty = tiny->tty; > > /* send the data to the tty layer for users to read. This doesn't > * actually push the data through unless tty->low_latency is set */ > for (i = 0; i < data_size; ++i) { > if (tty->flip.count >= TTY_FLIPBUF_SIZE) > tty_flip_buffer_push(tty); > tty_insert_flip_char(tty, data[i], TTY_NORMAL); > } > tty_flip_buffer_push(tty); > > /* resubmit the timer again */ > tiny->timer->expires = jiffies + DELAY_TIME; > add_timer(tiny->timer); > } > > static int tiny_open(struct tty_struct *tty, struct file *file) > { > struct tiny_serial *tiny; > struct timer_list *timer; > int index; > > /* initialize the pointer in case something fails */ > tty->driver_data = NULL; > > /* get the serial object associated with this tty pointer */ > index = tty->index; > tiny = tiny_table[index]; > if (tiny == NULL) { > /* first time accessing this device, let's create it */ > tiny = kmalloc(sizeof(*tiny), GFP_KERNEL); > if (!tiny) > return -ENOMEM; > > init_MUTEX(&tiny->sem); > tiny->open_count = 0; > tiny->timer = NULL; > > tiny_table[index] = tiny; > } > > down(&tiny->sem); > > /* save our structure within the tty structure */ > tty->driver_data = tiny; > tiny->tty = tty; > > ++tiny->open_count; > if (tiny->open_count == 1) { > /* this is the first time this port is opened */ > /* do any hardware initialization needed here */ > > /* create our timer and submit it */ > if (!tiny->timer) { > timer = kmalloc(sizeof(*timer), GFP_KERNEL); > if (!timer) { > up(&tiny->sem); > return -ENOMEM; > } > tiny->timer = timer; > } > tiny->timer->data = (unsigned long )tiny; > tiny->timer->expires = jiffies + DELAY_TIME; > tiny->timer->function = tiny_timer; > add_timer(tiny->timer); > } > > up(&tiny->sem); > return 0; > } > > static void do_close(struct tiny_serial *tiny) > { > down(&tiny->sem); > > if (!tiny->open_count) { > /* port was never opened */ > goto exit; > } > > --tiny->open_count; > if (tiny->open_count <= 0) { > /* The port is being closed by the last user. */ > /* Do any hardware specific stuff here */ > > /* shut down our timer */ > del_timer(tiny->timer); > } > exit: > up(&tiny->sem); > } > > static void tiny_close(struct tty_struct *tty, struct file *file) > { > struct tiny_serial *tiny = tty->driver_data; > > if (tiny) > do_close(tiny); > } > > static int tiny_write(struct tty_struct *tty, const unsigned char *buffer, int count) > { > struct tiny_serial *tiny = tty->driver_data; > int i; > int retval = -EINVAL; Here you set retval to EINVAL. > if (!tiny) > return -ENODEV; > > down(&tiny->sem); > > if (!tiny->open_count) > /* port was not opened */ > goto exit; > > /* fake sending the data out a hardware port by > * writing it to the kernel debug log. > */ > printk(KERN_DEBUG "%s - ", __FUNCTION__); > for (i = 0; i < count; ++i) > printk("%02x ", buffer[i]); > printk("\n"); > > exit: > up(&tiny->sem); > return retval; Now you return it. You never changed it. No wonder cat receives 'EINVAL' as a response to its write request. This is obviously not what you intended. > } > > static int tiny_write_room(struct tty_struct *tty) > { > struct tiny_serial *tiny = tty->driver_data; > int room = -EINVAL; > > if (!tiny) > return -ENODEV; > > down(&tiny->sem); > > if (!tiny->open_count) { > /* port was not opened */ > goto exit; > } > > /* calculate how much room is left in the device */ > room = 255; > > exit: > up(&tiny->sem); > return room; > } > > #define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) > > static void tiny_set_termios(struct tty_struct *tty, struct termios *old_termios) > { > unsigned int cflag; > > cflag = tty->termios->c_cflag; > > /* check that they really want us to change something */ > if (old_termios) { > if ((cflag == old_termios->c_cflag) && > (RELEVANT_IFLAG(tty->termios->c_iflag) == > RELEVANT_IFLAG(old_termios->c_iflag))) { > printk(KERN_DEBUG " - nothing to change...\n"); > return; > } > } > > /* get the byte size */ > switch (cflag & CSIZE) { > case CS5: > printk(KERN_DEBUG " - data bits = 5\n"); > break; > case CS6: > printk(KERN_DEBUG " - data bits = 6\n"); > break; > case CS7: > printk(KERN_DEBUG " - data bits = 7\n"); > break; > default: > case CS8: > printk(KERN_DEBUG " - data bits = 8\n"); > break; > } > > /* determine the parity */ > if (cflag & PARENB) > if (cflag & PARODD) > printk(KERN_DEBUG " - parity = odd\n"); > else > printk(KERN_DEBUG " - parity = even\n"); > else > printk(KERN_DEBUG " - parity = none\n"); > > /* figure out the stop bits requested */ > if (cflag & CSTOPB) > printk(KERN_DEBUG " - stop bits = 2\n"); > else > printk(KERN_DEBUG " - stop bits = 1\n"); > > /* figure out the hardware flow control settings */ > if (cflag & CRTSCTS) > printk(KERN_DEBUG " - RTS/CTS is enabled\n"); > else > printk(KERN_DEBUG " - RTS/CTS is disabled\n"); > > /* determine software flow control */ > /* if we are implementing XON/XOFF, set the start and > * stop character in the device */ > if (I_IXOFF(tty) || I_IXON(tty)) { > unsigned char stop_char = STOP_CHAR(tty); > unsigned char start_char = START_CHAR(tty); > > /* if we are implementing INBOUND XON/XOFF */ > if (I_IXOFF(tty)) > printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, " > "XON = %2x, XOFF = %2x", start_char, stop_char); > else > printk(KERN_DEBUG" - INBOUND XON/XOFF is disabled"); > > /* if we are implementing OUTBOUND XON/XOFF */ > if (I_IXON(tty)) > printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, " > "XON = %2x, XOFF = %2x", start_char, stop_char); > else > printk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled"); > } > > /* get the baud rate wanted */ > printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty)); > } > > /* Our fake UART values */ > #define MCR_DTR 0x01 > #define MCR_RTS 0x02 > #define MCR_LOOP 0x04 > #define MSR_CTS 0x08 > #define MSR_CD 0x10 > #define MSR_RI 0x20 > #define MSR_DSR 0x40 > > static int tiny_tiocmget(struct tty_struct *tty, struct file *file) > { > struct tiny_serial *tiny = tty->driver_data; > > unsigned int result = 0; > unsigned int msr = tiny->msr; > unsigned int mcr = tiny->mcr; > > result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0) | /* DTR is set */ > ((mcr & MCR_RTS) ? TIOCM_RTS : 0) | /* RTS is set */ > ((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) | /* LOOP is set */ > ((msr & MSR_CTS) ? TIOCM_CTS : 0) | /* CTS is set */ > ((msr & MSR_CD) ? TIOCM_CAR : 0) | /* Carrier detect is set*/ > ((msr & MSR_RI) ? TIOCM_RI : 0) | /* Ring Indicator is set */ > ((msr & MSR_DSR) ? TIOCM_DSR : 0); /* DSR is set */ > > return result; > } > > static int tiny_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear) > { > struct tiny_serial *tiny = tty->driver_data; > unsigned int mcr = tiny->mcr; > > if (set & TIOCM_RTS) > mcr |= MCR_RTS; > if (set & TIOCM_DTR) > mcr |= MCR_RTS; > > if (clear & TIOCM_RTS) > mcr &= ~MCR_RTS; > if (clear & TIOCM_DTR) > mcr &= ~MCR_RTS; > > /* set the new MCR value in the device */ > tiny->mcr = mcr; > return 0; > } > > static int tiny_read_proc(char *page, char **start, off_t off, int count, > int *eof, void *data) > { > struct tiny_serial *tiny; > off_t begin = 0; > int length = 0; > int i; > > length += sprintf(page, "tinyserinfo:1.0 driver:%s\n", DRIVER_VERSION); > for (i = 0; i < TINY_TTY_MINORS && length < PAGE_SIZE; ++i) { > tiny = tiny_table[i]; > if (tiny == NULL) > continue; > > length += sprintf(page+length, "%d\n", i); > if ((length + begin) > (off + count)) > goto done; > if ((length + begin) < off) { > begin += length; > length = 0; > } > } > *eof = 1; > done: > if (off >= (length + begin)) > return 0; > *start = page + (off-begin); > return (count < begin+length-off) ? count : begin + length-off; > } > > #define tiny_ioctl tiny_ioctl_tiocgserial > static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) > { > struct tiny_serial *tiny = tty->driver_data; > > if (cmd == TIOCGSERIAL) { > struct serial_struct tmp; > > if (!arg) > return -EFAULT; > > memset(&tmp, 0, sizeof(tmp)); > > tmp.type = tiny->serial.type; > tmp.line = tiny->serial.line; > tmp.port = tiny->serial.port; > tmp.irq = tiny->serial.irq; > tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; > tmp.xmit_fifo_size = tiny->serial.xmit_fifo_size; > tmp.baud_base = tiny->serial.baud_base; > tmp.close_delay = 5*HZ; > tmp.closing_wait = 30*HZ; > tmp.custom_divisor = tiny->serial.custom_divisor; > tmp.hub6 = tiny->serial.hub6; > tmp.io_type = tiny->serial.io_type; > > if (copy_to_user((void __user *)arg, &tmp, sizeof(struct serial_struct))) > return -EFAULT; > return 0; > } > return -ENOIOCTLCMD; > } > #undef tiny_ioctl > > #define tiny_ioctl tiny_ioctl_tiocmiwait > static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) > { > struct tiny_serial *tiny = tty->driver_data; > > if (cmd == TIOCMIWAIT) { > DECLARE_WAITQUEUE(wait, current); > struct async_icount cnow; > struct async_icount cprev; > > cprev = tiny->icount; > while (1) { > add_wait_queue(&tiny->wait, &wait); > set_current_state(TASK_INTERRUPTIBLE); > schedule(); > remove_wait_queue(&tiny->wait, &wait); > > /* see if a signal woke us up */ > if (signal_pending(current)) > return -ERESTARTSYS; > > cnow = tiny->icount; > if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && > cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) > return -EIO; /* no change => error */ > if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || > ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || > ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || > ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) { > return 0; > } > cprev = cnow; > } > > } > return -ENOIOCTLCMD; > } > #undef tiny_ioctl > > #define tiny_ioctl tiny_ioctl_tiocgicount > static int tiny_ioctl(struct tty_struct *tty, struct file *file, > unsigned int cmd, unsigned long arg) > { > struct tiny_serial *tiny = tty->driver_data; > > if (cmd == TIOCGICOUNT) { > struct async_icount cnow = tiny->icount; > struct serial_icounter_struct icount; > > icount.cts = cnow.cts; > icount.dsr = cnow.dsr; > icount.rng = cnow.rng; > icount.dcd = cnow.dcd; > icount.rx = cnow.rx; > icount.tx = cnow.tx; > icount.frame = cnow.frame; > icount.overrun = cnow.overrun; > icount.parity = cnow.parity; > icount.brk = cnow.brk; > icount.buf_overrun = cnow.buf_overrun; > > if (copy_to_user((void __user *)arg, &icount, sizeof(icount))) > return -EFAULT; > return 0; > } > return -ENOIOCTLCMD; > } > #undef tiny_ioctl > > /* the real tiny_ioctl function. The above is done to get the small functions in the book */ > static int tiny_ioctl(struct tty_struct *tty, struct file *file, > unsigned int cmd, unsigned long arg) > { > switch (cmd) { > case TIOCGSERIAL: > return tiny_ioctl_tiocgserial(tty, file, cmd, arg); > case TIOCMIWAIT: > return tiny_ioctl_tiocmiwait(tty, file, cmd, arg); > case TIOCGICOUNT: > return tiny_ioctl_tiocgicount(tty, file, cmd, arg); > } > > return -ENOIOCTLCMD; > } > > static struct tty_operations serial_ops = { > .open = tiny_open, > .close = tiny_close, > .write = tiny_write, > .write_room = tiny_write_room, > .set_termios = tiny_set_termios, > }; > > static struct tty_driver *tiny_tty_driver; > > static int __init tiny_init(void) > { > int retval; > int i; > > /* allocate the tty driver */ > tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS); > if (!tiny_tty_driver) > return -ENOMEM; > > /* initialize the tty driver */ > tiny_tty_driver->owner = THIS_MODULE; > tiny_tty_driver->driver_name = "tiny_tty"; > tiny_tty_driver->name = "ttty"; > tiny_tty_driver->devfs_name = "tts/ttty%d"; > tiny_tty_driver->major = TINY_TTY_MAJOR, > tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, > tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL, > tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS, > tiny_tty_driver->init_termios = tty_std_termios; > tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; > tty_set_operations(tiny_tty_driver, &serial_ops); > > /* hack to make the book purty, yet still use these functions in the > * real driver. They really should be set up in the serial_ops > * structure above... */ > tiny_tty_driver->read_proc = tiny_read_proc; > tiny_tty_driver->tiocmget = tiny_tiocmget; > tiny_tty_driver->tiocmset = tiny_tiocmset; > tiny_tty_driver->ioctl = tiny_ioctl; > > /* register the tty driver */ > retval = tty_register_driver(tiny_tty_driver); > if (retval) { > printk(KERN_ERR "failed to register tiny tty driver"); > put_tty_driver(tiny_tty_driver); > return retval; > } > > for (i = 0; i < TINY_TTY_MINORS; ++i) > tty_register_device(tiny_tty_driver, i, NULL); > > printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION); > return retval; > } > > static void __exit tiny_exit(void) > { > struct tiny_serial *tiny; > int i; > > for (i = 0; i < TINY_TTY_MINORS; ++i) > tty_unregister_device(tiny_tty_driver, i); > tty_unregister_driver(tiny_tty_driver); > > /* shut down all of the timers and free the memory */ > for (i = 0; i < TINY_TTY_MINORS; ++i) { > tiny = tiny_table[i]; > if (tiny) { > /* close the port */ > while (tiny->open_count) > do_close(tiny); > > /* shut down our timer and free the memory */ > del_timer(tiny->timer); > kfree(tiny->timer); > kfree(tiny); > tiny_table[i] = NULL; > } > } > } > > module_init(tiny_init); > module_exit(tiny_exit); -- Len Sorensen - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/