knetctrl.c (20554B)
1 /* 2 * 3 * This license is set out in https://raw.githubusercontent.com/Broadcom-Network-Switching-Software/OpenBCM/master/Legal/LICENSE file. 4 * 5 * Copyright 2007-2019 Broadcom Inc. All rights reserved. 6 * 7 * File: knetctrl.c 8 * Purpose: Kernel network control 9 * Requires: 10 */ 11 12 13 #ifdef INCLUDE_KNET 14 15 #include <sal/appl/sal.h> 16 17 #include <soc/knet.h> 18 19 #include <bcm/knet.h> 20 21 #include <appl/diag/shell.h> 22 #include <appl/diag/parse.h> 23 #include <appl/diag/system.h> 24 25 #include <shared/bsl.h> 26 27 char cmd_knet_ctrl_usage[] = 28 "Usages:\n" 29 #ifdef COMPILER_STRING_CONST_LIMIT 30 " knetctrl <subcmd> [options]\n" 31 #else 32 " knetctrl netif create [Vlan=<vlan>] [Port=<port>] [RCPU=yes|no]\n" 33 " Create a virtual network interface.\n" 34 " Supported options:\n" 35 " [AddTag=yes|no] - Add tag before sending to switch port\n" 36 " [KeepRxTag=yes|no] - Keep tag of packets which are sent to this netif\n" 37 " [IFName=<str>] - Optional network device name\n" 38 " [CBData=<val>] - User data for knet rx cb\n" 39 " knetctrl netif destroy <id>\n" 40 " Destroy a virtual network interface.\n" 41 " knetctrl netif show\n" 42 " Show virtual network interfaces.\n" 43 " knetctrl filter create DestType=Null|NetIF|RxAPI [DestID=<id>] [options]\n" 44 " Create a packet filter.\n" 45 " DestID is required for DestType=NetIF only.\n" 46 " Supported options:\n" 47 " [DESCription=<str>] - Optional filter description\n" 48 " [PRIOrity=<prio>] - Filter match order priority (0 is highest)\n" 49 " [StripTag=yes|no] - Strip VLAN tag before sending packet to NetIF\n" 50 " [Mirror=yes|no] - Copy packet to RxAPI (if DestType=NetIF)\n" 51 " [DestProto=<proto>] - Override default protocol (if DestType=NetIF)\n" 52 " [Vlan=<vlan>] - Match specified VLAN ID\n" 53 " [IngPort=<port>] - Match specified local ingress port\n" 54 " [SrcPort=<port>] - Match specified module port\n" 55 " [SrcModid=<modid>] - Match specified module ID\n" 56 " [Reason=<reason>] - Match specified reason for copy to CPU\n" 57 " [FPRule=<rule>] - Match specified FP rule number (if FP match)\n" 58 " [PktOffset=<offet>] - Match packet data starting at this offset\n" 59 " [PktByteX=<val>] - Match packet data byte X (X=[0..7] supported)\n" 60 " [PktByte0=<val>] - Match packet data byte 0 (example)\n" 61 " [CBData=<val>] - User data for knet rx cb\n" 62 " knetctrl filter destroy <id>\n" 63 " Destroy a packet filter.\n" 64 " knetctrl filter show\n" 65 " Show all packet filters.\n" 66 " knetctrl debugpkt [on|off|show]\n" 67 " Enable/Disable debugging packet function.\n" 68 " Once enabling this function, there will be one packet copy being\n" 69 " sent/mirrored to RX API for debugging usage.\n" 70 #endif 71 ; 72 73 /* Note: See knet.h, BCM_KNET_NETIF_T_xxx */ 74 STATIC char *netif_type[] = { 75 "unknown", "Vlan", "Port", "Meta", NULL 76 }; 77 78 /* Note: See knet.h, BCM_KNET_DEST_T_xxx */ 79 STATIC char *filter_dest_type[] = { 80 "Null", "NetIF", "RxAPI", "CallBack", NULL 81 }; 82 83 STATIC char *reason_str[bcmRxReasonCount+1] = BCM_RX_REASON_NAMES_INITIALIZER; 84 85 STATIC void 86 _show_netif(int unit, bcm_knet_netif_t *netif) 87 { 88 char *type_str = "?"; 89 char *port_str = "n/a"; 90 91 switch (netif->type) { 92 case BCM_KNET_NETIF_T_TX_CPU_INGRESS: 93 case BCM_KNET_NETIF_T_TX_META_DATA: 94 type_str = netif_type[netif->type]; 95 break; 96 case BCM_KNET_NETIF_T_TX_LOCAL_PORT: 97 type_str = netif_type[netif->type]; 98 port_str = BCM_PORT_NAME(unit, netif->port); 99 break; 100 default: 101 break; 102 } 103 104 cli_out("Interface ID %d: name=%s type=%s vlan=%d port=%s", 105 netif->id, netif->name, type_str, netif->vlan, port_str); 106 107 if (netif->flags & BCM_KNET_NETIF_F_ADD_TAG) { 108 cli_out(" addtag"); 109 } 110 if (netif->flags & BCM_KNET_NETIF_F_RCPU_ENCAP) { 111 cli_out(" rcpu"); 112 } 113 if (netif->flags & BCM_KNET_NETIF_F_KEEP_RX_TAG) { 114 cli_out(" keeprxtag"); 115 } 116 cli_out("\n"); 117 } 118 119 STATIC int 120 _trav_netif(int unit, bcm_knet_netif_t *netif, void *user_data) 121 { 122 int *count = (int *)user_data; 123 124 _show_netif(unit, netif); 125 126 if (count) { 127 (*count)++; 128 } 129 130 return BCM_E_NONE; 131 } 132 133 static void 134 _show_filter(int unit, bcm_knet_filter_t *filter) 135 { 136 char *dest_str = "?"; 137 char proto_str[16]; 138 int idx, edx; 139 140 switch (filter->dest_type) { 141 case BCM_KNET_DEST_T_NULL: 142 case BCM_KNET_DEST_T_NETIF: 143 case BCM_KNET_DEST_T_BCM_RX_API: 144 case BCM_KNET_DEST_T_CALLBACK: 145 dest_str = filter_dest_type[filter->dest_type]; 146 break; 147 default: 148 break; 149 } 150 151 proto_str[0] = 0; 152 if (filter->dest_proto) { 153 sal_sprintf(proto_str, "[0x%04x]", filter->dest_proto); 154 } 155 cli_out("Filter ID %d: prio=%d dest=%s(%d)%s desc='%s'", 156 filter->id, filter->priority, dest_str, 157 filter->dest_id, proto_str, filter->desc); 158 if (filter->mirror_type == BCM_KNET_DEST_T_BCM_RX_API) { 159 cli_out(" mirror=rxapi"); 160 } else if (filter->mirror_type == BCM_KNET_DEST_T_NETIF) { 161 proto_str[0] = 0; 162 if (filter->mirror_proto) { 163 sal_sprintf(proto_str, "[0x%04x]", filter->mirror_proto); 164 } 165 cli_out(" mirror=netif(%d)%s", filter->mirror_id, proto_str); 166 } 167 if (filter->flags & BCM_KNET_FILTER_F_STRIP_TAG) { 168 cli_out(" striptag"); 169 } 170 if (filter->match_flags & BCM_KNET_FILTER_M_VLAN) { 171 cli_out(" vlan=%d", filter->m_vlan); 172 } 173 if (filter->match_flags & BCM_KNET_FILTER_M_INGPORT) { 174 cli_out(" ingport=%s", BCM_PORT_NAME(unit, filter->m_ingport)); 175 } 176 if (filter->match_flags & BCM_KNET_FILTER_M_SRC_MODID) { 177 cli_out(" srcmod=%d", filter->m_src_modid); 178 } 179 if (filter->match_flags & BCM_KNET_FILTER_M_SRC_MODPORT) { 180 cli_out(" srcport=%d", filter->m_src_modport); 181 } 182 if (filter->match_flags & BCM_KNET_FILTER_M_FP_RULE) { 183 cli_out(" fprule=%d", filter->m_fp_rule); 184 } 185 if (filter->match_flags & BCM_KNET_FILTER_M_REASON) { 186 cli_out(" reason="); 187 for (idx = 0; idx < bcmRxReasonCount; idx++) { 188 if (BCM_RX_REASON_GET(filter->m_reason, idx)) { 189 cli_out("%s", reason_str[idx]); 190 break; 191 } 192 } 193 } 194 if (filter->match_flags & BCM_KNET_FILTER_M_RAW) { 195 cli_out(" rawdata"); 196 for (idx = 0; idx < filter->raw_size; idx++) { 197 if (filter->m_raw_mask[idx]) { 198 break; 199 } 200 } 201 for (edx = filter->raw_size - 1; edx > idx; edx--) { 202 if (filter->m_raw_mask[edx]) { 203 break; 204 } 205 } 206 if (edx < idx) { 207 /* Entire mask is empty - should not happen */ 208 cli_out("?"); 209 } else { 210 /* Show offset of first valid byte */ 211 cli_out("[%d]", idx); 212 /* Dump data */ 213 for (; idx <= edx; idx++) { 214 if (filter->m_raw_mask[idx]) { 215 cli_out(":0x%02x", filter->m_raw_data[idx]); 216 if (filter->m_raw_mask[idx] != 0xff) { 217 cli_out("/0x%02x", filter->m_raw_mask[idx]); 218 } 219 } else { 220 cli_out(":-"); 221 } 222 } 223 } 224 } 225 cli_out("\n"); 226 } 227 228 STATIC int 229 _trav_filter(int unit, bcm_knet_filter_t *filter, void *user_data) 230 { 231 int *count = (int *)user_data; 232 233 _show_filter(unit, filter); 234 235 if (count) { 236 (*count)++; 237 } 238 239 return BCM_E_NONE; 240 } 241 242 cmd_result_t 243 cmd_knet_ctrl(int unit, args_t *args) 244 { 245 parse_table_t pt; 246 char *subcmd; 247 int rv; 248 int idx, pdx, count; 249 int if_id; 250 int if_type; 251 int if_vlan; 252 int if_port; 253 int if_addtag; 254 int if_rcpu; 255 char *if_name; 256 int pf_id; 257 int pf_prio; 258 int pf_dest_type; 259 int pf_dest_id; 260 int pf_striptag; 261 int pf_mirror; 262 int pf_mirror_id; 263 int pf_vlan; 264 int pf_ingport; 265 int pf_src_modport; 266 int pf_src_modid; 267 int pf_reason; 268 int pf_fp_rule; 269 char *pf_desc; 270 uint32 pkt_offset; 271 int pkt_data[8]; 272 bcm_knet_netif_t netif; 273 bcm_knet_filter_t filter; 274 int pf_dest_proto; 275 int pf_mirror_proto; 276 int if_keeprxtag; 277 int if_cb_user_data; 278 int pf_cb_user_data; 279 280 if ((subcmd = ARG_GET(args)) == NULL) { 281 cli_out("Requires string argument\n"); 282 return CMD_USAGE; 283 } 284 if (sal_strcasecmp(subcmd, "netif") == 0) { 285 if ((subcmd = ARG_GET(args)) == NULL) { 286 cli_out("Requires additional string argument\n"); 287 return CMD_USAGE; 288 } 289 if (sal_strcasecmp(subcmd, "create") == 0) { 290 if_type = BCM_KNET_NETIF_T_TX_CPU_INGRESS; 291 if_vlan = 1; 292 if_port = -1; 293 if_addtag = 0; 294 if_rcpu = 0; 295 if_name = NULL; 296 if_keeprxtag = 0; 297 if_cb_user_data = 0; 298 parse_table_init(unit, &pt); 299 parse_table_add(&pt, "Type", PQ_DFL|PQ_MULTI, 0, &if_type, netif_type); 300 parse_table_add(&pt, "Vlan", PQ_DFL|PQ_INT, 0, &if_vlan, 0); 301 parse_table_add(&pt, "Port", PQ_DFL|PQ_PORT, 0, &if_port, 0); 302 parse_table_add(&pt, "AddTag", PQ_DFL|PQ_BOOL, 0, &if_addtag, 0); 303 parse_table_add(&pt, "KeepRxTag", PQ_DFL|PQ_BOOL, 0, &if_keeprxtag, 0); 304 parse_table_add(&pt, "RCPU", PQ_DFL|PQ_BOOL, 0, &if_rcpu, 0); 305 parse_table_add(&pt, "IFName", PQ_DFL|PQ_STRING, 0, &if_name, 0); 306 parse_table_add(&pt, "CBData", PQ_DFL|PQ_INT, 0, &if_cb_user_data, 0); 307 if (parse_arg_eq(args, &pt) < 0) { 308 parse_arg_eq_done(&pt); 309 return CMD_USAGE; 310 } 311 bcm_knet_netif_t_init(&netif); 312 if (if_name) { 313 sal_strncpy(netif.name, if_name, sizeof(netif.name) - 1); 314 } 315 parse_arg_eq_done(&pt); 316 /* Force port mode if port was specified */ 317 if (if_port >= 0) { 318 if_type = BCM_KNET_NETIF_T_TX_LOCAL_PORT; 319 } 320 /* Force meta mode if RCPU was specified */ 321 if (if_rcpu) { 322 if_type = BCM_KNET_NETIF_T_TX_META_DATA; 323 } 324 netif.type = if_type; 325 netif.vlan = if_vlan; 326 if (if_port >= 0) { 327 netif.port = if_port; 328 } 329 if (if_addtag) { 330 netif.flags |= BCM_KNET_NETIF_F_ADD_TAG; 331 } 332 if (if_rcpu) { 333 netif.flags |= BCM_KNET_NETIF_F_RCPU_ENCAP; 334 } 335 if (if_keeprxtag) { 336 netif.flags |= BCM_KNET_NETIF_F_KEEP_RX_TAG; 337 } 338 netif.cb_user_data = if_cb_user_data; 339 if ((rv = bcm_knet_netif_create(unit, &netif)) < 0) { 340 cli_out("Error creating network interface: %s\n", 341 bcm_errmsg(rv)); 342 return CMD_FAIL; 343 } 344 _show_netif(unit, &netif); 345 } else if (sal_strcasecmp(subcmd, "destroy") == 0) { 346 if (!ARG_CNT(args) || !isint(ARG_CUR(args))) { 347 return(CMD_USAGE); 348 } 349 if_id = parse_integer(ARG_GET(args)); 350 if ((rv = bcm_knet_netif_destroy(unit, if_id)) < 0) { 351 cli_out("Error destroying network interface: %s\n", 352 bcm_errmsg(rv)); 353 return CMD_FAIL; 354 } 355 } else if (sal_strcasecmp(subcmd, "show") == 0) { 356 count = 0; 357 if (bcm_knet_netif_traverse(unit, _trav_netif, &count) < 0) { 358 return CMD_FAIL; 359 } 360 if (count == 0) { 361 cli_out("<no network interfaces>\n"); 362 } 363 } else { 364 cli_out("Subcommand not found: %s\n", subcmd); 365 return CMD_USAGE; 366 } 367 } else if (sal_strcasecmp(subcmd, "filter") == 0) { 368 if ((subcmd = ARG_GET(args)) == NULL) { 369 cli_out("Requires string argument\n"); 370 return CMD_USAGE; 371 } 372 if (sal_strcasecmp(subcmd, "create") == 0) { 373 pf_dest_type = BCM_KNET_DEST_T_NULL; 374 pf_dest_id = -1; 375 pf_mirror = 0; 376 pf_mirror_id = 0; 377 pf_vlan = -1; 378 pf_ingport = -1; 379 pf_src_modport = -1; 380 pf_src_modid = -1; 381 pf_reason = bcmRxReasonInvalid; 382 pf_fp_rule = -1; 383 pkt_offset = 0; 384 for (idx = 0; idx < COUNTOF(pkt_data); idx++) { 385 pkt_data[idx] = -1; 386 } 387 pf_desc = NULL; 388 pf_prio = 0; 389 pf_striptag = 1; 390 pf_dest_proto = 0; 391 pf_mirror_proto = 0; 392 pf_cb_user_data = 0; 393 parse_table_init(unit, &pt); 394 parse_table_add(&pt, "DestType", PQ_DFL|PQ_MULTI, 0, &pf_dest_type, 395 filter_dest_type); 396 parse_table_add(&pt, "DestID", PQ_DFL|PQ_INT, 0, &pf_dest_id, 0); 397 parse_table_add(&pt, "PRIOrity", PQ_DFL|PQ_INT, 0, &pf_prio, 0); 398 parse_table_add(&pt, "DESCription", PQ_DFL|PQ_STRING, 0, &pf_desc, 0); 399 parse_table_add(&pt, "StripTag", PQ_DFL|PQ_BOOL, 0, &pf_striptag, 0); 400 parse_table_add(&pt, "Mirror", PQ_DFL|PQ_BOOL, 0, &pf_mirror, 0); 401 parse_table_add(&pt, "MirrorID", PQ_DFL|PQ_INT, 0, &pf_mirror_id, 0); 402 parse_table_add(&pt, "Vlan", PQ_DFL|PQ_INT, 0, &pf_vlan, 0); 403 parse_table_add(&pt, "IngPort", PQ_DFL|PQ_PORT, 0, &pf_ingport, 0); 404 parse_table_add(&pt, "SrcPort", PQ_DFL|PQ_INT, 0, &pf_src_modport, 0); 405 parse_table_add(&pt, "SrcModid", PQ_DFL|PQ_INT, 0, &pf_src_modid, 0); 406 parse_table_add(&pt, "Reason", PQ_DFL|PQ_MULTI, 0, &pf_reason, 407 reason_str); 408 parse_table_add(&pt, "FPRule", PQ_DFL|PQ_INT, 0, &pf_fp_rule, 0); 409 parse_table_add(&pt, "PktOffset", PQ_DFL|PQ_HEX, 0, &pkt_offset, 0); 410 parse_table_add(&pt, "PktByte0", PQ_DFL|PQ_INT, 0, &pkt_data[0], 0); 411 parse_table_add(&pt, "PktByte1", PQ_DFL|PQ_INT, 0, &pkt_data[1], 0); 412 parse_table_add(&pt, "PktByte2", PQ_DFL|PQ_INT, 0, &pkt_data[2], 0); 413 parse_table_add(&pt, "PktByte3", PQ_DFL|PQ_INT, 0, &pkt_data[3], 0); 414 parse_table_add(&pt, "PktByte4", PQ_DFL|PQ_INT, 0, &pkt_data[4], 0); 415 parse_table_add(&pt, "PktByte5", PQ_DFL|PQ_INT, 0, &pkt_data[5], 0); 416 parse_table_add(&pt, "PktByte6", PQ_DFL|PQ_INT, 0, &pkt_data[6], 0); 417 parse_table_add(&pt, "PktByte7", PQ_DFL|PQ_INT, 0, &pkt_data[7], 0); 418 parse_table_add(&pt, "DestProto", PQ_DFL|PQ_INT, 0, &pf_dest_proto, 0); 419 parse_table_add(&pt, "MirrorProto", PQ_DFL|PQ_INT, 0, &pf_mirror_proto, 0); 420 parse_table_add(&pt, "CBData", PQ_DFL|PQ_INT, 0, &pf_cb_user_data, 0); 421 if (parse_arg_eq(args, &pt) < 0) { 422 parse_arg_eq_done(&pt); 423 return CMD_USAGE; 424 } 425 bcm_knet_filter_t_init(&filter); 426 filter.type = BCM_KNET_FILTER_T_RX_PKT; 427 if (pf_desc) { 428 sal_strncpy(filter.desc, pf_desc, sizeof(filter.desc) - 1); 429 } 430 parse_arg_eq_done(&pt); 431 432 filter.priority = pf_prio; 433 filter.dest_type = pf_dest_type; 434 filter.dest_id = pf_dest_id; 435 filter.dest_proto = pf_dest_proto; 436 /* 437 * Specifying a mirror ID implies mirroring to another 438 * netif. Enabling mirroring while leaving mirror ID at 439 * zero implies mirroring to the Rx API. 440 */ 441 if (pf_mirror_id) { 442 pf_mirror = 1; 443 } 444 if (pf_dest_type == BCM_KNET_DEST_T_NETIF && pf_mirror) { 445 filter.mirror_type = BCM_KNET_DEST_T_BCM_RX_API; 446 if (pf_mirror_id) { 447 filter.mirror_type = BCM_KNET_DEST_T_NETIF; 448 filter.mirror_id = pf_mirror_id; 449 filter.mirror_proto = pf_mirror_proto; 450 } 451 } 452 if (pf_striptag) { 453 filter.flags |= BCM_KNET_FILTER_F_STRIP_TAG; 454 } 455 filter.cb_user_data = pf_cb_user_data; 456 if (pf_vlan >= 0) { 457 filter.m_vlan = pf_vlan; 458 filter.match_flags |= BCM_KNET_FILTER_M_VLAN; 459 } 460 if (pf_ingport >= 0) { 461 filter.m_ingport = pf_ingport; 462 filter.match_flags |= BCM_KNET_FILTER_M_INGPORT; 463 } 464 if (pf_src_modport >= 0) { 465 filter.m_src_modport = pf_src_modport; 466 filter.match_flags |= BCM_KNET_FILTER_M_SRC_MODPORT; 467 } 468 if (pf_src_modid >= 0) { 469 filter.m_src_modid = pf_src_modid; 470 filter.match_flags |= BCM_KNET_FILTER_M_SRC_MODID; 471 } 472 if (pf_reason != bcmRxReasonInvalid) { 473 BCM_RX_REASON_SET(filter.m_reason, pf_reason); 474 filter.match_flags |= BCM_KNET_FILTER_M_REASON; 475 } 476 if (pf_fp_rule >= 0) { 477 filter.m_fp_rule = pf_fp_rule; 478 filter.match_flags |= BCM_KNET_FILTER_M_FP_RULE; 479 } 480 for (idx = 0; idx < COUNTOF(pkt_data); idx++) { 481 pdx = pkt_offset + idx; 482 if (pdx >= sizeof(filter.m_raw_data)) { 483 cli_out("PktOffset too large - max is %d\n", 484 (int)sizeof(filter.m_raw_data) - COUNTOF(pkt_data)); 485 return CMD_USAGE; 486 } 487 if (pkt_data[idx] >= 0) { 488 filter.raw_size = pdx + 1; 489 filter.match_flags |= BCM_KNET_FILTER_M_RAW; 490 filter.m_raw_data[pdx] = (uint8)pkt_data[idx]; 491 filter.m_raw_mask[pdx] = 0xff; 492 } 493 } 494 if ((rv = bcm_knet_filter_create(unit, &filter)) < 0) { 495 cli_out("Error creating packet filter: %s\n", 496 bcm_errmsg(rv)); 497 return CMD_FAIL; 498 } 499 _show_filter(unit, &filter); 500 } else if (sal_strcasecmp(subcmd, "destroy") == 0) { 501 if (!ARG_CNT(args) || !isint(ARG_CUR(args))) { 502 return(CMD_USAGE); 503 } 504 pf_id = parse_integer(ARG_GET(args)); 505 if ((rv = bcm_knet_filter_destroy(unit, pf_id)) < 0) { 506 cli_out("Error destroying packet filter: %s\n", 507 bcm_errmsg(rv)); 508 return CMD_FAIL; 509 } 510 } else if (sal_strcasecmp(subcmd, "show") == 0) { 511 count = 0; 512 if (bcm_knet_filter_traverse(unit, _trav_filter, &count) < 0) { 513 return CMD_FAIL; 514 } 515 if (count == 0) { 516 cli_out("<no filters>\n"); 517 } 518 } else { 519 cli_out("Subcommand not found: %s\n", subcmd); 520 return CMD_USAGE; 521 } 522 } else if (sal_strcasecmp(subcmd, "debugpkt") == 0) { 523 if ((subcmd = ARG_GET(args)) == NULL) { 524 cli_out("Requires string argument\n"); 525 return CMD_USAGE; 526 } 527 528 if (sal_strcasecmp(subcmd, "on") == 0) { 529 if (soc_knet_debug_pkt_set(unit, TRUE) < 0) { 530 return CMD_FAIL; 531 } 532 } else if (sal_strcasecmp(subcmd, "off") == 0) { 533 if (soc_knet_debug_pkt_set(unit, FALSE) < 0) { 534 return CMD_FAIL; 535 } 536 } else if (sal_strcasecmp(subcmd, "show") == 0) { 537 int value = 0; 538 if (soc_knet_debug_pkt_get(unit, &value) < 0) { 539 return CMD_FAIL; 540 } 541 542 if (TRUE == value) { 543 cli_out("Knet debugging packet function is on!\n"); 544 } else { 545 cli_out("Knet debugging packet function is off!\n"); 546 } 547 } else { 548 cli_out("Subcommand not found: %s\n", subcmd); 549 return CMD_USAGE; 550 } 551 } else { 552 cli_out("Subcommand not found: %s\n", subcmd); 553 return CMD_USAGE; 554 } 555 return CMD_OK; 556 } 557 558 #else 559 int _knetctrl_not_empty; 560 #endif