This is some general notes on kernel modules

Kernel code is a moving target

As new and improved kernels go into use, some of the kernel module code has to be changed along with them. Code that compiled and ran on an older kernel might have contained items that have since been removed.

Recently, the following changes had to be made:

  1. The send_sig_info(signalnumber, SEND_SIG_FORCE, task_from_oid) call which sends a signal from the kernel to a process in user-space, failed to compile with kernel versions 5.x, because the SEND_SIG_FORCE has been deprecated and removed. So it will now have to use SEND_SIG_PRIV instead.
  2. The procfs is now defined with struct proc_ops rather than struct file_operations This means that open, read, write, release, llseek of the file_operations have to be replaced with proc_open, proc_read, proc_write, proc_release, proc_lseek accordingly.

Compilation

Get the kernel headers

For Armbian, there is a package that can be installed, via armbian-config or apt install linux-headers-next-sunxi64. This puts the required files into the /usr/src in a predictable directory there.

Once the files are in place, in order to access them, the Kbuild system expects there to exist an environment variable KERNEL_SRC that specifies the header root directory. To enable this, with the simple Kbuild files, add the following into ~/.profile and log-in again:

export KERNEL_SRC=/usr/src/linux-headers-`uname -r`

This will make the definition match the current kernel, for example, when seen via env:

KERNEL_SRC=/usr/src/linux-headers-3.10-104-1-pine64-longsleep

As the kernel gets updated, the corresponding files here get added as well so uname -a or uname -r will be usefult to determine which one is the right one.

The entire Kbuild file looks like the following:

MODULE=accumulator

obj-m:= ${MODULE}.o

all:
	make -C ${KERNEL_SRC} M=$(PWD) modules

clean:
	make -C ${KERNEL_SRC} M=$(PWD) clean

Then with the source file in place (accumulator.c in the example here), compile with:

$ make -f Kbuild

If there are errors these have to be fixed. On success there is a module file named accumulator.ko

To load this, become root, and use insmod:

# insmod accumulator.ko
#

Again there might be failures to load, so check with lsmod and see if there is any messages from the module in dmesg.

A module source is generally just one file.

Elements of the modules

Visible or local variables

Since the module is loaded into the kernel's namespace, where all other modules live, there is a potential for collision, unless all global variables and functions are declared as static. That makes them local to the file, and do not get in the way of anything else in the kernel that might have the same names.

Automated numbering of misc-devices

Most of the simpler devices are in the class of misc devices. These get some system-defined major and minor number associated with them, on loading. The particular values are not very relevant.

Init and exit

On loading the module, the init function is called. It may be helpful to call it something ending in _init, but as it is declared static it won't be visible anywhere outside, so if there is another init function somewhere with the same name, there will be no collision.

The macro module_init tells the system that this is the module's init function. There is a similar module_exit for the exit function, which is called when the module is unloaded.

static int acc_init(void);

static void acc_exit(void);

module_init(acc_init);
module_exit(acc_exit);

Init should return 0 for everything OK and good to go, and some non-0 value for errors.

While things are allocated and reserved in the init function, these are released and deallocated in the exit function. Generally, all the following sections will refer to which calls go in the init and exit functions, in order to acquire and release that resource or structure.

File-operations on the device-file

The device will have a node associated with it, living in /dev

This gets treated much like any file, in the classic Unix "everything is a file" style. As such it can have actions for opening, reading, writing, seeking, and closing.

The module will contain functions for handling all of these. Generally, on opening, resources (GPIOs, interrupt-slots, memory, etc.) are allocated or reserved, and if there is a physical device on the outside this is put in some predictable state. The closing function which is called "release" is expected to undo whatever has been done on behalf of the device: deallocate memory, release (thus the name) GPIOs, and whatever other resources that have been borrowed for the time being.

the init function has the call to misc_register(&acc_misc_device); which causes the device file to appear. The corresponding removal call in the exit function is misc_deregister(&acc_misc_device);


static struct file_operations acc_fops = 
{
	open: acc_open;
	release: acc_release;
	read: acc_read;
	write: acc_write;
	llseek: acc_llseek;
};

