1. 程式人生 > >pixhawk px4 字元型裝置驅動

pixhawk px4 字元型裝置驅動

分析字元型裝置為什麼register/open/read/write怎樣與底層驅動程式碼聯絡在一起的,為什麼需要註冊,為什麼會有路徑,為什麼open之後read/write就可以讀/寫了

另:此篇blog是以nuttx官網介紹為出發點,先介紹nuttx的字元型裝置驅動框架,再以GPS/串列埠為例思考pixhawk字元型裝置驅動編寫過程

也可以參考linux字元型驅動驅動

字元型裝置

所有的結構體和API都在Firmware/Nuttx/nuttx/include/nuttx/fs/fs.h

每個字元裝置驅動程式必須實現struct file_operations的例項
struct file_operations{
int open(FAR struct file *filep);
int close(FAR struct file *filep);
ssize_t read(FAR struct file *filep, FAR char *buffer, size_t buflen);
ssize_t write(FAR struct file *filep, FAR const char *buffer, size_t buflen);
off_t seek(FAR struct file *filep, off_t offset, int whence);
int ioctl(FAR struct file *filep, int cmd, unsigned long arg);
int poll(FAR struct file *filep, struct pollfd *fds, bool setup);
}

呼叫int register_driver(const char *path, const struct file_operations*fops, mode_t mode, void *priv)(Firmware/Nuttx/nuttx/fs/fs_registerdrivers.c)之後就可以使用使用者介面 driver operations 包括 open()close()read()write(), etc.

串列埠

所有串列埠要用到的結構體和API都在include/nuttx/serial/serial.h

每個串列埠裝置驅動程式必須實現struct uart_ops_s的例項

struct uart_ops_s{
int setup(FAR struct uart_dev_s *dev);
void shutdown(FAR struct uart_dev_s *dev);
int attach(FAR struct uart_dev_s *dev);
void detach(FAR struct uart_dev_s *dev);
int ioctl(FAR struct file *filep, int cmd, unsigned long arg);
int receive(FAR struct uart_dev_s *dev, unsigned int *status);
void rxint(FAR struct uart_dev_s *dev, bool enable);
bool rxavailable(FAR struct uart_dev_s *dev);
#ifdef CONFIG_SERIAL_IFLOWCONTROL
bool rxflowcontrol(FAR struct uart_dev_s *dev, unsigned int nbuffered, bool upper);
#endif
void send(FAR struct uart_dev_s *dev, int ch);
void txint(FAR struct uart_dev_s *dev, bool enable);
bool txready(FAR struct uart_dev_s *dev);
bool txempty(FAR struct uart_dev_s *dev);
}

呼叫intuart_register(FAR const char *path, FAR uart_dev_t *dev)

( Firmware/Nuttx/nuttx/drivers/serial/serial.c)註冊,註冊路徑通常為/dev/ttyS0/dev/ttyS1, etc

int uart_register(FAR const char *path, FAR uart_dev_t *dev)
{
  sem_init(&dev->xmit.sem, 0, 1);
  sem_init(&dev->recv.sem, 0, 1);
  sem_init(&dev->closesem, 0, 1);
  sem_init(&dev->xmitsem,  0, 0);
  sem_init(&dev->recvsem,  0, 0);
#ifndef CONFIG_DISABLE_POLL
  sem_init(&dev->pollsem,  0, 1);
#endif

  dbg("Registering %s\n", path);
  return register_driver(path, &g_serialops, 0666, dev);//呼叫//Firmware/Nuttx/nuttx/fs/fs_registerdrivers.c
}

之後就可以呼叫常用的使用者介面 character drivers open()close()read()write(), etc.

例子:Firmware/Nuttx/nuttx/arch/arm/src/stm32/stm32_serial.c

可以看出

struct file_operations
{
  /* The device driver open method differs from the mountpoint open method */
  int     (*open)(FAR struct file *filp);
  /* The following methods must be identical in signature and position because
   * the struct file_operations and struct mountp_operations are treated like
   * unions.
   */
  int     (*close)(FAR struct file *filp);
  ssize_t (*read)(FAR struct file *filp, FAR char *buffer, size_t buflen);
  ssize_t (*write)(FAR struct file *filp, FAR const char *buffer, size_t buflen);
  off_t   (*seek)(FAR struct file *filp, off_t offset, int whence);
  int     (*ioctl)(FAR struct file *filp, int cmd, unsigned long arg);
#ifndef CONFIG_DISABLE_POLL
  int     (*poll)(FAR struct file *filp, struct pollfd *fds, bool setup);
#endif
  /* The two structures need not be common after this point */
};

定義了file_operations結構體,指向函式的結構體

static const struct file_operations g_serialops =
{
  uart_open,  /* open */
  uart_close, /* close */
  uart_read,  /* read */
  uart_write, /* write */
  0,          /* seek */
  uart_ioctl  /* ioctl */
#ifndef CONFIG_DISABLE_POLL
  , uart_poll /* poll */
#endif
};

