tiny4412 串口驱动分析五 --- LDD3上TTY驱动程序源码

关于tty这部分请参考:

《Linux设备驱动开发详解 第二版》第14章 Linux终端设备驱动

《精通Linux设备驱动程序开发》第6章 串行设备驱动程序

《Linux设备驱动程序 第三版》第18章 TTY驱动程序

 

下面是一些串口相关的文档:

 http://pan.baidu.com/s/1mg20Umc

 

Makefile:

# Comment/uncomment the following line to disable/enable debugging #DEBUG = y # Add your debugging flag (or not) to CFLAGS ifeq ($(DEBUG),y) DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines else DEBFLAGS = -O2 endif EXTRA_CFLAGS += $(DEBFLAGS) EXTRA_CFLAGS += -I.. ifneq ($(KERNELRELEASE),) # call from kernel build system obj-m    := tiny_tty.o tiny_serial.o else #KERNELDIR ?= /lib/modules/$(shell uname -r)/build KERNELDIR ?= /root/Tiny4412_android_4_1_2/linux-3.0.31 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions depend .depend dep: $(CC) $(EXTRA_CFLAGS) -M *.c > .depend ifeq (.depend,$(wildcard .depend)) include .depend endif

 

tiny_tty.c

/* * Tiny TTY driver * * Copyright (C) 2002-2004 Greg Kroah-Hartman ([email protected]) * * 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 <linux/kernel.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/wait.h> #include <linux/tty.h> #include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/serial.h> #include <linux/sched.h> #include <linux/seq_file.h> #include <asm/uaccess.h>


#define DRIVER_VERSION "v2.0"
#define DRIVER_AUTHOR "Greg Kroah-Hartman <[email protected]>"
#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_buffer_request_room(tty, 1)) 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; sema_init(&tiny->sem, 1); 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; } init_timer(tiny->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; 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; } 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 ktermios *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 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, 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_proc_show(struct seq_file *m, void *v) { struct tiny_serial *tiny; int i; seq_printf(m, "tinyserinfo:1.0 driver:%s\n", DRIVER_VERSION); for (i = 0; i < TINY_TTY_MINORS; ++i) { tiny = tiny_table[i]; if (tiny == NULL) continue; seq_printf(m, "%d\n", i); } return 0; } #define tiny_ioctl tiny_ioctl_tiocgserial
static int tiny_ioctl(struct tty_struct *tty, 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, 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, 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, unsigned int cmd, unsigned long arg) { switch (cmd) { case TIOCGSERIAL: return tiny_ioctl_tiocgserial(tty, cmd, arg); case TIOCMIWAIT: return tiny_ioctl_tiocmiwait(tty, cmd, arg); case TIOCGICOUNT: return tiny_ioctl_tiocgicount(tty, cmd, arg); } return -ENOIOCTLCMD; } static int tiny_proc_open(struct inode *inode, struct file *file) { return single_open(file, tiny_proc_show, NULL); } static const struct file_operations serial_proc_fops = { .owner = THIS_MODULE, .open = tiny_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; 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, .proc_fops = &serial_proc_fops, .tiocmget = tiny_tiocmget, .tiocmset = tiny_tiocmset, .ioctl = tiny_ioctl, }; 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->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_DYNAMIC_DEV, 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); /* 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);

 

tiny_serial.c

/* * Tiny Serial driver * * Copyright (C) 2002-2004 Greg Kroah-Hartman ([email protected]) * * 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 serial driver. It does not rely on * any backing hardware, but creates a timer that emulates data being received * from some kind of hardware. */ #include <linux/kernel.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/serial.h> #include <linux/serial_core.h> #include <linux/module.h>


#define DRIVER_AUTHOR "Greg Kroah-Hartman <[email protected]>"
#define DRIVER_DESC "Tiny serial 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_SERIAL_MAJOR    240    /* experimental range */
#define TINY_SERIAL_MINORS    1    /* only have one minor */
#define UART_NR            1    /* only use one port */

#define TINY_SERIAL_NAME    "ttytiny"

#define MY_NAME            TINY_SERIAL_NAME

