summaryrefslogtreecommitdiff
path: root/drivers/staging/ozwpan/ozusbsvc.c
blob: bf15dc301cb5a7106eca81c440bfdca6ac68ae87 (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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/* -----------------------------------------------------------------------------
 * Copyright (c) 2011 Ozmo Inc
 * Released under the GNU General Public License Version 2 (GPLv2).
 *
 * This file provides protocol independent part of the implementation of the USB
 * service for a PD.
 * The implementation of this service is split into two parts the first of which
 * is protocol independent and the second contains protocol specific details.
 * This split is to allow alternative protocols to be defined.
 * The implementation of this service uses ozhcd.c to implement a USB HCD.
 * -----------------------------------------------------------------------------
 */

#include <linux/module.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/netdevice.h>
#include <linux/errno.h>
#include <linux/input.h>
#include <asm/unaligned.h>
#include "ozdbg.h"
#include "ozprotocol.h"
#include "ozeltbuf.h"
#include "ozpd.h"
#include "ozproto.h"
#include "ozusbif.h"
#include "ozhcd.h"
#include "ozusbsvc.h"

/*
 * This is called once when the driver is loaded to initialise the USB service.
 * Context: process
 */
int oz_usb_init(void)
{
	return oz_hcd_init();
}

/*
 * This is called once when the driver is unloaded to terminate the USB service.
 * Context: process
 */
void oz_usb_term(void)
{
	oz_hcd_term();
}

/*
 * This is called when the USB service is started or resumed for a PD.
 * Context: softirq
 */
int oz_usb_start(struct oz_pd *pd, int resume)
{
	int rc = 0;
	struct oz_usb_ctx *usb_ctx;
	struct oz_usb_ctx *old_ctx;

	if (resume) {
		oz_dbg(ON, "USB service resumed\n");
		return 0;
	}
	oz_dbg(ON, "USB service started\n");
	/* Create a USB context in case we need one. If we find the PD already
	 * has a USB context then we will destroy it.
	 */
	usb_ctx = kzalloc(sizeof(struct oz_usb_ctx), GFP_ATOMIC);
	if (usb_ctx == NULL)
		return -ENOMEM;
	atomic_set(&usb_ctx->ref_count, 1);
	usb_ctx->pd = pd;
	usb_ctx->stopped = 0;
	/* Install the USB context if the PD doesn't already have one.
	 * If it does already have one then destroy the one we have just
	 * created.
	 */
	spin_lock_bh(&pd->app_lock[OZ_APPID_USB]);
	old_ctx = pd->app_ctx[OZ_APPID_USB];
	if (old_ctx == NULL)
		pd->app_ctx[OZ_APPID_USB] = usb_ctx;
	oz_usb_get(pd->app_ctx[OZ_APPID_USB]);
	spin_unlock_bh(&pd->app_lock[OZ_APPID_USB]);
	if (old_ctx) {
		oz_dbg(ON, "Already have USB context\n");
		kfree(usb_ctx);
		usb_ctx = old_ctx;
	} else if (usb_ctx) {
		/* Take a reference to the PD. This will be released when
		 * the USB context is destroyed.
		 */
		oz_pd_get(pd);
	}
	/* If we already had a USB context and had obtained a port from
	 * the USB HCD then just reset the port. If we didn't have a port
	 * then report the arrival to the USB HCD so we get one.
	 */
	if (usb_ctx->hport) {
		oz_hcd_pd_reset(usb_ctx, usb_ctx->hport);
	} else {
		usb_ctx->hport = oz_hcd_pd_arrived(usb_ctx);
		if (usb_ctx->hport == NULL) {
			oz_dbg(ON, "USB hub returned null port\n");
			spin_lock_bh(&pd->app_lock[OZ_APPID_USB]);
			pd->app_ctx[OZ_APPID_USB] = NULL;
			spin_unlock_bh(&pd->app_lock[OZ_APPID_USB]);
			oz_usb_put(usb_ctx);
			rc = -1;
		}
	}
	oz_usb_put(usb_ctx);
	return rc;
}

/*
 * This is called when the USB service is stopped or paused for a PD.
 * Context: softirq or process
 */
void oz_usb_stop(struct oz_pd *pd, int pause)
{
	struct oz_usb_ctx *usb_ctx;

	if (pause) {
		oz_dbg(ON, "USB service paused\n");
		return;
	}
	spin_lock_bh(&pd->app_lock[OZ_APPID_USB]);
	usb_ctx = (struct oz_usb_ctx *) pd->app_ctx[OZ_APPID_USB];
	pd->app_ctx[OZ_APPID_USB] = NULL;
	spin_unlock_bh(&pd->app_lock[OZ_APPID_USB]);
	if (usb_ctx) {
		struct timespec ts, now;

		getnstimeofday(&ts);
		oz_dbg(ON, "USB service stopping...\n");
		usb_ctx->stopped = 1;
		/* At this point the reference count on the usb context should
		 * be 2 - one from when we created it and one from the hcd
		 * which claims a reference. Since stopped = 1 no one else
		 * should get in but someone may already be in. So wait
		 * until they leave but timeout after 1 second.
		 */
		while ((atomic_read(&usb_ctx->ref_count) > 2)) {
			getnstimeofday(&now);
			/*Approx 1 Sec. this is not perfect calculation*/
			if (now.tv_sec != ts.tv_sec)
				break;
		}
		oz_dbg(ON, "USB service stopped\n");
		oz_hcd_pd_departed(usb_ctx->hport);
		/* Release the reference taken in oz_usb_start.
		 */
		oz_usb_put(usb_ctx);
	}
}

/*
 * This increments the reference count of the context area for a specific PD.
 * This ensures this context area does not disappear while still in use.
 * Context: softirq
 */
void oz_usb_get(void *hpd)
{
	struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;

	atomic_inc(&usb_ctx->ref_count);
}

/*
 * This decrements the reference count of the context area for a specific PD
 * and destroys the context area if the reference count becomes zero.
 * Context: irq or process
 */
void oz_usb_put(void *hpd)
{
	struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;

	if (atomic_dec_and_test(&usb_ctx->ref_count)) {
		oz_dbg(ON, "Dealloc USB context\n");
		oz_pd_put(usb_ctx->pd);
		kfree(usb_ctx);
	}
}

/*
 * Context: softirq
 */
int oz_usb_heartbeat(struct oz_pd *pd)
{
	struct oz_usb_ctx *usb_ctx;
	int rc = 0;

	spin_lock_bh(&pd->app_lock[OZ_APPID_USB]);
	usb_ctx = (struct oz_usb_ctx *) pd->app_ctx[OZ_APPID_USB];
	if (usb_ctx)
		oz_usb_get(usb_ctx);
	spin_unlock_bh(&pd->app_lock[OZ_APPID_USB]);
	if (usb_ctx == NULL)
		return rc;
	if (usb_ctx->stopped)
		goto done;
	if (usb_ctx->hport)
		if (oz_hcd_heartbeat(usb_ctx->hport))
			rc = 1;
done:
	oz_usb_put(usb_ctx);
	return rc;
}

/*
 * Context: softirq
 */
int oz_usb_stream_create(void *hpd, u8 ep_num)
{
	struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
	struct oz_pd *pd = usb_ctx->pd;

	oz_dbg(ON, "%s: (0x%x)\n", __func__, ep_num);
	if (pd->mode & OZ_F_ISOC_NO_ELTS) {
		oz_isoc_stream_create(pd, ep_num);
	} else {
		oz_pd_get(pd);
		if (oz_elt_stream_create(&pd->elt_buff, ep_num,
			4*pd->max_tx_size)) {
			oz_pd_put(pd);
			return -1;
		}
	}
	return 0;
}

/*
 * Context: softirq
 */
int oz_usb_stream_delete(void *hpd, u8 ep_num)
{
	struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;

	if (usb_ctx) {
		struct oz_pd *pd = usb_ctx->pd;

		if (pd) {
			oz_dbg(ON, "%s: (0x%x)\n", __func__, ep_num);
			if (pd->mode & OZ_F_ISOC_NO_ELTS) {
				oz_isoc_stream_delete(pd, ep_num);
			} else {
				if (oz_elt_stream_delete(&pd->elt_buff, ep_num))
					return -1;
				oz_pd_put(pd);
			}
		}
	}
	return 0;
}

/*
 * Context: softirq or process
 */
void oz_usb_request_heartbeat(void *hpd)
{
	struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;

	if (usb_ctx && usb_ctx->pd)
		oz_pd_request_heartbeat(usb_ctx->pd);
}