[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