关于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);