[PATCHES] pcmcia: struct pcmcia_device, proper sysfs representation for PCMCIA cards

Dominik Brodowski linux at brodo.de
Tue Jul 8 18:00:25 BST 2003


These patches add a new "struct pcmcia_device", which contains a
corresponding struct device. For multifunction PCMCIA cards, the same is
done as for multifunction PCI devices: one such device for each function is
created. It makes life *soooo* much easier.


pcmcia-2.5.74-bk6-ds_license - add ds.c license to be GPL only

As some of this code is based on GPL-only code, it has been necessary to
switch the license to be GPL only, too. Dave Hinds has OK'ed this change.


pcmcia-2.5.74-bk6-resources_1ab - check whether resource setup is done

see various threads, especially my latest RFCs


pcmcia-2.5.74-bk6-pcmcia_device - actual adding of struct pcmcia_device

pcmcia-2.5.74-bk6-sysfs_device_info - more sysfs output for pcmcia_devices



Please test & apply,

	Dominik
-------------- next part --------------
Change the license of ds.c to GPL only. This is necessary as I want to
merge code which is clearly derivative work of other GPL-only
code. OK'ed with David Hinds.

 drivers/pcmcia/ds.c |   35 ++++++++++-------------------------
 1 files changed, 10 insertions(+), 25 deletions(-)

diff -ruN linux-original/drivers/pcmcia/ds.c linux/drivers/pcmcia/ds.c
--- linux-original/drivers/pcmcia/ds.c	2003-07-08 15:54:10.000000000 +0200
+++ linux/drivers/pcmcia/ds.c	2003-07-08 16:01:38.000000000 +0200
@@ -1,34 +1,19 @@
 /*======================================================================
 
     PC Card Driver Services
-    
-    ds.c 1.112 2001/10/13 00:08:28
-    
-    The contents of this file are subject to the Mozilla Public
-    License Version 1.1 (the "License"); you may not use this file
-    except in compliance with the License. You may obtain a copy of
-    the License at http://www.mozilla.org/MPL/
-
-    Software distributed under the License is distributed on an "AS
-    IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-    implied. See the License for the specific language governing
-    rights and limitations under the License.
+   
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License version 2 as
+    published by the Free Software Foundation.
 
     The initial developer of the original code is David A. Hinds
     <dahinds at users.sourceforge.net>.  Portions created by David A. Hinds
     are Copyright (C) 1999 David A. Hinds.  All Rights Reserved.
 
-    Alternatively, the contents of this file may be used under the
-    terms of the GNU General Public License version 2 (the "GPL"), in
-    which case the provisions of the GPL are applicable instead of the
-    above.  If you wish to allow the use of your version of this file
-    only under the terms of the GPL and not to allow others to use
-    your version of this file under the MPL, indicate your decision
-    by deleting the provisions above and replace them with the notice
-    and other provisions required by the GPL.  If you do not delete
-    the provisions above, a recipient may use your version of this
-    file under either the MPL or the GPL.
-    
+    Adaption to the Linux driver model, hotplug and module functions
+    of kernel 2.5 are Copyright (C) 2003 Dominik Brodowski. All Rights 
+    Reserved
+
 ======================================================================*/
 
 #include <linux/config.h>
@@ -63,9 +48,9 @@
 
 /* Module parameters */
 
-MODULE_AUTHOR("David Hinds <dahinds at users.sourceforge.net>");
+MODULE_AUTHOR("David Hinds <dahinds at users.sourceforge.net>, Dominik Brodowski <linux at brodo.de>");
 MODULE_DESCRIPTION("PCMCIA Driver Services " CS_RELEASE);
-MODULE_LICENSE("Dual MPL/GPL");
+MODULE_LICENSE("GPL");
 
 #define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i")
 
-------------- next part --------------
This patch adds a struct pcmcia_device, and fills it with content for
every function of every pcmcia card inserted into the socket as long
as there are resources to play with (see previous patch)

 drivers/pcmcia/cistpl.c |    1
 drivers/pcmcia/cs.c     |    3
 drivers/pcmcia/ds.c     |  179 +++++++++++++++++++++++++++++++++++++++++++++++-
 include/pcmcia/ds.h     |   25 ++++++
 4 files changed, 206 insertions(+), 2 deletions(-)

