Short Position-only kernel module

Perhaps I should have asked for some advice here earlier in the process, but I struggled to communicate with the micro-Maestro for a few weeks, and I ended up writing a very basic kernel module that sends positions and has decent performance when working with my Gumstix Verdex (400MHz XScale). If anyone has questions let me know. The kernel module isn’t completely cleaned up, but it is fully functional and provides a very simple user interface:
you can write to the file from within a program, but it creates a very simple testing mechanism of:
echo “1 500” > /dev/serv0
which will set servo 1 (inexed at 1 not 0) to the middle of its range.

/*
 * USB Skeleton driver - 2.0
 *
 * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License as
 *	published by the Free Software Foundation, version 2.
 *
 * This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c 
 * but has been rewritten to be easy to read and use, as no locks are now
 * needed anymore.
 *
 */




// This version is modified by Chris Stockbridge for use with the pololu usb servo controllers.
//syntax is to write:{servo(1-4), position(1-1000)} to the device created at /dev/serv0





#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kref.h>
#include <linux/smp_lock.h>
#include <linux/usb.h>
#include <asm/uaccess.h>
#include "protocol.h" //includes the manufacturer's device info, currently only takes REQUEST_SET_TARGET


//Vendor and product set to match the 6 servo controller only
#define USB_SKEL_VENDOR_ID	0x1ffb
#define USB_SKEL_PRODUCT_ID	0x0089

/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
	{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
	{ }					/* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, skel_table);


/* Get a minor range for your devices from the usb maintainer */
#define USB_SKEL_MINOR_BASE	192

/* Structure to hold all of our device specific stuff */
struct usb_skel {
	struct usb_device *	udev;			/* the usb device for this device */
	struct usb_interface *	interface;		/* the interface for this device */
	__u8	ctrl_out_endpointAddr;
	struct kref		kref;
};
#define to_skel_dev(d) container_of(d, struct usb_skel, kref)

static struct usb_driver skel_driver;

static void skel_delete(struct kref *kref)
{	
	struct usb_skel *dev = to_skel_dev(kref);

	usb_put_dev(dev->udev);
	kfree (dev);
}

static int skel_open(struct inode *inode, struct file *file)
{
	struct usb_skel *dev;
	struct usb_interface *interface;
	int subminor;
	int retval = 0;

	subminor = iminor(inode);

	interface = usb_find_interface(&skel_driver, subminor);
	if (!interface) {
		err ("%s - error, can't find device for minor %d",
		     __FUNCTION__, subminor);
		retval = -ENODEV;
		goto exit;
	}

	dev = usb_get_intfdata(interface);
	if (!dev) {
		retval = -ENODEV;
		goto exit;
	}
	
	/* increment our usage count for the device */
	kref_get(&dev->kref);

	/* save our object in the file's private structure */
	file->private_data = dev;

exit:
	return retval;
}

static int skel_release(struct inode *inode, struct file *file)
{
	struct usb_skel *dev;

	dev = (struct usb_skel *)file->private_data;
	if (dev == NULL)
		return -ENODEV;

	/* decrement the count on our device */
	kref_put(&dev->kref, skel_delete);
	return 0;
}


static ssize_t skel_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *ppos)
{
	struct usb_skel *dev;
	int retval = 0;
	
	uint16_t position; // position value. type is defined by the usb control message 
	uint16_t servo; // servo number
	unsigned long inputServo;
	unsigned long inputPosition;
	char *input;
	input = kmalloc(10*sizeof(char), GFP_KERNEL);

	dev = (struct usb_skel *)file->private_data;

	
	if (count == 0)
		goto exit;
	
	//read data from the user
	if(copy_from_user(input, user_buffer, count)){
		retval = -EFAULT;
		goto error;
	}
	
	
	inputServo = (uint16_t)simple_strtoul(&input[0], NULL, 10);
	inputPosition = simple_strtoul(&input[2], NULL, 10); // start looking at third character (following servo and space)
	
	
	
	position = 900 + inputPosition; //adjust position to a pulse width range of 900-1900um
	servo = inputServo - 1; //adjust servo index;
	
	if(position > 1900){
		printk(KERN_ALERT "sent a high position of %d\nresetting to 1900", position);
		position = 1900;
	}
	if(position < 900){
		printk(KERN_ALERT "sent a low position of %d\nresetting to 900", position);
		position = 900;
	}
	if(servo > 3){
		printk(KERN_ALERT "unrecognized servo no position will be sent%d\n", servo);
		goto error;
	}
	
	
	//create and send a usb control message
	retval = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
							 REQUEST_SET_TARGET, 0x40, 
							 position*4, servo,
							 0, 0, 1*HZ);
	


