LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   Accessing tty in kernel space (http://www.linuxquestions.org/questions/programming-9/accessing-tty-in-kernel-space-596538/)

RichardS 11-02-2007 03:30 AM

Accessing tty in kernel space
 
Hi All,

In an embedded application we have a serial modem connected to a UART. Unfortunately due to the nature of the solution we need to access the modem through kernel space.

I have looked up on the internet about accessing the tty in kernel space but only seem to find how to implement a new tty device.

Does anyone here know how to access the an existing tty device in kernel space?

thanks,
Rich

osor 11-03-2007 06:19 PM

First, are you quite sure you are constricted to kernel space? When you try to do userspace things in kernel-space, things might get a little difficult. Go ponder this for an hour and come back only if you can think of no other way ;)

Anyway, you can access all the kernel-level system calls from kernel space, so any userspace serial program may be rewritten in bare system calls. But not all glibc calls are system calls, so you’ll have to translate. For example, in userspace you’d probably use tcgetattr() and tcsetattr(), but when using direct kernel-level system calls, you’d use sys_ioctl(fd, TCGETS, void *data) or sys_ioctl(fd, TCSETS, void *data) instead (i.e., tcgetattr() is implemented by glibc by calling the ioctl() system call). Also, there is no kernel-space symbol for errno (most errnos are actually just returned as the negative of the respective macros in a system call). For example, if (in kernelspace) your call to sys_open() fails because of an invalid argument, instead of being given a valid file descriptor, you are given -EINVAL. It is up to you to keep track of this.

Another thing to consider about being in kernelspace but acting like you’re in userspace is that you’ll have to switch the current “data segment” (I use scare quotes because the term is horribly misnamed). Generally, it goes something like this:
Code:

mm_segment_t oldfs;
oldfs = get_fs();
set_fs (KERNEL_DS);


