[Ubuntu] 通用 I/O driver sample code

Ubuntu 18.04

通用型 I/O module source code (Makefile, giodrv.h, giodrv.c),測試程式顯示 Super I/O temperature sensor 温度 (Fintek F71889A):

# Makefile
KVERSION := $(shell uname -r)

obj-m := giodrv.o

all:
	$(MAKE) -C /lib/modules/$(KVERSION)/build M=${PWD} modules

clean:
	$(MAKE) -C /lib/modules/$(KVERSION)/build M=${PWD} clean

// giodrv.h

#ifndef _GIODRV_H_

#define MSG(format, arg...) printk(KERN_INFO "giodrv: " format "\n", ## arg)

#include <linux/ioctl.h>

#define DEVICE_NAME 	"giodrv"
#define DEVICE_IOCTLID 	0xD0

struct ioctl_arg {
        unsigned int port;
        unsigned int val;
        unsigned int size;
};

#define IOCTL_IOW _IOW(DEVICE_IOCTLID, 0, struct ioctl_arg)
#define IOCTL_IOR _IOR(DEVICE_IOCTLID, 1, struct ioctl_arg)
#define IOCTL_IO_REQUEST _IOR(DEVICE_IOCTLID, 2, struct ioctl_arg)
#define IOCTL_IO_RELEASE _IOR(DEVICE_IOCTLID, 3, struct ioctl_arg)

#endif
 
//giodrv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
//#include <asm/uaccess.h>
#include <linux/uaccess.h>

#include "giodrv.h"

#define DRIVER_NAME "giodrv"
#define CLASS_NAME "giodrv"

static unsigned int chrdev_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev chrdev_cdev;
static struct class *chrdev_class;

struct dev_data {
	unsigned char val;
	rwlock_t lock;
};

void iow(unsigned int port, unsigned int val, unsigned int size)
{
	switch (size) {
	case 1:
		outb(val, port);
		break;

	case 2:
		outw(val, port);
		break;

	case 4:
		outl(val, port);
		break;
	}
}

unsigned ior(unsigned int port, unsigned int size)
{
	unsigned int value = 0;

	switch (size) {
	case 1:
		value = inb(port);
		break;

	case 2:
		value = inw(port);
		break;

	case 4:
		value = inl(port);
		break;
	}
	return value;
}


static long dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct dev_data *dev = filp->private_data;
	struct ioctl_arg data;
	int retval = 0;

	memset(&data, 0, sizeof(data));

	switch (cmd) {
	case IOCTL_IO_REQUEST:
		if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
			retval = -1;
			goto done;
		}
		if (request_region(data.port, data.size, DRIVER_NAME) == NULL) {
			retval = -1;
			goto done;
		}
		break;

	case IOCTL_IO_RELEASE:
		if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
			retval = -1;
			goto done;
		}
		release_region(data.port, data.size);
		break;

	case IOCTL_IOW:
		if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
			retval = -1;
			goto done;
		}
		write_lock(&dev->lock);
		MSG("IOW port:0x%X val:0x%X size:%d", data.port, data.val, data.size);
		iow(data.port, data.val, data.size);
		write_unlock(&dev->lock);
		break;

	case IOCTL_IOR:
		if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
			retval = -1;
			goto done;
		}
		read_lock(&dev->lock);
		MSG("IOR port:0x%X size:%d", data.port, data.size);
		data.val = ior(data.port, data.size);
		MSG("Value: %x\n", data.val);
		if (copy_to_user((int __user *)arg, &data, sizeof(data))) {
			read_unlock(&dev->lock);
			retval = -1;
			goto done;
		}
		read_unlock(&dev->lock);
		break;

	default:
		return -EFAULT;
	}

done:
	return (retval);
}


/**************************************************/

int dev_release(struct inode *inode, struct file *filp)
{
	module_put(THIS_MODULE);

	if (filp->private_data) {
		kfree(filp->private_data);
		filp->private_data = NULL;
	}
	return 0;
};


int dev_open(struct inode *inode, struct file *filp)
{
	struct dev_data *dev = filp->private_data;

	try_module_get(THIS_MODULE);


	dev = kmalloc(sizeof(struct dev_data), GFP_KERNEL);
	if (dev == NULL) {
		return -ENOMEM;
	}
	rwlock_init(&dev->lock);
	dev->val = 0xFF;

	filp->private_data = dev;

	return 0;
};

