#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/interrupt.h>

static struct tty_driver *tty_sample_driver;
static struct tty_struct *tty_sample;
static ktime_t fireup_time;
static struct hrtimer emu_kbd_desc;
static atomic_t access_count = ATOMIC_INIT(-1);

static enum hrtimer_restart emu_keyboard( struct hrtimer *hrt )
{
    hrtimer_forward_now( &emu_kbd_desc, ktime_set(1,0) );
    tty_insert_flip_string( tty_sample, "Hi\n", 3 );
    tty_flip_buffer_push( tty_sample );
    return HRTIMER_RESTART;
}

static int tty_sample_open(struct tty_struct *tty,
    struct file *filp)
{
    if (!atomic_inc_and_test(&access_count))
        return -EIO;
    printk("tty_sample_open\n");
    tty_sample = tty;
    fireup_time = ktime_set( 2, 0 );
    hrtimer_start(&emu_kbd_desc, fireup_time, HRTIMER_MODE_REL);
    return 0;
}

static void tty_sample_close(struct tty_struct *tty,
    struct file *filp)
{
    printk("tty_sample_close\n");
    atomic_dec( &access_count );
    if (atomic_read(&access_count)==(-1) )
        hrtimer_cancel( &emu_kbd_desc );
}

static int tty_sample_write(struct tty_struct *tty,
    const unsigned char *buf, int count)
{
    int i;

    for(i=0; i<count; i++ )
        printk("%02x %c", buf[i], isalpha(buf[i])?buf[i]:' ' );
    printk("\n");
    return i;
}

static int tty_sample_write_room(struct tty_struct *tty)
{
    return 256; // always enough space
}

static const struct tty_operations tty_sample_ops = {
    .open            = tty_sample_open,
    .close           = tty_sample_close,
    .write           = tty_sample_write,
    .write_room      = tty_sample_write_room,
};

static int __init mod_init( void )
{
    int ret=-1;

    tty_sample_driver = alloc_tty_driver(1);
    if (!tty_sample_driver)
        return ret;
    tty_sample_driver->owner        = THIS_MODULE;
    tty_sample_driver->driver_name  = "tty-sample";
    tty_sample_driver->name         = "ttySample";
    tty_sample_driver->type         = TTY_DRIVER_TYPE_SERIAL;
    tty_sample_driver->subtype      = SERIAL_TYPE_NORMAL;
    tty_sample_driver->init_termios = tty_std_termios;
    tty_set_operations(tty_sample_driver, &tty_sample_ops);

    ret = tty_register_driver(tty_sample_driver);
    if (ret) {
        put_tty_driver(tty_sample_driver);
        return ret;
    }
    hrtimer_init(&emu_kbd_desc,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
    emu_kbd_desc.function = emu_keyboard;
    printk("tty_sample: initialized\n");
    return 0;
}

static void __exit mod_exit(void)
{
    hrtimer_cancel( &emu_kbd_desc );
    tty_unregister_driver(tty_sample_driver);
    put_tty_driver(tty_sample_driver);
}

module_init( mod_init );
module_exit( mod_exit );
MODULE_LICENSE("GPL");