pixhawk px4 字符型设备驱动

时间:2023-01-27 16:06:43

分析字符型设备为什么register/open/read/write怎样与底层驱动代码联系在一起的,为什么需要注册,为什么会有路径,为什么open之后read/write就可以读/写了

另:此篇blog是以nuttx官网介绍为出发点,先介绍nuttx的字符型设备驱动框架,再以GPS/串口为例思考pixhawk字符型设备驱动编写过程

参考:NuttX Device Drivers

也可以参考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就可以直接操作到底层驱动了。



如果您觉得此文对您的发展有用,请随意打赏。 
您的鼓励将是笔者书写高质量文章的最大动力^_^!!

pixhawk px4 字符型设备驱动