diff -ruN linux-original/drivers/pcmcia/cistpl.c linux/drivers/pcmcia/cistpl.c
--- linux-original/drivers/pcmcia/cistpl.c	2003-07-08 15:58:51.000000000 +0200
+++ linux/drivers/pcmcia/cistpl.c	2003-07-08 16:08:05.000000000 +0200
@@ -1417,6 +1417,7 @@
     ret = pcmcia_parse_tuple(handle, &tuple, parse);
     return ret;
 }
+EXPORT_SYMBOL(read_tuple);
 
 /*======================================================================
 
diff -ruN linux-original/drivers/pcmcia/cs.c linux/drivers/pcmcia/cs.c
--- linux-original/drivers/pcmcia/cs.c	2003-07-08 15:58:51.000000000 +0200
+++ linux/drivers/pcmcia/cs.c	2003-07-08 16:08:05.000000000 +0200
@@ -128,6 +128,9 @@
 LIST_HEAD(pcmcia_socket_list);
 DECLARE_RWSEM(pcmcia_socket_list_rwsem);
 
+EXPORT_SYMBOL(pcmcia_socket_list);
+EXPORT_SYMBOL(pcmcia_socket_list_rwsem);
+
 /*====================================================================*/
 
 /* String tables for error messages */
diff -ruN linux-original/drivers/pcmcia/ds.c linux/drivers/pcmcia/ds.c
--- linux-original/drivers/pcmcia/ds.c	2003-07-08 16:09:00.000000000 +0200
+++ linux/drivers/pcmcia/ds.c	2003-07-08 16:11:22.000000000 +0200
@@ -93,6 +93,11 @@
 	socket_bind_t		*bind;
 	struct device		*socket_dev;
 	struct pcmcia_socket	*parent;
+
+	/* the PCMCIA devices connected to this socket (normally one, more
+	 * for multifunction devices: */
+	struct list_head	devices_list;
+	struct semaphore	devices_list_sem;
 };
 
 #define SOCKET_PRESENT		0x01
@@ -127,6 +132,9 @@
 
 /*======================================================================*/
 
+extern int validate_cis(client_handle_t handle, cisinfo_t *info);
+extern int read_tuple(client_handle_t handle, cisdata_t code, void *parse);
+
 static struct pcmcia_driver * get_pcmcia_driver (dev_info_t *dev_info);
 static struct pcmcia_bus_socket * get_socket_info_by_nr(unsigned int nr);
 
@@ -183,6 +191,141 @@
 }
 #endif
 