在當前原始檔中都有函式的實現

/************************************************************************************
 * Name: uart_open
 *
 * Description:
 *   This routine is called whenever a serial port is opened.
 *
 ************************************************************************************/
static int uart_open(FAR struct file *filep)
{
  struct inode *inode = filep->f_inode;
  uart_dev_t   *dev   = inode->i_private;
  uint8_t       tmp;
  int           ret;
  /* If the port is the middle of closing, wait until the close is finished.
   * If a signal is received while we are waiting, then return EINTR.
   */
  ret = uart_takesem(&dev->closesem, true);
  if (ret < 0)
    {
      /* A signal received while waiting for the last close operation. */
      return ret;
    }
#ifdef CONFIG_SERIAL_REMOVABLE
  /* If the removable device is no longer connected, refuse to open the
   * device.  We check this after obtaining the close semaphore because
   * we might have been waiting when the device was disconnected.
   */
  if (dev->disconnected)
    {
      ret = -ENOTCONN;
      goto errout_with_sem;
    }
#endif
  /* Start up serial port */
  /* Increment the count of references to the device. */
  tmp = dev->open_count + 1;
  if (tmp == 0)
    {
      /* More than 255 opens; uint8_t overflows to zero */
      ret = -EMFILE;
      goto errout_with_sem;
    }
  /* Check if this is the first time that the driver has been opened. */
  if (tmp == 1)
    {
      irqstate_t flags = irqsave();
      /* If this is the console, then the UART has already been initialized. */
      if (!dev->isconsole)
        {
          /* Perform one time hardware initialization */
          ret = uart_setup(dev);
          if (ret < 0)
            {
              irqrestore(flags);
              goto errout_with_sem;
            }
        }
      /* In any event, we do have to configure for interrupt driven mode of
       * operation.  Attach the hardware IRQ(s). Hmm.. should shutdown() the
       * the device in the rare case that uart_attach() fails, tmp==1, and
       * this is not the console.
       */
      ret = uart_attach(dev);
      if (ret < 0)
        {
           uart_shutdown(dev);
           irqrestore(flags);
           goto errout_with_sem;
        }
      /* Mark the io buffers empty */
      dev->xmit.head = 0;
      dev->xmit.tail = 0;
      dev->recv.head = 0;
      dev->recv.tail = 0;
      uart_onrxdeque(dev);
      /* initialise termios state */
#ifdef CONFIG_SERIAL_TERMIOS
      dev->tc_iflag = 0;
      if (dev->isconsole == true)
        {
          /* enable \n -> \r\n translation for the console */
          dev->tc_oflag = OPOST | ONLCR;
        }
      else
        {
          dev->tc_oflag = 0;
        }
#endif
      /* Enable the RX interrupt */
      uart_enablerxint(dev);
      irqrestore(flags);
    }
  /* Save the new open count on success */
  dev->open_count = tmp;
errout_with_sem:
  uart_givesem(&dev->closesem);
  return ret;
}

每個uart都會使用uart_ops_s結構體

struct uart_ops_s
{
  /* Configure the UART baud, bits, parity, fifos, etc. This method is called
   * the first time that the serial port is opened.  For the serial console,
   * this will occur very early in initialization; for other serial ports this
   * will occur when the port is first opened.  This setup does not include
   * attaching or enabling interrupts.  That portion of the UART setup is
   * performed when the attach() method is called.
   */
  CODE int (*setup)(FAR struct uart_dev_s *dev);
  /* Disable the UART.  This method is called when the serial port is closed.
   * This method reverses the operation the setup method.  NOTE that the serial
   * console is never shutdown.
   */
  CODE void (*shutdown)(FAR struct uart_dev_s *dev);
  /* Configure the UART to operation in interrupt driven mode.  This method is
   * called when the serial port is opened.  Normally, this is just after the
   * the setup() method is called, however, the serial console may operate in
   * a non-interrupt driven mode during the boot phase.
   *
   * RX and TX interrupts are not enabled when by the attach method (unless the
   * hardware supports multiple levels of interrupt enabling).  The RX and TX
   * interrupts are not enabled until the txint() and rxint() methods are called.
   */
  CODE int (*attach)(FAR struct uart_dev_s *dev);
  /* Detach UART interrupts.  This method is called when the serial port is
   * closed normally just before the shutdown method is called.  The exception is
   * the serial console which is never shutdown.
   */
  CODE void (*detach)(FAR struct uart_dev_s *dev);
  /* All ioctl calls will be routed through this method */
  CODE int (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg);
  /* Called (usually) from the interrupt level to receive one character from
   * the UART.  Error bits associated with the receipt are provided in the
   * the return 'status'.
   */
  CODE int (*receive)(FAR struct uart_dev_s *dev, FAR unsigned int *status);
  /* Call to enable or disable RX interrupts */
  CODE void (*rxint)(FAR struct uart_dev_s *dev, bool enable);
  /* Return true if the receive data is available */
  CODE bool (*rxavailable)(FAR struct uart_dev_s *dev);
  /* This method will send one byte on the UART */
  CODE void (*send)(FAR struct uart_dev_s *dev, int ch);
  /* Call to enable or disable TX interrupts */
  CODE void (*txint)(FAR struct uart_dev_s *dev, bool enable);
  /* Return true if the tranmsit hardware is ready to send another byte.  This
   * is used to determine if send() method can be called.
   */
  CODE bool (*txready)(FAR struct uart_dev_s *dev);
  /* Return true if all characters have been sent.  If for example, the UART
   * hardware implements FIFOs, then this would mean the transmit FIFO is
   * empty.  This method is called when the driver needs to make sure that
   * all characters are "drained" from the TX hardware.
   */
  CODE bool (*txempty)(FAR struct uart_dev_s *dev);
  /*
   * Drivers can optionally provide this callback which is invoked any time
   * received characters have been dequeued in uart_read().  The expected
   * use-case is for reenabling nRTS if the driver is doing software assisted
   * HW flow control.
   */
  CODE void (*onrxdeque)(FAR struct uart_dev_s *dev);
};
static const struct uart_ops_s g_uart_ops =
{
  .setup          = up_setup,
  .shutdown       = up_shutdown,
  .attach         = up_attach,
  .detach         = up_detach,
  .ioctl          = up_ioctl,
  .receive        = up_receive,
  .rxint          = up_rxint,
  .rxavailable    = up_rxavailable,
  .send           = up_send,
  .txint          = up_txint,
  .txready        = up_txready,
  .txempty        = up_txready,
#ifdef HWRTS_BROKEN
  .onrxdeque      = up_onrxdeque,
#endif
};

