1 /* 2 * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. 3 * 4 * Permission to use, copy, modify, and/or distribute this software for 5 * any purpose with or without fee is hereby granted, provided that the 6 * above copyright notice and this permission notice appear in all 7 * copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 14 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 * PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /** 20 * DOC: vdev_mgr_ops.c 21 * 22 * This file provide API definitions for filling data structures 23 * and sending vdev mgmt commands to target_if/mlme 24 */ 25 #include "vdev_mgr_ops.h" 26 #include <wlan_objmgr_vdev_obj.h> 27 #include <wlan_vdev_mlme_api.h> 28 #include <wlan_mlme_dbg.h> 29 #include <wlan_vdev_mgr_tgt_if_tx_api.h> 30 #include <target_if.h> 31 #include <init_deinit_lmac.h> 32 #include <wlan_lmac_if_api.h> 33 #include <wlan_reg_services_api.h> 34 #include <wlan_dfs_tgt_api.h> 35 #include <wlan_dfs_utils_api.h> 36 #include <wlan_vdev_mgr_ucfg_api.h> 37 #include <qdf_module.h> 38 39 static QDF_STATUS vdev_mgr_create_param_update( 40 struct vdev_mlme_obj *mlme_obj, 41 struct vdev_create_params *param) 42 { 43 struct wlan_objmgr_pdev *pdev; 44 struct wlan_objmgr_vdev *vdev; 45 struct vdev_mlme_mbss_11ax *mbss; 46 47 vdev = mlme_obj->vdev; 48 if (!vdev) { 49 mlme_err("VDEV is NULL"); 50 return QDF_STATUS_E_INVAL; 51 } 52 53 pdev = wlan_vdev_get_pdev(vdev); 54 if (!pdev) { 55 mlme_err("PDEV is NULL"); 56 return QDF_STATUS_E_INVAL; 57 } 58 59 mbss = &mlme_obj->mgmt.mbss_11ax; 60 param->pdev_id = wlan_objmgr_pdev_get_pdev_id(pdev); 61 param->vdev_id = wlan_vdev_get_id(vdev); 62 param->nss_2g = mlme_obj->proto.generic.nss_2g; 63 param->nss_5g = mlme_obj->proto.generic.nss_5g; 64 param->type = mlme_obj->mgmt.generic.type; 65 param->subtype = mlme_obj->mgmt.generic.subtype; 66 param->mbssid_flags = mbss->mbssid_flags; 67 param->vdevid_trans = mbss->vdevid_trans; 68 69 return QDF_STATUS_SUCCESS; 70 } 71 72 QDF_STATUS vdev_mgr_create_send(struct vdev_mlme_obj *mlme_obj) 73 { 74 QDF_STATUS status; 75 struct vdev_create_params param = {0}; 76 77 if (!mlme_obj) { 78 mlme_err("VDEV_MLME is NULL"); 79 return QDF_STATUS_E_INVAL; 80 } 81 82 status = vdev_mgr_create_param_update(mlme_obj, ¶m); 83 if (QDF_IS_STATUS_ERROR(status)) { 84 mlme_err("Param Update Error: %d", status); 85 return status; 86 } 87 88 status = tgt_vdev_mgr_create_send(mlme_obj, ¶m); 89 90 return status; 91 } 92 93 static QDF_STATUS vdev_mgr_start_param_update( 94 struct vdev_mlme_obj *mlme_obj, 95 struct vdev_start_params *param) 96 { 97 struct wlan_channel *des_chan; 98 uint32_t dfs_reg; 99 bool set_agile = false, dfs_set_cfreq2 = false; 100 struct wlan_objmgr_vdev *vdev; 101 struct wlan_objmgr_pdev *pdev; 102 enum QDF_OPMODE op_mode; 103 104 vdev = mlme_obj->vdev; 105 if (!vdev) { 106 mlme_err("VDEV is NULL"); 107 return QDF_STATUS_E_INVAL; 108 } 109 110 pdev = wlan_vdev_get_pdev(vdev); 111 if (!pdev) { 112 mlme_err("PDEV is NULL"); 113 return QDF_STATUS_E_INVAL; 114 } 115 116 if (wlan_objmgr_pdev_try_get_ref(pdev, WLAN_MLME_SB_ID) != 117 QDF_STATUS_SUCCESS) { 118 mlme_err("Failed to get pdev reference"); 119 return QDF_STATUS_E_FAILURE; 120 } 121 122 des_chan = wlan_vdev_mlme_get_des_chan(vdev); 123 param->vdev_id = wlan_vdev_get_id(vdev); 124 125 op_mode = wlan_vdev_mlme_get_opmode(vdev); 126 if ((op_mode == QDF_SAP_MODE || op_mode == QDF_P2P_GO_MODE) && 127 (WLAN_REG_IS_5GHZ_CH_FREQ(des_chan->ch_freq) || 128 WLAN_REG_IS_49GHZ_FREQ(des_chan->ch_freq) || 129 WLAN_REG_IS_6GHZ_CHAN_FREQ(des_chan->ch_freq))) 130 tgt_dfs_set_current_channel_for_freq(pdev, des_chan->ch_freq, 131 des_chan->ch_flags, 132 des_chan->ch_flagext, 133 des_chan->ch_ieee, 134 des_chan->ch_freq_seg1, 135 des_chan->ch_freq_seg2, 136 des_chan->ch_cfreq1, 137 des_chan->ch_cfreq2); 138 139 param->beacon_interval = mlme_obj->proto.generic.beacon_interval; 140 param->dtim_period = mlme_obj->proto.generic.dtim_period; 141 param->disable_hw_ack = mlme_obj->mgmt.generic.disable_hw_ack; 142 param->preferred_rx_streams = 143 mlme_obj->mgmt.chainmask_info.num_rx_chain; 144 param->preferred_tx_streams = 145 mlme_obj->mgmt.chainmask_info.num_tx_chain; 146 147 wlan_reg_get_dfs_region(pdev, &dfs_reg); 148 param->regdomain = dfs_reg; 149 param->he_ops = mlme_obj->proto.he_ops_info.he_ops; 150 151 param->channel.chan_id = des_chan->ch_ieee; 152 param->channel.pwr = mlme_obj->mgmt.generic.tx_power; 153 param->channel.mhz = des_chan->ch_freq; 154 param->channel.half_rate = mlme_obj->mgmt.rate_info.half_rate; 155 param->channel.quarter_rate = mlme_obj->mgmt.rate_info.quarter_rate; 156 param->channel.dfs_set = wlan_reg_is_dfs_for_freq(pdev, 157 des_chan->ch_freq); 158 param->channel.dfs_set_cfreq2 = utils_is_dfs_cfreq2_ch(pdev); 159 param->channel.is_chan_passive = 160 utils_is_dfs_chan_for_freq(pdev, param->channel.mhz); 161 param->channel.allow_ht = mlme_obj->proto.ht_info.allow_ht; 162 param->channel.allow_vht = mlme_obj->proto.vht_info.allow_vht; 163 param->channel.phy_mode = mlme_obj->mgmt.generic.phy_mode; 164 param->channel.cfreq1 = des_chan->ch_cfreq1; 165 param->channel.cfreq2 = des_chan->ch_cfreq2; 166 param->channel.maxpower = mlme_obj->mgmt.generic.maxpower; 167 param->channel.minpower = mlme_obj->mgmt.generic.minpower; 168 param->channel.maxregpower = mlme_obj->mgmt.generic.maxregpower; 169 param->channel.antennamax = mlme_obj->mgmt.generic.antennamax; 170 param->channel.reg_class_id = mlme_obj->mgmt.generic.reg_class_id; 171 param->bcn_tx_rate_code = mlme_obj->mgmt.rate_info.bcn_tx_rate; 172 param->ldpc_rx_enabled = mlme_obj->proto.generic.ldpc; 173 if (mlme_obj->mgmt.generic.type == WLAN_VDEV_MLME_TYPE_AP) { 174 param->hidden_ssid = mlme_obj->mgmt.ap.hidden_ssid; 175 param->cac_duration_ms = mlme_obj->mgmt.ap.cac_duration_ms; 176 } 177 wlan_vdev_mlme_get_ssid(vdev, param->ssid.mac_ssid, 178 ¶m->ssid.length); 179 180 if (des_chan->ch_phymode == WLAN_PHYMODE_11AC_VHT80 || 181 des_chan->ch_phymode == WLAN_PHYMODE_11AXA_HE80) { 182 tgt_dfs_find_vht80_precac_chan_freq(pdev, 183 des_chan->ch_phymode, 184 des_chan->ch_freq_seg1, 185 ¶m->channel.cfreq1, 186 ¶m->channel.cfreq2, 187 ¶m->channel.phy_mode, 188 &dfs_set_cfreq2, 189 &set_agile); 190 param->channel.dfs_set_cfreq2 = dfs_set_cfreq2; 191 param->channel.set_agile = set_agile; 192 } 193 194 wlan_objmgr_pdev_release_ref(pdev, WLAN_MLME_SB_ID); 195 return QDF_STATUS_SUCCESS; 196 } 197 198 QDF_STATUS vdev_mgr_start_send( 199 struct vdev_mlme_obj *mlme_obj, 200 bool restart) 201 { 202 QDF_STATUS status; 203 struct vdev_start_params param = {0}; 204 205 if (!mlme_obj) { 206 mlme_err("VDEV_MLME is NULL"); 207 return QDF_STATUS_E_INVAL; 208 } 209 210 status = vdev_mgr_start_param_update(mlme_obj, ¶m); 211 if (QDF_IS_STATUS_ERROR(status)) { 212 mlme_err("Param Update Error: %d", status); 213 return status; 214 } 215 216 param.is_restart = restart; 217 status = tgt_vdev_mgr_start_send(mlme_obj, ¶m); 218 219 return status; 220 } 221 222 static QDF_STATUS vdev_mgr_delete_param_update( 223 struct vdev_mlme_obj *mlme_obj, 224 struct vdev_delete_params *param) 225 { 226 struct wlan_objmgr_vdev *vdev; 227 228 vdev = mlme_obj->vdev; 229 if (!vdev) { 230 mlme_err("VDEV is NULL"); 231 return QDF_STATUS_E_INVAL; 232 } 233 234 param->vdev_id = wlan_vdev_get_id(vdev); 235 return QDF_STATUS_SUCCESS; 236 } 237 238 QDF_STATUS vdev_mgr_delete_send(struct vdev_mlme_obj *mlme_obj) 239 { 240 QDF_STATUS status; 241 struct vdev_delete_params param; 242 243 if (!mlme_obj) { 244 mlme_err("VDEV_MLME is NULL"); 245 return QDF_STATUS_E_INVAL; 246 } 247 248 status = vdev_mgr_delete_param_update(mlme_obj, ¶m); 249 if (QDF_IS_STATUS_ERROR(status)) { 250 mlme_err("Param Update Error: %d", status); 251 return status; 252 } 253 254 status = tgt_vdev_mgr_delete_send(mlme_obj, ¶m); 255 256 return status; 257 } 258 259 static QDF_STATUS vdev_mgr_stop_param_update( 260 struct vdev_mlme_obj *mlme_obj, 261 struct vdev_stop_params *param) 262 { 263 struct wlan_objmgr_vdev *vdev; 264 265 vdev = mlme_obj->vdev; 266 if (!vdev) { 267 mlme_err("VDEV is NULL"); 268 return QDF_STATUS_E_INVAL; 269 } 270 271 param->vdev_id = wlan_vdev_get_id(vdev); 272 273 return QDF_STATUS_SUCCESS; 274 } 275 276 QDF_STATUS vdev_mgr_stop_send(struct vdev_mlme_obj *mlme_obj) 277 { 278 QDF_STATUS status; 279 struct vdev_stop_params param = {0}; 280 281 if (!mlme_obj) { 282 mlme_err("VDEV_MLME is NULL"); 283 return QDF_STATUS_E_INVAL; 284 } 285 286 status = vdev_mgr_stop_param_update(mlme_obj, ¶m); 287 if (QDF_IS_STATUS_ERROR(status)) { 288 mlme_err("Param Update Error: %d", status); 289 return status; 290 } 291 292 status = tgt_vdev_mgr_stop_send(mlme_obj, ¶m); 293 294 return status; 295 } 296 297 static QDF_STATUS vdev_mgr_bcn_tmpl_param_update( 298 struct vdev_mlme_obj *mlme_obj, 299 struct beacon_tmpl_params *param) 300 { 301 return QDF_STATUS_SUCCESS; 302 } 303 304 static QDF_STATUS vdev_mgr_sta_ps_param_update( 305 struct vdev_mlme_obj *mlme_obj, 306 struct sta_ps_params *param) 307 { 308 struct wlan_objmgr_vdev *vdev; 309 310 vdev = mlme_obj->vdev; 311 param->vdev_id = wlan_vdev_get_id(vdev); 312 param->param_id = WLAN_MLME_CFG_UAPSD; 313 param->value = mlme_obj->proto.sta.uapsd_cfg; 314 return QDF_STATUS_SUCCESS; 315 } 316 317 static QDF_STATUS vdev_mgr_up_param_update( 318 struct vdev_mlme_obj *mlme_obj, 319 struct vdev_up_params *param) 320 { 321 struct vdev_mlme_mbss_11ax *mbss; 322 struct wlan_objmgr_vdev *vdev; 323 324 vdev = mlme_obj->vdev; 325 param->vdev_id = wlan_vdev_get_id(vdev); 326 param->assoc_id = mlme_obj->proto.sta.assoc_id; 327 mbss = &mlme_obj->mgmt.mbss_11ax; 328 if (mbss->profile_idx) { 329 param->profile_idx = mbss->profile_idx; 330 param->profile_num = mbss->profile_num; 331 qdf_mem_copy(param->trans_bssid, mbss->trans_bssid, 332 QDF_MAC_ADDR_SIZE); 333 } 334 335 return QDF_STATUS_SUCCESS; 336 } 337 338 QDF_STATUS vdev_mgr_up_send(struct vdev_mlme_obj *mlme_obj) 339 { 340 QDF_STATUS status; 341 struct vdev_up_params param = {0}; 342 struct sta_ps_params ps_param = {0}; 343 struct beacon_tmpl_params bcn_tmpl_param = {0}; 344 enum QDF_OPMODE opmode; 345 struct wlan_objmgr_vdev *vdev; 346 struct config_fils_params fils_param = {0}; 347 uint8_t is_6g_sap_fd_enabled; 348 349 if (!mlme_obj) { 350 mlme_err("VDEV_MLME is NULL"); 351 return QDF_STATUS_E_INVAL; 352 } 353 354 vdev = mlme_obj->vdev; 355 if (!vdev) { 356 mlme_err("VDEV is NULL"); 357 return QDF_STATUS_E_INVAL; 358 } 359 360 vdev_mgr_up_param_update(mlme_obj, ¶m); 361 vdev_mgr_bcn_tmpl_param_update(mlme_obj, &bcn_tmpl_param); 362 363 opmode = wlan_vdev_mlme_get_opmode(vdev); 364 if (opmode == QDF_STA_MODE) { 365 vdev_mgr_sta_ps_param_update(mlme_obj, &ps_param); 366 status = tgt_vdev_mgr_sta_ps_param_send(mlme_obj, &ps_param); 367 368 } 369 370 status = tgt_vdev_mgr_beacon_tmpl_send(mlme_obj, &bcn_tmpl_param); 371 if (QDF_IS_STATUS_ERROR(status)) 372 return status; 373 374 status = tgt_vdev_mgr_up_send(mlme_obj, ¶m); 375 if (QDF_IS_STATUS_ERROR(status)) 376 return status; 377 378 is_6g_sap_fd_enabled = wlan_vdev_mlme_feat_ext_cap_get(vdev, 379 WLAN_VDEV_FEXT_FILS_DISC_6G_SAP); 380 mlme_debug("SAP FD enabled %d", is_6g_sap_fd_enabled); 381 if (opmode == QDF_SAP_MODE && mlme_obj->vdev->vdev_mlme.des_chan && 382 is_6g_sap_fd_enabled && 383 WLAN_REG_IS_6GHZ_CHAN_FREQ( 384 mlme_obj->vdev->vdev_mlme.des_chan->ch_freq)) { 385 fils_param.vdev_id = wlan_vdev_get_id(mlme_obj->vdev); 386 fils_param.fd_period = DEFAULT_FILS_DISCOVERY_PERIOD; 387 status = tgt_vdev_mgr_fils_enable_send(mlme_obj, 388 &fils_param); 389 } 390 391 return status; 392 } 393 394 static QDF_STATUS vdev_mgr_down_param_update( 395 struct vdev_mlme_obj *mlme_obj, 396 struct vdev_down_params *param) 397 { 398 struct wlan_objmgr_vdev *vdev; 399 400 vdev = mlme_obj->vdev; 401 if (!vdev) { 402 mlme_err("VDEV is NULL"); 403 return QDF_STATUS_E_INVAL; 404 } 405 406 param->vdev_id = wlan_vdev_get_id(vdev); 407 408 return QDF_STATUS_SUCCESS; 409 } 410 411 QDF_STATUS vdev_mgr_down_send(struct vdev_mlme_obj *mlme_obj) 412 { 413 QDF_STATUS status; 414 struct vdev_down_params param = {0}; 415 416 if (!mlme_obj) { 417 mlme_err("VDEV_MLME is NULL"); 418 return QDF_STATUS_E_INVAL; 419 } 420 421 status = vdev_mgr_down_param_update(mlme_obj, ¶m); 422 if (QDF_IS_STATUS_ERROR(status)) { 423 mlme_err("Param Update Error: %d", status); 424 return status; 425 } 426 427 status = tgt_vdev_mgr_down_send(mlme_obj, ¶m); 428 429 return status; 430 } 431 432 static QDF_STATUS vdev_mgr_peer_flush_tids_param_update( 433 struct vdev_mlme_obj *mlme_obj, 434 struct peer_flush_params *param, 435 uint8_t *mac, 436 uint32_t peer_tid_bitmap) 437 { 438 struct wlan_objmgr_vdev *vdev; 439 440 vdev = mlme_obj->vdev; 441 if (!vdev) { 442 mlme_err("VDEV is NULL"); 443 return QDF_STATUS_E_INVAL; 444 } 445 446 param->vdev_id = wlan_vdev_get_id(vdev); 447 param->peer_tid_bitmap = peer_tid_bitmap; 448 qdf_mem_copy(param->peer_mac, mac, QDF_MAC_ADDR_SIZE); 449 return QDF_STATUS_SUCCESS; 450 } 451 452 QDF_STATUS vdev_mgr_peer_flush_tids_send(struct vdev_mlme_obj *mlme_obj, 453 uint8_t *mac, 454 uint32_t peer_tid_bitmap) 455 { 456 QDF_STATUS status; 457 struct peer_flush_params param = {0}; 458 459 if (!mlme_obj || !mac) { 460 mlme_err("Invalid input"); 461 return QDF_STATUS_E_INVAL; 462 } 463 464 status = vdev_mgr_peer_flush_tids_param_update(mlme_obj, ¶m, 465 mac, peer_tid_bitmap); 466 if (QDF_IS_STATUS_ERROR(status)) { 467 mlme_err("Param Update Error: %d", status); 468 return status; 469 } 470 471 status = tgt_vdev_mgr_peer_flush_tids_send(mlme_obj, ¶m); 472 473 return status; 474 } 475 476 static QDF_STATUS vdev_mgr_multiple_restart_param_update( 477 struct wlan_objmgr_pdev *pdev, 478 struct mlme_channel_param *chan, 479 uint32_t disable_hw_ack, 480 uint32_t *vdev_ids, 481 uint32_t num_vdevs, 482 struct multiple_vdev_restart_params *param) 483 { 484 param->pdev_id = wlan_objmgr_pdev_get_pdev_id(pdev); 485 param->requestor_id = MULTIPLE_VDEV_RESTART_REQ_ID; 486 param->disable_hw_ack = disable_hw_ack; 487 param->cac_duration_ms = WLAN_DFS_WAIT_MS; 488 param->num_vdevs = num_vdevs; 489 490 qdf_mem_copy(param->vdev_ids, vdev_ids, 491 sizeof(uint32_t) * (param->num_vdevs)); 492 qdf_mem_copy(¶m->ch_param, chan, 493 sizeof(struct mlme_channel_param)); 494 495 return QDF_STATUS_SUCCESS; 496 } 497 498 QDF_STATUS vdev_mgr_multiple_restart_send(struct wlan_objmgr_pdev *pdev, 499 struct mlme_channel_param *chan, 500 uint32_t disable_hw_ack, 501 uint32_t *vdev_ids, 502 uint32_t num_vdevs) 503 { 504 struct multiple_vdev_restart_params param = {0}; 505 506 vdev_mgr_multiple_restart_param_update(pdev, chan, 507 disable_hw_ack, 508 vdev_ids, num_vdevs, 509 ¶m); 510 511 return tgt_vdev_mgr_multiple_vdev_restart_send(pdev, ¶m); 512 } 513 514 qdf_export_symbol(vdev_mgr_multiple_restart_send); 515 516 static QDF_STATUS vdev_mgr_set_custom_aggr_size_param_update( 517 struct vdev_mlme_obj *mlme_obj, 518 struct set_custom_aggr_size_params *param, 519 bool is_amsdu) 520 { 521 struct wlan_objmgr_vdev *vdev; 522 523 vdev = mlme_obj->vdev; 524 if (!vdev) { 525 mlme_err("VDEV is NULL"); 526 return QDF_STATUS_E_INVAL; 527 } 528 529 param->aggr_type = is_amsdu ? WLAN_MLME_CUSTOM_AGGR_TYPE_AMSDU 530 : WLAN_MLME_CUSTOM_AGGR_TYPE_AMPDU; 531 /* 532 * We are only setting TX params, therefore 533 * we are disabling rx_aggr_size 534 */ 535 param->rx_aggr_size_disable = true; 536 param->tx_aggr_size = is_amsdu ? mlme_obj->mgmt.generic.amsdu 537 : mlme_obj->mgmt.generic.ampdu; 538 param->vdev_id = wlan_vdev_get_id(vdev); 539 540 return QDF_STATUS_SUCCESS; 541 } 542 543 QDF_STATUS vdev_mgr_set_custom_aggr_size_send( 544 struct vdev_mlme_obj *vdev_mlme, 545 bool is_amsdu) 546 { 547 QDF_STATUS status; 548 struct set_custom_aggr_size_params param = {0}; 549 550 status = vdev_mgr_set_custom_aggr_size_param_update(vdev_mlme, 551 ¶m, is_amsdu); 552 if (QDF_IS_STATUS_ERROR(status)) { 553 mlme_err("Param Update Error: %d", status); 554 return status; 555 } 556 557 return tgt_vdev_mgr_set_custom_aggr_size_send(vdev_mlme, ¶m); 558 } 559 560 static QDF_STATUS vdev_mgr_peer_delete_all_param_update( 561 struct vdev_mlme_obj *mlme_obj, 562 struct peer_delete_all_params *param) 563 { 564 struct wlan_objmgr_vdev *vdev; 565 566 vdev = mlme_obj->vdev; 567 if (!vdev) { 568 mlme_err("VDEV is NULL"); 569 return QDF_STATUS_E_INVAL; 570 } 571 572 param->vdev_id = wlan_vdev_get_id(vdev); 573 return QDF_STATUS_SUCCESS; 574 } 575 576 QDF_STATUS vdev_mgr_peer_delete_all_send(struct vdev_mlme_obj *mlme_obj) 577 { 578 QDF_STATUS status; 579 struct peer_delete_all_params param = {0}; 580 581 if (!mlme_obj) { 582 mlme_err("Invalid input"); 583 return QDF_STATUS_E_INVAL; 584 } 585 586 status = vdev_mgr_peer_delete_all_param_update(mlme_obj, ¶m); 587 if (QDF_IS_STATUS_ERROR(status)) { 588 mlme_err("Param Update Error: %d", status); 589 return status; 590 } 591 592 status = tgt_vdev_mgr_peer_delete_all_send(mlme_obj, ¶m); 593 594 return status; 595 } 596 597