aboutsummaryrefslogtreecommitdiff
path: root/drivers/net/wireless/iwmc3200wifi/hal.c
blob: 1cabcb39643f48e1ecd9d0b731ceb753399fa9d2 (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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
/*
 * Intel Wireless Multicomm 3200 WiFi driver
 *
 * Copyright (C) 2009 Intel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * Intel Corporation <ilw@linux.intel.com>
 * Samuel Ortiz <samuel.ortiz@intel.com>
 * Zhu Yi <yi.zhu@intel.com>
 *
 */

/*
 * Hardware Abstraction Layer for iwm.
 *
 * This file mostly defines an abstraction API for
 * sending various commands to the target.
 *
 * We have 2 types of commands: wifi and non-wifi ones.
 *
 * - wifi commands:
 *   They are used for sending LMAC and UMAC commands,
 *   and thus are the most commonly used ones.
 *   There are 2 different wifi command types, the regular
 *   one and the LMAC one. The former is used to send
 *   UMAC commands (see UMAC_CMD_OPCODE_* from umac.h)
 *   while the latter is used for sending commands to the
 *   LMAC. If you look at LMAC commands you'll se that they
 *   are actually regular iwlwifi target commands encapsulated
 *   into a special UMAC command called UMAC passthrough.
 *   This is due to the fact the host talks exclusively
 *   to the UMAC and so there needs to be a special UMAC
 *   command for talking to the LMAC.
 *   This is how a wifi command is laid out:
 *    ------------------------
 *   | iwm_udma_out_wifi_hdr  |
 *    ------------------------
 *   | SW meta_data (32 bits) |
 *    ------------------------
 *   | iwm_dev_cmd_hdr        |
 *    ------------------------
 *   | payload                |
 *   | ....                   |
 *
 * - non-wifi, or general commands:
 *   Those commands are handled by the device's bootrom,
 *   and are typically sent when the UMAC and the LMAC
 *   are not yet available.
 *    *   This is how a non-wifi command is laid out:
 *    ---------------------------
 *   | iwm_udma_out_nonwifi_hdr  |
 *    ---------------------------
 *   | payload                   |
 *   | ....                      |

 *
 * All the commands start with a UDMA header, which is
 * basically a 32 bits field. The 4 LSB there define
 * an opcode that allows the target to differentiate
 * between wifi (opcode is 0xf) and non-wifi commands
 * (opcode is [0..0xe]).
 *
 * When a command (wifi or non-wifi) is supposed to receive
 * an answer, we queue the command buffer. When we do receive
 * a command response from the UMAC, we go through the list
 * of pending command, and pass both the command and the answer
 * to the rx handler. Each command is sent with a unique
 * sequence id, and the answer is sent with the same one. This
 * is how we're supposed to match an answer with its command.
 * See rx.c:iwm_rx_handle_[non]wifi() and iwm_get_pending_[non]wifi()
 * for the implementation details.
 */
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/slab.h>

#include "iwm.h"
#include "bus.h"
#include "hal.h"
#include "umac.h"
#include "debug.h"
#include "trace.h"

static int iwm_nonwifi_cmd_init(struct iwm_priv *iwm,
				struct iwm_nonwifi_cmd *cmd,
				struct iwm_udma_nonwifi_cmd *udma_cmd)
{
	INIT_LIST_HEAD(&cmd->pending);

	spin_lock(&iwm->cmd_lock);

	cmd->resp_received = 0;

	cmd->seq_num = iwm->nonwifi_seq_num;
	udma_cmd->seq_num = cpu_to_le16(cmd->seq_num);

	iwm->nonwifi_seq_num++;
	iwm->nonwifi_seq_num %= UMAC_NONWIFI_SEQ_NUM_MAX;

	if (udma_cmd->resp)
		list_add_tail(&cmd->pending, &iwm->nonwifi_pending_cmd);

	spin_unlock(&iwm->cmd_lock);

	cmd->buf.start = cmd->buf.payload;
	cmd->buf.len = 0;

	memcpy(&cmd->udma_cmd, udma_cmd, sizeof(*udma_cmd));

	return cmd->seq_num;
}

u16 iwm_alloc_wifi_cmd_seq(struct iwm_priv *iwm)
{
	u16 seq_num = iwm->wifi_seq_num;

	iwm->wifi_seq_num++;
	iwm->wifi_seq_num %= UMAC_WIFI_SEQ_NUM_MAX;

	return seq_num;
}

static void iwm_wifi_cmd_init(struct iwm_priv *iwm,
			      struct iwm_wifi_cmd *cmd,
			      struct iwm_udma_wifi_cmd *udma_cmd,
			      struct iwm_umac_cmd *umac_cmd,
			      struct iwm_lmac_cmd *lmac_cmd,
			      u16 payload_size)
{
	INIT_LIST_HEAD(&cmd->pending);

	spin_lock(&iwm->cmd_lock);

	cmd->seq_num = iwm_alloc_wifi_cmd_seq(iwm);
	umac_cmd->seq_num = cpu_to_le16(cmd->seq_num);

	if (umac_cmd->resp)
		list_add_tail(&cmd->pending, &iwm->wifi_pending_cmd);

	spin_unlock(&iwm->cmd_lock);

	cmd->buf.start = cmd->buf.payload;
	cmd->buf.len = 0;

	if (lmac_cmd) {
		cmd->buf.start -= sizeof(struct iwm_lmac_hdr);

		lmac_cmd->seq_num = cpu_to_le16(cmd->seq_num);
		lmac_cmd->count = cpu_to_le16(payload_size);

		memcpy(&cmd->lmac_cmd, lmac_cmd, sizeof(*lmac_cmd));

		umac_cmd->count = cpu_to_le16(sizeof(struct iwm_lmac_hdr));
	} else
		umac_cmd->count = 0;

	umac_cmd->count = cpu_to_le16(payload_size +
				      le16_to_cpu(umac_cmd->count));
	udma_cmd->count = cpu_to_le16(sizeof(struct iwm_umac_fw_cmd_hdr) +
				      le16_to_cpu(umac_cmd->count));

	memcpy(&cmd->udma_cmd, udma_cmd, sizeof(*udma_cmd));
	memcpy(&cmd->umac_cmd, umac_cmd, sizeof(*umac_cmd));
}

void iwm_cmd_flush(struct iwm_priv *iwm)
{
	struct iwm_wifi_cmd *wcmd, *wnext;
	struct iwm_nonwifi_cmd *nwcmd, *nwnext;

	list_for_each_entry_safe(wcmd, wnext, &iwm->wifi_pending_cmd, pending) {
		list_del(&wcmd->pending);
		kfree(wcmd);
	}

	list_for_each_entry_safe(nwcmd, nwnext, &iwm->nonwifi_pending_cmd,
				 pending) {
		list_del(&nwcmd->pending);
		kfree(nwcmd);
	}
}

struct iwm_wifi_cmd *iwm_get_pending_wifi_cmd(struct iwm_priv *iwm, u16 seq_num)
{
	struct iwm_wifi_cmd *cmd;

	list_for_each_entry(cmd, &iwm->wifi_pending_cmd, pending)
		if (cmd->seq_num == seq_num) {
			list_del(&cmd->pending);
			return cmd;
		}

	return NULL;
}

struct iwm_nonwifi_cmd *iwm_get_pending_nonwifi_cmd(struct iwm_priv *iwm,
						    u8 seq_num, u8 cmd_opcode)
{
	struct iwm_nonwifi_cmd *cmd;

	list_for_each_entry(cmd, &iwm->nonwifi_pending_cmd, pending)
		if ((cmd->seq_num == seq_num) &&
		    (cmd->udma_cmd.opcode == cmd_opcode) &&
		    (cmd->resp_received)) {
			list_del(&cmd->pending);
			return cmd;
		}

	return NULL;
}

static void iwm_build_udma_nonwifi_hdr(struct iwm_priv *iwm,
				       struct iwm_udma_out_nonwifi_hdr *hdr,
				       struct iwm_udma_nonwifi_cmd *cmd)
{
	memset(hdr, 0, sizeof(*hdr));

	SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_OPCODE, cmd->opcode);
	SET_VAL32(hdr->cmd, UDMA_HDI_OUT_NW_CMD_RESP, cmd->resp);
	SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_EOT, 1);
	SET_VAL32(hdr->cmd, UDMA_HDI_OUT_NW_CMD_HANDLE_BY_HW,
		  cmd->handle_by_hw);
	SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_SIGNATURE, UMAC_HDI_OUT_SIGNATURE);
	SET_VAL32(hdr->cmd, UDMA_HDI_OUT_CMD_NON_WIFI_HW_SEQ_NUM,
		  le16_to_cpu(cmd->seq_num));

	hdr->addr = cmd->addr;
	hdr->op1_sz = cmd->op1_sz;
	hdr->op2 = cmd->op2;
}