+/*********************** device adding / removing ***************************/
+
+
+static int pcmcia_add_card(struct pcmcia_socket *s)
+{
+	struct pcmcia_device *p_dev;
+	int ret;
+	unsigned int has_cis = 0;
+	unsigned int func = 0, no_funcs = 1;
+
+	down(&s->pcmcia->devices_list_sem);
+
+ next_device:
+	DEBUG(0, "ds: pcmcia_add_card(%i):", func);
+
+	p_dev = kmalloc(sizeof(struct pcmcia_device), GFP_KERNEL);
+	if (!p_dev) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	memset(p_dev, 0, sizeof(struct pcmcia_device));
+	p_dev->dev.bus = &pcmcia_bus_type;
+	p_dev->dev.parent = s->dev.dev;
+	p_dev->socket = s;
+
+	/* first case: no resources are available, so we can't decode
+	 * any device information */
+	if (!((dynamic_resources_available) || 
+	     (s->features & SS_CAP_HAS_RESOURCES))) {
+		DEBUG(0, "no resources available\n");
+		sprintf (p_dev->dev.bus_id, "pcmcia%d.?", 
+			 s->sock);
+		sprintf (p_dev->dev.name, "unknown");
+		ret = device_register(&p_dev->dev);
+		if (ret) {
+			kfree(p_dev);
+			goto out;
+		}
+		list_add(&p_dev->socket_device_list, &s->pcmcia->devices_list);
+		goto out;
+	}
+
+	/* if we got here for the first time, we need to check whether
+	 * the CIS is valid etc. */
+	if (!func) {
+		cisinfo_t cisinfo;
+		ret = pcmcia_validate_cis(s->pcmcia->handle, &cisinfo);
+		if (ret || !cisinfo.Chains) {
+			DEBUG(0, "invalid CIS\n");
+		} else {
+			cistpl_longlink_mfc_t mfc;
+
+			DEBUG(0, "has CIS\n");
+			has_cis = 1;
+			if (!read_tuple(s->pcmcia->handle, CISTPL_LONGLINK_MFC, &mfc))
+				no_funcs = mfc.nfn;
+			else
+				no_funcs = 1;
+		}
+	}
+
+	p_dev->func = func;
+	p_dev->has_cis = has_cis;
+
+	sprintf (p_dev->dev.bus_id, "pcmcia%d.%d", s->sock, p_dev->func);
+
+	/* read out device name "vers_1" */
+	if (!has_cis || (read_tuple(s->pcmcia->handle, CISTPL_VERS_1, &p_dev->vers1)))
+		p_dev->vers1.ns = 0;
+
+	if (p_dev->vers1.ns) {
+		char name[255] = "\0";
+		int i = 0;
+		for (i=0; i < p_dev->vers1.ns; i++) {
+			strcat(name, (p_dev->vers1.str + p_dev->vers1.ofs[i]));
+			strcat(name, " ");
+		}
+		snprintf(p_dev->dev.name, DEVICE_NAME_SIZE, "%s", name);
+	} else
+		sprintf (p_dev->dev.name, "unknown");
+
+	/* read out manufacture ID */
+	if (has_cis && !(read_tuple(s->pcmcia->handle, CISTPL_MANFID, &p_dev->manfid)))
+		p_dev->has_manfid = 1;
+
+	/* read out function ID -- rule of thumb: cards with no FUNCID, but with 
+	 * common memory device geometry information, are probably memory cards */
+	if (has_cis && !(read_tuple(s->pcmcia->handle, CISTPL_FUNCID, &p_dev->funcid)))
+		p_dev->has_funcid = 1;
+	else {
+		cistpl_device_geo_t devgeo;
+		if (has_cis && !(read_tuple(s->pcmcia->handle, CISTPL_DEVICE_GEO, &devgeo))) {
+			DEBUG(0, "(mem device geometry) leads to funcid_memory\n");
+			p_dev->has_funcid = 1;
+			p_dev->funcid.func = CISTPL_FUNCID_MEMORY;
+		}
+	}
+
+	/* register it both with the driver model core and with the bus socket list */
+	ret = device_register(&p_dev->dev);
+	if (ret) {
+		kfree(p_dev);
+		goto out;
+	}
+	list_add(&p_dev->socket_device_list, &s->pcmcia->devices_list);
+
+	/* if we got a multifunction device, we need to register more devices,
+	 * so go back up */
+	func++;
+	if (no_funcs > func)
+		goto next_device;
+
+ out:
+	up(&s->pcmcia->devices_list_sem);
+	return ret;
+}
+
+static void pcmcia_remove_card(struct pcmcia_socket *s)
+{
+	struct list_head *p1;
+	struct list_head *p2;
+
+	DEBUG(0, "ds: pcmcia_remove_card()\n");
+
+	down(&s->pcmcia->devices_list_sem);
+	list_for_each_safe(p1, p2, &s->pcmcia->devices_list) {
+		struct pcmcia_device *p_dev = container_of(p1, struct pcmcia_device, socket_device_list);
+		device_unregister(&p_dev->dev);
+		list_del(&p_dev->socket_device_list);
+		kfree(p_dev);
+	}
+	up(&s->pcmcia->devices_list_sem);
+}
+
+
 /*======================================================================
 
     These manage a ring buffer of events pending for one user process
@@ -236,6 +379,7 @@
 static void handle_removal(void *data)
 {
     struct pcmcia_bus_socket *s = data;
+    pcmcia_remove_card(s->parent);
     handle_event(s, CS_EVENT_CARD_REMOVAL);
     s->state &= ~SOCKET_REMOVAL_PENDING;
 }
@@ -260,6 +404,10 @@
     case CS_EVENT_CARD_REMOVAL:
 	s->state &= ~SOCKET_PRESENT;
 	if (!(s->state & SOCKET_REMOVAL_PENDING)) {
+		/* this is delayed so that the other "clients" [i.e. the 
+		 * specific drivers can shut down]. Once the client and 
+		 * driver management is unified, this delay can go away.
+		 */
 		s->state |= SOCKET_REMOVAL_PENDING;
 		schedule_delayed_work(&s->removal,  HZ/10);
 	}
