/* * Copyright (c) 2016-2019 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HAVE_CONSTRUCTOR_ATTRIBUTE #ifdef HAVE_CONSTRUCTOR_ATTRIBUTE #define CONSTRUCTOR_ATTRIBUTE \ __attribute__ ((constructor)) #else #define CONSTRUCTOR_ATTRIBUTE #endif /* HAVE_CONSTRUCTOR_ATTRIBUTE */ #define HAVE_DESTRUCTOR_ATTRIBUTE #ifdef HAVE_DESTRUCTOR_ATTRIBUTE #define DESTRUCTOR_ATTRIBUTE \ __attribute__ ((destructor)) #else #define DESTRUCTOR_ATTRIBUTE #endif #define LDP_MAX_NWORKERS 32 typedef struct ldp_worker_ctx_ { u8 *io_buffer; clib_time_t clib_time; /* * Select state */ clib_bitmap_t *rd_bitmap; clib_bitmap_t *wr_bitmap; clib_bitmap_t *ex_bitmap; clib_bitmap_t *si_rd_bitmap; clib_bitmap_t *si_wr_bitmap; clib_bitmap_t *si_ex_bitmap; clib_bitmap_t *libc_rd_bitmap; clib_bitmap_t *libc_wr_bitmap; clib_bitmap_t *libc_ex_bitmap; /* * Poll state */ vcl_poll_t *vcl_poll; struct pollfd *libc_poll; u16 *libc_poll_idxs; /* * Epoll state */ u8 epoll_wait_vcl; int vcl_mq_epfd; } ldp_worker_ctx_t; /* clib_bitmap_t, fd_mask and vcl_si_set are used interchangeably. Make sure * they are the same size */ STATIC_ASSERT (sizeof (clib_bitmap_t) == sizeof (fd_mask), "ldp bitmap size mismatch"); STATIC_ASSERT (sizeof (vcl_si_set) == sizeof (fd_mask), "ldp bitmap size mismatch"); typedef struct { ldp_worker_ctx_t *workers; int init; char app_name[LDP_APP_NAME_MAX]; u32 vlsh_bit_val; u32 vlsh_bit_mask; u32 debug; /** vcl needs next epoll_create to go to libc_epoll */ u8 vcl_needs_real_epoll; } ldp_main_t; #define LDP_DEBUG ldp->debug #define LDBG(_lvl, _fmt, _args...) \ if (ldp->debug > _lvl) \ clib_warning ("ldp<%d>: " _fmt, getpid(), ##_args) static ldp_main_t ldp_main = { .vlsh_bit_val = (1 << LDP_SID_BIT_MIN), .vlsh_bit_mask = (1 << LDP_SID_BIT_MIN) - 1, .debug = LDP_DEBUG_INIT, }; static ldp_main_t *ldp = &ldp_main; static inline ldp_worker_ctx_t * ldp_worker_get_current (void) { return (ldp->workers + vppcom_worker_index ()); } /* * RETURN: 0 on success or -1 on error. * */ static inline void ldp_set_app_name (char *app_name) { int rv = snprintf (ldp->app_name, LDP_APP_NAME_MAX, "ldp-%d-%s", getpid (), app_name); if (rv >= LDP_APP_NAME_MAX) app_name[LDP_APP_NAME_MAX - 1] = 0; } static inline char * ldp_get_app_name () { if (ldp->app_name[0] == '\0') ldp_set_app_name ("app"); return ldp->app_name; } static inline int ldp_vlsh_to_fd (vls_handle_t vlsh) { return (vlsh + ldp->vlsh_bit_val); } static inline vls_handle_t ldp_fd_to_vlsh (int fd) { if (fd < ldp->vlsh_bit_val) return VLS_INVALID_HANDLE; return (fd - ldp->vlsh_bit_val); } static void ldp_alloc_workers (void) { if (ldp->workers) return; pool_alloc (ldp->workers, LDP_MAX_NWORKERS); } static inline int ldp_init (void) { ldp_worker_ctx_t *ldpw; int rv; if (PREDICT_TRUE (ldp->init)) return 0; ldp->init = 1; ldp->vcl_needs_real_epoll = 1; rv = vls_app_create (ldp_get_app_name ()); if (rv != VPPCOM_OK) { ldp->vcl_needs_real_epoll = 0; if (rv == VPPCOM_EEXIST) return 0; LDBG (2, "\nERROR: ldp_init: vppcom_app_create()" " failed! rv = %d (%s)\n", rv, vppcom_retval_str (rv)); ldp->init = 0; return rv; } ldp->vcl_needs_real_epoll = 0; ldp_alloc_workers (); ldpw = ldp_worker_get_current (); char *env_var_str = getenv (LDP_ENV_DEBUG); if (env_var_str) { u32 tmp; if (sscanf (env_var_str, "%u", &tmp) != 1) clib_warning ("LDP<%d>: WARNING: Invalid LDP debug level specified in" " the env var " LDP_ENV_DEBUG " (%s)!", getpid (), env_var_str); else { ldp->debug = tmp; LDBG (0, "configured LDP debug level (%u) from env var " LDP_ENV_DEBUG "!", ldp->debug); } } env_var_str = getenv (LDP_ENV_APP_NAME); if (env_var_str) { ldp_set_app_name (env_var_str); LDBG (0, "configured LDP app name (%s) from the env var " LDP_ENV_APP_NAME "!", ldp->app_name); } env_var_str = getenv (LDP_ENV_SID_BIT); if (env_var_str) { u32 sb; if (sscanf (env_var_str, "%u", &sb) != 1) { LDBG (0, "WARNING: Invalid LDP sid bit specified in the env var " LDP_ENV_SID_BIT " (%s)! sid bit value %d (0x%x)", env_var_str, ldp->vlsh_bit_val, ldp->vlsh_bit_val); } else if (sb < LDP_SID_BIT_MIN) { ldp->vlsh_bit_val = (1 << LDP_SID_BIT_MIN); ldp->vlsh_bit_mask = ldp->vlsh_bit_val - 1; LDBG (0, "WARNING: LDP sid bit (%u) specified in the env var " LDP_ENV_SID_BIT " (%s) is too small. Using LDP_SID_BIT_MIN" " (%d)! sid bit value %d (0x%x)", sb, env_var_str, LDP_SID_BIT_MIN, ldp->vlsh_bit_val, ldp->vlsh_bit_val); } else if (sb > LDP_SID_BIT_MAX) { ldp->vlsh_bit_val = (1 << LDP_SID_BIT_MAX); ldp->vlsh_bit_mask = ldp->vlsh_bit_val - 1; LDBG (0, "WARNING: LDP sid bit (%u) specified in the env var " LDP_ENV_SID_BIT " (%s) is too big. Using LDP_SID_BIT_MAX" " (%d)! sid bit value %d (0x%x)", sb, env_var_str, LDP_SID_BIT_MAX, ldp->vlsh_bit_val, ldp->vlsh_bit_val); } else { ldp->vlsh_bit_val = (1 << sb); ldp->vlsh_bit_mask = ldp->vlsh_bit_val - 1; LDBG (0, "configured LDP sid bit (%u) from " LDP_ENV_SID_BIT "! sid bit value %d (0x%x)", sb, ldp->vlsh_bit_val, ldp->vlsh_bit_val); } /* Make sure there are enough bits in the fd set for vcl sessions */ if (ldp->vlsh_bit_val > FD_SETSIZE / 2) { LDBG (0, "ERROR: LDP vlsh bit value %d > FD_SETSIZE/2 %d!", ldp->vlsh_bit_val, FD_SETSIZE / 2); ldp->init = 0; return -1; } } /* *INDENT-OFF* */ pool_foreach (ldpw, ldp->workers, ({ clib_memset (&ldpw->clib_time, 0, sizeof (ldpw->clib_time)); })); /* *INDENT-ON* */ LDBG (0, "LDP initialization: done!"); return 0; } int close (int fd) { vls_handle_t vlsh; int rv, epfd; if ((errno = -ldp_init ())) return -1; vlsh = ldp_fd_to_vlsh (fd); if (vlsh != VLS_INVALID_HANDLE) { epfd = vls_attr (vlsh, VPPCOM_ATTR_GET_LIBC_EPFD, 0, 0); if (epfd > 0) { LDBG (0, "fd %d: calling libc_close: epfd %u", fd, epfd); rv = libc_close (epfd); if (rv < 0) { u32 size = sizeof (epfd); epfd = 0; (void) vls_attr (vlsh, VPPCOM_ATTR_SET_LIBC_EPFD, &epfd, &size); } } else if (PREDICT_FALSE (epfd < 0)) { errno = -epfd; rv = -1; goto done; } LDBG (0, "fd %d: calling vls_close: vlsh %u", fd, vlsh); rv = vls_close (vlsh); if (rv != VPPCOM_OK) { errno = -rv; rv = -1; } } else { LDBG (0, "fd %d: calling libc_close", fd); rv = libc_close (fd); } done: return rv; } ssize_t read (int fd, void *buf, size_t nbytes) { vls_handle_t vlsh; ssize_t size; if ((errno = -ldp_init ())) return -1; vlsh = ldp_fd_to_vlsh (fd); if (vlsh != VLS_INVALID_HANDLE) { size = vls_read (vlsh, buf, nbytes); if (size < 0) { errno = -size; size = -1; } } else { size = libc_read (fd, buf, nbytes); } return size; } ssize_t readv (int fd, const struct iovec * iov, int iovcnt) { int rv = 0, i, total = 0; vls_handle_t vlsh; ssize_t size = 0; if ((errno = -ldp_init ())) return -1; vlsh = ldp_fd_to_vlsh (fd); if (vlsh != VLS_INVALID_HANDLE) { do { for (i = 0; i < iovcnt; ++i) { rv = vls_read (vlsh, iov[i].iov_base, iov[i].iov_len); if (rv < 0) break; else { total += rv; if (rv < iov[i].iov_len) break; } } } while ((rv >= 0) && (total == 0)); if (rv < 0) { errno = -rv; size = -1; } else size = total; } else { size = libc_readv (fd, iov, iovcnt); } return size; } ssize_t write (int fd, const void *buf, size_t nbytes) { vls_handle_t vlsh; ssize_t size = 0; if ((errno = -ldp_init ())) return -1; vlsh = ldp_fd_to_vlsh (fd); if (vlsh != VLS_INVALID_HANDLE) { size = vls_write_msg (vlsh, (void *) buf, nbytes); if (size < 0) { errno = -size; size = -1; } } else { size = libc_write (fd, buf, nbytes); } return size; } ssize_t writev (int fd, const struct iovec * iov, int iovcnt) { ssize_t size = 0, total = 0; vls_handle_t vlsh; int i, rv = 0; if ((errno = -ldp_init ())) return -1; vlsh = ldp_fd_to_vlsh (fd); if (vlsh != VLS_INVALID_HANDLE) { do { for (i = 0; i < iovcnt; ++i) { rv = vls_write_msg (vlsh, iov[i].iov_base, iov[i].iov_len); if (rv < 0) break; else { total += rv; if (rv < iov[i].iov_len) break; } } } while ((rv >= 0) && (total == 0)); if (rv < 0) { errno = -rv; size = -1; } else size = total; } else { size = libc_writev (fd, iov, iovcnt); } return size; } int fcntl (int fd, int cmd, ...) { vls_handle_t vlsh; int rv = 0; va_list ap; if ((errno = -ldp_init ())) return -1; va_start (ap, cmd); vlsh = ldp_fd_to_vlsh (fd); LDBG (0, "fd %u vlsh %d, cmd %u", fd, vlsh, cmd); if (vlsh != VLS_INVALID_HANDLE) { int flags = va_arg (ap, int); u32 size; size = sizeof (flags); rv = -EOPNOTSUPP; switch (cmd) { case F_SETFL: rv = vls_attr (vlsh, VPPCOM_ATTR_SET_FLAGS, &flags, &size); break; case F_GETFL: rv = vls_attr (vlsh, VPPCOM_ATTR_GET_FLAGS, &flags, &size); if (rv == VPPCOM_OK) rv = flags; break; case F_SETFD: /* TODO handle this */ LDBG (0, "F_SETFD ignored flags %u", flags); rv = 0; break; default: rv = -EOPNOTSUPP; break; } if (rv < 0) { errno = -rv; rv = -1; } } else { rv = libc_vfcntl (fd, cmd, ap); } va_end (ap); return rv; } int ioctl (int fd, unsigned long int cmd, ...) { vls_handle_t vlsh; va_list ap; int rv; if ((errno = -ldp_init ())) return -1; va_start (ap, cmd); vlsh = ldp_fd_to_vlsh (fd); if (vlsh != VLS_INVALID_HANDLE) { switch (cmd) { case FIONREAD: rv = vls_attr (vlsh, VPPCOM_ATTR_GET_NREAD, 0, 0); break; case FIONBIO: { u32 flags = va_arg (ap, int) ? O_NONBLOCK : 0; u32 size = sizeof (flags); /* TBD: When VPPCOM_ATTR_[GS]ET_FLAGS supports flags other than * non-blocking, the flags should be read here and merged * with O_NONBLOCK. */ rv = vls_attr (vlsh, VPPCOM_ATTR_SET_FLAGS, &flags, &size); } break; default: rv = -EOPNOTSUPP; break; } if (rv < 0) { errno = -rv; rv = -1; } } else { rv = libc_vioctl (fd, cmd, ap); } va_end (ap); return rv; } always_inline void ldp_select_init_maps (fd_set * __restrict original, clib_bitmap_t ** resultb, clib_bitmap_t ** libcb, clib_bitmap_t ** vclb, int nfds, u32 minbits, u32 n_bytes, uword * si_bits, uword * libc_bits) { uword si_bits_set, libc_bits_set; vls_handle_t vlsh; int f
/*
 * Copyright (c) 2016 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <vlib/vlib.h>
#include <vlib/i2c.h>

static inline void
i2c_delay (i2c_bus_t * b, f64 timeout)
{
  vlib_main_t *vm = vlib_get_main ();
  vlib_time_wait (vm, timeout);
}

static void
i2c_wait_for_scl (i2c_bus_t * b)
{
  f64 t = 0;

  while (t < b->hold_time)
    {
      int sda, scl;
      i2c_delay (b, b->rise_fall_time);
      b->get_bits (b, &scl, &sda);

      if (scl)
	return;

      t += b->rise_fall_time;
    }
  b->timeout = 1;
}

static void
i2c_start (i2c_bus_t * b)
{
  b->timeout = 0;

  b->put_bits (b, 1, 1);
  i2c_wait_for_scl (b);

  if (vlib_i2c_bus_timed_out (b))
    return;

  b->put_bits (b, 1, 0);
  i2c_delay (b, b->hold_time);
  b->put_bits (b, 0, 0);
  i2c_delay (b, b->hold_time);
}

static void
i2c_stop (i2c_bus_t * b)
{
  b->put_bits (b, 0, 0);
  i2c_delay (b, b->rise_fall_time);

  b->put_bits (b, 1, 0);
  i2c_delay (b, b->hold_time);

  b->put_bits (b, 1, 1);
  i2c_delay (b, b->hold_time);
}

static void
i2c_write_bit (i2c_bus_t * b, int sda)
{
  b->put_bits (b, 0, sda);
  i2c_delay (b, b->rise_fall_time);

  b->put_bits (b, 1, sda);
  i2c_wait_for_scl (b);
  i2c_delay (b, b->hold_time);

  b->put_bits (b, 0, sda);
  i2c_delay (b, b->rise_fall_time);
}

static void
i2c_read_bit (i2c_bus_t * b, int *sda)
{
  int scl;

  b->put_bits (b, 1, 1);
  i2c_wait_for_scl (b);
  i2c_delay (b, b->hold_time);

  b->get_bits (b, &scl, sda);

  b->put_bits (b, 0, 1);
  i2c_delay (b, b->rise_fall_time);
}

static void
i2c_write_byte (i2c_bus_t * b, u8 data)
{
  int i, sda;

  for (i = 7; i >= 0; i--)
    {
      i2c_write_bit (b, (data >> i) & 1);
      if (b->timeout)
	return;
    }

  b->put_bits (b, 0, 1);
  i2c_delay (b, b->rise_fall_time);

  i2c_read_bit (b, &sda);

  if (sda)
    b->timeout = 1;
}


static void
i2c_read_byte (i2c_bus_t * b, u8 * data, int ack)
{
  int i, sda;

  *data = 0;

  b->put_bits (b, 0, 1);
  i2c_delay (b, b->rise_fall_time);

  for (i = 7; i >= 0; i--)
    {
      i2c_read_bit (b, &sda);
      if (b->timeout)
	return;

      *data |= (sda != 0) << i;
    }

  i2c_write_bit (b, ack == 0);
}


void
vlib_i2c_init (i2c_bus_t * b)
{
  f64 tick;
  if (!b->clock)
    b->clock = 400000;

  tick = 1.0 / b->clock;

  /* Spend 40% of time in low and high states */
  if (!b->hold_time)
    b->hold_time = 0.4 * tick;

  /* Spend 10% of time waiting for rise and fall */
  if (!b->rise_fall_time)
    b->rise_fall_time = 0.1 * tick;
}