llseek might not be needed, and if the device is read-only it will not need the write function either.


static struct miscdevice acc_misc_device =
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = "accumulator",
	.fops = &acc_fops,
	.mode = S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP | S_IROTH
};

Later on, the actual assigned minor number is available as acc_misc_device.minor if it is of interest.

Note there is no comma at the end of the last line, before the right bracket.


static int acc_init(void)
{
	int rx;

	// reserve and allocated most of gpios, interrupts etc, create procfs entries for parameters 

	/* With all that in place, register the device */
	rx = misc_register(&acc_misc_device);
	if(rx < 0)
	{
		printk(KERN_INFO, "%s(%d): Register misc device failure %d\n", THISFILE, __LINE__, rx);

		return(rx);
	}

	// registering the device is nearly the last thing to do

	return(0);
}

static void acc_exit(void)
{
	// Remove procfs entries, release interrupts and gpios, deallocate memory etc.

	/* With all of that out of the way, deregister the device */
	misc_deregister(&acc_misc_device);

	return;
}
	

The printk() function puts out a message that can be found in the dmesg output. It is useful to have the various possible failures annotated like this. It operates with a format string and arguments pretty much like the functions in the printf() family does.

GPIOs

Kernel GPIO numbering follows the same pattern as we see with the sysfs interface. (/sys/class/gpio).

gpio_request(gpionumber, descrstring); gpio_free(gpionumber); GPIOs can be set to operate as input or output -- it is possible to change this throughout. gpio_direction_input(gpionumber); gpio_direction_output(gpionumber, state); And the state of the line can be read or changed: state = gpio_get_value(gpionumber); gpio_set_value(gpionumber, state); The descrstring is something short and informative, which will appear in /sys/kernel/debug/gpio listings among other places. It is also used when enabling interrupts.

Interrupts

If an allocated GPIO supports interrupts, it can have an interrupt line assigned

acc_irq = gpio_to_irq(gpionumber);

request_irq(acc_irq, 					/* Interrupt as found from for the gpio */
	(irq_handler_t) acc_irq_handler,    /* The function that gets called */
	IRQF_TRIGGER_FALLING,              /* IRQF_TRIGGER_RISING is an alternative */
	GPIO_PIN_STR,                      /* Description string for the gpio */
	IRQ_STR);							/* Descriptive name for the irq. */

To deallocate and release the irq, on rollback or during the exit function, call

free_irq(acc_irq, IRQ_STR); 

The function is defined something like this:

static irqreturn_t acc_irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned long flags;
	local_irq_save(flags);

	if(acc_irq_enabled)
	{
		acc_value += acc_A;
		acc_ticks ++;
		if(acc_ticks >= acc_N)
		{
			acc_value += acc_D;
			acc_ticks = 0;
	}
	else
	{
		printk(KERN_INFO, "%s(%d): irq is not active\n", THISFILE, __LINE__);
	}

	local_irq_restore(flags);
	return IRQ_HANDLED;
}

It updates static variables acc_value and acc_ticks as required.

Parameters

Internal variables can be exposed as parameters. These become readable and/or writeable files in /sys/module/accumulator/parameters, with the same names as the variables.

For example, the accumulator has three parameters A, N, and D, that may be changed on the fly. On each input pulse, the sum has the value of A added. If there have been N such pulses since last time, the additional value of D is also added.

static int acc_param_A; 
static int acc_param_N; 
static int acc_param_D; 