if((serial_fd = sys_open("/dev/ttyS0",  O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) {
        printk(KERN_ALERT "Recieved error %lu when attempting to open /dev/ttyS0\n", serial_fd);
        return serial_fd;
}



/***PLACE ALL OTHER CALLS to sys_*() HERE***/



set_fs(oldfs);

The text in bold serves sort of as a “header” and “footer” for almost any system calls you do from kernelspace. Obviously, this method gets cluttered real fast.

A completely different approach is to bypass any and all kernel abstraction whatsoever, and use hardware directly. In this case you would probably end up using outb() on a hardware port directly (e.g., 0x3f8). If you have any experience with un-protected assembly serial programming, it is quite similar. Essentially, you will probably end up writing a “mini”-driver which is usable only by your own code.

RichardS 11-05-2007 02:27 AM

Thanks for the info. I was complete unaware of the sys_... functions.

The choice to go to kernel space was not mine. I would love to do this in user space as it would be so so much easier. But the designers of the system, being embedded, choose already that only their code runs in user space and all other code is implemented as a device driver. Hence their code opens a specific driver to access the modem, which of cause does not conform to the normal way modems are used in Linux. They could have made life easy....

I was told by some else here about line disciplines. I looked into it, but it seemed a lot of work compared to this way.

Do you know any pros and/or cons for either method?

thanks,
Rich

osor 11-05-2007 01:37 PM

Quote:

Originally Posted by RichardS (Post 2948478)
I was told by some else here about line disciplines. I looked into it, but it seemed a lot of work compared to this way.

Do you know any pros and/or cons for either method?

Well, line disciplines would fall somewhere in-between using the kernel’s full abstraction and using none at all. They are usually designed to add functionality to existing serial hardware (they exist as a layer in-between the tty layer and the hardware driver layer). You probably already use line disciplines from userspace. For example, if (in userspace) you want to use a serial device with the ppp line discipline (used for certain types of modems), you would do:
Code:

int temp = N_PPP;
ioctl(serial_fd, TIOCSETD, &temp);

Line disciplines determine what actually gets read or written to tty devices. For example, if you write a buffer to a slip tty, it is encapsulated into an IP packet by slip_receive_buf() from the slip line discipline. Defining your own line discipline will allow you to specify what actually happens on reads and writes (and opens and ioctls). You can register line disciplines (of type struct tty_ldisc) with the kernel by using tty_register_ldisc(). Even after registering, you would still need code to open a device, change the line discipline, and write to it (which presumably, you are constricted to write in kernelspace). Moreover, you would most likely re do much of the work of the ppp module. So I don’t see creating your own line discipline as an alternative method, but just a lot more work that gets you the same place you already are (after all, the N_PPP discipline already exists for your modem, though in that case, you would end up reading and writing to /dev/ppp instead of the tty directly).

Basically, the advantage of using the full kernel abstraction is that you don’t have to learn any new tricks. The steps are pretty simple:
  1. Write a userspace program that does exactly what you need.
  2. Debug it as much as possible.
  3. Translate this program so it may be used without a C library. To do this replace syscall wrappers with the underlying system calls. The strace utility will help greatly here. You should also rewrite any convenience functions unavailable in kernelspace.
  4. Debug some more.
  5. Translate the program from userspace to kernel-space (enclosing it by the appropriate data segment switches). Change all checks of errno to checks on the return values of the functions.
  6. Debug.
The real work occurs in steps 1 and 2 (which you should already know how to do). The rest of the steps are just tedia.

RichardS 11-06-2007 05:49 AM

Thanks for the info.

sundialsvcs 11-06-2007 08:09 PM

I realize that we are all faced with the reality that "the powers that be" make technically-poor decisions. But sometimes we can find ingenious ways to code around them.

For example, if their code happens to be using a library call to "open the device," you might be able to slide in a stub-library that "does what you want."

Or, if you have to grab this at the kernel level, maybe you can find a way (say, with a virtual device handler) to slough this whole thing off to a user-space process that is running alongside the "sacrosanct application in question." Even in an embedded environment such approaches might well be feasible. More feasible, in fact, than gerrymandering the kernel to do what the kernel was not designed to do.

Every engineering decision, and every engineering mistake, has an associated hard cost. It is only human nature to sometimes try to solve a technical problem through fiat: "I'm your boss, so do it this way." (And sometimes, that's what you have to do!) But if in your professional opinion the hard-costs are much higher than the person who is making that decision understands, you might need to try to make the persuasive case that another approach should or must be tried. It is very important, when you choose to do that, that you approach "even your boss" as your colleague, and that you listen just as carefully as you talk. You must go into any such encounter fully-prepared either to "win" or to "lose," and to pursue whatever course may be determined with equal enthusiasm.

osor 11-06-2007 10:27 PM

Coincidentally, I came across a couple articles today entitled “Making System Calls from Kernel Space” and “Serial Drivers” (both by Alessandro Rubini). Since both of these deal with your issue, I thought I’d post them here.

TimN 01-11-2008 02:58 AM

TimN
 
I have taken over this port from RichardS and have implemented it using the sys_.. call method.eg :-
...
mm_segment_t oldfs;

oldfs = get_fs();
set_fs( KERNEL_DS);

szSizeRead = sys_read (g_iMODEMuartDevice, pbBuffer, szLength );

set_fs( oldfs);
...

It builds OK and the main App runs OK but when the App attempts to load the Modem module it fails with the following warnings (XXX= symbolic for Company Acronym) :-

11:11:47-000129 TD_DEVINFO_LoadDeviceNode(320) :Attempting to open device at /dev/XXX/XXXModem

11:11:47-000130 TD_DEVINFO_LoadDeviceNode(325) : Unable to open device: No such file or directory

11:11:47-000131 TD_DEVINFO_LoadDeviceNode(326) : Attempting to create device

11:11:47-000132 TD_DEVINFO_CreateXXXDeviceNodes(428) : Loading module with "insmod drivers.ko"
insmod:
error inserting 'drivers.ko': -1 Unknown symbol in module

11:11:47-000133 TD_DEVINFO_CreateXXXDeviceNodes(431) : Failed to load module: No such file or directory

11:11:47-000134 TD_DEVINFO_LoadDeviceNode(332) : Device node not available - failed to create Node

11:11:47-000135 TD_MODEM_Menu(141) : Unable to continue, MODEM Device not available

...

And the kernel debug tosses out
...
drivers: Unknown symbol sys_read
drivers: Unknown symbol sys_open
drivers: Unknown symbol sys_write
...

I included <linux/syscalls.h> in the modem module so presumably the module cannot find the actual code at load time. Also there no files in /dev/XXX/ Any ideas why this is happening ?

osor 01-12-2008 11:58 AM

Quote:

Originally Posted by TimN (Post 3018719)
...
drivers: Unknown symbol sys_read
drivers: Unknown symbol sys_open
drivers: Unknown symbol sys_write
...

Since my original reply, the kernel no longer exports those symbols (for good reason).

If you want to continue using them, you’ll have to export them yourself. Luckily, you are writing a custom kernel, so it shouldn’t be too difficult to patch. For example (this is against a vanilla git kernel):
Code:

diff --git a/fs/open.c b/fs/open.c
index 4932b4d..cc74773 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1061,7 +1061,7 @@ asmlinkage long sys_open(const char __user *filename, int flags, int mode)
        prevent_tail_call(ret);
        return ret;
 }
-EXPORT_UNUSED_SYMBOL_GPL(sys_open); /* To be deleted for 2.6.25 */
+EXPORT_SYMBOL(sys_open);
 
 asmlinkage long sys_openat(int dfd, const char __user *filename, int flags,
                            int mode)
diff --git a/fs/read_write.c b/fs/read_write.c
index ea1f94c..453a71c 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -370,7 +370,7 @@ asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
 
        return ret;
 }
-EXPORT_UNUSED_SYMBOL_GPL(sys_read); /* to be deleted for 2.6.25 */
+EXPORT_SYMBOL(sys_read);
 
 asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count)
 {
@@ -388,6 +388,7 @@ asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t co
 
        return ret;
 }
