text: /****************************************************************************
   * drivers/net/phy_notify.c
   *
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.  The
   * ASF licenses this file to you 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.
   *
   ****************************************************************************/
  
  /****************************************************************************
   * Included Files
   ****************************************************************************/
  
  #include <nuttx/config.h>
  
  /* Force verbose debug on in this file only to support unit-level testing. */
  
  #ifdef CONFIG_NETDEV_PHY_DEBUG
  #  undef  CONFIG_DEBUG_INFO
  #  define CONFIG_DEBUG_INFO 1
  #  undef  CONFIG_DEBUG_NET
  #  define CONFIG_DEBUG_NET 1
  #endif
  
  #include <stdio.h>
  #include <unistd.h>
  #include <string.h>
  #include <assert.h>
  #include <errno.h>
  #include <debug.h>
  #include <net/if.h>
  
  #include <nuttx/arch.h>
  #include <nuttx/irq.h>
  #include <nuttx/semaphore.h>
  #include <nuttx/signal.h>
  #include <nuttx/net/phy.h>
  
  #ifdef CONFIG_ARCH_PHY_INTERRUPT
  
  /****************************************************************************
   * Pre-processor Definitions
   ****************************************************************************/
  
  /* The current design artificially limits the number of notification client.
   * This is an arbitrary limit.  If you exceed it, simply adjust the affected
   * areas.
   */
  
  #if CONFIG_PHY_NOTIFICATION_NCLIENTS > 4
  #  warning Fix me!! Support currently limited to 4 clients
  #  undef  CONFIG_PHY_NOTIFICATION_NCLIENTS
  #  define CONFIG_PHY_NOTIFICATION_NCLIENTS 4
  #endif
  
  /* Debug ********************************************************************/
  
  /* Extra, in-depth debug output that is only available if
   * CONFIG_NETDEV_PHY_DEBUG us defined.
   */
  
  #ifdef CONFIG_NETDEV_PHY_DEBUG
  #  define phyinfo   _info
  #  define phyerr    _err
  #else
  #  define phyinfo(x...)
  #  define phyerr(x...)
  #endif
  
  /****************************************************************************
   * Private Types
   ****************************************************************************/
  
  /* This describes the state of one notification.  There may be up to
   * CONFIG_PHY_NOTIFICATION_NCLIENTS such notifications active simultaneously.
   *
   * There
   */
  
  struct phy_notify_s
  {
    bool assigned;
    char intf[IFNAMSIZ + 1];
    pid_t pid;
    struct sigevent event;
    struct sigwork_s work;
    phy_enable_t enable;
  };
  
  /****************************************************************************
   * Private Function Prototypes
   ****************************************************************************/
  
  static int phy_semtake(void);
  static FAR struct phy_notify_s *phy_find_unassigned(void);
  static FAR struct phy_notify_s *phy_find_assigned(FAR const char *intf,
                                                    pid_t pid);
  static int phy_handler(int irq, FAR void *context, FAR void *arg);
  
  /****************************************************************************
   * Private Data
   ****************************************************************************/
  
  /* Serializes access to the g_notify_clients array */
  
  static sem_t g_notify_clients_sem = SEM_INITIALIZER(1);
  
  /* This is a array the hold information for each PHY notification client */
  
  static struct phy_notify_s
    g_notify_clients[CONFIG_PHY_NOTIFICATION_NCLIENTS];
  
  /****************************************************************************
   * Private Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: phy_semtake
   ****************************************************************************/
  
  static int phy_semtake(void)
  {
    return nxsem_wait_uninterruptible(&g_notify_clients_sem);
  }
  
  #define phy_semgive() nxsem_post(&g_notify_clients_sem);
  
  /****************************************************************************
   * Name: phy_find_unassigned
   ****************************************************************************/
  
  static FAR struct phy_notify_s *phy_find_unassigned(void)
  {
    FAR struct phy_notify_s *client;
    int ret;
    int i;
  
    ret = phy_semtake();
    if (ret < 0)
      {
        phyerr("ERROR: phy_semtake failed: %d\n", ret);
        return NULL;
      }
  
    for (i = 0; i < CONFIG_PHY_NOTIFICATION_NCLIENTS; i++)
      {
        client = &g_notify_clients[i];
        if (!client->assigned)
          {
            /* Assign and re-initialized the entry */
  
            client->assigned = true;
            client->intf[0]  = '\0';
            client->pid      = -1;
            client->enable   = NULL;
  
            /* Return the client entry assigned to the caller */
  
            phy_semgive();
            phyinfo("Returning client %d\n", i);
            return client;
          }
      }
  
    /* Ooops... too many */
  
    phyerr("ERROR: No free client entries\n");
    phy_semgive();
    return NULL;
  }
  
  /****************************************************************************
   * Name: phy_find_assigned
   ****************************************************************************/
  
  static FAR struct phy_notify_s *phy_find_assigned(FAR const char *intf,
                                                    pid_t pid)
  {
    FAR struct phy_notify_s *client;
    int ret;
    int i;
  
    ret = phy_semtake();
    if (ret < 0)
      {
        phyerr("ERROR: phy_semtake failed: %d\n", ret);
        return NULL;
      }
  
    for (i = 0; i < CONFIG_PHY_NOTIFICATION_NCLIENTS; i++)
      {
        client = &g_notify_clients[i];
        if (client->assigned && client->pid == pid &&
            strncmp(client->intf, intf, IFNAMSIZ) == 0)
          {
            /* Return the matching client entry to the caller */
  
            phy_semgive();
            phyinfo("Returning client %d\n", i);
            return client;
          }
      }
  
    /* Ooops... not found */
  
    phy_semgive();
    return NULL;
  }
  
  /****************************************************************************
   * Name: phy_handler
   ****************************************************************************/
  
  static int phy_handler(int irq, FAR void *context, FAR void *arg)
  {
    FAR struct phy_notify_s *client = (FAR struct phy_notify_s *)arg;
    int ret;
  
    DEBUGASSERT(client != NULL && client->assigned && client->enable);
    phyinfo("Signaling PID=%d with event %p\n", client->pid, &client->event);
  
    /* Disable further interrupts */
  
    client->enable(false);
  
    /* Signal the client that the PHY has something interesting to say to us */
  
    ret = nxsig_notification(client->pid, &client->event,
                             SI_QUEUE, &client->work);
    if (ret < 0)
      {
        phyerr("ERROR: nxsig_notification failed: %d\n", ret);
      }
  
    return OK;
  }
  
  /****************************************************************************
   * Public Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: phy_notify_subscribe
   *
   * Description:
   *   Setup up to deliver signals to the task identified by 'pid' when
   *   there is any change indicated by an interrupt from the PHY associated
   *   with 'intf'
   *
   *   NOTE: This function is intended to be called only from an Ethernet
   *   driver in support of the SIOCMIISIG ioctl command.  It should never
   *   by called directly by application logic.
   *
   * Input Parameters:
   *   intf  - Provides the name of the network interface, for example, "eth0".
   *   pid   - Identifies the task to receive the signal.  The special value
   *           of zero means to use the pid of the current task.
   *   event - Describes the way a task is to be notified
   *
   * Returned Value:
   *   OK on success; Negated errno on failure.
   *
   ****************************************************************************/
  
  int phy_notify_subscribe(FAR const char *intf, pid_t pid,
                           FAR struct sigevent *event)
  {
    FAR struct phy_notify_s *client;
    int ret = OK;
  
    DEBUGASSERT(intf);
  
    phyinfo("%s: PID=%d event=%p\n", intf, pid, event);
  
    /* The special value pid == 0 means to use the pid of the current task. */
  
    if (pid == 0)
      {
        pid = getpid();
        phyinfo("Actual PID=%d\n", pid);
      }
  
    /* Unsubscribe if sigev_notify field equals SIGEV_NONE */
  
    if (event->sigev_notify == SIGEV_NONE)
      {
        return phy_notify_unsubscribe(intf, pid);
      }
  
    /* Check if this client already exists */
  
    client = phy_find_assigned(intf, pid);
    if (client != NULL)
      {
        /* Yes.. update the signal number and argument */
  
        client->event = *event;
      }
    else
      {
        /* No, allocate a new slot in the client notification table */
  
        client = phy_find_unassigned();
        if (client == NULL)
          {
            phyerr("ERROR: Failed to allocate a client entry\n");
            return -ENOMEM;
          }
  
        /* Initialize the new client entry */
  
        client->pid   = pid;
        client->event = *event;
        strncpy(client->intf, intf, IFNAMSIZ + 1);
        client->intf[IFNAMSIZ] = '\0';
  
        /* Attach/re-attach the PHY interrupt */
  
        ret = arch_phy_irq(intf, phy_handler, client, &client->enable);
      }
  
    /* Enable/re-enable the PH interrupt */
  
    DEBUGASSERT(client->enable);
    client->enable(true);
    return ret;
  }
  
  /****************************************************************************
   * Name: phy_notify_unsubscribe
   *
   * Description:
   *   Stop the deliver of signals for events from the PHY associated with
   *   'intf' to the task identified by 'pid'
   *
   *   NOTE: This function is intended to be called only from an Ethernet
   *   driver in support of the SIOCMIISIG ioctl command.  It should never
   *   by called directly by application logic.
   *
   * Input Parameters:
   *   intf  - Provides the name of the network interface, for example, "eth0".
   *   pid   - Identifies the task that was receiving notifications.
   *
   * Returned Value:
   *   OK on success; Negated errno on failure.
   *
   ****************************************************************************/
  
  int phy_notify_unsubscribe(FAR const char *intf, pid_t pid)
  {
    FAR struct phy_notify_s *client;
    int ret;
  
    phyinfo("%s: PID=%d\n", intf, pid);
  
    /* Find the client entry for this interface */
  
    client = phy_find_assigned(intf, pid);
    if (client == NULL)
      {
        phyerr("ERROR: No such client\n");
        return -ENOENT;
      }
  
    /* Detach and disable the PHY interrupt */
  
    ret = phy_semtake();
    if (ret >= 0)
      {
        arch_phy_irq(intf, NULL, NULL, NULL);
  
        /* Cancel any pending notification */
  
        nxsig_cancel_notification(&client->work);
  
        /* Un-initialize the client entry */
  
        client->assigned = false;
        client->intf[0]  = '\0';
        client->pid      = -1;
  
        phy_semgive();
      }
  
    return OK;
  }
  
  #endif /* CONFIG_ARCH_PHY_INTERRUPT */
  