uart_ops_s結構體中的成員都是指向函式的指標,函式都在當前原始檔中實現

/****************************************************************************
 * Name: up_setup
 *
 * Description:
 *   Configure the USART baud, bits, parity, etc. This method is called the
 *   first time that the serial port is opened.
 *
 ****************************************************************************/
static int up_setup(struct uart_dev_s *dev)
{
  struct up_dev_s *priv = (struct up_dev_s*)dev->priv;
#ifndef CONFIG_SUPPRESS_UART_CONFIG
  uint32_t regval;
  /* Note: The logic here depends on the fact that that the USART module
   * was enabled in stm32_lowsetup().
   */
  /* Configure pins for USART use */
  stm32_configgpio(priv->tx_gpio);
  stm32_configgpio(priv->rx_gpio);
#ifdef CONFIG_SERIAL_OFLOWCONTROL
  if (priv->cts_gpio != 0)
    {
      stm32_configgpio(priv->cts_gpio);
    }
#endif
#ifdef CONFIG_SERIAL_IFLOWCONTROL
  if (priv->rts_gpio != 0)
    {
      uint32_t config = priv->rts_gpio;
#ifdef HWRTS_BROKEN
      config = (config & ~GPIO_MODE_MASK) | GPIO_OUTPUT; /* Instead of letting hw manage this pin, we will bitbang */
#endif
      stm32_configgpio(config);
    }
#endif
#if HAVE_RS485
  if (priv->rs485_dir_gpio != 0)
    {
      stm32_configgpio(priv->rs485_dir_gpio);
      stm32_gpiowrite(priv->rs485_dir_gpio, !priv->rs485_dir_polarity);
    }
#endif
  /* Configure CR2 */
  /* Clear STOP, CLKEN, CPOL, CPHA, LBCL, and interrupt enable bits */
  regval  = up_serialin(priv, STM32_USART_CR2_OFFSET);
  regval &= ~(USART_CR2_STOP_MASK | USART_CR2_CLKEN | USART_CR2_CPOL |
              USART_CR2_CPHA | USART_CR2_LBCL | USART_CR2_LBDIE);
  /* Configure STOP bits */
  if (priv->stopbits2)
    {
      regval |= USART_CR2_STOP2;
    }
  up_serialout(priv, STM32_USART_CR2_OFFSET, regval);
  /* Configure CR1 */
  /* Clear TE, REm and all interrupt enable bits */
  regval  = up_serialin(priv, STM32_USART_CR1_OFFSET);
  regval &= ~(USART_CR1_TE | USART_CR1_RE | USART_CR1_ALLINTS);
  up_serialout(priv, STM32_USART_CR1_OFFSET, regval);
  /* Configure CR3 */
  /* Clear CTSE, RTSE, and all interrupt enable bits */
  regval  = up_serialin(priv, STM32_USART_CR3_OFFSET);
  regval &= ~(USART_CR3_CTSIE | USART_CR3_CTSE | USART_CR3_RTSE | USART_CR3_EIE);
  up_serialout(priv, STM32_USART_CR3_OFFSET, regval);
  /* Configure the USART line format and speed. */
  up_set_format(dev);
  /* Enable Rx, Tx, and the USART */
  regval      = up_serialin(priv, STM32_USART_CR1_OFFSET);
  regval     |= (USART_CR1_UE | USART_CR1_TE | USART_CR1_RE);
  up_serialout(priv, STM32_USART_CR1_OFFSET, regval);
  /* Set up the cached interrupt enables value */
  priv->ie    = 0;
  return OK;
}