@@ -267,6 +415,7 @@
 	
     case CS_EVENT_CARD_INSERTION:
 	s->state |= SOCKET_PRESENT;
+	pcmcia_add_card(s->parent);
 	handle_event(s, event);
 	break;
 
@@ -643,6 +792,9 @@
 
 /*====================================================================*/
 
+extern struct rw_semaphore pcmcia_socket_list_rwsem;
+extern struct list_head pcmcia_socket_list;
+
 static int ds_ioctl(struct inode * inode, struct file * file,
 		    u_int cmd, u_long arg)
 {
@@ -687,12 +839,31 @@
     if (cmd & IOC_IN) copy_from_user((char *)&buf, (char *)arg, size);
     
     if ((cmd != DS_ADJUST_RESOURCE_INFO) && (!dyn_res_status)) {
+	    struct pcmcia_socket *sock;
+
 	    dyn_res_status = 2;
 
 	    /* resources have just become available, and will stay available forever...
 	     * well, at least as long as the kernel is rebooted [pcmcia isn't modular
 	     * at the moment] */
 	    dynamic_resources_available = 1;
+
+	    /* rescan the sockets, if necessary */
+	    down_read(&pcmcia_socket_list_rwsem);
+
+	    list_for_each_entry(sock, &pcmcia_socket_list, socket_list) {
+		    if (!sock->pcmcia)
+			    continue;
+		    if (sock->features & SS_CAP_HAS_RESOURCES)
+			    continue;
+		    if (list_empty(&sock->pcmcia->devices_list))
+			    continue;
+		    pcmcia_remove_card(sock);
+		    pcmcia_add_card(sock);
+	    }
+
+	    up_read(&pcmcia_socket_list_rwsem);
+
     }
 
     switch (cmd) {
@@ -860,6 +1031,9 @@
 	s->socket_dev = socket->dev.dev;
 	INIT_WORK(&s->removal, handle_removal, s);
 	s->parent = socket;
+	socket->pcmcia = s;
+	init_MUTEX(&s->devices_list_sem);
+	INIT_LIST_HEAD(&s->devices_list);
 
 	/* Set up hotline to Card Services */
 	client_reg.dev_info = bind.dev_info = &dev_info;
@@ -886,11 +1060,10 @@
 	if (ret != CS_SUCCESS) {
 		cs_error(NULL, RegisterClient, ret);
 		kfree(s);
+		socket->pcmcia = NULL;
 		return -EINVAL;
 	}
 
-	socket->pcmcia = s;
-
 	return 0;
 }
 
@@ -902,6 +1075,8 @@
 	if (!socket || !socket->pcmcia)
 		return;
 
+	pcmcia_remove_card(socket);
+
 	flush_scheduled_work();
 
 	pcmcia_deregister_client(socket->pcmcia->handle);
diff -ruN linux-original/include/pcmcia/ds.h linux/include/pcmcia/ds.h
--- linux-original/include/pcmcia/ds.h	2003-07-08 15:54:13.000000000 +0200
+++ linux/include/pcmcia/ds.h	2003-07-08 16:08:05.000000000 +0200
@@ -159,5 +159,30 @@
 /* error reporting */
 void cs_error(client_handle_t handle, int func, int ret);
 
+
+struct pcmcia_socket;
+
+struct pcmcia_device {
+	struct pcmcia_socket		*socket;
+
+	struct list_head		socket_device_list;
+
+	unsigned int			func;
+	struct device			dev;
+
+	/* cis information */
+	unsigned int			has_cis;
+	cistpl_vers_1_t			vers1;
+
+	unsigned int			has_manfid;
+	cistpl_manfid_t			manfid;
+
+	unsigned int			has_funcid;
+	cistpl_funcid_t			funcid;
+};
+
+#define to_pcmcia_dev(n) container_of(n, struct pcmcia_device, dev)
+#define to_pcmcia_drv(n) container_of(n, struct pcmcia_driver, drv)
+
 #endif /* __KERNEL__ */
 #endif /* _LINUX_DS_H */
-------------- next part --------------
To do anything proper in the kernel, it needs to be known whether
a) the socket driver only uses a fixed area of IOPORTS and IOMEM,
therefore can't clash with other e.g. ISA devices -- then
socket->flags should be |= SS_CAP_HAS_RESOURCES --, or
b) the resource database (for all other sockets) has been filled with
enough information [currently by cardmgr, later using a different
interface]

