1 /*
2 * Linux cfg80211 driver - Dongle Host Driver (DHD) related
3 *
4 * Copyright (C) 1999-2019, Broadcom.
5 *
6 * Unless you and Broadcom execute a separate written software license
7 * agreement governing use of this software, this software is licensed to you
8 * under the terms of the GNU General Public License version 2 (the "GPL"),
9 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
10 * following added to such license:
11 *
12 * As a special exception, the copyright holders of this software give you
13 * permission to link this software with independent modules, and to copy and
14 * distribute the resulting executable under terms of your choice, provided that
15 * you also meet, for each linked independent module, the terms and conditions
16 * of the license of that module. An independent module is a module which is
17 * not derived from this software. The special exception does not apply to any
18 * modifications of the software.
19 *
20 * Notwithstanding the above, under no circumstances may you combine this
21 * software in any way with any other Broadcom software provided under a license
22 * other than the GPL, without Broadcom's express prior written consent.
23 *
24 *
25 * <<Broadcom-WL-IPTag/Open:>>
26 *
27 * $Id: wl_cfg_btcoex.c 814554 2019-04-11 23:06:22Z $
28 */
29
30 #include <net/rtnetlink.h>
31
32 #include <bcmutils.h>
33 #include <wldev_common.h>
34 #include <wl_cfg80211.h>
35 #include <dhd_cfg80211.h>
36 #include <dngl_stats.h>
37 #include <dhd.h>
38 #include <dhdioctl.h>
39 #include <wlioctl.h>
40
41 #ifdef PKT_FILTER_SUPPORT
42 extern uint dhd_pkt_filter_enable;
43 extern uint dhd_master_mode;
44 extern void dhd_pktfilter_offload_enable(dhd_pub_t *dhd, char *arg, int enable,
45 int master_mode);
46 #endif // endif
47
48 struct btcoex_info {
49 timer_list_compat_t timer;
50 u32 timer_ms;
51 u32 timer_on;
52 u32 ts_dhcp_start; /* ms ts ecord time stats */
53 u32 ts_dhcp_ok; /* ms ts ecord time stats */
54 bool dhcp_done; /* flag, indicates that host done with
55 * dhcp before t1/t2 expiration
56 */
57 s32 bt_state;
58 struct work_struct work;
59 struct net_device *dev;
60 };
61
62 static struct btcoex_info *btcoex_info_loc = NULL;
63
64 /* clean up the BT-Coex code, it still have some legacy ioctl/iovar
65 * functions */
66
67 /* use New SCO/eSCO smart YG suppression */
68 #define BT_DHCP_eSCO_FIX
69 /* this flag boost wifi pkt priority to max, caution: -not fair to sco */
70 #define BT_DHCP_USE_FLAGS
71 /* T1 start SCO/ESCo priority suppression */
72 #define BT_DHCP_OPPR_WIN_TIME 2500
73 /* T2 turn off SCO/SCO supperesion is (timeout) */
74 #define BT_DHCP_FLAG_FORCE_TIME 5500
75
76 #define BTCOEXMODE "BTCOEXMODE"
77 #define POWERMODE "POWERMODE"
78
79 enum wl_cfg80211_btcoex_status {
80 BT_DHCP_IDLE,
81 BT_DHCP_START,
82 BT_DHCP_OPPR_WIN,
83 BT_DHCP_FLAG_FORCE_TIMEOUT
84 };
85
86 /*
87 * get named driver variable to uint register value and return error indication
88 * calling example: dev_wlc_intvar_get_reg(dev, "btc_params",66, ®_value)
89 */
dev_wlc_intvar_get_reg(struct net_device * dev,char * name,uint reg,int * retval)90 static int dev_wlc_intvar_get_reg(struct net_device *dev, char *name, uint reg,
91 int *retval)
92 {
93 union {
94 char buf[WLC_IOCTL_SMLEN];
95 int val;
96 } var;
97 int error;
98
99 bzero(&var, sizeof(var));
100 error = bcm_mkiovar(name, (char *)(®), sizeof(reg), (char *)(&var),
101 sizeof(var.buf));
102 if (error == 0) {
103 return BCME_BUFTOOSHORT;
104 }
105 error = wldev_ioctl_get(dev, WLC_GET_VAR, (char *)(&var), sizeof(var.buf));
106
107 *retval = dtoh32(var.val);
108 return (error);
109 }
110
dev_wlc_bufvar_set(struct net_device * dev,char * name,char * buf,int len)111 static int dev_wlc_bufvar_set(struct net_device *dev, char *name, char *buf,
112 int len)
113 {
114 char ioctlbuf_local[WLC_IOCTL_SMLEN];
115 int ret;
116
117 ret = bcm_mkiovar(name, buf, len, ioctlbuf_local, sizeof(ioctlbuf_local));
118 if (ret == 0) {
119 return BCME_BUFTOOSHORT;
120 }
121 return (wldev_ioctl_set(dev, WLC_SET_VAR, ioctlbuf_local, ret));
122 }
123
124 /*
125 get named driver variable to uint register value and return error indication
126 calling example: dev_wlc_intvar_set_reg(dev, "btc_params",66, value)
127 */
dev_wlc_intvar_set_reg(struct net_device * dev,char * name,char * addr,char * val)128 static int dev_wlc_intvar_set_reg(struct net_device *dev, char *name,
129 char *addr, char *val)
130 {
131 char reg_addr[0x8];
132
133 bzero(reg_addr, sizeof(reg_addr));
134 memcpy((char *)®_addr[0], (char *)addr, 0x4);
135 memcpy((char *)®_addr[0x4], (char *)val, 0x4);
136
137 return (
138 dev_wlc_bufvar_set(dev, name, (char *)®_addr[0], sizeof(reg_addr)));
139 }
140
btcoex_is_sco_active(struct net_device * dev)141 static bool btcoex_is_sco_active(struct net_device *dev)
142 {
143 int ioc_res = 0;
144 bool res = FALSE;
145 int sco_id_cnt = 0;
146 int param27;
147 int i;
148
149 for (i = 0; i < 0xC; i++) {
150 ioc_res = dev_wlc_intvar_get_reg(dev, "btc_params", 0x1B, ¶m27);
151
152 WL_TRACE(("sample[%d], btc params: 27:%x\n", i, param27));
153
154 if (ioc_res < 0) {
155 WL_ERR(("ioc read btc params error\n"));
156 break;
157 }
158
159 if ((param27 & 0x6) == 0x2) { /* count both sco & esco */
160 sco_id_cnt++;
161 }
162
163 if (sco_id_cnt > 0x2) {
164 WL_TRACE(("sco/esco detected, pkt id_cnt:%d samples:%d\n",
165 sco_id_cnt, i));
166 res = TRUE;
167 break;
168 }
169
170 OSL_SLEEP(0x5);
171 }
172
173 return res;
174 }
175
176 #if defined(BT_DHCP_eSCO_FIX)
177 /* Enhanced BT COEX settings for eSCO compatibility during DHCP window */
set_btc_esco_params(struct net_device * dev,bool trump_sco)178 static int set_btc_esco_params(struct net_device *dev, bool trump_sco)
179 {
180 static bool saved_status = FALSE;
181
182 char buf_reg50va_dhcp_on[0x8] = {50, 00, 00, 00, 0x22, 0x80, 0x00, 0x00};
183 char buf_reg51va_dhcp_on[0x8] = {51, 00, 00, 00, 0x00, 0x00, 0x00, 0x00};
184 char buf_reg64va_dhcp_on[0x8] = {64, 00, 00, 00, 0x00, 0x00, 0x00, 0x00};
185 char buf_reg65va_dhcp_on[0x8] = {65, 00, 00, 00, 0x00, 0x00, 0x00, 0x00};
186 char buf_reg71va_dhcp_on[0x8] = {71, 00, 00, 00, 0x00, 0x00, 0x00, 0x00};
187 uint32 regaddr;
188 static uint32 saved_reg50;
189 static uint32 saved_reg51;
190 static uint32 saved_reg64;
191 static uint32 saved_reg65;
192 static uint32 saved_reg71;
193
194 if (trump_sco) {
195 /* this should reduce eSCO agressive retransmit
196 * w/o breaking it
197 */
198
199 /* 1st save current */
200 WL_TRACE(("Do new SCO/eSCO coex algo {save &"
201 "override}\n"));
202 if ((!dev_wlc_intvar_get_reg(dev, "btc_params", 0x32, &saved_reg50)) &&
203 (!dev_wlc_intvar_get_reg(dev, "btc_params", 0x33, &saved_reg51)) &&
204 (!dev_wlc_intvar_get_reg(dev, "btc_params", 0x40, &saved_reg64)) &&
205 (!dev_wlc_intvar_get_reg(dev, "btc_params", 0x41, &saved_reg65)) &&
206 (!dev_wlc_intvar_get_reg(dev, "btc_params", 0x47, &saved_reg71))) {
207 saved_status = TRUE;
208 WL_TRACE(("saved bt_params[50,51,64,65,71]:"
209 "0x%x 0x%x 0x%x 0x%x 0x%x\n",
210 saved_reg50, saved_reg51, saved_reg64, saved_reg65,
211 saved_reg71));
212 } else {
213 WL_ERR((":%s: save btc_params failed\n", __FUNCTION__));
214 saved_status = FALSE;
215 return -1;
216 }
217
218 WL_TRACE(("override with [50,51,64,65,71]:"
219 "0x%x 0x%x 0x%x 0x%x 0x%x\n",
220 *(u32 *)(buf_reg50va_dhcp_on + 0x4),
221 *(u32 *)(buf_reg51va_dhcp_on + 0x4),
222 *(u32 *)(buf_reg64va_dhcp_on + 0x4),
223 *(u32 *)(buf_reg65va_dhcp_on + 0x4),
224 *(u32 *)(buf_reg71va_dhcp_on + 0x4)));
225
226 dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg50va_dhcp_on[0],
227 0x8);
228 dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg51va_dhcp_on[0],
229 0x8);
230 dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg64va_dhcp_on[0],
231 0x8);
232 dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg65va_dhcp_on[0],
233 0x8);
234 dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg71va_dhcp_on[0],
235 0x8);
236
237 saved_status = TRUE;
238 } else if (saved_status) {
239 /* restore previously saved bt params */
240 WL_TRACE(("Do new SCO/eSCO coex algo {save &"
241 "override}\n"));
242
243 regaddr = 0x32;
244 dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr,
245 (char *)&saved_reg50);
246 regaddr = 0x33;
247 dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr,
248 (char *)&saved_reg51);
249 regaddr = 0x40;
250 dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr,
251 (char *)&saved_reg64);
252 regaddr = 0x41;
253 dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr,
254 (char *)&saved_reg65);
255 regaddr = 0x47;
256 dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr,
257 (char *)&saved_reg71);
258
259 WL_TRACE(("restore bt_params[50,51,64,65,71]:"
260 "0x%x 0x%x 0x%x 0x%x 0x%x\n",
261 saved_reg50, saved_reg51, saved_reg64, saved_reg65,
262 saved_reg71));
263
264 saved_status = FALSE;
265 } else {
266 WL_ERR((":%s att to restore not saved BTCOEX params\n", __FUNCTION__));
267 return -1;
268 }
269 return 0;
270 }
271 #endif /* BT_DHCP_eSCO_FIX */
272
wl_cfg80211_bt_setflag(struct net_device * dev,bool set)273 static void wl_cfg80211_bt_setflag(struct net_device *dev, bool set)
274 {
275 #if defined(BT_DHCP_USE_FLAGS)
276 char buf_flag7_dhcp_on[0x8] = {7, 00, 00, 00, 0x1, 0x0, 0x00, 0x00};
277 char buf_flag7_default[0x8] = {7, 00, 00, 00, 0x0, 0x00, 0x00, 0x00};
278 #endif // endif
279
280 #if defined(BT_DHCP_eSCO_FIX)
281 /* set = 1, save & turn on 0 - off & restore prev settings */
282 set_btc_esco_params(dev, set);
283 #endif // endif
284
285 #if defined(BT_DHCP_USE_FLAGS)
286 WL_TRACE(("WI-FI priority boost via bt flags, set:%d\n", set));
287 if (set == TRUE) {
288 /* Forcing bt_flag7 */
289 dev_wlc_bufvar_set(dev, "btc_flags", (char *)&buf_flag7_dhcp_on[0],
290 sizeof(buf_flag7_dhcp_on));
291 } else {
292 /* Restoring default bt flag7 */
293 dev_wlc_bufvar_set(dev, "btc_flags", (char *)&buf_flag7_default[0],
294 sizeof(buf_flag7_default));
295 }
296 #endif // endif
297 }
298
wl_cfg80211_bt_timerfunc(ulong data)299 static void wl_cfg80211_bt_timerfunc(ulong data)
300 {
301 struct btcoex_info *bt_local = (struct btcoex_info *)data;
302 WL_TRACE(("Enter\n"));
303 bt_local->timer_on = 0;
304 schedule_work(&bt_local->work);
305 }
306
wl_cfg80211_bt_handler(struct work_struct * work)307 static void wl_cfg80211_bt_handler(struct work_struct *work)
308 {
309 struct btcoex_info *btcx_inf;
310
311 GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST();
312 btcx_inf = container_of(work, struct btcoex_info, work);
313 GCC_DIAGNOSTIC_POP();
314
315 if (btcx_inf->timer_on) {
316 btcx_inf->timer_on = 0;
317 del_timer_sync(&btcx_inf->timer);
318 }
319
320 switch (btcx_inf->bt_state) {
321 case BT_DHCP_START:
322 /* DHCP started
323 * provide OPPORTUNITY window to get DHCP address
324 */
325 WL_TRACE(("bt_dhcp stm: started \n"));
326
327 btcx_inf->bt_state = BT_DHCP_OPPR_WIN;
328 mod_timer(&btcx_inf->timer,
329 jiffies + msecs_to_jiffies(BT_DHCP_OPPR_WIN_TIME));
330 btcx_inf->timer_on = 1;
331 break;
332
333 case BT_DHCP_OPPR_WIN:
334 if (btcx_inf->dhcp_done) {
335 WL_TRACE(("DHCP Done before T1 expiration\n"));
336 goto btc_coex_idle;
337 }
338
339 /* DHCP is not over yet, start lowering BT priority
340 * enforce btc_params + flags if necessary
341 */
342 WL_TRACE(("DHCP T1:%d expired\n", BT_DHCP_OPPR_WIN_TIME));
343 if (btcx_inf->dev) {
344 wl_cfg80211_bt_setflag(btcx_inf->dev, TRUE);
345 }
346 btcx_inf->bt_state = BT_DHCP_FLAG_FORCE_TIMEOUT;
347 mod_timer(&btcx_inf->timer,
348 jiffies + msecs_to_jiffies(BT_DHCP_FLAG_FORCE_TIME));
349 btcx_inf->timer_on = 1;
350 break;
351
352 case BT_DHCP_FLAG_FORCE_TIMEOUT:
353 if (btcx_inf->dhcp_done) {
354 WL_TRACE(("DHCP Done before T2 expiration\n"));
355 } else {
356 /* Noo dhcp during T1+T2, restore BT priority */
357 WL_TRACE(("DHCP wait interval T2:%d msec expired\n",
358 BT_DHCP_FLAG_FORCE_TIME));
359 }
360
361 /* Restoring default bt priority */
362 if (btcx_inf->dev) {
363 wl_cfg80211_bt_setflag(btcx_inf->dev, FALSE);
364 }
365 btc_coex_idle:
366 btcx_inf->bt_state = BT_DHCP_IDLE;
367 btcx_inf->timer_on = 0;
368 break;
369
370 default:
371 WL_ERR(("error g_status=%d !!!\n", btcx_inf->bt_state));
372 if (btcx_inf->dev) {
373 wl_cfg80211_bt_setflag(btcx_inf->dev, FALSE);
374 }
375 btcx_inf->bt_state = BT_DHCP_IDLE;
376 btcx_inf->timer_on = 0;
377 break;
378 }
379
380 net_os_wake_unlock(btcx_inf->dev);
381 }
382
wl_cfg80211_btcoex_init(struct net_device * ndev)383 void *wl_cfg80211_btcoex_init(struct net_device *ndev)
384 {
385 struct btcoex_info *btco_inf = NULL;
386
387 btco_inf = kmalloc(sizeof(struct btcoex_info), GFP_KERNEL);
388 if (!btco_inf) {
389 return NULL;
390 }
391
392 btco_inf->bt_state = BT_DHCP_IDLE;
393 btco_inf->ts_dhcp_start = 0;
394 btco_inf->ts_dhcp_ok = 0;
395 /* Set up timer for BT */
396 btco_inf->timer_ms = 0xA;
397 init_timer_compat(&btco_inf->timer, wl_cfg80211_bt_timerfunc, btco_inf);
398
399 btco_inf->dev = ndev;
400
401 INIT_WORK(&btco_inf->work, wl_cfg80211_bt_handler);
402
403 btcoex_info_loc = btco_inf;
404 return btco_inf;
405 }
406
wl_cfg80211_btcoex_deinit()407 void wl_cfg80211_btcoex_deinit()
408 {
409 if (!btcoex_info_loc) {
410 return;
411 }
412
413 if (btcoex_info_loc->timer_on) {
414 btcoex_info_loc->timer_on = 0;
415 del_timer_sync(&btcoex_info_loc->timer);
416 }
417
418 cancel_work_sync(&btcoex_info_loc->work);
419
420 kfree(btcoex_info_loc);
421 }
422
wl_cfg80211_set_btcoex_dhcp(struct net_device * dev,dhd_pub_t * dhd,char * command)423 int wl_cfg80211_set_btcoex_dhcp(struct net_device *dev, dhd_pub_t *dhd,
424 char *command)
425 {
426 struct btcoex_info *btco_inf = btcoex_info_loc;
427 char powermode_val = 0;
428 uint8 cmd_len = 0;
429 char buf_reg66va_dhcp_on[0x8] = {66, 00, 00, 00, 0x10, 0x27, 0x00, 0x00};
430 char buf_reg41va_dhcp_on[0x8] = {41, 00, 00, 00, 0x33, 0x00, 0x00, 0x00};
431 char buf_reg68va_dhcp_on[0x8] = {68, 00, 00, 00, 0x90, 0x01, 0x00, 0x00};
432
433 uint32 regaddr;
434 static uint32 saved_reg66;
435 static uint32 saved_reg41;
436 static uint32 saved_reg68;
437 static bool saved_status = FALSE;
438
439 char buf_flag7_default[0x8] = {7, 00, 00, 00, 0x0, 0x00, 0x00, 0x00};
440
441 /* Figure out powermode 1 or o command */
442 cmd_len = sizeof(BTCOEXMODE);
443 powermode_val = command[cmd_len];
444
445 if (powermode_val == '1') {
446 WL_TRACE_HW4(("DHCP session starts\n"));
447
448 #ifdef PKT_FILTER_SUPPORT
449 dhd->dhcp_in_progress = 1;
450
451 #if defined(APSTA_BLOCK_ARP_DURING_DHCP)
452 if (DHD_OPMODE_STA_SOFTAP_CONCURR(dhd)) {
453 /* Block ARP frames while DHCP of STA interface is in
454 * progress in case of STA/SoftAP concurrent mode
455 */
456 wl_cfg80211_block_arp(dev, TRUE);
457 } else
458 #endif /* APSTA_BLOCK_ARP_DURING_DHCP */
459 if (dhd->early_suspended) {
460 WL_TRACE_HW4(
461 ("DHCP in progressing , disable packet filter!!!\n"));
462 dhd_enable_packet_filter(0, dhd);
463 }
464 #endif /* PKT_FILTER_SUPPORT */
465
466 /* Retrieve and saved orig regs value */
467 if ((saved_status == FALSE) &&
468 (!dev_wlc_intvar_get_reg(dev, "btc_params", 0x42, &saved_reg66)) &&
469 (!dev_wlc_intvar_get_reg(dev, "btc_params", 0x29, &saved_reg41)) &&
470 (!dev_wlc_intvar_get_reg(dev, "btc_params", 0x44, &saved_reg68))) {
471 saved_status = TRUE;
472 WL_TRACE(("Saved 0x%x 0x%x 0x%x\n", saved_reg66, saved_reg41,
473 saved_reg68));
474
475 /* Disable PM mode during dhpc session */
476
477 /* Disable PM mode during dhpc session */
478 /* Start BT timer only for SCO connection */
479 if (btcoex_is_sco_active(dev)) {
480 /* btc_params 66 */
481 dev_wlc_bufvar_set(dev, "btc_params",
482 (char *)&buf_reg66va_dhcp_on[0],
483 sizeof(buf_reg66va_dhcp_on));
484 /* btc_params 41 0x33 */
485 dev_wlc_bufvar_set(dev, "btc_params",
486 (char *)&buf_reg41va_dhcp_on[0],
487 sizeof(buf_reg41va_dhcp_on));
488 /* btc_params 68 0x190 */
489 dev_wlc_bufvar_set(dev, "btc_params",
490 (char *)&buf_reg68va_dhcp_on[0],
491 sizeof(buf_reg68va_dhcp_on));
492 saved_status = TRUE;
493
494 btco_inf->bt_state = BT_DHCP_START;
495 btco_inf->timer_on = 1;
496 mod_timer(&btco_inf->timer, timer_expires(&btco_inf->timer));
497 WL_TRACE(("enable BT DHCP Timer\n"));
498 }
499 } else if (saved_status == TRUE) {
500 WL_ERR(("was called w/o DHCP OFF. Continue\n"));
501 }
502 } else if (powermode_val == '2') {
503 #ifdef PKT_FILTER_SUPPORT
504 dhd->dhcp_in_progress = 0;
505 WL_TRACE_HW4(("DHCP is complete \n"));
506
507 #if defined(APSTA_BLOCK_ARP_DURING_DHCP)
508 if (DHD_OPMODE_STA_SOFTAP_CONCURR(dhd)) {
509 /* Unblock ARP frames */
510 wl_cfg80211_block_arp(dev, FALSE);
511 } else
512 #endif /* APSTA_BLOCK_ARP_DURING_DHCP */
513 if (dhd->early_suspended) {
514 /* Enable packet filtering */
515 WL_TRACE_HW4(("DHCP is complete , enable packet filter!!!\n"));
516 dhd_enable_packet_filter(1, dhd);
517 }
518 #endif /* PKT_FILTER_SUPPORT */
519
520 /* Restoring PM mode */
521
522 /* Stop any bt timer because DHCP session is done */
523 WL_TRACE(("disable BT DHCP Timer\n"));
524 if (btco_inf->timer_on) {
525 btco_inf->timer_on = 0;
526 del_timer_sync(&btco_inf->timer);
527
528 if (btco_inf->bt_state != BT_DHCP_IDLE) {
529 /* need to restore original btc flags & extra btc params */
530 WL_TRACE(("bt->bt_state:%d\n", btco_inf->bt_state));
531 /* wake up btcoex thread to restore btlags+params */
532 schedule_work(&btco_inf->work);
533 }
534 }
535
536 /* Restoring btc_flag paramter anyway */
537 if (saved_status == TRUE) {
538 dev_wlc_bufvar_set(dev, "btc_flags", (char *)&buf_flag7_default[0],
539 sizeof(buf_flag7_default));
540 }
541
542 /* Restore original values */
543 if (saved_status == TRUE) {
544 regaddr = 0x42;
545 dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr,
546 (char *)&saved_reg66);
547 regaddr = 0x29;
548 dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr,
549 (char *)&saved_reg41);
550 regaddr = 0x44;
551 dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr,
552 (char *)&saved_reg68);
553
554 WL_TRACE(("restore regs {66,41,68} <- 0x%x 0x%x 0x%x\n",
555 saved_reg66, saved_reg41, saved_reg68));
556 }
557 saved_status = FALSE;
558 } else {
559 WL_ERR(("Unkwown yet power setting, ignored\n"));
560 }
561
562 return (snprintf(command, sizeof("OK"), "OK") + 1);
563 }
564