X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=app%2Ftest-pmd%2Ftestpmd.c;h=d1041afa507c7530c248c1d3fedd72ce88f2ce0a;hb=refs%2Fchanges%2F26%2F6726%2F1;hp=a0332c2651e64de8f741ddb5e02d722353aa0e90;hpb=ce3d555e43e3795b5d9507fcfc76b7a0a92fd0d6;p=deb_dpdk.git diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c index a0332c26..d1041afa 100644 --- a/app/test-pmd/testpmd.c +++ b/app/test-pmd/testpmd.c @@ -1,7 +1,7 @@ /*- * BSD LICENSE * - * Copyright(c) 2010-2016 Intel Corporation. All rights reserved. + * Copyright(c) 2010-2017 Intel Corporation. All rights reserved. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,14 @@ #ifdef RTE_LIBRTE_PDUMP #include #endif +#include +#include +#ifdef RTE_LIBRTE_BITRATE +#include +#endif +#ifdef RTE_LIBRTE_LATENCY_STATS +#include +#endif #include "testpmd.h" @@ -86,6 +95,7 @@ uint16_t verbose_level = 0; /**< Silent by default. */ /* use master core for command line ? */ uint8_t interactive = 0; uint8_t auto_start = 0; +char cmdline_filename[PATH_MAX] = {0}; /* * NUMA support configuration. @@ -94,7 +104,7 @@ uint8_t auto_start = 0; * probed ports among the CPU sockets 0 and 1. * Otherwise, all memory is allocated from CPU socket 0. */ -uint8_t numa_support = 0; /**< No numa support by default */ +uint8_t numa_support = 1; /**< numa enabled by default */ /* * In UMA mode,all memory is allocated from socket 0 if --socket-num is @@ -110,7 +120,7 @@ uint8_t mp_anon = 0; /* * Record the Ethernet address of peer target ports to which packets are * forwarded. - * Must be instanciated with the ethernet addresses of peer traffic generator + * Must be instantiated with the ethernet addresses of peer traffic generator * ports. */ struct ether_addr peer_eth_addrs[RTE_MAX_ETHPORTS]; @@ -261,6 +271,27 @@ uint8_t no_flush_rx = 0; /* flush by default */ */ uint8_t no_link_check = 0; /* check by default */ +/* + * Enable link status change notification + */ +uint8_t lsc_interrupt = 1; /* enabled by default */ + +/* + * Enable device removal notification. + */ +uint8_t rmv_interrupt = 1; /* enabled by default */ + +/* + * Display or mask ether events + * Default to all events except VF_MBOX + */ +uint32_t event_print_mask = (UINT32_C(1) << RTE_ETH_EVENT_UNKNOWN) | + (UINT32_C(1) << RTE_ETH_EVENT_INTR_LSC) | + (UINT32_C(1) << RTE_ETH_EVENT_QUEUE_STATE) | + (UINT32_C(1) << RTE_ETH_EVENT_INTR_RESET) | + (UINT32_C(1) << RTE_ETH_EVENT_MACSEC) | + (UINT32_C(1) << RTE_ETH_EVENT_INTR_RMV); + /* * NIC bypass mode configuration options. */ @@ -271,6 +302,20 @@ uint32_t bypass_timeout = RTE_BYPASS_TMT_OFF; #endif +#ifdef RTE_LIBRTE_LATENCY_STATS + +/* + * Set when latency stats is enabled in the commandline + */ +uint8_t latencystats_enabled; + +/* + * Lcore ID to serive latency statistics. + */ +lcoreid_t latencystats_lcore_id = -1; + +#endif + /* * Ethernet device configuration. */ @@ -283,7 +328,7 @@ struct rte_eth_rxmode rx_mode = { .hw_vlan_strip = 1, /**< VLAN strip enabled. */ .hw_vlan_extend = 0, /**< Extended VLAN disabled. */ .jumbo_frame = 0, /**< Jumbo Frame Support disabled. */ - .hw_strip_crc = 0, /**< CRC stripping by hardware disabled. */ + .hw_strip_crc = 1, /**< CRC stripping by hardware enabled. */ }; struct rte_fdir_conf fdir_conf = { @@ -320,11 +365,22 @@ struct queue_stats_mappings *rx_queue_stats_mappings = rx_queue_stats_mappings_a uint16_t nb_tx_queue_stats_mappings = 0; uint16_t nb_rx_queue_stats_mappings = 0; -unsigned max_socket = 0; +unsigned int num_sockets = 0; +unsigned int socket_ids[RTE_MAX_NUMA_NODES]; + +#ifdef RTE_LIBRTE_BITRATE +/* Bitrate statistics */ +struct rte_stats_bitrates *bitrate_data; +lcoreid_t bitrate_lcore_id; +uint8_t bitrate_enabled; +#endif /* Forward function declarations */ static void map_port_queue_stats_mapping_registers(uint8_t pi, struct rte_port *port); static void check_all_ports_link_status(uint32_t port_mask); +static void eth_event_callback(uint8_t port_id, + enum rte_eth_event_type type, + void *param); /* * Check if all the ports are started. @@ -333,17 +389,19 @@ static void check_all_ports_link_status(uint32_t port_mask); static int all_ports_started(void); /* - * Find next enabled port + * Helper function to check if socket is allready discovered. + * If yes, return positive value. If not, return zero. */ -portid_t -find_next_port(portid_t p, struct rte_port *ports, int size) +int +new_socket_id(unsigned int socket_id) { - if (ports == NULL) - rte_exit(-EINVAL, "failed to find a next port id\n"); + unsigned int i; - while ((p < size) && (ports[p].enabled == 0)) - p++; - return p; + for (i = 0; i < num_sockets; i++) { + if (socket_ids[i] == socket_id) + return 0; + } + return 1; } /* @@ -358,11 +416,14 @@ set_default_fwd_lcores_config(void) nb_lc = 0; for (i = 0; i < RTE_MAX_LCORE; i++) { - sock_num = rte_lcore_to_socket_id(i) + 1; - if (sock_num > max_socket) { - if (sock_num > RTE_MAX_NUMA_NODES) - rte_exit(EXIT_FAILURE, "Total sockets greater than %u\n", RTE_MAX_NUMA_NODES); - max_socket = sock_num; + sock_num = rte_lcore_to_socket_id(i); + if (new_socket_id(sock_num)) { + if (num_sockets >= RTE_MAX_NUMA_NODES) { + rte_exit(EXIT_FAILURE, + "Total sockets greater than %u\n", + RTE_MAX_NUMA_NODES); + } + socket_ids[num_sockets++] = sock_num; } if (!rte_lcore_is_enabled(i)) continue; @@ -476,7 +537,7 @@ check_socket_id(const unsigned int socket_id) { static int warning_once = 0; - if (socket_id >= max_socket) { + if (new_socket_id(socket_id)) { if (!warning_once && numa_support) printf("Warning: NUMA should be configured manually by" " using --port-numa-config and" @@ -499,6 +560,13 @@ init_config(void) uint8_t port_per_socket[RTE_MAX_NUMA_NODES]; memset(port_per_socket,0,RTE_MAX_NUMA_NODES); + + if (numa_support) { + memset(port_numa, NUMA_NO_CONFIG, RTE_MAX_ETHPORTS); + memset(rxring_numa, NUMA_NO_CONFIG, RTE_MAX_ETHPORTS); + memset(txring_numa, NUMA_NO_CONFIG, RTE_MAX_ETHPORTS); + } + /* Configuration of logical cores. */ fwd_lcores = rte_zmalloc("testpmd: fwd_lcores", sizeof(struct fwd_lcore *) * nb_lcores, @@ -518,35 +586,7 @@ init_config(void) fwd_lcores[lc_id]->cpuid_idx = lc_id; } - /* - * Create pools of mbuf. - * If NUMA support is disabled, create a single pool of mbuf in - * socket 0 memory by default. - * Otherwise, create a pool of mbuf in the memory of sockets 0 and 1. - * - * Use the maximum value of nb_rxd and nb_txd here, then nb_rxd and - * nb_txd can be configured at run time. - */ - if (param_total_num_mbufs) - nb_mbuf_per_pool = param_total_num_mbufs; - else { - nb_mbuf_per_pool = RTE_TEST_RX_DESC_MAX + (nb_lcores * mb_mempool_cache) - + RTE_TEST_TX_DESC_MAX + MAX_PKT_BURST; - - if (!numa_support) - nb_mbuf_per_pool = - (nb_mbuf_per_pool * RTE_MAX_ETHPORTS); - } - - if (!numa_support) { - if (socket_num == UMA_NO_CONFIG) - mbuf_pool_create(mbuf_data_size, nb_mbuf_per_pool, 0); - else - mbuf_pool_create(mbuf_data_size, nb_mbuf_per_pool, - socket_num); - } - - FOREACH_PORT(pid, ports) { + RTE_ETH_FOREACH_DEV(pid) { port = &ports[pid]; rte_eth_dev_info_get(pid, &port->dev_info); @@ -568,20 +608,38 @@ init_config(void) port->need_reconfig_queues = 1; } + /* + * Create pools of mbuf. + * If NUMA support is disabled, create a single pool of mbuf in + * socket 0 memory by default. + * Otherwise, create a pool of mbuf in the memory of sockets 0 and 1. + * + * Use the maximum value of nb_rxd and nb_txd here, then nb_rxd and + * nb_txd can be configured at run time. + */ + if (param_total_num_mbufs) + nb_mbuf_per_pool = param_total_num_mbufs; + else { + nb_mbuf_per_pool = RTE_TEST_RX_DESC_MAX + + (nb_lcores * mb_mempool_cache) + + RTE_TEST_TX_DESC_MAX + MAX_PKT_BURST; + nb_mbuf_per_pool *= RTE_MAX_ETHPORTS; + } + if (numa_support) { uint8_t i; - unsigned int nb_mbuf; - - if (param_total_num_mbufs) - nb_mbuf_per_pool = nb_mbuf_per_pool/nb_ports; - for (i = 0; i < max_socket; i++) { - nb_mbuf = (nb_mbuf_per_pool * RTE_MAX_ETHPORTS); - if (nb_mbuf) - mbuf_pool_create(mbuf_data_size, - nb_mbuf,i); - } + for (i = 0; i < num_sockets; i++) + mbuf_pool_create(mbuf_data_size, nb_mbuf_per_pool, + socket_ids[i]); + } else { + if (socket_num == UMA_NO_CONFIG) + mbuf_pool_create(mbuf_data_size, nb_mbuf_per_pool, 0); + else + mbuf_pool_create(mbuf_data_size, nb_mbuf_per_pool, + socket_num); } + init_port_config(); /* @@ -631,7 +689,7 @@ init_fwd_streams(void) queueid_t q; /* set socket id according to numa or not */ - FOREACH_PORT(pid, ports) { + RTE_ETH_FOREACH_DEV(pid) { port = &ports[pid]; if (nb_rxq > port->dev_info.max_rx_queues) { printf("Fail: nb_rxq(%d) is greater than " @@ -921,12 +979,42 @@ run_pkt_fwd_on_lcore(struct fwd_lcore *fc, packet_fwd_t pkt_fwd) struct fwd_stream **fsm; streamid_t nb_fs; streamid_t sm_id; - +#ifdef RTE_LIBRTE_BITRATE + uint64_t tics_per_1sec; + uint64_t tics_datum; + uint64_t tics_current; + uint8_t idx_port, cnt_ports; + + cnt_ports = rte_eth_dev_count(); + tics_datum = rte_rdtsc(); + tics_per_1sec = rte_get_timer_hz(); +#endif fsm = &fwd_streams[fc->stream_idx]; nb_fs = fc->stream_nb; do { for (sm_id = 0; sm_id < nb_fs; sm_id++) (*pkt_fwd)(fsm[sm_id]); +#ifdef RTE_LIBRTE_BITRATE + if (bitrate_enabled != 0 && + bitrate_lcore_id == rte_lcore_id()) { + tics_current = rte_rdtsc(); + if (tics_current - tics_datum >= tics_per_1sec) { + /* Periodic bitrate calculation */ + for (idx_port = 0; + idx_port < cnt_ports; + idx_port++) + rte_stats_bitrate_calc(bitrate_data, + idx_port); + tics_datum = tics_current; + } + } +#endif +#ifdef RTE_LIBRTE_LATENCY_STATS + if (latencystats_enabled != 0 && + latencystats_lcore_id == rte_lcore_id()) + rte_latencystats_update(); +#endif + } while (! fc->stopped); } @@ -1252,7 +1340,7 @@ all_ports_started(void) portid_t pi; struct rte_port *port; - FOREACH_PORT(pi, ports) { + RTE_ETH_FOREACH_DEV(pi) { port = &ports[pi]; /* Check if there is a port which is not started */ if ((port->port_status != RTE_PORT_STARTED) && @@ -1270,7 +1358,7 @@ all_ports_stopped(void) portid_t pi; struct rte_port *port; - FOREACH_PORT(pi, ports) { + RTE_ETH_FOREACH_DEV(pi) { port = &ports[pi]; if ((port->port_status != RTE_PORT_STOPPED) && (port->slave_flag == 0)) @@ -1312,13 +1400,14 @@ start_port(portid_t pid) queueid_t qi; struct rte_port *port; struct ether_addr mac_addr; + enum rte_eth_event_type event_type; if (port_id_is_invalid(pid, ENABLED_WARN)) return 0; if(dcb_config) dcb_test = 1; - FOREACH_PORT(pi, ports) { + RTE_ETH_FOREACH_DEV(pi) { if (pid != pi && pid != (portid_t)RTE_PORT_ALL) continue; @@ -1423,6 +1512,21 @@ start_port(portid_t pid) return -1; } } + + for (event_type = RTE_ETH_EVENT_UNKNOWN; + event_type < RTE_ETH_EVENT_MAX; + event_type++) { + diag = rte_eth_dev_callback_register(pi, + event_type, + eth_event_callback, + NULL); + if (diag) { + printf("Failed to setup even callback for event %d\n", + event_type); + return -1; + } + } + /* start port */ if (rte_eth_dev_start(pi) < 0) { printf("Fail to start port %d\n", pi); @@ -1475,7 +1579,7 @@ stop_port(portid_t pid) printf("Stopping ports...\n"); - FOREACH_PORT(pi, ports) { + RTE_ETH_FOREACH_DEV(pi) { if (pid != pi && pid != (portid_t)RTE_PORT_ALL) continue; @@ -1518,7 +1622,7 @@ close_port(portid_t pid) printf("Closing ports...\n"); - FOREACH_PORT(pi, ports) { + RTE_ETH_FOREACH_DEV(pi) { if (pid != pi && pid != (portid_t)RTE_PORT_ALL) continue; @@ -1545,6 +1649,8 @@ close_port(portid_t pid) continue; } + if (port->flow_list) + port_flow_flush(pi); rte_eth_dev_close(pi); if (rte_atomic16_cmpset(&(port->port_status), @@ -1571,7 +1677,6 @@ attach_port(char *identifier) if (rte_eth_dev_attach(identifier, &pi)) return; - ports[pi].enabled = 1; socket_id = (unsigned)rte_eth_dev_socket_id(pi); /* if socket_id is invalid, set to 0 */ if (check_socket_id(socket_id) < 0) @@ -1599,10 +1704,12 @@ detach_port(uint8_t port_id) return; } + if (ports[port_id].flow_list) + port_flow_flush(port_id); + if (rte_eth_dev_detach(port_id, name)) return; - ports[port_id].enabled = 0; nb_ports = rte_eth_dev_count(); printf("Port '%s' is detached. Now total ports is %d\n", @@ -1621,7 +1728,7 @@ pmd_test_exit(void) if (ports != NULL) { no_link_check = 1; - FOREACH_PORT(pt_id, ports) { + RTE_ETH_FOREACH_DEV(pt_id) { printf("\nShutting down port %d...\n", pt_id); fflush(stdout); stop_port(pt_id); @@ -1652,7 +1759,7 @@ check_all_ports_link_status(uint32_t port_mask) fflush(stdout); for (count = 0; count <= MAX_CHECK_TIME; count++) { all_ports_up = 1; - FOREACH_PORT(portid, ports) { + RTE_ETH_FOREACH_DEV(portid) { if ((port_mask & (1 << portid)) == 0) continue; memset(&link, 0, sizeof(link)); @@ -1689,6 +1796,70 @@ check_all_ports_link_status(uint32_t port_mask) if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) { print_flag = 1; } + + if (lsc_interrupt) + break; + } +} + +static void +rmv_event_callback(void *arg) +{ + struct rte_eth_dev *dev; + struct rte_devargs *da; + char name[32] = ""; + uint8_t port_id = (intptr_t)arg; + + RTE_ETH_VALID_PORTID_OR_RET(port_id); + dev = &rte_eth_devices[port_id]; + da = dev->device->devargs; + + stop_port(port_id); + close_port(port_id); + if (da->type == RTE_DEVTYPE_VIRTUAL) + snprintf(name, sizeof(name), "%s", da->virt.drv_name); + else if (da->type == RTE_DEVTYPE_WHITELISTED_PCI) + rte_pci_device_name(&da->pci.addr, name, sizeof(name)); + printf("removing device %s\n", name); + rte_eal_dev_detach(name); + dev->state = RTE_ETH_DEV_UNUSED; +} + +/* This function is used by the interrupt thread */ +static void +eth_event_callback(uint8_t port_id, enum rte_eth_event_type type, void *param) +{ + static const char * const event_desc[] = { + [RTE_ETH_EVENT_UNKNOWN] = "Unknown", + [RTE_ETH_EVENT_INTR_LSC] = "LSC", + [RTE_ETH_EVENT_QUEUE_STATE] = "Queue state", + [RTE_ETH_EVENT_INTR_RESET] = "Interrupt reset", + [RTE_ETH_EVENT_VF_MBOX] = "VF Mbox", + [RTE_ETH_EVENT_MACSEC] = "MACsec", + [RTE_ETH_EVENT_INTR_RMV] = "device removal", + [RTE_ETH_EVENT_MAX] = NULL, + }; + + RTE_SET_USED(param); + + if (type >= RTE_ETH_EVENT_MAX) { + fprintf(stderr, "\nPort %" PRIu8 ": %s called upon invalid event %d\n", + port_id, __func__, type); + fflush(stderr); + } else if (event_print_mask & (UINT32_C(1) << type)) { + printf("\nPort %" PRIu8 ": %s event\n", port_id, + event_desc[type]); + fflush(stdout); + } + + switch (type) { + case RTE_ETH_EVENT_INTR_RMV: + if (rte_eal_alarm_set(100000, + rmv_event_callback, (void *)(intptr_t)port_id)) + fprintf(stderr, "Could not set up deferred device removal\n"); + break; + default: + break; } } @@ -1817,7 +1988,7 @@ init_port_config(void) portid_t pid; struct rte_port *port; - FOREACH_PORT(pid, ports) { + RTE_ETH_FOREACH_DEV(pid) { port = &ports[pid]; port->dev_conf.rxmode = rx_mode; port->dev_conf.fdir_conf = fdir_conf; @@ -1829,24 +2000,13 @@ init_port_config(void) port->dev_conf.rx_adv_conf.rss_conf.rss_hf = 0; } - if (port->dcb_flag == 0 && port->dev_info.max_vfs == 0) { + if (port->dcb_flag == 0) { if( port->dev_conf.rx_adv_conf.rss_conf.rss_hf != 0) port->dev_conf.rxmode.mq_mode = ETH_MQ_RX_RSS; else port->dev_conf.rxmode.mq_mode = ETH_MQ_RX_NONE; } - if (port->dev_info.max_vfs != 0) { - if (port->dev_conf.rx_adv_conf.rss_conf.rss_hf != 0) - port->dev_conf.rxmode.mq_mode = - ETH_MQ_RX_VMDQ_RSS; - else - port->dev_conf.rxmode.mq_mode = - ETH_MQ_RX_NONE; - - port->dev_conf.txmode.mq_mode = ETH_MQ_TX_NONE; - } - rxtx_port_config(port); rte_eth_macaddr_get(pid, &port->eth_addr); @@ -1855,6 +2015,15 @@ init_port_config(void) #ifdef RTE_NIC_BYPASS rte_eth_dev_bypass_init(pid); #endif + + if (lsc_interrupt && + (rte_eth_devices[pid].data->dev_flags & + RTE_ETH_DEV_INTR_LSC)) + port->dev_conf.intr_conf.lsc = 1; + if (rmv_interrupt && + (rte_eth_devices[pid].data->dev_flags & + RTE_ETH_DEV_INTR_RMV)) + port->dev_conf.intr_conf.rmv = 1; } } @@ -1907,7 +2076,7 @@ get_eth_dcb_conf(struct rte_eth_conf *eth_conf, struct rte_eth_vmdq_dcb_tx_conf *vmdq_tx_conf = ð_conf->tx_adv_conf.vmdq_dcb_tx_conf; - /* VMDQ+DCB RX and TX configrations */ + /* VMDQ+DCB RX and TX configurations */ vmdq_rx_conf->enable_default_pool = 0; vmdq_rx_conf->default_pool = 0; vmdq_rx_conf->nb_queue_pools = @@ -1938,9 +2107,9 @@ get_eth_dcb_conf(struct rte_eth_conf *eth_conf, rx_conf->nb_tcs = num_tcs; tx_conf->nb_tcs = num_tcs; - for (i = 0; i < num_tcs; i++) { - rx_conf->dcb_tc[i] = i; - tx_conf->dcb_tc[i] = i; + for (i = 0; i < ETH_DCB_NUM_USER_PRIORITIES; i++) { + rx_conf->dcb_tc[i] = i % num_tcs; + tx_conf->dcb_tc[i] = i % num_tcs; } eth_conf->rxmode.mq_mode = ETH_MQ_RX_DCB_RSS; eth_conf->rx_adv_conf.rss_conf.rss_hf = rss_hf; @@ -2041,8 +2210,6 @@ init_port_dcb_config(portid_t pid, static void init_port(void) { - portid_t pid; - /* Configuration of Ethernet ports. */ ports = rte_zmalloc("testpmd: ports", sizeof(struct rte_port) * RTE_MAX_ETHPORTS, @@ -2052,10 +2219,6 @@ init_port(void) "rte_zmalloc(%d struct rte_port) failed\n", RTE_MAX_ETHPORTS); } - - /* enabled allocated ports */ - for (pid = 0; pid < nb_ports; pid++) - ports[pid].enabled = 1; } static void @@ -2074,6 +2237,9 @@ signal_handler(int signum) #ifdef RTE_LIBRTE_PDUMP /* uninitialize packet capture framework */ rte_pdump_uninit(); +#endif +#ifdef RTE_LIBRTE_LATENCY_STATS + rte_latencystats_uninit(); #endif force_quit(); /* exit with the expected status */ @@ -2112,6 +2278,14 @@ main(int argc, char** argv) rte_panic("Empty set of forwarding logical cores - check the " "core mask supplied in the command parameters\n"); + /* Bitrate/latency stats disabled by default */ +#ifdef RTE_LIBRTE_BITRATE + bitrate_enabled = 0; +#endif +#ifdef RTE_LIBRTE_LATENCY_STATS + latencystats_enabled = 0; +#endif + argc -= diag; argv += diag; if (argc > 1) @@ -2130,16 +2304,45 @@ main(int argc, char** argv) rte_exit(EXIT_FAILURE, "Start ports failed\n"); /* set all ports to promiscuous mode by default */ - FOREACH_PORT(port_id, ports) + RTE_ETH_FOREACH_DEV(port_id) rte_eth_promiscuous_enable(port_id); + /* Init metrics library */ + rte_metrics_init(rte_socket_id()); + +#ifdef RTE_LIBRTE_LATENCY_STATS + if (latencystats_enabled != 0) { + int ret = rte_latencystats_init(1, NULL); + if (ret) + printf("Warning: latencystats init()" + " returned error %d\n", ret); + printf("Latencystats running on lcore %d\n", + latencystats_lcore_id); + } +#endif + + /* Setup bitrate stats */ +#ifdef RTE_LIBRTE_BITRATE + if (bitrate_enabled != 0) { + bitrate_data = rte_stats_bitrate_create(); + if (bitrate_data == NULL) + rte_exit(EXIT_FAILURE, + "Could not allocate bitrate data.\n"); + rte_stats_bitrate_reg(bitrate_data); + } +#endif + #ifdef RTE_LIBRTE_CMDLINE + if (strlen(cmdline_filename) != 0) + cmdline_read_from_file(cmdline_filename); + if (interactive == 1) { if (auto_start) { printf("Start automatic packet forwarding\n"); start_packet_forwarding(0); } prompt(); + pmd_test_exit(); } else #endif {