Later, pccardd will only be started -- and thus insert events only be
handled -- once these resources have been set. So some of this code
will disappear again soon.

 drivers/pcmcia/ds.c |   31 +++++++++++++++++++++++++++++--
 include/pcmcia/ss.h |    1 +
 2 files changed, 30 insertions(+), 2 deletions(-)

diff -ruN linux-original/drivers/pcmcia/ds.c linux/drivers/pcmcia/ds.c
--- linux-original/drivers/pcmcia/ds.c	2003-07-08 16:03:57.000000000 +0200
+++ linux/drivers/pcmcia/ds.c	2003-07-08 16:03:49.000000000 +0200
@@ -110,6 +110,14 @@
 
 /*====================================================================*/
 
+/* As soon as the iomem/ioport/irq configuration is done by cardmgr
+ * (3.2.x), this is set to 1.
+ */
+static int dynamic_resources_available = 0;
+
+
+/*====================================================================*/
+
 void cs_error(client_handle_t handle, int func, int ret)
 {
 	error_info_t err = { func, ret };
@@ -643,6 +651,8 @@
     u_int size;
     int ret, err;
     ds_ioctl_arg_t buf;
+    static int dyn_res_status = 1;
+
 
     DEBUG(2, "ds_ioctl(socket %d, %#x, %#lx)\n", i, cmd, arg);
     
@@ -676,10 +686,27 @@
     
     if (cmd & IOC_IN) copy_from_user((char *)&buf, (char *)arg, size);
     
+    if ((cmd != DS_ADJUST_RESOURCE_INFO) && (!dyn_res_status)) {
+	    dyn_res_status = 2;
+
+	    /* resources have just become available, and will stay available forever...
+	     * well, at least as long as the kernel is rebooted [pcmcia isn't modular
+	     * at the moment] */
+	    dynamic_resources_available = 1;
+    }
+
     switch (cmd) {
     case DS_ADJUST_RESOURCE_INFO:
-	ret = pcmcia_adjust_resource_info(s->handle, &buf.adjust);
-	break;
+    {
+	    if (dyn_res_status == 2) {
+		    printk(KERN_INFO "pcmcia: warning: DS_ADJUST_RESOURCE_INFO calls don't do anything...\n");
+		    dyn_res_status++;
+		    break;
+	    }
+	    dyn_res_status = 0;
+	    ret = pcmcia_adjust_resource_info(s->handle, &buf.adjust);
+	    break;
+    }
     case DS_GET_CARD_SERVICES_INFO:
 	ret = pcmcia_get_card_services_info(&buf.servinfo);
 	break;
diff -ruN linux-original/include/pcmcia/ss.h linux/include/pcmcia/ss.h
--- linux-original/include/pcmcia/ss.h	2003-07-08 15:58:51.000000000 +0200
+++ linux/include/pcmcia/ss.h	2003-07-08 16:03:49.000000000 +0200
@@ -57,6 +57,7 @@
 #define SS_CAP_VIRTUAL_BUS	0x0002
 #define SS_CAP_MEM_ALIGN	0x0004
 #define SS_CAP_STATIC_MAP	0x0008
+#define SS_CAP_HAS_RESOURCES	0x0010
 #define SS_CAP_PCCARD		0x4000
 #define SS_CAP_CARDBUS		0x8000
 
-------------- next part --------------
This patch adds seven files (manf_id, card_id, func_id, vers1 to
vers4) to any pcmcia device registered with the driver model core.

 drivers/pcmcia/ds.c |   55 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 55 insertions(+)

diff -ruN linux-original/drivers/pcmcia/ds.c linux/drivers/pcmcia/ds.c
--- linux-original/drivers/pcmcia/ds.c	2003-07-08 16:14:02.000000000 +0200
+++ linux/drivers/pcmcia/ds.c	2003-07-08 16:15:55.000000000 +0200
@@ -191,6 +191,43 @@
 }
 #endif
 
+/************************ per-device sysfs output ***************************/
+
+#define show_one(file_name, object, test, format)			\
+static ssize_t show_##file_name 					\
+(struct device *dev, char *buf)						\
+{									\
+	struct pcmcia_device *p_dev = to_pcmcia_dev(dev);		\
+	return p_dev->test ? sprintf (buf, format, p_dev->object) : 	\
+			sprintf(buf, "unknown\n");			\
+}	 								\
+DEVICE_ATTR(file_name, 0444, show_##file_name, NULL);
+
+show_one(func_id, funcid.func, has_funcid, "%1X\n");
+show_one(manf_id, manfid.manf, has_manfid, "%04X\n");
+show_one(card_id, manfid.card, has_manfid, "%04X\n");
+
+#define show_one_s(file_name, object, test)	 			\
+static ssize_t show_##file_name 					\
+(struct device *dev, char *buf)						\
+{									\
+	struct pcmcia_device *p_dev = to_pcmcia_dev(dev);		\
+	return test ? sprintf (buf, "%s\n", object) : 			\
+			sprintf(buf, "\n");				\
+}	 								\
+DEVICE_ATTR(file_name, 0444, show_##file_name, NULL);
+
+show_one_s(vers1, (p_dev->vers1.str + p_dev->vers1.ofs[0]), 
+	   (p_dev->vers1.ns >= 1));
+show_one_s(vers2, (p_dev->vers1.str + p_dev->vers1.ofs[1]), 
+	   (p_dev->vers1.ns >= 2));
+show_one_s(vers3, (p_dev->vers1.str + p_dev->vers1.ofs[2]), 
+	   (p_dev->vers1.ns >= 3));
+show_one_s(vers4, (p_dev->vers1.str + p_dev->vers1.ofs[3]), 
+	   (p_dev->vers1.ns >= 4));
+
+
+
 /*********************** device adding / removing ***************************/
 
 
@@ -297,6 +334,15 @@
 	}
 	list_add(&p_dev->socket_device_list, &s->pcmcia->devices_list);
 
+	/* add sysfs files */
+	device_create_file(&p_dev->dev, &dev_attr_func_id);
+	device_create_file(&p_dev->dev, &dev_attr_manf_id);
+	device_create_file(&p_dev->dev, &dev_attr_card_id);
+	device_create_file(&p_dev->dev, &dev_attr_vers1);
+	device_create_file(&p_dev->dev, &dev_attr_vers2);
+	device_create_file(&p_dev->dev, &dev_attr_vers3);
+	device_create_file(&p_dev->dev, &dev_attr_vers4);
+
 	/* if we got a multifunction device, we need to register more devices,
 	 * so go back up */
 	func++;
@@ -318,6 +364,15 @@
 	down(&s->pcmcia->devices_list_sem);
 	list_for_each_safe(p1, p2, &s->pcmcia->devices_list) {
 		struct pcmcia_device *p_dev = container_of(p1, struct pcmcia_device, socket_device_list);
+
+		device_remove_file(&p_dev->dev, &dev_attr_func_id);
+		device_remove_file(&p_dev->dev, &dev_attr_manf_id);
+		device_remove_file(&p_dev->dev, &dev_attr_card_id);
+		device_remove_file(&p_dev->dev, &dev_attr_vers1);
+		device_remove_file(&p_dev->dev, &dev_attr_vers2);
+		device_remove_file(&p_dev->dev, &dev_attr_vers3);
+		device_remove_file(&p_dev->dev, &dev_attr_vers4);
+
 		device_unregister(&p_dev->dev);
 		list_del(&p_dev->socket_device_list);
 		kfree(p_dev);


More information about the linux-pcmcia mailing list