diff --git a/driver/axidma.h b/driver/axidma.h index 14c6765..d322a23 100644 --- a/driver/axidma.h +++ b/driver/axidma.h @@ -105,8 +105,9 @@ int axidma_write_transfer(struct axidma_device *dev, struct axidma_transaction *trans); int axidma_rw_transfer(struct axidma_device *dev, struct axidma_inout_transaction *trans); -int axidma_video_write_transfer(struct axidma_device *dev, - struct axidma_video_transaction *trans); +int axidma_video_transfer(struct axidma_device *dev, + struct axidma_video_transaction *trans, + enum axidma_dir dir); int axidma_stop_channel(struct axidma_device *dev, struct axidma_chan *chan); dma_addr_t axidma_uservirt_to_dma(struct axidma_device *dev, void *user_addr, size_t size); diff --git a/driver/axidma_chrdev.c b/driver/axidma_chrdev.c index d4c879a..c4147e2 100644 --- a/driver/axidma_chrdev.c +++ b/driver/axidma_chrdev.c @@ -445,6 +445,26 @@ static long axidma_ioctl(struct file *file, unsigned int cmd, unsigned long arg) rc = axidma_rw_transfer(dev, &inout_trans); break; + case AXIDMA_DMA_VIDEO_READ: + if (copy_from_user(&video_trans, arg_ptr, + sizeof(video_trans)) != 0) { + axidma_err("Unable to copy transfer info from userspace for " + "AXIDMA_DMA_VIDEO_READ.\n"); + return -EFAULT; + } + + // Verify that we can access the array of frame buffers + size = video_trans.num_frame_buffers * + sizeof(video_trans.frame_buffers[0]); + if (!axidma_access_ok(video_trans.frame_buffers, size, true)) { + axidma_err("Unable to copy frame buffer addresses from " + "userspace for AXIDMA_DMA_VIDEO_WRITE.\n"); + return -EFAULT; + } + + rc = axidma_video_transfer(dev, &video_trans, AXIDMA_READ); + break; + case AXIDMA_DMA_VIDEO_WRITE: if (copy_from_user(&video_trans, arg_ptr, sizeof(video_trans)) != 0) { @@ -454,6 +474,7 @@ static long axidma_ioctl(struct file *file, unsigned int cmd, unsigned long arg) } // Verify that we can access the array of frame buffers + // TODO: This array should be copied into kernel space size = video_trans.num_frame_buffers * sizeof(video_trans.frame_buffers[0]); if (!axidma_access_ok(video_trans.frame_buffers, size, true)) { @@ -462,7 +483,7 @@ static long axidma_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return -EFAULT; } - rc = axidma_video_write_transfer(dev, &video_trans); + rc = axidma_video_transfer(dev, &video_trans, AXIDMA_WRITE); break; case AXIDMA_STOP_DMA_CHANNEL: diff --git a/driver/axidma_dma.c b/driver/axidma_dma.c index cc4b69a..cb377dc 100644 --- a/driver/axidma_dma.c +++ b/driver/axidma_dma.c @@ -539,8 +539,9 @@ int axidma_rw_transfer(struct axidma_device *dev, return 0; } -int axidma_video_write_transfer(struct axidma_device *dev, - struct axidma_video_transaction *trans) +int axidma_video_transfer(struct axidma_device *dev, + struct axidma_video_transaction *trans, + enum axidma_dir dir) { int rc, i; size_t image_size; @@ -550,7 +551,7 @@ int axidma_video_write_transfer(struct axidma_device *dev, // Setup transmit transfer structure for DMA struct axidma_transfer tx_tfr = { .sg_len = trans->num_frame_buffers, - .dir = AXIDMA_WRITE, + .dir = dir, .type = AXIDMA_VDMA, .wait = false, .channel_id = trans->channel_id, diff --git a/include/axidma_ioctl.h b/include/axidma_ioctl.h index ffe3b48..b35bcfc 100644 --- a/include/axidma_ioctl.h +++ b/include/axidma_ioctl.h @@ -274,6 +274,34 @@ struct axidma_video_transaction { #define AXIDMA_DMA_READWRITE _IOR(AXIDMA_IOCTL_MAGIC, 6, \ struct axidma_inout_transaction) +/** + * Performs frame-buffer based transfers from a camera on the fabric. + * + * This function performs a video transfer from the logic fabric. It receives + * the given buffers from logic fabric (intended for a camera pipeline). When it + * reaches the end of the buffers, it loops back and receives the data again in + * the first buffer. This is used for frame-buffer based cameras. + * + * All of the frame buffers must be within an address range that was allocated + * by a call to mmap with the AXI DMA device. Also, each buffer must + * be able to hold a frame of (width * height * depth) bytes. The input array of + * buffers must be a memory location that holds `num_frame_buffers` addresses. + * + * This call is always non-blocking as the VDMA engine will run forever. In + * order to end the transaction, you must make a call to the stop dma channel + * ioctl. + * + * Inputs: + * - channel_id - The id for the channel you want to send data over. + * - num_frame_buffers - The number of frame buffers you're using. + * - frame_buffers - An array of the frame buffer addresses. + * - width - The width of the frame (image) in pixels. + * - height - The height of the frame in lines. + * - depth - The size of each pixel in the frame in bytes. + **/ +#define AXIDMA_DMA_VIDEO_READ _IOR(AXIDMA_IOCTL_MAGIC, 7, \ + struct axidma_video_transaction) + /** * Performs frame-buffer based transfers to a display on the logic fabric. * @@ -299,7 +327,7 @@ struct axidma_video_transaction { * - height - The height of the frame in lines. * - depth - The size of each pixel in the frame in bytes. **/ -#define AXIDMA_DMA_VIDEO_WRITE _IOR(AXIDMA_IOCTL_MAGIC, 7, \ +#define AXIDMA_DMA_VIDEO_WRITE _IOR(AXIDMA_IOCTL_MAGIC, 8, \ struct axidma_video_transaction) /** @@ -315,7 +343,7 @@ struct axidma_video_transaction { * - channel_id - The integer id for the channel. * - chan - This field is unused an can be safely left uninitialized. */ -#define AXIDMA_STOP_DMA_CHANNEL _IOR(AXIDMA_IOCTL_MAGIC, 8, \ +#define AXIDMA_STOP_DMA_CHANNEL _IOR(AXIDMA_IOCTL_MAGIC, 9, \ struct axidma_chan) /** @@ -330,6 +358,6 @@ struct axidma_video_transaction { * Inputs: * - user_addr - The user virtual address of the external DMA buffer. **/ -#define AXIDMA_UNREGISTER_BUFFER _IO(AXIDMA_IOCTL_MAGIC, 9) +#define AXIDMA_UNREGISTER_BUFFER _IO(AXIDMA_IOCTL_MAGIC, 10) #endif /* AXIDMA_IOCTL_H_ */ diff --git a/include/libaxidma.h b/include/libaxidma.h index f183dda..74d01d4 100644 --- a/include/libaxidma.h +++ b/include/libaxidma.h @@ -242,16 +242,19 @@ int axidma_twoway_transfer(axidma_dev_t dev, int tx_channel, void *tx_buf, size_t tx_len, int rx_channel, void *rx_buf, size_t rx_len, bool wait); /** - * Starts a video DMA (VDMA) transfer on the given DMA channel. + * Starts a video DMA (VDMA) loop/continuous transfer on the given channel. * - * A video DMA transfer differs from a typical DMA transfer in that it is - * cyclic, and ends only when requested by the user. A VDMA transfer will - * continuously transmit the frame buffers, transmitting the first buffer, - * then the second, etc., and then repeating once the last buffer is reached. + * A video loop transfer differs from a typical DMA transfer in that it is + * cyclic, and ends only when requested by the user. A video loop transfer will + * continuously transmit/receive the frame buffers, transmitting the first + * buffer, then the second, etc., and then repeating from the beginning once the + * last buffer is reached. This is suitable when continuously sending data to a + * display, or continuous receiving data from a camera. * * This function supports an arbitrary number of frame buffers, allowing - * for both double-buffering and triple-buffering. This will only transmit - * the buffers, the receive direction is not supported. + * for both double-buffering and triple-buffering. This function is + * non-blocking, and returns immediately. The only way to stop the transfer is + * via a call to #axidma_stop_transfer. * * @param[in] dev An #axidma_dev_t returned by #axidma_init. * @param[in] display_channel DMA channel the video transfer will take place diff --git a/library/libaxidma.c b/library/libaxidma.c index 9e20b71..ae74ecf 100644 --- a/library/libaxidma.c +++ b/library/libaxidma.c @@ -499,29 +499,33 @@ int axidma_twoway_transfer(axidma_dev_t dev, int tx_channel, void *tx_buf, return rc; } -/* This function performs a video transfer over AXI DMA, setting up the DMA to - * read from the given frame buffers on-demand continuously. This call is - * always non-blocking. The transfer must be stopped with a call to - * axidma_stop_transfer. */ +/* This function performs a video transfer over AXI DMA, setting up a VDMA + * channel to either read from or write to given frame buffers on-demand + * continuously. This call is always non-blocking. The transfer can only be + * stopped with a call to axidma_stop_transfer. */ int axidma_video_transfer(axidma_dev_t dev, int display_channel, size_t width, size_t height, size_t depth, void **frame_buffers, int num_buffers) { int rc; + unsigned long axidma_cmd; struct axidma_video_transaction trans; + dma_channel_t *dma_chan; assert(find_channel(dev, display_channel) != NULL); - assert(find_channel(dev, display_channel)->dir == AXIDMA_WRITE); + assert(find_channel(dev, display_channel)->type == AXIDMA_VDMA); // Setup the argument structure for the IOCTL + dma_chan = find_channel(dev, display_channel); trans.channel_id = display_channel; trans.num_frame_buffers = num_buffers; trans.frame_buffers = frame_buffers; trans.width = width; trans.height = height; trans.depth = depth; - + axidma_cmd = (dma_chan->dir == AXIDMA_READ) ? AXIDMA_DMA_VIDEO_READ : + AXIDMA_DMA_VIDEO_WRITE; // Perform the video transfer - rc = ioctl(dev->fd, AXIDMA_DMA_VIDEO_WRITE, &trans); + rc = ioctl(dev->fd, axidma_cmd, &trans); if (rc < 0) { perror("Failed to perform the AXI DMA video write transfer"); }