How to use serial ports in kernel?
Hi there,
I am a newbie to kernel,So please be patient if I have asked something silly. Recently, I am trying to use serial port in my kernel module. In user space, this is simple because we can open("/dev/ttyS0",......). However, as a beginner, I don't know how to user serial port in kernel module. I have tried to open "/dev/ttyS0" by filp_open(): struct file* filp=filp_open("/dev/ttyS0",O_RDWR,S_IRWXU|S_IRWXG|S_IRWXO); I can correctly get the serial port's device major number from: filp->f_dentry->d_inode->i_rdev However, when I attempting to send data using: if{(flag=filp->f_op->write(filp,buf,size))<0) printk(KERN_DEBUG,"failed to write"); It failed. How could this happened? What should I do if I would like to enable my kernel module to use serial ports? Any suggestion would be welcome! |
http://www.beyondlogic.org/serial/serial.htm
http://tldp.org/HOWTO/Serial-HOWTO.html -in short: 1)allocate your range with request_range(). on nearly every comp, serial port 1 starts at 0x3f8, port 2 0x2f8. 2)allocate your irq, port 1: 4, port 2: 3 3)read and write to your registers i just recently wrote a driver for an infrared remote that uses serial port, it's not too much code, and found below on my website. |
Oh great! I will try it later.
But I have another question, your solution looks like to write a driver. Would it conflict with the original driver builded in linux kernel? By the way, I am not able to access your site. Would you please send me a copy to peppeng@hotmail.com? Just the lines to open/write the serial port are ok if the whole one is not very convenient to give me. |
I have wrote the following codes to just send data through serial port while there is another tool to read the data. But when the program stopped when entering the first call to outb() with the "Segmentation Faults".
Here is my simple codes: #include <stdio.h> #include <asm/io.h> #define PORT1 0x3F8 /* Port Address Goes Here */ #define INTVECT 0x04 /* Com Port's IRQ here (Must also change PIC setting) */ #define outb_s(port,command) outb(command,port) #define inb_s(port) inb(port) /* Defines Serial Ports Base Address */ /* COM1 0x3F8 */ /* COM2 0x2F8 */ /* COM3 0x3E8 */ /* COM4 0x2E8 */ int count = 0; char ch; char buffer[]="\xc0Hello Serial!\xc0"; int main(void) { outb_s(PORT1 + 1 , 0); /* Turn off interrupts - Port1 */ /* PORT 1 - Communication Settings */ outb_s(PORT1 + 3 , 0x80); /* SET DLAB ON */ outb_s(PORT1 + 0 , 0x0C); /* Set Baud rate - Divisor Latch Low Byte */ /* Default 0x03 = 38,400 BPS */ /* 0x01 = 115,200 BPS */ /* 0x02 = 57,600 BPS */ /* 0x06 = 19,200 BPS */ /* 0x0C = 9,600 BPS */ /* 0x18 = 4,800 BPS */ /* 0x30 = 2,400 BPS */ outb_s(PORT1 + 1 , 0x00); /* Set Baud rate - Divisor Latch High Byte */ outb_s(PORT1 + 3 , 0x03); /* 8 Bits, No Parity, 1 Stop Bit */ outb_s(PORT1 + 2 , 0xC7); /* FIFO Control Register */ outb_s(PORT1 + 4 , 0x0B); /* Turn on DTR, RTS, and OUT2 */ outb_s(0x21,(inb_s(0x21) & 0xEF)); /* Set Programmable Interrupt Controller */ /* COM1 (IRQ4) - 0xEF */ /* COM2 (IRQ3) - 0xF7 */ /* COM3 (IRQ4) - 0xEF */ /* COM4 (IRQ3) - 0xF7 */ outb_s(PORT1 + 1 , 0x01); /* Interrupt when data received */ do { outb_s(PORT1, buffer[count]); } while (count++<sizeof(buffer)); outb_s(PORT1 + 1 , 0); /* Turn off interrupts - Port1 */ return 0; } |
These codes are just for test, so I didn't put them under the kernel.
|
the serial driver, by default, is built into the kernel and cannot be unloaded. u either need to rebuild the kernel and modularize it, or use the setserial program to change the i/o address and irq that the driver uses. this second solution is somewhat of a hack, as what u do is just redirect the driver to use bogus i/o address/irq line. as to the segfault, i suggest u read the materials i gave u. u need to call ioperm() or iopl() to gain access to serial ports from user space. my site is up and working fine, not sure why u couldnt connect? are u using a proxy that's filtering or something?
|
Is there any way to use the existing driver in stead of write one when I need to use serial port in my kernel module?
|
of course not. your driver needs to have exclusive access to the i/o ports it is using. if u leave the regular serial module installed, any user space program that uses serial ports could start writing to your devices registers freely. u need to claim your region with request_region(). here is some example code from my driver:
Code:
static int ir_init(void) |
one more question please,
My module can send data through serial port correctly now. however, there is a problem when I am attempting to receive. 2 methods were used: (1) read the serial port without interrupt //////////////////////////////////////////// int receiver(void){ int c=0,count=0; char temp[BUFFERSIZE]; do { c = inb(PORT1 + 5); if (c & 1) { temp[count] = inb(PORT1); printk("%c(%02x) ",temp[count],temp[count]); count++; if (count == BUFFERSIZE-1) {count = 0;} } }while (c & 1); return 0; } int init_module(void) { init_com(); receiver(); return 0; } ///////////////////////////////////////////// I write some chars into serial port, but I can just receive the first one in my module. I don't know how to figure this problem. (2) read in interrupt //////////////////////////////////////////// void receiver(int irq, void* dev_id, struct pt_regs* regs){ int c=0,count=0; char temp[BUFFERSIZE]; printk("\n<1>we receive:\n"); do { c = inb(PORT1 + 5); if (c & 1) { temp[count] = inb(PORT1); printk("%c(%02x) ",temp[count],temp[count]); count++; if (count == BUFFERSIZE-1) {count = 0;} } }while (c & 1); } int init_module(void) { int flag=0; init_com(); if((flag=request_irq(Serial_IRQ,receiver,SA_SHIRQ,Serial_NAME,NULL))!=0) printk("<1>\nFailed to request irq\n"); else printk("<1>\nRequest irq ok\n"); return 0; } //////////////////////////////////////////// It doesn't work, because if my module is insert, /proc/interrupt shows that my system can no longer get interrupt on irq4. Thus the interrup handler cannot read the serial port. What's worse, if I use my user space program to communicate through serial port after inserting my module. The system will be down. Data in stack will be shown, plus some "EIP:0060:[<d090b290>] Not tainted", "EFLAGS:00010202" and "Bad EIP Value" errors. Could you help me with some tips and solutions? |
first off, u should have looked at the serial modules in the kernel. u need to be using the symbolic defines, ur code would be much simpler to read.
/include/linux/serial_reg.h recode ur module to use those constants and i'll look at it. and when u request a shared IRQ u CANNOT pass the final p ointer as NULL, that pointer is used to identify what irq handler to free when u call free_irq(). when you post code, USE CODE TAGS SO THE INDENTATION IS PRESERVED. read the link in my sig for how to use code tags. in your handler, u need to clear the interrupt pending bit in the IIR register, if u dont clear the bit, no more interrupts will be generated. and finally, you're writing kernel code, if u can't figure out how to debug faults on your own... i suggest reading this book: http://www.xml.com/ldd/chapter/book/ , and also read the links i gave u. |
I have modified some part of my code according to your suggestion, and
I dont have a system crash any longer when interrupt occurs. However, there is one thing that doesnt change. I still have only ONE charactor received through my interrupt handler. I list all my code below Code:
#include <linux/serial_reg.h> // for macros related to serial register |
System crash again!
just server minutes after removing my module. server log says: Unable to handle kernel paging request at virtual address d089a2e5 ....... Would it be caused by free_irq fail? |
It seems that every time I remove my module, system will down if any use serial port again.
I think after request_irq & free_irq, the original IRQ4 interrupt handler is lost. If another serial port interrupt occurs, the system cannot find a handler, thus it crashes. Am I right? And how to preserve the original handler? |
1) you dont write to IIR to clear the bit, all u do is this:
while( !(inb(mird_ioaddr+UART_IIR) & UART_IIR_NO_INT) ); 2)you are NOT passing the correct arg to free_irq or request_irq: Code:
(request_irq(mird_irq, irq_handler, SA_INTERRUPT|SA_SHIRQ, MOD_NAME, |
According to Linux Device Drivers 2ed
Code:
int request_irq(unsigned int irq, need I support it? In my simple code, I even dont know how to set the dev_id. By the way, I am studing kdb now. Is it the optimal debugger in kernel? |
All times are GMT -5. The time now is 07:00 PM. |