uart_ops_s結構體用於uart埠使用

/* This describes the state of the STM32 USART1 ports. */
#ifdef CONFIG_STM32_USART1
static struct up_dev_s g_usart1priv =
{
  .dev =
    {
#if CONSOLE_UART == 1
      .isconsole = true,
#endif
      .recv      =
      {
        .size    = CONFIG_USART1_RXBUFSIZE,
        .buffer  = g_usart1rxbuffer,
      },
      .xmit      =
      {
        .size    = CONFIG_USART1_TXBUFSIZE,
        .buffer  = g_usart1txbuffer,
      },
#ifdef CONFIG_USART1_RXDMA
      .ops       = &g_uart_dma_ops,
#else
      .ops       = &g_uart_ops,
#endif
      .priv      = &g_usart1priv,
    },
  .irq           = STM32_IRQ_USART1,
  .parity        = CONFIG_USART1_PARITY,
  .bits          = CONFIG_USART1_BITS,
  .stopbits2     = CONFIG_USART1_2STOP,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
  .iflow         = false,
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
  .oflow         = false,
#endif
  .baud          = CONFIG_USART1_BAUD,
  .apbclock      = STM32_PCLK2_FREQUENCY,
  .usartbase     = STM32_USART1_BASE,
  .tx_gpio       = GPIO_USART1_TX,
  .rx_gpio       = GPIO_USART1_RX,
#if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_USART1_OFLOWCONTROL)
  .cts_gpio      = GPIO_USART1_CTS,
#endif
#if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_USART1_IFLOWCONTROL)
  .rts_gpio      = GPIO_USART1_RTS,
#endif
#ifdef CONFIG_USART1_RXDMA
  .rxdma_channel = DMAMAP_USART1_RX,
  .rxfifo        = g_usart1rxfifo,
#endif
  .vector        = up_interrupt_usart1,

#ifdef CONFIG_USART1_RS485
  .rs485_dir_gpio = GPIO_USART1_RS485_DIR,
#  if (CONFIG_USART1_RS485_DIR_POLARITY == 0)
  .rs485_dir_polarity = false,
#  else
  .rs485_dir_polarity = true,
#  endif
#endif
};
#endif

uart埠號最後都寫在*uart_devs結構體中,當作結構成員

/* This table lets us iterate over the configured USARTs */
static struct up_dev_s *uart_devs[STM32_NUSART] =
{
#ifdef CONFIG_STM32_USART1
  [0] = &g_usart1priv,
#endif
#ifdef CONFIG_STM32_USART2
  [1] = &g_usart2priv,
#endif
#ifdef CONFIG_STM32_USART3
  [2] = &g_usart3priv,
#endif
#ifdef CONFIG_STM32_UART4
  [3] = &g_uart4priv,
#endif
#ifdef CONFIG_STM32_UART5
  [4] = &g_uart5priv,
#endif
#ifdef CONFIG_STM32_USART6
  [5] = &g_usart6priv,
#endif
#ifdef CONFIG_STM32_UART7
  [6] = &g_uart7priv,
#endif
#ifdef CONFIG_STM32_UART8
  [7] = &g_uart8priv,
#endif
};

然後用於up_serialinit()等,這樣就可以直接操作uart埠了

/****************************************************************************
 * Name: up_serialinit
 *
 * Description:
 *   Register serial console and serial ports.  This assumes
 *   that up_earlyserialinit was called previously.
 *
 ****************************************************************************/
void up_serialinit(void)
{
#ifdef HAVE_UART
  char devname[16];
  unsigned i;
  unsigned minor = 0;
#ifdef CONFIG_PM
  int ret;
#endif
  /* Register to receive power management callbacks */
#ifdef CONFIG_PM
  ret = pm_register(&g_serialcb);
  DEBUGASSERT(ret == OK);
#endif
  /* Register the console */
#if CONSOLE_UART > 0
  (void)uart_register("/dev/console", &uart_devs[CONSOLE_UART - 1]->dev);
#ifndef CONFIG_SERIAL_DISABLE_REORDERING
  /* If not disabled, register the console UART to ttyS0 and exclude
   * it from initializing it further down
   */
  (void)uart_register("/dev/ttyS0",   &uart_devs[CONSOLE_UART - 1]->dev);
  minor = 1;
#endif /* CONFIG_SERIAL_DISABLE_REORDERING not defined */
/* If we need to re-initialise the console to enable DMA do that here. */
# ifdef SERIAL_HAVE_CONSOLE_DMA
  up_dma_setup(&uart_devs[CONSOLE_UART - 1]->dev);
# endif
#endif /* CONSOLE_UART > 0 */
  /* Register all remaining USARTs */
  strcpy(devname, "/dev/ttySx");
  for (i = 0; i < STM32_NUSART; i++)
    {
      /* Don't create a device for non configured ports */
      if (uart_devs[i] == 0)
        {
          continue;
        }
#ifndef CONFIG_SERIAL_DISABLE_REORDERING
      /* Don't create a device for the console - we did that above */
      if (uart_devs[i]->dev.isconsole)
        {
          continue;
        }
#endif
      /* Register USARTs as devices in increasing order */
      devname[9] = '0' + minor++;
      (void)uart_register(devname, &uart_devs[i]->dev);
    }
#endif /* HAVE UART */
}

