/**************************************************************************** * drivers/misc/goldfish_pipe.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. * ****************************************************************************/ /* Usage from the guest is simple: * * int fd = open("/dev/goldfish_pipe", O_RDWR); * .... write() or read() through the pipe. * * Connect to a specific qemu service: * * // Do this immediately after opening the fd * const char* msg = ""; * if (write(fd, msg, strlen(msg) + 1) < 0) { * ... could not connect to service * close(fd); * } * * After this, simply read() and write() to communicate with the service. */ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* List of bitflags returned in status of GOLDFISH_PIPE_CMD_POLL command */ #define GOLDFISH_PIPE_POLL_IN (1 << 0) #define GOLDFISH_PIPE_POLL_OUT (1 << 1) #define GOLDFISH_PIPE_POLL_HUP (1 << 2) /* Possible status values used to signal errors */ #define GOLDFISH_PIPE_ERROR_INVAL -1 #define GOLDFISH_PIPE_ERROR_AGAIN -2 #define GOLDFISH_PIPE_ERROR_NOMEM -3 #define GOLDFISH_PIPE_ERROR_IO -4 /* Bit-flags used to signal events from the emulator */ #define GOLDFISH_PIPE_WAKE_CLOSED (1 << 0) /* Emulator closed pipe */ #define GOLDFISH_PIPE_WAKE_READ (1 << 1) /* Pipe can now be read from */ #define GOLDFISH_PIPE_WAKE_WRITE (1 << 2) /* Pipe can now be written to */ #define GOLDFISH_PIPE_WAKE_UNLOCK_DMA (1 << 3) /* Unlock this pipe's DMA buffer */ #define GOLDFISH_PIPE_WAKE_UNLOCK_DMA_SHARED (1 << 4) /* Unlock DMA buffer of the pipe shared to this pipe */ /* Possible pipe closing reasons */ #define GOLDFISH_PIPE_CLOSE_GRACEFUL 0 /* Guest sent a close command */ #define GOLDFISH_PIPE_CLOSE_REBOOT 1 /* Guest rebooted, we're closing the pipes */ #define GOLDFISH_PIPE_CLOSE_LOAD_SNAPSHOT 2 /* Close old pipes on snapshot load */ #define GOLDFISH_PIPE_CLOSE_ERROR 3 /* Some unrecoverable error on the pipe */ /* Register offset */ #define GOLDFISH_PIPE_REG_CMD 0 #define GOLDFISH_PIPE_REG_SIGNAL_BUFFER_HIGH 4 #define GOLDFISH_PIPE_REG_SIGNAL_BUFFER 8 #define GOLDFISH_PIPE_REG_SIGNAL_BUFFER_COUNT 12 #define GOLDFISH_PIPE_REG_OPEN_BUFFER_HIGH 20 #define GOLDFISH_PIPE_REG_OPEN_BUFFER 24 #define GOLDFISH_PIPE_REG_VERSION 36 #define GOLDFISH_PIPE_REG_GET_SIGNALLED 48 /* Possible pipe command */ #define GOLDFISH_PIPE_CMD_OPEN 1 #define GOLDFISH_PIPE_CMD_CLOSE 2 #define GOLDFISH_PIPE_CMD_POLL 3 #define GOLDFISH_PIPE_CMD_WRITE 4 #define GOLDFISH_PIPE_CMD_WAKE_ON_WRITE 5 #define GOLDFISH_PIPE_CMD_READ 6 #define GOLDFISH_PIPE_CMD_WAKE_ON_READ 7 #define GOLDFISH_PIPE_CMD_WAKE_ON_DONE_IO 8 /* Version number */ #define GOLDFISH_PIPE_DRIVER_VERSION 4 #define GOLDFISH_PIPE_CURRENT_DEVICE_VERSION 2 #define GOLDFISH_PIPE_MAX_POLL_WAITERS 2 #define GOLDFISH_PIPE_MAX_COMMAND_BUFFERS 1 #define GOLDFISH_PIPE_MAX_SIGNALLED 16 #define GOLDFISH_PIPE_MAX_PIPES 32 #define goldfish_pipe_putreg32(v, x) (*(FAR volatile uint32_t *)(x) = (v)) #define goldfish_pipe_getreg32(x) (*(FAR volatile uint32_t *)(x)) /**************************************************************************** * Private Types ****************************************************************************/ /* A per-pipe command structure, shared with the host */ struct goldfish_pipe_command_s { int32_t cmd; /* PipeCmdCode, guest -> host */ int32_t id; /* Pipe id, guest -> host */ int32_t status; /* Command execution status, host -> guest */ int32_t reserved; /* To pad to 64-bit boundary */ struct /* Parameters for GOLDFISH_PIPE_CMD_[READ|WRITE] */ { uint32_t buffers_count; /* Number of buffers, guest -> host */ int32_t consumed_size; /* Number of consumed bytes, host -> guest */ uint64_t ptrs[GOLDFISH_PIPE_MAX_COMMAND_BUFFERS]; /* Buffer pointers, guest -> host */ uint32_t sizes[GOLDFISH_PIPE_MAX_COMMAND_BUFFERS]; /* Buffer sizes, guest -> host */ } rw_params; }; /* A single signalled pipe information */ struct goldfish_pipe_signalled_s { uint32_t id; uint32_t flags; }; /* Parameters for the GOLDFISH_PIPE_CMD_OPEN command */ struct goldfish_pipe_open_param_s { uint64_t command_buffer_ptr; uint32_t rw_params_max_count; }; /* This data type models a given pipe instance */ struct goldfish_pipe_dev_s; struct goldfish_pipe_s { uint32_t id; bool closed; struct goldfish_pipe_command_s command; mutex_t lock; sem_t wait_for_write; sem_t wait_for_read; FAR struct goldfish_pipe_dev_s *dev; FAR struct pollfd *fds[GOLDFISH_PIPE_MAX_POLL_WAITERS]; }; struct goldfish_pipe_dev_s { spinlock_t lock; FAR struct goldfish_pipe_s *pipes[GOLDFISH_PIPE_MAX_PIPES]; struct goldfish_pipe_open_param_s open_params; struct goldfish_pipe_signalled_s pipes_signalled[GOLDFISH_PIPE_MAX_SIGNALLED]; FAR uint8_t *base; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static ssize_t goldfish_pipe_read(FAR struct file *filp, FAR char *buffer, size_t bufflen); static ssize_t goldfish_pipe_write(FAR struct file *filp, FAR const char *buffer, size_t bufflen); static int goldfish_pipe_poll(FAR struct file *filp, FAR struct pollfd *fds, bool setup); static int goldfish_pipe_open(FAR struct file *filep); static int goldfish_pipe_close(FAR struct file *filep); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_goldfish_pipe_fops = { .read = goldfish_pipe_read, .write = goldfish_pipe_write, .poll = goldfish_pipe_poll, .open = goldfish_pipe_open, .close = goldfish_pipe_close, }; /**************************************************************************** * Private Functions ****************************************************************************/ static int goldfish_pipe_convert_status(int status) { switch (status) { case GOLDFISH_PIPE_ERROR_INVAL: return -EINVAL; case GOLDFISH_PIPE_ERROR_AGAIN: return -EAGAIN; case GOLDFISH_PIPE_ERROR_NOMEM: return -ENOMEM; case GOLDFISH_PIPE_ERROR_IO: return -EIO; default: return status; } } static int goldfish_pipe_command_locked(FAR struct goldfish_pipe_s *pipe, int cmd) { pipe->command.cmd = cmd; pipe->command.status = GOLDFISH_PIPE_ERROR_INVAL; goldfish_pipe_putreg32(pipe->id, pipe->dev->base + GOLDFISH_PIPE_REG_CMD); return goldfish_pipe_convert_status(pipe->command.status); } static int goldfish_pipe_command(FAR struct goldfish_pipe_s *pipe, int cmd) { int ret; ret = nxmutex_lock(&pipe->lock); if (ret < 0) { return ret; } ret = goldfish_pipe_command_locked(pipe, cmd); nxmutex_unlock(&pipe->lock); return ret; } static int goldfish_pipe_wait(FAR struct goldfish_pipe_s *pipe, bool is_write) { int ret; ret = goldfish_pipe_command(pipe, is_write ? GOLDFISH_PIPE_CMD_WAKE_ON_WRITE : GOLDFISH_PIPE_CMD_WAKE_ON_READ); if (ret < 0) { return ret; } return nxsem_wait(is_write ? &pipe->wait_for_write : &pipe->wait_for_read); } static ssize_t goldfish_pipe_transfer_one(FAR struct goldfish_pipe_s *pipe, FAR char *buffer, size_t buflen, bool is_write) { ssize_t ret; ret = nxmutex_lock(&pipe->lock); if (ret < 0) { return ret; } pipe->command.rw_params.ptrs[0] = up_addrenv_va_to_pa(buffer); pipe->command.rw_params.sizes[0] = buflen; pipe->command.rw_params.buffers_count = 1; ret = goldfish_pipe_command_locked(pipe, is_write ? GOLDFISH_PIPE_CMD_WRITE : GOLDFISH_PIPE_CMD_READ); if (ret >= 0) { ret = pipe->command.rw_params.consumed_size; } nxmutex_unlock(&pipe->lock); return ret; } static ssize_t goldfish_pipe_transfer(FAR struct file *filp, FAR char *buffer, size_t bufflen, bool is_write) { FAR struct goldfish_pipe_s *pipe = filp->f_priv; size_t count = 0; int ret = 0; while (bufflen > 0) { /* If the emulator already closed the pipe, no need to go further */ if (pipe->closed) { ret = -EIO; break; } ret = goldfish_pipe_transfer_one(pipe, buffer, bufflen, is_write); if (ret > 0) { buffer += ret; bufflen -= ret; count += ret; continue; } if (ret != -EAGAIN || count || (filp->f_oflags & O_NONBLOCK)) { break; } ret = goldfish_pipe_wait(pipe, is_write); if (ret < 0) { break; } } return count > 0 ? count : ret; } static ssize_t goldfish_pipe_read(FAR struct file *filp, FAR char *buffer, size_t bufflen) { return goldfish_pipe_transfer(filp, buffer, bufflen, false); } static ssize_t goldfish_pipe_write(FAR struct file *filp, FAR const char *buffer, size_t bufflen) { return goldfish_pipe_transfer(filp, (FAR char *)buffer, bufflen, true); } static int goldfish_pipe_poll(FAR struct file *filp, FAR struct pollfd *fds, bool setup) { if (setup) { FAR struct goldfish_pipe_s *pipe = filp->f_priv; FAR struct goldfish_pipe_dev_s *dev = pipe->dev; pollevent_t eventset = 0; irqstate_t flags; int ret; int i; ret = goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_POLL); if (ret < 0) { return ret; } flags = spin_lock_irqsave(&dev->lock); /* This is a request to set up the poll. Find an available * slot for the poll structure reference */ for (i = 0; i < GOLDFISH_PIPE_MAX_POLL_WAITERS; i++) { /* Find an available slot */ if (pipe->fds[i] == NULL) { /* Bind the poll structure and this slot */ pipe->fds[i] = fds; fds->priv = &pipe->fds[i]; break; } } spin_unlock_irqrestore(&dev->lock, flags); if (i >= GOLDFISH_PIPE_MAX_POLL_WAITERS) { return -EBUSY; } if (ret & GOLDFISH_PIPE_POLL_IN) { eventset |= POLLIN; } if (ret & GOLDFISH_PIPE_POLL_OUT) { eventset |= POLLOUT; } if (ret & GOLDFISH_PIPE_POLL_HUP) { eventset |= POLLHUP; } if (pipe->closed) { eventset |= POLLERR; } if (eventset) { poll_notify(&fds, 1, eventset); } else { goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_WAKE_ON_READ); } } else { /* This is a request to tear down the poll. */ FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv; if (slot == NULL) { return -EINVAL; } *slot = NULL; fds->priv = NULL; } return 0; } static int goldfish_pipe_open(FAR struct file *filep) { FAR struct goldfish_pipe_dev_s *dev = filep->f_inode->i_private; FAR struct goldfish_pipe_s *pipe; irqstate_t flags; int ret = -ENFILE; int id; pipe = kmm_zalloc(sizeof(*pipe)); if (pipe == NULL) { return -ENOMEM; } pipe->dev = dev; nxmutex_init(&pipe->lock); nxsem_init(&pipe->wait_for_read, 0, 0); nxsem_init(&pipe->wait_for_write, 0, 0); flags = spin_lock_irqsave(&dev->lock); for (id = 0; id < GOLDFISH_PIPE_MAX_PIPES; id++) { if (dev->pipes[id] == NULL) { break; } } if (id >= GOLDFISH_PIPE_MAX_PIPES) { goto out; } pipe->id = id; pipe->command.id = id; /* Now tell the emulator we're opening a new pipe. */ dev->open_params.command_buffer_ptr = up_addrenv_va_to_pa(&pipe->command); dev->open_params.rw_params_max_count = GOLDFISH_PIPE_MAX_COMMAND_BUFFERS; ret = goldfish_pipe_command_locked(pipe, GOLDFISH_PIPE_CMD_OPEN); if (ret < 0) { goto out; } dev->pipes[id] = pipe; spin_unlock_irqrestore(&dev->lock, flags); filep->f_priv = pipe; return 0; out: spin_unlock_irqrestore(&dev->lock, flags); nxsem_destroy(&pipe->wait_for_write); nxsem_destroy(&pipe->wait_for_read); nxmutex_destroy(&pipe->lock); kmm_free(pipe); return ret; } static int goldfish_pipe_close(FAR struct file *filp) { FAR struct goldfish_pipe_s *pipe = filp->f_priv; FAR struct goldfish_pipe_dev_s *dev = pipe->dev; irqstate_t flags; goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_CLOSE); flags = spin_lock_irqsave(&dev->lock); dev->pipes[pipe->id] = NULL; spin_unlock_irqrestore(&dev->lock, flags); filp->f_priv = NULL; nxsem_destroy(&pipe->wait_for_write); nxsem_destroy(&pipe->wait_for_read); nxmutex_destroy(&pipe->lock); kmm_free(pipe); return 0; } static void goldfish_pipe_wake(FAR struct goldfish_pipe_s *pipe, bool is_write) { FAR sem_t *sem = is_write ? &pipe->wait_for_write : &pipe->wait_for_read; int sval; while (nxsem_get_value(sem, &sval) >= 0 && sval <= 0) { nxsem_post(sem); } } static int goldfish_pipe_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct goldfish_pipe_dev_s *dev = arg; irqstate_t irqflags; uint32_t count; uint32_t i; irqflags = spin_lock_irqsave(&dev->lock); count = goldfish_pipe_getreg32(dev->base + GOLDFISH_PIPE_REG_GET_SIGNALLED); if (count > GOLDFISH_PIPE_MAX_SIGNALLED) { count = GOLDFISH_PIPE_MAX_SIGNALLED; } for (i = 0; i < count; i++) { uint32_t id = dev->pipes_signalled[i].id; uint32_t flags = dev->pipes_signalled[i].flags; FAR struct goldfish_pipe_s *pipe = dev->pipes[id]; pollevent_t eventset = 0; if (pipe == NULL) { continue; } if (flags & GOLDFISH_PIPE_WAKE_READ) { eventset |= POLLIN; } if (flags & GOLDFISH_PIPE_WAKE_WRITE) { eventset |= POLLOUT; } if (flags & GOLDFISH_PIPE_WAKE_CLOSED) { eventset |= POLLERR; dev->pipes[id]->closed = true; } if (eventset & (POLLIN | POLLERR)) { goldfish_pipe_wake(pipe, false); } if (eventset & (POLLOUT | POLLERR)) { goldfish_pipe_wake(pipe, true); } if (eventset) { poll_notify(pipe->fds, GOLDFISH_PIPE_MAX_POLL_WAITERS, eventset); } } spin_unlock_irqrestore(&dev->lock, irqflags); return 0; } static void goldfish_pipe_write_addr(FAR void *addr, FAR void *portl, FAR void *porth) { uintptr_t paddr = up_addrenv_va_to_pa(addr); goldfish_pipe_putreg32((paddr >> 16) >> 16, porth); goldfish_pipe_putreg32(paddr, portl); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: goldfish_pipe_register * * Description: * register /dev/goldfish_pipe device * ****************************************************************************/ int goldfish_pipe_register(FAR void *base, int irq) { FAR struct goldfish_pipe_dev_s *dev; uint32_t version; int ret = -ENOTSUP; /* Allocate and initialize a new device structure instance */ dev = (FAR struct goldfish_pipe_dev_s *)kmm_zalloc(sizeof(*dev)); if (dev == NULL) { return -ENOMEM; } spin_lock_init(&dev->lock); dev->base = (FAR uint8_t *)base; /* Exchange the versions with the host device */ goldfish_pipe_putreg32(GOLDFISH_PIPE_DRIVER_VERSION, dev->base + GOLDFISH_PIPE_REG_VERSION); version = goldfish_pipe_getreg32(dev->base + GOLDFISH_PIPE_REG_VERSION); if (version < GOLDFISH_PIPE_CURRENT_DEVICE_VERSION) { goto out; } ret = irq_attach(irq, goldfish_pipe_interrupt, dev); if (ret < 0) { goto out; } up_enable_irq(irq); /* Send the buffer addresses to the host */ goldfish_pipe_write_addr(&dev->pipes_signalled, dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER, dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER_HIGH); goldfish_pipe_putreg32(GOLDFISH_PIPE_MAX_SIGNALLED, dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER_COUNT); goldfish_pipe_write_addr(&dev->open_params, dev->base + GOLDFISH_PIPE_REG_OPEN_BUFFER, dev->base + GOLDFISH_PIPE_REG_OPEN_BUFFER_HIGH); /* Register the pipe device */ return register_driver("/dev/goldfish_pipe", &g_goldfish_pipe_fops, 0666, dev); out: kmm_free(dev); return ret; }