1// SPDX-License-Identifier: GPL-2.0
2/******************************************************************************
3 * rtl8712_cmd.c
4 *
5 * Copyright(c) 2007 - 2010 Realtek Corporation. All rights reserved.
6 * Linux device driver for RTL8192SU
7 *
8 * Modifications for inclusion into the Linux staging tree are
9 * Copyright(c) 2010 Larry Finger. All rights reserved.
10 *
11 * Contact information:
12 * WLAN FAE <wlanfae@realtek.com>.
13 * Larry Finger <Larry.Finger@lwfinger.net>
14 *
15 ******************************************************************************/
16
17#define _RTL8712_CMD_C_
18
19#include <linux/compiler.h>
20#include <linux/kernel.h>
21#include <linux/errno.h>
22#include <linux/slab.h>
23#include <linux/sched/signal.h>
24#include <linux/module.h>
25#include <linux/kref.h>
26#include <linux/netdevice.h>
27#include <linux/skbuff.h>
28#include <linux/usb.h>
29#include <linux/usb/ch9.h>
30#include <linux/circ_buf.h>
31#include <linux/uaccess.h>
32#include <asm/byteorder.h>
33#include <linux/atomic.h>
34#include <linux/semaphore.h>
35#include <linux/rtnetlink.h>
36
37#include "osdep_service.h"
38#include "drv_types.h"
39#include "recv_osdep.h"
40#include "mlme_osdep.h"
41#include "rtl871x_ioctl_set.h"
42
43static void check_hw_pbc(struct _adapter *padapter)
44{
45	u8	tmp1byte;
46
47	r8712_write8(padapter, MAC_PINMUX_CTRL, (GPIOMUX_EN | GPIOSEL_GPIO));
48	tmp1byte = r8712_read8(padapter, GPIO_IO_SEL);
49	tmp1byte &= ~(HAL_8192S_HW_GPIO_WPS_BIT);
50	r8712_write8(padapter, GPIO_IO_SEL, tmp1byte);
51	tmp1byte = r8712_read8(padapter, GPIO_CTRL);
52	if (tmp1byte == 0xff)
53		return;
54	if (tmp1byte & HAL_8192S_HW_GPIO_WPS_BIT) {
55		/* Here we only set bPbcPressed to true
56		 * After trigger PBC, the variable will be set to false
57		 */
58		netdev_dbg(padapter->pnetdev, "CheckPbcGPIO - PBC is pressed !!!!\n");
59		/* 0 is the default value and it means the application monitors
60		 * the HW PBC doesn't provide its pid to driver.
61		 */
62		if (padapter->pid == 0)
63			return;
64		kill_pid(find_vpid(padapter->pid), SIGUSR1, 1);
65	}
66}
67
68/* query rx phy status from fw.
69 * Adhoc mode: beacon.
70 * Infrastructure mode: beacon , data.
71 */
72static void query_fw_rx_phy_status(struct _adapter *padapter)
73{
74	u32 val32 = 0;
75	int pollingcnts = 50;
76
77	if (check_fwstate(&padapter->mlmepriv, _FW_LINKED)) {
78		r8712_write32(padapter, IOCMD_CTRL_REG, 0xf4000001);
79		msleep(100);
80		/* Wait FW complete IO Cmd */
81		while ((r8712_read32(padapter, IOCMD_CTRL_REG)) &&
82		       (pollingcnts > 0)) {
83			pollingcnts--;
84			msleep(20);
85		}
86		if (pollingcnts != 0)
87			val32 = r8712_read32(padapter, IOCMD_DATA_REG);
88		else /* time out */
89			val32 = 0;
90		val32 >>= 4;
91		padapter->recvpriv.fw_rssi =
92			 (u8)r8712_signal_scale_mapping(val32);
93	}
94}
95
96/* check mlme, hw, phy, or dynamic algorithm status. */
97static void StatusWatchdogCallback(struct _adapter *padapter)
98{
99	check_hw_pbc(padapter);
100	query_fw_rx_phy_status(padapter);
101}
102
103static void r871x_internal_cmd_hdl(struct _adapter *padapter, u8 *pbuf)
104{
105	struct drvint_cmd_parm *pdrvcmd;
106
107	if (!pbuf)
108		return;
109	pdrvcmd = (struct drvint_cmd_parm *)pbuf;
110	switch (pdrvcmd->i_cid) {
111	case WDG_WK_CID:
112		StatusWatchdogCallback(padapter);
113		break;
114	default:
115		break;
116	}
117	kfree(pdrvcmd->pbuf);
118}
119
120static u8 read_bbreg_hdl(struct _adapter *padapter, u8 *pbuf)
121{
122	struct cmd_obj *pcmd  = (struct cmd_obj *)pbuf;
123
124	r8712_free_cmd_obj(pcmd);
125	return H2C_SUCCESS;
126}
127
128static u8 write_bbreg_hdl(struct _adapter *padapter, u8 *pbuf)
129{
130	void (*pcmd_callback)(struct _adapter *dev, struct cmd_obj *pcmd);
131	struct cmd_obj *pcmd  = (struct cmd_obj *)pbuf;
132
133	pcmd_callback = cmd_callback[pcmd->cmdcode].callback;
134	if (!pcmd_callback)
135		r8712_free_cmd_obj(pcmd);
136	else
137		pcmd_callback(padapter, pcmd);
138	return H2C_SUCCESS;
139}
140
141static u8 read_rfreg_hdl(struct _adapter *padapter, u8 *pbuf)
142{
143	u32 val;
144	void (*pcmd_callback)(struct _adapter *dev, struct cmd_obj *pcmd);
145	struct cmd_obj *pcmd  = (struct cmd_obj *)pbuf;
146
147	if (pcmd->rsp && pcmd->rspsz > 0)
148		memcpy(pcmd->rsp, (u8 *)&val, pcmd->rspsz);
149	pcmd_callback = cmd_callback[pcmd->cmdcode].callback;
150	if (!pcmd_callback)
151		r8712_free_cmd_obj(pcmd);
152	else
153		pcmd_callback(padapter, pcmd);
154	return H2C_SUCCESS;
155}
156
157static u8 write_rfreg_hdl(struct _adapter *padapter, u8 *pbuf)
158{
159	void (*pcmd_callback)(struct _adapter *dev, struct cmd_obj *pcmd);
160	struct cmd_obj *pcmd  = (struct cmd_obj *)pbuf;
161
162	pcmd_callback = cmd_callback[pcmd->cmdcode].callback;
163	if (!pcmd_callback)
164		r8712_free_cmd_obj(pcmd);
165	else
166		pcmd_callback(padapter, pcmd);
167	return H2C_SUCCESS;
168}
169
170static u8 sys_suspend_hdl(struct _adapter *padapter, u8 *pbuf)
171{
172	struct cmd_obj *pcmd  = (struct cmd_obj *)pbuf;
173
174	r8712_free_cmd_obj(pcmd);
175	return H2C_SUCCESS;
176}
177
178static struct cmd_obj *cmd_hdl_filter(struct _adapter *padapter,
179				      struct cmd_obj *pcmd)
180{
181	struct cmd_obj *pcmd_r;
182
183	if (!pcmd)
184		return pcmd;
185	pcmd_r = NULL;
186
187	switch (pcmd->cmdcode) {
188	case GEN_CMD_CODE(_Read_BBREG):
189		read_bbreg_hdl(padapter, (u8 *)pcmd);
190		break;
191	case GEN_CMD_CODE(_Write_BBREG):
192		write_bbreg_hdl(padapter, (u8 *)pcmd);
193		break;
194	case GEN_CMD_CODE(_Read_RFREG):
195		read_rfreg_hdl(padapter, (u8 *)pcmd);
196		break;
197	case GEN_CMD_CODE(_Write_RFREG):
198		write_rfreg_hdl(padapter, (u8 *)pcmd);
199		break;
200	case GEN_CMD_CODE(_SetUsbSuspend):
201		sys_suspend_hdl(padapter, (u8 *)pcmd);
202		break;
203	case GEN_CMD_CODE(_JoinBss):
204		r8712_joinbss_reset(padapter);
205		/* Before set JoinBss_CMD to FW, driver must ensure FW is in
206		 * PS_MODE_ACTIVE. Directly write rpwm to radio on and assign
207		 * new pwr_mode to Driver, instead of use workitem to change
208		 * state.
209		 */
210		if (padapter->pwrctrlpriv.pwr_mode > PS_MODE_ACTIVE) {
211			padapter->pwrctrlpriv.pwr_mode = PS_MODE_ACTIVE;
212			mutex_lock(&padapter->pwrctrlpriv.mutex_lock);
213			r8712_set_rpwm(padapter, PS_STATE_S4);
214			mutex_unlock(&padapter->pwrctrlpriv.mutex_lock);
215		}
216		pcmd_r = pcmd;
217		break;
218	case _DRV_INT_CMD_:
219		r871x_internal_cmd_hdl(padapter, pcmd->parmbuf);
220		r8712_free_cmd_obj(pcmd);
221		pcmd_r = NULL;
222		break;
223	default:
224		pcmd_r = pcmd;
225		break;
226	}
227	return pcmd_r; /* if returning pcmd_r == NULL, pcmd must be free. */
228}
229
230u8 r8712_fw_cmd(struct _adapter *pAdapter, u32 cmd)
231{
232	int pollingcnts = 50;
233
234	r8712_write32(pAdapter, IOCMD_CTRL_REG, cmd);
235	msleep(100);
236	while ((r8712_read32(pAdapter, IOCMD_CTRL_REG != 0)) &&
237	       (pollingcnts > 0)) {
238		pollingcnts--;
239		msleep(20);
240	}
241	if (pollingcnts == 0)
242		return false;
243	return true;
244}
245
246void r8712_fw_cmd_data(struct _adapter *pAdapter, u32 *value, u8 flag)
247{
248	if (flag == 0)	/* set */
249		r8712_write32(pAdapter, IOCMD_DATA_REG, *value);
250	else		/* query */
251		*value = r8712_read32(pAdapter, IOCMD_DATA_REG);
252}
253
254int r8712_cmd_thread(void *context)
255{
256	struct cmd_obj *pcmd;
257	unsigned int cmdsz, wr_sz;
258	__le32 *pcmdbuf;
259	struct tx_desc *pdesc;
260	void (*pcmd_callback)(struct _adapter *dev, struct cmd_obj *pcmd);
261	struct _adapter *padapter = context;
262	struct	cmd_priv *pcmdpriv = &padapter->cmdpriv;
263	struct completion *cmd_queue_comp =
264		&pcmdpriv->cmd_queue_comp;
265	struct mutex *pwctrl_lock = &padapter->pwrctrlpriv.mutex_lock;
266
267	allow_signal(SIGTERM);
268	while (1) {
269		if (wait_for_completion_interruptible(cmd_queue_comp))
270			break;
271		if (padapter->driver_stopped || padapter->surprise_removed)
272			break;
273		if (r8712_register_cmd_alive(padapter))
274			continue;
275_next:
276		pcmd = r8712_dequeue_cmd(&pcmdpriv->cmd_queue);
277		if (!(pcmd)) {
278			r8712_unregister_cmd_alive(padapter);
279			continue;
280		}
281		pcmdbuf = (__le32 *)pcmdpriv->cmd_buf;
282		pdesc = (struct tx_desc *)pcmdbuf;
283		memset(pdesc, 0, TXDESC_SIZE);
284		pcmd = cmd_hdl_filter(padapter, pcmd);
285		if (pcmd) { /* if pcmd != NULL, cmd will be handled by f/w */
286			struct dvobj_priv *pdvobj = &padapter->dvobjpriv;
287			u8 blnPending = 0;
288			u16 cmdcode = pcmd->cmdcode;
289
290			pcmdpriv->cmd_issued_cnt++;
291			cmdsz = round_up(pcmd->cmdsz, 8);
292			wr_sz = TXDESC_SIZE + 8 + cmdsz;
293			pdesc->txdw0 |= cpu_to_le32((wr_sz - TXDESC_SIZE) &
294						     0x0000ffff);
295			if (pdvobj->ishighspeed) {
296				if ((wr_sz % 512) == 0)
297					blnPending = 1;
298			} else {
299				if ((wr_sz % 64) == 0)
300					blnPending = 1;
301			}
302			if (blnPending) { /* 32 bytes for TX Desc - 8 offset */
303				pdesc->txdw0 |= cpu_to_le32(((TXDESC_SIZE +
304						OFFSET_SZ + 8) << OFFSET_SHT) &
305						0x00ff0000);
306			} else {
307				pdesc->txdw0 |= cpu_to_le32(((TXDESC_SIZE +
308							      OFFSET_SZ) <<
309							      OFFSET_SHT) &
310							      0x00ff0000);
311			}
312			pdesc->txdw0 |= cpu_to_le32(OWN | FSG | LSG);
313			pdesc->txdw1 |= cpu_to_le32((0x13 << QSEL_SHT) &
314						    0x00001f00);
315			pcmdbuf += (TXDESC_SIZE >> 2);
316			*pcmdbuf = cpu_to_le32((cmdsz & 0x0000ffff) |
317					       (pcmd->cmdcode << 16) |
318					       (pcmdpriv->cmd_seq << 24));
319			pcmdbuf += 2; /* 8 bytes alignment */
320			memcpy((u8 *)pcmdbuf, pcmd->parmbuf, pcmd->cmdsz);
321			if (blnPending)
322				wr_sz += 8;   /* Append 8 bytes */
323			r8712_write_mem(padapter, RTL8712_DMA_H2CCMD, wr_sz,
324					(u8 *)pdesc);
325			pcmdpriv->cmd_seq++;
326			if (cmdcode == GEN_CMD_CODE(_CreateBss)) {
327				pcmd->res = H2C_SUCCESS;
328				pcmd_callback = cmd_callback[cmdcode].callback;
329				if (pcmd_callback)
330					pcmd_callback(padapter, pcmd);
331				continue;
332			}
333			if (cmdcode == GEN_CMD_CODE(_SetPwrMode)) {
334				if (padapter->pwrctrlpriv.bSleep) {
335					mutex_lock(pwctrl_lock);
336					r8712_set_rpwm(padapter, PS_STATE_S2);
337					mutex_unlock(pwctrl_lock);
338				}
339			}
340			r8712_free_cmd_obj(pcmd);
341			if (list_empty(&pcmdpriv->cmd_queue.queue)) {
342				r8712_unregister_cmd_alive(padapter);
343				continue;
344			} else {
345				goto _next;
346			}
347		} else {
348			goto _next;
349		}
350		flush_signals_thread();
351	}
352	/* free all cmd_obj resources */
353	do {
354		pcmd = r8712_dequeue_cmd(&pcmdpriv->cmd_queue);
355		if (!pcmd)
356			break;
357		r8712_free_cmd_obj(pcmd);
358	} while (1);
359	complete(&pcmdpriv->terminate_cmdthread_comp);
360	return 0;
361}
362
363void r8712_event_handle(struct _adapter *padapter, __le32 *peventbuf)
364{
365	u8 evt_code, evt_seq;
366	u16 evt_sz;
367	void (*event_callback)(struct _adapter *dev, u8 *pbuf);
368	struct	evt_priv *pevt_priv = &padapter->evtpriv;
369
370	if (!peventbuf)
371		goto _abort_event_;
372	evt_sz = (u16)(le32_to_cpu(*peventbuf) & 0xffff);
373	evt_seq = (u8)((le32_to_cpu(*peventbuf) >> 24) & 0x7f);
374	evt_code = (u8)((le32_to_cpu(*peventbuf) >> 16) & 0xff);
375	/* checking event sequence... */
376	if ((evt_seq & 0x7f) != pevt_priv->event_seq) {
377		pevt_priv->event_seq = ((evt_seq + 1) & 0x7f);
378		goto _abort_event_;
379	}
380	/* checking if event code is valid */
381	if (evt_code >= MAX_C2HEVT) {
382		pevt_priv->event_seq = ((evt_seq + 1) & 0x7f);
383		goto _abort_event_;
384	} else if ((evt_code == GEN_EVT_CODE(_Survey)) &&
385		   (evt_sz > sizeof(struct wlan_bssid_ex))) {
386		pevt_priv->event_seq = ((evt_seq + 1) & 0x7f);
387		goto _abort_event_;
388	}
389	/* checking if event size match the event parm size */
390	if ((wlanevents[evt_code].parmsize) &&
391	    (wlanevents[evt_code].parmsize != evt_sz)) {
392		pevt_priv->event_seq = ((evt_seq + 1) & 0x7f);
393		goto _abort_event_;
394	} else if ((evt_sz == 0) && (evt_code != GEN_EVT_CODE(_WPS_PBC))) {
395		pevt_priv->event_seq = ((evt_seq + 1) & 0x7f);
396		goto _abort_event_;
397	}
398	pevt_priv->event_seq++;	/* update evt_seq */
399	if (pevt_priv->event_seq > 127)
400		pevt_priv->event_seq = 0;
401	/* move to event content, 8 bytes alignment */
402	peventbuf = peventbuf + 2;
403	event_callback = wlanevents[evt_code].event_callback;
404	if (event_callback)
405		event_callback(padapter, (u8 *)peventbuf);
406	pevt_priv->evt_done_cnt++;
407_abort_event_:
408	return;
409}
410