struct file_operations card_fops = {
	.owner = THIS_MODULE,
	.open = dev_open,
	.release = dev_release,
	.unlocked_ioctl = dev_ioctl,
};

static char* devnode(struct device *dev, umode_t *mode)
{
	if (!mode) return NULL;
	if (dev->devt == MKDEV(chrdev_major, 0) ||
		dev->devt == MKDEV(chrdev_major, 2)) *mode = 0666;
	return NULL;
}

static int init_giodrv_module(void)
{
	dev_t dev = MKDEV(chrdev_major, 0);
	int alloc_ret = 0;
	int cdev_ret = 0;

	alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
	if (alloc_ret) goto error;

	chrdev_major = MAJOR(dev);
	cdev_init(&chrdev_cdev, &card_fops);
	cdev_ret = cdev_add(&chrdev_cdev, dev, num_of_dev);
	if (cdev_ret) goto error;

	chrdev_class = class_create(THIS_MODULE, CLASS_NAME);       //Name to be displayed inside /sys/class/ when driver is loaded.
	if (IS_ERR(chrdev_class)) goto error;

	chrdev_class->devnode = devnode;                            //Set device file (/dev/giodrv) permission to 0666 on creation

	device_create(chrdev_class, NULL, dev, NULL, DEVICE_NAME);  //device file to be created under /dev.

	printk(KERN_ALERT "%s module(major: %d) installed.\n", DRIVER_NAME, chrdev_major);
	printk(KERN_ALERT "%s: Class /sys/class/%s created.\n", DRIVER_NAME, CLASS_NAME);
	printk(KERN_ALERT "%s: Device /dev/%s created.\n", DRIVER_NAME, DEVICE_NAME);
	return 0;

error:
	if (cdev_ret == 0) cdev_del(&chrdev_cdev);
	if (alloc_ret == 0) unregister_chrdev_region(dev, num_of_dev);

	return -1;
}


static void exit_giodrv_module(void)
{
	dev_t dev = MKDEV(chrdev_major, 0);

	device_destroy(chrdev_class, dev);
	class_destroy(chrdev_class);

	cdev_del(&chrdev_cdev);
	unregister_chrdev_region(dev, num_of_dev);
	printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}


module_init(init_giodrv_module);
module_exit(exit_giodrv_module);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("RayKuo's blog");
MODULE_DESCRIPTION("Generic I/O Driver");
MODULE_VERSION("1.0");
MODULE_INFO(intree, "Y");           //fix err: "giodrv: loading out-of-tree module taints kernel."

測試程式 (Makefile, test.c): 

# Makefile

all:
	gcc test.c -o test

clean:
	rm test
 
// test.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../drv/giodrv.h"

unsigned int isa_ior(int devfd, unsigned int port, unsigned reg)
{
	struct ioctl_arg data;

	data.port = port;
	data.val = reg;
	data.size = 1;
	ioctl(devfd, IOCTL_IOW, &data);

	data.port = port+1;
	data.size = 1;
	data.val = 0;
	ioctl(devfd, IOCTL_IOR, &data);

	return data.val;
}

int main(int argc, char *argv[])
{

	struct ioctl_arg data;
	int devfd;

	devfd = open("/dev/" DEVICE_NAME, O_RDWR);
	if (devfd == -1) {
		printf("Can't open /dev/%s\n", DEVICE_NAME);
		return -1;
	}

	data.port=0x295;
	data.size = 2;			//295,296
	if (ioctl(devfd, IOCTL_IO_REQUEST, &data) == -1) {
		printf("Port 0x%x request fail\n", data.port);
		return -1;
	}

	printf("SIO Temp1: %d \n", isa_ior(devfd, 0x295, 0x72));
	printf("SIO Temp2: %d \n", isa_ior(devfd, 0x295, 0x74));
	printf("SIO Temp2: %d \n", isa_ior(devfd, 0x295, 0x76));

	data.port=0x295;
	data.size = 2;			//295,296
	ioctl(devfd, IOCTL_IO_RELEASE, &data);

	printf("Done.\n");

	close(devfd);

	return 0;
} 

下載:generic_io_driver_src

 

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *