| /* |
| * |
| * Copyright (c) International Business Machines Corp., 2001 |
| * |
| * 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; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See |
| * the GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| /* |
| * This test module is for executing and testing |
| * the kernel code from drivers/base. This module |
| * is driven by a user space program through |
| * calls to the ioctl |
| * |
| * author: Sean Ruyle |
| * date: 07/14/2003 |
| * |
| * module: tbase |
| */ |
| |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/fs.h> |
| #include <linux/ioctl.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/device.h> |
| #include <linux/pci.h> |
| #include <linux/sysdev.h> |
| #include <asm/uaccess.h> |
| |
| #include "tbase.h" |
| #include "str_mod.h" |
| |
| MODULE_AUTHOR("Sean Ruyle <srruyle@us.ibm.com>"); |
| MODULE_DESCRIPTION(TMOD_DRIVER_NAME); |
| MODULE_LICENSE("GPL"); |
| |
| static int tbase_ioctl(struct inode *, struct file *, unsigned int, |
| unsigned long); |
| static int tbase_open(struct inode *, struct file *); |
| static int tbase_close(struct inode *, struct file *); |
| |
| static int test_device_register(void); |
| static int test_device_unregister(void); |
| static int test_bus_add(void); |
| static int test_get_drv(void); |
| static int test_put_drv(void); |
| static int test_reg_firm(void); |
| static int test_create_file(void); |
| static int test_dev_suspend(void); |
| static int test_dev_file(void); |
| static int test_bus_rescan(void); |
| static int test_bus_file(void); |
| static int test_class_reg(void); |
| static int test_class_get(void); |
| static int test_class_file(void); |
| static int test_classdev_reg(void); |
| static int test_classint_reg(void); |
| static int test_sysdev_cls_reg(void); |
| static int test_sysdev_reg(void); |
| |
| static int Major = TBASEMAJOR; |
| static ltpmod_user_t ltp_mod; |
| |
| /* |
| * File operations struct, to use operations find the |
| * correct file descriptor |
| */ |
| static struct file_operations tbase_fops = { |
| open : tbase_open, |
| release: tbase_close, |
| ioctl: tbase_ioctl, |
| }; |
| |
| static int tbase_open(struct inode *ino, struct file *f) { |
| return 0; |
| } |
| |
| static int tbase_close(struct inode *ino, struct file *f) { |
| return 0; |
| } |
| |
| /* my bus stuff */ |
| struct device_driver test_driver; |
| struct device test_device; |
| |
| static int test_device_match (struct device *dev, struct device_driver *drv) { |
| |
| printk("tbase: driver is %s\n", drv->name); |
| // printk("tbase: device is %s\n", dev->name); |
| |
| if (drv == &test_driver && dev == &test_device) { |
| printk("tbase: match\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: no match\n"); |
| return 0; |
| } |
| |
| } |
| |
| struct bus_type test_bus_type = { |
| .name = "test_bus", |
| .match = test_device_match, |
| }; |
| |
| /* my driver stuff */ |
| int test_dev_probe(struct device *dev) { |
| printk("tbase: Entered test_dev_probe\n"); |
| return 0; |
| } |
| |
| int test_dev_remove(struct device *dev) { |
| printk("tbase: Entered test_dev_remove\n"); |
| return 0; |
| } |
| |
| struct device_driver test_driver = { |
| .name = "TestDriver", |
| .bus = &test_bus_type, |
| .probe = test_dev_probe, |
| .remove = test_dev_remove, |
| }; |
| |
| /* my device stuff */ |
| struct device test_device = { |
| // .name = "TestDevice", |
| .bus = &test_bus_type, |
| .bus_id = "test_bus", |
| }; |
| |
| /* my class stuff */ |
| static void test_class_release(struct class_device *class_dev) { |
| printk("tbase: Entered test_class_release\n"); |
| } |
| |
| int test_class_hotplug(struct class_device *dev, char **envp, |
| int num_envp, char *buffer, int buffer_size) { |
| printk("tbase: Entered test_class_hotplug\n"); |
| return 0; |
| } |
| |
| struct class test_class = { |
| .name = "TestClass", |
| .hotplug = test_class_hotplug, |
| .release = test_class_release, |
| }; |
| |
| /* my class device stuff */ |
| struct class_device test_class_dev = { |
| .class_id = "test_bus", |
| .dev = &test_device, |
| .class = &test_class, |
| }; |
| |
| /* my class interface stuff */ |
| int test_intf_add(struct class_device *class_dev) { |
| printk("tbase: Entered test_intf_add for the test class_interface\n"); |
| return 0; |
| } |
| |
| void test_intf_rem(struct class_device *class_dev) { |
| printk("tbase: Entered test_intf_rem for the test class interface\n"); |
| } |
| |
| struct class_interface test_interface = { |
| .class = &test_class, |
| .add = &test_intf_add, |
| .remove = &test_intf_rem, |
| }; |
| |
| /* my sys_device stuff */ |
| int test_resume(struct sys_device *dev) { |
| printk("tbase: Entered test resume for sys device\n"); |
| return 0; |
| } |
| |
| struct sysdev_class test_sysclass = { |
| set_kset_name("TestSysclass"), |
| .resume = test_resume, |
| }; |
| |
| struct sys_device test_sys_device = { |
| .id = 0, |
| .cls = &test_sysclass, |
| }; |
| |
| /* my attribute stuff */ |
| static inline ssize_t |
| store_new_id(struct device_driver * driver, const char * buf, size_t count) { |
| printk("tbase: Entered store new id\n"); |
| return count; |
| } |
| |
| /* create attribute driver_attr_new_id */ |
| DRIVER_ATTR(new_id, 0200, NULL, store_new_id); |
| |
| /* create attribute dev_attr_test_id */ |
| DEVICE_ATTR(test_id, S_IRUGO, NULL, NULL); |
| |
| /* create attribute bus_attr_test_id */ |
| BUS_ATTR(test_id, S_IRUGO, NULL, NULL); |
| |
| /* create attribute class_attr_test_id */ |
| CLASS_ATTR(test_id, 0644, NULL, NULL); |
| |
| /* create attribute class_device_attr_test_id */ |
| CLASS_DEVICE_ATTR(test_id, 0644, NULL, NULL); |
| |
| /* |
| * tbase_ioctl: |
| * a user space program can drive the test functions |
| * through a call to ioctl once the correct file |
| * descriptor has been attained |
| */ |
| static int tbase_ioctl(struct inode *ino, struct file *f, |
| unsigned int cmd, unsigned long l) { |
| int rc; |
| tmod_interface_t tif; |
| caddr_t *inparms; |
| caddr_t *outparms; |
| |
| printk("Enter tbase_ioctl\n"); |
| |
| inparms = NULL; |
| outparms = NULL; |
| rc = 0; |
| |
| /* |
| * the following calls are used to setup the |
| * parameters that might need to be passed |
| * between user and kernel space, using the tif |
| * pointer that is passed in as the last |
| * parameter to the ioctl |
| * |
| */ |
| if (copy_from_user(&tif, (void *)l, sizeof(tif))) { |
| /* Bad address */ |
| return(-EFAULT); |
| } |
| |
| /* |
| * Setup inparms and outparms as needed |
| */ |
| if (tif.in_len > 0) { |
| inparms = (caddr_t *)kmalloc(tif.in_len, GFP_KERNEL); if (!inparms) { |
| return(-ENOMEM); |
| } |
| |
| rc = copy_from_user(inparms, tif.in_data, tif.in_len); |
| if (rc) { |
| kfree(inparms); |
| return(-EFAULT); |
| } |
| } |
| if (tif.out_len > 0) { |
| outparms = (caddr_t *)kmalloc(tif.out_len, GFP_KERNEL); |
| if (!outparms) { |
| kfree(inparms); |
| return(-ENOMEM); |
| } |
| } |
| |
| /* |
| * Use a switch statement to determine which function |
| * to call, based on the cmd flag that is specified |
| * in user space. Pass in inparms or outparms as |
| * needed |
| * |
| */ |
| switch(cmd) { |
| case REG_DEVICE: rc = test_device_register(); break; |
| case UNREG_DEVICE: rc = test_device_unregister(); break; |
| case BUS_ADD: rc = test_bus_add(); break; |
| case GET_DRV: rc = test_get_drv(); break; |
| case PUT_DRV: rc = test_put_drv(); break; |
| case REG_FIRM: rc = test_reg_firm(); break; |
| case CREATE_FILE: rc = test_create_file(); break; |
| case DEV_SUSPEND: rc = test_dev_suspend(); break; |
| case DEV_FILE: rc = test_dev_file(); break; |
| case BUS_RESCAN: rc = test_bus_rescan(); break; |
| case BUS_FILE: rc = test_bus_file(); break; |
| case CLASS_REG: rc = test_class_reg(); break; |
| case CLASS_UNREG: class_unregister(&test_class); break; |
| case CLASS_GET: rc = test_class_get(); break; |
| case CLASS_FILE: rc = test_class_file(); break; |
| case CLASSDEV_REG: rc = test_classdev_reg(); break; |
| case CLASSINT_REG: rc = test_classint_reg(); break; |
| case SYSDEV_CLS_REG: rc = test_sysdev_cls_reg(); break; |
| case SYSDEV_CLS_UNREG: sysdev_class_unregister(&test_sysclass); break; |
| case SYSDEV_REG: rc = test_sysdev_reg(); break; |
| case SYSDEV_UNREG: sys_device_unregister(&test_sys_device); break; |
| default: |
| printk("tbase: Mismatching ioctl command\n"); |
| break; |
| } |
| |
| /* |
| * copy in the test return code, the reason we |
| * this is so that in user space we can tell the |
| * difference between an error in one of our test |
| * calls or an error in the ioctl function |
| */ |
| tif.out_rc = rc; |
| rc = 0; |
| |
| /* |
| * setup the rest of tif pointer for returning to |
| * to user space, using copy_to_user if needed |
| */ |
| |
| /* if outparms then copy outparms into tif.out_data */ |
| if (outparms) { |
| if (copy_to_user(tif.out_data, outparms, tif.out_len)) { |
| printk("tbase: Unsuccessful copy_to_user of outparms\n"); |
| rc = -EFAULT; |
| } |
| } |
| |
| /* copy tif structure into l so that can be used by user program */ |
| if (copy_to_user((void*)l, &tif, sizeof(tif))) { |
| printk("tbase: Unsuccessful copy_to_user of tif\n"); |
| rc = -EFAULT; |
| } |
| |
| /* |
| * free inparms and outparms |
| */ |
| if (inparms) { |
| kfree(inparms); |
| } |
| if (outparms) { |
| kfree(outparms); |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * test_device_register |
| * makes call to device register passing in |
| * the device pointer that we found in a previos |
| * function, returns an error code |
| */ |
| static int test_device_register() { |
| struct device *dev = ltp_mod.dev; |
| struct device_driver *drv = dev->driver; |
| |
| /* check if device register returns an error */ |
| if (device_register(dev)) { |
| printk("tbase: Device not registered\n"); |
| return 1; |
| } |
| else |
| printk("tbase: Device registered\n"); |
| |
| driver_unregister(drv); |
| |
| /* check if driver_register returns an error */ |
| if (driver_register(drv)) { |
| printk("tbase: Driver not registered\n"); |
| return 1; |
| } |
| else |
| printk("tbase: Driver registered\n"); |
| |
| return 0; |
| |
| } |
| |
| /* |
| * test_device_unregister |
| * make test call to device_unregister which |
| * will in turn make calls that will decrememnt |
| * the reference count and clean up as required |
| */ |
| static int test_device_unregister() { |
| struct device *dev = ltp_mod.dev; |
| |
| /* increment reference count */ |
| get_device(dev); |
| |
| /* reset remove pointer */ |
| if (dev->driver->remove) |
| dev->driver->remove = NULL; |
| |
| device_unregister(dev); |
| //check that reference count is smaller by one |
| |
| return 0; |
| } |
| |
| /* |
| * test_bus_add |
| * make call to bus_add_device, which will |
| * in turn add the device that is passed in |
| * to the bus |
| */ |
| static int test_bus_add() { |
| /* check if device register returns an error */ |
| if (bus_add_device(&test_device)) { |
| printk("tbase: Device not added to bus\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: Device added to bus\n"); |
| return 0; |
| } |
| } |
| |
| /* |
| * test_get_drv |
| * make test call to get_driver which should |
| * return a pointer to the driver passed in |
| * and increase the reference count to that |
| * kobject |
| */ |
| static int test_get_drv() { |
| int a, rc; |
| struct device_driver *drv = &test_driver, |
| *tmp = NULL; |
| |
| /* get reference count before test call */ |
| a = atomic_read(&drv->kobj.refcount); |
| |
| /* make test call */ |
| if ((tmp = get_driver(drv))) { |
| rc = 0; |
| printk("tbase: get driver returned driver\n"); |
| } |
| else { |
| rc = 1; |
| printk("tbase: get driver failed to return driver\n"); |
| } |
| |
| /* check reference count */ |
| if ((a == (atomic_read(&drv->kobj.refcount) - 1) )) { |
| rc = 0; |
| printk("tbase: correctly set ref count get driver\n"); |
| } |
| else { |
| rc = 1; |
| printk("tbase: incorrect ref count get driver\n"); |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * test_class_get |
| * make test call to class_get which should return |
| * a pointer to the class passed in and increase |
| * the reference count to that kobject |
| */ |
| static int test_class_get() { |
| int rc; |
| struct class *tmp = NULL; |
| |
| /* get reference count before test call */ |
| tmp = class_get(&test_class); |
| if (tmp == &test_class) { |
| printk("tbase: Success get class\n"); |
| rc = 0; |
| } |
| else { |
| printk("tbase: Failure get class\n"); |
| rc = 1; |
| } |
| |
| class_put(&test_class); |
| return rc; |
| } |
| |
| /* |
| * test_put_drv |
| * make test call to put_driver which should |
| * decrease the reference count to the kobject |
| * pointer in the driver structure |
| */ |
| static int test_put_drv() { |
| int a, rc; |
| struct device_driver *drv = &test_driver; |
| |
| /* get reference count before test call */ |
| a = atomic_read(&drv->kobj.refcount); |
| |
| /* make test call */ |
| put_driver(drv); |
| |
| /* check reference count */ |
| if ((a == (atomic_read(&drv->kobj.refcount) + 1) )) { |
| rc = 0; |
| printk("tbase: correctly set ref count put driver\n"); |
| } |
| else { |
| rc = 1; |
| printk("tbase: incorrect ref count put driver\n"); |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * test_reg_firm |
| * test call to register_firmware, which will |
| * register the subsystem, takes in a struct |
| * subsystem pointer, we can use our bus pointer |
| * that should have been found in a previous test |
| * to pass in a subsystem pointer, returns an |
| * error code |
| */ |
| static int test_reg_firm() { |
| struct subsystem *subsys = NULL; |
| |
| /* check pointer exists */ |
| if (!(subsys = &test_bus_type.subsys)) { |
| printk("tbase: subsys pointer not set in reg firmware\n"); |
| return 1; |
| } |
| |
| /* unregiser firmware */ |
| firmware_unregister(subsys); |
| |
| /* make test call */ |
| if (firmware_register(subsys)) { |
| printk("tbase: failed register firmware\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: regsitered firmware\n"); |
| return 0; |
| } |
| |
| } |
| |
| /* |
| * test_create_file |
| * make test call to create sysfs file for the |
| * driver and if that call is successful then |
| * make a call to remove the file |
| */ |
| static int test_create_file() { |
| struct device_driver *drv = &test_driver; |
| |
| if (driver_create_file(drv, &driver_attr_new_id)) { |
| printk("tbase: failed create sysfs file\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: created sysfs file\n"); |
| driver_remove_file(drv, &driver_attr_new_id); |
| return 0; |
| } |
| |
| } |
| |
| /* |
| * test_dev_suspend |
| * make test call to device_suspend and |
| * if that call is successful then make |
| * a call to device_resume |
| */ |
| static int test_dev_suspend() { |
| int error = 0; |
| |
| error = device_suspend(SUSPEND_SAVE_STATE); |
| if (error) |
| printk("tbase: Failed on device suspend call\n"); |
| else { |
| printk("tbase: Successful on device suspend call\n"); |
| device_resume(); |
| } |
| |
| error = device_suspend(SUSPEND_DISABLE); |
| if (error) |
| printk("tbase: Failed on device suspend call\n"); |
| else { |
| printk("tbase: Successful on device suspend call\n"); |
| device_resume(); |
| } |
| |
| return error; |
| |
| } |
| |
| /* |
| * test_dev_file |
| * make test call to device_create_file |
| * and if that call is successful make |
| * another call to device_remove_file |
| */ |
| static int test_dev_file() { |
| struct device *dev = &test_device; |
| |
| if (device_create_file(dev, &dev_attr_test_id)) { |
| printk("tbase: failed to create dev sysfs file\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: created dev sysfs file\n"); |
| device_remove_file(dev, &dev_attr_test_id); |
| return 0; |
| } |
| |
| } |
| |
| /* |
| * test_bus_rescan |
| * make test call to bus_rescan_devices which |
| * will rescan the bus and attempt to match devices |
| * to drivers, will return 0 for no matches or |
| * the number of matches made, check that the |
| * value returned is not negative |
| */ |
| static int test_bus_rescan() { |
| int count = 0; |
| |
| count = bus_rescan_devices(&test_bus_type); |
| if (count == 0) |
| printk("tbase: found no device/driver matches\n"); |
| else if (count > 0) |
| printk("tbase; found match\n"); |
| else { |
| printk("tbase: bus rescan failed\n"); |
| return count; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * test_bus_file |
| * make test call to bus_create_file |
| * and if that call is successful make |
| * another call to bus_remove_file |
| */ |
| static int test_bus_file() { |
| struct bus_type *bus = &test_bus_type; |
| |
| if (bus_create_file(bus, &bus_attr_test_id)) { |
| printk("tbase: failed to create bus sysfs file\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: created bus sysfs file\n"); |
| bus_remove_file(bus, &bus_attr_test_id); |
| return 0; |
| } |
| |
| } |
| |
| /* |
| * test_class_file |
| * make test call to class_create_file |
| * and if that call is successful make |
| * another call to class_remove_file |
| */ |
| static int test_class_file() { |
| struct class * cls = &test_class; |
| |
| if (class_create_file(cls, &class_attr_test_id)) { |
| printk("tbase: failed to create class sysfs file\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: created class sysfs file\n"); |
| class_remove_file(cls, &class_attr_test_id); |
| return 0; |
| } |
| |
| } |
| |
| /* |
| * test_class_reg |
| * make test call to class_register |
| * with the test_class that is defined |
| * in this module, if that call is |
| * successful then call unregister |
| */ |
| static int test_class_reg() { |
| int error; |
| |
| error = class_register(&test_class); |
| if (error) |
| printk("tbase: class register failed\n"); |
| else |
| printk("tbase: class register succeeded\n"); |
| |
| return error; |
| } |
| |
| /* |
| * test_classdev_reg |
| * make test call to class_device_register |
| * and if that returns successful then |
| * make call to class_device_unregister |
| */ |
| static int test_classdev_reg() { |
| int rc = 0; |
| |
| if (class_device_register(&test_class_dev)) { |
| printk("tbase: Failed to register class device\n"); |
| rc = 1; |
| } |
| else { |
| printk("tbase: Registered class device\n"); |
| |
| /* make class device sysfs file */ |
| if (class_device_create_file(&test_class_dev, &class_device_attr_test_id)) { |
| rc = 1; |
| printk("tbase: Failed to create class device sysfs file\n"); |
| } |
| else { |
| printk("tbase: Created class device sysfs file\n"); |
| class_device_remove_file(&test_class_dev, &class_device_attr_test_id); |
| } |
| |
| class_device_unregister(&test_class_dev); |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * test_classint_reg |
| * make test call to class_interface_register |
| * and if that returns successfule then |
| * make call to class_interface_unregister |
| */ |
| static int test_classint_reg() { |
| |
| if (class_interface_register(&test_interface)) { |
| printk("tbase: Failed to register class interface\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: Registered class interface\n"); |
| class_interface_unregister(&test_interface); |
| return 0; |
| } |
| |
| } |
| |
| /* |
| * test_sysdev_cls_reg |
| * make test call to sysdev_class_register |
| * to register the test_sysclass pointer |
| * as a sysdev_class with the system, check |
| * the return code |
| */ |
| static int test_sysdev_cls_reg() { |
| |
| if (sysdev_class_register(&test_sysclass)) { |
| printk("tbase: Failed to register sysdev class\n"); |
| return 1; |
| } |
| else { |
| printk("tbase: Registered sysdev class\n"); |
| return 0; |
| } |
| |
| } |
| |
| /* |
| * test_sysdev_reg |
| * make test call to sys_device_register |
| * to register the test_sysdev pointer |
| * as a sys_device with the system, check |
| * the return code |
| */ |
| static int test_sysdev_reg() { |
| |
| if (sys_device_register(&test_sys_device)) { |
| printk("tbase: Failed to register sysdev \n"); |
| return 1; |
| } |
| else { |
| printk("tbase: Registered sysdev \n"); |
| return 0; |
| } |
| |
| } |
| |
| /* |
| * tbase_init_module |
| * set the owner of tbase_fops, register the module |
| * as a char device, and perform any necessary |
| * initialization |
| */ |
| static int tbase_init_module(void) { |
| int rc; |
| |
| bus_register(&test_bus_type); |
| driver_register(&test_driver); |
| device_register(&test_device); |
| |
| tbase_fops.owner = THIS_MODULE; |
| |
| printk("tbase: *** Register device %s **\n", DEVICE_NAME); |
| |
| rc = register_chrdev(Major, DEVICE_NAME, &tbase_fops); |
| if (rc < 0) { |
| printk("tbase: Failed to register device.\n"); |
| return rc; |
| } |
| |
| if (Major == 0) |
| Major = rc; |
| |
| /* call any other init functions you might use here */ |
| |
| printk("tbase: Registration success.\n"); |
| return 0; |
| } |
| |
| /* |
| * tmod_exit_module |
| * unregister the device and any necessary |
| * operations to close devices |
| */ |
| static void tbase_exit_module(void) { |
| int rc; |
| |
| device_unregister(&test_device); |
| driver_unregister(&test_driver); |
| bus_unregister(&test_bus_type); |
| |
| /* free any pointers still allocated, using kfree*/ |
| |
| rc = unregister_chrdev(Major, DEVICE_NAME); |
| if (rc < 0) |
| printk("tbase: unregister failed\n"); |
| else |
| printk("tbase: unregister success\n"); |
| |
| } |
| |
| /* specify what that init is run when the module is first |
| loaded and that exit is run when it is removed */ |
| |
| module_init(tbase_init_module) |
| module_exit(tbase_exit_module) |