티스토리 뷰

Hardware

Device Driver Development [part 3]

알 수 없는 사용자 2007. 7. 16. 02:12

Device Driver Development [part 3]


By Dave Jones (daveATpowertweak.com)
In the previous installments of this article, I showed how to export a /proc interface from kernel space, and then how to get information to the kernel by writing to a /proc entry.

This time, I'll give an example of something which could be exported.

*nb*
Some of the code in this article has changed somewhat to the previous articles, but the functionality remains the same. I had mail after part 1 of this article from Jeff Garzik who pointed out some interesting ideas. I'll be expanding his points (and those from some other people who gave feedback) in a future part of this article which will detail overhauling an existing driver. For now we will concentrate on writing a driver from scratch (Well, not really as this code is based heavily on the nvram driver).


One worthwhile thing that we can export from kernel space, are results from ring 0 instructions. For example, MSRs (Machine Specific Registers). Trying to read these directly from a userspace application will cause a segmentation fault.

A further example of this could be used for exporting the Intel Pentium III serial number to userspace, or the Time Stamp Counters.

The code below shows one possible way of exporting MSRs to userspace. Note how it uses tables of valid MSRs. The correct set of MSRs is chosen depending upon the CPU which has been detected during the boot process (see arch/i386/kernel/setup.c for more on this).

First some includes..
  #include
#include
#include
#include #include

Next the size of each text string that the module outputs.
 #define MSR_BYTES 20		/* Output string size. */ 

Now the data tables. As mentioned above, there is one of these for each CPU type that the module supports. This probably isn't the best way to do this, but if we didn't, strange things could happen if you try to read an MSR that doesn't exist on a certain CPU. The downside is that each time a new CPU comes out, you'll have to add an extra data table.
 	/* Number of MSRs, MSR list.. */ 	const int MSR_AMD_K6[]=	{7,	0, 1, 0xE, 0x10, 					0xC0000080, 0xC0000081, 0xC0000082};  	const int MSR_WINCHIP2[]= {27,	0,1,2,3,4,5,6,7,8,9,0xA, 0xB, 0xC, 0xD, 					0xE, 0xF, 0x11, 0x12, 0x13, 0x107, 0x108, 					0x10A, 0x120, 0x131, 0x142, 0x143, 0x144, 					0x145, 0x146, 0x147, 0x150, 0x151};  	const int MSR_INTEL_P5[]= {14,	0,1,2,3,4,5,6,7,8,9,0xA, 0xB, 0xC, 0xD, 					0xE, 0xF, 0x11, 0x12, 0x13}; 

Next, we have the routine which is called upon module insertion (Or during kernel initialisation if it's built in) This is the routine which figures out which data-table to read the list of valid MSRs from. It then calls a routine to create all the entries in /proc
 __initfunc(int MSR_init(void)) { #ifndef MODULE 	struct proc_dir_entry *proc_CPU; #endif 	struct proc_dir_entry *ent;  	if ((current_cpu_data.cpuid_level==-1) || 	    (!(current_cpu_data.x86_capability & X86_FEATURE_MSR))) { 	    printk("MSR: msr not supported on this CPU.m"); 	    return(1); 	}  	printk(KERN_INFO "MSR driver v%s ", MSR_VERSION );  	proc_CPU = create_proc_entry( "cpu", S_IFDIR, 0 ); 	ent = create_proc_entry("0", S_IFDIR, proc_CPU); 	ent = create_proc_entry("msr", S_IFDIR, ent);  	switch (current_cpu_data.x86_vendor) {  		case X86_VENDOR_INTEL : 			if (current_cpu_data.x86 == 5 && current_cpu_data.x86_model == 2) 				fill_in_MSRs(MSR_INTEL_P5, ent); 			break;   		case X86_VENDOR_AMD : 			if (current_cpu_data.x86 == 5 && current_cpu_data.x86_model > 5) 				fill_in_MSRs(MSR_AMD_K6, ent); 			break;  		case X86_VENDOR_CENTAUR: 			if (current_cpu_data.x86 == 5 && current_cpu_data.x86_model == 8) 				fill_in_MSRs(MSR_WINCHIP2, ent); 			break;  		default : 			printf("MSR: Unknown CPU. "); 			break; 	} 	return (0); } 

The bright eyed amongst you will have noticed the calls to fill_in_MSRs() mentioned in the last routine. This is where the actual building of files in /proc takes place
  void fill_in_MSRs(const int MSRs[], struct proc_dir_entry *root) { 	int num, i; 	char name[9]; 	struct proc_dir_entry *ent;  	num=(MSRs[0]);  	for (i=1;i<=num;i++) { 		sprintf(name, "%08x", MSRs[i]); 		if ((ent = create_proc_entry(name, 0, root))) { 			ent->read_proc = MSR_read_proc; 			/* We store a ptr to the struct, so we can extract the 			# of the MSR when we need to. */ 			ent->data=ent; 		} 	} } 

This is the routine which is called when we do something like 'cat /proc/cpu/0/msr/c0000000'. It translates the filename to an actual MSR, reads it, and exports it back to userspace.
 static int proc_infos( unsigned char *MSR, char *buffer, int *len, 			  off_t *begin, off_t offset, int size, struct proc_dir_entry *ent ) { 	unsigned int val1, val2; 	unsigned long reg;  	reg=simple_strtoul(ent->name, 0, 16);  	rdmsr(reg, val1, val2); 	PRINT_PROC( "MSR:%s:%08x%08x ", ent->name, val1, val2 ); 	return( 1 ); } 

This next bit is 99% exactly the same as the routine in the previous parts of this article. It just does the magic copying to userspace bit.
 static int MSR_read_proc( char *buffer, char **start, off_t offset, 			    int size, int *eof, void *data ) {     unsigned char contents[MSR_BYTES];     int len = 0;     off_t begin = 0;      *eof = proc_infos(contents, buffer, &len, &begin, offset, size, data );      if (offset >= begin + len) 	return( 0 );     *start = buffer + (begin - offset);     return( size < begin + len - offset ? size : begin + len - offset ); } 

Finally, the routine to remove the /proc entries. Called when we rmmod the routine. This is only built in when we build as a module, as removing parts of in-kernel code after booting has completed is not possible. You may release things marked with _initcode, _initdata and _init, but these get freed real early on the boot process.
This is a little different from the removal code featured in earlier articles, as it only removes the toplevel node. This is perfectly valid, doesn't leak any memory, and saves having to write some rather ugly code here to delete each MSR file from /proc one by one.
 void cleanup_module (void) { 	if (proc_CPU) { 		remove_proc_entry( "cpu", 0 ); 	} } #endif /* MODULE */