static int iwm_send_udma_nonwifi_cmd(struct iwm_priv *iwm,
				     struct iwm_nonwifi_cmd *cmd)
{
	struct iwm_udma_out_nonwifi_hdr *udma_hdr;
	struct iwm_nonwifi_cmd_buff *buf;
	struct iwm_udma_nonwifi_cmd *udma_cmd = &cmd->udma_cmd;

	buf = &cmd->buf;

	buf->start -= sizeof(struct iwm_umac_nonwifi_out_hdr);
	buf->len += sizeof(struct iwm_umac_nonwifi_out_hdr);

	udma_hdr = (struct iwm_udma_out_nonwifi_hdr *)(buf->start);

	iwm_build_udma_nonwifi_hdr(iwm, udma_hdr, udma_cmd);

	IWM_DBG_CMD(iwm, DBG,
		    "Send UDMA nonwifi cmd: opcode = 0x%x, resp = 0x%x, "
		    "hw = 0x%x, seqnum = %d, addr = 0x%x, op1_sz = 0x%x, "
		    "op2 = 0x%x\n", udma_cmd->opcode, udma_cmd->resp,
		    udma_cmd->handle_by_hw, cmd->seq_num, udma_cmd->addr,
		    udma_cmd->op1_sz, udma_cmd->op2);

