Device Driver Development (part 1)

Device driver development. [part 1]

By Dave Jones (dave@ext2.net)
This is the first part in a series of articles.
At the end of the series, we will have discussed the development of a complete device driver usable as an insertable kernel module, or as a monolithic driver.

The first article will discuss how to export information to/from kernel space using the /proc filesystem.


Files.
The simplest entry in the proc filesystem is a file. In our example we will create a file called /proc/test. If you insmod our test module, and then cat /proc/test you should get a “Hello world!”message on the screen.

 /*  * /proc file driver.  */  #define TEST_VERSION		"1.0"  #include
 #include
 #include
 #include  #include   static int proc_infos( unsigned char *contents, char *buffer, int *len, 				off_t *begin, off_t offset, int size ); 

Nothing special so far, some defines, includes and prototypes.

 struct proc_dir_entry *proc_TEST; 

This is a structure defined in the kernel source. It is used to hold details about the entry in the proc filesystem that we are about to create.

 static int TEST_read_proc( char *buffer, char **start, off_t offset, 			    int size, int *eof, void *data ) {     unsigned char contents[2048];     int len = 0;     off_t begin = 0;      *eof = proc_infos( contents, buffer, &len, &begin, offset, size );      if (offset >= begin + len) 	return( 0 );     *start = buffer + (begin - offset);     return( size < begin + len - offset ? size : begin + len - offset ); } 

Our first real code. This defines a 2kb buffer, and fills it with the output of the routine ‘proc_infos’ which is defined below.

 /* This macro frees the machine specific function from bounds checking  * and things like that... */ #define	PRINT_PROC(fmt,args...)					 	do {							 		*len += sprintf( buffer+*len, fmt, ##args );	 		if (*begin + *len > offset + size)		 			return( 0 );				 		if (*begin + *len < offset) {			 			*begin += *len;				 			*len = 0;				 		}						 	} while(0) 

This macro is used in a few places in the kernel source. As it’s name suggests, it fills a buffer with formatted output.

 __initfunc(int TEST_init(void)) { 	printk(KERN_INFO "TEST driver v%s\n", TEST_VERSION );  	if ((proc_TEST = create_proc_entry( "test", 0, 0 ))) 		proc_TEST->read_proc = TEST_read_proc; 	return( 0 ); } 

If this has been built into the kernel (ie, not a module), this routine will disappear after booting. All routines with the __initfunc prefix will get moved together at compile time, which means a memory block which is a multiple of 4kb can be free’d. The printk shows up in syslogs output, and displays status. If some other module has already created a file with the name we want, creation of a file called ‘test’ in /proc will fail. Otherwise, the entry is created, and the following line tells the proc filesystem which routine to run if someone tries to do something like ‘cat /proc/test’.

 #ifdef MODULE int init_module (void) { 	return( TEST_init() ); }  void cleanup_module (void) { 	if (proc_TEST) 		remove_proc_entry( "test", 0 ); } #endif 

If our driver has been built as a module, we need these two routines. Init_module() is called when we ‘insmod file.o’, and contains setup routines. In our example, we simply call the routine that would otherwise have been called at boot-time if it were not built as a module.
cleanup_module() is run when we ‘rmmod file’, and frees all the resources which our module has allocated. In this case, just the one entry in the /proc filesystem.

 static int proc_infos( unsigned char *TEST, char *buffer, int *len, 			  off_t *begin, off_t offset, int size ) { 	PRINT_PROC( "Hello world!\n"); 	return( 1 ); } 

This final routine is where the ‘work’ is done. If you want to export something from kernel space, you would put it here. This routine is called by the read procedure that has been set in the proc entry for this file.


Directories
A Directory is not much more difficult to export to the proc filesystem. If we wanted to create a file called ‘testfile’ in /proc/test/, we would have to make the following changes.

First of all, we now need two structures. One for the directory, and one for the file. So we add

 struct proc_dir_entry *proc_TESTDIR; 

to the structures at the top of the code.The next change is in the creation of the filesystem in TEST_init()

 __initfunc(int TEST_init(void)) { 	printk(KERN_INFO "TEST driver v%s\n", TEST_VERSION );  	proc_TESTDIR = create_proc_entry( "testdir", S_IFDIR, 0 ); 	if ((proc_TEST = create_proc_entry( "test", 0, proc_TESTDIR ))) 		proc_TEST->read_proc = TEST_read_proc; 	return( 0 ); } 

The difference here, is that the first create_proc_entry() call now has the S_IFDIR argument. This specifies that the object being created is a directory. In the following line, the create_proc_entry takes the result of the first call as an argument. This tells /proc that the file being created belongs in the specified directory.
The final changes are in the removal function.

 void cleanup_module (void) { 	if (proc_TEST) 		remove_proc_entry( "testdir/test", 0 ); 	if (proc_TESTDIR) 		remove_proc_entry( "testdir", 0 ); } 

Note, that everything must be removed in the reverse order to what it was created. If we remove the directory before removing the files that are in it, then we could get a memory leak, or worse.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중

%d 블로거가 이것을 좋아합니다: