From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from EUR03-AM5-obe.outbound.protection.outlook.com (mail-eopbgr30069.outbound.protection.outlook.com [40.107.3.69]) by dpdk.org (Postfix) with ESMTP id 46C081B83B for ; Thu, 12 Apr 2018 08:09:06 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nxp.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version; bh=oZtMtd3uknaPxNc66Ri7Qt6XJ2ihXOaI2QYuUfg5qv0=; b=aCmksdm5qqTZJpON0D9zqi/WXAGYpyfciL9hvF7nA9wyl/nFFk+2zNra+3R6+ChYUVzJEfoHMm5pC/9gvFxAeV1b2Z026VXm4TojHU2sdE687LYhjsZ5Hm9etIkrpTDHeVfB0OpWJy3GcUe/in/OzFKDcjPMdcfZxJLto2NGuFk= Received: from AM5PR0401MB2660.eurprd04.prod.outlook.com (10.169.245.151) by AM5PR0401MB2595.eurprd04.prod.outlook.com (10.169.245.22) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.675.11; Thu, 12 Apr 2018 06:09:04 +0000 Received: from AM5PR0401MB2660.eurprd04.prod.outlook.com ([fe80::99c2:b950:282d:9a8]) by AM5PR0401MB2660.eurprd04.prod.outlook.com ([fe80::99c2:b950:282d:9a8%11]) with mapi id 15.20.0675.009; Thu, 12 Apr 2018 06:09:04 +0000 From: Sunil Kumar Kori To: "dev@dpdk.org" CC: Hemant Agrawal , Sunil Kumar Kori Thread-Topic: [PATCH] examples/l3fwd: adding event queue support Thread-Index: AQHTv4isYTYrrSxbDEq9ta/Ju9vbnaP8ykWw Date: Thu, 12 Apr 2018 06:09:04 +0000 Message-ID: References: <20180319134520.28155-1-sunil.kori@nxp.com> In-Reply-To: <20180319134520.28155-1-sunil.kori@nxp.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [14.142.187.166] x-ms-publictraffictype: Email x-microsoft-exchange-diagnostics: 1; AM5PR0401MB2595; 7:DlhjIrvbdEVsffyNCJQBU4MogVbqRE+MJ93NedAsuxBnEnJjcTwaequicHGs6oDQnoBO73KpPiLEq1GVNrHkJUa4kBxd7u3aMNCvPv82i+RwcdutClUQ2oZg/Oij1MEN2xzlduO/oCWeD/9IoqyJTB6jNSAPd4RwqZ97zYEN6MXaVRQjzJsCUrRhPZurV0bQLdtvLYZ36Koal0I6YuUXtvO17SE9sOF5VP968lJ1d3ReWX7VnZNmEhdufwqj0+3X x-ms-exchange-antispam-srfa-diagnostics: SOS; x-ms-office365-filtering-ht: Tenant x-microsoft-antispam: UriScan:; BCL:0; PCL:0; RULEID:(7020095)(4652020)(5600026)(4534165)(7168020)(4627221)(201703031133081)(201702281549075)(48565401081)(2017052603328)(7153060)(7193020); SRVR:AM5PR0401MB2595; x-ms-traffictypediagnostic: AM5PR0401MB2595: authentication-results: spf=none (sender IP is ) smtp.mailfrom=sunil.kori@nxp.com; x-microsoft-antispam-prvs: x-exchange-antispam-report-test: UriScan:(185117386973197); x-exchange-antispam-report-cfa-test: BCL:0; PCL:0; RULEID:(8211001083)(6040522)(2401047)(8121501046)(5005006)(3231221)(944501327)(52105095)(3002001)(10201501046)(93006095)(93001095)(6055026)(6041310)(20161123562045)(20161123558120)(20161123560045)(20161123564045)(201703131423095)(201702281528075)(20161123555045)(201703061421075)(201703061406153)(6072148)(201708071742011); SRVR:AM5PR0401MB2595; BCL:0; PCL:0; RULEID:; SRVR:AM5PR0401MB2595; x-forefront-prvs: 06400060E1 x-forefront-antispam-report: SFV:NSPM; SFS:(10009020)(346002)(396003)(376002)(39380400002)(39860400002)(366004)(13464003)(199004)(189003)(4326008)(74316002)(8676002)(5660300001)(316002)(3660700001)(1730700003)(81166006)(81156014)(478600001)(68736007)(2900100001)(97736004)(5250100002)(66066001)(7696005)(99286004)(8936002)(2906002)(2501003)(14454004)(25786009)(3280700002)(86362001)(54906003)(106356001)(105586002)(6506007)(305945005)(53546011)(9686003)(76176011)(53936002)(33656002)(7736002)(229853002)(55236004)(55016002)(53946003)(59450400001)(6246003)(102836004)(16200700003)(11346002)(476003)(6116002)(3846002)(186003)(446003)(26005)(6916009)(5640700003)(486006)(6436002)(2351001)(559001)(579004)(569006); DIR:OUT; SFP:1101; SCL:1; SRVR:AM5PR0401MB2595; H:AM5PR0401MB2660.eurprd04.prod.outlook.com; FPR:; SPF:None; LANG:en; PTR:InfoNoRecords; A:1; MX:1; received-spf: None (protection.outlook.com: nxp.com does not designate permitted sender hosts) x-microsoft-antispam-message-info: YWoAnyXcKiKzZ5nS5/3sm+Khxx+h9CTHH/EP+0pMfWap4em93vynv6whkh5JWFEPCmb2WFsXI3W/1qLDjXi8l20YOKkeB5/Ugwnz5VfupU0lgfm3BC4FbAe4ozlJbWQZLGtNpIkG+PrMgbSV5JT3cewbXHvD2070VqTLFG/gvrfze3q7WtjNsdQw+8rY3vsd spamdiagnosticoutput: 1:99 spamdiagnosticmetadata: NSPM Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-MS-Office365-Filtering-Correlation-Id: 9c5c75e0-9d68-48f0-a205-08d5a03be517 X-OriginatorOrg: nxp.com X-MS-Exchange-CrossTenant-Network-Message-Id: 9c5c75e0-9d68-48f0-a205-08d5a03be517 X-MS-Exchange-CrossTenant-originalarrivaltime: 12 Apr 2018 06:09:04.8037 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 686ea1d3-bc2b-4c6f-a92c-d99c5c301635 X-MS-Exchange-Transport-CrossTenantHeadersStamped: AM5PR0401MB2595 Subject: Re: [dpdk-dev] [PATCH] examples/l3fwd: adding event queue support X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 12 Apr 2018 06:09:06 -0000 Gentle reminder to review the RFC. Regards Sunil Kumar -----Original Message----- From: Sunil Kumar Kori [mailto:sunil.kori@nxp.com]=20 Sent: Monday, March 19, 2018 7:15 PM To: dev@dpdk.org Cc: Sunil Kumar Kori ; Hemant Agrawal Subject: [PATCH] examples/l3fwd: adding event queue support This patch set to add the support for eventdev based queue mode support to = the l3fwd application. 1. Eventdev support with parallel queue 2. Eventdev support with atomic queue This patch adds - New command line parameter is added named as "dequeue-mode" which identifies dequeue method i.e. dequeue via eventdev or polling (default is polling) . If dequeue mode is via: a. eventdev: New parameters are added -e, -a, -l to cater eventdev config, adapter config and link configuration respectively. "--config" option will be invalid in this case. b. poll mode: It will work as of existing way and option for eventdev parameters(-e, -a, -l) will be invalid. - Functions are added in l3fwd_em.c and l3fwd_lpm.c for packet I/O operation The main purpose of this RFC is get comments on the approach. This is a *not tested* code. Signed-off-by: Sunil Kumar Kori --- examples/l3fwd/Makefile | 2 +- examples/l3fwd/l3fwd.h | 21 ++ examples/l3fwd/l3fwd_em.c | 100 ++++++++ examples/l3fwd/l3fwd_eventdev.c | 541 ++++++++++++++++++++++++++++++++++++= ++++ examples/l3fwd/l3fwd_eventdev.h | 85 +++++++ examples/l3fwd/l3fwd_lpm.c | 100 ++++++++ examples/l3fwd/main.c | 318 +++++++++++++++++++---- examples/l3fwd/meson.build | 2 +- 8 files changed, 1120 insertions(+), 49 deletions(-) create mode 100644 e= xamples/l3fwd/l3fwd_eventdev.c create mode 100644 examples/l3fwd/l3fwd_eve= ntdev.h diff --git a/examples/l3fwd/Makefile b/examples/l3fwd/Makefile index cccdd9= d..94ba537 100644 --- a/examples/l3fwd/Makefile +++ b/examples/l3fwd/Makefile @@ -5,7 +5,7 @@ APP =3D l3fwd =20 # all source are stored in SRCS-y -SRCS-y :=3D main.c l3fwd_lpm.c l3fwd_em.c +SRCS-y :=3D main.c l3fwd_lpm.c l3fwd_em.c l3fwd_eventdev.c =20 # Build using pkg-config variables if possible $(shell pkg-config --exist= s libdpdk) diff --git a/examples/l3fwd/l3fwd.h b/examples/l3fwd/l3fwd.h ind= ex c962dea..675abd1 100644 --- a/examples/l3fwd/l3fwd.h +++ b/examples/l3fwd/l3fwd.h @@ -69,6 +69,17 @@ struct lcore_conf { void *ipv6_lookup_struct; } __rte_cache_aligned; =20 +struct l3fwd_lkp_mode { + void (*setup)(int); + int (*check_ptype)(int); + rte_rx_callback_fn cb_parse_ptype; + int (*main_loop)(void *); + void* (*get_ipv4_lookup_struct)(int); + void* (*get_ipv6_lookup_struct)(int); +}; + +extern struct l3fwd_lkp_mode l3fwd_lkp; + extern volatile bool force_quit; =20 /* ethernet addresses of ports */ @@ -81,11 +92,16 @@ extern uint32_t enabled_port_mask; /* Used only in exact match mode. */ extern int ipv6; /**< ipv6 is false by default. */ extern uint32_t hash_e= ntry_number; +extern int promiscuous_on; +extern int numa_on; /**< NUMA is enabled by default. */ extern uint16_t=20 +nb_rxd; =20 extern xmm_t val_eth[RTE_MAX_ETHPORTS]; =20 extern struct lcore_conf lcore_conf[RTE_MAX_LCORE]; =20 +extern struct rte_mempool *pktmbuf_pool[]; + /* Send burst of packets on an output interface */ static inline int sen= d_burst(struct lcore_conf *qconf, uint16_t n, uint16_t port) @@ -208,5 +224= ,10 @@ lpm_get_ipv4_l3fwd_lookup_struct(const int socketid); =20 void * lpm_get_ipv6_l3fwd_lookup_struct(const int socketid); +int +prepare_ptype_parser(uint16_t portid, uint16_t queueid); + +void +check_all_ports_link_status(uint16_t port_num, uint32_t port_mask); =20 #endif /* __L3_FWD_H__ */ diff --git a/examples/l3fwd/l3fwd_em.c b/examples/l3fwd/l3fwd_em.c index 9d= c3b8c..199ff75 100644 --- a/examples/l3fwd/l3fwd_em.c +++ b/examples/l3fwd/l3fwd_em.c @@ -21,12 +21,14 @@ #include #include #include +#include #include #include #include #include =20 #include "l3fwd.h" +#include "l3fwd_eventdev.h" =20 #if defined(RTE_ARCH_X86) || defined(RTE_MACHINE_CPUFLAG_CRC32) #define EM_HASH_CRC 1 @@ -612,6 +614,101 @@ em_cb_parse_ptype(uint16_t port __rte_unused, uint16_= t queue __rte_unused, return nb_pkts; } =20 +/* main EM processing loop for eventdev*/ int +em_eventdev_main_loop(__attribute__((unused)) void *dummy) { + struct rte_event ev[MAX_PKT_BURST]; + struct rte_mbuf *pkts_burst[MAX_PKT_BURST]; + struct rte_event_port_conf event_port_conf; + unsigned int lcore_id; + uint64_t prev_tsc, diff_tsc, cur_tsc; + int i, nb_rx; + uint8_t queueid; + uint16_t portid, dequeue_len; + uint8_t event_port_id =3D INVALID_EVENDEV_ID; + struct lcore_conf *qconf; + const uint64_t drain_tsc =3D (rte_get_tsc_hz() + US_PER_S - 1) / + US_PER_S * BURST_TX_DRAIN_US; + + prev_tsc =3D 0; + + lcore_id =3D rte_lcore_id(); + qconf =3D &lcore_conf[lcore_id]; + + if (qconf->n_rx_queue =3D=3D 0) { + RTE_LOG(INFO, L3FWD, "lcore %u has nothing to do\n", lcore_id); + return 0; + } + + RTE_LOG(INFO, L3FWD, "entering main loop on lcore %u\n", lcore_id); + + for (i =3D 0; i < qconf->n_rx_queue; i++) { + + portid =3D qconf->rx_queue_list[i].port_id; + queueid =3D qconf->rx_queue_list[i].queue_id; + RTE_LOG(INFO, L3FWD, + " -- lcoreid=3D%u portid=3D%u rxqueueid=3D%hhu\n", + lcore_id, portid, queueid); + } + + for (i =3D 0; i < link_config.nb_links; i++) { + if (link_config.links[i].lcore_id =3D=3D lcore_id) + event_port_id =3D link_config.links[i].event_portid; + } + + rte_event_port_default_conf_get(event_devices[0].dev_id, event_port_id, + &event_port_conf); + dequeue_len =3D event_port_conf.dequeue_depth; + + while (!force_quit) { + + cur_tsc =3D rte_rdtsc(); + + /* + * TX burst queue drain + */ + diff_tsc =3D cur_tsc - prev_tsc; + if (unlikely(diff_tsc > drain_tsc)) { + + for (i =3D 0; i < qconf->n_tx_port; ++i) { + portid =3D qconf->tx_port_id[i]; + if (qconf->tx_mbufs[portid].len =3D=3D 0) + continue; + send_burst(qconf, + qconf->tx_mbufs[portid].len, + portid); + qconf->tx_mbufs[portid].len =3D 0; + } + + prev_tsc =3D cur_tsc; + } + + /* + * Read packet from event ports + */ + + nb_rx =3D rte_event_dequeue_burst(event_devices[0].dev_id, + event_port_id, + ev, dequeue_len, 0); + if (nb_rx =3D=3D 0) + continue; + + for (i =3D 0; i < nb_rx; ++i) { + pkts_burst[0] =3D ev[i].mbuf; + portid =3D ev[i].flow_id; +#if defined RTE_ARCH_X86 || defined RTE_MACHINE_CPUFLAG_NEON + l3fwd_em_send_packets(1, pkts_burst, portid, qconf); #else + l3fwd_em_no_opt_send_packets(1, pkts_burst, + portid, qconf); +#endif + } + } + + return 0; +} + /* main processing loop */ int em_main_loop(__attribute__((unused)) void *dummy) @@ -631,6 +728,9 @@ em_m= ain_loop(__attribute__((unused)) void *dummy) lcore_id =3D rte_lcore_id(); qconf =3D &lcore_conf[lcore_id]; =20 + if (lcore_dequeue_mode[lcore_id] =3D=3D EVENTDEV_DEQUEUE) + return em_eventdev_main_loop(dummy); + if (qconf->n_rx_queue =3D=3D 0) { RTE_LOG(INFO, L3FWD, "lcore %u has nothing to do\n", lcore_id); return 0; diff --git a/examples/l3fwd/l3fwd_eventdev.c b/examples/l3fwd/l3fwd_eventde= v.c new file mode 100644 index 0000000..f7d9b4c --- /dev/null +++ b/examples/l3fwd/l3fwd_eventdev.c @@ -0,0 +1,541 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "l3fwd.h" +#include "l3fwd_eventdev.h" + + +static struct eventdev_params eventdev_config[RTE_MAX_EVENTDEV_COUNT]; +static uint16_t nb_eventdev_params; +struct eventdev_info *event_devices; +static struct adapter_params rx_adapter_config; struct link_params=20 +link_config; enum dequeue_mode lcore_dequeue_mode[RTE_MAX_LCORE]; + + + +int +parse_eventdev_config(const char *evq_arg) { + char s[256]; + const char *p, *p0 =3D evq_arg; + char *end; + enum fieldnames { + FLD_EVENTDEV_ID =3D 0, + FLD_EVENT_QUEUE, + FLD_EVENT_PORT, + FLD_COUNT + }; + unsigned long int_fld[FLD_COUNT]; + char *str_fld[FLD_COUNT]; + int i; + unsigned int size; + + /*First set all eventdev_config to default*/ + for (i =3D 0; i < RTE_MAX_EVENTDEV_COUNT; i++) { + eventdev_config[i].num_eventqueue =3D 1; + eventdev_config[i].num_eventport =3D RTE_MAX_LCORE; + } + + nb_eventdev_params =3D 0; + + while ((p =3D strchr(p0, '(')) !=3D NULL) { + ++p; + if ((p0 =3D strchr(p, ')')) =3D=3D NULL) + return -1; + + size =3D p0 - p; + if (size >=3D sizeof(s)) + return -1; + + snprintf(s, sizeof(s), "%.*s", size, p); + if (rte_strsplit(s, sizeof(s), str_fld, FLD_COUNT, ',') !=3D + FLD_COUNT) + return -1; + + for (i =3D 0; i < FLD_COUNT; i++) { + errno =3D 0; + int_fld[i] =3D strtoul(str_fld[i], &end, 0); + if (errno !=3D 0 || end =3D=3D str_fld[i] || int_fld[i] > 255) + return -1; + } + + if (nb_eventdev_params >=3D RTE_MAX_EVENTDEV_COUNT) { + printf("exceeded max number of eventdev params: %hu\n", + nb_eventdev_params); + return -1; + } + + eventdev_config[nb_eventdev_params].num_eventqueue =3D + (uint8_t)int_fld[FLD_EVENT_QUEUE]; + eventdev_config[nb_eventdev_params].num_eventport =3D + (uint8_t)int_fld[FLD_EVENT_PORT]; + eventdev_config[nb_eventdev_params].eventdev_id =3D + (uint8_t)int_fld[FLD_EVENTDEV_ID]; + ++nb_eventdev_params; + } + + return 0; +} + +int +parse_adapter_config(const char *evq_arg) { + char s[256]; + const char *p, *p0 =3D evq_arg; + char *end; + enum fieldnames { + FLD_ETHDEV_ID =3D 0, + FLD_ETHDEV_QID, + FLD_EVENT_QID_MODE, + FLD_EVENTQ_ID, + FLD_EVENT_PRIO, + FLD_EVENT_DEVID, + FLD_COUNT + }; + unsigned long int_fld[FLD_COUNT]; + char *str_fld[FLD_COUNT]; + int i, index =3D 0, j =3D 0; + unsigned int size; + + index =3D rx_adapter_config.nb_rx_adapter; + + while ((p =3D strchr(p0, '(')) !=3D NULL) { + j =3D rx_adapter_config.config[index].nb_connections; + ++p; + if ((p0 =3D strchr(p, ')')) =3D=3D NULL) + return -1; + + size =3D p0 - p; + if (size >=3D sizeof(s)) + return -1; + + snprintf(s, sizeof(s), "%.*s", size, p); + if (rte_strsplit(s, sizeof(s), str_fld, FLD_COUNT, ',') !=3D + FLD_COUNT) + return -1; + + for (i =3D 0; i < FLD_COUNT; i++) { + errno =3D 0; + int_fld[i] =3D strtoul(str_fld[i], &end, 0); + if (errno !=3D 0 || end =3D=3D str_fld[i] || int_fld[i] > 255) + return -1; + } + + if (index >=3D RTE_MAX_EVENTDEV_COUNT) { + printf("exceeded max number of eventdev params: %hu\n", + rx_adapter_config.nb_rx_adapter); + return -1; + } + + rx_adapter_config.config[index].connections[j].ethdev_id =3D + (uint8_t)int_fld[FLD_ETHDEV_ID]; + rx_adapter_config.config[index].connections[j].ethdev_rx_qid =3D + (uint8_t)int_fld[FLD_ETHDEV_QID]; + rx_adapter_config.config[index].connections[j].ethdev_rx_qid_mode =3D + (uint8_t)int_fld[FLD_EVENT_QID_MODE]; + rx_adapter_config.config[index].connections[j].eventq_id =3D + (uint8_t)int_fld[FLD_EVENTQ_ID]; + rx_adapter_config.config[index].connections[j].event_prio =3D + (uint8_t)int_fld[FLD_EVENT_PRIO]; + rx_adapter_config.config[index].connections[j].eventdev_id =3D + (uint8_t)int_fld[FLD_EVENT_DEVID]; + rx_adapter_config.config[index].nb_connections++; + } + + rx_adapter_config.nb_rx_adapter++; + return 0; +} + +int +parse_link_config(const char *evq_arg) +{ + char s[256]; + const char *p, *p0 =3D evq_arg; + char *end; + enum fieldnames { + FLD_EVENT_PORTID =3D 0, + FLD_EVENT_QID, + FLD_EVENT_DEVID, + FLD_LCORE_ID, + FLD_COUNT + }; + unsigned long int_fld[FLD_COUNT]; + char *str_fld[FLD_COUNT]; + int i, index =3D 0; + unsigned int size; + + /*First set all adapter_config to default*/ + memset(&link_config, 0, sizeof(struct link_params)); + while ((p =3D strchr(p0, '(')) !=3D NULL) { + index =3D link_config.nb_links; + ++p; + if ((p0 =3D strchr(p, ')')) =3D=3D NULL) + return -1; + + size =3D p0 - p; + if (size >=3D sizeof(s)) + return -1; + + snprintf(s, sizeof(s), "%.*s", size, p); + if (rte_strsplit(s, sizeof(s), str_fld, FLD_COUNT, ',') !=3D + FLD_COUNT) + return -1; + + for (i =3D 0; i < FLD_COUNT; i++) { + errno =3D 0; + int_fld[i] =3D strtoul(str_fld[i], &end, 0); + if (errno !=3D 0 || end =3D=3D str_fld[i] || int_fld[i] > 255) + return -1; + } + + if (index >=3D RTE_MAX_EVENTDEV_COUNT) { + printf("exceeded max number of eventdev params: %hu\n", + link_config.nb_links); + return -1; + } + + link_config.links[index].event_portid =3D + (uint8_t)int_fld[FLD_EVENT_PORTID]; + link_config.links[index].eventq_id =3D + (uint8_t)int_fld[FLD_EVENT_QID]; + link_config.links[index].eventdev_id =3D + (uint8_t)int_fld[FLD_EVENT_DEVID]; + link_config.links[index].lcore_id =3D + (uint8_t)int_fld[FLD_LCORE_ID]; + lcore_dequeue_mode[link_config.links[index].lcore_id] =3D + EVENTDEV_DEQUEUE; + link_config.nb_links++; + } + + return 0; +} + +static int +eventdev_configure(void) +{ + int ret =3D -1; + uint8_t i, j; + void *ports, *queues; + struct rte_event_dev_config eventdev_conf =3D {0}; + struct rte_event_dev_info eventdev_def_conf =3D {0}; + struct rte_event_queue_conf eventq_conf =3D {0}; + struct rte_event_port_conf port_conf =3D {0}; + struct rte_event_eth_rx_adapter_queue_conf queue_conf =3D {0}; + + /*First allocate space for event device information*/ + event_devices =3D rte_zmalloc("event-dev", + sizeof(struct eventdev_info) * nb_eventdev_params, 0); + if (event_devices =3D=3D NULL) { + printf("Error in allocating memory for event devices\n"); + return ret; + } + + for (i =3D 0; i < nb_eventdev_params; i++) { + /*Now allocate space for event ports request from user*/ + ports =3D rte_zmalloc("event-ports", + sizeof(uint8_t) * eventdev_config[i].num_eventport, 0); + if (ports =3D=3D NULL) { + printf("Error in allocating memory for event ports\n"); + rte_free(event_devices); + return ret; + } + + event_devices[i].port =3D ports; + + /*Now allocate space for event queues request from user*/ + queues =3D rte_zmalloc("event-queues", + sizeof(uint8_t) * eventdev_config[i].num_eventqueue, 0); + if (queues =3D=3D NULL) { + printf("Error in allocating memory for event queues\n"); + rte_free(event_devices[i].port); + rte_free(event_devices); + return ret; + } + + event_devices[i].queue =3D queues; + event_devices[i].dev_id =3D eventdev_config[i].eventdev_id; + + /* get default values of eventdev*/ + memset(&eventdev_def_conf, 0, + sizeof(struct rte_event_dev_info)); + ret =3D rte_event_dev_info_get(event_devices[i].dev_id, + &eventdev_def_conf); + if (ret < 0) { + printf("Error in getting event device info, devid: %d\n", + event_devices[i].dev_id); + return ret; + } + + memset(&eventdev_conf, 0, sizeof(struct rte_event_dev_config)); + eventdev_conf.nb_events_limit =3D -1; + eventdev_conf.nb_event_queues =3D + eventdev_config[i].num_eventqueue; + eventdev_conf.nb_event_ports =3D + eventdev_config[i].num_eventport; + eventdev_conf.nb_event_queue_flows =3D + eventdev_def_conf.max_event_queue_flows; + eventdev_conf.nb_event_port_dequeue_depth =3D + eventdev_def_conf.max_event_port_dequeue_depth; + eventdev_conf.nb_event_port_enqueue_depth =3D + eventdev_def_conf.max_event_port_enqueue_depth; + + ret =3D rte_event_dev_configure(event_devices[i].dev_id, + &eventdev_conf); + if (ret < 0) { + printf("Error in configuring event device\n"); + return ret; + } + + memset(&eventq_conf, 0, sizeof(struct rte_event_queue_conf)); + eventq_conf.nb_atomic_flows =3D 1; + eventq_conf.schedule_type =3D RTE_SCHED_TYPE_ATOMIC; + for (j =3D 0; j < eventdev_config[i].num_eventqueue; j++) { + ret =3D rte_event_queue_setup(event_devices[i].dev_id, j, + &eventq_conf); + if (ret < 0) { + printf("Error in event queue setup\n"); + return ret; + } + event_devices[i].queue[j] =3D j; + } + + for (j =3D 0; j < eventdev_config[i].num_eventport; j++) { + ret =3D rte_event_port_setup(event_devices[i].dev_id, j, NULL); + if (ret < 0) { + printf("Error in event port setup\n"); + return ret; + } + event_devices[i].port[j] =3D j; + } + } + + for (i =3D 0; i < rx_adapter_config.nb_rx_adapter; i++) { + for (j =3D 0; j < rx_adapter_config.config[i].nb_connections; j++) { + ret =3D rte_event_eth_rx_adapter_create(j, + rx_adapter_config.config[i].connections[j].eventdev_id, + &port_conf); + if (ret < 0) { + printf("Error in event eth adapter creation\n"); + return ret; + } + rx_adapter_config.config[i].connections[j].adapter_id =3D + j; + } + } + + for (j =3D 0; j < link_config.nb_links; j++) { + ret =3D rte_event_port_link(link_config.links[j].eventdev_id, + link_config.links[j].event_portid, + &link_config.links[j].eventq_id, NULL, 1); + if (ret < 0) { + printf("Error in event port linking\n"); + return ret; + } + } + + queue_conf.rx_queue_flags =3D + RTE_EVENT_ETH_RX_ADAPTER_QUEUE_FLOW_ID_VALID; + + for (i =3D 0; i < rx_adapter_config.nb_rx_adapter; i++) { + for (j =3D 0; j < rx_adapter_config.config[i].nb_connections; j++) { + queue_conf.ev.queue_id =3D + rx_adapter_config.config[i].connections[j].eventq_id; + queue_conf.ev.priority =3D + rx_adapter_config.config[i].connections[j].event_prio; + queue_conf.ev.flow_id =3D + rx_adapter_config.config[i].connections[j].ethdev_id; + queue_conf.ev.sched_type =3D + rx_adapter_config.config[i].connections[j].ethdev_rx_qid_mode; + ret =3D rte_event_eth_rx_adapter_queue_add( + rx_adapter_config.config[i].connections[j].adapter_id, + rx_adapter_config.config[i].connections[j].ethdev_id, + rx_adapter_config.config[i].connections[j].ethdev_rx_qid, + &queue_conf); + if (ret < 0) { + printf("Error in adding eth queue in event adapter\n"); + return ret; + } + } + } + + for (i =3D 0; i < nb_eventdev_params; i++) { + ret =3D rte_event_dev_start(event_devices[i].dev_id); + if (ret < 0) { + printf("Error in starting event device, devid: %d\n", + event_devices[i].dev_id); + return ret; + } + } + + return 0; +} + + +int +config_eventdev(void) +{ + struct lcore_conf *qconf; + struct rte_eth_dev_info dev_info; + int i, j, ret; + unsigned nb_ports; + uint16_t queueid, portid; + unsigned lcore_id; + uint8_t queue, socketid; + + nb_ports =3D rte_eth_dev_count(); + + /* Rx queue configuration is to be done */ + for (i =3D 0; i < rx_adapter_config.nb_rx_adapter; i++) { + printf("\nInitializing rx queues on lcore %u ... ", lcore_id); + fflush(stdout); + /* init RX queues */ + for (j =3D 0; j < rx_adapter_config.config[i].nb_connections; j++) { + struct rte_eth_dev *dev; + struct rte_eth_conf *conf; + struct rte_eth_rxconf rxq_conf; + + portid =3D rx_adapter_config.config[i].connections[j].ethdev_id; + queueid =3D rx_adapter_config.config[i].connections[j].ethdev_rx_qid; + dev =3D &rte_eth_devices[portid]; + conf =3D &dev->data->dev_conf; + + if (numa_on) + socketid =3D + (uint8_t)rte_lcore_to_socket_id(lcore_id); + else + socketid =3D 0; + + printf("rxq=3D%d,%d,%d ", portid, queueid, socketid); + fflush(stdout); + + rte_eth_dev_info_get(portid, &dev_info); + rxq_conf =3D dev_info.default_rxconf; + rxq_conf.offloads =3D conf->rxmode.offloads; + ret =3D rte_eth_rx_queue_setup(portid, queueid, nb_rxd, + socketid, + &rxq_conf, + pktmbuf_pool[socketid]); + if (ret < 0) + rte_exit(EXIT_FAILURE, + "rte_eth_rx_queue_setup: err=3D%d, port=3D%d\n", + ret, portid); + } + } + + printf("\n"); + + ret =3D eventdev_configure(); + if (ret < 0) + rte_exit(EXIT_FAILURE, + "event dev configure: err=3D%d\n", ret); + + /* start ports */ + for (portid =3D 0; portid < nb_ports; portid++) { + if ((enabled_port_mask & (1 << portid)) =3D=3D 0) { + continue; + } + /* Start device */ + ret =3D rte_eth_dev_start(portid); + if (ret < 0) + rte_exit(EXIT_FAILURE, + "rte_eth_dev_start: err=3D%d, port=3D%d\n", + ret, portid); + + /* + * If enabled, put device in promiscuous mode. + * This allows IO forwarding mode to forward packets + * to itself through 2 cross-connected ports of the + * target machine. + */ + if (promiscuous_on) + rte_eth_promiscuous_enable(portid); + } + + printf("\n"); + + for (lcore_id =3D 0; lcore_id < RTE_MAX_LCORE; lcore_id++) { + if (rte_lcore_is_enabled(lcore_id) =3D=3D 0) + continue; + qconf =3D &lcore_conf[lcore_id]; + for (queue =3D 0; queue < qconf->n_rx_queue; ++queue) { + portid =3D qconf->rx_queue_list[queue].port_id; + queueid =3D qconf->rx_queue_list[queue].queue_id; + if (prepare_ptype_parser(portid, queueid) =3D=3D 0) + rte_exit(EXIT_FAILURE, "ptype check fails\n"); + } + } + + + check_all_ports_link_status(nb_ports, enabled_port_mask); + + ret =3D 0; + /* launch per-lcore init on every lcore */ + rte_eal_mp_remote_launch(l3fwd_lkp.main_loop, NULL, CALL_MASTER); + RTE_LCORE_FOREACH_SLAVE(lcore_id) { + if (rte_eal_wait_lcore(lcore_id) < 0) { + ret =3D -1; + break; + } + } + + /* stop ports */ + for (portid =3D 0; portid < nb_ports; portid++) { + if ((enabled_port_mask & (1 << portid)) =3D=3D 0) + continue; + printf("Closing port %d...", portid); + rte_eth_dev_stop(portid); + rte_eth_dev_close(portid); + printf(" Done\n"); + } + printf("Bye...\n"); + + return ret; +} diff --git a/examples/l3fwd/l3fwd_eventdev.h b/examples/l3fwd/l3fwd_eventde= v.h new file mode 100644 index 0000000..6a778b0 --- /dev/null +++ b/examples/l3fwd/l3fwd_eventdev.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018 NXP + */ + +#ifndef __L3_FWD_EVENTDEV_H__ +#define __L3_FWD_EVENTDEV_H__ + +#define INVALID_EVENDEV_ID 0xFF + +#define RTE_MAX_EVENTDEV_COUNT RTE_MAX_LCORE + +/* + * Default RX Dequeue size + */ +#define MAX_EVENTDEV_PKT_BURST 8 + +struct eventdev_params { + uint8_t num_eventqueue; + uint8_t num_eventport; + uint8_t eventdev_id; +}; + +struct connection_info { + uint8_t ethdev_id; + uint8_t eventq_id; + uint8_t event_prio; + uint8_t ethdev_rx_qid; + int32_t ethdev_rx_qid_mode; + int32_t eventdev_id; + int32_t adapter_id; +}; + +struct adapter_config { + struct connection_info connections[RTE_MAX_EVENTDEV_COUNT]; + uint8_t nb_connections; +}; + +struct adapter_params { + struct adapter_config config[RTE_MAX_EVENTDEV_COUNT]; + uint8_t nb_rx_adapter; +}; + +struct eventdev_info { + uint8_t dev_id; + uint8_t *port; + uint8_t *queue; +}; + +struct link_info { + uint8_t event_portid; + uint8_t eventq_id; + uint8_t eventdev_id; + uint8_t lcore_id; +}; +struct link_params { + struct link_info links[RTE_MAX_EVENTDEV_COUNT]; + uint8_t nb_links; +}; + +enum dequeue_mode { + POLL_DEQUEUE =3D 0, + EVENTDEV_DEQUEUE, +}; + +extern struct link_params link_config; +extern struct eventdev_info *event_devices; extern enum dequeue_mode=20 +lcore_dequeue_mode[RTE_MAX_LCORE]; + +int +em_eventdev_main_loop(__attribute__((unused)) void *dummy); + +int +lpm_eventdev_main_loop(__attribute__((unused)) void *dummy); + +int +parse_eventdev_config(const char *evq_arg); int=20 +parse_adapter_config(const char *evq_arg); int parse_link_config(const=20 +char *evq_arg); + +int +config_eventdev(void); + +#endif /* __L3_FWD_EVENTDEV_H__ */ diff --git a/examples/l3fwd/l3fwd_lpm.c b/examples/l3fwd/l3fwd_lpm.c index = a747126..b2a3c1d 100644 --- a/examples/l3fwd/l3fwd_lpm.c +++ b/examples/l3fwd/l3fwd_lpm.c @@ -25,8 +25,10 @@ #include #include #include +#include =20 #include "l3fwd.h" +#include "l3fwd_eventdev.h" =20 struct ipv4_l3fwd_lpm_route { uint32_t ip; @@ -168,6 +170,101 @@ lpm_get_dst_port_with_ipv4(const struct lcore_conf *q= conf, struct rte_mbuf *pkt, #include "l3fwd_lpm.h" #endif =20 +/* main LPM processing loop for eventdev*/ int +lpm_eventdev_main_loop(__attribute__((unused)) void *dummy) { + struct rte_mbuf *pkts_burst[MAX_PKT_BURST]; + struct rte_event ev[MAX_PKT_BURST]; + struct rte_event_port_conf event_port_conf; + unsigned int lcore_id; + uint64_t prev_tsc, diff_tsc, cur_tsc; + int i, nb_rx; + uint16_t portid, dequeue_len; + uint8_t event_port_id =3D INVALID_EVENDEV_ID; + uint8_t queueid; + struct lcore_conf *qconf; + const uint64_t drain_tsc =3D (rte_get_tsc_hz() + US_PER_S - 1) / + US_PER_S * BURST_TX_DRAIN_US; + + prev_tsc =3D 0; + + lcore_id =3D rte_lcore_id(); + qconf =3D &lcore_conf[lcore_id]; + + if (qconf->n_rx_queue =3D=3D 0) { + RTE_LOG(INFO, L3FWD, "lcore %u has nothing to do\n", lcore_id); + return 0; + } + + RTE_LOG(INFO, L3FWD, "entering main loop on lcore %u\n", lcore_id); + + for (i =3D 0; i < qconf->n_rx_queue; i++) { + + portid =3D qconf->rx_queue_list[i].port_id; + queueid =3D qconf->rx_queue_list[i].queue_id; + RTE_LOG(INFO, L3FWD, + " -- lcoreid=3D%u portid=3D%u rxqueueid=3D%hhu\n", + lcore_id, portid, queueid); + } + + for (i =3D 0; i < link_config.nb_links; i++) { + if (link_config.links[i].lcore_id =3D=3D lcore_id) + event_port_id =3D link_config.links[i].event_portid; + } + + rte_event_port_default_conf_get(event_devices[0].dev_id, event_port_id, + &event_port_conf); + dequeue_len =3D event_port_conf.dequeue_depth; + + while (!force_quit) { + + cur_tsc =3D rte_rdtsc(); + + /* + * TX burst queue drain + */ + diff_tsc =3D cur_tsc - prev_tsc; + if (unlikely(diff_tsc > drain_tsc)) { + + for (i =3D 0; i < qconf->n_tx_port; ++i) { + portid =3D qconf->tx_port_id[i]; + if (qconf->tx_mbufs[portid].len =3D=3D 0) + continue; + send_burst(qconf, + qconf->tx_mbufs[portid].len, + portid); + qconf->tx_mbufs[portid].len =3D 0; + } + + prev_tsc =3D cur_tsc; + } + + /* + * Read packet from event ports + */ + nb_rx =3D rte_event_dequeue_burst(event_devices[0].dev_id, + event_port_id, + ev, dequeue_len, 0); + if (nb_rx =3D=3D 0) + continue; + + for (i =3D 0; i < nb_rx; i++) { + pkts_burst[0] =3D ev[i].mbuf; + portid =3D ev[i].flow_id; +#if defined RTE_ARCH_X86 || defined RTE_MACHINE_CPUFLAG_NEON \ + || defined RTE_ARCH_PPC_64 + l3fwd_lpm_send_packets(1, pkts_burst, portid, qconf); #else + l3fwd_lpm_no_opt_send_packets(1, pkts_burst, + portid, qconf); +#endif /* X86 */ + } + } + + return 0; +} + /* main processing loop */ int lpm_main_loop(__attribute__((unused)) void *dummy) @@ -187,6 +284,9 @@ lpm= _main_loop(__attribute__((unused)) void *dummy) lcore_id =3D rte_lcore_id(); qconf =3D &lcore_conf[lcore_id]; =20 + if (lcore_dequeue_mode[lcore_id] =3D=3D EVENTDEV_DEQUEUE) + return lpm_eventdev_main_loop(dummy); + if (qconf->n_rx_queue =3D=3D 0) { RTE_LOG(INFO, L3FWD, "lcore %u has nothing to do\n", lcore_id); return 0; diff --git a/examples/l3fwd/main.c b/examples/l3fwd/main.c index e7111fa..e= ff1ac5 100644 --- a/examples/l3fwd/main.c +++ b/examples/l3fwd/main.c @@ -46,6 +46,7 @@ #include =20 #include "l3fwd.h" +#include "l3fwd_eventdev.h" =20 /* * Configurable number of RX/TX ring descriptors @@ -59,17 +60,17 @@ #def= ine MAX_LCORE_PARAMS 1024 =20 /* Static global variables used within this file. */ -static uint16_t nb_r= xd =3D RTE_TEST_RX_DESC_DEFAULT; +uint16_t nb_rxd =3D RTE_TEST_RX_DESC_DEFAULT; static uint16_t nb_txd =3D RTE_TEST_TX_DESC_DEFAULT; =20 /**< Ports set in promiscuous mode off by default. */ -static int promiscu= ous_on; +int promiscuous_on; =20 /* Select Longest-Prefix or Exact match. */ static int l3fwd_lpm_on; sta= tic int l3fwd_em_on; =20 -static int numa_on =3D 1; /**< NUMA is enabled by default. */ +int numa_on =3D 1; /**< NUMA is enabled by default. */ static int parse_ptype; /**< Parse packet type using rx callback, and */ /**< disabled by default */ =20 @@ -90,6 +91,9 @@ uint32_t enabled_port_mask; int ipv6; /**< ipv6 is false= by default. */ uint32_t hash_entry_number =3D HASH_ENTRY_NUMBER_DEFAULT; =20 +/** MAX_JUMBO_PKT_LEN) { + fprintf(stderr, + "invalid maximum packet length\n"); + print_usage(prgname); + return -1; + } + port_conf.rxmode.max_rx_pkt_len =3D ret; + } + break; + } + + case CMD_LINE_OPT_HASH_ENTRY_NUM_NUM: + ret =3D parse_hash_entry_number(optarg); + if ((ret > 0) && (ret <=3D L3FWD_HASH_ENTRIES)) { + hash_entry_number =3D ret; + } else { + fprintf(stderr, "invalid hash entry number\n"); + print_usage(prgname); + return -1; + } + break; + + case CMD_LINE_OPT_PARSE_PTYPE_NUM: + printf("soft parse-ptype is enabled\n"); + parse_ptype =3D 1; + break; + + default: + print_usage(prgname); + return -1; + } + } + + /* If both LPM and EM are selected, return error. */ + if (l3fwd_lpm_on && l3fwd_em_on) { + fprintf(stderr, "LPM and EM are mutually exclusive, select only one\n"); + return -1; + } + + /* + * Nothing is selected, pick longest-prefix match + * as default match. + */ + if (!l3fwd_lpm_on && !l3fwd_em_on) { + fprintf(stderr, "LPM or EM none selected, default LPM on\n"); + l3fwd_lpm_on =3D 1; + } + + /* + * ipv6 and hash flags are valid only for + * exact macth, reset them to default for + * longest-prefix match. + */ + if (l3fwd_lpm_on) { + ipv6 =3D 0; + hash_entry_number =3D HASH_ENTRY_NUMBER_DEFAULT; + } + + if (optind >=3D 0) + argv[optind-1] =3D prgname; + + ret =3D optind-1; + optind =3D 1; /* reset getopt lib */ + return ret; +} + +/* Parse the argument given in the command line of the application */=20 +static int parse_args(int argc, char **argv) { + int opt; + char **argvopt; + int option_index; + char *end =3D NULL; + + argvopt =3D argv; + + /* Error or normal output strings. */ + while ((opt =3D getopt_long(argc, argvopt, short_options, + lgopts, &option_index)) !=3D EOF) { + /* long options */ + if (opt =3D=3D CMD_LINE_OPT_DEQUEUE_MODE_NUM) { + dq_mode =3D strtoul(optarg, &end, 10); + break; + } + } + + if (dq_mode =3D=3D EVENTDEV_DEQUEUE) + return parse_args_eventdev(argc, argv); + + return parse_args_poll(argc, argv); +} + static void print_ethaddr(const char *name, const struct ether_addr *eth_addr) { @@ -= 693,7 +904,7 @@ init_mem(unsigned nb_mbuf) } =20 /* Check the link status of all ports in up to 9s, and print them finally = */ -static void +void check_all_ports_link_status(uint16_t port_num, uint32_t port_mask) { #de= fine CHECK_INTERVAL 100 /* 100ms */ @@ -761,7 +972,7 @@ signal_handler(int = signum) } } =20 -static int +int prepare_ptype_parser(uint16_t portid, uint16_t queueid) { if (parse_ptype) { @@ -783,8 +994,8 @@ prepare_ptype_parser(uint16_t portid, uint16_t queueid) return 0; } =20 -int -main(int argc, char **argv) +static int +config_poll(void) { struct lcore_conf *qconf; struct rte_eth_dev_info dev_info; @@ -796,36 +1007,15 @@ main(int argc, char **argv) uint32_t n_tx_queue, nb_lcores; uint8_t nb_rx_queue, queue, socketid; =20 - /* init EAL */ - ret =3D rte_eal_init(argc, argv); - if (ret < 0) - rte_exit(EXIT_FAILURE, "Invalid EAL parameters\n"); - argc -=3D ret; - argv +=3D ret; + if (dq_mode !=3D EVENTDEV_DEQUEUE) { =20 - force_quit =3D false; - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); + if (check_lcore_params() < 0) + rte_exit(EXIT_FAILURE, "check_lcore_params failed\n"); =20 - /* pre-init dst MACs for all ports to 02:00:00:00:00:xx */ - for (portid =3D 0; portid < RTE_MAX_ETHPORTS; portid++) { - dest_eth_addr[portid] =3D - ETHER_LOCAL_ADMIN_ADDR + ((uint64_t)portid << 40); - *(uint64_t *)(val_eth + portid) =3D dest_eth_addr[portid]; + ret =3D init_lcore_rx_queues(); + if (ret < 0) + rte_exit(EXIT_FAILURE, "init_lcore_rx_queues failed\n"); } - - /* parse application arguments (after the EAL ones) */ - ret =3D parse_args(argc, argv); - if (ret < 0) - rte_exit(EXIT_FAILURE, "Invalid L3FWD parameters\n"); - - if (check_lcore_params() < 0) - rte_exit(EXIT_FAILURE, "check_lcore_params failed\n"); - - ret =3D init_lcore_rx_queues(); - if (ret < 0) - rte_exit(EXIT_FAILURE, "init_lcore_rx_queues failed\n"); - nb_ports =3D rte_eth_dev_count(); =20 if (check_port_config(nb_ports) < 0) @@ -927,6 +1117,8 @@ main(int argc, char **argv) } printf("\n"); } + if (dq_mode =3D=3D EVENTDEV_DEQUEUE) + return config_eventdev(); =20 for (lcore_id =3D 0; lcore_id < RTE_MAX_LCORE; lcore_id++) { if (rte_lcore_is_enabled(lcore_id) =3D=3D 0) @@ -1032,3 +1224,35 @@ main= (int argc, char **argv) =20 return ret; } + +int +main(int argc, char **argv) +{ + int ret; + uint16_t portid; + + /* init EAL */ + ret =3D rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid EAL parameters\n"); + argc -=3D ret; + argv +=3D ret; + + force_quit =3D false; + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + /* pre-init dst MACs for all ports to 02:00:00:00:00:xx */ + for (portid =3D 0; portid < RTE_MAX_ETHPORTS; portid++) { + dest_eth_addr[portid] =3D + ETHER_LOCAL_ADMIN_ADDR + ((uint64_t)portid << 40); + *(uint64_t *)(val_eth + portid) =3D dest_eth_addr[portid]; + } + + /* parse application arguments (after the EAL ones) */ + ret =3D parse_args(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "Invalid L3FWD parameters\n"); + + return config_poll(); +} diff --git a/examples/l3fwd/meson.build b/examples/l3fwd/meson.build index = 6dd4b90..e6fe32b 100644 --- a/examples/l3fwd/meson.build +++ b/examples/l3fwd/meson.build @@ -8,5 +8,5 @@ =20 deps +=3D ['hash', 'lpm'] sources =3D files( - 'l3fwd_em.c', 'l3fwd_lpm.c', 'main.c' + 'l3fwd_em.c', 'l3fwd_lpm.c', 'l3fwd_eventdev.c', 'main.c' ) -- 2.9.3