	trace_iwm_tx_nonwifi_cmd(iwm, udma_hdr);
	return iwm_bus_send_chunk(iwm, buf->start, buf->len);
}

void iwm_udma_wifi_hdr_set_eop(struct iwm_priv *iwm, u8 *buf, u8 eop)
{
	struct iwm_udma_out_wifi_hdr *hdr = (struct iwm_udma_out_wifi_hdr *)buf;

	SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_EOT, eop);
}

void iwm_build_udma_wifi_hdr(struct iwm_priv *iwm,
			     struct iwm_udma_out_wifi_hdr *hdr,
			     struct iwm_udma_wifi_cmd *cmd)
{
	memset(hdr, 0, sizeof(*hdr));

	SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_OPCODE, UMAC_HDI_OUT_OPCODE_WIFI);
	SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_EOT, cmd->eop);
	SET_VAL32(hdr->cmd, UMAC_HDI_OUT_CMD_SIGNATURE, UMAC_HDI_OUT_SIGNATURE);

	SET_VAL32(hdr->meta_data, UMAC_HDI_OUT_BYTE_COUNT,
		  le16_to_cpu(cmd->count));
	SET_VAL32(hdr->meta_data, UMAC_HDI_OUT_CREDIT_GRP, cmd->credit_group);
	SET_VAL32(hdr->meta_data, UMAC_HDI_OUT_RATID, cmd->ra_tid);
	SET_VAL32(hdr->meta_data, UMAC_HDI_OUT_LMAC_OFFSET, cmd->lmac_offset);
}

void iwm_build_umac_hdr(struct iwm_priv *iwm,
			struct iwm_umac_fw_cmd_hdr *hdr,
			struct iwm_umac_cmd *cmd)
{
	memset(hdr, 0, sizeof(*hdr));

	SET_VAL32(hdr->meta_data, UMAC_FW_CMD_BYTE_COUNT,
		  le16_to_cpu(cmd->count));
	SET_VAL32(hdr->meta_data, UMAC_FW_CMD_TX_STA_COLOR, cmd->color);
	SET_VAL8(hdr->cmd.flags, UMAC_DEV_CMD_FLAGS_RESP_REQ, cmd->resp);

	hdr->cmd.cmd = cmd->id;
	hdr->cmd.seq_num = cmd->seq_num;
}

static int iwm_send_udma_wifi_cmd(struct iwm_priv *iwm,
				  struct iwm_wifi_cmd *cmd)
{
	struct iwm_umac_wifi_out_hdr *umac_hdr;
	struct iwm_wifi_cmd_buff *buf;
	struct iwm_udma_wifi_cmd *udma_cmd = &cmd->udma_cmd;
	struct iwm_umac_cmd *umac_cmd = &cmd->umac_cmd;
	int ret;

	buf = &cmd->buf;

	buf->start -= sizeof(struct iwm_umac_wifi_out_hdr);
	buf->len += sizeof(struct iwm_umac_wifi_out_hdr);

	umac_hdr = (struct iwm_umac_wifi_out_hdr *)(buf->start);

	iwm_build_udma_wifi_hdr(iwm, &umac_hdr->hw_hdr, udma_cmd);
	iwm_build_umac_hdr(iwm, &umac_hdr->sw_hdr, umac_cmd);

	IWM_DBG_CMD(iwm, DBG,
		    "Send UDMA wifi cmd: opcode = 0x%x, UMAC opcode = 0x%x, "
		    "eop = 0x%x, count = 0x%x, credit_group = 0x%x, "
		    "ra_tid = 0x%x, lmac_offset = 0x%x, seqnum = %d\n",
		    UMAC_HDI_OUT_OPCODE_WIFI, umac_cmd->id,
		    udma_cmd->eop, udma_cmd->count, udma_cmd->credit_group,
		    udma_cmd->ra_tid, udma_cmd->lmac_offset, cmd->seq_num);

	if (umac_cmd->id == UMAC_CMD_OPCODE_WIFI_PASS_THROUGH)
		IWM_DBG_CMD(iwm, DBG, "\tLMAC opcode: 0x%x\n",
			    cmd->lmac_cmd.id);

