Click to See Complete Forum and Search --> : TTY Driver "driving" me crazy.


q1001001
02-14-2008, 03:02 PM
Ok. So I have the task of writing a driver that is a tty on the top and interfaces to our proprietary system on the bottom. No problem. Simple. I'm thinking. So, I wrote it as a module.

When I run it with minicom it was crashing the app. It looked like it was doing some writes (minicom modem initialization) and then doing a select() and crashing.

Then I ran in with minicom --noinit. I thought the write()s were doing something bad maybe. But no. It still crashes. Then I wrote a small app to open it and do a select(), which is essentially what minicom --noinit does.

My little app worked, but I noticed that minicom includes fd 0 1 and 2 (stdin, stdout, stderr) in the select() list. And thats the magic that makes the select() crash. I'm perplexed.


I have no idea what is wrong. I stripped down the driver to the ridiculously small module that I have shown below and I can still make it crash by doing an open() and do a select() on it along with fd 0 1 and 2. I am running kernel 2.6.23.8

I have also included the output of the crash.

Can anybody help. Maybe shed some light on this. Thanks so much for the help.

- Mike

#include <linux/init.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/major.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/capability.h>
#include <linux/console.h>
#include <linux/module.h>
#include <linux/serial.h>
#include <linux/serialP.h>
#include <linux/sysrq.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#ifdef CONFIG_KDB
# include <linux/kdb.h>
#endif

static struct tty_driver *my_tty_driver;

static int my_open(struct tty_struct *tty, struct file * filp)
{
tty->driver_data = NULL;
return 0;
}

static void my_close(struct tty_struct *tty, struct file * filp)
{
}

static int my_write(struct tty_struct * tty, const unsigned char *buf, int count)
{
return count;
}

static int my_write_room(struct tty_struct *tty)
{
return 1000;
}

static const struct tty_operations my_ops = {
.open = my_open,
.close = my_close,
.write = my_write,
.write_room = my_write_room,
};

MODULE_AUTHOR("Me");
MODULE_DESCRIPTION("TTY driver");
MODULE_LICENSE("GPL");

static int __init my_init (void)
{
int i;

my_tty_driver = alloc_tty_driver(4);

if (!my_tty_driver)
return -ENOMEM;

/* Initialize the tty_driver structure */

my_tty_driver->owner = THIS_MODULE;
my_tty_driver->driver_name = "/dev/my";
my_tty_driver->name = "my";
my_tty_driver->major = 99;
my_tty_driver->minor_start = 0;
my_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
my_tty_driver->subtype = SERIAL_TYPE_NORMAL;
my_tty_driver->init_termios = tty_std_termios;
my_tty_driver->init_termios.c_cflag =
B9600 | CS8 | CREAD | HUPCL | CLOCAL;
my_tty_driver->flags = TTY_DRIVER_REAL_RAW;

tty_set_operations(my_tty_driver, &my_ops);

if((i = tty_register_driver(my_tty_driver)))
{
printk(KERN_INFO "tty_register_driver returned 0x%x\n", i);
}

return 0;
}

static void __exit my_exit(void)
{
}

module_init(my_init);
module_exit(my_exit);

---------------------------------------------------------------------
Crash output:
BUG: unable to handle kernel NULL pointer dereference at virtual address 00000000
printing eip:
00000000
*pde = 00000000
Oops: 0000 [#1]
PREEMPT
Modules linked in: cygnus_lic cygnus_sys cygnus_local cygnus_pcm cygnus_serial_dma cygnus_reg cygnus_dma
CPU: 0
EIP: 0060:[<00000000>] Not tainted VLI
EFLAGS: 00010246 (2.6.23.8 #7)
EIP is at _stext+0x3fefff50/0x19
eax: cdbf1400 ebx: cdbf1400 ecx: 00000000 edx: ce43b600
esi: 00000000 edi: cdb28440 ebp: cd4d3b60 esp: cd4d3b4c
ds: 007b es: 007b fs: 0000 gs: 0033 ss: 0068
Process select (pid: 2256, ti=cd4d2000 task=c1242570 task.ti=cd4d2000)
Stack: c021c66c 0021846f cdbf1400 c021c579 cdb28440 cd4d3b80 c021905d 00000000
cdbf140c 00000000 00000100 cdb28440 00000003 cd4d3e2c c01597fc cd4d3f9c
cd4d3f48 00000000 cd4d3e54 cd4d3e58 cd4d3e5c cd4d3e48 cd4d3e4c cd4d3e50
Call Trace:
[<c01034fa>] show_trace_log_lvl+0x1a/0x2f
[<c01035aa>] show_stack_log_lvl+0x9b/0xa3
[<c0103799>] show_registers+0x1e7/0x328
[<c01039d7>] die+0xfd/0x1e0
[<c011022e>] do_page_fault+0x495/0x563
[<c03417d2>] error_code+0x6a/0x70
[<c021905d>] tty_poll+0x4a/0x62
[<c01597fc>] do_select+0x2d5/0x4c3
[<c0159c80>] core_sys_select+0x296/0x2b3
[<c0159fc6>] sys_select+0x95/0x165
[<c0102652>] syscall_call+0x7/0xb
=======================
Code: Bad EIP value.
EIP: [<00000000>] _stext+0x3fefff50/0x19 SS:ESP 0068:cd4d3b4c

----------------------------------------------------------------------
Strace output of my little select() app:

execve("./select", ["./select"], [/* 24 vars */]) = 0
brk(0) = 0x804a000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f82000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=9582, ...}) = 0
mmap2(NULL, 9582, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f7f000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0` \1\000"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1216808, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f7e000
mmap2(NULL, 1226148, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7e52000
mmap2(0xb7f78000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x125) = 0xb7f78000
mmap2(0xb7f7b000, 9636, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7f7b000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7e51000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7e516c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7f78000, 4096, PROT_READ) = 0
munmap(0xb7f7f000, 9582) = 0
open("/dev/my", O_RDWR) = 3
select(4, [0 1 2 3], NULL, NULL, {5, 0}

bwkaz
02-14-2008, 08:22 PM
There; I've added code tags to your code, to both preserve the indentation and scroll themselves when lots of data is present. :)

As for the actual problem, let me see if I can find anything...

One issue I see is that you never de-register your TTY driver on module unload. If you've loaded and unloaded this module before, then you probably need to reboot to clear out the references to the (now-unloaded) data in the kernel; you should also add calls to whatever function is required to unregister your driver in the my_exit function. (Unless TTY drivers de-register themselves automatically, but I don't think they'd be able to. I'm not sure though.)

The other thing to do would be to go looking around in your kernel's sources for other TTY drivers, to make sure you're setting up all the right fields in your tty_driver, and that you're setting them to the right values. It's possible that the internal kernel interfaces have changed, and you might be using old info or something.

Also see drivers/char/tty_io.c for the tty_poll function that's in that backtrace -- my version of tty_poll (from 2.6.24) doesn't call error_code directly, but maybe yours does. (Or maybe it's called via a #define somewhere, I didn't look that closely.)