exit:
	return count;

error:
	printk(KERN_ALERT "error sending\n");
	printk(KERN_ALERT "had an error with #%d but returning count to throw out invalid input\n", retval);
	return count;
}

static struct file_operations skel_fops = {
	.owner =	THIS_MODULE,
	.write =	skel_write,
	.open =		skel_open,
	.release =	skel_release,
};

/* 
 * usb class driver info in order to get a minor number from the usb core,
 * and to have the device registered with devfs and the driver core
 */
static struct usb_class_driver skel_class = {
	.name = "usb/serv%d",
	.fops = &skel_fops,
	//.mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH,
	.minor_base = USB_SKEL_MINOR_BASE,
};

static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
	struct usb_skel *dev = NULL;
	struct usb_host_interface *iface_desc;
	int retval = -ENOMEM;
	
	//printk(KERN_ALERT "in the probe function\n");
	
	/* allocate memory for our device state and initialize it */
	dev = kmalloc(sizeof(struct usb_skel), GFP_KERNEL);
	if (dev == NULL) {
		err("Out of memory");
		goto error;
	}
	memset(dev, 0x00, sizeof (*dev));
	kref_init(&dev->kref);

	dev->udev = usb_get_dev(interface_to_usbdev(interface));
	dev->interface = interface;
	

	
	// This driver will find no endpoints: only the control endpoint at location 0 is implemented on the hardware.
	iface_desc = interface->cur_altsetting;
	printk(KERN_ALERT "found a total of %d endpoints\n", iface_desc->desc.bNumEndpoints);
	
	dev->ctrl_out_endpointAddr = 0; // first control endpoint address
	
	/* save our data pointer in this interface device */
	usb_set_intfdata(interface, dev);

	/* we can register the device now, as it is ready */
	retval = usb_register_dev(interface, &skel_class);
	if (retval) {
		/* something prevented us from registering this driver */
		err("Not able to get a minor for this device.");
		usb_set_intfdata(interface, NULL);
		goto error;
	}

	/* let the user know what node this device is now attached to */
	info("USB Servo controller is installed at serv%d", interface->minor);
	return 0;

error:
	if (dev)
		kref_put(&dev->kref, skel_delete);
	return retval;
}

static void skel_disconnect(struct usb_interface *interface)
{
	struct usb_skel *dev;
	int minor = interface->minor;

	/* prevent skel_open() from racing skel_disconnect() */
	lock_kernel();

	dev = usb_get_intfdata(interface);
	usb_set_intfdata(interface, NULL);

	/* give back our minor */
	usb_deregister_dev(interface, &skel_class);

	unlock_kernel();

	/* decrement our usage count */
	kref_put(&dev->kref, skel_delete);

	info("USB servo contoller #%d now disconnected", minor);
}

static struct usb_driver skel_driver = {
	//.owner = THIS_MODULE,
	.name = "usbServo",
	.id_table = skel_table,
	.probe = skel_probe,
	.disconnect = skel_disconnect,
};

static int __init usb_skel_init(void)
{
	int result;

	/* register this driver with the USB subsystem */
	result = usb_register(&skel_driver);
	if (result)
		err("usb_register failed. Error number %d", result);

	return result;
}

static void __exit usb_skel_exit(void)
{
	/* deregister this driver with the USB subsystem */
	usb_deregister(&skel_driver);
}

module_init (usb_skel_init);
module_exit (usb_skel_exit);

MODULE_LICENSE("GPL");

There are a few reasons for the new kernel module: The class that this was for is about writing linux device drivers , so a kernel module seemed like a better project than a user space program. I am also running an older kernel, so there was no good libusb support. Hopefully someone with similar limitations will find this useful.

-Chris

Thanks for sharing that with us! How would you compile and load it in to your kernel? Does it get more complicated because you have to use a cross compiler?

–David

You do have to cross compile the kernel module. My makefile is shown below.

ifneq ($(KERNELRELEASE),)
	obj-m := usbServo.o
else
	KERNELDIR := $(EC535)/gumstix/oe/linux-2.6.21/
	PWD := $(shell pwd)
	ARCH := arm
	GCC := gcc
	CROSS := $(EC535)/gumstix/oe/cross/bin/arm-linux-

default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) clean

endif

loading the module is done with “insmod usbServo.ko” where I named my module usbServo. Because it is a usb module you don’t have to create a node or anything like that because that work is taken care of by the usb system each time it detects a device that your module can handle. This actually makes it a little easier to work with than some kernel modules.
Cross compiling is a little bit of a pain, especially for kernel modules, and I am actually working on setting up that environment now. Because this was a class project my professor had the development environment set up for me which made things a lot easier.