	ret = iwm_tx_credit_alloc(iwm, udma_cmd->credit_group, buf->len);

	/* We keep sending UMAC reset regardless of the command credits.
	 * The UMAC is supposed to be reset anyway and the Tx credits are
	 * reinitialized afterwards. If we are lucky, the reset could
	 * still be done even though we have run out of credits for the
	 * command pool at this moment.*/
	if (ret && (umac_cmd->id != UMAC_CMD_OPCODE_RESET)) {
		IWM_DBG_TX(iwm, DBG, "Failed to alloc tx credit for cmd %d\n",
			   umac_cmd->id);
		return ret;
	}

	trace_iwm_tx_wifi_cmd(iwm, umac_hdr);
	return iwm_bus_send_chunk(iwm, buf->start, buf->len);
}

/* target_cmd a.k.a udma_nonwifi_cmd can be sent when UMAC is not available */
int iwm_hal_send_target_cmd(struct iwm_priv *iwm,
			    struct iwm_udma_nonwifi_cmd *udma_cmd,
			    const void *payload)
{
	struct iwm_nonwifi_cmd *cmd;
	int ret, seq_num;

	cmd = kzalloc(sizeof(struct iwm_nonwifi_cmd), GFP_KERNEL);
	if (!cmd) {
		IWM_ERR(iwm, "Couldn't alloc memory for hal cmd\n");
		return -ENOMEM;
	}

	seq_num = iwm_nonwifi_cmd_init(iwm, cmd, udma_cmd);

	if (cmd->udma_cmd.opcode == UMAC_HDI_OUT_OPCODE_WRITE ||
	    cmd->udma_cmd.opcode == UMAC_HDI_OUT_OPCODE_WRITE_PERSISTENT) {
		cmd->buf.len = le32_to_cpu(cmd->udma_cmd.op1_sz);
		memcpy(&cmd->buf.payload, payload, cmd->buf.len);
	}

	ret = iwm_send_udma_nonwifi_cmd(iwm, cmd);

	if (!udma_cmd->resp)
		kfree(cmd);

	if (ret < 0)
		return ret;

	return seq_num;
}

static void iwm_build_lmac_hdr(struct iwm_priv *iwm, struct iwm_lmac_hdr *hdr,
			       struct iwm_lmac_cmd *cmd)
{
	memset(hdr, 0, sizeof(*hdr));

	hdr->id = cmd->id;
	hdr->flags = 0; /* Is this ever used? */
	hdr->seq_num = cmd->seq_num;
}

/*
 * iwm_hal_send_host_cmd(): sends commands to the UMAC or the LMAC.
 * Sending command to the LMAC is equivalent to sending a
 * regular UMAC command with the LMAC passthrough or the LMAC
 * wrapper UMAC command IDs.
 */
int iwm_hal_send_host_cmd(struct iwm_priv *iwm,
			  struct iwm_udma_wifi_cmd *udma_cmd,
			  struct iwm_umac_cmd *umac_cmd,
			  struct iwm_lmac_cmd *lmac_cmd,
			  const void *payload, u16 payload_size)
{
	struct iwm_wifi_cmd *cmd;
	struct iwm_lmac_hdr *hdr;
	int lmac_hdr_len = 0;
	int ret;

	cmd = kzalloc(sizeof(struct iwm_wifi_cmd), GFP_KERNEL);
	if (!cmd) {
		IWM_ERR(iwm, "Couldn't alloc memory for wifi hal cmd\n");
		return -ENOMEM;
	}

	iwm_wifi_cmd_init(iwm, cmd, udma_cmd, umac_cmd, lmac_cmd, payload_size);

	if (lmac_cmd) {
		hdr = (struct iwm_lmac_hdr *)(cmd->buf.start);

		iwm_build_lmac_hdr(iwm, hdr, &cmd->lmac_cmd);
		lmac_hdr_len = sizeof(struct iwm_lmac_hdr);
	}

	memcpy(cmd->buf.payload, payload, payload_size);
	cmd->buf.len = le16_to_cpu(umac_cmd->count);

	ret = iwm_send_udma_wifi_cmd(iwm, cmd);

	/* We free the cmd if we're not expecting any response */
	if (!umac_cmd->resp)
		kfree(cmd);
	return ret;
}

/*
 * iwm_hal_send_umac_cmd(): This is a special case for
 * iwm_hal_send_host_cmd() to send direct UMAC cmd (without
 * LMAC involved).
 */
int iwm_hal_send_umac_cmd(struct iwm_priv *iwm,
			  struct iwm_udma_wifi_cmd *udma_cmd,
			  struct iwm_umac_cmd *umac_cmd,
			  const void *payload, u16 payload_size)
{
	return iwm_hal_send_host_cmd(iwm, udma_cmd, umac_cmd, NULL,
				     payload, payload_size);
}