[patch] dynamic char and block devices

Jeff Garzik (jgarzik@pobox.com)
Wed, 13 Oct 1999 04:33:13 -0400


This is a multi-part message in MIME format.
--------------6490499963B66DF0BB388384
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Linus and other penguins--

The attached patch against 2.3.21 updates fs/devices.c to be dynamic,
instead of using two static arrays indexed by major number.

This shrinks the code, and gets rid of something indexed by major/minor
-- helpful on the road to a better kdev_t.

Tests ok on local system after a few days. HFS was only tested to
compile, not actually run-time tested, due to its experimental (a.k.a.
probably broken) nature.

The only other comment is that STATIC_ENT_POOL_MAX could probably be
made smaller, as it is only needed during boot time.

Full changelog below.

Regards,

Jeff

fs/devices.c:
* replace chrdevs[], blkdevs[] static arrays with sorted linked list of
devices. This saves some space by eliminating two static, pre-init'd
arrays and some duplicate code.
* const-ify [kbc]devname return values. All callers checked.

include/linux/fs.h:
* update [kbc]devname prototypes

include/linux/kdev_t.h:
* update kdevname prototype

include/linux/hfs_sysdep.h:
* update for new kdevname return type

-- 
Custom driver development	|    Never worry about theory as long
Open source programming		|    as the machinery does what it's
				|    supposed to do.  -- R. A. Heinlein
--------------6490499963B66DF0BB388384
Content-Type: text/plain; charset=us-ascii;
 name="dynamic-devices.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="dynamic-devices.patch"