static struct timer_list *timer; static void tiny_stop_tx(struct uart_port *port) { } static void tiny_stop_rx(struct uart_port *port) { } static void tiny_enable_ms(struct uart_port *port) { } static void tiny_tx_chars(struct uart_port *port) { struct circ_buf *xmit = &port->state->xmit; int count; if (port->x_char) { pr_debug("wrote %2x", port->x_char); port->icount.tx++; port->x_char = 0; return; } if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { tiny_stop_tx(port); return; } count = port->fifosize >> 1; do { pr_debug("wrote %2x", xmit->buf[xmit->tail]); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); port->icount.tx++; if (uart_circ_empty(xmit)) break; } while (--count > 0); if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(port); if (uart_circ_empty(xmit)) tiny_stop_tx(port); } static void tiny_start_tx(struct uart_port *port) { } static void tiny_timer(unsigned long data) { struct uart_port *port; struct tty_struct *tty; port = (struct uart_port *)data; if (!port) return; if (!port->state) return; tty = port->state->port.tty; if (!tty) return; /* add one character to the tty port */
    /* this doesn't actually push the data through unless tty->low_latency is set */ tty_insert_flip_char(tty, TINY_DATA_CHARACTER, 0); tty_flip_buffer_push(tty); /* resubmit the timer again */ timer->expires = jiffies + DELAY_TIME; add_timer(timer); /* see if we have any data to transmit */ tiny_tx_chars(port); } static unsigned int tiny_tx_empty(struct uart_port *port) { return 0; } static unsigned int tiny_get_mctrl(struct uart_port *port) { return 0; } static void tiny_set_mctrl(struct uart_port *port, unsigned int mctrl) { } static void tiny_break_ctl(struct uart_port *port, int break_state) { } static void tiny_set_termios(struct uart_port *port, struct ktermios *new, struct ktermios *old) { int baud, quot, cflag = new->c_cflag; /* 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: // CS8
        printk(KERN_DEBUG " - data bits = 8\n"); break; } /* determine the parity */
    if (cflag & PARENB) if (cflag & PARODD) pr_debug(" - parity = odd\n"); else pr_debug(" - parity = even\n"); else pr_debug(" - parity = none\n"); /* figure out the stop bits requested */
    if (cflag & CSTOPB) pr_debug(" - stop bits = 2\n"); else pr_debug(" - stop bits = 1\n"); /* figure out the flow control settings */
    if (cflag & CRTSCTS) pr_debug(" - RTS/CTS is enabled\n"); else pr_debug(" - RTS/CTS is disabled\n"); /* Set baud rate */ baud = uart_get_baud_rate(port, new, old, 0, port->uartclk/16); quot = uart_get_divisor(port, baud); //UART_PUT_DIV_LO(port, (quot & 0xff)); //UART_PUT_DIV_HI(port, ((quot & 0xf00) >> 8));
} static int tiny_startup(struct uart_port *port) { /* this is the first time this port is opened */
    /* do any hardware initialization needed here */

    /* create our timer and submit it */
    if (!timer) { timer = kmalloc(sizeof(*timer), GFP_KERNEL); if (!timer) return -ENOMEM; } init_timer(timer); timer->data = (unsigned long)port; timer->expires = jiffies + DELAY_TIME; timer->function = tiny_timer; add_timer(timer); return 0; } static void tiny_shutdown(struct uart_port *port) { /* The port is being closed by the last user. */
    /* Do any hardware specific stuff here */

    /* shut down our timer */ del_timer(timer); } static const char *tiny_type(struct uart_port *port) { return "tinytty"; } static void tiny_release_port(struct uart_port *port) { } static int tiny_request_port(struct uart_port *port) { return 0; } static void tiny_config_port(struct uart_port *port, int flags) { } static int tiny_verify_port(struct uart_port *port, struct serial_struct *ser) { return 0; } static struct uart_ops tiny_ops = { .tx_empty = tiny_tx_empty, .set_mctrl = tiny_set_mctrl, .get_mctrl = tiny_get_mctrl, .stop_tx = tiny_stop_tx, .start_tx = tiny_start_tx, .stop_rx = tiny_stop_rx, .enable_ms = tiny_enable_ms, .break_ctl = tiny_break_ctl, .startup = tiny_startup, .shutdown = tiny_shutdown, .set_termios = tiny_set_termios, .type = tiny_type, .release_port = tiny_release_port, .request_port = tiny_request_port, .config_port = tiny_config_port, .verify_port = tiny_verify_port, }; static struct uart_port tiny_port = { .ops = &tiny_ops, }; static struct uart_driver tiny_reg = { .owner = THIS_MODULE, .driver_name = TINY_SERIAL_NAME, .dev_name = TINY_SERIAL_NAME, .major = TINY_SERIAL_MAJOR, .minor = TINY_SERIAL_MINORS, .nr = UART_NR, }; static int __init tiny_init(void) { int result; printk(KERN_INFO "Tiny serial driver loaded\n"); result = uart_register_driver(&tiny_reg); if (result) return result; result = uart_add_one_port(&tiny_reg, &tiny_port); if (result) uart_unregister_driver(&tiny_reg); return result; } module_init(tiny_init);

 

你可能感兴趣的:(ldd)