此時(void)uart_register("/dev/ttyS0",&uart_devs[CONSOLE_UART-1]->dev);就把uart_devs[CONSOLE_UART-1]->dev註冊到了/dev/ttyS0路徑下

int uart_register(FAR const char *path, FAR uart_dev_t *dev)
{
  sem_init(&dev->xmit.sem, 0, 1);
  sem_init(&dev->recv.sem, 0, 1);
  sem_init(&dev->closesem, 0, 1);
  sem_init(&dev->xmitsem,  0, 0);
  sem_init(&dev->recvsem,  0, 0);
#ifndef CONFIG_DISABLE_POLL
  sem_init(&dev->pollsem,  0, 1);
#endif

  dbg("Registering %s\n", path);
  return register_driver(path, &g_serialops, 0666, dev);//呼叫//Firmware/Nuttx/nuttx/fs/fs_registerdrivers.c
}
/****************************************************************************
 * Name: register_driver
 *
 * Description:
 *   Register a character driver inode the pseudo file system.
 *
 * Input parameters:
 *   path - The path to the inode to create
 *   fops - The file operations structure
 *   mode - inmode priviledges (not used)
 *   priv - Private, user data that will be associated with the inode.
 *
 * Returned Value:
 *   Zero on success (with the inode point in 'inode'); A negated errno
 *   value is returned on a failure (all error values returned by
 *   inode_reserve):
 *
 *   EINVAL - 'path' is invalid for this operation
 *   EEXIST - An inode already exists at 'path'
 *   ENOMEM - Failed to allocate in-memory resources for the operation
 *
 ****************************************************************************/
int register_driver(FAR const char *path, FAR const struct file_operations *fops,
                    mode_t mode, FAR void *priv)
{
  FAR struct inode *node;
  int ret;
  /* Insert a dummy node -- we need to hold the inode semaphore because we
   * will have a momentarily bad structure.
   */
  inode_semtake();
  ret = inode_reserve(path, &node);
  if (ret >= 0)
    {
      /* We have it, now populate it with driver specific information. */
      INODE_SET_DRIVER(node);
      node->u.i_ops   = fops;
#ifdef CONFIG_FILE_MODE
      node->i_mode    = mode;
#endif
      node->i_private = priv;
      ret             = OK;
    }
  inode_semgive();
  return ret;
}

到此,還沒涉及字元型裝置基本操作,read/write/open等

在GPS驅動中ret = ::read(_serial_fd, buf, buf_length);

進read函式,就到了Firmware/Nuttx/nuttx/fs/fs_read.c中了

ssize_t read(int fd, FAR void *buf, size_t nbytes)
{
  /* Did we get a valid file descriptor? */
#if CONFIG_NFILE_DESCRIPTORS > 0
  if ((unsigned int)fd >= CONFIG_NFILE_DESCRIPTORS)
#endif
    {
      /* No.. If networking is enabled, read() is the same as recv() with
       * the flags parameter set to zero.
       */
#if defined(CONFIG_NET) && CONFIG_NSOCKET_DESCRIPTORS > 0
      return recv(fd, buf, nbytes, 0);
#else
      /* No networking... it is a bad descriptor in any event */
      set_errno(EBADF);
      return ERROR;
#endif
    }
  /* The descriptor is in a valid range to file descriptor... do the read */
#if CONFIG_NFILE_DESCRIPTORS > 0
  return file_read(fd, buf, nbytes);
#endif
}

所有的字元型裝置操作都會定位到這裡,所以針對不同的裝置必須有區分,那如何區分?只有靠fd這個字元型描述符了

那麼來看看GPS裡fd是如何產生的

_serial_fd = ::open(_port, O_RDWR |O_NOCTTY);

然後這個open也定位到Firmware/Nuttx/nuttx/fs這個路徑下的字元型裝置基本操作fs_open.c了

/****************************************************************************
 * Name: open
 *
 * Description:
 *   Standard 'open' interface
 *
 ****************************************************************************/