+EXPORT_SYMBOL(sys_write);
 
 asmlinkage ssize_t sys_pread64(unsigned int fd, char __user *buf,
                              size_t count, loff_t pos)

E.g., instead of something like this:
Code:

mm_segment_t oldfs;
oldfs = get_fs();
set_fs (KERNEL_DS);

fd = sys_open("/path/to/file", FLAGS, MODE)
sys_read(fd, buf, len);
sys_write(fd, otherbuf, otherlen);

sys_close(fd);

set_fs(oldfs);

Migrate your code to something like this:
Code:

struct file *f = filp_open("/path/to/file", FLAGS, MODE);

mm_segment_t oldfs;
oldfs = get_fs();
set_fs (KERNEL_DS);

f->f_op->read(f, buf, len, &f->f_pos);
f->f_op->write(f, otherbuf, otherlen, &f->f_pos);

set_fs(oldfs);

fput(f);

While it does not exactly mimic how you would attempt something in userspace, it has a few advantages over the previous method:
  1. The sys_* functions are not being exported, so you’re not supposed to use them.
  2. They don’t pollute the process file descriptor table as sys_open does.
  3. They won’t require segment switching if the buffers are actually in userspace (though that is not normally the case, so I’ve included the segment switching code).

TimN 01-17-2008 03:04 AM

How do I get the length read ? Also how does the fput(f) fit into the scheme ?

osor 01-18-2008 12:13 PM

Quote:

Originally Posted by TimN (Post 3025467)
How do I get the length read ? Also how does the fput(f) fit into the scheme ?

The length read or written is returned by the callback function. If an error occured, the negative of the error number is returned. As for fput, it is similar to closing a file descriptor. fput will decrement the file counter, and if it reaches zero (i.e., if no other process is using it), the file will be released.

Btw, the example I showed was very dangerous, as it doesn’t check for certain things that should exist. For example, you are not guaranteed that your file has a non-NULL f_op, and even if it does, you are not guaranteed that it has a non-NULL f_op->read. On top of that, you might not have sufficient permissions, or you might want to use f_op->aio_read instead. In any case, there are safe functions that do all this checking for you: vfs_read() and vfs_write(). So you could, for example, do this instead:
Code:

struct file *f = filp_open("/path/to/file", FLAGS, MODE);

mm_segment_t oldfs;
oldfs = get_fs();
set_fs (KERNEL_DS);

vfs_read(f, buf, len, &f->f_pos);
vfs_write(f, otherbuf, otherlen, &f->f_pos);

set_fs(oldfs);

fput(f);

The return values are still the number of bytes read (or negative error numbers), but the functions are much safer to use on files to non-normal files (such as character devices).


All times are GMT -5. The time now is 09:52 AM.