wl_cfg_btcoex.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /*
  2. * Linux cfg80211 driver - Dongle Host Driver (DHD) related
  3. *
  4. * Copyright (C) 1999-2015, Broadcom Corporation
  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 of
  16. * the license of that module. An independent module is a module which is not
  17. * 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. * $Id: wl_cfg_btcoex.c 427707 2013-10-04 10:28:29Z $
  25. */
  26. #include <net/rtnetlink.h>
  27. #include <bcmutils.h>
  28. #include <wldev_common.h>
  29. #include <wl_cfg80211.h>
  30. #include <dhd_cfg80211.h>
  31. #include <dngl_stats.h>
  32. #include <dhd.h>
  33. #include <dhdioctl.h>
  34. #include <wlioctl.h>
  35. #ifdef PKT_FILTER_SUPPORT
  36. extern uint dhd_pkt_filter_enable;
  37. extern uint dhd_master_mode;
  38. extern void dhd_pktfilter_offload_enable(dhd_pub_t * dhd, char *arg, int enable, int master_mode);
  39. #endif
  40. struct btcoex_info {
  41. struct timer_list timer;
  42. u32 timer_ms;
  43. u32 timer_on;
  44. u32 ts_dhcp_start; /* ms ts ecord time stats */
  45. u32 ts_dhcp_ok; /* ms ts ecord time stats */
  46. bool dhcp_done; /* flag, indicates that host done with
  47. * dhcp before t1/t2 expiration
  48. */
  49. s32 bt_state;
  50. struct work_struct work;
  51. struct net_device *dev;
  52. };
  53. static struct btcoex_info *btcoex_info_loc = NULL;
  54. /* TODO: clean up the BT-Coex code, it still have some legacy ioctl/iovar functions */
  55. /* use New SCO/eSCO smart YG suppression */
  56. #define BT_DHCP_eSCO_FIX
  57. /* this flag boost wifi pkt priority to max, caution: -not fair to sco */
  58. #define BT_DHCP_USE_FLAGS
  59. /* T1 start SCO/ESCo priority suppression */
  60. #define BT_DHCP_OPPR_WIN_TIME 2500
  61. /* T2 turn off SCO/SCO supperesion is (timeout) */
  62. #define BT_DHCP_FLAG_FORCE_TIME 5500
  63. enum wl_cfg80211_btcoex_status {
  64. BT_DHCP_IDLE,
  65. BT_DHCP_START,
  66. BT_DHCP_OPPR_WIN,
  67. BT_DHCP_FLAG_FORCE_TIMEOUT
  68. };
  69. /*
  70. * get named driver variable to uint register value and return error indication
  71. * calling example: dev_wlc_intvar_get_reg(dev, "btc_params",66, &reg_value)
  72. */
  73. static int
  74. dev_wlc_intvar_get_reg(struct net_device *dev, char *name,
  75. uint reg, int *retval)
  76. {
  77. union {
  78. char buf[WLC_IOCTL_SMLEN];
  79. int val;
  80. } var;
  81. int error;
  82. bcm_mkiovar(name, (char *)(&reg), sizeof(reg),
  83. (char *)(&var), sizeof(var.buf));
  84. error = wldev_ioctl(dev, WLC_GET_VAR, (char *)(&var), sizeof(var.buf), false);
  85. *retval = dtoh32(var.val);
  86. return (error);
  87. }
  88. static int
  89. dev_wlc_bufvar_set(struct net_device *dev, char *name, char *buf, int len)
  90. {
  91. #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)
  92. char ioctlbuf_local[1024];
  93. #else
  94. static char ioctlbuf_local[1024];
  95. #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) */
  96. bcm_mkiovar(name, buf, len, ioctlbuf_local, sizeof(ioctlbuf_local));
  97. return (wldev_ioctl(dev, WLC_SET_VAR, ioctlbuf_local, sizeof(ioctlbuf_local), true));
  98. }
  99. /*
  100. get named driver variable to uint register value and return error indication
  101. calling example: dev_wlc_intvar_set_reg(dev, "btc_params",66, value)
  102. */
  103. static int
  104. dev_wlc_intvar_set_reg(struct net_device *dev, char *name, char *addr, char * val)
  105. {
  106. char reg_addr[8];
  107. memset(reg_addr, 0, sizeof(reg_addr));
  108. memcpy((char *)&reg_addr[0], (char *)addr, 4);
  109. memcpy((char *)&reg_addr[4], (char *)val, 4);
  110. return (dev_wlc_bufvar_set(dev, name, (char *)&reg_addr[0], sizeof(reg_addr)));
  111. }
  112. static bool btcoex_is_sco_active(struct net_device *dev)
  113. {
  114. int ioc_res = 0;
  115. bool res = FALSE;
  116. int sco_id_cnt = 0;
  117. int param27;
  118. int i;
  119. for (i = 0; i < 12; i++) {
  120. ioc_res = dev_wlc_intvar_get_reg(dev, "btc_params", 27, &param27);
  121. WL_TRACE(("sample[%d], btc params: 27:%x\n", i, param27));
  122. if (ioc_res < 0) {
  123. WL_ERR(("ioc read btc params error\n"));
  124. break;
  125. }
  126. if ((param27 & 0x6) == 2) { /* count both sco & esco */
  127. sco_id_cnt++;
  128. }
  129. if (sco_id_cnt > 2) {
  130. WL_TRACE(("sco/esco detected, pkt id_cnt:%d samples:%d\n",
  131. sco_id_cnt, i));
  132. res = TRUE;
  133. break;
  134. }
  135. OSL_SLEEP(5);
  136. }
  137. return res;
  138. }
  139. #if defined(BT_DHCP_eSCO_FIX)
  140. /* Enhanced BT COEX settings for eSCO compatibility during DHCP window */
  141. static int set_btc_esco_params(struct net_device *dev, bool trump_sco)
  142. {
  143. static bool saved_status = FALSE;
  144. char buf_reg50va_dhcp_on[8] =
  145. { 50, 00, 00, 00, 0x22, 0x80, 0x00, 0x00 };
  146. char buf_reg51va_dhcp_on[8] =
  147. { 51, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 };
  148. char buf_reg64va_dhcp_on[8] =
  149. { 64, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 };
  150. char buf_reg65va_dhcp_on[8] =
  151. { 65, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 };
  152. char buf_reg71va_dhcp_on[8] =
  153. { 71, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 };
  154. uint32 regaddr;
  155. static uint32 saved_reg50;
  156. static uint32 saved_reg51;
  157. static uint32 saved_reg64;
  158. static uint32 saved_reg65;
  159. static uint32 saved_reg71;
  160. if (trump_sco) {
  161. /* this should reduce eSCO agressive retransmit
  162. * w/o breaking it
  163. */
  164. /* 1st save current */
  165. WL_TRACE(("Do new SCO/eSCO coex algo {save &"
  166. "override}\n"));
  167. if ((!dev_wlc_intvar_get_reg(dev, "btc_params", 50, &saved_reg50)) &&
  168. (!dev_wlc_intvar_get_reg(dev, "btc_params", 51, &saved_reg51)) &&
  169. (!dev_wlc_intvar_get_reg(dev, "btc_params", 64, &saved_reg64)) &&
  170. (!dev_wlc_intvar_get_reg(dev, "btc_params", 65, &saved_reg65)) &&
  171. (!dev_wlc_intvar_get_reg(dev, "btc_params", 71, &saved_reg71))) {
  172. saved_status = TRUE;
  173. WL_TRACE(("saved bt_params[50,51,64,65,71]:"
  174. "0x%x 0x%x 0x%x 0x%x 0x%x\n",
  175. saved_reg50, saved_reg51,
  176. saved_reg64, saved_reg65, saved_reg71));
  177. } else {
  178. WL_ERR((":%s: save btc_params failed\n",
  179. __FUNCTION__));
  180. saved_status = FALSE;
  181. return -1;
  182. }
  183. WL_TRACE(("override with [50,51,64,65,71]:"
  184. "0x%x 0x%x 0x%x 0x%x 0x%x\n",
  185. *(u32 *)(buf_reg50va_dhcp_on+4),
  186. *(u32 *)(buf_reg51va_dhcp_on+4),
  187. *(u32 *)(buf_reg64va_dhcp_on+4),
  188. *(u32 *)(buf_reg65va_dhcp_on+4),
  189. *(u32 *)(buf_reg71va_dhcp_on+4)));
  190. dev_wlc_bufvar_set(dev, "btc_params",
  191. (char *)&buf_reg50va_dhcp_on[0], 8);
  192. dev_wlc_bufvar_set(dev, "btc_params",
  193. (char *)&buf_reg51va_dhcp_on[0], 8);
  194. dev_wlc_bufvar_set(dev, "btc_params",
  195. (char *)&buf_reg64va_dhcp_on[0], 8);
  196. dev_wlc_bufvar_set(dev, "btc_params",
  197. (char *)&buf_reg65va_dhcp_on[0], 8);
  198. dev_wlc_bufvar_set(dev, "btc_params",
  199. (char *)&buf_reg71va_dhcp_on[0], 8);
  200. saved_status = TRUE;
  201. } else if (saved_status) {
  202. /* restore previously saved bt params */
  203. WL_TRACE(("Do new SCO/eSCO coex algo {save &"
  204. "override}\n"));
  205. regaddr = 50;
  206. dev_wlc_intvar_set_reg(dev, "btc_params",
  207. (char *)&regaddr, (char *)&saved_reg50);
  208. regaddr = 51;
  209. dev_wlc_intvar_set_reg(dev, "btc_params",
  210. (char *)&regaddr, (char *)&saved_reg51);
  211. regaddr = 64;
  212. dev_wlc_intvar_set_reg(dev, "btc_params",
  213. (char *)&regaddr, (char *)&saved_reg64);
  214. regaddr = 65;
  215. dev_wlc_intvar_set_reg(dev, "btc_params",
  216. (char *)&regaddr, (char *)&saved_reg65);
  217. regaddr = 71;
  218. dev_wlc_intvar_set_reg(dev, "btc_params",
  219. (char *)&regaddr, (char *)&saved_reg71);
  220. WL_TRACE(("restore bt_params[50,51,64,65,71]:"
  221. "0x%x 0x%x 0x%x 0x%x 0x%x\n",
  222. saved_reg50, saved_reg51, saved_reg64,
  223. saved_reg65, saved_reg71));
  224. saved_status = FALSE;
  225. } else {
  226. WL_ERR((":%s att to restore not saved BTCOEX params\n",
  227. __FUNCTION__));
  228. return -1;
  229. }
  230. return 0;
  231. }
  232. #endif /* BT_DHCP_eSCO_FIX */
  233. static void
  234. wl_cfg80211_bt_setflag(struct net_device *dev, bool set)
  235. {
  236. #if defined(BT_DHCP_USE_FLAGS)
  237. char buf_flag7_dhcp_on[8] = { 7, 00, 00, 00, 0x1, 0x0, 0x00, 0x00 };
  238. char buf_flag7_default[8] = { 7, 00, 00, 00, 0x0, 0x00, 0x00, 0x00};
  239. #endif
  240. #if defined(BT_DHCP_eSCO_FIX)
  241. /* set = 1, save & turn on 0 - off & restore prev settings */
  242. set_btc_esco_params(dev, set);
  243. #endif
  244. #if defined(BT_DHCP_USE_FLAGS)
  245. WL_TRACE(("WI-FI priority boost via bt flags, set:%d\n", set));
  246. if (set == TRUE)
  247. /* Forcing bt_flag7 */
  248. dev_wlc_bufvar_set(dev, "btc_flags",
  249. (char *)&buf_flag7_dhcp_on[0],
  250. sizeof(buf_flag7_dhcp_on));
  251. else
  252. /* Restoring default bt flag7 */
  253. dev_wlc_bufvar_set(dev, "btc_flags",
  254. (char *)&buf_flag7_default[0],
  255. sizeof(buf_flag7_default));
  256. #endif
  257. }
  258. static void wl_cfg80211_bt_timerfunc(ulong data)
  259. {
  260. struct btcoex_info *bt_local = (struct btcoex_info *)data;
  261. WL_TRACE(("Enter\n"));
  262. bt_local->timer_on = 0;
  263. schedule_work(&bt_local->work);
  264. }
  265. static void wl_cfg80211_bt_handler(struct work_struct *work)
  266. {
  267. struct btcoex_info *btcx_inf;
  268. btcx_inf = container_of(work, struct btcoex_info, work);
  269. if (btcx_inf->timer_on) {
  270. btcx_inf->timer_on = 0;
  271. del_timer_sync(&btcx_inf->timer);
  272. }
  273. switch (btcx_inf->bt_state) {
  274. case BT_DHCP_START:
  275. /* DHCP started
  276. * provide OPPORTUNITY window to get DHCP address
  277. */
  278. WL_TRACE(("bt_dhcp stm: started \n"));
  279. btcx_inf->bt_state = BT_DHCP_OPPR_WIN;
  280. mod_timer(&btcx_inf->timer,
  281. jiffies + msecs_to_jiffies(BT_DHCP_OPPR_WIN_TIME));
  282. btcx_inf->timer_on = 1;
  283. break;
  284. case BT_DHCP_OPPR_WIN:
  285. if (btcx_inf->dhcp_done) {
  286. WL_TRACE(("DHCP Done before T1 expiration\n"));
  287. goto btc_coex_idle;
  288. }
  289. /* DHCP is not over yet, start lowering BT priority
  290. * enforce btc_params + flags if necessary
  291. */
  292. WL_TRACE(("DHCP T1:%d expired\n", BT_DHCP_OPPR_WIN_TIME));
  293. if (btcx_inf->dev)
  294. wl_cfg80211_bt_setflag(btcx_inf->dev, TRUE);
  295. btcx_inf->bt_state = BT_DHCP_FLAG_FORCE_TIMEOUT;
  296. mod_timer(&btcx_inf->timer,
  297. jiffies + msecs_to_jiffies(BT_DHCP_FLAG_FORCE_TIME));
  298. btcx_inf->timer_on = 1;
  299. break;
  300. case BT_DHCP_FLAG_FORCE_TIMEOUT:
  301. if (btcx_inf->dhcp_done) {
  302. WL_TRACE(("DHCP Done before T2 expiration\n"));
  303. } else {
  304. /* Noo dhcp during T1+T2, restore BT priority */
  305. WL_TRACE(("DHCP wait interval T2:%d msec expired\n",
  306. BT_DHCP_FLAG_FORCE_TIME));
  307. }
  308. /* Restoring default bt priority */
  309. if (btcx_inf->dev)
  310. wl_cfg80211_bt_setflag(btcx_inf->dev, FALSE);
  311. btc_coex_idle:
  312. btcx_inf->bt_state = BT_DHCP_IDLE;
  313. btcx_inf->timer_on = 0;
  314. break;
  315. default:
  316. WL_ERR(("error g_status=%d !!!\n", btcx_inf->bt_state));
  317. if (btcx_inf->dev)
  318. wl_cfg80211_bt_setflag(btcx_inf->dev, FALSE);
  319. btcx_inf->bt_state = BT_DHCP_IDLE;
  320. btcx_inf->timer_on = 0;
  321. break;
  322. }
  323. net_os_wake_unlock(btcx_inf->dev);
  324. }
  325. void* wl_cfg80211_btcoex_init(struct net_device *ndev)
  326. {
  327. struct btcoex_info *btco_inf = NULL;
  328. btco_inf = kmalloc(sizeof(struct btcoex_info), GFP_KERNEL);
  329. if (!btco_inf)
  330. return NULL;
  331. btco_inf->bt_state = BT_DHCP_IDLE;
  332. btco_inf->ts_dhcp_start = 0;
  333. btco_inf->ts_dhcp_ok = 0;
  334. /* Set up timer for BT */
  335. btco_inf->timer_ms = 10;
  336. init_timer(&btco_inf->timer);
  337. btco_inf->timer.data = (ulong)btco_inf;
  338. btco_inf->timer.function = wl_cfg80211_bt_timerfunc;
  339. btco_inf->dev = ndev;
  340. INIT_WORK(&btco_inf->work, wl_cfg80211_bt_handler);
  341. btcoex_info_loc = btco_inf;
  342. return btco_inf;
  343. }
  344. void wl_cfg80211_btcoex_deinit()
  345. {
  346. if (!btcoex_info_loc)
  347. return;
  348. if (btcoex_info_loc->timer_on) {
  349. btcoex_info_loc->timer_on = 0;
  350. del_timer_sync(&btcoex_info_loc->timer);
  351. }
  352. cancel_work_sync(&btcoex_info_loc->work);
  353. kfree(btcoex_info_loc);
  354. }
  355. int wl_cfg80211_set_btcoex_dhcp(struct net_device *dev, dhd_pub_t *dhd, char *command)
  356. {
  357. struct btcoex_info *btco_inf = btcoex_info_loc;
  358. char powermode_val = 0;
  359. char buf_reg66va_dhcp_on[8] = { 66, 00, 00, 00, 0x10, 0x27, 0x00, 0x00 };
  360. char buf_reg41va_dhcp_on[8] = { 41, 00, 00, 00, 0x33, 0x00, 0x00, 0x00 };
  361. char buf_reg68va_dhcp_on[8] = { 68, 00, 00, 00, 0x90, 0x01, 0x00, 0x00 };
  362. uint32 regaddr;
  363. static uint32 saved_reg66;
  364. static uint32 saved_reg41;
  365. static uint32 saved_reg68;
  366. static bool saved_status = FALSE;
  367. char buf_flag7_default[8] = { 7, 00, 00, 00, 0x0, 0x00, 0x00, 0x00};
  368. /* Figure out powermode 1 or o command */
  369. strncpy((char *)&powermode_val, command + strlen("BTCOEXMODE") +1, 1);
  370. if (strnicmp((char *)&powermode_val, "1", strlen("1")) == 0) {
  371. WL_TRACE_HW4(("DHCP session starts\n"));
  372. #ifdef PKT_FILTER_SUPPORT
  373. dhd->dhcp_in_progress = 1;
  374. if (dhd->early_suspended) {
  375. WL_TRACE_HW4(("DHCP in progressing , disable packet filter!!!\n"));
  376. dhd_enable_packet_filter(0, dhd);
  377. }
  378. #endif
  379. /* Retrieve and saved orig regs value */
  380. if ((saved_status == FALSE) &&
  381. (!dev_wlc_intvar_get_reg(dev, "btc_params", 66, &saved_reg66)) &&
  382. (!dev_wlc_intvar_get_reg(dev, "btc_params", 41, &saved_reg41)) &&
  383. (!dev_wlc_intvar_get_reg(dev, "btc_params", 68, &saved_reg68))) {
  384. saved_status = TRUE;
  385. WL_TRACE(("Saved 0x%x 0x%x 0x%x\n",
  386. saved_reg66, saved_reg41, saved_reg68));
  387. /* Disable PM mode during dhpc session */
  388. /* Disable PM mode during dhpc session */
  389. /* Start BT timer only for SCO connection */
  390. if (btcoex_is_sco_active(dev)) {
  391. /* btc_params 66 */
  392. dev_wlc_bufvar_set(dev, "btc_params",
  393. (char *)&buf_reg66va_dhcp_on[0],
  394. sizeof(buf_reg66va_dhcp_on));
  395. /* btc_params 41 0x33 */
  396. dev_wlc_bufvar_set(dev, "btc_params",
  397. (char *)&buf_reg41va_dhcp_on[0],
  398. sizeof(buf_reg41va_dhcp_on));
  399. /* btc_params 68 0x190 */
  400. dev_wlc_bufvar_set(dev, "btc_params",
  401. (char *)&buf_reg68va_dhcp_on[0],
  402. sizeof(buf_reg68va_dhcp_on));
  403. saved_status = TRUE;
  404. btco_inf->bt_state = BT_DHCP_START;
  405. btco_inf->timer_on = 1;
  406. mod_timer(&btco_inf->timer, btco_inf->timer.expires);
  407. WL_TRACE(("enable BT DHCP Timer\n"));
  408. }
  409. }
  410. else if (saved_status == TRUE) {
  411. WL_ERR(("was called w/o DHCP OFF. Continue\n"));
  412. }
  413. }
  414. else if (strnicmp((char *)&powermode_val, "2", strlen("2")) == 0) {
  415. #ifdef PKT_FILTER_SUPPORT
  416. dhd->dhcp_in_progress = 0;
  417. WL_TRACE_HW4(("DHCP is complete \n"));
  418. /* Enable packet filtering */
  419. if (dhd->early_suspended) {
  420. WL_TRACE_HW4(("DHCP is complete , enable packet filter!!!\n"));
  421. dhd_enable_packet_filter(1, dhd);
  422. }
  423. #endif /* PKT_FILTER_SUPPORT */
  424. /* Restoring PM mode */
  425. /* Stop any bt timer because DHCP session is done */
  426. WL_TRACE(("disable BT DHCP Timer\n"));
  427. if (btco_inf->timer_on) {
  428. btco_inf->timer_on = 0;
  429. del_timer_sync(&btco_inf->timer);
  430. if (btco_inf->bt_state != BT_DHCP_IDLE) {
  431. /* need to restore original btc flags & extra btc params */
  432. WL_TRACE(("bt->bt_state:%d\n", btco_inf->bt_state));
  433. /* wake up btcoex thread to restore btlags+params */
  434. schedule_work(&btco_inf->work);
  435. }
  436. }
  437. /* Restoring btc_flag paramter anyway */
  438. if (saved_status == TRUE)
  439. dev_wlc_bufvar_set(dev, "btc_flags",
  440. (char *)&buf_flag7_default[0], sizeof(buf_flag7_default));
  441. /* Restore original values */
  442. if (saved_status == TRUE) {
  443. regaddr = 66;
  444. dev_wlc_intvar_set_reg(dev, "btc_params",
  445. (char *)&regaddr, (char *)&saved_reg66);
  446. regaddr = 41;
  447. dev_wlc_intvar_set_reg(dev, "btc_params",
  448. (char *)&regaddr, (char *)&saved_reg41);
  449. regaddr = 68;
  450. dev_wlc_intvar_set_reg(dev, "btc_params",
  451. (char *)&regaddr, (char *)&saved_reg68);
  452. WL_TRACE(("restore regs {66,41,68} <- 0x%x 0x%x 0x%x\n",
  453. saved_reg66, saved_reg41, saved_reg68));
  454. }
  455. saved_status = FALSE;
  456. }
  457. else {
  458. WL_ERR(("Unkwown yet power setting, ignored\n"));
  459. }
  460. snprintf(command, 3, "OK");
  461. return (strlen("OK"));
  462. }