X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=vnet%2Fvnet%2Fdevices%2Fdpdk%2Fdpdk.h;h=2cb301ce291e0f963aa638e08c91adc1ff752bbd;hb=a7cc4479db4b3be0eb2b3ebf7cb569a0a4ed0c17;hp=830bf102aa3b565dd803bb9e581e24104a5baa15;hpb=1f0da170e818a6991d841df47de0885da720b0c3;p=vpp.git diff --git a/vnet/vnet/devices/dpdk/dpdk.h b/vnet/vnet/devices/dpdk/dpdk.h index 830bf102aa3..2cb301ce291 100644 --- a/vnet/vnet/devices/dpdk/dpdk.h +++ b/vnet/vnet/devices/dpdk/dpdk.h @@ -75,13 +75,6 @@ extern vnet_device_class_t dpdk_device_class; extern vlib_node_registration_t dpdk_input_node; extern vlib_node_registration_t handoff_dispatch_node; -typedef enum { - VNET_DPDK_DEV_ETH = 1, /* Standard DPDK PMD driver */ - VNET_DPDK_DEV_KNI, /* Kernel NIC Interface */ - VNET_DPDK_DEV_VHOST_USER, - VNET_DPDK_DEV_UNKNOWN, /* must be last */ -} dpdk_device_type_t; - #define foreach_dpdk_pmd \ _ ("rte_nicvf_pmd", THUNDERX) \ _ ("rte_em_pmd", E1000EM) \ @@ -100,15 +93,17 @@ typedef enum { _ ("rte_cxgbe_pmd", CXGBE) \ _ ("rte_dpaa2_dpni", DPAA2) -typedef enum { +typedef enum +{ VNET_DPDK_PMD_NONE, #define _(s,f) VNET_DPDK_PMD_##f, foreach_dpdk_pmd #undef _ - VNET_DPDK_PMD_UNKNOWN, /* must be last */ + VNET_DPDK_PMD_UNKNOWN, /* must be last */ } dpdk_pmd_t; -typedef enum { +typedef enum +{ VNET_DPDK_PORT_TYPE_ETH_1G, VNET_DPDK_PORT_TYPE_ETH_10G, VNET_DPDK_PORT_TYPE_ETH_40G, @@ -118,14 +113,16 @@ typedef enum { VNET_DPDK_PORT_TYPE_UNKNOWN, } dpdk_port_type_t; -typedef struct { +typedef struct +{ f64 deadline; - vlib_frame_t * frame; + vlib_frame_t *frame; } dpdk_frame_t; #define DPDK_EFD_MAX_DISCARD_RATE 10 -typedef struct { +typedef struct +{ u16 last_burst_sz; u16 max_burst_sz; u32 full_frames_cnt; @@ -138,7 +135,8 @@ typedef struct { } dpdk_efd_agent_t; #if DPDK_VHOST_USER -typedef struct { +typedef struct +{ int callfd; int kickfd; int errfd; @@ -150,7 +148,8 @@ typedef struct { u64 bytes; } dpdk_vu_vring; -typedef struct { +typedef struct +{ u32 is_up; u32 unix_fd; u32 unix_file_index; @@ -169,22 +168,23 @@ typedef struct { } dpdk_vu_intf_t; #endif -typedef void (*dpdk_flowcontrol_callback_t) (vlib_main_t *vm, - u32 hw_if_index, - u32 n_packets); +typedef void (*dpdk_flowcontrol_callback_t) (vlib_main_t * vm, + u32 hw_if_index, u32 n_packets); /* * The header for the tx_vector in dpdk_device_t. * Head and tail are indexes into the tx_vector and are of type * u64 so they never overflow. */ -typedef struct { +typedef struct +{ u64 tx_head; u64 tx_tail; } tx_ring_hdr_t; -typedef struct { - CLIB_CACHE_LINE_ALIGN_MARK(cacheline0); +typedef struct +{ + CLIB_CACHE_LINE_ALIGN_MARK (cacheline0); volatile u32 **lockp; /* Instance ID */ @@ -197,35 +197,36 @@ typedef struct { u32 per_interface_next_index; /* dpdk rte_mbuf rx and tx vectors, VLIB_FRAME_SIZE */ - struct rte_mbuf *** tx_vectors; /* one per worker thread */ - struct rte_mbuf *** rx_vectors; + struct rte_mbuf ***tx_vectors; /* one per worker thread */ + struct rte_mbuf ***rx_vectors; /* vector of traced contexts, per device */ - u32 * d_trace_buffers; - - /* per-worker destination frame queue */ - dpdk_frame_t * frames; - - /* number of sub-interfaces */ - u16 vlan_subifs; + u32 *d_trace_buffers; - dpdk_device_type_t dev_type:8; dpdk_pmd_t pmd:8; i8 cpu_socket; - u8 admin_up; - u8 promisc; + u16 flags; +#define DPDK_DEVICE_FLAG_ADMIN_UP (1 << 0) +#define DPDK_DEVICE_FLAG_PROMISC (1 << 1) +#define DPDK_DEVICE_FLAG_PMD (1 << 2) +#define DPDK_DEVICE_FLAG_KNI (1 << 3) +#define DPDK_DEVICE_FLAG_VHOST_USER (1 << 4) +#define DPDK_DEVICE_FLAG_HAVE_SUBIF (1 << 5) + + CLIB_CACHE_LINE_ALIGN_MARK (cacheline1); - CLIB_CACHE_LINE_ALIGN_MARK(cacheline1); + u8 *interface_name_suffix; - u8 * interface_name_suffix; + /* number of sub-interfaces */ + u16 num_subifs; /* PMD related */ u16 tx_q_used; u16 rx_q_used; u16 nb_rx_desc; u16 nb_tx_desc; - u16 * cpu_socket_id_by_queue; + u16 *cpu_socket_id_by_queue; struct rte_eth_conf port_conf; struct rte_eth_txconf tx_conf; @@ -236,7 +237,7 @@ typedef struct { #if DPDK_VHOST_USER /* vhost-user related */ u32 vu_if_id; - struct virtio_net vu_vhost_dev; + struct virtio_net vu_vhost_dev; u32 vu_is_running; dpdk_vu_intf_t *vu_intf; #endif @@ -251,36 +252,38 @@ typedef struct { struct rte_eth_stats last_stats; struct rte_eth_stats last_cleared_stats; #if RTE_VERSION >= RTE_VERSION_NUM(16, 7, 0, 0) - struct rte_eth_xstat * xstats; - struct rte_eth_xstat * last_cleared_xstats; + struct rte_eth_xstat *xstats; + struct rte_eth_xstat *last_cleared_xstats; #else - struct rte_eth_xstats * xstats; - struct rte_eth_xstats * last_cleared_xstats; + struct rte_eth_xstats *xstats; + struct rte_eth_xstats *last_cleared_xstats; #endif f64 time_last_stats_update; dpdk_port_type_t port_type; dpdk_efd_agent_t efd_agent; - u8 need_txlock; /* Used by VNET_DPDK_DEV_VHOST_USER */ + u8 need_txlock; /* Used by VNET_DPDK_DEV_VHOST_USER */ } dpdk_device_t; #define DPDK_TX_RING_SIZE (4 * 1024) #define DPDK_STATS_POLL_INTERVAL (10.0) -#define DPDK_MIN_STATS_POLL_INTERVAL (0.001) /* 1msec */ +#define DPDK_MIN_STATS_POLL_INTERVAL (0.001) /* 1msec */ #define DPDK_LINK_POLL_INTERVAL (3.0) -#define DPDK_MIN_LINK_POLL_INTERVAL (0.001) /* 1msec */ +#define DPDK_MIN_LINK_POLL_INTERVAL (0.001) /* 1msec */ -typedef struct { - CLIB_CACHE_LINE_ALIGN_MARK(cacheline0); +typedef struct +{ + CLIB_CACHE_LINE_ALIGN_MARK (cacheline0); /* total input packet counter */ u64 aggregate_rx_packets; } dpdk_worker_t; -typedef struct { +typedef struct +{ u32 device; u16 queue_id; } dpdk_device_and_queue_t; @@ -294,7 +297,8 @@ typedef struct { #define DPDK_EFD_DEFAULT_DEVICE_QUEUE_HI_THRESH_PCT 90 #define DPDK_EFD_DEFAULT_CONSEC_FULL_FRAMES_HI_THRESH 6 -typedef struct dpdk_efd_t { +typedef struct dpdk_efd_t +{ u16 enabled; u16 queue_hi_thresh; u16 consec_full_frames_hi_thresh; @@ -308,10 +312,11 @@ typedef struct dpdk_efd_t { _ (num_tx_desc) \ _ (rss_fn) -typedef struct { - vlib_pci_addr_t pci_addr; - u8 is_blacklisted; - u8 vlan_strip_offload; +typedef struct +{ + vlib_pci_addr_t pci_addr; + u8 is_blacklisted; + u8 vlan_strip_offload; #define DPDK_DEVICE_VLAN_STRIP_DEFAULT 0 #define DPDK_DEVICE_VLAN_STRIP_OFF 1 #define DPDK_DEVICE_VLAN_STRIP_ON 2 @@ -322,12 +327,13 @@ typedef struct { clib_bitmap_t * workers; } dpdk_device_config_t; -typedef struct { +typedef struct +{ /* Config stuff */ - u8 ** eal_init_args; - u8 * eal_init_args_str; - u8 * uio_driver_name; + u8 **eal_init_args; + u8 *eal_init_args_str; + u8 *uio_driver_name; u8 no_multi_seg; u8 enable_tcp_udp_checksum; @@ -337,7 +343,7 @@ typedef struct { u32 coremask; u32 nchannels; u32 num_mbufs; - u8 num_kni;/* while kni_init allows u32, port_id in callback fn is only u8 */ + u8 num_kni; /* while kni_init allows u32, port_id in callback fn is only u8 */ /* * format interface names ala xxxEthernet%d/%d/%d instead of @@ -354,21 +360,22 @@ typedef struct { /* per-device config */ dpdk_device_config_t default_devconf; - dpdk_device_config_t * dev_confs; - uword * device_config_index_by_pci_addr; + dpdk_device_config_t *dev_confs; + uword *device_config_index_by_pci_addr; } dpdk_config_main_t; dpdk_config_main_t dpdk_config_main; -typedef struct { +typedef struct +{ /* Devices */ - dpdk_device_t * devices; - dpdk_device_and_queue_t ** devices_by_cpu; + dpdk_device_t *devices; + dpdk_device_and_queue_t **devices_by_cpu; /* per-thread recycle lists */ - u32 ** recycle; + u32 **recycle; /* buffer flags template, configurable to enable/disable tcp / udp cksum */ u32 buffer_flags_template; @@ -380,7 +387,7 @@ typedef struct { u32 vlib_buffer_free_list_index; /* dpdk worker "threads" */ - dpdk_worker_t * workers; + dpdk_worker_t *workers; /* Ethernet input node index */ @@ -389,15 +396,15 @@ typedef struct { /* pcap tracing [only works if (CLIB_DEBUG > 0)] */ int tx_pcap_enable; pcap_main_t pcap_main; - u8 * pcap_filename; + u8 *pcap_filename; u32 pcap_sw_if_index; u32 pcap_pkts_to_capture; /* hashes */ - uword * dpdk_device_by_kni_port_id; - uword * vu_sw_if_index_by_listener_fd; - uword * vu_sw_if_index_by_sock_fd; - u32 * vu_inactive_interfaces_device_index; + uword *dpdk_device_by_kni_port_id; + uword *vu_sw_if_index_by_listener_fd; + uword *vu_sw_if_index_by_sock_fd; + u32 *vu_inactive_interfaces_device_index; u32 next_vu_if_id; @@ -424,14 +431,15 @@ typedef struct { u32 poll_sleep; /* convenience */ - vlib_main_t * vlib_main; - vnet_main_t * vnet_main; - dpdk_config_main_t * conf; + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; + dpdk_config_main_t *conf; } dpdk_main_t; dpdk_main_t dpdk_main; -typedef enum { +typedef enum +{ DPDK_RX_NEXT_IP4_INPUT, DPDK_RX_NEXT_IP6_INPUT, DPDK_RX_NEXT_MPLS_INPUT, @@ -440,7 +448,8 @@ typedef enum { DPDK_RX_N_NEXT, } dpdk_rx_next_t; -typedef struct { +typedef struct +{ u32 buffer_index; u16 device_index; u8 queue_index; @@ -449,37 +458,38 @@ typedef struct { vlib_buffer_t buffer; } dpdk_tx_dma_trace_t; -typedef struct { +typedef struct +{ u32 buffer_index; u16 device_index; u16 queue_index; struct rte_mbuf mb; - vlib_buffer_t buffer; /* Copy of VLIB buffer; pkt data stored in pre_data. */ - u8 data[256]; /* First 256 data bytes, used for hexdump */ + vlib_buffer_t buffer; /* Copy of VLIB buffer; pkt data stored in pre_data. */ + u8 data[256]; /* First 256 data bytes, used for hexdump */ } dpdk_rx_dma_trace_t; void vnet_buffer_needs_dpdk_mb (vlib_buffer_t * b); void dpdk_set_next_node (dpdk_rx_next_t, char *); -clib_error_t * dpdk_set_mac_address (vnet_hw_interface_t * hi, char * address); +clib_error_t *dpdk_set_mac_address (vnet_hw_interface_t * hi, char *address); -clib_error_t * dpdk_set_mc_filter (vnet_hw_interface_t * hi, - struct ether_addr mc_addr_vec[], int naddr); +clib_error_t *dpdk_set_mc_filter (vnet_hw_interface_t * hi, + struct ether_addr mc_addr_vec[], int naddr); void dpdk_thread_input (dpdk_main_t * dm, dpdk_device_t * xd); -clib_error_t * dpdk_port_setup (dpdk_main_t * dm, dpdk_device_t * xd); +clib_error_t *dpdk_port_setup (dpdk_main_t * dm, dpdk_device_t * xd); -void dpdk_set_flowcontrol_callback (vlib_main_t *vm, - dpdk_flowcontrol_callback_t callback); +void dpdk_set_flowcontrol_callback (vlib_main_t * vm, + dpdk_flowcontrol_callback_t callback); u32 dpdk_interface_tx_vector (vlib_main_t * vm, u32 dev_instance); -void set_efd_bitmap (u8 *bitmap, u32 value, u32 op); +void set_efd_bitmap (u8 * bitmap, u32 value, u32 op); -struct rte_mbuf * dpdk_replicate_packet_mb (vlib_buffer_t * b); -struct rte_mbuf * dpdk_zerocopy_replicate_packet_mb (vlib_buffer_t * b); +struct rte_mbuf *dpdk_replicate_packet_mb (vlib_buffer_t * b); +struct rte_mbuf *dpdk_zerocopy_replicate_packet_mb (vlib_buffer_t * b); #define foreach_dpdk_error \ _(NONE, "no error") \ @@ -495,113 +505,113 @@ struct rte_mbuf * dpdk_zerocopy_replicate_packet_mb (vlib_buffer_t * b); _(MPLS_EFD_DROP_PKTS, "MPLS Early Fast Discard rx drops") \ _(VLAN_EFD_DROP_PKTS, "VLAN Early Fast Discard rx drops") -typedef enum { +typedef enum +{ #define _(f,s) DPDK_ERROR_##f, foreach_dpdk_error #undef _ - DPDK_N_ERROR, + DPDK_N_ERROR, } dpdk_error_t; /* * Increment EFD drop counter */ -static_always_inline -void increment_efd_drop_counter (vlib_main_t * vm, u32 counter_index, u32 count) +static_always_inline void +increment_efd_drop_counter (vlib_main_t * vm, u32 counter_index, u32 count) { - vlib_node_t *my_n; + vlib_node_t *my_n; - my_n = vlib_get_node (vm, dpdk_input_node.index); - vm->error_main.counters[my_n->error_heap_index+counter_index] += count; + my_n = vlib_get_node (vm, dpdk_input_node.index); + vm->error_main.counters[my_n->error_heap_index + counter_index] += count; } int dpdk_set_stat_poll_interval (f64 interval); int dpdk_set_link_state_poll_interval (f64 interval); void dpdk_update_link_state (dpdk_device_t * xd, f64 now); -void dpdk_device_lock_init(dpdk_device_t * xd); -void dpdk_device_lock_free(dpdk_device_t * xd); -void dpdk_efd_update_counters(dpdk_device_t *xd, u32 n_buffers, u16 enabled); -u32 is_efd_discardable(vlib_thread_main_t *tm, - vlib_buffer_t * b0, - struct rte_mbuf *mb); +void dpdk_device_lock_init (dpdk_device_t * xd); +void dpdk_device_lock_free (dpdk_device_t * xd); +void dpdk_efd_update_counters (dpdk_device_t * xd, u32 n_buffers, + u16 enabled); +u32 is_efd_discardable (vlib_thread_main_t * tm, vlib_buffer_t * b0, + struct rte_mbuf *mb); #if DPDK_VHOST_USER /* dpdk vhost-user interrupt management */ -u8 dpdk_vhost_user_want_interrupt (dpdk_device_t *xd, int idx); +u8 dpdk_vhost_user_want_interrupt (dpdk_device_t * xd, int idx); void dpdk_vhost_user_send_interrupt (vlib_main_t * vm, dpdk_device_t * xd, - int idx); + int idx); #endif -static inline u64 vnet_get_aggregate_rx_packets (void) +static inline u64 +vnet_get_aggregate_rx_packets (void) { - dpdk_main_t * dm = &dpdk_main; - u64 sum = 0; - dpdk_worker_t * dw; + dpdk_main_t *dm = &dpdk_main; + u64 sum = 0; + dpdk_worker_t *dw; - vec_foreach(dw, dm->workers) - sum += dw->aggregate_rx_packets; + vec_foreach (dw, dm->workers) sum += dw->aggregate_rx_packets; - return sum; + return sum; } void dpdk_rx_trace (dpdk_main_t * dm, - vlib_node_runtime_t * node, - dpdk_device_t * xd, - u16 queue_id, - u32 * buffers, - uword n_buffers); + vlib_node_runtime_t * node, + dpdk_device_t * xd, + u16 queue_id, u32 * buffers, uword n_buffers); #define EFD_OPERATION_LESS_THAN 0 #define EFD_OPERATION_GREATER_OR_EQUAL 1 -void efd_config(u32 enabled, - u32 ip_prec, u32 ip_op, - u32 mpls_exp, u32 mpls_op, - u32 vlan_cos, u32 vlan_op); +void efd_config (u32 enabled, + u32 ip_prec, u32 ip_op, + u32 mpls_exp, u32 mpls_op, u32 vlan_cos, u32 vlan_op); -void post_sw_interface_set_flags (vlib_main_t *vm, u32 sw_if_index, u32 flags); +void post_sw_interface_set_flags (vlib_main_t * vm, u32 sw_if_index, + u32 flags); #if DPDK_VHOST_USER typedef struct vhost_user_memory vhost_user_memory_t; void dpdk_vhost_user_process_init (void **ctx); void dpdk_vhost_user_process_cleanup (void *ctx); -uword dpdk_vhost_user_process_if (vlib_main_t *vm, dpdk_device_t *xd, void *ctx); +uword dpdk_vhost_user_process_if (vlib_main_t * vm, dpdk_device_t * xd, + void *ctx); // vhost-user calls int dpdk_vhost_user_create_if (vnet_main_t * vnm, vlib_main_t * vm, - const char * sock_filename, - u8 is_server, - u32 * sw_if_index, - u64 feature_mask, - u8 renumber, u32 custom_dev_instance, - u8 *hwaddr); + const char *sock_filename, + u8 is_server, + u32 * sw_if_index, + u64 feature_mask, + u8 renumber, u32 custom_dev_instance, + u8 * hwaddr); int dpdk_vhost_user_modify_if (vnet_main_t * vnm, vlib_main_t * vm, - const char * sock_filename, - u8 is_server, - u32 sw_if_index, - u64 feature_mask, - u8 renumber, u32 custom_dev_instance); + const char *sock_filename, + u8 is_server, + u32 sw_if_index, + u64 feature_mask, + u8 renumber, u32 custom_dev_instance); int dpdk_vhost_user_delete_if (vnet_main_t * vnm, vlib_main_t * vm, - u32 sw_if_index); + u32 sw_if_index); int dpdk_vhost_user_dump_ifs (vnet_main_t * vnm, vlib_main_t * vm, - vhost_user_intf_details_t **out_vuids); + vhost_user_intf_details_t ** out_vuids); #endif u32 dpdk_get_admin_up_down_in_progress (void); u32 dpdk_num_mbufs (void); -dpdk_pmd_t dpdk_get_pmd_type (vnet_hw_interface_t *hi); +dpdk_pmd_t dpdk_get_pmd_type (vnet_hw_interface_t * hi); -i8 dpdk_get_cpu_socket (vnet_hw_interface_t *hi); +i8 dpdk_get_cpu_socket (vnet_hw_interface_t * hi); -void * dpdk_input_multiarch_select(); -void * dpdk_input_rss_multiarch_select(); -void * dpdk_input_efd_multiarch_select(); +void *dpdk_input_multiarch_select (); +void *dpdk_input_rss_multiarch_select (); +void *dpdk_input_efd_multiarch_select (); -clib_error_t* -dpdk_get_hw_interface_stats (u32 hw_if_index, struct rte_eth_stats* dest); +clib_error_t *dpdk_get_hw_interface_stats (u32 hw_if_index, + struct rte_eth_stats *dest); format_function_t format_dpdk_device_name; format_function_t format_dpdk_device; @@ -610,11 +620,11 @@ format_function_t format_dpdk_rx_dma_trace; format_function_t format_dpdk_rte_mbuf; format_function_t format_dpdk_rx_rte_mbuf; unformat_function_t unformat_socket_mem; -clib_error_t * unformat_rss_fn(unformat_input_t * input, uword * rss_fn); +clib_error_t *unformat_rss_fn (unformat_input_t * input, uword * rss_fn); static inline void -dpdk_pmd_constructor_init() +dpdk_pmd_constructor_init () { /* Add references to DPDK Driver Constructor functions to get the dynamic * loader to pull in the driver library & run the constructors. @@ -629,58 +639,43 @@ dpdk_pmd_constructor_init() #ifdef RTE_LIBRTE_EM_PMD _(em_pmd_drv) #endif - #ifdef RTE_LIBRTE_IGB_PMD - _(pmd_igb_drv) + _(pmd_igb_drv) #endif - #ifdef RTE_LIBRTE_IXGBE_PMD - _(rte_ixgbe_driver) + _(rte_ixgbe_driver) #endif - #ifdef RTE_LIBRTE_I40E_PMD - _(rte_i40e_driver) - _(rte_i40evf_driver) + _(rte_i40e_driver) _(rte_i40evf_driver) #endif - #ifdef RTE_LIBRTE_FM10K_PMD - _(rte_fm10k_driver) + _(rte_fm10k_driver) #endif - #ifdef RTE_LIBRTE_VIRTIO_PMD - _(rte_virtio_driver) + _(rte_virtio_driver) #endif - #ifdef RTE_LIBRTE_VMXNET3_PMD - _(rte_vmxnet3_driver) + _(rte_vmxnet3_driver) #endif - #ifdef RTE_LIBRTE_VICE_PMD - _(rte_vice_driver) + _(rte_vice_driver) #endif - #ifdef RTE_LIBRTE_ENIC_PMD - _(rte_enic_driver) + _(rte_enic_driver) #endif - #ifdef RTE_LIBRTE_PMD_AF_PACKET - _(pmd_af_packet_drv) + _(pmd_af_packet_drv) #endif - #ifdef RTE_LIBRTE_CXGBE_PMD - _(rte_cxgbe_driver) + _(rte_cxgbe_driver) #endif - #ifdef RTE_LIBRTE_PMD_BOND - _(bond_drv) + _(bond_drv) #endif - #ifdef RTE_LIBRTE_DPAA2_PMD - _(pmd_dpaa2_drv) + _(pmd_dpaa2_drv) #endif - #undef _ - /* * At the moment, the ThunderX NIC driver doesn't have * an entry point named "devinitfn_rte_xxx_driver" @@ -691,17 +686,22 @@ dpdk_pmd_constructor_init() __attribute__((unused)) void (* volatile pf)(void); \ pf = d; \ } while(0); - #ifdef RTE_LIBRTE_THUNDERVNIC_PMD - _(rte_nicvf_pmd_init) + _(rte_nicvf_pmd_init) #endif #undef _ - } uword admin_up_down_process (vlib_main_t * vm, - vlib_node_runtime_t * rt, - vlib_frame_t * f); + vlib_node_runtime_t * rt, vlib_frame_t * f); #endif /* __included_dpdk_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */