//-------------------------------------------------------------------
//	iopl.c
//
//	The sole purpose of this character-mode device-driver is for
//	our "proof-of-concept" demonstration of a technique we shall
//	employ in our subsequent "uhci.c" module whereby a user-mode
//	program can acquire the ability to directly access I/O-ports 
//	as a 'side-effect' of opening the '/dev/uhci' device-file. 
//
//	NOTE: Written and tested with Linux kernel version 2.6.31.5.
//
//	programmer: ALLAN CRUSE
//	written on: 19 MAR 2010
//-------------------------------------------------------------------

#include <linux/module.h>	// for init_module() 
#include <linux/proc_fs.h>	// for create_proc_read_entry() 
#include <linux/sched.h>	// for thread_struct

char modname[] = "uhci";
char devname[] = "uhci";
int 	my_major = 74;


int my_open( struct inode *inode, struct file *file )
{
	// As a 'side-effect' of opening the device-file, the
	// task's I/O Privilege-Level will be set equal to 3.

	// This code below closely follows ideas from Linux's 
	// 'sys_iopl()' implementation, but without checks on
	// CAP_SYS_RAWIO and without options for IOPL values

	unsigned long	structsize = sizeof( struct pt_regs );
	struct thread_struct	*ts = &current->thread;
	unsigned long	regsp = ts->sp0 - structsize;
	struct pt_regs	*regs = (struct pt_regs *)regsp;
	
	// For the i386 architecture (or in "compatibility mode"
	// of the x86_64 architecture) the user's FLAGS-register
	// is saved and restored during system-calls from fields 
	// in its kernel data-structures (which we modify here) 
	regs->flags |= (3 << 12);
	ts->iopl = (3 << 12);
	set_iopl_mask( ts->iopl );

#ifndef i386
	// In the x86_64 architecture, the 'syscall' instruction
	// saves the user's RFLAGS values in register R11.  Then
	// the Linux kernel pushes %r11 onto its stack and later
	// restores R11 from its stack before executing 'sysret' 
	regs->r11 |= (3 << 12);		// sets IOPL-image to 3
#endif
	return	0;	// SUCCESS
}


struct file_operations	my_fops = {
				  owner:	THIS_MODULE,
				  open:		my_open,
				  };


static int __init iopl_init( void )
{
	printk( "<1>\nInstalling \'%s\' module ", modname );
	printk( "(major=%d) \n", my_major );
	return	register_chrdev( my_major, devname, &my_fops );
}


static void __exit iopl_exit(void )
{
	unregister_chrdev( my_major, devname );
	printk( "<1>Removing \'%s\' module\n", modname );
}

module_init( iopl_init );
module_exit( iopl_exit );
MODULE_LICENSE("GPL");