summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/bcmdhd/circularbuf.c
blob: 30c60db3bf6ab37074d5f4a918a88a30baca8f14 (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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/*
 * Initialization and support routines for self-booting compressed image.
 *
 * Copyright (C) 1999-2016, Broadcom Corporation
 * 
 *      Unless you and Broadcom execute a separate written software license
 * agreement governing use of this software, this software is licensed to you
 * under the terms of the GNU General Public License version 2 (the "GPL"),
 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 * following added to such license:
 * 
 *      As a special exception, the copyright holders of this software give you
 * permission to link this software with independent modules, and to copy and
 * distribute the resulting executable under terms of your choice, provided that
 * you also meet, for each linked independent module, the terms and conditions of
 * the license of that module.  An independent module is a module which is not
 * derived from this software.  The special exception does not apply to any
 * modifications of the software.
 * 
 *      Notwithstanding the above, under no circumstances may you combine this
 * software in any way with any other Broadcom software provided under a license
 * other than the GPL, without Broadcom's express prior written consent.
 *
 * $Id: circularbuf.c 452261 2014-01-29 19:30:23Z $
 */

#include <circularbuf.h>
#include <bcmmsgbuf.h>
#include <osl.h>

#define CIRCULARBUF_READ_SPACE_AT_END(x)		\
			((x->w_ptr >= x->rp_ptr) ? (x->w_ptr - x->rp_ptr) : (x->e_ptr - x->rp_ptr))

#define CIRCULARBUF_READ_SPACE_AVAIL(x)		\
			(((CIRCULARBUF_READ_SPACE_AT_END(x) == 0) && (x->w_ptr < x->rp_ptr)) ? \
				x->w_ptr : CIRCULARBUF_READ_SPACE_AT_END(x))

int cbuf_msg_level = CBUF_ERROR_VAL | CBUF_TRACE_VAL | CBUF_INFORM_VAL;

/* #define CBUF_DEBUG */
#ifdef CBUF_DEBUG
#define CBUF_DEBUG_CHECK(x)	x
#else
#define CBUF_DEBUG_CHECK(x)
#endif	/* CBUF_DEBUG */

/*
 * -----------------------------------------------------------------------------
 * Function   : circularbuf_init
 * Description:
 *
 *
 * Input Args :
 *
 *
 * Return Values :
 *
 * -----------------------------------------------------------------------------
 */
void
circularbuf_init(circularbuf_t *handle, void *buf_base_addr, uint16 total_buf_len)
{
	handle->buf_addr = buf_base_addr;

	handle->depth = handle->e_ptr = HTOL32(total_buf_len);

	/* Initialize Read and Write pointers */
	handle->w_ptr = handle->r_ptr = handle->wp_ptr = handle->rp_ptr = HTOL32(0);
	handle->mb_ring_bell = NULL;
	handle->mb_ctx = NULL;

	return;
}

void
circularbuf_register_cb(circularbuf_t *handle, mb_ring_t mb_ring_func, void *ctx)
{
	handle->mb_ring_bell = mb_ring_func;
	handle->mb_ctx = ctx;
}

#ifdef CBUF_DEBUG
static void
circularbuf_check_sanity(circularbuf_t *handle)
{
	if ((handle->e_ptr > handle->depth) ||
	    (handle->r_ptr > handle->e_ptr) ||
		(handle->rp_ptr > handle->e_ptr) ||
		(handle->w_ptr > handle->e_ptr))
	{
		printf("%s:%d: Pointers are corrupted.\n", __FUNCTION__, __LINE__);
		circularbuf_debug_print(handle);
		ASSERT(0);
	}
	return;
}
#endif /* CBUF_DEBUG */

/*
 * -----------------------------------------------------------------------------
 * Function   : circularbuf_reserve_for_write
 *
 * Description:
 * This function reserves N bytes for write in the circular buffer. The circularbuf
 * implementation will only reserve space in the ciruclar buffer and return
 * the pointer to the address where the new data can be written.
 * The actual write implementation (bcopy/dma) is outside the scope of
 * circularbuf implementation.
 *
 * Input Args :
 *		size - No. of bytes to reserve for write
 *
 * Return Values :
 *		void * : Pointer to the reserved location. This is the address
 *		          that will be used for write (dma/bcopy)
 *
 * -----------------------------------------------------------------------------
 */
void * BCMFASTPATH
circularbuf_reserve_for_write(circularbuf_t *handle, uint16 size)
{
	int16 avail_space;
	void *ret_ptr = NULL;

	CBUF_DEBUG_CHECK(circularbuf_check_sanity(handle));
	ASSERT(size < handle->depth);

	if (handle->wp_ptr >= handle->r_ptr)
		avail_space = handle->depth - handle->wp_ptr;
	else
		avail_space = handle->r_ptr - handle->wp_ptr;

	ASSERT(avail_space <= handle->depth);
	if (avail_space > size)
	{
		/* Great. We have enough space. */
		ret_ptr = CIRCULARBUF_START(handle) + handle->wp_ptr;

		/*
		 * We need to update the wp_ptr for the next guy to write.
		 *
		 * Please Note : We are not updating the write pointer here. This can be
		 * done only after write is complete (In case of DMA, we can only schedule
		 * the DMA. Actual completion will be known only on DMA complete interrupt).
		 */
		handle->wp_ptr += size;
		return ret_ptr;
	}

	/*
	 * If there is no available space, we should check if there is some space left
	 * in the beginning of the circular buffer.  Wrap-around case, where there is
	 * not enough space in the end of the circular buffer. But, there might be
	 * room in the beginning of the buffer.
	 */
	if (handle->wp_ptr >= handle->r_ptr)
	{
		avail_space = handle->r_ptr;
		if (avail_space > size)
		{
			/* OK. There is room in the beginning. Let's go ahead and use that.
			 * But, before that, we have left a hole at the end of the circular
			 * buffer as that was not sufficient to accomodate the requested
			 * size. Let's make sure this is updated in the circularbuf structure
			 * so that consumer does not use the hole.
			 */
			handle->e_ptr  = handle->wp_ptr;
			handle->wp_ptr = size;

			return CIRCULARBUF_START(handle);
		}
	}

	/* We have tried enough to accomodate the new packet. There is no room for now. */
	return NULL;
}

/*
 * -----------------------------------------------------------------------------
 * Function   : circularbuf_write_complete
 *
 * Description:
 * This function has to be called by the producer end of circularbuf to indicate to
 * the circularbuf layer that data has been written and the write pointer can be
 * updated. In the process, if there was a doorbell callback registered, that
 * function would also be invoked.
 *
 * Input Args :
 *		dest_addr	  : Address where the data was written. This would be the
 *					    same address that was reserved earlier.
 *		bytes_written : Length of data written
 *
 * -----------------------------------------------------------------------------
 */
void BCMFASTPATH
circularbuf_write_complete(circularbuf_t *handle, uint16 bytes_written)
{
	CBUF_DEBUG_CHECK(circularbuf_check_sanity(handle));

	/* Update the write pointer */
	if ((handle->w_ptr + bytes_written) >= handle->depth) {
		OSL_CACHE_FLUSH((void *) CIRCULARBUF_START(handle), bytes_written);
		handle->w_ptr = bytes_written;
	} else {
		OSL_CACHE_FLUSH((void *) (CIRCULARBUF_START(handle) + handle->w_ptr),
			bytes_written);
		handle->w_ptr += bytes_written;
	}

	/* And ring the door bell (mail box interrupt) to indicate to the peer that
	 * message is available for consumption.
	 */
	if (handle->mb_ring_bell)
		handle->mb_ring_bell(handle->mb_ctx);
}

/*
 * -----------------------------------------------------------------------------
 * Function   : circularbuf_get_read_ptr
 *
 * Description:
 * This function will be called by the consumer of circularbuf for reading data from
 * the circular buffer. This will typically be invoked when the consumer gets a
 * doorbell interrupt.
 * Please note that the function only returns the pointer (and length) from
 * where the data can be read. Actual read implementation is upto the
 * consumer. It could be a bcopy or dma.
 *
 * Input Args :
 *		void *			: Address from where the data can be read.
 *		available_len	: Length of data available for read.
 *
 * -----------------------------------------------------------------------------
 */
void * BCMFASTPATH
circularbuf_get_read_ptr(circularbuf_t *handle, uint16 *available_len)
{
	uint8 *ret_addr;

	CBUF_DEBUG_CHECK(circularbuf_check_sanity(handle));

	/* First check if there is any data available in the circular buffer */
	*available_len = CIRCULARBUF_READ_SPACE_AVAIL(handle);
	if (*available_len == 0)
		return NULL;

	/*
	 * Although there might be data in the circular buffer for read, in
	 * cases of write wrap-around and read still in the end of the circular
	 * buffer, we might have to wrap around the read pending pointer also.
	 */
	if (CIRCULARBUF_READ_SPACE_AT_END(handle) == 0)
		handle->rp_ptr = 0;

	ret_addr = CIRCULARBUF_START(handle) + handle->rp_ptr;

	/*
	 * Please note that we do not update the read pointer here. Only
	 * read pending pointer is updated, so that next reader knows where
	 * to read data from.
	 * read pointer can only be updated when the read is complete.
	 */
	handle->rp_ptr = (uint16)(ret_addr - CIRCULARBUF_START(handle) + *available_len);

	ASSERT(*available_len <= handle->depth);

	OSL_CACHE_INV((void *) ret_addr, *available_len);

	return ret_addr;
}

/*
 * -----------------------------------------------------------------------------
 * Function   : circularbuf_read_complete
 * Description:
 * This function has to be called by the consumer end of circularbuf to indicate
 * that data has been consumed and the read pointer can be updated.
 *
 * Input Args :
 *		bytes_read : No. of bytes consumed by the consumer. This has to match
 *					 the length returned by circularbuf_get_read_ptr
 *
 * Return Values :
 *		CIRCULARBUF_SUCCESS		: Otherwise
 *
 * -----------------------------------------------------------------------------
 */
circularbuf_ret_t BCMFASTPATH
circularbuf_read_complete(circularbuf_t *handle, uint16 bytes_read)
{
	CBUF_DEBUG_CHECK(circularbuf_check_sanity(handle));
	ASSERT(bytes_read < handle->depth);

	/* Update the read pointer */
	if ((handle->r_ptr + bytes_read) >= handle->depth)
		handle->r_ptr = bytes_read;
	else
		handle->r_ptr += bytes_read;

	return CIRCULARBUF_SUCCESS;
}
/*
 * -----------------------------------------------------------------------------
 * Function	: circularbuf_revert_rp_ptr
 *
 * Description:
 * The rp_ptr update during circularbuf_get_read_ptr() is done to reflect the amount of data
 * that is sent out to be read by the consumer. But the consumer may not always read the
 * entire data. In such a case, the rp_ptr needs to be reverted back by 'left' bytes, where
 * 'left' is the no. of bytes left unread.
 *
 * Input args:
 * 	bytes : The no. of bytes left unread by the consumer
 *
 * -----------------------------------------------------------------------------
 */
circularbuf_ret_t
circularbuf_revert_rp_ptr(circularbuf_t *handle, uint16 bytes)
{
	CBUF_DEBUG_CHECK(circularbuf_check_sanity(handle));
	ASSERT(bytes < handle->depth);

	handle->rp_ptr -= bytes;

	return CIRCULARBUF_SUCCESS;
}