int open(const char *path, int oflags, ...)
{
  FAR struct filelist *list;
  FAR struct inode    *inode;
  FAR const char      *relpath = NULL;
#if defined(CONFIG_FILE_MODE) || !defined(CONFIG_DISABLE_MOUNTPOINT)
  mode_t               mode = 0666;
#endif
  int                  ret;
  int                  fd;
  /* Get the thread-specific file list */
  list = sched_getfiles();
  if (!list)
    {
      ret = EMFILE;
      goto errout;
    }
#ifdef CONFIG_FILE_MODE
#  ifdef CONFIG_CPP_HAVE_WARNING
#    warning "File creation not implemented"
#  endif
  /* If the file is opened for creation, then get the mode bits */
  if (oflags & (O_WRONLY|O_CREAT) != 0)
    {
      va_list ap;
      va_start(ap, oflags);
      mode = va_arg(ap, mode_t);
      va_end(ap);
    }
#endif
  /* Get an inode for this file */
  inode = inode_find(path, &relpath);
  if (!inode)
    {
      /* "O_CREAT is not set and the named file does not exist.  Or, a
       * directory component in pathname does not exist or is a dangling
       * symbolic link."
       */
      ret = ENOENT;
      goto errout;
    }
  /* Verify that the inode is valid and either a "normal" or a mountpoint.  We
   * specifically exclude block drivers.
   */
#ifndef CONFIG_DISABLE_MOUNTPOINT
  if ((!INODE_IS_DRIVER(inode) && !INODE_IS_MOUNTPT(inode)) || !inode->u.i_ops)
#else
  if (!INODE_IS_DRIVER(inode) || !inode->u.i_ops)
#endif
    {
      ret = ENXIO;
      goto errout_with_inode;
    }
  /* Make sure that the inode supports the requested access */
  ret = inode_checkflags(inode, oflags);
  if (ret < 0)
    {
      ret = -ret;
      goto errout_with_inode;
    }
  /* Associate the inode with a file structure */
  fd = files_allocate(inode, oflags, 0, 0);
  if (fd < 0)
    {
      ret = EMFILE;
      goto errout_with_inode;
    }
  /* Perform the driver open operation.  NOTE that the open method may be
   * called many times.  The driver/mountpoint logic should handled this
   * because it may also be closed that many times.
   */
  ret = OK;
  if (inode->u.i_ops->open)
    {
#ifndef CONFIG_DISABLE_MOUNTPOINT
      if (INODE_IS_MOUNTPT(inode))
        {
          ret = inode->u.i_mops->open((FAR struct file*)&list->fl_files[fd],
                                      relpath, oflags, mode);
        }
      else
#endif
        {
          ret = inode->u.i_ops->open((FAR struct file*)&list->fl_files[fd]);
        }
    }
  if (ret < 0)
    {
      ret = -ret;
      goto errout_with_fd;
    }
  return fd;
 errout_with_fd:
  files_release(fd);
 errout_with_inode:
  inode_release(inode);
 errout:
  set_errno(ret);
  return ERROR;
}

可以回頭看看_serial_fd = ::open(_port, O_RDWR |O_NOCTTY);

_port就是註冊路徑,之前(void)uart_register("/dev/ttyS0",&uart_devs[CONSOLE_UART-1]->dev);註冊已經將註冊路徑和裝置進行對應了

O_RDWR | O_NOCTTY是操作許可權

/* open flag settings for open() (and related APIs) */
#define O_RDONLY    (1 << 0)        /* Open for read access (only) */
#define O_RDOK      O_RDONLY        /* Read access is permitted (non-standard) */
#define O_WRONLY    (1 << 1)        /* Open for write access (only) */
#define O_WROK      O_WRONLY        /* Write access is permitted (non-standard) */
#define O_RDWR      (O_RDOK|O_WROK) /* Open for both read & write access */
#define O_CREAT     (1 << 2)        /* Create file/sem/mq object */
#define O_EXCL      (1 << 3)        /* Name must not exist when opened  */
#define O_APPEND    (1 << 4)        /* Keep contents, append to end */
#define O_TRUNC     (1 << 5)        /* Delete contents */
#define O_NONBLOCK  (1 << 6)        /* Don't wait for data */
#define O_NDELAY    O_NONBLOCK      /* Synonym for O_NONBLOCK */
#define O_SYNC      (1 << 7)        /* Synchronize output on write */
#define O_DSYNC     O_SYNC          /* Equivalent to OSYNC in NuttX */
#define O_BINARY    (1 << 8)        /* Open the file in binary (untranslated) mode. */
/* Unsupported, but required open flags */
#define O_RSYNC     0               /* Synchronize input on read */
#define O_ACCMODE   0               /* Required by POSIX */
#define O_NOCTTY    0               /* Required by POSIX */
#define O_TEXT      0               /* Open the file in text (translated) mode. */

繼續看int open(const char *path, int oflags, ...)函式

ret = inode->u.i_ops->open((FARstruct file*)&list->fl_files[fd]);

定位到

