nuttx-update/drivers/pci/pci_uio_ivshmem.c
YAMAMOTO Takashi 761ee81956 move readv/writev to the kernel
currently, nuttx implements readv/writev on the top of read/write.
while it might work for the simplest cases, it's broken by design.
for example, it's impossible to make it work correctly for files
which need to preserve data boundaries without allocating a single
contiguous buffer. (udp socket, some character devices, etc)

this change is a start of the migration to a better design.
that is, implement read/write on the top of readv/writev.

to avoid a single huge change, following things will NOT be done in
this commit:

* fix actual bugs caused by the original readv-based-on-read design.
  (cf. https://github.com/apache/nuttx/pull/12674)

* adapt filesystems/drivers to actually benefit from the new interface.
  (except a few trivial examples)

* eventually retire the old interface.

* retire read/write syscalls. implement them in libc instead.

* pread/pwrite/preadv/pwritev (except the introduction of struct uio,
  which is a preparation to back these variations with the new
  interface.)
2024-10-30 17:07:54 +08:00

535 lines
16 KiB
C

/****************************************************************************
* drivers/pci/pci_uio_ivshmem.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>
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <nuttx/fs/fs.h>
#include <nuttx/mm/map.h>
#include <nuttx/pci/pci_ivshmem.h>
#include <nuttx/semaphore.h>
#include <nuttx/spinlock.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define dev_to_udev(dev) \
((FAR struct uio_ivshmem_dev_s *)ivshmem_get_driver(dev))
/****************************************************************************
* Private Types
****************************************************************************/
struct uio_ivshmem_notify_s;
typedef CODE void (*uio_ivshmem_notify_t)(
FAR struct uio_ivshmem_notify_s *notify, int32_t newevent);
struct uio_ivshmem_notify_s
{
struct list_node node;
uio_ivshmem_notify_t cb;
};
struct uio_ivshmem_read_s
{
struct uio_ivshmem_notify_s notify;
sem_t wait;
};
struct uio_ivshmem_poll_s
{
struct uio_ivshmem_notify_s notify;
FAR struct pollfd *fds;
FAR void **ppriv;
};
struct uio_ivshmem_dev_s
{
struct ivshmem_driver_s drv;
FAR struct ivshmem_device_s *dev;
char name[32];
spinlock_t lock;
int32_t event_count;
struct list_node notify_list;
struct uio_ivshmem_poll_s polls[CONFIG_PCI_UIO_IVSHMEM_NPOLLWAITERS];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int uio_ivshmem_open(FAR struct file *filep);
static int uio_ivshmem_close(FAR struct file *filep);
static ssize_t uio_ivshmem_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t uio_ivshmem_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen);
static int uio_ivshmem_unmap(FAR struct task_group_s *group,
FAR struct mm_map_entry_s *entry,
FAR void *start, size_t length);
static int uio_ivshmem_mmap(FAR struct file *filep,
FAR struct mm_map_entry_s *map);
static int uio_ivshmem_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
static int uio_ivshmem_probe(FAR struct ivshmem_device_s *dev);
static void uio_ivshmem_remove(FAR struct ivshmem_device_s *dev);
static int uio_ivshmem_interrupt(int irq, FAR void *context, FAR void *arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_uio_ivshmem_fops =
{
uio_ivshmem_open, /* open */
uio_ivshmem_close, /* close */
uio_ivshmem_read, /* read */
uio_ivshmem_write, /* write */
NULL, /* seek */
NULL, /* ioctl */
uio_ivshmem_mmap, /* mmap */
NULL, /* truncate */
uio_ivshmem_poll, /* poll */
NULL, /* readv */
NULL /* writev */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
, NULL /* unlink */
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: uio_ivshmem_add_notify
****************************************************************************/
static inline void
uio_ivshmem_add_notify(FAR struct uio_ivshmem_dev_s *dev,
FAR struct uio_ivshmem_notify_s *notify)
{
irqstate_t flags;
flags = spin_lock_irqsave(&dev->lock);
list_add_tail(&dev->notify_list, &notify->node);
spin_unlock_irqrestore(&dev->lock, flags);
}
/****************************************************************************
* Name: uio_ivshmem_remove_notify
****************************************************************************/
static inline void
uio_ivshmem_remove_notify(FAR struct uio_ivshmem_dev_s *dev,
FAR struct uio_ivshmem_notify_s *notify)
{
irqstate_t flags;
flags = spin_lock_irqsave(&dev->lock);
list_delete(&notify->node);
spin_unlock_irqrestore(&dev->lock, flags);
}
/****************************************************************************
* Name: uio_ivshmem_open
****************************************************************************/
static int uio_ivshmem_open(FAR struct file *filep)
{
FAR struct uio_ivshmem_dev_s *dev;
irqstate_t flags;
DEBUGASSERT(filep->f_inode != NULL && filep->f_inode->i_private != NULL);
dev = filep->f_inode->i_private;
flags = spin_lock_irqsave(&dev->lock);
filep->f_priv = (FAR void *)(uintptr_t)dev->event_count;
spin_unlock_irqrestore(&dev->lock, flags);
return 0;
}
/****************************************************************************
* Name: uio_ivshmem_close
****************************************************************************/
static int uio_ivshmem_close(FAR struct file *filep)
{
UNUSED(filep);
return 0;
}
/****************************************************************************
* Name: uio_ivshmem_notify_read
****************************************************************************/
static void uio_ivshmem_notify_read(FAR struct uio_ivshmem_notify_s *notify,
int32_t newevent)
{
FAR struct uio_ivshmem_read_s *read =
(FAR struct uio_ivshmem_read_s *)notify;
nxsem_post(&read->wait);
}
/****************************************************************************
* Name: uio_ivshmem_read
****************************************************************************/
static ssize_t uio_ivshmem_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct uio_ivshmem_dev_s *dev;
struct uio_ivshmem_read_s read;
irqstate_t flags;
int ret = OK;
if (buflen != sizeof(int32_t))
{
return -EINVAL;
}
DEBUGASSERT(filep->f_inode != NULL && filep->f_inode->i_private != NULL);
dev = filep->f_inode->i_private;
nxsem_init(&read.wait, 0, 0);
read.notify.cb = uio_ivshmem_notify_read;
uio_ivshmem_add_notify(dev, &read.notify);
for (; ; )
{
flags = spin_lock_irqsave(&dev->lock);
if (dev->event_count != (int32_t)(uintptr_t)filep->f_priv)
{
filep->f_priv = (FAR void *)(uintptr_t)dev->event_count;
memcpy(buffer, &dev->event_count, sizeof(int32_t));
spin_unlock_irqrestore(&dev->lock, flags);
ret = sizeof(int32_t);
break;
}
spin_unlock_irqrestore(&dev->lock, flags);
if (filep->f_oflags & O_NONBLOCK)
{
ret = -EAGAIN;
break;
}
nxsem_wait_uninterruptible(&read.wait);
}
uio_ivshmem_remove_notify(dev, &read.notify);
nxsem_destroy(&read.wait);
return ret;
}
/****************************************************************************
* Name: uio_ivshmem_write
****************************************************************************/
static ssize_t uio_ivshmem_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen)
{
FAR struct uio_ivshmem_dev_s *dev;
int32_t irq_on;
DEBUGASSERT(filep->f_inode != NULL && filep->f_inode->i_private != NULL);
dev = filep->f_inode->i_private;
if (buflen != sizeof(int32_t))
{
return -EINVAL;
}
irq_on = *(FAR int32_t *)buffer;
if (irq_on != 0 && irq_on != 1)
{
return -EINVAL;
}
return ivshmem_control_irq(dev->dev, irq_on);
}
/****************************************************************************
* Name: uio_ivshmem_unmap
****************************************************************************/
static int uio_ivshmem_unmap(FAR struct task_group_s *group,
FAR struct mm_map_entry_s *entry,
FAR void *start, size_t length)
{
off_t offset;
int ret;
offset = (uintptr_t)start - (uintptr_t)entry->vaddr;
if (offset + length < entry->length)
{
pcierr("ERROR: Cannot umap without unmapping to the end\n");
return -ENOSYS;
}
/* Okay.. the region is being unmapped to the end. Make sure the length
* indicates the unmap length.
*/
length = entry->length - offset;
if (length < entry->length)
{
pcierr("ERROR: Cannot umap portion of the memory\n");
return -ENOSYS;
}
ret = mm_map_remove(get_group_mm(group), entry);
if (ret < 0)
{
pcierr("ERROR: mm_map_remove failed, ret=%d\n", ret);
return ret;
}
return ret;
}
/****************************************************************************
* Name: uio_ivshmem_mmap
****************************************************************************/
static int uio_ivshmem_mmap(FAR struct file *filep,
FAR struct mm_map_entry_s *map)
{
FAR struct uio_ivshmem_dev_s *dev;
size_t shmem_size;
FAR void *shmem;
DEBUGASSERT(filep->f_inode != NULL && filep->f_inode->i_private != NULL);
dev = filep->f_inode->i_private;
shmem = ivshmem_get_shmem(dev->dev, &shmem_size);
if (map->offset < 0 || map->offset >= shmem_size ||
map->length == 0 || map->offset + map->length > shmem_size)
{
return -EINVAL;
}
map->vaddr = (FAR char *)shmem + map->offset;
map->priv.p = dev;
map->munmap = uio_ivshmem_unmap;
/* Not allow mapped memory overlap */
if (mm_map_find(get_current_mm(), map->vaddr, map->length) != NULL)
{
return -EINVAL;
}
return mm_map_add(get_current_mm(), map);
}
/****************************************************************************
* Name: uio_ivshmem_notify_poll
****************************************************************************/
void uio_ivshmem_notify_poll(FAR struct uio_ivshmem_notify_s *notify,
int32_t newevent)
{
FAR struct uio_ivshmem_poll_s *poll =
(FAR struct uio_ivshmem_poll_s *)notify;
if (newevent != (int32_t)(uintptr_t)*poll->ppriv)
{
poll_notify(&poll->fds, 1, POLLIN | POLLRDNORM);
}
}
/****************************************************************************
* Name: uio_ivshmem_poll
****************************************************************************/
static int uio_ivshmem_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct uio_ivshmem_poll_s *poll;
FAR struct uio_ivshmem_dev_s *dev;
irqstate_t flags;
int i;
DEBUGASSERT(filep->f_inode != NULL && filep->f_inode->i_private != NULL);
dev = filep->f_inode->i_private;
if (setup)
{
flags = spin_lock_irqsave(&dev->lock);
for (i = 0; i < CONFIG_PCI_UIO_IVSHMEM_NPOLLWAITERS; i++)
{
poll = &dev->polls[i];
if (poll->fds == NULL)
{
/* Bind the poll structure and this slot */
poll->fds = fds;
fds->priv = poll;
break;
}
}
if (i >= CONFIG_PCI_UIO_IVSHMEM_NPOLLWAITERS)
{
spin_unlock_irqrestore(&dev->lock, flags);
return -EBUSY;
}
if (dev->event_count != (int32_t)(uintptr_t)filep->f_priv)
{
spin_unlock_irqrestore(&dev->lock, flags);
poll_notify(&fds, 1, POLLIN | POLLRDNORM);
}
else
{
spin_unlock_irqrestore(&dev->lock, flags);
}
poll->notify.cb = uio_ivshmem_notify_poll;
poll->ppriv = &filep->f_priv;
uio_ivshmem_add_notify(dev, &poll->notify);
}
else if (fds->priv != NULL)
{
poll = fds->priv;
uio_ivshmem_remove_notify(dev, &poll->notify);
flags = spin_lock_irqsave(&dev->lock);
poll->fds = NULL;
fds->priv = NULL;
spin_unlock_irqrestore(&dev->lock, flags);
}
return 0;
}
/****************************************************************************
* Name: uio_ivshmem_interrupt
****************************************************************************/
static int uio_ivshmem_interrupt(int irq, FAR void *context, FAR void *arg)
{
FAR struct uio_ivshmem_notify_s *notify;
FAR struct uio_ivshmem_dev_s *dev = arg;
irqstate_t flags;
flags = spin_lock_irqsave(&dev->lock);
dev->event_count++;
list_for_every_entry(&dev->notify_list, notify,
struct uio_ivshmem_notify_s, node)
{
notify->cb(notify, dev->event_count);
}
spin_unlock_irqrestore(&dev->lock, flags);
return 0;
}
/****************************************************************************
* Name: uio_ivshmem_probe
****************************************************************************/
static int uio_ivshmem_probe(FAR struct ivshmem_device_s *dev)
{
FAR struct uio_ivshmem_dev_s *udev = dev_to_udev(dev);
int ret;
udev->dev = dev;
spin_lock_init(&udev->lock);
list_initialize(&udev->notify_list);
/* Init the irq and ignore error */
ivshmem_attach_irq(dev, uio_ivshmem_interrupt, udev);
ivshmem_control_irq(dev, true);
snprintf(udev->name, sizeof(udev->name), "/dev/uio%d", udev->drv.id);
ret = register_driver(udev->name, &g_uio_ivshmem_fops, 0666, udev);
if (ret < 0)
{
pcierr("ERROR: Ivshmem register_driver failed, ret=%d\n", ret);
}
return ret;
}
/****************************************************************************
* Name: uio_ivshmem_remove
****************************************************************************/
static void uio_ivshmem_remove(FAR struct ivshmem_device_s *dev)
{
FAR struct uio_ivshmem_dev_s *udev = dev_to_udev(dev);
unregister_driver(udev->name);
ivshmem_detach_irq(dev);
}
/****************************************************************************
* Public Functions
****************************************************************************/
int pci_register_uio_ivshmem_driver(void)
{
FAR struct uio_ivshmem_dev_s *dev;
FAR char *start = CONFIG_PCI_UIO_IVSHMEM_IDTABLE;
do
{
dev = kmm_zalloc(sizeof(*dev));
if (dev == NULL)
{
return -ENOMEM;
}
dev->drv.id = strtoul(start, &start, 0);
dev->drv.probe = uio_ivshmem_probe;
dev->drv.remove = uio_ivshmem_remove;
if (ivshmem_register_driver(&dev->drv) < 0)
{
kmm_free(dev);
}
pciinfo("Register ivshmem driver, id=%d\n", dev->drv.id);
}
while (*start++ != '\0');
return 0;
}