module_param(acc_param_A, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 
MODULE_PARM_DESC(acc_param_A, "Accumulator factor A");

module_param(acc_param_N, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 
MODULE_PARM_DESC(acc_param_N, "Accumulator additionals rate N");

module_param(acc_param_D, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 
MODULE_PARM_DESC(acc_param_D, "Accumulator additional D");

Now these will appear as /sys/module/accumulator/parameters/acc_param_A, /sys/module/accumulator/parameters/acc_param_N, and /sys/module/accumulator/parameters/acc_param_D. The file-permissions are set by the S_xxx flags, read and write for owner (USR), group (GRP), and everyone else (OTH).

procfs variables

Similar to the parameters, but with more possibilities for actions, some values can be made as files under /proc/

These are files, and they require handling functions for actions such as open, read, write, seek, and release.


static int acc_opmode_show(struct seq_file *f, void *vp)
{
	unsigned long long rdb;
	rdb = acc_value;
	seq_printf(f, "%llu\n", rdb);
	return(0);
}

static int acc_opmode_open(struct inode *inode, struct file *ff)
{
	return(single_open(ff, acc_opmode_show, (void *) 1234);
}


static struct proc_ops acc_proc_opmode_ops = 
{
	proc_open: acc_opmode_open,
	proc_read: seq_read,
	proc_lseek: seq_lseek,
	proc_release: single_release
};

static struct proc_ops acc_proc_reset_ops = 
{
	proc_write: acc_reset_write;
};

acc_ppdir = proc_mkdir_mode("accumulator", 0555, NULL);
proc_create("opmode", 0444, acc_ppdir, &acc_proc_opmode_ops);
proc_create("reset", 0222, acc_ppdir, &acc_proc_reset_ops);

The reset file is a write-only action, it may resets the counter if written with certain value


/* There seems to be a similar _store function that can be 
	used for the write when the system passes data to us. */

static ssize_t acc_reset_write(struct file *filp, 
	const char *buf, size_t count, loff_t *f_pos)
{
	size_t locount, rcount, acount;
	char msg[128];
	int cs;

	printk(KERN_INFO "%s(%d): inbound %zu\n", 
			THISFILE, __LINE__, count);

	locount = sizeof(msg);
	memset(msg, 0, locount);
	acount = count;
	if(acount > locount) acount = locount;

	cs = copy_from_user(msg, buf, acount); 

	printk(KERN_INFO "%s(%d): copied %zu = %d\n", 
			THISFILE, __LINE__, acount, cs);

	printk(KERN_INFO "%s(%d): Received <%s> %zu\n", 
			THISFILE, __LINE__, msg, acount );

	/* Do something interesting with this */
	acc_value = 0;
	cs = kstrtoull(msg, 10, &acc_value); 

	printk(KERN_INFO "%s(%d): conv - %d Accu now = %llu\n", 
			THISFILE, __LINE__, cs, acc_value);

	rcount = strlen(buf);

	return(rcount);
}

Signals

It is possible to send some signal from the module to a process in userland. The power-meter code does this, for every tick.

This requires the process id of the user-level process that expects to receive the signal, the signal number has to be known and that is easiest done through a parameter:


static short cl_signal = SIG_CLWCV;
module_param(cl_signal, short, S_IRUSR | S_IRGRP | S_IROTH);
MODULE_PARAM_DESC(cl_signal, "Signal to send on each hit");



irq_handler( /* ... */ )
{
	// ... 

	int ssig;.
	struct task_struct *task_from_pid;

	task_from_pid = pid_task( find_vpid(send_to_pid), PIDTYPE_PID);
	ssig = send_sig_info(cl_signal, SEND_SIG_PRIV, task_from_pid);

	// ... 
}

The userspace code reads the signal number to catch, from /sys/module/pometer/cl_signal and uses that in the call to sigaction():


static void on_cl(int sig, siginfo_t *siginfo, void *c)
{
	/* Do whatever is wanted when the signal comes. */
		
	return;
}

mainjob()
{
	int sig_clwcv;
	FILE *fp;
	struct sigaction sact;

	fp = fopen("/sys/module/pometer/cl_signal", "rt");
	fgets(txt, 90, fp);
	fclose(fp);
	sig_clwcv = strtol(txt, NULL, 10);

	memset(&sact, 0, sizeof(struct sigaction);
	sact.sa_sigaction = on_cl;
	sact.sa_flags = SA_SIGINFO | SA_RESTART; 
	sigaction(sig_clwcv, &sact, 0);

	/* now hang around doing something else while the signals come.  */ 

}


Powered by Apache

Valid XHTML 1.0!