struct file_operations
{
  /* The device driver open method differs from the mountpoint open method */
  int     (*open)(FAR struct file *filp);
  /* The following methods must be identical in signature and position because
   * the struct file_operations and struct mountp_operations are treated like
   * unions.
   */
  int     (*close)(FAR struct file *filp);
  ssize_t (*read)(FAR struct file *filp, FAR char *buffer, size_t buflen);
  ssize_t (*write)(FAR struct file *filp, FAR const char *buffer, size_t buflen);
  off_t   (*seek)(FAR struct file *filp, off_t offset, int whence);
  int     (*ioctl)(FAR struct file *filp, int cmd, unsigned long arg);
#ifndef CONFIG_DISABLE_POLL
  int     (*poll)(FAR struct file *filp, struct pollfd *fds, bool setup);
#endif
  /* The two structures need not be common after this point */
};

看到這個,很熟悉吧,就只是之前說的,Firmware/Nuttx/nuttx/include/nuttx/fs/fs.h,所有的字元型裝置的使用都會用的到字元型操作結構體,這個結構體的成員是指向函式的指標

再連線到Firmware/Nuttx/nuttx/serial/serial.c

static const struct file_operations g_serialops =
{
  uart_open,  /* open */
  uart_close, /* close */
  uart_read,  /* read */
  uart_write, /* write */
  0,          /* seek */
  uart_ioctl  /* ioctl */
#ifndef CONFIG_DISABLE_POLL
  , uart_poll /* poll */
#endif
};

再連線到uart_open

/************************************************************************************
 * Name: uart_open
 *
 * Description:
 *   This routine is called whenever a serial port is opened.
 *
 ************************************************************************************/
static int uart_open(FAR struct file *filep)
{
  struct inode *inode = filep->f_inode;
  uart_dev_t   *dev   = inode->i_private;
  uint8_t       tmp;
  int           ret;
  /* If the port is the middle of closing, wait until the close is finished.
   * If a signal is received while we are waiting, then return EINTR.
   */
  ret = uart_takesem(&dev->closesem, true);
  if (ret < 0)
    {
      /* A signal received while waiting for the last close operation. */
      return ret;
    }
#ifdef CONFIG_SERIAL_REMOVABLE
  /* If the removable device is no longer connected, refuse to open the
   * device.  We check this after obtaining the close semaphore because
   * we might have been waiting when the device was disconnected.
   */
  if (dev->disconnected)
    {
      ret = -ENOTCONN;
      goto errout_with_sem;
    }
#endif
  /* Start up serial port */
  /* Increment the count of references to the device. */
  tmp = dev->open_count + 1;
  if (tmp == 0)
    {
      /* More than 255 opens; uint8_t overflows to zero */
      ret = -EMFILE;
      goto errout_with_sem;
    }
  /* Check if this is the first time that the driver has been opened. */
  if (tmp == 1)
    {
      irqstate_t flags = irqsave();
      /* If this is the console, then the UART has already been initialized. */
      if (!dev->isconsole)
        {
          /* Perform one time hardware initialization */
          ret = uart_setup(dev);
          if (ret < 0)
            {
              irqrestore(flags);
              goto errout_with_sem;
            }
        }
      /* In any event, we do have to configure for interrupt driven mode of
       * operation.  Attach the hardware IRQ(s). Hmm.. should shutdown() the
       * the device in the rare case that uart_attach() fails, tmp==1, and
       * this is not the console.
       */
      ret = uart_attach(dev);
      if (ret < 0)
        {
           uart_shutdown(dev);
           irqrestore(flags);
           goto errout_with_sem;
        }
      /* Mark the io buffers empty */
      dev->xmit.head = 0;
      dev->xmit.tail = 0;
      dev->recv.head = 0;
      dev->recv.tail = 0;
      uart_onrxdeque(dev);
      /* initialise termios state */
#ifdef CONFIG_SERIAL_TERMIOS
      dev->tc_iflag = 0;
      if (dev->isconsole == true)
        {
          /* enable \n -> \r\n translation for the console */
          dev->tc_oflag = OPOST | ONLCR;
        }
      else
        {
          dev->tc_oflag = 0;
        }
#endif
      /* Enable the RX interrupt */
      uart_enablerxint(dev);
      irqrestore(flags);
    }
  /* Save the new open count on success */
  dev->open_count = tmp;
errout_with_sem:
  uart_givesem(&dev->closesem);
  return ret;
}

是不是這個過程很清晰了?以上分析還有問題嗎?

當然有問題!

所有的字元型裝置都是open後,就可以read,怎麼這個open就和你想開啟的裝置關聯在一起?

繼續回到GPS驅動中_serial_fd = ::open(_port, O_RDWR | O_NOCTTY);

然後這個open也定位到Firmware/Nuttx/nuttx/fs這個路徑下的字元型裝置基本操作fs_open.c

