[PATCH] pcmcia: make config_t independent, add reference counting
Handle config_t structs independent of struct pcmcia_socket, and add
reference counting for them.
Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
diff --git a/drivers/pcmcia/cs.c b/drivers/pcmcia/cs.c
index 613f2f1..45cffbf 100644
--- a/drivers/pcmcia/cs.c
+++ b/drivers/pcmcia/cs.c
@@ -406,8 +406,6 @@
cb_free(s);
#endif
s->functions = 0;
- kfree(s->config);
- s->config = NULL;
s->ops->get_status(s, &status);
if (status & SS_POWERON) {
diff --git a/drivers/pcmcia/cs_internal.h b/drivers/pcmcia/cs_internal.h
index 88c96ae..01c32af 100644
--- a/drivers/pcmcia/cs_internal.h
+++ b/drivers/pcmcia/cs_internal.h
@@ -16,6 +16,7 @@
#define _LINUX_CS_INTERNAL_H
#include <linux/config.h>
+#include <linux/kref.h>
/* Flags in client state */
#define CLIENT_CONFIG_LOCKED 0x0001
@@ -40,6 +41,7 @@
/* Each card function gets one of these guys */
typedef struct config_t {
+ struct kref ref;
u_int state;
u_int Attributes;
u_int IntType;
diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c
index 5166f00..37ba124 100644
--- a/drivers/pcmcia/ds.c
+++ b/drivers/pcmcia/ds.c
@@ -23,6 +23,7 @@
#include <linux/workqueue.h>
#include <linux/crc32.h>
#include <linux/firmware.h>
+#include <linux/kref.h>
#define IN_CARD_SERVICES
#include <pcmcia/cs_types.h>
@@ -343,12 +344,19 @@
put_device(&p_dev->dev);
}
+static void pcmcia_release_function(struct kref *ref)
+{
+ struct config_t *c = container_of(ref, struct config_t, ref);
+ kfree(c);
+}
+
static void pcmcia_release_dev(struct device *dev)
{
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
ds_dbg(1, "releasing dev %p\n", p_dev);
pcmcia_put_socket(p_dev->socket);
kfree(p_dev->devname);
+ kref_put(&p_dev->function_config->ref, pcmcia_release_function);
kfree(p_dev);
}
@@ -377,30 +385,14 @@
p_drv = to_pcmcia_drv(dev->driver);
s = p_dev->socket;
- if ((!p_drv->probe) || (!try_module_get(p_drv->owner))) {
+ if ((!p_drv->probe) || (!p_dev->function_config) ||
+ (!try_module_get(p_drv->owner))) {
ret = -EINVAL;
goto put_dev;
}
p_dev->state &= ~CLIENT_UNBOUND;
- /* set up the device configuration, if it hasn't been done before */
- if (!s->functions) {
- cistpl_longlink_mfc_t mfc;
- if (pccard_read_tuple(s, p_dev->func, CISTPL_LONGLINK_MFC,
- &mfc) == CS_SUCCESS)
- s->functions = mfc.nfn;
- else
- s->functions = 1;
- s->config = kzalloc(sizeof(config_t) * s->functions,
- GFP_KERNEL);
- if (!s->config) {
- ret = -ENOMEM;
- goto put_module;
- }
- }
- p_dev->function_config = &s->config[p_dev->func];
-
ret = p_drv->probe(p_dev);
if (ret)
goto put_module;
@@ -576,7 +568,7 @@
struct pcmcia_device * pcmcia_device_add(struct pcmcia_socket *s, unsigned int function)
{
- struct pcmcia_device *p_dev;
+ struct pcmcia_device *p_dev, *tmp_dev;
unsigned long flags;
int bus_id_len;
@@ -597,6 +589,8 @@
p_dev->socket = s;
p_dev->device_no = (s->device_count++);
p_dev->func = function;
+ if (s->functions < function)
+ s->functions = function;
p_dev->dev.bus = &pcmcia_bus_type;
p_dev->dev.parent = s->dev.dev;
@@ -611,28 +605,50 @@
/* compat */
p_dev->state = CLIENT_UNBOUND;
- /* Add to the list in pcmcia_bus_socket */
+
spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+
+ /*
+ * p_dev->function_config must be the same for all card functions.
+ * Note that this is serialized by the device_add_lock, so that
+ * only one such struct will be created.
+ */
+ list_for_each_entry(tmp_dev, &s->devices_list, socket_device_list)
+ if (p_dev->func == tmp_dev->func) {
+ p_dev->function_config = tmp_dev->function_config;
+ kref_get(&p_dev->function_config->ref);
+ }
+
+ /* Add to the list in pcmcia_bus_socket */
list_add_tail(&p_dev->socket_device_list, &s->devices_list);
+
spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+ if (!p_dev->function_config) {
+ p_dev->function_config = kzalloc(sizeof(struct config_t),
+ GFP_KERNEL);
+ if (!p_dev->function_config)
+ goto err_unreg;
+ kref_init(&p_dev->function_config->ref);
+ }
+
printk(KERN_NOTICE "pcmcia: registering new device %s\n",
p_dev->devname);
pcmcia_device_query(p_dev);
- if (device_register(&p_dev->dev)) {
- spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
- list_del(&p_dev->socket_device_list);
- spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
-
- goto err_free;
- }
+ if (device_register(&p_dev->dev))
+ goto err_unreg;
up(&device_add_lock);
return p_dev;
+ err_unreg:
+ spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+ list_del(&p_dev->socket_device_list);
+ spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+
err_free:
kfree(p_dev->devname);
kfree(p_dev);
diff --git a/include/pcmcia/ss.h b/include/pcmcia/ss.h
index 2889a69..6529ccc 100644
--- a/include/pcmcia/ss.h
+++ b/include/pcmcia/ss.h
@@ -186,7 +186,6 @@
u_short lock_count;
pccard_mem_map cis_mem;
void __iomem *cis_virt;
- struct config_t *config;
struct {
u_int AssignedIRQ;
u_int Config;