summaryrefslogtreecommitdiffstats
path: root/drivers/net/irda/sir_dongle.c
blob: c5b76746e72b07f34db645adcde01a3550f68ca5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/*********************************************************************
 *
 *	sir_dongle.c:	manager for serial dongle protocol drivers
 *
 *	Copyright (c) 2002 Martin Diehl
 *
 *	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.
 *
 ********************************************************************/    

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <linux/kmod.h>

#include <net/irda/irda.h>

#include "sir-dev.h"

/**************************************************************************
 *
 * dongle registration and attachment
 *
 */

static LIST_HEAD(dongle_list);			/* list of registered dongle drivers */
static DECLARE_MUTEX(dongle_list_lock);		/* protects the list */

int irda_register_dongle(struct dongle_driver *new)
{
	struct list_head *entry;
	struct dongle_driver *drv;

	IRDA_DEBUG(0, "%s : registering dongle \"%s\" (%d).\n",
		   __FUNCTION__, new->driver_name, new->type);

	down(&dongle_list_lock);
	list_for_each(entry, &dongle_list) {
		drv = list_entry(entry, struct dongle_driver, dongle_list);
		if (new->type == drv->type) {
			up(&dongle_list_lock);
			return -EEXIST;
		}
	}
	list_add(&new->dongle_list, &dongle_list);
	up(&dongle_list_lock);
	return 0;
}

int irda_unregister_dongle(struct dongle_driver *drv)
{
	down(&dongle_list_lock);
	list_del(&drv->dongle_list);
	up(&dongle_list_lock);
	return 0;
}

int sirdev_get_dongle(struct sir_dev *dev, IRDA_DONGLE type)
{
	struct list_head *entry;
	const struct dongle_driver *drv = NULL;
	int err = -EINVAL;

#ifdef CONFIG_KMOD
	request_module("irda-dongle-%d", type);
#endif

	if (dev->dongle_drv != NULL)
		return -EBUSY;
	
	/* serialize access to the list of registered dongles */
	down(&dongle_list_lock);

	list_for_each(entry, &dongle_list) {
		drv = list_entry(entry, struct dongle_driver, dongle_list);
		if (drv->type == type)
			break;
		else
			drv = NULL;
	}

	if (!drv) {
		err = -ENODEV;
		goto out_unlock;	/* no such dongle */
	}

	/* handling of SMP races with dongle module removal - three cases:
	 * 1) dongle driver was already unregistered - then we haven't found the
	 *	requested dongle above and are already out here
	 * 2) the module is already marked deleted but the driver is still
	 *	registered - then the try_module_get() below will fail
	 * 3) the try_module_get() below succeeds before the module is marked
	 *	deleted - then sys_delete_module() fails and prevents the removal
	 *	because the module is in use.
	 */

	if (!try_module_get(drv->owner)) {
		err = -ESTALE;
		goto out_unlock;	/* rmmod already pending */
	}
	dev->dongle_drv = drv;

	if (!drv->open  ||  (err=drv->open(dev))!=0)
		goto out_reject;		/* failed to open driver */

	up(&dongle_list_lock);
	return 0;

out_reject:
	dev->dongle_drv = NULL;
	module_put(drv->owner);
out_unlock:
	up(&dongle_list_lock);
	return err;
}

int sirdev_put_dongle(struct sir_dev *dev)
{
	const struct dongle_driver *drv = dev->dongle_drv;

	if (drv) {
		if (drv->close)
			drv->close(dev);		/* close this dongle instance */

		dev->dongle_drv = NULL;			/* unlink the dongle driver */
		module_put(drv->owner);/* decrement driver's module refcount */
	}

	return 0;
}