int open(const char *path, int oflags, ...)
{
  FAR struct filelist *list;
  FAR struct inode    *inode;
  FAR const char      *relpath = NULL;
#if defined(CONFIG_FILE_MODE) || !defined(CONFIG_DISABLE_MOUNTPOINT)
  mode_t               mode = 0666;
#endif
  int                  ret;
  int                  fd;
  /* Get the thread-specific file list */
  list = sched_getfiles();
  if (!list)
    {
      ret = EMFILE;
      goto errout;
    }
#ifdef CONFIG_FILE_MODE
#  ifdef CONFIG_CPP_HAVE_WARNING
#    warning "File creation not implemented"
#  endif
  /* If the file is opened for creation, then get the mode bits */
  if (oflags & (O_WRONLY|O_CREAT) != 0)
    {
      va_list ap;
      va_start(ap, oflags);
      mode = va_arg(ap, mode_t);
      va_end(ap);
    }
#endif
  /* Get an inode for this file */
  inode = inode_find(path, &relpath);
  if (!inode)
    {
      /* "O_CREAT is not set and the named file does not exist.  Or, a
       * directory component in pathname does not exist or is a dangling
       * symbolic link."
       */
      ret = ENOENT;
      goto errout;
    }
  /* Verify that the inode is valid and either a "normal" or a mountpoint.  We
   * specifically exclude block drivers.
   */
#ifndef CONFIG_DISABLE_MOUNTPOINT
  if ((!INODE_IS_DRIVER(inode) && !INODE_IS_MOUNTPT(inode)) || !inode->u.i_ops)
#else
  if (!INODE_IS_DRIVER(inode) || !inode->u.i_ops)
#endif
    {
      ret = ENXIO;
      goto errout_with_inode;
    }
  /* Make sure that the inode supports the requested access */
  ret = inode_checkflags(inode, oflags);
  if (ret < 0)
    {
      ret = -ret;
      goto errout_with_inode;
    }
  /* Associate the inode with a file structure */
  fd = files_allocate(inode, oflags, 0, 0);
  if (fd < 0)
    {
      ret = EMFILE;
      goto errout_with_inode;
    }
  /* Perform the driver open operation.  NOTE that the open method may be
   * called many times.  The driver/mountpoint logic should handled this
   * because it may also be closed that many times.
   */
  ret = OK;
  if (inode->u.i_ops->open)
    {
#ifndef CONFIG_DISABLE_MOUNTPOINT
      if (INODE_IS_MOUNTPT(inode))
        {
          ret = inode->u.i_mops->open((FAR struct file*)&list->fl_files[fd],
                                      relpath, oflags, mode);
        }
      else
#endif
        {
          ret = inode->u.i_ops->open((FAR struct file*)&list->fl_files[fd]);
        }
    }
  if (ret < 0)
    {
      ret = -ret;
      goto errout_with_fd;
    }
  return fd;
 errout_with_fd:
  files_release(fd);
 errout_with_inode:
  inode_release(inode);
 errout:
  set_errno(ret);
  return ERROR;
}

之前分析到是用這個ret = inode->u.i_ops->open((FARstruct file*)&list->fl_files[fd]);

連線到Firmware/Nuttx/nuttx/include/nuttx/fs/fs.h,所有的字元型裝置的使用都會用的到字元型操作結構體,再連線到Firmware/Nuttx/nuttx/serial/serial.c

static const struct file_operations g_serialops =
{
  uart_open,  /* open */
  uart_close, /* close */
  uart_read,  /* read */
  uart_write, /* write */
  0,          /* seek */
  uart_ioctl  /* ioctl */
#ifndef CONFIG_DISABLE_POLL
  , uart_poll /* poll */
#endif
};

再連線到uart_open

關鍵的inode->u.i_ops->open我們看看inode->u.i_ops如何得到的

inode = inode_find(path, &relpath);

/****************************************************************************
 * Name: inode_find
 *
 * Description:
 *   This is called from the open() logic to get a reference to the inode
 *   associated with a path.
 *
 ****************************************************************************/
FAR struct inode *inode_find(FAR const char *path, FAR const char **relpath)
{
  FAR struct inode *node;
  if (!*path || path[0] != '/')
    {
      return NULL;
    }
  /* Find the node matching the path.  If found, increment the count of
   * references on the node.
   */
  inode_semtake();
  node = inode_search(&path, (FAR struct inode**)NULL, (FAR struct inode**)NULL, relpath);
  if (node)
    {
      node->i_crefs++;
    }
  inode_semgive();
  return node;
}

inode是根據路徑得到的,這樣只要路徑不一樣,open的就不一樣,而路徑在註冊時和底層驅動有了對應關係,這樣open就可以open到對應的字元型驅動。

總結一下:註冊讓路徑和裝置驅動有了一個對應關係,open/read/write都附有路徑,這樣open/read/write就可以直接操作到底層驅動了。

如果您覺得此文對您的發展有用,請隨意打賞。 
您的鼓勵將是筆者書寫高質量文章的最大動力^_^!!