Index: fs/devices.c =================================================================== RCS file: /g/cvslan/linux_2_3/fs/devices.c,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 devices.c --- fs/devices.c 1999/10/07 07:42:42 1.1.1.1 +++ fs/devices.c 1999/10/13 08:25:26 @@ -7,6 +7,10 @@ * * Added kerneld support: Jacques Gelinas and Bjorn Ekwall * (changed to kmod) + * + * Ditched static array indexed by major number, and updated + * to use a linked list: Jeff Garzik, 13 Oct 1999 + * */ #include <linux/config.h> @@ -15,49 +19,83 @@ #include <linux/string.h> #include <linux/sched.h> #include <linux/stat.h> +#include <linux/slab.h> #include <linux/fcntl.h> +#include <linux/init.h> #include <linux/errno.h> + #ifdef CONFIG_KMOD #include <linux/kmod.h> - #include <linux/tty.h> /* serial module kmod load support */ struct tty_driver *get_tty_driver(kdev_t device); #define isa_tty_dev(ma) (ma == TTY_MAJOR || ma == TTYAUX_MAJOR) #define need_serial(ma,mi) (get_tty_driver(MKDEV(ma,mi)) == NULL) -#endif +#endif /* CONFIG_KMOD */ +typedef enum { + CHRDEV, + BLKDEV +} devtype_t; + +struct device_struct; struct device_struct { + devtype_t devtype; + int major; const char * name; struct file_operations * fops; + struct device_struct *next; }; -static struct device_struct chrdevs[MAX_CHRDEV] = { - { NULL, NULL }, -}; +/* static array used during initialization, when kmalloc is absent */ +#define STATIC_ENT_POOL_MAX 512 +static struct device_struct __initdata static_ent_pool[STATIC_ENT_POOL_MAX]; +static unsigned int __initdata static_ent_pool_len = 0; -static struct device_struct blkdevs[MAX_BLKDEV] = { - { NULL, NULL }, -}; +/* linked list of registered char and block devices */ +static struct device_struct *devlist = NULL; + +/* is it safe to use kmalloc? */ +static int use_kmalloc = 0; + +static struct device_struct *ent_from_major(devtype_t devtype, int major) +{ + struct device_struct *tmp; + + for (tmp = devlist; tmp; tmp = tmp->next) + if (tmp->devtype == devtype && tmp->major == major) + return tmp; + + return NULL; +} + +extern inline struct device_struct * chrdev_from_major (int major) { + return ent_from_major (CHRDEV, major); +} + +extern inline struct device_struct * blkdev_from_major (int major) { + return ent_from_major (BLKDEV, major); +} +/* generate /proc/devices output */ int get_device_list(char * page) { - int i; int len; - + struct device_struct *tmp; + len = sprintf(page, "Character devices:\n"); - for (i = 0; i < MAX_CHRDEV ; i++) { - if (chrdevs[i].fops) { - len += sprintf(page+len, "%3d %s\n", i, chrdevs[i].name); - } - } + + for (tmp = devlist; tmp; tmp = tmp->next) + if (tmp->devtype == CHRDEV) + len += sprintf(page+len, "%3d %s\n", tmp->major, tmp->name); + len += sprintf(page+len, "\nBlock devices:\n"); - for (i = 0; i < MAX_BLKDEV ; i++) { - if (blkdevs[i].fops) { - len += sprintf(page+len, "%3d %s\n", i, blkdevs[i].name); - } - } + + for (tmp = devlist; tmp; tmp = tmp->next) + if (tmp->devtype == BLKDEV) + len += sprintf(page+len, "%3d %s\n", tmp->major, tmp->name); + return len; } @@ -66,41 +104,44 @@ Load the driver if needed. */ static struct file_operations * get_fops( + devtype_t devtype, unsigned int major, unsigned int minor, unsigned int maxdev, - const char *mangle, /* String to use to build the module name */ - struct device_struct tb[]) + const char *mangle) /* String to use to build the module name */ { - struct file_operations *ret = NULL; + struct device_struct *ent = ent_from_major (devtype, major); + + /* + * I do get request for device 0. I have no idea why. It happen + * at shutdown time for one. Without the following test, the + * kernel will happily trigger a request_module() which will + * trigger kmod and modprobe for nothing (since there + * is no device with major number == 0. And furthermore + * it locks the reboot process :-( + * + * Jacques Gelinas (jacques@solucorp.qc.ca) + */ + if (major == 0 || major >= maxdev) + return NULL; - if (major < maxdev){ #ifdef CONFIG_KMOD - /* - * I do get request for device 0. I have no idea why. It happen - * at shutdown time for one. Without the following test, the - * kernel will happily trigger a request_module() which will - * trigger kmod and modprobe for nothing (since there - * is no device with major number == 0. And furthermore - * it locks the reboot process :-( - * - * Jacques Gelinas (jacques@solucorp.qc.ca) - * - * A. Haritsis <ah@doc.ic.ac.uk>: fix for serial module - * though we need the minor here to check if serial dev, - * we pass only the normal major char dev to kmod - * as there is no other loadable dev on these majors - */ - if ((isa_tty_dev(major) && need_serial(major,minor)) || - (major != 0 && !tb[major].fops)) { - char name[20]; - sprintf(name, mangle, major); - request_module(name); - } -#endif - ret = tb[major].fops; + /* + * A. Haritsis <ah@doc.ic.ac.uk>: fix for serial module + * though we need the minor here to check if serial dev, + * we pass only the normal major char dev to kmod + * as there is no other loadable dev on these majors + */ + if ((isa_tty_dev(major) && need_serial(major,minor)) || !ent) { + char name[20]; + sprintf(name, mangle, major); + request_module(name); + + ent = ent_from_major (devtype, major); } - return ret; +#endif + + return ent ? ent->fops : NULL; } @@ -110,82 +151,164 @@ */ struct file_operations * get_blkfops(unsigned int major) { - return get_fops (major,0,MAX_BLKDEV,"block-major-%d",blkdevs); + return get_fops (BLKDEV, major, 0, MAX_BLKDEV, "block-major-%d"); } struct file_operations * get_chrfops(unsigned int major, unsigned int minor) { - return get_fops (major,minor,MAX_CHRDEV,"char-major-%d",chrdevs); + return get_fops (CHRDEV, major, minor, MAX_CHRDEV, "char-major-%d"); } -int register_chrdev(unsigned int major, const char * name, struct file_operations *fops) +static struct device_struct * __init new_static_device (void) +{ + if (static_ent_pool_len < STATIC_ENT_POOL_MAX) { + static_ent_pool_len++; + return &static_ent_pool[static_ent_pool_len - 1]; + } + return NULL; +} + +static struct device_struct *new_device_struct + (devtype_t devtype, + unsigned int major, + const char * name, + struct file_operations *fops) { + struct device_struct *last, *tmp, *ent; + + if (use_kmalloc) + ent = kmalloc (sizeof (*ent), GFP_KERNEL); + else + ent = new_static_device (); + + if (!ent) + return NULL; + + ent->devtype = devtype; + ent->major = major; + ent->name = name; + ent->fops = fops; + ent->next = NULL; + + if (!devlist) { + devlist = ent; + return ent; + } + + tmp = devlist; + last = NULL; + while (tmp && tmp->major < major) { + last = tmp; + tmp = tmp->next; + } + + if (!last) { + ent->next = devlist; + devlist = ent; + } else { + ent->next = last->next; + last->next = ent; + } + + return ent; +} + +/* + * note - fops==NULL must succeed -jgarzik + */ +static int register_anydev(unsigned int maxdev, + devtype_t devtype, + unsigned int major, + const char * name, + struct file_operations *fops) +{ + struct device_struct *ent; + + if (major >= maxdev) + return -EINVAL; + if (major == 0) { - for (major = MAX_CHRDEV-1; major > 0; major--) { - if (chrdevs[major].fops == NULL) { - chrdevs[major].name = name; - chrdevs[major].fops = fops; + for (major = maxdev-1; major > 0; major--) { + ent = ent_from_major(devtype, major); + if (!ent) { + ent = new_device_struct (devtype, major, name, fops); + if (!ent) return -ENOMEM; return major; } + else if (ent && !ent->fops) { + ent->name = name; + ent->fops = fops; + return major; + } } return -EBUSY; } - if (major >= MAX_CHRDEV) - return -EINVAL; - if (chrdevs[major].fops && chrdevs[major].fops != fops) - return -EBUSY; - chrdevs[major].name = name; - chrdevs[major].fops = fops; + + ent = ent_from_major(devtype, major); + + if (!ent) { + ent = new_device_struct (devtype, major, name, fops); + if (!ent) return -ENOMEM; + } else { + ent->name = name; + ent->fops = fops; + } + return 0; } + + +int register_chrdev(unsigned int major, const char * name, struct file_operations *fops) +{ + return register_anydev (MAX_CHRDEV, CHRDEV, major, name, fops); +} + int register_blkdev(unsigned int major, const char * name, struct file_operations *fops) { - if (major == 0) { - for (major = MAX_BLKDEV-1; major > 0; major--) { - if (blkdevs[major].fops == NULL) { - blkdevs[major].name = name; - blkdevs[major].fops = fops; - return major; - } - } - return -EBUSY; + return register_anydev (MAX_BLKDEV, BLKDEV, major, name, fops); +} + +static int unregister_anydev(devtype_t devtype, + unsigned int major, + const char * name) +{ + struct device_struct *tmp, *last; + + if (!devlist) + return -EINVAL; + + tmp = devlist; + last = NULL; + while (tmp && tmp->major != major) { + last = tmp; + tmp = tmp->next; } - if (major >= MAX_BLKDEV) + if (!tmp || strcmp(tmp->name, name)) return -EINVAL; - if (blkdevs[major].fops && blkdevs[major].fops != fops) - return -EBUSY; - blkdevs[major].name = name; - blkdevs[major].fops = fops; + + if (!last) + devlist = devlist->next; + else + last->next = tmp->next; + + if (use_kmalloc) + kfree (tmp); + return 0; } int unregister_chrdev(unsigned int major, const char * name) { - if (major >= MAX_CHRDEV) - return -EINVAL; - if (!chrdevs[major].fops) - return -EINVAL; - if (strcmp(chrdevs[major].name, name)) - return -EINVAL; - chrdevs[major].name = NULL; - chrdevs[major].fops = NULL; - return 0; + return unregister_anydev (CHRDEV, major, name); } int unregister_blkdev(unsigned int major, const char * name) { - if (major >= MAX_BLKDEV) - return -EINVAL; - if (!blkdevs[major].fops) - return -EINVAL; - if (strcmp(blkdevs[major].name, name)) - return -EINVAL; - blkdevs[major].name = NULL; - blkdevs[major].fops = NULL; - return 0; + return unregister_anydev (BLKDEV, major, name); } + /* * This routine checks whether a removable media has been changed, * and invalidates all buffer-cache-entries in that case. This @@ -197,16 +320,17 @@ */ int check_disk_change(kdev_t dev) { - int i; struct file_operations * fops; struct super_block * sb; + struct device_struct *ent; - i = MAJOR(dev); - if (i >= MAX_BLKDEV || (fops = blkdevs[i].fops) == NULL) - return 0; - if (fops->check_media_change == NULL) + ent = blkdev_from_major(MAJOR(dev)); + if (!ent || !ent->fops) return 0; - if (!fops->check_media_change(dev)) + + fops = ent->fops; + if (!fops->check_media_change || + !fops->check_media_change(dev)) return 0; printk(KERN_DEBUG "VFS: Disk change detected on device %s\n", @@ -349,34 +473,40 @@ * Print device name (in decimal, hexadecimal or symbolic) * Note: returns pointer to static data! */ -char * kdevname(kdev_t dev) +const char * kdevname(kdev_t dev) { static char buffer[32]; sprintf(buffer, "%02x:%02x", MAJOR(dev), MINOR(dev)); return buffer; } -char * bdevname(kdev_t dev) +static const char *anydevname (devtype_t devtype, kdev_t dev) { static char buffer[32]; - const char * name = blkdevs[MAJOR(dev)].name; + const char * name; + struct device_struct *ent = ent_from_major(devtype, MAJOR(dev)); - if (!name) - name = "unknown-block"; + if (ent) + name = ent->name; + else + switch (devtype) { + case CHRDEV: name = "unknown-char"; break; + case BLKDEV: name = "unknown-block"; break; + default: name = "unknown"; break; + } sprintf(buffer, "%s(%d,%d)", name, MAJOR(dev), MINOR(dev)); return buffer; } -char * cdevname(kdev_t dev) +const char * bdevname(kdev_t dev) { - static char buffer[32]; - const char * name = chrdevs[MAJOR(dev)].name; + return anydevname (BLKDEV, dev); +} - if (!name) - name = "unknown-char"; - sprintf(buffer, "%s(%d,%d)", name, MAJOR(dev), MINOR(dev)); - return buffer; +const char * cdevname(kdev_t dev) +{ + return anydevname (CHRDEV, dev); } void init_special_inode(struct inode *inode, umode_t mode, int rdev) @@ -396,3 +526,38 @@ else printk(KERN_DEBUG "init_special_inode: bogus imode (%o)\n", mode); } + + +/* + * copy device list from static, __initdata'd vector into dynamic RAM + */ + +static int __init devices_copy_init_list (void) +{ + struct device_struct *ent, *tmp, *last; + + tmp = devlist; + last = NULL; + while (tmp != NULL) { + ent = kmalloc (sizeof (*ent), GFP_KERNEL); + if (!ent) + panic("kmalloc failed: Cannot init devices subsystem"); + memcpy (ent, tmp, sizeof (*ent)); + + if (last == NULL) { + devlist = tmp = ent; + } else { + last->next = tmp = ent; + } + + last = tmp; + tmp = tmp->next; + } + + use_kmalloc = 1; + + return 0; +} + +/* cheap way to get something done "sometime after kmalloc works" */ +module_init(devices_copy_init_list); Index: include/linux/fs.h =================================================================== RCS file: /g/cvslan/linux_2_3/include/linux/fs.h,v retrieving revision 1.1.1.5 retrieving revision 1.1.1.5.2.1 diff -u -r1.1.1.5 -r1.1.1.5.2.1 --- include/linux/fs.h 1999/10/12 01:24:41 1.1.1.5 +++ include/linux/fs.h 1999/10/12 10:23:54 1.1.1.5.2.1 @@ -741,9 +741,9 @@ extern int chrdev_open(struct inode *, struct file *); extern struct file_operations def_chr_fops; extern struct inode_operations chrdev_inode_operations; -extern char * bdevname(kdev_t); -extern char * cdevname(kdev_t); -extern char * kdevname(kdev_t); +extern const char * bdevname(kdev_t); +extern const char * cdevname(kdev_t); +extern const char * kdevname(kdev_t); extern void init_special_inode(struct inode *, umode_t, int); extern void init_fifo(struct inode *); Index: include/linux/hfs_sysdep.h =================================================================== RCS file: /g/cvslan/linux_2_3/include/linux/hfs_sysdep.h,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.18.1 diff -u -r1.1.1.1 -r1.1.1.1.18.1 --- include/linux/hfs_sysdep.h 1999/10/07 07:42:52 1.1.1.1 +++ include/linux/hfs_sysdep.h 1999/10/12 10:23:54 1.1.1.1.18.1 @@ -121,7 +121,7 @@ sys_mdb->s_dirt = 1; } -extern inline char *hfs_mdb_name(hfs_sysmdb sys_mdb) { +extern inline const char *hfs_mdb_name(hfs_sysmdb sys_mdb) { return kdevname(sys_mdb->s_dev); } Index: include/linux/kdev_t.h =================================================================== RCS file: /g/cvslan/linux_2_3/include/linux/kdev_t.h,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.18.1 diff -u -r1.1.1.1 -r1.1.1.1.18.1 --- include/linux/kdev_t.h 1999/10/07 07:42:51 1.1.1.1 +++ include/linux/kdev_t.h 1999/10/12 10:23:54 1.1.1.1.18.1 @@ -73,7 +73,8 @@ #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) #define B_FREE 0xffff /* yuk */ -extern char * kdevname(kdev_t); /* note: returns pointer to static data! */ +/* note: returns pointer to static data! */ +extern const char * kdevname(kdev_t); /* As long as device numbers in the outside world have 16 bits only,

--------------6490499963B66DF0BB388384--

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/