| /* |
| * Copyright (c) 2009-2014 Intel Corporation. All rights reserved. |
| * Copyright (c) 2013 Mellanox Technologies LTD. All rights reserved. |
| * |
| * This software is available to you under the OpenIB.org BSD license |
| * below: |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AWV |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #define _GNU_SOURCE |
| |
| #include <config.h> |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <osd.h> |
| #include <arpa/inet.h> |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <infiniband/acm.h> |
| #include <infiniband/acm_prov.h> |
| #include <infiniband/umad.h> |
| #include <infiniband/verbs.h> |
| #include <infiniband/umad_types.h> |
| #include <infiniband/umad_sa.h> |
| #include <dlfcn.h> |
| #include <search.h> |
| #include <net/if.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <net/if_arp.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <rdma/rdma_netlink.h> |
| #include <rdma/ib_user_sa.h> |
| #include <poll.h> |
| #include <inttypes.h> |
| #include <getopt.h> |
| #include <systemd/sd-daemon.h> |
| #include <ccan/list.h> |
| #include <util/util.h> |
| #include "acm_mad.h" |
| #include "acm_util.h" |
| |
| #define NL_MSG_BUF_SIZE 4096 |
| #define ACM_PROV_NAME_SIZE 64 |
| #define NL_CLIENT_INDEX 0 |
| |
| struct acmc_subnet { |
| struct list_node entry; |
| __be64 subnet_prefix; |
| }; |
| |
| struct acmc_prov { |
| struct acm_provider *prov; |
| void *handle; |
| struct list_node entry; |
| struct list_head subnet_list; |
| }; |
| |
| struct acmc_prov_context { |
| struct list_node entry; |
| atomic_t refcnt; |
| struct acm_provider *prov; |
| void *context; |
| }; |
| |
| struct acmc_device; |
| |
| struct acmc_port { |
| struct acmc_device *dev; |
| struct acm_port port; |
| struct acm_provider *prov; /* limit to 1 provider per port for now */ |
| void *prov_port_context; |
| int mad_portid; |
| int mad_agentid; |
| struct ib_mad_addr sa_addr; |
| struct list_head sa_pending; |
| struct list_head sa_wait; |
| int sa_credits; |
| pthread_mutex_t lock; |
| struct list_head ep_list; |
| enum ibv_port_state state; |
| int gid_cnt; |
| union ibv_gid *gid_tbl; |
| uint16_t lid; |
| uint16_t lid_mask; |
| int sa_pkey_index; |
| bool pending_rereg; |
| uint16_t def_acm_pkey; |
| }; |
| |
| struct acmc_device { |
| struct acm_device device; |
| struct list_node entry; |
| struct list_head prov_dev_context_list; |
| int port_cnt; |
| struct acmc_port port[0]; |
| }; |
| |
| struct acmc_addr { |
| struct acm_address addr; |
| void *prov_addr_context; |
| char string_buf[ACM_MAX_ADDRESS]; |
| }; |
| |
| struct acmc_ep { |
| struct acmc_port *port; |
| struct acm_endpoint endpoint; |
| void *prov_ep_context; |
| /* Although the below two entries are used for dynamic allocations, |
| * they are accessed by a single thread, so no locking is required. |
| */ |
| int nmbr_ep_addrs; |
| struct acmc_addr *addr_info; |
| struct list_node entry; |
| }; |
| |
| struct acmc_client { |
| pthread_mutex_t lock; /* acquire ep lock first */ |
| int sock; |
| int index; |
| atomic_t refcnt; |
| }; |
| |
| union socket_addr { |
| struct sockaddr sa; |
| struct sockaddr_in sin; |
| struct sockaddr_in6 sin6; |
| }; |
| |
| struct acmc_sa_req { |
| struct list_node entry; |
| struct acmc_ep *ep; |
| void (*resp_handler)(struct acm_sa_mad *); |
| struct acm_sa_mad mad; |
| }; |
| |
| struct acm_nl_path { |
| struct nlattr attr_hdr; |
| struct ib_path_rec_data rec; |
| }; |
| |
| struct acm_nl_msg { |
| struct nlmsghdr nlmsg_header; |
| union { |
| uint8_t data[ACM_MSG_DATA_LENGTH]; |
| struct rdma_ls_resolve_header resolve_header; |
| struct nlattr attr[0]; |
| struct acm_nl_path path[0]; |
| }; |
| }; |
| |
| static char def_prov_name[ACM_PROV_NAME_SIZE] = "ibacmp"; |
| static LIST_HEAD(provider_list); |
| static struct acmc_prov *def_provider = NULL; |
| |
| static LIST_HEAD(dev_list); |
| |
| static int listen_socket; |
| static int ip_mon_socket; |
| static struct acmc_client client_array[FD_SETSIZE - 1]; |
| |
| static FILE *flog; |
| static pthread_mutex_t log_lock; |
| static __thread char log_data[ACM_MAX_ADDRESS]; |
| static atomic_t counter[ACM_MAX_COUNTER]; |
| |
| static struct acmc_device * |
| acm_get_device_from_gid(union ibv_gid *sgid, uint8_t *port); |
| static struct acmc_ep *acm_find_ep(struct acmc_port *port, uint16_t pkey); |
| static int acm_ep_insert_addr(struct acmc_ep *ep, const char *name, uint8_t *addr, |
| uint8_t addr_type); |
| static void acm_event_handler(struct acmc_device *dev); |
| static int acm_nl_send(int sock, struct acm_msg *msg); |
| |
| static struct sa_data { |
| int timeout; |
| int retries; |
| int depth; |
| pthread_t thread_id; |
| struct pollfd *fds; |
| struct acmc_port **ports; |
| int nfds; |
| } sa = { 2000, 2, 1, 0, NULL, NULL, 0}; |
| |
| /* |
| * Service options - may be set through ibacm_opts.cfg file. |
| */ |
| static const char *acme = IBACM_BIN_PATH "/ib_acme -A"; |
| static const char *opts_file = ACM_CONF_DIR "/" ACM_OPTS_FILE; |
| static const char *addr_file = ACM_CONF_DIR "/" ACM_ADDR_FILE; |
| static char log_file[128] = IBACM_LOG_FILE; |
| static int log_level = 0; |
| static int umad_debug_level; |
| static char lock_file[128] = IBACM_PID_FILE; |
| static short server_port = 6125; |
| static int server_mode = IBACM_SERVER_MODE_DEFAULT; |
| static int acme_plus_kernel_only = IBACM_ACME_PLUS_KERNEL_ONLY_DEFAULT; |
| static int support_ips_in_addr_cfg = 0; |
| static char prov_lib_path[256] = IBACM_LIB_PATH; |
| |
| void acm_write(int level, const char *format, ...) |
| { |
| va_list args; |
| struct timeval tv; |
| struct tm tmtime; |
| char buffer[20]; |
| |
| if (level > log_level) |
| return; |
| |
| gettimeofday(&tv, NULL); |
| localtime_r(&tv.tv_sec, &tmtime); |
| strftime(buffer, 20, "%Y-%m-%dT%H:%M:%S", &tmtime); |
| va_start(args, format); |
| pthread_mutex_lock(&log_lock); |
| fprintf(flog, "%s.%03u: ", buffer, (unsigned) (tv.tv_usec / 1000)); |
| vfprintf(flog, format, args); |
| fflush(flog); |
| pthread_mutex_unlock(&log_lock); |
| va_end(args); |
| } |
| |
| void acm_format_name(int level, char *name, size_t name_size, |
| uint8_t addr_type, const uint8_t *addr, size_t addr_size) |
| { |
| struct ibv_path_record *path; |
| |
| if (level > log_level) |
| return; |
| |
| switch (addr_type) { |
| case ACM_EP_INFO_NAME: |
| memcpy(name, addr, addr_size); |
| break; |
| case ACM_EP_INFO_ADDRESS_IP: |
| inet_ntop(AF_INET, addr, name, name_size); |
| break; |
| case ACM_EP_INFO_ADDRESS_IP6: |
| case ACM_ADDRESS_GID: |
| inet_ntop(AF_INET6, addr, name, name_size); |
| break; |
| case ACM_EP_INFO_PATH: |
| path = (struct ibv_path_record *) addr; |
| if (path->dlid) { |
| snprintf(name, name_size, "SLID(%u) DLID(%u)", |
| be16toh(path->slid), be16toh(path->dlid)); |
| } else { |
| acm_format_name(level, name, name_size, ACM_ADDRESS_GID, |
| path->dgid.raw, sizeof path->dgid); |
| } |
| break; |
| case ACM_ADDRESS_LID: |
| snprintf(name, name_size, "LID(%u)", be16toh(*((__be16 *) addr))); |
| break; |
| default: |
| strcpy(name, "Unknown"); |
| break; |
| } |
| } |
| |
| int ib_any_gid(union ibv_gid *gid) |
| { |
| return ((gid->global.subnet_prefix | gid->global.interface_id) == 0); |
| } |
| |
| const char *acm_get_opts_file(void) |
| { |
| return opts_file; |
| } |
| |
| void acm_increment_counter(int type) |
| { |
| if (type >= 0 && type < ACM_MAX_COUNTER) |
| atomic_inc(&counter[type]); |
| } |
| |
| static struct acmc_prov_context * |
| acm_alloc_prov_context(struct acm_provider *prov) |
| { |
| struct acmc_prov_context *ctx; |
| |
| ctx = calloc(1, sizeof(*ctx)); |
| if (!ctx) { |
| acm_log(0, "Error: failed to allocate prov context\n"); |
| return NULL; |
| } |
| atomic_set(&ctx->refcnt, 1); |
| ctx->prov = prov; |
| return ctx; |
| } |
| |
| static struct acmc_prov_context * |
| acm_get_prov_context(struct list_head *list, struct acm_provider *prov) |
| { |
| struct acmc_prov_context *ctx; |
| |
| list_for_each(list, ctx, entry) { |
| if (ctx->prov == prov) { |
| return ctx; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct acmc_prov_context * |
| acm_acquire_prov_context(struct list_head *list, struct acm_provider *prov) |
| { |
| struct acmc_prov_context *ctx; |
| |
| ctx = acm_get_prov_context(list, prov); |
| if (!ctx) { |
| ctx = acm_alloc_prov_context(prov); |
| if (!ctx) { |
| acm_log(0, "Error -- failed to allocate provider context\n"); |
| return NULL; |
| } |
| list_add_tail(list, &ctx->entry); |
| } else { |
| atomic_inc(&ctx->refcnt); |
| } |
| |
| return ctx; |
| } |
| |
| static void |
| acm_release_prov_context(struct acmc_prov_context *ctx) |
| { |
| if (atomic_dec(&ctx->refcnt) <= 0) { |
| list_del(&ctx->entry); |
| free(ctx); |
| } |
| } |
| |
| uint8_t acm_gid_index(struct acm_port *port, union ibv_gid *gid) |
| { |
| uint8_t i; |
| struct acmc_port *cport; |
| |
| cport = container_of(port, struct acmc_port, port); |
| for (i = 0; i < cport->gid_cnt; i++) { |
| if (!memcmp(&cport->gid_tbl[i], gid, sizeof (*gid))) |
| break; |
| } |
| return i; |
| } |
| |
| int acm_get_gid(struct acm_port *port, int index, union ibv_gid *gid) |
| { |
| struct acmc_port *cport; |
| |
| cport = container_of(port, struct acmc_port, port); |
| if (index >= 0 && index < cport->gid_cnt) { |
| *gid = cport->gid_tbl[index]; |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| static size_t acm_addr_len(uint8_t addr_type) |
| { |
| switch (addr_type) { |
| case ACM_ADDRESS_NAME: |
| return ACM_MAX_ADDRESS; |
| case ACM_ADDRESS_IP: |
| return sizeof(struct in_addr); |
| case ACM_ADDRESS_IP6: |
| return sizeof(struct in6_addr); |
| case ACM_ADDRESS_GID: |
| return sizeof(union ibv_gid); |
| case ACM_ADDRESS_LID: |
| return sizeof(uint16_t); |
| default: |
| acm_log(2, "illegal address type %d\n", addr_type); |
| } |
| return 0; |
| } |
| |
| static int acm_addr_cmp(struct acm_address *acm_addr, uint8_t *addr, uint8_t addr_type) |
| { |
| if (acm_addr->type != addr_type) |
| return -2; |
| |
| if (acm_addr->type == ACM_ADDRESS_NAME) |
| return strncasecmp((char *) acm_addr->info.name, |
| (char *) addr, acm_addr_len(acm_addr->type)); |
| return memcmp(acm_addr->info.addr, addr, acm_addr_len(acm_addr->type)); |
| } |
| |
| static void acm_mark_addr_invalid(struct acmc_ep *ep, |
| struct acm_ep_addr_data *data) |
| { |
| int i; |
| |
| for (i = 0; i < ep->nmbr_ep_addrs; i++) { |
| if (!acm_addr_cmp(&ep->addr_info[i].addr, data->info.addr, data->type)) { |
| ep->addr_info[i].addr.type = ACM_ADDRESS_INVALID; |
| ep->port->prov->remove_address(ep->addr_info[i].prov_addr_context); |
| break; |
| } |
| } |
| } |
| |
| static struct acm_address * |
| acm_addr_lookup(const struct acm_endpoint *endpoint, uint8_t *addr, uint8_t addr_type) |
| { |
| struct acmc_ep *ep; |
| int i; |
| |
| ep = container_of(endpoint, struct acmc_ep, endpoint); |
| for (i = 0; i < ep->nmbr_ep_addrs; i++) |
| if (!acm_addr_cmp(&ep->addr_info[i].addr, addr, addr_type)) |
| return &ep->addr_info[i].addr; |
| |
| return NULL; |
| } |
| |
| __be64 acm_path_comp_mask(struct ibv_path_record *path) |
| { |
| uint32_t fl_hop; |
| uint16_t qos_sl; |
| __be64 comp_mask = 0; |
| |
| acm_log(2, "\n"); |
| if (path->service_id) |
| comp_mask |= IB_COMP_MASK_PR_SERVICE_ID; |
| if (!ib_any_gid(&path->dgid)) |
| comp_mask |= IB_COMP_MASK_PR_DGID; |
| if (!ib_any_gid(&path->sgid)) |
| comp_mask |= IB_COMP_MASK_PR_SGID; |
| if (path->dlid) |
| comp_mask |= IB_COMP_MASK_PR_DLID; |
| if (path->slid) |
| comp_mask |= IB_COMP_MASK_PR_SLID; |
| |
| fl_hop = be32toh(path->flowlabel_hoplimit); |
| if (fl_hop >> 8) |
| comp_mask |= IB_COMP_MASK_PR_FLOW_LABEL; |
| if (fl_hop & 0xFF) |
| comp_mask |= IB_COMP_MASK_PR_HOP_LIMIT; |
| |
| if (path->tclass) |
| comp_mask |= IB_COMP_MASK_PR_TCLASS; |
| if (path->reversible_numpath & 0x80) |
| comp_mask |= IB_COMP_MASK_PR_REVERSIBLE; |
| if (path->pkey) |
| comp_mask |= IB_COMP_MASK_PR_PKEY; |
| |
| qos_sl = be16toh(path->qosclass_sl); |
| if (qos_sl >> 4) |
| comp_mask |= IB_COMP_MASK_PR_QOS_CLASS; |
| if (qos_sl & 0xF) |
| comp_mask |= IB_COMP_MASK_PR_SL; |
| |
| if (path->mtu & 0xC0) |
| comp_mask |= IB_COMP_MASK_PR_MTU_SELECTOR; |
| if (path->mtu & 0x3F) |
| comp_mask |= IB_COMP_MASK_PR_MTU; |
| if (path->rate & 0xC0) |
| comp_mask |= IB_COMP_MASK_PR_RATE_SELECTOR; |
| if (path->rate & 0x3F) |
| comp_mask |= IB_COMP_MASK_PR_RATE; |
| if (path->packetlifetime & 0xC0) |
| comp_mask |= IB_COMP_MASK_PR_PACKET_LIFETIME_SELECTOR; |
| if (path->packetlifetime & 0x3F) |
| comp_mask |= IB_COMP_MASK_PR_PACKET_LIFETIME; |
| |
| return comp_mask; |
| } |
| |
| int acm_resolve_response(uint64_t id, struct acm_msg *msg) |
| { |
| struct acmc_client *client = &client_array[id]; |
| int ret; |
| |
| acm_log(2, "client %d, status 0x%x\n", client->index, msg->hdr.status); |
| |
| if (msg->hdr.status == ACM_STATUS_ENODATA) |
| atomic_inc(&counter[ACM_CNTR_NODATA]); |
| else if (msg->hdr.status) |
| atomic_inc(&counter[ACM_CNTR_ERROR]); |
| |
| pthread_mutex_lock(&client->lock); |
| if (client->sock == -1) { |
| acm_log(0, "ERROR - connection lost\n"); |
| ret = ACM_STATUS_ENOTCONN; |
| goto release; |
| } |
| |
| if (id == NL_CLIENT_INDEX) |
| ret = acm_nl_send(client->sock, msg); |
| else |
| ret = send(client->sock, (char *) msg, msg->hdr.length, 0); |
| |
| if (ret != msg->hdr.length) |
| acm_log(0, "ERROR - failed to send response\n"); |
| else |
| ret = 0; |
| |
| release: |
| pthread_mutex_unlock(&client->lock); |
| (void) atomic_dec(&client->refcnt); |
| return ret; |
| } |
| |
| static int |
| acmc_resolve_response(uint64_t id, struct acm_msg *req_msg, uint8_t status) |
| { |
| req_msg->hdr.opcode |= ACM_OP_ACK; |
| req_msg->hdr.status = status; |
| if (status != ACM_STATUS_SUCCESS) |
| req_msg->hdr.length = ACM_MSG_HDR_LENGTH; |
| memset(req_msg->hdr.data, 0, sizeof(req_msg->hdr.data)); |
| |
| return acm_resolve_response(id, req_msg); |
| } |
| |
| int acm_query_response(uint64_t id, struct acm_msg *msg) |
| { |
| struct acmc_client *client = &client_array[id]; |
| int ret; |
| |
| acm_log(2, "status 0x%x\n", msg->hdr.status); |
| pthread_mutex_lock(&client->lock); |
| if (client->sock == -1) { |
| acm_log(0, "ERROR - connection lost\n"); |
| ret = ACM_STATUS_ENOTCONN; |
| goto release; |
| } |
| |
| ret = send(client->sock, (char *) msg, msg->hdr.length, 0); |
| if (ret != msg->hdr.length) |
| acm_log(0, "ERROR - failed to send response\n"); |
| else |
| ret = 0; |
| |
| release: |
| pthread_mutex_unlock(&client->lock); |
| (void) atomic_dec(&client->refcnt); |
| return ret; |
| } |
| |
| static int acmc_query_response(uint64_t id, struct acm_msg *msg, uint8_t status) |
| { |
| acm_log(2, "status 0x%x\n", status); |
| msg->hdr.opcode |= ACM_OP_ACK; |
| msg->hdr.status = status; |
| return acm_query_response(id, msg); |
| } |
| |
| static void acm_init_server(void) |
| { |
| FILE *f; |
| int i; |
| |
| for (i = 0; i < FD_SETSIZE - 1; i++) { |
| pthread_mutex_init(&client_array[i].lock, NULL); |
| client_array[i].index = i; |
| client_array[i].sock = -1; |
| atomic_init(&client_array[i].refcnt); |
| } |
| |
| if (server_mode != IBACM_SERVER_MODE_UNIX) { |
| f = fopen(IBACM_IBACME_PORT_FILE, "w"); |
| if (f) { |
| fprintf(f, "%hu\n", server_port); |
| fclose(f); |
| } else |
| acm_log(0, |
| "notice - cannot publish ibacm port number\n"); |
| |
| unlink(IBACM_PORT_FILE); |
| if (!acme_plus_kernel_only) { |
| if (symlink(IBACM_PORT_BASE, IBACM_PORT_FILE) != 0) |
| acm_log(0, |
| "notice - can't create port symlink\n"); |
| } |
| } else { |
| unlink(IBACM_IBACME_PORT_FILE); |
| unlink(IBACM_PORT_FILE); |
| } |
| } |
| |
| static int acm_listen(void) |
| { |
| union { |
| struct sockaddr any; |
| struct sockaddr_in inet; |
| struct sockaddr_un unx; |
| } addr; |
| mode_t saved_mask; |
| int ret, saved_errno; |
| |
| acm_log(2, "\n"); |
| |
| memset(&addr, 0, sizeof(addr)); |
| |
| if (server_mode == IBACM_SERVER_MODE_UNIX) { |
| addr.any.sa_family = AF_UNIX; |
| BUILD_ASSERT(sizeof(IBACM_IBACME_SERVER_PATH) <= |
| sizeof(addr.unx.sun_path)); |
| strcpy(addr.unx.sun_path, IBACM_IBACME_SERVER_PATH); |
| |
| listen_socket = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (listen_socket < 0) { |
| acm_log(0, |
| "ERROR - unable to allocate unix socket\n"); |
| return errno; |
| } |
| |
| unlink(addr.unx.sun_path); |
| saved_mask = umask(0); |
| ret = bind(listen_socket, &addr.any, sizeof(addr.unx)); |
| saved_errno = errno; |
| umask(saved_mask); |
| |
| if (ret) { |
| acm_log(0, |
| "ERROR - unable to bind listen socket '%s'\n", |
| addr.unx.sun_path); |
| return saved_errno; |
| } |
| |
| unlink(IBACM_SERVER_PATH); |
| if (!acme_plus_kernel_only) { |
| if (symlink(IBACM_SERVER_BASE, |
| IBACM_SERVER_PATH) != 0) { |
| saved_errno = errno; |
| acm_log(0, |
| "notice - can't create symlink\n"); |
| return saved_errno; |
| } |
| } |
| } else { |
| unlink(IBACM_IBACME_SERVER_PATH); |
| unlink(IBACM_SERVER_PATH); |
| |
| listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
| if (listen_socket == -1) { |
| acm_log(0, |
| "ERROR - unable to allocate TCP socket\n"); |
| return errno; |
| } |
| |
| addr.any.sa_family = AF_INET; |
| addr.inet.sin_port = htobe16(server_port); |
| if (server_mode == IBACM_SERVER_MODE_LOOP) |
| addr.inet.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| ret = bind(listen_socket, &addr.any, sizeof(addr.inet)); |
| if (ret == -1) { |
| acm_log(0, "ERROR - unable to bind listen socket\n"); |
| return errno; |
| } |
| } |
| |
| ret = listen(listen_socket, 0); |
| if (ret == -1) { |
| acm_log(0, "ERROR - unable to start listen\n"); |
| return errno; |
| } |
| |
| acm_log(2, "listen active\n"); |
| return 0; |
| } |
| |
| /* Retrieve the listening socket from systemd. */ |
| static int acm_listen_systemd(void) |
| { |
| int fd; |
| |
| int rc = sd_listen_fds(1); |
| if (rc == -1) { |
| fprintf(stderr, "sd_listen_fds failed %d\n", rc); |
| return rc; |
| } |
| |
| if (rc > 2) { |
| fprintf(stderr, |
| "sd_listen_fds returned %d fds, expected <= 2\n", rc); |
| return -1; |
| } |
| |
| for (fd = SD_LISTEN_FDS_START; fd != SD_LISTEN_FDS_START + rc; fd++) { |
| if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, 0)) { |
| /* ListenNetlink for RDMA_NL_GROUP_LS multicast |
| * messages from the kernel |
| */ |
| if (client_array[NL_CLIENT_INDEX].sock != -1) { |
| fprintf(stderr, |
| "sd_listen_fds returned more than one netlink socket\n"); |
| return -1; |
| } |
| client_array[NL_CLIENT_INDEX].sock = fd; |
| |
| /* systemd sets NONBLOCK on the netlink socket, while |
| * we want blocking send to the kernel. |
| */ |
| if (set_fd_nonblock(fd, false)) { |
| fprintf(stderr, |
| "Unable to drop O_NOBLOCK on netlink socket"); |
| return -1; |
| } |
| } else if (sd_is_socket(SD_LISTEN_FDS_START, AF_UNSPEC, |
| SOCK_STREAM, 1)) { |
| /* Socket for user space client communication */ |
| if (listen_socket != -1) { |
| fprintf(stderr, |
| "sd_listen_fds returned more than one listening socket\n"); |
| return -1; |
| } |
| listen_socket = fd; |
| } else { |
| fprintf(stderr, |
| "sd_listen_fds socket is not a SOCK_STREAM/SOCK_NETLINK listening socket\n"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void acm_disconnect_client(struct acmc_client *client) |
| { |
| pthread_mutex_lock(&client->lock); |
| shutdown(client->sock, SHUT_RDWR); |
| close(client->sock); |
| client->sock = -1; |
| pthread_mutex_unlock(&client->lock); |
| (void) atomic_dec(&client->refcnt); |
| } |
| |
| static void acm_svr_accept(void) |
| { |
| int s; |
| int i; |
| |
| acm_log(2, "\n"); |
| s = accept(listen_socket, NULL, NULL); |
| if (s == -1) { |
| acm_log(0, "ERROR - failed to accept connection\n"); |
| return; |
| } |
| |
| for (i = 0; i < FD_SETSIZE - 1; i++) { |
| if (i == NL_CLIENT_INDEX) |
| continue; |
| if (!atomic_get(&client_array[i].refcnt)) |
| break; |
| } |
| |
| if (i == FD_SETSIZE - 1) { |
| acm_log(0, "ERROR - all connections busy - rejecting\n"); |
| close(s); |
| return; |
| } |
| |
| client_array[i].sock = s; |
| atomic_set(&client_array[i].refcnt, 1); |
| acm_log(2, "assigned client %d\n", i); |
| } |
| |
| static int |
| acm_is_path_from_port(struct acmc_port *port, struct ibv_path_record *path) |
| { |
| uint8_t i; |
| |
| if (!ib_any_gid(&path->sgid)) { |
| return (acm_gid_index(&port->port, &path->sgid) < |
| port->gid_cnt); |
| } |
| |
| if (path->slid) { |
| return (port->lid == (be16toh(path->slid) & port->lid_mask)); |
| } |
| |
| if (ib_any_gid(&path->dgid)) { |
| return 1; |
| } |
| |
| if (acm_gid_index(&port->port, &path->dgid) < port->gid_cnt) { |
| return 1; |
| } |
| |
| for (i = 0; i < port->gid_cnt; i++) { |
| if (port->gid_tbl[i].global.subnet_prefix == |
| path->dgid.global.subnet_prefix) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static bool acm_same_partition(uint16_t pkey_a, uint16_t pkey_b) |
| { |
| |
| acm_log(2, "pkey_a: 0x%04x pkey_b: 0x%04x\n", pkey_a, pkey_b); |
| |
| return ((pkey_a | IB_PKEY_FULL_MEMBER) == (pkey_b | IB_PKEY_FULL_MEMBER)); |
| } |
| |
| static struct acmc_addr * |
| acm_get_port_ep_address(struct acmc_port *port, struct acm_ep_addr_data *data) |
| { |
| struct acmc_ep *ep; |
| struct acm_address *addr; |
| int i; |
| |
| if (port->state != IBV_PORT_ACTIVE) |
| return NULL; |
| |
| if (data->type == ACM_EP_INFO_PATH && |
| !acm_is_path_from_port(port, &data->info.path)) |
| return NULL; |
| |
| list_for_each(&port->ep_list, ep, entry) { |
| if ((data->type == ACM_EP_INFO_PATH) && |
| (!data->info.path.pkey || |
| acm_same_partition(be16toh(data->info.path.pkey), ep->endpoint.pkey))) { |
| for (i = 0; i < ep->nmbr_ep_addrs; i++) { |
| if (ep->addr_info[i].addr.type) |
| return &ep->addr_info[i]; |
| } |
| return NULL; |
| } |
| |
| if ((addr = acm_addr_lookup(&ep->endpoint, data->info.addr, |
| (uint8_t) data->type))) |
| return container_of(addr, struct acmc_addr, addr); |
| } |
| |
| return NULL; |
| } |
| |
| static struct acmc_addr *acm_get_ep_address(struct acm_ep_addr_data *data) |
| { |
| struct acmc_device *dev; |
| struct acmc_addr *addr; |
| int i; |
| |
| acm_format_name(2, log_data, sizeof log_data, |
| data->type, data->info.addr, sizeof data->info.addr); |
| acm_log(2, "%s\n", log_data); |
| list_for_each(&dev_list, dev, entry) { |
| for (i = 0; i < dev->port_cnt; i++) { |
| addr = acm_get_port_ep_address(&dev->port[i], data); |
| if (addr) |
| return addr; |
| } |
| } |
| |
| acm_format_name(0, log_data, sizeof log_data, |
| data->type, data->info.addr, sizeof data->info.addr); |
| acm_log(1, "notice - could not find %s\n", log_data); |
| return NULL; |
| } |
| |
| /* If port_num is zero, iterate through all ports, otherwise consider |
| * only the specific port_num */ |
| static struct acmc_ep *acm_get_ep(int index, uint8_t port_num) |
| { |
| struct acmc_device *dev; |
| struct acmc_ep *ep; |
| int i, inx = 0; |
| |
| acm_log(2, "ep index %d\n", index); |
| list_for_each(&dev_list, dev, entry) { |
| for (i = 0; i < dev->port_cnt; i++) { |
| if (port_num && port_num != (i + 1)) |
| continue; |
| if (dev->port[i].state != IBV_PORT_ACTIVE) |
| continue; |
| list_for_each(&dev->port[i].ep_list, ep, entry) { |
| if (index == inx) |
| return ep; |
| ++inx; |
| } |
| } |
| } |
| |
| acm_log(1, "notice - could not find ep %d\n", index); |
| return NULL; |
| } |
| |
| static int |
| acm_svr_query_path(struct acmc_client *client, struct acm_msg *msg) |
| { |
| struct acmc_addr *addr; |
| struct acmc_ep *ep; |
| |
| acm_log(2, "client %d\n", client->index); |
| if (msg->hdr.length != ACM_MSG_HDR_LENGTH + ACM_MSG_EP_LENGTH) { |
| acm_log(0, "ERROR - invalid length: 0x%x\n", msg->hdr.length); |
| return acmc_query_response(client->index, msg, ACM_STATUS_EINVAL); |
| } |
| |
| addr = acm_get_ep_address(&msg->resolve_data[0]); |
| if (!addr) { |
| acm_log(1, "notice - could not find local end point address\n"); |
| return acmc_query_response(client->index, msg, ACM_STATUS_ESRCADDR); |
| } |
| |
| ep = container_of(addr->addr.endpoint, struct acmc_ep, endpoint); |
| return ep->port->prov->query(addr->prov_addr_context, msg, client->index); |
| } |
| |
| static int acm_svr_select_src(struct acm_ep_addr_data *src, struct acm_ep_addr_data *dst) |
| { |
| union socket_addr addr; |
| socklen_t len; |
| int ret; |
| int s; |
| |
| acm_log(2, "selecting source address\n"); |
| memset(&addr, 0, sizeof addr); |
| switch (dst->type) { |
| case ACM_EP_INFO_ADDRESS_IP: |
| addr.sin.sin_family = AF_INET; |
| memcpy(&addr.sin.sin_addr, dst->info.addr, 4); |
| len = sizeof(struct sockaddr_in); |
| break; |
| case ACM_EP_INFO_ADDRESS_IP6: |
| addr.sin6.sin6_family = AF_INET6; |
| memcpy(&addr.sin6.sin6_addr, dst->info.addr, 16); |
| len = sizeof(struct sockaddr_in6); |
| break; |
| default: |
| acm_log(1, "notice - bad destination type, cannot lookup source\n"); |
| return ACM_STATUS_EDESTTYPE; |
| } |
| |
| s = socket(addr.sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); |
| if (s == -1) { |
| acm_log(0, "ERROR - unable to allocate socket\n"); |
| return errno; |
| } |
| |
| ret = connect(s, &addr.sa, len); |
| if (ret) { |
| acm_log(0, "ERROR - unable to connect socket\n"); |
| ret = errno; |
| goto out; |
| } |
| |
| ret = getsockname(s, &addr.sa, &len); |
| if (ret) { |
| acm_log(0, "ERROR - failed to get socket address\n"); |
| ret = errno; |
| goto out; |
| } |
| |
| src->type = dst->type; |
| src->flags = ACM_EP_FLAG_SOURCE; |
| if (dst->type == ACM_EP_INFO_ADDRESS_IP) { |
| memcpy(&src->info.addr, &addr.sin.sin_addr, 4); |
| } else { |
| memcpy(&src->info.addr, &addr.sin6.sin6_addr, 16); |
| } |
| out: |
| close(s); |
| return ret; |
| } |
| |
| /* |
| * Verify the resolve message from the client and return |
| * references to the source and destination addresses. |
| * The message buffer contains extra address data buffers. If a |
| * source address is not given, reference an empty address buffer, |
| * and we'll resolve a source address later. Record the location of |
| * the source and destination addresses in the message header data |
| * to avoid further searches. |
| */ |
| static uint8_t acm_svr_verify_resolve(struct acm_msg *msg) |
| { |
| int i, cnt, have_dst = 0; |
| |
| if (msg->hdr.length < ACM_MSG_HDR_LENGTH) { |
| acm_log(0, "ERROR - invalid msg hdr length %d\n", msg->hdr.length); |
| return ACM_STATUS_EINVAL; |
| } |
| |
| msg->hdr.src_out = 1; |
| cnt = (msg->hdr.length - ACM_MSG_HDR_LENGTH) / ACM_MSG_EP_LENGTH; |
| for (i = 0; i < cnt; i++) { |
| if (msg->resolve_data[i].flags & ACM_EP_FLAG_SOURCE) { |
| if (!msg->hdr.src_out) { |
| acm_log(0, "ERROR - multiple sources specified\n"); |
| return ACM_STATUS_ESRCADDR; |
| } |
| if (!msg->resolve_data[i].type || |
| (msg->resolve_data[i].type >= ACM_ADDRESS_RESERVED)) { |
| acm_log(0, "ERROR - unsupported source address type\n"); |
| return ACM_STATUS_ESRCTYPE; |
| } |
| msg->hdr.src_out = 0; |
| msg->hdr.src_index = i; |
| } |
| if (msg->resolve_data[i].flags & ACM_EP_FLAG_DEST) { |
| if (have_dst) { |
| acm_log(0, "ERROR - multiple destinations specified\n"); |
| return ACM_STATUS_EDESTADDR; |
| } |
| if (!msg->resolve_data[i].type || |
| (msg->resolve_data[i].type >= ACM_ADDRESS_RESERVED)) { |
| acm_log(0, "ERROR - unsupported destination address type\n"); |
| return ACM_STATUS_EDESTTYPE; |
| } |
| have_dst = 1; |
| msg->hdr.dst_index = i; |
| } |
| } |
| |
| if (!have_dst) { |
| acm_log(0, "ERROR - destination address required\n"); |
| return ACM_STATUS_EDESTTYPE; |
| } |
| |
| if (msg->hdr.src_out) { |
| msg->hdr.src_index = i; |
| memset(&msg->resolve_data[i], 0, sizeof(struct acm_ep_addr_data)); |
| } |
| return ACM_STATUS_SUCCESS; |
| } |
| |
| static int |
| acm_svr_resolve_dest(struct acmc_client *client, struct acm_msg *msg) |
| { |
| struct acmc_addr *addr; |
| struct acmc_ep *ep; |
| struct acm_ep_addr_data *saddr, *daddr; |
| uint8_t status; |
| |
| acm_log(2, "client %d\n", client->index); |
| status = acm_svr_verify_resolve(msg); |
| if (status) { |
| acm_log(0, "notice - misformatted or unsupported request\n"); |
| return acmc_resolve_response(client->index, msg, status); |
| } |
| |
| saddr = &msg->resolve_data[msg->hdr.src_index]; |
| daddr = &msg->resolve_data[msg->hdr.dst_index]; |
| if (msg->hdr.src_out) { |
| status = acm_svr_select_src(saddr, daddr); |
| if (status) { |
| acm_log(0, "notice - unable to select suitable source address\n"); |
| return acmc_resolve_response(client->index, msg, status); |
| } |
| } |
| |
| acm_format_name(2, log_data, sizeof log_data, |
| saddr->type, saddr->info.addr, sizeof saddr->info.addr); |
| acm_log(2, "src %s\n", log_data); |
| addr = acm_get_ep_address(saddr); |
| if (!addr) { |
| acm_log(0, "notice - unknown local end point address\n"); |
| return acmc_resolve_response(client->index, msg, ACM_STATUS_ESRCADDR); |
| } |
| |
| ep = container_of(addr->addr.endpoint, struct acmc_ep, endpoint); |
| return ep->port->prov->resolve(addr->prov_addr_context, msg, client->index); |
| } |
| |
| /* |
| * The message buffer contains extra address data buffers. We extract the |
| * destination address from the path record into an extra buffer, so we can |
| * lookup the destination by either LID or GID. |
| */ |
| static int |
| acm_svr_resolve_path(struct acmc_client *client, struct acm_msg *msg) |
| { |
| struct acmc_addr *addr; |
| struct acmc_ep *ep; |
| struct ibv_path_record *path; |
| |
| acm_log(2, "client %d\n", client->index); |
| if (msg->hdr.length < (ACM_MSG_HDR_LENGTH + ACM_MSG_EP_LENGTH)) { |
| acm_log(0, "notice - invalid msg hdr length %d\n", msg->hdr.length); |
| return acmc_resolve_response(client->index, msg, ACM_STATUS_EINVAL); |
| } |
| |
| path = &msg->resolve_data[0].info.path; |
| if (!path->dlid && ib_any_gid(&path->dgid)) { |
| acm_log(0, "notice - no destination specified\n"); |
| return acmc_resolve_response(client->index, msg, |
| ACM_STATUS_EDESTADDR); |
| } |
| |
| acm_format_name(2, log_data, sizeof log_data, ACM_EP_INFO_PATH, |
| msg->resolve_data[0].info.addr, sizeof *path); |
| acm_log(2, "path %s\n", log_data); |
| addr = acm_get_ep_address(&msg->resolve_data[0]); |
| if (!addr) { |
| acm_log(0, "notice - unknown local end point address\n"); |
| return acmc_resolve_response(client->index, msg, |
| ACM_STATUS_ESRCADDR); |
| } |
| |
| ep = container_of(addr->addr.endpoint, struct acmc_ep, endpoint); |
| return ep->port->prov->resolve(addr->prov_addr_context, msg, |
| client->index); |
| } |
| |
| static int acm_svr_resolve(struct acmc_client *client, struct acm_msg *msg) |
| { |
| (void) atomic_inc(&client->refcnt); |
| |
| if (msg->resolve_data[0].type == ACM_EP_INFO_PATH) { |
| if (msg->resolve_data[0].flags & ACM_FLAGS_QUERY_SA) { |
| return acm_svr_query_path(client, msg); |
| } else { |
| return acm_svr_resolve_path(client, msg); |
| } |
| } else { |
| return acm_svr_resolve_dest(client, msg); |
| } |
| } |
| |
| static int acm_svr_perf_query(struct acmc_client *client, struct acm_msg *msg) |
| { |
| int ret, i; |
| uint16_t len; |
| struct acmc_addr *addr; |
| struct acmc_ep *ep = NULL; |
| int index; |
| |
| acm_log(2, "client %d\n", client->index); |
| index = msg->hdr.src_index; |
| msg->hdr.opcode |= ACM_OP_ACK; |
| msg->hdr.status = ACM_STATUS_SUCCESS; |
| msg->hdr.dst_index = 0; |
| |
| if ((be16toh(msg->hdr.length) < (ACM_MSG_HDR_LENGTH + ACM_MSG_EP_LENGTH) |
| && index < 1) || |
| ((be16toh(msg->hdr.length) >= (ACM_MSG_HDR_LENGTH + ACM_MSG_EP_LENGTH) |
| && !(msg->resolve_data[0].flags & ACM_EP_FLAG_SOURCE)))) { |
| for (i = 0; i < ACM_MAX_COUNTER; i++) |
| msg->perf_data[i] = htobe64((uint64_t) atomic_get(&counter[i])); |
| |
| msg->hdr.src_out = ACM_MAX_COUNTER; |
| len = ACM_MSG_HDR_LENGTH + (ACM_MAX_COUNTER * sizeof(uint64_t)); |
| } else { |
| if (index >= 1) { |
| ep = acm_get_ep(index - 1, msg->hdr.src_index); |
| } else { |
| addr = acm_get_ep_address(&msg->resolve_data[0]); |
| if (addr) |
| ep = container_of(addr->addr.endpoint, |
| struct acmc_ep, endpoint); |
| } |
| |
| if (ep) { |
| ep->port->prov->query_perf(ep->prov_ep_context, |
| msg->perf_data, &msg->hdr.src_out); |
| len = ACM_MSG_HDR_LENGTH + (msg->hdr.src_out * sizeof(uint64_t)); |
| } else { |
| msg->hdr.status = ACM_STATUS_ESRCADDR; |
| len = ACM_MSG_HDR_LENGTH; |
| } |
| } |
| msg->hdr.length = htobe16(len); |
| |
| ret = send(client->sock, (char *) msg, len, 0); |
| if (ret != len) |
| acm_log(0, "ERROR - failed to send response\n"); |
| else |
| ret = 0; |
| |
| return ret; |
| } |
| |
| static int may_be_realloc(struct acm_msg **msg_ptr, |
| int len, |
| int cnt, |
| int *cur_msg_siz_ptr, |
| int max_msg_siz) |
| { |
| |
| /* Check if a new address exceeds the protocol constrained max size */ |
| if (len + (cnt + 1) * ACM_MAX_ADDRESS > max_msg_siz) { |
| acm_log(0, "ERROR - unable to amend more addresses to acm_msg due to protocol constraints\n"); |
| return ENOMEM; |
| } |
| |
| /* Check if a new address exceeds current size of msg */ |
| if (len + (cnt + 1) * ACM_MAX_ADDRESS > *cur_msg_siz_ptr) { |
| const size_t chunk_size = 16 * ACM_MAX_ADDRESS; |
| struct acm_msg *new_msg = realloc(*msg_ptr, *cur_msg_siz_ptr + chunk_size); |
| |
| if (!new_msg) { |
| acm_log(0, "ERROR - failed to allocate longer acm_msg\n"); |
| return ENOMEM; |
| } |
| |
| *msg_ptr = new_msg; |
| *cur_msg_siz_ptr += chunk_size; |
| } |
| |
| return 0; |
| } |
| |
| static int acm_svr_ep_query(struct acmc_client *client, struct acm_msg **_msg) |
| { |
| int sts; |
| int ret, i; |
| uint16_t len; |
| struct acmc_ep *ep; |
| int index, cnt = 0; |
| struct acm_msg *msg = *_msg; |
| int cur_msg_siz = sizeof(*msg); |
| int max_msg_siz = USHRT_MAX; |
| |
| acm_log(2, "client %d\n", client->index); |
| index = msg->hdr.src_out; |
| ep = acm_get_ep(index - 1, msg->hdr.src_index); |
| if (ep) { |
| msg->hdr.status = ACM_STATUS_SUCCESS; |
| msg->ep_data.dev_guid = ep->port->dev->device.dev_guid; |
| msg->ep_data.port_num = ep->port->port.port_num; |
| msg->ep_data.phys_port_cnt = ep->port->dev->port_cnt; |
| msg->ep_data.pkey = htobe16(ep->endpoint.pkey); |
| strncpy((char *)msg->ep_data.prov_name, ep->port->prov->name, |
| ACM_MAX_PROV_NAME - 1); |
| msg->ep_data.prov_name[ACM_MAX_PROV_NAME - 1] = '\0'; |
| len = ACM_MSG_HDR_LENGTH + sizeof(struct acm_ep_config_data); |
| for (i = 0; i < ep->nmbr_ep_addrs; i++) { |
| if (ep->addr_info[i].addr.type != ACM_ADDRESS_INVALID) { |
| sts = may_be_realloc(_msg, len, cnt, &cur_msg_siz, max_msg_siz); |
| msg = *_msg; |
| if (sts) |
| break; |
| memcpy(msg->ep_data.addrs[cnt++].name, |
| ep->addr_info[i].string_buf, |
| ACM_MAX_ADDRESS); |
| } |
| } |
| msg->ep_data.addr_cnt = htobe16(cnt); |
| len += cnt * ACM_MAX_ADDRESS; |
| } else { |
| msg->hdr.status = ACM_STATUS_EINVAL; |
| len = ACM_MSG_HDR_LENGTH; |
| } |
| msg->hdr.opcode |= ACM_OP_ACK; |
| msg->hdr.src_index = 0; |
| msg->hdr.dst_index = 0; |
| msg->hdr.length = htobe16(len); |
| |
| ret = send(client->sock, (char *) msg, len, 0); |
| if (ret != len) |
| acm_log(0, "ERROR - failed to send response\n"); |
| else |
| ret = 0; |
| |
| return ret; |
| } |
| |
| static int acm_msg_length(struct acm_msg *msg) |
| { |
| return (msg->hdr.opcode == ACM_OP_RESOLVE) ? |
| msg->hdr.length : be16toh(msg->hdr.length); |
| } |
| |
| static void acm_svr_receive(struct acmc_client *client) |
| { |
| struct acm_msg *msg = malloc(sizeof(*msg)); |
| int ret; |
| |
| if (!msg) { |
| acm_log(0, "ERROR - Unable to alloc acm_msg\n"); |
| ret = ENOMEM; |
| goto out; |
| } |
| |
| acm_log(2, "client %d\n", client->index); |
| ret = recv(client->sock, (char *)msg, sizeof(*msg), 0); |
| if (ret <= 0 || ret != acm_msg_length(msg)) { |
| acm_log(2, "client disconnected\n"); |
| ret = ACM_STATUS_ENOTCONN; |
| goto out; |
| } |
| |
| if (msg->hdr.version != ACM_VERSION) { |
| acm_log(0, "ERROR - unsupported version %d\n", msg->hdr.version); |
| goto out; |
| } |
| |
| switch (msg->hdr.opcode & ACM_OP_MASK) { |
| case ACM_OP_RESOLVE: |
| atomic_inc(&counter[ACM_CNTR_RESOLVE]); |
| ret = acm_svr_resolve(client, msg); |
| break; |
| case ACM_OP_PERF_QUERY: |
| ret = acm_svr_perf_query(client, msg); |
| break; |
| case ACM_OP_EP_QUERY: |
| ret = acm_svr_ep_query(client, &msg); |
| break; |
| default: |
| acm_log(0, "ERROR - unknown opcode 0x%x\n", msg->hdr.opcode); |
| break; |
| } |
| |
| out: |
| free(msg); |
| if (ret) |
| acm_disconnect_client(client); |
| } |
| |
| static int acm_nl_to_addr_data(struct acm_ep_addr_data *ad, |
| int af_family, uint8_t *addr, size_t addr_len) |
| { |
| if (addr_len > ACM_MAX_ADDRESS) |
| return EINVAL; |
| |
| /* find the ep associated with this address "if any" */ |
| switch (af_family) { |
| case AF_INET: |
| ad->type = ACM_ADDRESS_IP; |
| break; |
| case AF_INET6: |
| ad->type = ACM_ADDRESS_IP6; |
| break; |
| default: |
| return EINVAL; |
| } |
| memcpy(&ad->info.addr, addr, addr_len); |
| return 0; |
| } |
| |
| static void acm_add_ep_ip(char *ifname, struct acm_ep_addr_data *data, char *ip_str) |
| { |
| struct acmc_ep *ep; |
| struct acmc_device *dev; |
| uint8_t port_num; |
| uint16_t pkey; |
| union ibv_gid sgid; |
| struct acmc_addr *addr; |
| |
| addr = acm_get_ep_address(data); |
| if (addr) { |
| acm_log(1, "Address '%s' already available\n", ip_str); |
| return; |
| } |
| |
| if (acm_if_get_sgid(ifname, &sgid)) |
| return; |
| |
| dev = acm_get_device_from_gid(&sgid, &port_num); |
| if (!dev) |
| return; |
| |
| if (acm_if_get_pkey(ifname, &pkey)) |
| return; |
| |
| acm_log(0, " %s\n", ip_str); |
| |
| ep = acm_find_ep(&dev->port[port_num - 1], pkey); |
| if (ep) { |
| if (acm_ep_insert_addr(ep, ip_str, data->info.addr, |
| data->type)) |
| acm_log(0, "Failed to add '%s' to EP\n", ip_str); |
| } else { |
| acm_log(0, "Failed to add '%s' no EP for pkey\n", ip_str); |
| } |
| } |
| |
| static void acm_rm_ep_ip(struct acm_ep_addr_data *data) |
| { |
| struct acmc_ep *ep; |
| struct acmc_addr *addr; |
| |
| addr = acm_get_ep_address(data); |
| if (addr) { |
| ep = container_of(addr->addr.endpoint, struct acmc_ep, endpoint); |
| acm_format_name(0, log_data, sizeof log_data, |
| data->type, data->info.addr, sizeof data->info.addr); |
| acm_log(0, " %s\n", log_data); |
| acm_mark_addr_invalid(ep, data); |
| } |
| } |
| |
| static int acm_ipnl_create(void) |
| { |
| struct sockaddr_nl addr; |
| |
| if ((ip_mon_socket = socket(PF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, NETLINK_ROUTE)) == -1) { |
| acm_log(0, "Failed to open NETLINK_ROUTE socket"); |
| return EIO; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.nl_family = AF_NETLINK; |
| addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR; |
| |
| if (bind(ip_mon_socket, (struct sockaddr *)&addr, sizeof(addr)) == -1) { |
| acm_log(0, "Failed to bind NETLINK_ROUTE socket"); |
| return EIO; |
| } |
| |
| return 0; |
| } |
| |
| static void acm_ip_iter_cb(char *ifname, union ibv_gid *gid, uint16_t pkey, |
| uint8_t addr_type, uint8_t *addr, |
| char *ip_str, void *ctx) |
| { |
| int ret = EINVAL; |
| struct acmc_device *dev; |
| struct acmc_ep *ep; |
| uint8_t port_num; |
| char gid_str[INET6_ADDRSTRLEN]; |
| |
| dev = acm_get_device_from_gid(gid, &port_num); |
| if (dev) { |
| ep = acm_find_ep(&dev->port[port_num - 1], pkey); |
| if (ep) |
| ret = acm_ep_insert_addr(ep, ip_str, addr, addr_type); |
| } |
| |
| if (ret) { |
| inet_ntop(AF_INET6, gid->raw, gid_str, sizeof(gid_str)); |
| acm_log(0, "Failed to add '%s' (gid %s; pkey 0x%x)\n", |
| ip_str, gid_str, pkey); |
| } |
| } |
| |
| /* Netlink updates have indicated a failure which means we are no longer in |
| * sync. This should be a rare condition so we handle this with a "big |
| * hammer" by clearing and re-reading all the system IP's. |
| */ |
| static int resync_system_ips(void) |
| { |
| struct acmc_device *dev; |
| struct acmc_port *port; |
| struct acmc_ep *ep; |
| int i, cnt; |
| |
| acm_log(0, "Resyncing all IP's\n"); |
| |
| /* mark all IP's invalid */ |
| list_for_each(&dev_list, dev, entry) { |
| for (cnt = 0; cnt < dev->port_cnt; cnt++) { |
| port = &dev->port[cnt]; |
| |
| list_for_each(&port->ep_list, ep, entry) { |
| for (i = 0; i < ep->nmbr_ep_addrs; i++) { |
| if (ep->addr_info[i].addr.type == ACM_ADDRESS_IP || |
| ep->addr_info[i].addr.type == ACM_ADDRESS_IP6) |
| ep->addr_info[i].addr.type = ACM_ADDRESS_INVALID; |
| } |
| } |
| } |
| } |
| |
| return acm_if_iter_sys(acm_ip_iter_cb, NULL); |
| } |
| |
| static void acm_ipnl_handler(void) |
| { |
| int len; |
| char buffer[NL_MSG_BUF_SIZE]; |
| struct nlmsghdr *nlh; |
| char ifname[IFNAMSIZ]; |
| char ip_str[INET6_ADDRSTRLEN]; |
| struct acm_ep_addr_data ad; |
| |
| while ((len = recv(ip_mon_socket, buffer, NL_MSG_BUF_SIZE, 0)) > 0) { |
| nlh = (struct nlmsghdr *)buffer; |
| while ((NLMSG_OK(nlh, len)) && (nlh->nlmsg_type != NLMSG_DONE)) { |
| struct ifaddrmsg *ifa = (struct ifaddrmsg *) NLMSG_DATA(nlh); |
| struct ifinfomsg *ifi = (struct ifinfomsg *) NLMSG_DATA(nlh); |
| struct rtattr *rth = IFA_RTA(ifa); |
| int rtl = IFA_PAYLOAD(nlh); |
| |
| switch (nlh->nlmsg_type) { |
| case RTM_NEWADDR: |
| if_indextoname(ifa->ifa_index, ifname); |
| while (rtl && RTA_OK(rth, rtl)) { |
| if (rth->rta_type == IFA_LOCAL) { |
| acm_log(1, "New system address available %s : %s\n", |
| ifname, inet_ntop(ifa->ifa_family, RTA_DATA(rth), |
| ip_str, sizeof(ip_str))); |
| if (!acm_nl_to_addr_data(&ad, ifa->ifa_family, |
| RTA_DATA(rth), |
| RTA_PAYLOAD(rth))) { |
| acm_add_ep_ip(ifname, &ad, ip_str); |
| } |
| } |
| rth = RTA_NEXT(rth, rtl); |
| } |
| break; |
| case RTM_DELADDR: |
| if_indextoname(ifa->ifa_index, ifname); |
| while (rtl && RTA_OK(rth, rtl)) { |
| if (rth->rta_type == IFA_LOCAL) { |
| acm_log(1, "System address removed %s : %s\n", |
| ifname, inet_ntop(ifa->ifa_family, RTA_DATA(rth), |
| ip_str, sizeof(ip_str))); |
| if (!acm_nl_to_addr_data(&ad, ifa->ifa_family, |
| RTA_DATA(rth), |
| RTA_PAYLOAD(rth))) { |
| acm_rm_ep_ip(&ad); |
| } |
| } |
| rth = RTA_NEXT(rth, rtl); |
| } |
| break; |
| case RTM_NEWLINK: |
| acm_log(2, "Link added : %s\n", |
| if_indextoname(ifi->ifi_index, ifname)); |
| break; |
| case RTM_DELLINK: |
| acm_log(2, "Link removed : %s\n", |
| if_indextoname(ifi->ifi_index, ifname)); |
| break; |
| default: |
| acm_log(2, "unknown netlink message\n"); |
| break; |
| } |
| nlh = NLMSG_NEXT(nlh, len); |
| } |
| } |
| |
| if (len < 0 && errno == ENOBUFS) { |
| acm_log(0, "ENOBUFS returned from netlink...\n"); |
| resync_system_ips(); |
| } |
| } |
| |
| static int acm_nl_send(int sock, struct acm_msg *msg) |
| { |
| struct sockaddr_nl dst_addr; |
| struct acm_nl_msg acmnlmsg; |
| struct acm_nl_msg *orig; |
| int ret; |
| int datalen; |
| |
| orig = (struct acm_nl_msg *)(uintptr_t)msg->hdr.tid; |
| |
| memset(&dst_addr, 0, sizeof(dst_addr)); |
| dst_addr.nl_family = AF_NETLINK; |
| dst_addr.nl_groups = (1 << (RDMA_NL_GROUP_LS - 1)); |
| |
| memset(&acmnlmsg, 0, sizeof(acmnlmsg)); |
| acmnlmsg.nlmsg_header.nlmsg_len = NLMSG_HDRLEN; |
| acmnlmsg.nlmsg_header.nlmsg_pid = getpid(); |
| acmnlmsg.nlmsg_header.nlmsg_type = orig->nlmsg_header.nlmsg_type; |
| acmnlmsg.nlmsg_header.nlmsg_seq = orig->nlmsg_header.nlmsg_seq; |
| |
| if (msg->hdr.status != ACM_STATUS_SUCCESS) { |
| acm_log(2, "acm status no success = %d\n", msg->hdr.status); |
| acmnlmsg.nlmsg_header.nlmsg_flags |= RDMA_NL_LS_F_ERR; |
| } else { |
| acm_log(2, "acm status success\n"); |
| acmnlmsg.nlmsg_header.nlmsg_len += |
| NLA_ALIGN(sizeof(struct acm_nl_path)); |
| acmnlmsg.path[0].attr_hdr.nla_type = LS_NLA_TYPE_PATH_RECORD; |
| acmnlmsg.path[0].attr_hdr.nla_len = sizeof(struct acm_nl_path); |
| if (orig->resolve_header.path_use == |
| LS_RESOLVE_PATH_USE_UNIDIRECTIONAL) |
| acmnlmsg.path[0].rec.flags = IB_PATH_PRIMARY | |
| IB_PATH_OUTBOUND; |
| else |
| acmnlmsg.path[0].rec.flags = IB_PATH_PRIMARY | |
| IB_PATH_GMP | IB_PATH_BIDIRECTIONAL; |
| memcpy(acmnlmsg.path[0].rec.path_rec, |
| &msg->resolve_data[0].info.path, |
| sizeof(struct ibv_path_record)); |
| } |
| |
| datalen = NLMSG_ALIGN(acmnlmsg.nlmsg_header.nlmsg_len); |
| ret = sendto(sock, &acmnlmsg, datalen, 0, |
| (const struct sockaddr *)&dst_addr, |
| (socklen_t)sizeof(dst_addr)); |
| if (ret != datalen) { |
| acm_log(0, "ERROR - sendto = %d errno = %d\n", ret, errno); |
| ret = -1; |
| } else { |
| ret = msg->hdr.length; |
| } |
| |
| free(orig); |
| |
| return ret; |
| } |
| |
| #define NLA_LEN(nla) ((nla)->nla_len - NLA_HDRLEN) |
| #define NLA_DATA(nla) ((char *)(nla) + NLA_HDRLEN) |
| |
| static int acm_nl_parse_path_attr(struct nlattr *attr, |
| struct acm_ep_addr_data *data) |
| { |
| struct ibv_path_record *path; |
| uint64_t *sid; |
| struct rdma_nla_ls_gid *gid; |
| uint8_t *tcl; |
| uint16_t *pkey; |
| uint16_t *qos; |
| uint16_t val; |
| int ret = 0; |
| |
| #define IBV_PATH_RECORD_QOS_MASK 0xfff0 |
| |
| path = &data->info.path; |
| switch (attr->nla_type & RDMA_NLA_TYPE_MASK) { |
| case LS_NLA_TYPE_SERVICE_ID: |
| sid = (uint64_t *) NLA_DATA(attr); |
| if (NLA_LEN(attr) == sizeof(*sid)) { |
| acm_log(2, "service_id 0x%" PRIx64 "\n", *sid); |
| path->service_id = htobe64(*sid); |
| } else { |
| ret = -1; |
| } |
| break; |
| |
| case LS_NLA_TYPE_DGID: |
| gid = (struct rdma_nla_ls_gid *) NLA_DATA(attr); |
| if (NLA_LEN(attr) == sizeof(gid->gid)) { |
| acm_format_name(2, log_data, sizeof(log_data), |
| ACM_ADDRESS_GID, gid->gid, |
| sizeof(union ibv_gid)); |
| acm_log(2, "path dgid %s\n", log_data); |
| memcpy(path->dgid.raw, gid->gid, sizeof(path->dgid)); |
| data->flags |= ACM_EP_FLAG_DEST; |
| } else { |
| ret = -1; |
| } |
| break; |
| |
| case LS_NLA_TYPE_SGID: |
| gid = (struct rdma_nla_ls_gid *) NLA_DATA(attr); |
| if (NLA_LEN(attr) == sizeof(gid->gid)) { |
| acm_format_name(2, log_data, sizeof(log_data), |
| ACM_ADDRESS_GID, gid->gid, |
| sizeof(union ibv_gid)); |
| acm_log(2, "path sgid %s\n", log_data); |
| memcpy(path->sgid.raw, gid->gid, sizeof(path->sgid)); |
| data->flags |= ACM_EP_FLAG_SOURCE; |
| } else { |
| ret = -1; |
| } |
| break; |
| |
| case LS_NLA_TYPE_TCLASS: |
| tcl = (uint8_t *) NLA_DATA(attr); |
| if (NLA_LEN(attr) == sizeof(*tcl)) { |
| acm_log(2, "tclass 0x%x\n", *tcl); |
| path->tclass = *tcl; |
| } else { |
| ret = -1; |
| } |
| break; |
| |
| case LS_NLA_TYPE_PKEY: |
| pkey = (uint16_t *) NLA_DATA(attr); |
| if (NLA_LEN(attr) == sizeof(*pkey)) { |
| acm_log(2, "pkey 0x%x\n", *pkey); |
| path->pkey = htobe16(*pkey); |
| } else { |
| ret = -1; |
| } |
| break; |
| |
| case LS_NLA_TYPE_QOS_CLASS: |
| qos = (uint16_t *) NLA_DATA(attr); |
| if (NLA_LEN(attr) == sizeof(*qos)) { |
| acm_log(2, "qos_class 0x%x\n", *qos); |
| val = be16toh(path->qosclass_sl); |
| val &= ~IBV_PATH_RECORD_QOS_MASK; |
| val |= (*qos & IBV_PATH_RECORD_QOS_MASK); |
| path->qosclass_sl = htobe16(val); |
| } else { |
| ret = -1; |
| } |
| break; |
| |
| default: |
| acm_log(1, "WARN: unknown attr %x\n", attr->nla_type); |
| /* We can not ignore a mandatory attribute */ |
| if (attr->nla_type & RDMA_NLA_F_MANDATORY) |
| ret = -1; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void acm_nl_process_invalid_request(struct acmc_client *client, |
| struct acm_nl_msg *acmnlmsg) |
| { |
| struct acm_msg msg; |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.hdr.opcode = ACM_OP_RESOLVE; |
| msg.hdr.version = ACM_VERSION; |
| msg.hdr.length = ACM_MSG_HDR_LENGTH; |
| msg.hdr.status = ACM_STATUS_EINVAL; |
| msg.hdr.tid = (uintptr_t) acmnlmsg; |
| |
| acm_nl_send(client->sock, &msg); |
| } |
| |
| static void acm_nl_process_resolve(struct acmc_client *client, |
| struct acm_nl_msg *acmnlmsg) |
| { |
| struct acm_msg msg; |
| struct nlattr *attr; |
| int payload_len; |
| int resolve_hdr_len; |
| int rem; |
| int total_attr_len; |
| int status; |
| unsigned char *data; |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.hdr.opcode = ACM_OP_RESOLVE; |
| msg.hdr.version = ACM_VERSION; |
| msg.hdr.length = ACM_MSG_HDR_LENGTH + ACM_MSG_EP_LENGTH; |
| msg.hdr.status = ACM_STATUS_SUCCESS; |
| msg.hdr.tid = (uintptr_t) acmnlmsg; |
| msg.resolve_data[0].type = ACM_EP_INFO_PATH; |
| |
| /* We support only one pathrecord */ |
| acm_log(2, "path use 0x%x\n", acmnlmsg->resolve_header.path_use); |
| if (acmnlmsg->resolve_header.path_use == |
| LS_RESOLVE_PATH_USE_UNIDIRECTIONAL) |
| msg.resolve_data[0].info.path.reversible_numpath = 1; |
| else |
| msg.resolve_data[0].info.path.reversible_numpath = |
| IBV_PATH_RECORD_REVERSIBLE | 1; |
| |
| data = (unsigned char *) &acmnlmsg->nlmsg_header + NLMSG_HDRLEN; |
| resolve_hdr_len = NLMSG_ALIGN(sizeof(struct rdma_ls_resolve_header)); |
| attr = (struct nlattr *) (data + resolve_hdr_len); |
| payload_len = acmnlmsg->nlmsg_header.nlmsg_len - NLMSG_HDRLEN - |
| resolve_hdr_len; |
| rem = payload_len; |
| while (1) { |
| if (rem < (int) sizeof(*attr) || |
| attr->nla_len < sizeof(*attr) || |
| attr->nla_len > rem) |
| break; |
| |
| status = acm_nl_parse_path_attr(attr, &msg.resolve_data[0]); |
| if (status) { |
| acm_nl_process_invalid_request(client, acmnlmsg); |
| return; |
| } |
| |
| /* Next attribute */ |
| total_attr_len = NLA_ALIGN(attr->nla_len); |
| rem -= total_attr_len; |
| attr = (struct nlattr *) ((char *) attr + total_attr_len); |
| } |
| |
| atomic_inc(&counter[ACM_CNTR_RESOLVE]); |
| acm_svr_resolve(client, &msg); |
| } |
| |
| static int acm_nl_is_valid_resolve_request(struct acm_nl_msg *acmnlmsg) |
| { |
| int payload_len; |
| |
| payload_len = acmnlmsg->nlmsg_header.nlmsg_len - NLMSG_HDRLEN; |
| if (payload_len < (sizeof(struct rdma_ls_resolve_header) + |
| sizeof(struct nlattr))) |
| return 0; |
| |
| return 1; |
| } |
| |
| static void acm_nl_receive(struct acmc_client *client) |
| { |
| struct acm_nl_msg *acmnlmsg; |
| int datalen = sizeof(*acmnlmsg); |
| int ret; |
| uint16_t client_inx, op; |
| |
| acmnlmsg = calloc(1, sizeof(*acmnlmsg)); |
| if (!acmnlmsg) { |
| acm_log(0, "Out of memory for recving nl msg.\n"); |
| return; |
| } |
| ret = recv(client->sock, acmnlmsg, datalen, 0); |
| if (!NLMSG_OK(&acmnlmsg->nlmsg_header, ret)) { |
| acm_log(0, "Netlink receive error: %d.\n", ret); |
| goto rcv_cleanup; |
| } |
| |
| acm_log(2, "nlmsg: len %d type 0x%x flags 0x%x seq %d pid %d\n", |
| acmnlmsg->nlmsg_header.nlmsg_len, |
| acmnlmsg->nlmsg_header.nlmsg_type, |
| acmnlmsg->nlmsg_header.nlmsg_flags, |
| acmnlmsg->nlmsg_header.nlmsg_seq, |
| acmnlmsg->nlmsg_header.nlmsg_pid); |
| |
| /* Currently we handle only request from the local service client */ |
| client_inx = RDMA_NL_GET_CLIENT(acmnlmsg->nlmsg_header.nlmsg_type); |
| op = RDMA_NL_GET_OP(acmnlmsg->nlmsg_header.nlmsg_type); |
| if (client_inx != RDMA_NL_LS) { |
| acm_log_once(0, "ERROR - Unknown NL client ID (%d)\n", client_inx); |
| goto rcv_cleanup; |
| } |
| |
| switch (op) { |
| case RDMA_NL_LS_OP_RESOLVE: |
| if (acm_nl_is_valid_resolve_request(acmnlmsg)) |
| acm_nl_process_resolve(client, acmnlmsg); |
| else |
| acm_nl_process_invalid_request(client, acmnlmsg); |
| break; |
| default: |
| /* Not supported*/ |
| acm_log_once(0, "WARN - invalid opcode %x\n", op); |
| acm_nl_process_invalid_request(client, acmnlmsg); |
| break; |
| } |
| |
| return; |
| rcv_cleanup: |
| free(acmnlmsg); |
| } |
| |
| static int acm_init_nl(void) |
| { |
| struct sockaddr_nl src_addr; |
| int ret; |
| int nl_rcv_socket; |
| |
| nl_rcv_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_RDMA); |
| if (nl_rcv_socket == -1) { |
| acm_log(0, "ERROR - unable to allocate netlink recv socket\n"); |
| return errno; |
| } |
| |
| memset(&src_addr, 0, sizeof(src_addr)); |
| src_addr.nl_family = AF_NETLINK; |
| src_addr.nl_pid = getpid(); |
| src_addr.nl_groups = (1 << (RDMA_NL_GROUP_LS - 1)); |
| |
| ret = bind(nl_rcv_socket, (struct sockaddr *)&src_addr, |
| sizeof(src_addr)); |
| if (ret == -1) { |
| acm_log(0, "ERROR - unable to bind netlink socket\n"); |
| close(nl_rcv_socket); |
| return errno; |
| } |
| |
| /* init nl client structure */ |
| client_array[NL_CLIENT_INDEX].sock = nl_rcv_socket; |
| return 0; |
| } |
| |
| static void acm_server(bool systemd) |
| { |
| fd_set readfds; |
| int i, n, ret; |
| struct acmc_device *dev; |
| |
| acm_log(0, "started\n"); |
| acm_init_server(); |
| |
| client_array[NL_CLIENT_INDEX].sock = -1; |
| listen_socket = -1; |
| if (systemd) { |
| ret = acm_listen_systemd(); |
| if (ret) { |
| acm_log(0, "ERROR - systemd server listen failed\n"); |
| return; |
| } |
| } |
| |
| if (listen_socket == -1) { |
| ret = acm_listen(); |
| if (ret) { |
| acm_log(0, "ERROR - server listen failed\n"); |
| return; |
| } |
| } |
| |
| if (client_array[NL_CLIENT_INDEX].sock == -1) { |
| ret = acm_init_nl(); |
| if (ret) |
| acm_log(1, "Warn - Netlink init failed\n"); |
| } |
| |
| if (systemd) |
| sd_notify(0, "READY=1"); |
| |
| while (1) { |
| n = (int) listen_socket; |
| FD_ZERO(&readfds); |
| FD_SET(listen_socket, &readfds); |
| n = max(n, (int) ip_mon_socket); |
| FD_SET(ip_mon_socket, &readfds); |
| |
| for (i = 0; i < FD_SETSIZE - 1; i++) { |
| if (client_array[i].sock != -1) { |
| FD_SET(client_array[i].sock, &readfds); |
| n = max(n, (int) client_array[i].sock); |
| } |
| } |
| |
| list_for_each(&dev_list, dev, entry) { |
| FD_SET(dev->device.verbs->async_fd, &readfds); |
| n = max(n, (int) dev->device.verbs->async_fd); |
| } |
| |
| ret = select(n + 1, &readfds, NULL, NULL, NULL); |
| if (ret == -1) { |
| acm_log(0, "ERROR - server select error\n"); |
| continue; |
| } |
| |
| if (FD_ISSET(listen_socket, &readfds)) |
| acm_svr_accept(); |
| |
| if (FD_ISSET(ip_mon_socket, &readfds)) |
| acm_ipnl_handler(); |
| |
| for (i = 0; i < FD_SETSIZE - 1; i++) { |
| if (client_array[i].sock != -1 && |
| FD_ISSET(client_array[i].sock, &readfds)) { |
| acm_log(2, "receiving from client %d\n", i); |
| if (i == NL_CLIENT_INDEX) |
| acm_nl_receive(&client_array[i]); |
| else |
| acm_svr_receive(&client_array[i]); |
| } |
| } |
| |
| list_for_each(&dev_list, dev, entry) { |
| if (FD_ISSET(dev->device.verbs->async_fd, &readfds)) { |
| acm_log(2, "handling event from %s\n", |
| dev->device.verbs->device->name); |
| acm_event_handler(dev); |
| } |
| } |
| } |
| } |
| |
| enum ibv_rate acm_get_rate(uint8_t width, uint8_t speed) |
| { |
| switch (width) { |
| case 1: /* 1x */ |
| switch (speed) { |
| case 1: return IBV_RATE_2_5_GBPS; |
| case 2: return IBV_RATE_5_GBPS; |
| case 4: /* fall through */ |
| case 8: return IBV_RATE_10_GBPS; |
| case 16: return IBV_RATE_14_GBPS; |
| case 32: return IBV_RATE_25_GBPS; |
| default: return IBV_RATE_MAX; |
| } |
| case 2: /* 4x */ |
| switch (speed) { |
| case 1: return IBV_RATE_10_GBPS; |
| case 2: return IBV_RATE_20_GBPS; |
| case 4: /* fall through */ |
| case 8: return IBV_RATE_40_GBPS; |
| case 16: return IBV_RATE_56_GBPS; |
| case 32: return IBV_RATE_100_GBPS; |
| default: return IBV_RATE_MAX; |
| } |
| case 4: /* 8x */ |
| switch (speed) { |
| case 1: return IBV_RATE_20_GBPS; |
| case 2: return IBV_RATE_40_GBPS; |
| case 4: /* fall through */ |
| case 8: return IBV_RATE_80_GBPS; |
| case 16: return IBV_RATE_112_GBPS; |
| case 32: return IBV_RATE_200_GBPS; |
| default: return IBV_RATE_MAX; |
| } |
| case 8: /* 12x */ |
| switch (speed) { |
| case 1: return IBV_RATE_30_GBPS; |
| case 2: return IBV_RATE_60_GBPS; |
| case 4: /* fall through */ |
| case 8: return IBV_RATE_120_GBPS; |
| case 16: return IBV_RATE_168_GBPS; |
| case 32: return IBV_RATE_300_GBPS; |
| default: return IBV_RATE_MAX; |
| } |
| default: |
| acm_log(0, "ERROR - unknown link width 0x%x\n", width); |
| return IBV_RATE_MAX; |
| } |
| } |
| |
| enum ibv_mtu acm_convert_mtu(int mtu) |
| { |
| switch (mtu) { |
| case 256: return IBV_MTU_256; |
| case 512: return IBV_MTU_512; |
| case 1024: return IBV_MTU_1024; |
| case 2048: return IBV_MTU_2048; |
| case 4096: return IBV_MTU_4096; |
| default: return IBV_MTU_2048; |
| } |
| } |
| |
| enum ibv_rate acm_convert_rate(int rate) |
| { |
| switch (rate) { |
| case 2: return IBV_RATE_2_5_GBPS; |
| case 5: return IBV_RATE_5_GBPS; |
| case 10: return IBV_RATE_10_GBPS; |
| case 20: return IBV_RATE_20_GBPS; |
| case 30: return IBV_RATE_30_GBPS; |
| case 40: return IBV_RATE_40_GBPS; |
| case 60: return IBV_RATE_60_GBPS; |
| case 80: return IBV_RATE_80_GBPS; |
| case 120: return IBV_RATE_120_GBPS; |
| case 14: return IBV_RATE_14_GBPS; |
| case 56: return IBV_RATE_56_GBPS; |
| case 112: return IBV_RATE_112_GBPS; |
| case 168: return IBV_RATE_168_GBPS; |
| case 25: return IBV_RATE_25_GBPS; |
| case 100: return IBV_RATE_100_GBPS; |
| case 200: return IBV_RATE_200_GBPS; |
| case 300: return IBV_RATE_300_GBPS; |
| default: return IBV_RATE_10_GBPS; |
| } |
| } |
| |
| static FILE *acm_open_addr_file(void) |
| { |
| FILE *f; |
| |
| if ((f = fopen(addr_file, "r"))) |
| return f; |
| |
| acm_log(0, "notice - generating %s file\n", addr_file); |
| if (!(f = popen(acme, "r"))) { |
| acm_log(0, "ERROR - cannot generate %s\n", addr_file); |
| return NULL; |
| } |
| pclose(f); |
| return fopen(addr_file, "r"); |
| } |
| |
| static int |
| __acm_ep_insert_addr(struct acmc_ep *ep, const char *name, uint8_t *addr, |
| uint8_t addr_type) |
| { |
| int i; |
| int ret; |
| uint8_t tmp[ACM_MAX_ADDRESS] = {}; |
| |
| memcpy(tmp, addr, acm_addr_len(addr_type)); |
| |
| for (i = 0; (i < ep->nmbr_ep_addrs) && |
| (ep->addr_info[i].addr.type != ACM_ADDRESS_INVALID); i++) |
| ; |
| if (i == ep->nmbr_ep_addrs) { |
| struct acmc_addr *new_info; |
| int j; |
| |
| new_info = realloc(ep->addr_info, (i + 1) * sizeof(*ep->addr_info)); |
| if (!new_info) { |
| ret = ENOMEM; |
| goto out; |
| } |
| |
| /* id_string needs to point to the reallocated string_buf */ |
| for (j = 0; (j < ep->nmbr_ep_addrs); j++) { |
| new_info[j].addr.id_string = new_info[j].string_buf; |
| } |
| |
| ep->addr_info = new_info; |
| |
| /* Added memory is not initialized */ |
| memset(ep->addr_info + i, 0, sizeof(*ep->addr_info)); |
| ep->addr_info[i].addr.endpoint = &ep->endpoint; |
| ep->addr_info[i].addr.id_string = ep->addr_info[i].string_buf; |
| ++ep->nmbr_ep_addrs; |
| } |
| |
| /* Open the provider endpoint only if at least a name or |
| address is found */ |
| if (!ep->prov_ep_context) { |
| ret = ep->port->prov->open_endpoint(&ep->endpoint, |
| ep->port->prov_port_context, |
| &ep->prov_ep_context); |
| if (ret) { |
| acm_log(0, "Error: failed to open prov ep\n"); |
| goto out; |
| } |
| } |
| ep->addr_info[i].addr.type = addr_type; |
| if (!check_snprintf(ep->addr_info[i].string_buf, |
| sizeof(ep->addr_info[i].string_buf), "%s", name)) |
| return EINVAL; |
| memcpy(ep->addr_info[i].addr.info.addr, tmp, ACM_MAX_ADDRESS); |
| ret = ep->port->prov->add_address(&ep->addr_info[i].addr, |
| ep->prov_ep_context, |
| &ep->addr_info[i].prov_addr_context); |
| if (ret) { |
| acm_log(0, "Error: failed to add addr to provider\n"); |
| ep->addr_info[i].addr.type = ACM_ADDRESS_INVALID; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int |
| acm_ep_insert_addr(struct acmc_ep *ep, const char *name, uint8_t *addr, |
| uint8_t addr_type) |
| { |
| int ret = -1; |
| |
| if (!acm_addr_lookup(&ep->endpoint, addr, addr_type)) { |
| ret = __acm_ep_insert_addr(ep, name, addr, addr_type); |
| } |
| |
| return ret; |
| } |
| |
| static struct acmc_device * |
| acm_get_device_from_gid(union ibv_gid *sgid, uint8_t *port) |
| { |
| struct acmc_device *dev; |
| int i; |
| |
| list_for_each(&dev_list, dev, entry) { |
| for (*port = 1; *port <= dev->port_cnt; (*port)++) { |
| |
| for (i = 0; i < dev->port[*port - 1].gid_cnt; i++) { |
| |
| if (!memcmp(sgid->raw, |
| dev->port[*port - 1].gid_tbl[i].raw, |
| sizeof(*sgid))) |
| return dev; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| static void acm_ep_ip_iter_cb(char *ifname, union ibv_gid *gid, uint16_t pkey, |
| uint8_t addr_type, uint8_t *addr, |
| char *ip_str, void *ctx) |
| { |
| uint8_t port_num; |
| struct acmc_device *dev; |
| struct acmc_ep *ep = ctx; |
| |
| dev = acm_get_device_from_gid(gid, &port_num); |
| if (dev && ep->port->dev == dev |
| && ep->port->port.port_num == port_num && |
| /* pkey retrieved from ipoib has always full mmbr bit set */ |
| (ep->endpoint.pkey | IB_PKEY_FULL_MEMBER) == pkey) { |
| if (!acm_ep_insert_addr(ep, ip_str, addr, addr_type)) { |
| acm_log(0, "Added %s %s %d 0x%x from %s\n", ip_str, |
| dev->device.verbs->device->name, port_num, ep->endpoint.pkey, |
| ifname); |
| } |
| } |
| } |
| |
| static int acm_get_system_ips(struct acmc_ep *ep) |
| { |
| return acm_if_iter_sys(acm_ep_ip_iter_cb, ep); |
| } |
| |
| static int acm_assign_ep_names(struct acmc_ep *ep) |
| { |
| FILE *faddr; |
| char *dev_name; |
| char s[120]; |
| char dev[32], name[ACM_MAX_ADDRESS], pkey_str[8]; |
| uint16_t pkey; |
| uint8_t addr[ACM_MAX_ADDRESS], type; |
| int port; |
| |
| dev_name = ep->port->dev->device.verbs->device->name; |
| acm_log(1, "device %s, port %d, pkey 0x%x\n", |
| dev_name, ep->port->port.port_num, ep->endpoint.pkey); |
| |
| acm_get_system_ips(ep); |
| |
| if (!(faddr = acm_open_addr_file())) { |
| acm_log(0, "ERROR - address file not found\n"); |
| goto out; |
| } |
| |
| while (fgets(s, sizeof s, faddr)) { |
| if (s[0] == '#') |
| continue; |
| |
| if (sscanf(s, "%46s%31s%d%7s", name, dev, &port, pkey_str) != 4) |
| continue; |
| |
| acm_log(2, "%s", s); |
| if (inet_pton(AF_INET, name, addr) > 0) { |
| if (!support_ips_in_addr_cfg) { |
| acm_log(0, "ERROR - IP's are not configured to be read from ibacm_addr.cfg\n"); |
| continue; |
| } |
| type = ACM_ADDRESS_IP; |
| } else if (inet_pton(AF_INET6, name, addr) > 0) { |
| if (!support_ips_in_addr_cfg) { |
| acm_log(0, "ERROR - IP's are not configured to be read from ibacm_addr.cfg\n"); |
| continue; |
| } |
| type = ACM_ADDRESS_IP6; |
| } else { |
| type = ACM_ADDRESS_NAME; |
| strncpy((char *)addr, name, sizeof(addr)); |
| } |
| |
| if (strcasecmp(pkey_str, "default")) { |
| if (sscanf(pkey_str, "%hx", &pkey) != 1) { |
| acm_log(0, "ERROR - bad pkey format %s\n", pkey_str); |
| continue; |
| } |
| } else { |
| pkey = ep->port->def_acm_pkey; |
| } |
| |
| if (!strcasecmp(dev_name, dev) && |
| (ep->port->port.port_num == (uint8_t) port) && |
| acm_same_partition(ep->endpoint.pkey, pkey)) { |
| acm_log(1, "assigning %s\n", name); |
| if (acm_ep_insert_addr(ep, name, addr, type)) { |
| acm_log(1, "maximum number of names assigned to EP\n"); |
| break; |
| } |
| } |
| } |
| fclose(faddr); |
| |
| out: |
| return (!ep->nmbr_ep_addrs || ep->addr_info[0].addr.type == ACM_ADDRESS_INVALID); |
| } |
| |
| static struct acmc_ep *acm_find_ep(struct acmc_port *port, uint16_t pkey) |
| { |
| struct acmc_ep *ep, *res = NULL; |
| |
| acm_log(2, "pkey 0x%x\n", pkey); |
| |
| list_for_each(&port->ep_list, ep, entry) { |
| if (acm_same_partition(ep->endpoint.pkey, pkey)) { |
| res = ep; |
| break; |
| } |
| } |
| return res; |
| } |
| |
| static void acm_ep_down(struct acmc_ep *ep) |
| { |
| int i; |
| |
| acm_log(1, "%s %d pkey 0x%04x\n", |
| ep->port->dev->device.verbs->device->name, |
| ep->port->port.port_num, ep->endpoint.pkey); |
| |
| for (i = 0; i < ep->nmbr_ep_addrs; i++) { |
| if (ep->addr_info[i].addr.type && |
| ep->addr_info[i].prov_addr_context) |
| ep->port->prov->remove_address(ep->addr_info[i]. |
| prov_addr_context); |
| } |
| |
| if (ep->prov_ep_context) |
| ep->port->prov->close_endpoint(ep->prov_ep_context); |
| |
| free(ep); |
| } |
| |
| static struct acmc_ep * |
| acm_alloc_ep(struct acmc_port *port, uint16_t pkey) |
| { |
| struct acmc_ep *ep; |
| |
| acm_log(1, "\n"); |
| ep = calloc(1, sizeof *ep); |
| if (!ep) |
| return NULL; |
| |
| ep->port = port; |
| ep->endpoint.port = &port->port; |
| ep->endpoint.pkey = pkey; |
| ep->addr_info = NULL; |
| ep->nmbr_ep_addrs = 0; |
| |
| return ep; |
| } |
| |
| static void acm_ep_up(struct acmc_port *port, uint16_t pkey) |
| { |
| struct acmc_ep *ep; |
| int ret; |
| |
| acm_log(1, "\n"); |
| if (acm_find_ep(port, pkey)) { |
| acm_log(2, "endpoint for pkey 0x%x already exists\n", pkey); |
| return; |
| } |
| |
| acm_log(2, "creating endpoint for pkey 0x%x\n", pkey); |
| ep = acm_alloc_ep(port, pkey); |
| if (!ep) |
| return; |
| |
| ret = acm_assign_ep_names(ep); |
| if (ret) { |
| acm_log(1, "unable to assign EP name for pkey 0x%x\n", pkey); |
| goto ep_close; |
| } |
| |
| list_add(&port->ep_list, &ep->entry); |
| return; |
| |
| ep_close: |
| if (ep->prov_ep_context) |
| port->prov->close_endpoint(ep->prov_ep_context); |
| |
| free(ep); |
| } |
| |
| static void acm_assign_provider(struct acmc_port *port) |
| { |
| struct acmc_prov *prov; |
| struct acmc_subnet *subnet; |
| |
| acm_log(2, "port %s/%d\n", port->port.dev->verbs->device->name, |
| port->port.port_num); |
| list_for_each(&provider_list, prov, entry) { |
| list_for_each(&prov->subnet_list, subnet, entry) { |
| if (subnet->subnet_prefix == |
| port->gid_tbl[0].global.subnet_prefix) { |
| acm_log(2, "Found provider %s for port %s/%d\n", |
| prov->prov->name, |
| port->port.dev->verbs->device->name, |
| port->port.port_num); |
| port->prov = prov->prov; |
| return; |
| } |
| } |
| } |
| |
| /* If no provider is found, assign the default provider*/ |
| if (!port->prov) { |
| acm_log(2, "No prov found, assign default prov %s to %s/%d\n", |
| def_provider ? def_provider->prov->name: "NULL", |
| port->port.dev->verbs->device->name, |
| port->port.port_num); |
| port->prov = def_provider ? def_provider->prov : NULL; |
| } |
| } |
| |
| static void acm_port_get_gid_tbl(struct acmc_port *port) |
| { |
| union ibv_gid gid; |
| int i, j, ret; |
| |
| for (i = 0;; i++) { |
| ret = ibv_query_gid(port->port.dev->verbs, port->port.port_num, |
| i, &gid); |
| if (ret || !gid.global.interface_id) |
| break; |
| } |
| |
| if (i > 0) { |
| port->gid_tbl = calloc(i, sizeof(union ibv_gid)); |
| if (!port->gid_tbl) { |
| acm_log(0, "Error: failed to allocate gid table\n"); |
| port->gid_cnt = 0; |
| return; |
| } |
| |
| for (j = 0; j < i; j++) { |
| ret = ibv_query_gid(port->port.dev->verbs, |
| port->port.port_num, j, |
| &port->gid_tbl[j]); |
| if (ret || !port->gid_tbl[j].global.interface_id) |
| break; |
| acm_log(2, "guid %d: 0x%" PRIx64 " %" PRIx64 "\n", j, |
| be64toh(port->gid_tbl[j].global.subnet_prefix), |
| be64toh(port->gid_tbl[j].global.interface_id)); |
| } |
| port->gid_cnt = j; |
| } |
| acm_log(2, "port %d gid_cnt %d\n", port->port.port_num, |
| port->gid_cnt); |
| } |
| |
| static void acm_port_up(struct acmc_port *port) |
| { |
| struct ibv_port_attr attr; |
| uint16_t pkey; |
| __be16 pkey_be; |
| int i, ret; |
| struct acmc_prov_context *dev_ctx; |
| int index = -1; |
| uint16_t first_pkey = 0; |
| |
| acm_log(1, "%s %d\n", port->dev->device.verbs->device->name, |
| port->port.port_num); |
| ret = ibv_query_port(port->dev->device.verbs, port->port.port_num, |
| &attr); |
| if (ret) { |
| acm_log(0, "ERROR - unable to get port state\n"); |
| return; |
| } |
| if (attr.state != IBV_PORT_ACTIVE) { |
| acm_log(1, "port not active\n"); |
| return; |
| } |
| |
| acm_port_get_gid_tbl(port); |
| port->lid = attr.lid; |
| port->lid_mask = 0xffff - ((1 << attr.lmc) - 1); |
| port->sa_addr.lid = htobe16(attr.sm_lid); |
| port->sa_addr.sl = attr.sm_sl; |
| port->state = IBV_PORT_ACTIVE; |
| acm_assign_provider(port); |
| if (!port->prov) { |
| acm_log(1, "no provider assigned to port\n"); |
| return; |
| } |
| dev_ctx = acm_acquire_prov_context(&port->dev->prov_dev_context_list, |
| port->prov); |
| if (!dev_ctx) { |
| acm_log(0, "Error -- failed to acquire dev context\n"); |
| return; |
| } |
| |
| if (atomic_get(&dev_ctx->refcnt) == 1) { |
| if (port->prov->open_device(&port->dev->device, &dev_ctx->context)) { |
| acm_log(0, "Error -- failed to open the prov device\n"); |
| goto err1; |
| } |
| } |
| |
| if (port->prov->open_port(&port->port, dev_ctx->context, |
| &port->prov_port_context)) { |
| acm_log(0, "Error -- failed to open the prov port\n"); |
| goto err1; |
| } |
| |
| /* Determine the default pkey for SA access first. |
| * Order of preference: 0xffff, 0x7fff |
| * Use the first pkey as the default pkey for parsing address file. |
| */ |
| for (i = 0; i < attr.pkey_tbl_len; i++) { |
| ret = ibv_query_pkey(port->dev->device.verbs, |
| port->port.port_num, i, &pkey_be); |
| if (ret) |
| continue; |
| pkey = be16toh(pkey_be); |
| if (i == 0) |
| first_pkey = pkey; |
| if (pkey == 0xffff) { |
| index = i; |
| break; |
| } |
| else if (pkey == 0x7fff) { |
| index = i; |
| } |
| } |
| port->sa_pkey_index = index < 0 ? 0 : index; |
| port->def_acm_pkey = first_pkey; |
| |
| for (i = 0; i < attr.pkey_tbl_len; i++) { |
| ret = ibv_query_pkey(port->dev->device.verbs, |
| port->port.port_num, i, &pkey_be); |
| if (ret) |
| continue; |
| pkey = be16toh(pkey_be); |
| if (!(pkey & 0x7fff)) |
| continue; |
| |
| acm_ep_up(port, pkey); |
| } |
| return; |
| err1: |
| acm_release_prov_context(dev_ctx); |
| } |
| |
| static void acm_shutdown_port(struct acmc_port *port) |
| { |
| struct acmc_ep *ep; |
| struct acmc_prov_context *dev_ctx; |
| |
| while ((ep = list_pop(&port->ep_list, struct acmc_ep, entry))) |
| acm_ep_down(ep); |
| |
| if (port->prov_port_context) { |
| port->prov->close_port(port->prov_port_context); |
| port->prov_port_context = NULL; |
| dev_ctx = acm_get_prov_context(&port->dev->prov_dev_context_list, |
| port->prov); |
| if (dev_ctx) { |
| if (atomic_get(&dev_ctx->refcnt) == 1) |
| port->prov->close_device(dev_ctx->context); |
| acm_release_prov_context(dev_ctx); |
| } |
| } |
| port->prov = NULL; |
| if (port->gid_tbl) { |
| free(port->gid_tbl); |
| port->gid_tbl = NULL; |
| } |
| port->gid_cnt = 0; |
| } |
| |
| static void acm_port_down(struct acmc_port *port) |
| { |
| struct ibv_port_attr attr; |
| int ret; |
| |
| acm_log(1, "%s %d\n", port->port.dev->verbs->device->name, port->port.port_num); |
| ret = ibv_query_port(port->port.dev->verbs, port->port.port_num, &attr); |
| if (!ret && attr.state == IBV_PORT_ACTIVE) { |
| acm_log(1, "port active\n"); |
| return; |
| } |
| |
| port->state = attr.state; |
| acm_shutdown_port(port); |
| |
| acm_log(1, "%s %d is down\n", port->dev->device.verbs->device->name, |
| port->port.port_num); |
| } |
| |
| static void acm_port_change(struct acmc_port *port) |
| { |
| struct ibv_port_attr attr; |
| int ret; |
| |
| acm_log(1, "%s %d\n", port->port.dev->verbs->device->name, port->port.port_num); |
| ret = ibv_query_port(port->port.dev->verbs, port->port.port_num, &attr); |
| if (ret || attr.state != IBV_PORT_ACTIVE) { |
| acm_log(1, "port not active: don't care\n"); |
| return; |
| } |
| |
| port->state = attr.state; |
| acm_shutdown_port(port); |
| acm_port_up(port); |
| } |
| |
| static void acm_event_handler(struct acmc_device *dev) |
| { |
| struct ibv_async_event event; |
| int i, ret; |
| |
| ret = ibv_get_async_event(dev->device.verbs, &event); |
| if (ret) |
| return; |
| |
| acm_log(2, "processing async event %s for %s\n", |
| ibv_event_type_str(event.event_type), |
| dev->device.verbs->device->name); |
| i = event.element.port_num - 1; |
| |
| switch (event.event_type) { |
| case IBV_EVENT_PORT_ACTIVE: |
| if (dev->port[i].state != IBV_PORT_ACTIVE) |
| acm_port_up(&dev->port[i]); |
| if (dev->port[i].pending_rereg && dev->port[i].prov_port_context) { |
| dev->port[i].prov->handle_event(dev->port[i].prov_port_context, |
| IBV_EVENT_CLIENT_REREGISTER); |
| dev->port[i].pending_rereg = false; |
| acm_log(1, "%s %d delayed reregistration\n", |
| dev->device.verbs->device->name, i + 1); |
| } |
| |
| break; |
| case IBV_EVENT_PORT_ERR: |
| if (dev->port[i].state == IBV_PORT_ACTIVE) |
| acm_port_down(&dev->port[i]); |
| break; |
| case IBV_EVENT_CLIENT_REREGISTER: |
| if ((dev->port[i].state == IBV_PORT_ACTIVE) && |
| dev->port[i].prov_port_context) { |
| dev->port[i].prov->handle_event(dev->port[i].prov_port_context, |
| event.event_type); |
| acm_log(1, "%s %d has reregistered\n", |
| dev->device.verbs->device->name, i + 1); |
| } else { |
| acm_log(2, "%s %d rereg on inactive port, postpone handling\n", |
| dev->device.verbs->device->name, i + 1); |
| dev->port[i].pending_rereg = true; |
| } |
| |
| break; |
| case IBV_EVENT_LID_CHANGE: |
| case IBV_EVENT_GID_CHANGE: |
| case IBV_EVENT_PKEY_CHANGE: |
| acm_port_change(&dev->port[i]); |
| break; |
| default: |
| break; |
| } |
| |
| ibv_ack_async_event(&event); |
| } |
| |
| static void acm_activate_devices(void) |
| { |
| struct acmc_device *dev; |
| int i; |
| |
| acm_log(1, "\n"); |
| list_for_each(&dev_list, dev, entry) { |
| for (i = 0; i < dev->port_cnt; i++) { |
| acm_port_up(&dev->port[i]); |
| } |
| } |
| } |
| |
| static void |
| acm_open_port(struct acmc_port *port, struct acmc_device *dev, uint8_t port_num) |
| { |
| acm_log(1, "%s %d\n", dev->device.verbs->device->name, port_num); |
| port->dev = dev; |
| port->port.dev = &dev->device; |
| port->port.port_num = port_num; |
| pthread_mutex_init(&port->lock, NULL); |
| list_head_init(&port->ep_list); |
| list_head_init(&port->sa_pending); |
| list_head_init(&port->sa_wait); |
| port->sa_credits = sa.depth; |
| port->sa_addr.qpn = htobe32(1); |
| port->sa_addr.qkey = htobe32(ACM_QKEY); |
| |
| port->mad_portid = umad_open_port(dev->device.verbs->device->name, port_num); |
| if (port->mad_portid < 0) |
| acm_log(0, "ERROR - unable to open MAD port\n"); |
| |
| port->mad_agentid = umad_register(port->mad_portid, |
| IB_MGMT_CLASS_SA, 1, 1, NULL); |
| if (port->mad_agentid < 0) { |
| umad_close_port(port->mad_portid); |
| acm_log(0, "ERROR - unable to register MAD client\n"); |
| } |
| |
| port->prov = NULL; |
| port->state = IBV_PORT_DOWN; |
| } |
| |
| static void acm_open_dev(struct ibv_device *ibdev) |
| { |
| struct acmc_device *dev; |
| struct ibv_device_attr attr; |
| struct ibv_port_attr port_attr; |
| struct ibv_context *verbs; |
| size_t size; |
| int i, ret; |
| bool has_ib_port = false; |
| |
| acm_log(1, "%s\n", ibdev->name); |
| verbs = ibv_open_device(ibdev); |
| if (verbs == NULL) { |
| acm_log(0, "ERROR - opening device %s\n", ibdev->name); |
| return; |
| } |
| |
| ret = ibv_query_device(verbs, &attr); |
| if (ret) { |
| acm_log(0, "ERROR - ibv_query_device (%d) %s\n", ret, ibdev->name); |
| goto err1; |
| } |
| |
| for (i = 0; i < attr.phys_port_cnt; i++) { |
| ret = ibv_query_port(verbs, i + 1, &port_attr); |
| if (ret) { |
| acm_log(0, "ERROR - ibv_query_port (%s, %d) return (%d)\n", |
| ibdev->name, i + 1, ret); |
| continue; |
| } |
| |
| if (port_attr.link_layer == IBV_LINK_LAYER_INFINIBAND) { |
| acm_log(1, "%s port %d is an InfiniBand port\n", ibdev->name, i + 1); |
| has_ib_port = true; |
| } else { |
| acm_log(1, "%s port %d is not an InfiniBand port\n", ibdev->name, i + 1); |
| } |
| } |
| |
| if (!has_ib_port) { |
| acm_log(1, "%s does not support InfiniBand.\n", ibdev->name); |
| goto err1; |
| } |
| |
| size = sizeof(*dev) + sizeof(struct acmc_port) * attr.phys_port_cnt; |
| dev = (struct acmc_device *) calloc(1, size); |
| if (!dev) |
| goto err1; |
| |
| dev->device.verbs = verbs; |
| dev->device.dev_guid = ibv_get_device_guid(ibdev); |
| dev->port_cnt = attr.phys_port_cnt; |
| list_head_init(&dev->prov_dev_context_list); |
| |
| for (i = 0; i < dev->port_cnt; i++) { |
| acm_open_port(&dev->port[i], dev, i + 1); |
| } |
| |
| list_add(&dev_list, &dev->entry); |
| |
| acm_log(1, "%s opened\n", ibdev->name); |
| return; |
| |
| err1: |
| ibv_close_device(verbs); |
| } |
| |
| static int acm_open_devices(void) |
| { |
| struct ibv_device **ibdev; |
| int dev_cnt; |
| int i; |
| |
| acm_log(1, "\n"); |
| ibdev = ibv_get_device_list(&dev_cnt); |
| if (!ibdev) { |
| acm_log(0, "ERROR - unable to get device list\n"); |
| return -1; |
| } |
| |
| for (i = 0; i < dev_cnt; i++) |
| acm_open_dev(ibdev[i]); |
| |
| ibv_free_device_list(ibdev); |
| if (list_empty(&dev_list)) { |
| acm_log(0, "ERROR - no devices\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void acm_load_prov_config(void) |
| { |
| FILE *fd; |
| char s[128]; |
| char *p, *ptr; |
| char prov_name[ACM_PROV_NAME_SIZE]; |
| uint64_t prefix; |
| struct acmc_prov *prov; |
| struct acmc_subnet *subnet; |
| |
| if (!(fd = fopen(opts_file, "r"))) |
| return; |
| |
| while (fgets(s, sizeof s, fd)) { |
| if (s[0] == '#') |
| continue; |
| |
| /* Ignore blank lines */ |
| if (!(p = strtok_r(s, " \n", &ptr))) |
| continue; |
| |
| if (strncasecmp(p, "provider", sizeof("provider") - 1)) |
| continue; |
| |
| p = strtok_r(NULL, " ", &ptr); |
| if (!p) |
| continue; |
| |
| strncpy(prov_name, p, sizeof(prov_name)); |
| prov_name[sizeof(prov_name) -1] = '\0'; |
| |
| p = strtok_r(NULL, " ", &ptr); |
| if (!p) |
| continue; |
| if (!strncasecmp(p, "default", sizeof("default") - 1)) { |
| strncpy(def_prov_name, prov_name, sizeof(def_prov_name)); |
| def_prov_name[sizeof(def_prov_name) -1] = '\0'; |
| acm_log(2, "default provider: %s\n", def_prov_name); |
| continue; |
| } |
| prefix = strtoull(p, NULL, 0); |
| acm_log(2, "provider %s subnet_prefix 0x%" PRIx64 "\n", |
| prov_name, prefix); |
| |
| list_for_each(&provider_list, prov, entry) { |
| if (!strcasecmp(prov->prov->name, prov_name)) { |
| subnet = calloc(1, sizeof (*subnet)); |
| if (!subnet) { |
| acm_log(0, "Error: out of memory\n"); |
| fclose(fd); |
| return; |
| } |
| subnet->subnet_prefix = htobe64(prefix); |
| list_add_tail(&prov->subnet_list, |
| &subnet->entry); |
| } |
| } |
| } |
| |
| fclose(fd); |
| |
| list_for_each(&provider_list, prov, entry) { |
| if (!strcasecmp(prov->prov->name, def_prov_name)) { |
| def_provider = prov; |
| break; |
| } |
| } |
| } |
| |
| static int acm_string_end_compare(const char *s1, const char *s2) |
| { |
| size_t s1_len = strlen(s1); |
| size_t s2_len = strlen(s2); |
| |
| if (s1_len < s2_len) |
| return -1; |
| |
| return strcmp(s1 + s1_len - s2_len, s2); |
| } |
| |
| static int acm_open_providers(void) |
| { |
| DIR *shlib_dir; |
| struct dirent *dent; |
| char file_name[256]; |
| struct stat buf; |
| void *handle; |
| struct acmc_prov *prov; |
| struct acm_provider *provider; |
| uint32_t version; |
| char *err_str; |
| int (*query)(struct acm_provider **, uint32_t *); |
| |
| acm_log(1, "\n"); |
| shlib_dir = opendir(prov_lib_path); |
| if (!shlib_dir) { |
| acm_log(0, "ERROR - could not open provider lib dir: %s\n", |
| prov_lib_path); |
| return -1; |
| } |
| |
| while ((dent = readdir(shlib_dir))) { |
| if (acm_string_end_compare(dent->d_name, ".so")) |
| continue; |
| |
| if (!check_snprintf(file_name, sizeof(file_name), "%s/%s", |
| prov_lib_path, dent->d_name)) |
| continue; |
| |
| if (lstat(file_name, &buf)) { |
| acm_log(0, "Error - could not stat: %s\n", file_name); |
| continue; |
| } |
| if (!S_ISREG(buf.st_mode)) |
| continue; |
| |
| acm_log(2, "Loading provider %s...\n", file_name); |
| if (!(handle = dlopen(file_name, RTLD_LAZY))) { |
| acm_log(0, "Error - could not load provider %s (%s)\n", |
| file_name, dlerror()); |
| continue; |
| } |
| |
| query = dlsym(handle, "provider_query"); |
| if ((err_str = dlerror()) != NULL) { |
| acm_log(0, "Error - provider_query not found in %s (%s)\n", |
| file_name, err_str); |
| dlclose(handle); |
| continue; |
| } |
| |
| if (query(&provider, &version)) { |
| acm_log(0, "Error - provider_query failed to %s\n", file_name); |
| dlclose(handle); |
| continue; |
| } |
| |
| if (version != ACM_PROV_VERSION || |
| provider->size != sizeof(struct acm_provider)) { |
| acm_log(0, "Error -unmatched provider version 0x%08x (size %zd)" |
| " core 0x%08x (size %zd)\n", version, provider->size, |
| ACM_PROV_VERSION, sizeof(struct acm_provider)); |
| dlclose(handle); |
| continue; |
| } |
| |
| acm_log(1, "Provider %s (%s) loaded\n", provider->name, file_name); |
| |
| prov = calloc(1, sizeof(*prov)); |
| if (!prov) { |
| acm_log(0, "Error -failed to allocate provider %s\n", file_name); |
| dlclose(handle); |
| continue; |
| } |
| |
| prov->prov = provider; |
| prov->handle = handle; |
| list_head_init(&prov->subnet_list); |
| list_add_tail(&provider_list, &prov->entry); |
| if (!strcasecmp(provider->name, def_prov_name)) |
| def_provider = prov; |
| } |
| |
| closedir(shlib_dir); |
| acm_load_prov_config(); |
| return 0; |
| } |
| |
| static void acm_close_providers(void) |
| { |
| struct acmc_prov *prov; |
| struct acmc_subnet *subnet; |
| |
| acm_log(1, "\n"); |
| def_provider = NULL; |
| |
| while ((prov = list_pop(&provider_list, struct acmc_prov, entry))) { |
| while ((subnet = list_pop(&prov->subnet_list, |
| struct acmc_subnet, entry))) |
| free(subnet); |
| dlclose(prov->handle); |
| free(prov); |
| } |
| } |
| |
| static int acmc_init_sa_fds(void) |
| { |
| struct acmc_device *dev; |
| int ret, p, i = 0; |
| |
| list_for_each(&dev_list, dev, entry) |
| sa.nfds += dev->port_cnt; |
| |
| sa.fds = calloc(sa.nfds, sizeof(*sa.fds)); |
| sa.ports = calloc(sa.nfds, sizeof(*sa.ports)); |
| if (!sa.fds || !sa.ports) |
| return -ENOMEM; |
| |
| list_for_each(&dev_list, dev, entry) { |
| for (p = 0; p < dev->port_cnt; p++) { |
| sa.fds[i].fd = umad_get_fd(dev->port[p].mad_portid); |
| sa.fds[i].events = POLLIN; |
| ret = set_fd_nonblock(sa.fds[i].fd, true); |
| if (ret) |
| acm_log(0, "WARNING - umad fd is blocking\n"); |
| |
| sa.ports[i++] = &dev->port[p]; |
| } |
| } |
| |
| return 0; |
| } |
| |
| struct acm_sa_mad * |
| acm_alloc_sa_mad(const struct acm_endpoint *endpoint, void *context, |
| void (*handler)(struct acm_sa_mad *)) |
| { |
| struct acmc_sa_req *req; |
| |
| if (!endpoint) { |
| acm_log(0, "Error: NULL endpoint\n"); |
| return NULL; |
| } |
| req = calloc(1, sizeof (*req)); |
| if (!req) { |
| acm_log(0, "Error: failed to allocate sa request\n"); |
| return NULL; |
| } |
| |
| req->ep = container_of(endpoint, struct acmc_ep, endpoint); |
| req->mad.context = context; |
| req->resp_handler = handler; |
| |
| acm_log(2, "%p\n", req); |
| return &req->mad; |
| } |
| |
| void acm_free_sa_mad(struct acm_sa_mad *mad) |
| { |
| struct acmc_sa_req *req; |
| req = container_of(mad, struct acmc_sa_req, mad); |
| acm_log(2, "%p\n", req); |
| free(req); |
| } |
| |
| int acm_send_sa_mad(struct acm_sa_mad *mad) |
| { |
| struct acmc_port *port; |
| struct acmc_sa_req *req; |
| int ret; |
| |
| req = container_of(mad, struct acmc_sa_req, mad); |
| acm_log(2, "%p from %s\n", req, req->ep->addr_info[0].addr.id_string); |
| |
| port = req->ep->port; |
| mad->umad.addr.qpn = port->sa_addr.qpn; |
| mad->umad.addr.qkey = port->sa_addr.qkey; |
| mad->umad.addr.lid = port->sa_addr.lid; |
| mad->umad.addr.sl = port->sa_addr.sl; |
| mad->umad.addr.pkey_index = req->ep->port->sa_pkey_index; |
| |
| pthread_mutex_lock(&port->lock); |
| if (port->sa_credits && list_empty(&port->sa_wait)) { |
| ret = umad_send(port->mad_portid, port->mad_agentid, &mad->umad, |
| sizeof mad->sa_mad, sa.timeout, sa.retries); |
| if (!ret) { |
| port->sa_credits--; |
| list_add_tail(&port->sa_pending, &req->entry); |
| } |
| } else { |
| ret = 0; |
| list_add_tail(&port->sa_wait, &req->entry); |
| } |
| pthread_mutex_unlock(&port->lock); |
| return ret; |
| } |
| |
| static void acmc_send_queued_req(struct acmc_port *port) |
| { |
| struct acmc_sa_req *req; |
| int ret; |
| |
| pthread_mutex_lock(&port->lock); |
| if (list_empty(&port->sa_wait) || !port->sa_credits) { |
| pthread_mutex_unlock(&port->lock); |
| return; |
| } |
| |
| req = list_pop(&port->sa_wait, struct acmc_sa_req, entry); |
| |
| ret = umad_send(port->mad_portid, port->mad_agentid, &req->mad.umad, |
| sizeof req->mad.sa_mad, sa.timeout, sa.retries); |
| if (!ret) { |
| port->sa_credits--; |
| list_add_tail(&port->sa_pending, &req->entry); |
| } |
| pthread_mutex_unlock(&port->lock); |
| |
| if (ret) { |
| req->mad.umad.status = -ret; |
| req->resp_handler(&req->mad); |
| } |
| } |
| |
| static void acmc_recv_mad(struct acmc_port *port) |
| { |
| struct acmc_sa_req *req; |
| struct acm_sa_mad resp; |
| int ret, len, found; |
| struct umad_hdr *hdr; |
| |
| if (!port->prov) { |
| acm_log(1, "no provider assigned to port\n"); |
| return; |
| } |
| |
| acm_log(2, "\n"); |
| len = sizeof(resp.sa_mad); |
| ret = umad_recv(port->mad_portid, &resp.umad, &len, 0); |
| if (ret < 0) { |
| acm_log(1, "umad_recv error %d\n", ret); |
| return; |
| } |
| |
| hdr = &resp.sa_mad.mad_hdr; |
| acm_log(2, "bv %x cls %x cv %x mtd %x st %d tid %" PRIx64 " at %x atm %x\n", |
| hdr->base_version, hdr->mgmt_class, hdr->class_version, |
| hdr->method, be16toh(hdr->status), be64toh(hdr->tid), |
| be16toh(hdr->attr_id), be32toh(hdr->attr_mod)); |
| found = 0; |
| pthread_mutex_lock(&port->lock); |
| list_for_each(&port->sa_pending, req, entry) { |
| /* The upper 32-bit of the tid is used for agentid in umad */ |
| if (req->mad.sa_mad.mad_hdr.tid == (hdr->tid & htobe64(0xFFFFFFFF))) { |
| found = 1; |
| list_del(&req->entry); |
| port->sa_credits++; |
| break; |
| } |
| } |
| pthread_mutex_unlock(&port->lock); |
| |
| if (found) { |
| memcpy(&req->mad.umad, &resp.umad, sizeof(resp.umad) + len); |
| req->resp_handler(&req->mad); |
| } |
| } |
| |
| static void *acm_sa_handler(void *context) |
| { |
| int i, ret; |
| |
| acm_log(0, "started\n"); |
| ret = acmc_init_sa_fds(); |
| if (ret) { |
| acm_log(0, "ERROR - failed to init fds\n"); |
| return NULL; |
| } |
| |
| if (pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL)) { |
| acm_log(0, "Error: failed to set cancel type \n"); |
| return NULL; |
| } |
| |
| if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)) { |
| acm_log(0, "Error: failed to set cancel state\n"); |
| return NULL; |
| } |
| |
| for (;;) { |
| pthread_testcancel(); |
| ret = poll(sa.fds, sa.nfds, -1); |
| if (ret < 0) { |
| acm_log(0, "ERROR - sa poll error: %d\n", errno); |
| continue; |
| } |
| |
| for (i = 0; i < sa.nfds; i++) { |
| if (!sa.fds[i].revents) |
| continue; |
| |
| if (sa.fds[i].revents & POLLIN) { |
| acmc_recv_mad(sa.ports[i]); |
| acmc_send_queued_req(sa.ports[i]); |
| } |
| sa.fds[i].revents = 0; |
| } |
| } |
| return NULL; |
| } |
| |
| static void acm_stop_sa_handler(void) |
| { |
| if (pthread_cancel(sa.thread_id)) { |
| acm_log(0, "Error: failed to cancel sa resp thread \n"); |
| return; |
| } |
| |
| if (pthread_join(sa.thread_id, NULL)) { |
| acm_log(0, "Error: failed to join sa resp thread\n"); |
| return; |
| } |
| } |
| |
| static void acm_set_options(void) |
| { |
| FILE *f; |
| char s[120]; |
| char opt[32], value[256]; |
| |
| if (!(f = fopen(opts_file, "r"))) |
| return; |
| |
| while (fgets(s, sizeof s, f)) { |
| if (s[0] == '#') |
| continue; |
| |
| if (sscanf(s, "%31s%255s", opt, value) != 2) |
| continue; |
| |
| if (!strcasecmp("log_file", opt)) |
| strcpy(log_file, value); |
| else if (!strcasecmp("log_level", opt)) |
| log_level = atoi(value); |
| else if (!strcasecmp("umad_debug_level", opt)) { |
| umad_debug_level = atoi(value); |
| if (umad_debug_level > 0) |
| umad_debug(umad_debug_level); |
| } |
| else if (!strcasecmp("lock_file", opt)) |
| strcpy(lock_file, value); |
| else if (!strcasecmp("server_port", opt)) |
| server_port = (short) atoi(value); |
| else if (!strcasecmp("server_mode", opt)) { |
| if (!strcasecmp(value, "open")) |
| server_mode = IBACM_SERVER_MODE_OPEN; |
| else if (!strcasecmp(value, "loop")) |
| server_mode = IBACM_SERVER_MODE_LOOP; |
| else |
| server_mode = IBACM_SERVER_MODE_UNIX; |
| } else if (!strcasecmp("acme_plus_kernel_only", opt)) |
| acme_plus_kernel_only = |
| !strcasecmp(value, "true") || |
| !strcasecmp(value, "yes") || |
| strtol(value, NULL, 0); |
| else if (!strcasecmp("provider_lib_path", opt)) |
| strcpy(prov_lib_path, value); |
| else if (!strcasecmp("support_ips_in_addr_cfg", opt)) |
| support_ips_in_addr_cfg = atoi(value); |
| else if (!strcasecmp("timeout", opt)) |
| sa.timeout = atoi(value); |
| else if (!strcasecmp("retries", opt)) |
| sa.retries = atoi(value); |
| else if (!strcasecmp("sa_depth", opt)) |
| sa.depth = atoi(value); |
| } |
| |
| fclose(f); |
| } |
| |
| static void acm_log_options(void) |
| { |
| static const char * const server_mode_names[] = { |
| [IBACM_SERVER_MODE_UNIX] = "unix", |
| [IBACM_SERVER_MODE_LOOP] = "loop", |
| [IBACM_SERVER_MODE_OPEN] = "open", |
| }; |
| |
| acm_log(0, "log file %s\n", log_file); |
| acm_log(0, "log level %d\n", log_level); |
| acm_log(0, "umad debug level %d\n", umad_debug_level); |
| acm_log(0, "lock file %s\n", lock_file); |
| acm_log(0, "server_port %d\n", server_port); |
| acm_log(0, "server_mode %s\n", server_mode_names[server_mode]); |
| acm_log(0, "acme_plus_kernel_only %s\n", |
| acme_plus_kernel_only ? "yes" : "no"); |
| acm_log(0, "timeout %d ms\n", sa.timeout); |
| acm_log(0, "retries %d\n", sa.retries); |
| acm_log(0, "sa depth %d\n", sa.depth); |
| acm_log(0, "options file %s\n", opts_file); |
| acm_log(0, "addr file %s\n", addr_file); |
| acm_log(0, "provider lib path %s\n", prov_lib_path); |
| acm_log(0, "support IP's in ibacm_addr.cfg %d\n", support_ips_in_addr_cfg); |
| } |
| |
| static FILE *acm_open_log(void) |
| { |
| FILE *f; |
| |
| if (!strcasecmp(log_file, "stdout")) |
| return stdout; |
| |
| if (!strcasecmp(log_file, "stderr")) |
| return stderr; |
| |
| if (!(f = fopen(log_file, "w"))) |
| f = stdout; |
| |
| return f; |
| } |
| |
| static int acm_open_lock_file(void) |
| { |
| int lock_fd; |
| char pid[16]; |
| |
| lock_fd = open(lock_file, O_RDWR | O_CREAT, 0640); |
| if (lock_fd < 0) |
| return lock_fd; |
| |
| if (lockf(lock_fd, F_TLOCK, 0)) { |
| close(lock_fd); |
| return -1; |
| } |
| |
| snprintf(pid, sizeof pid, "%d\n", getpid()); |
| if (write(lock_fd, pid, strlen(pid)) != strlen(pid)){ |
| close(lock_fd); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static void show_usage(char *program) |
| { |
| printf("usage: %s\n", program); |
| printf(" [-D] - run as a daemon (default)\n"); |
| printf(" [-P] - run as a standard process\n"); |
| printf(" [-A addr_file] - address configuration file\n"); |
| printf(" (default %s/%s)\n", ACM_CONF_DIR, ACM_ADDR_FILE); |
| printf(" [-O option_file] - option configuration file\n"); |
| printf(" (default %s/%s)\n", ACM_CONF_DIR, ACM_OPTS_FILE); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int i, op, as_daemon = 1; |
| bool systemd = false; |
| |
| static const struct option long_opts[] = { |
| {"systemd", 0, NULL, 's'}, |
| {} |
| }; |
| |
| while ((op = getopt_long(argc, argv, "DPA:O:", long_opts, NULL)) != |
| -1) { |
| switch (op) { |
| case 'D': |
| /* option no longer required */ |
| break; |
| case 'P': |
| as_daemon = 0; |
| break; |
| case 'A': |
| addr_file = optarg; |
| break; |
| case 'O': |
| opts_file = optarg; |
| break; |
| case 's': |
| systemd = true; |
| break; |
| default: |
| show_usage(argv[0]); |
| exit(1); |
| } |
| } |
| |
| if (as_daemon && !systemd) { |
| if (daemon(0, 0)) |
| return EXIT_FAILURE; |
| } |
| |
| acm_set_options(); |
| |
| /* usage of systemd implies unix-domain communication */ |
| if (systemd) |
| server_mode = IBACM_SERVER_MODE_UNIX; |
| |
| if (acm_open_lock_file()) |
| return -1; |
| |
| pthread_mutex_init(&log_lock, NULL); |
| flog = acm_open_log(); |
| |
| acm_log(0, "Assistant to the InfiniBand Communication Manager\n"); |
| acm_log_options(); |
| |
| for (i = 0; i < ACM_MAX_COUNTER; i++) |
| atomic_init(&counter[i]); |
| |
| if (umad_init() != 0) { |
| acm_log(0, "ERROR - fail to initialize umad\n"); |
| return -1; |
| } |
| |
| if (acm_open_providers()) { |
| acm_log(0, "ERROR - unable to open any providers\n"); |
| return -1; |
| } |
| |
| if (acm_open_devices()) { |
| acm_log(0, "ERROR - unable to open any devices\n"); |
| return -1; |
| } |
| |
| acm_log(1, "creating IP Netlink socket\n"); |
| acm_ipnl_create(); |
| |
| acm_log(1, "starting sa response receiving thread\n"); |
| if (pthread_create(&sa.thread_id, NULL, acm_sa_handler, NULL)) { |
| acm_log(0, "Error: failed to create sa resp rcving thread"); |
| return -1; |
| } |
| |
| if (acm_init_if_iter_sys()) { |
| acm_log(0, "Error: unable to initialize acm_if_iter_sys"); |
| return -1; |
| } |
| |
| acm_activate_devices(); |
| acm_log(1, "starting server\n"); |
| acm_server(systemd); |
| |
| acm_log(0, "shutting down\n"); |
| if (client_array[NL_CLIENT_INDEX].sock != -1) |
| close(client_array[NL_CLIENT_INDEX].sock); |
| acm_close_providers(); |
| acm_stop_sa_handler(); |
| umad_done(); |
| acm_fini_if_iter_sys(); |
| fclose(flog); |
| return 0; |
| } |