3
\$\begingroup\$

My aim is to write a blocking char driver that return to the user space a complete protocol message from the kernel uart driver.

Protocol description

I'm fighting with a very bad protocol, called DMX. Protocol packet is very simple:

  • start with a break on serial line
  • first char has to be 0x00
  • follows 512 bytes

No CRC or start and end packet are defined.

The break is also the sync event in the whole system. The user space has to receive the whole packet in a single shot and ASAP.

I enabled preemption in the Linux kernel.

Driver implementation

I started from Freescale's uart driver, which is a platform driver. Platform drivers read function is not available, and Freescale gives the read/write function through the tty driver.

I read on LDD3 about char drivers, file_operations and blocking I/O. Then I started to think that a simple char driver is what I need: using wake_up_interruptible and wait_event_interruptible functions.

I modified the Freescale uart driver to install and init my_char_driver at the startup, passing itself to the char driver.

Freescale probe function uart driver

my_char_driver_init(imx_port);

my_char_driver

int my_char_driver_init(struct imx_port *imx_port)
{
    int ret;

    printk(KERN_INFO "my_char_driver: Initializing the my_char_driver driver\n");

    // Try to dynamically allocate a major number for the device -- more difficult but worth it
    majorNumber = register_chrdev(0, DEV_NAME, &fops);
    if (majorNumber<0)
    {
        printk(KERN_ALERT "my_char_driver failed to register a major number\n");
        return majorNumber;
    }
    printk(KERN_INFO "my_char_driver: registered correctly with major number %d\n", majorNumber);

    // Register the device class
    my_char_driver_class = class_create(THIS_MODULE, CLASS_NAME);
    // Check for error and clean up if there is
    if (IS_ERR(my_char_driver_class))
    {
        unregister_chrdev(majorNumber, DEV_NAME);
        printk(KERN_ALERT "my_char_driver: Failed to register device class\n");
        return PTR_ERR(my_char_driver_class);          // Correct way to return an error on a pointer
    }
    printk(KERN_INFO "my_char_driver: device class created correctly\n");

    // Register the device driver
    my_char_driver_device = device_create(my_char_driver_class, NULL, MKDEV(majorNumber, 0), NULL, DEV_NAME);
    // Clean up if there is an error
    if (IS_ERR(my_char_driver_device))
    {
        class_destroy(my_char_driver_class);
        unregister_chrdev(majorNumber, DEV_NAME);
        printk(KERN_ALERT "my_char_driver: Failed to create the device\n");
        return PTR_ERR(my_char_driver_device);
    }

    my_char_driver_file.imx_port = imx_port;
    imx_port->my_char_driver_wait_queue = &my_char_driver_read_wait;

    int i;
    for (i=0; i< MY_CHAR_DRIVER_BUFFER_LENGTH; i++)
        imx_port->my_char_driver_buffer[i] = 0;

    printk(KERN_INFO "my_char_driver: device class created correctly\n"); // Device was initialized
    return 0;
}

Wake up from interrupt the my_char_driver read function:

static irqreturn_t imx_rxint(int irq, void *dev_id)
{
    struct imx_port *sport = dev_id;
    unsigned int rx, flg, ignored = 0;
    struct tty_port *port = &sport->port.state->port;
    unsigned long flags, temp;

    spin_lock_irqsave(&sport->port.lock, flags);

    while (readl(sport->port.membase + USR2) & USR2_RDR)
    {
        flg = TTY_NORMAL;
        sport->port.icount.rx++;

        rx = readl(sport->port.membase + URXD0);

        temp = readl(sport->port.membase + USR2);
        // BREAK RECEIVED
        if (temp & USR2_BRCD)
        {
            writel(USR2_BRCD, sport->port.membase + USR2);

            // Flag for wait condition
            sport->data_ready = 1;
            // wake up the my_char_driver read function
            wake_up_interruptible(sport->my_char_driver_wait_queue);

            sport->my_char_driver_Buffer_idx=0;

            if (sport->port.flags & UPF_SAK)
                do_SAK(port->tty);
        }
        else
        {
            sport->my_char_driver_buffer[sport->my_char_driver_Buffer_idx++] = rx;
        }

        //..........
        // other stuff
        //..........

    }

    tty_insert_flip_char(port, rx, flg);

out:
    spin_unlock_irqrestore(&sport->port.lock, flags);
    tty_flip_buffer_push(port);
    return IRQ_HANDLED;
}

The read function of my_char_driver:

static ssize_t my_char_driver_read(struct file *filep, char *buffer, size_t len, loff_t *offset)
{
    int error_count = 0;

//  printk(KERN_INFO "my_char_driver: Wait for data\n", size_of_message);

    if (wait_event_interruptible(my_char_driver_wait_queue, my_char_driver_file.imx_port->data_ready == 1) == 0)
    {
        my_char_driver_file.imx_port->data_ready = 0;

        if (copy_to_user(buffer, &my_char_driver_file.imx_port->my_char_driver_buffer[0], MY_CHAR_DRIVER_BUFFER_LENGTH))
        {
            return -EFAULT;
        }

//      printk(KERN_INFO "my_char_driver: Data sent to user space\n");

        return MY_CHAR_DRIVER_BUFFER_LENGTH;
    }

    return 0;
}

Now I'm starting, obviously, implementing a double buffer to avoid reading buffer while interrupt is writing.

After all this stuff, my doubt are:

  1. Is my implementation correct, or is there a different/better way to do this kind of "protocol managing"?
  2. Is double buffer required for this kind of implementation?
  3. Can wake_up_interruptible affect interrupt performance?
\$\endgroup\$
  • 1
    \$\begingroup\$ Break off lines at 80 characters - this way it's easier to read. I can't comment on the rest though, I don't know anything about device drivers. \$\endgroup\$ – jacwah Jul 8 '15 at 8:18
  • \$\begingroup\$ I'm fighting with a very bad protocol, called DMX. It's not bad, just designed to be optimal for hardware instead of software. \$\endgroup\$ – Mast Jul 8 '15 at 8:34
  • \$\begingroup\$ @Mast In my opinion a "protocol" without a CRC/checksum , that leave all error handling to RS485 HW and parity and stop bits is not good, but it's just my opinion.. \$\endgroup\$ – LPs Jul 8 '15 at 8:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.