void
vlib_i2c_xfer (i2c_bus_t * bus, i2c_msg_t * msgs)
{
  i2c_msg_t *msg;
  int i;

  vec_foreach (msg, msgs)
  {
    i2c_start (bus);
    i2c_write_byte (bus,
		    (msg->addr << 1) + (msg->flags == I2C_MSG_FLAG_READ));

    if (msg->flags & I2C_MSG_FLAG_READ)
      for (i = 0; i < msg->len; i++)
	{
	  i2c_read_byte (bus, &msg->buffer[i], /* ack */ i + 1 != msg->len);
	  if (bus->timeout)
	    goto done;
	}

    else
      for (i = 0; i < msg->len; i++)
	{
	  i2c_write_byte (bus, msg->buffer[i]);
	  if (bus->timeout)
	    goto done;
	}
  }

done:
  i2c_stop (bus);
}

void
vlib_i2c_read_eeprom (i2c_bus_t * bus, u8 i2c_addr, u16 start_addr,
		      u16 length, u8 * data)
{
  i2c_msg_t *msg = 0;
  u8 start_address[1];

  vec_validate (msg, 1);

  start_address[0] = start_addr;
  msg[0].addr = i2c_addr;
  msg[0].flags = I2C_MSG_FLAG_WRITE;
  msg[0].buffer = (u8 *) & start_address;
  msg[0].len = 1;

  msg[1].addr = i2c_addr;
  msg[1].flags = I2C_MSG_FLAG_READ;
  msg[1].buffer = data;
  msg[1].len = length;

  vlib_i2c_xfer (bus, msg);

  vec_free (msg);
}

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */
ldpw->epoll_wait_vcl = 1; goto done; } else if (rv < 0) { errno = -rv; rv = -1; goto done; } } else ldpw->epoll_wait_vcl = 0; if (libc_epfd > 0) { rv = libc_epoll_pwait (libc_epfd, events, maxevents, 0, sigmask); if (rv != 0) goto done; } } while ((timeout == -1) || (clib_time_now (&ldpw->clib_time) < max_time)); done: return rv; } int epoll_pwait (int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t * sigmask) { return ldp_epoll_pwait (epfd, events, maxevents, timeout, sigmask); } int epoll_wait (int epfd, struct epoll_event *events, int maxevents, int timeout) { return ldp_epoll_pwait (epfd, events, maxevents, timeout, NULL); } int poll (struct pollfd *fds, nfds_t nfds, int timeout) { ldp_worker_ctx_t *ldpw = ldp_worker_get_current (); int rv, i, n_revents = 0; vls_handle_t vlsh; vcl_poll_t *vp; double max_time; LDBG (3, "fds %p, nfds %d, timeout %d", fds, nfds, timeout); if (PREDICT_FALSE (ldpw->clib_time.init_cpu_time == 0)) clib_time_init (&ldpw->clib_time); max_time = (timeout >= 0) ? (f64) timeout / 1000 : 0; max_time += clib_time_now (&ldpw->clib_time); for (i = 0; i < nfds; i++) { if (fds[i].fd < 0) continue; vlsh = ldp_fd_to_vlsh (fds[i].fd); if (vlsh != VLS_INVALID_HANDLE) { fds[i].fd = -fds[i].fd; vec_add2 (ldpw->vcl_poll, vp, 1); vp->fds_ndx = i; vp->sh = vlsh_to_sh (vlsh); vp->events = fds[i].events; #ifdef __USE_XOPEN2K if (fds[i].events & POLLRDNORM) vp->events |= POLLIN; if (fds[i].events & POLLWRNORM) vp->events |= POLLOUT; #endif vp->revents = fds[i].revents; } else { vec_add1 (ldpw->libc_poll, fds[i]); vec_add1 (ldpw->libc_poll_idxs, i); } } do { if (vec_len (ldpw->vcl_poll)) { rv = vppcom_poll (ldpw->vcl_poll, vec_len (ldpw->vcl_poll), 0); if (rv < 0) { errno = -rv; rv = -1; goto done; } else n_revents += rv; } if (vec_len (ldpw->libc_poll)) { rv = libc_poll (ldpw->libc_poll, vec_len (ldpw->libc_poll), 0); if (rv < 0) goto done; else n_revents += rv; } if (n_revents) { rv = n_revents; goto done; } } while ((timeout < 0) || (clib_time_now (&ldpw->clib_time) < max_time)); rv = 0; done: vec_foreach (vp, ldpw->vcl_poll) { fds[vp->fds_ndx].fd = -fds[vp->fds_ndx].fd; fds[vp->fds_ndx].revents = vp->revents; #ifdef __USE_XOPEN2K if ((fds[vp->fds_ndx].revents & POLLIN) && (fds[vp->fds_ndx].events & POLLRDNORM)) fds[vp->fds_ndx].revents |= POLLRDNORM; if ((fds[vp->fds_ndx].revents & POLLOUT) && (fds[vp->fds_ndx].events & POLLWRNORM)) fds[vp->fds_ndx].revents |= POLLWRNORM; #endif } vec_reset_length (ldpw->vcl_poll); for (i = 0; i < vec_len (ldpw->libc_poll); i++) { fds[ldpw->libc_poll_idxs[i]].revents = ldpw->libc_poll[i].revents; } vec_reset_length (ldpw->libc_poll_idxs); vec_reset_length (ldpw->libc_poll); return rv; } #ifdef USE_GNU int ppoll (struct pollfd *fds, nfds_t nfds, const struct timespec *timeout, const sigset_t * sigmask) { if ((errno = -ldp_init ())) return -1; clib_warning ("LDP<%d>: LDP-TBD", getpid ()); errno = ENOSYS; return -1; } #endif void CONSTRUCTOR_ATTRIBUTE ldp_constructor (void); void DESTRUCTOR_ATTRIBUTE ldp_destructor (void); /* * This function is called when the library is loaded */ void ldp_constructor (void) { swrap_constructor (); if (ldp_init () != 0) { fprintf (stderr, "\nLDP<%d>: ERROR: ldp_constructor: failed!\n", getpid ()); _exit (1); } else if (LDP_DEBUG > 0) clib_warning ("LDP<%d>: LDP constructor: done!\n", getpid ()); } /* * This function is called when the library is unloaded */ void ldp_destructor (void) { /* swrap_destructor (); if (ldp->init) ldp->init = 0; */ /* Don't use clib_warning() here because that calls writev() * which will call ldp_init(). */ if (LDP_DEBUG > 0) fprintf (stderr, "%s:%d: LDP<%d>: LDP destructor: done!\n", __func__, __LINE__, getpid ()); } /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */