summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/bcmdhd/circularbuf.c
blob: bfb308c34f05d7826ea701b41c1d4c71f5a82872 (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
/** @file circularbuf.c
 *
 * PCIe host driver and dongle firmware need to communicate with each other. The mechanism consists
 * of multiple circular buffers located in (DMA'able) host memory. A circular buffer is either used
 * for host -> dongle (h2d) or dongle -> host communication. Both host driver and firmware make use
 * of this source file. This source file contains functions to manage such a set of circular
 * buffers, but does not contain the code to read or write the data itself into the buffers. It
 * leaves that up to the software layer that uses this file, which can be implemented either using
 * pio or DMA transfers. It also leaves the format of the data that is written and read to a higher
 * layer. Typically the data is in the form of so-called 'message buffers'.
 *
 * $Copyright Open Broadcom Corporation$
 *
 * $Id: circularbuf.c 467150 2014-04-02 17:30:43Z $
 */

#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 : buf_base_addr: address of DMA'able host memory provided by caller
 *
 *
 * 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;
}

/**
 * When an item is added to the circular buffer by the producing party, the consuming party has to
 * be notified by means of a 'door bell' or 'ring'. This function allows the caller to register a
 * 'ring' function that will be called when a 'write complete' occurs.
 */
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 circular 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 as to notify the consuming party.
 *
 * 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 up to 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, so the producing side
 * can can use the freed space for new entries.
 *
 *
 * 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->w_ptr < handle->e_ptr) && (handle->r_ptr + bytes_read) > handle->e_ptr)
		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;
}