📄 dv1394.c
字号:
PCI_DMA_FROMDEVICE); kfree(video->packet_buffer); video->packet_buffer = NULL; video->packet_buffer_size = 0; } debug_printk("dv1394: shutdown complete\n"); return 0;}/* ********************************** *** MMAP() THEORY OF OPERATION *** ********************************** The ringbuffer cannot be re-allocated or freed while a user program maintains a mapping of it. (note that a mapping can persist even after the device fd is closed!) So, only let the user process allocate the DMA buffer once. To resize or deallocate it, you must close the device file and open it again. Previously Dan M. hacked out a scheme that allowed the DMA buffer to change by forcefully unmapping it from the user's address space. It was prone to error because it's very hard to track all the places the buffer could have been mapped (we would have had to walk the vma list of every process in the system to be sure we found all the mappings!). Instead, we force the user to choose one buffer size and stick with it. This small sacrifice is worth the huge reduction in error-prone code in dv1394. Note: dv1394_mmap does no page table manipulation. The page table entries are created by the dv1394_nopage() handler as page faults are taken by the user.*/static struct page * dv1394_nopage(struct vm_area_struct * area, unsigned long address, int write_access){ unsigned long offset; unsigned long kernel_virt_addr; struct page *ret = NOPAGE_SIGBUS; struct video_card *video = (struct video_card*) area->vm_private_data; /* guard against process-context operations and the interrupt */ /* (by definition page faults are taken in interrupt context) */ spin_lock(&video->spinlock); if(!video->user_buf) goto out; if( (address < (unsigned long) area->vm_start) || (address > (unsigned long) area->vm_start + video->user_buf_size) ) goto out; offset = address - area->vm_start; kernel_virt_addr = (unsigned long) video->user_buf + offset; ret = vmalloc_to_page((void *)kernel_virt_addr); get_page(ret); out: spin_unlock(&video->spinlock); return ret;}static struct vm_operations_struct dv1394_vm_ops = { .nopage = dv1394_nopage};/* dv1394_mmap does no page table manipulation. The page table entries are created by the dv1394_nopage() handler as page faults are taken by the user.*/int dv1394_mmap(struct file *file, struct vm_area_struct *vma){ struct video_card *video = file_to_video_card(file); unsigned long size; int res = -EINVAL; /* serialize mmap */ down(&video->sem); if( ! video_card_initialized(video) ) { res = do_dv1394_init_default(video); if(res) goto err; } /* region must be page-aligned */ if(vma->vm_pgoff != 0) goto err; /* check the size the user is trying to map */ size = vma->vm_end - vma->vm_start; if(size > video->user_buf_size) goto err; /* we don't actually mess with the page tables here. (nopage() takes care of that from the page fault handler) Just set up the vma->vm_ops. */ vma->vm_ops = &dv1394_vm_ops; vma->vm_private_data = video; vma->vm_file = file; /* don't try to swap this out =) */ vma->vm_flags |= VM_RESERVED; up(&video->sem); return 0; err: up(&video->sem); return res;}/*** DEVICE FILE INTERFACE *************************************************//* no need to serialize, multiple threads OK */static unsigned int dv1394_poll(struct file *file, struct poll_table_struct *wait){ struct video_card *video = file_to_video_card(file); unsigned int mask = 0; unsigned long flags; poll_wait(file, &video->waitq, wait); spin_lock_irqsave(&video->spinlock, flags); if( video->n_frames == 0 ) { } else if( video->active_frame == -1 ) { /* nothing going on */ mask |= POLLOUT; } else { /* any clear/ready buffers? */ if(video->n_clear_frames >0) mask |= POLLOUT | POLLIN; } spin_unlock_irqrestore(&video->spinlock, flags); return mask;}static int dv1394_fasync(int fd, struct file *file, int on){ /* I just copied this code verbatim from Alan Cox's mouse driver example (linux/Documentation/DocBook/) */ struct video_card *video = file_to_video_card(file); int retval = fasync_helper(fd, file, on, &video->fasync); if (retval < 0) return retval; return 0;}static ssize_t dv1394_write(struct file *file, const char *buffer, size_t count, loff_t *ppos){ struct video_card *video = file_to_video_card(file); DECLARE_WAITQUEUE(wait, current); ssize_t ret; size_t cnt; unsigned long flags; int target_frame; /* serialize this to prevent multi-threaded mayhem */ if(file->f_flags & O_NONBLOCK) { if(down_trylock(&video->sem)) return -EAGAIN; } else { if(down_interruptible(&video->sem)) return -ERESTARTSYS; } if( !video_card_initialized(video) ) { ret = do_dv1394_init_default(video); if(ret) { up(&video->sem); return ret; } } ret = 0; add_wait_queue(&video->waitq, &wait); while(count > 0) { /* must set TASK_INTERRUPTIBLE *before* checking for free buffers; otherwise we could miss a wakeup if the interrupt fires between the check and the schedule() */ set_current_state(TASK_INTERRUPTIBLE); spin_lock_irqsave(&video->spinlock, flags); target_frame = video->first_clear_frame; spin_unlock_irqrestore(&video->spinlock, flags); if(video->frames[target_frame]->state == FRAME_CLEAR) { /* how much room is left in the target frame buffer */ cnt = video->frame_size - (video->write_off - target_frame * video->frame_size); } else { /* buffer is already used */ cnt = 0; } if(cnt > count) cnt = count; if (cnt <= 0) { /* no room left, gotta wait */ if(file->f_flags & O_NONBLOCK) { if (!ret) ret = -EAGAIN; break; } if (signal_pending(current)) { if (!ret) ret = -ERESTARTSYS; break; } schedule(); continue; /* start over from 'while(count > 0)...' */ } if(copy_from_user(video->user_buf + video->write_off, buffer, cnt)) { if(!ret) ret = -EFAULT; break; } video->write_off = (video->write_off + cnt) % (video->n_frames * video->frame_size); count -= cnt; buffer += cnt; ret += cnt; if(video->write_off == video->frame_size * ((target_frame + 1) % video->n_frames)) frame_prepare(video, target_frame); } remove_wait_queue(&video->waitq, &wait); set_current_state(TASK_RUNNING); up(&video->sem); return ret;}static ssize_t dv1394_read(struct file *file, char *buffer, size_t count, loff_t *ppos){ struct video_card *video = file_to_video_card(file); DECLARE_WAITQUEUE(wait, current); ssize_t ret; size_t cnt; unsigned long flags; int target_frame; /* serialize this to prevent multi-threaded mayhem */ if(file->f_flags & O_NONBLOCK) { if(down_trylock(&video->sem)) return -EAGAIN; } else { if(down_interruptible(&video->sem)) return -ERESTARTSYS; } if( !video_card_initialized(video) ) { ret = do_dv1394_init_default(video); if(ret) { up(&video->sem); return ret; } receive_packets(video, video->frames[video->first_clear_frame]); } ret = 0; add_wait_queue(&video->waitq, &wait); while(count > 0) { /* must set TASK_INTERRUPTIBLE *before* checking for free buffers; otherwise we could miss a wakeup if the interrupt fires between the check and the schedule() */ set_current_state(TASK_INTERRUPTIBLE); spin_lock_irqsave(&video->spinlock, flags); target_frame = video->first_clear_frame; spin_unlock_irqrestore(&video->spinlock, flags); if(target_frame >= 0 && video->n_clear_frames > 0 && video->frames[target_frame]->state == FRAME_CLEAR) { /* how much room is left in the target frame buffer */ cnt = video->frame_size - (video->write_off - target_frame * video->frame_size); } else { /* buffer is already used */ cnt = 0; } if(cnt > count) cnt = count; if (cnt <= 0) { /* no room left, gotta wait */ if(file->f_flags & O_NONBLOCK) { if (!ret) ret = -EAGAIN; break; } if (signal_pending(current)) { if (!ret) ret = -ERESTARTSYS; break; } schedule(); continue; /* start over from 'while(count > 0)...' */ } if(copy_to_user(buffer, video->user_buf + video->write_off, cnt)) { if(!ret) ret = -EFAULT; break; } video->write_off = (video->write_off + cnt) % (video->n_frames * video->frame_size); count -= cnt; buffer += cnt; ret += cnt; if(video->write_off == video->frame_size * ((target_frame + 1) % video->n_frames)) { spin_lock_irqsave(&video->spinlock, flags); video->n_clear_frames--; video->first_clear_frame = (video->first_clear_frame + 1) % video->n_frames; spin_unlock_irqrestore(&video->spinlock, flags); } } remove_wait_queue(&video->waitq, &wait); set_current_state(TASK_RUNNING); up(&video->sem); return ret;}/*** DEVICE IOCTL INTERFACE ************************************************//* I *think* the VFS serializes ioctl() for us, so we don't have to worry about situations like having two threads in here at once... */static int dv1394_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ struct video_card *video = file_to_video_card(file); unsigned long flags; int ret = -EINVAL; DECLARE_WAITQUEUE(wait, current); /* serialize this to prevent multi-threaded mayhem */ if(file->f_flags & O_NONBLOCK) { if(down_trylock(&video->sem)) return -EAGAIN; } else { if(down_interruptible(&video->sem)) return -ERESTARTSYS; } switch(cmd) { case DV1394_SUBMIT_FRAMES: { unsigned int n_submit; if( !video_card_initialized(video) ) { ret = do_dv1394_init_default(video); if(ret) goto out; } n_submit = (unsigned int) arg; if(n_submit > video->n_frames) { ret = -EINVAL; goto out; } while(n_submit > 0) { add_wait_queue(&video->waitq, &wait); set_current_state(TASK_INTERRUPTIBLE); spin_lock_irqsave(&video->spinlock, flags); /* wait until video->first_clear_frame is really CLEAR */ while(video->frames[video->first_clear_frame]->state != FRAME_CLEAR) { spin_unlock_irqrestore(&video->spinlock, flags); if(signal_pending(current)) { remove_wait_queue(&video->waitq, &wait); set_current_state(TASK_RUNNING); ret = -EINTR; goto out; } schedule(); set_current_state(TASK_INTERRUPTIBLE); spin_lock_irqsave(&video->spinlock, flags); } spin_unlock_irqrestore(&video->spinlock, flags); remove_wait_queue(&video->waitq, &wait); set_current_state(TASK_RUNNING); frame_prepare(video, video->first_clear_frame); n_submit--; } ret = 0; break; } case DV1394_WAIT_FRAMES: { unsigned int n_wait; if( !video_card_initialized(video) ) { ret = -EINVAL; goto out; } n_wait = (unsigned int) arg; /* since we re-run the last frame on underflow, we will never actually have n_frames clear frames; at most only n_frames - 1 */ if(n_wait > (video->n_frames-1) ) { ret = -EINVAL; goto out; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -