示例#1
0
/* The fsl fifo writing tasklet.  It loops through *all* FIFOs
   see if any are waiting to write, and does so until it blocks.
   if *any* FIFO blocks on writing, the tasklet is rescheduled.

   We could alternatively have one tasklet per FIFO , not sure which would
   be more efficient... */
void write_tasklet_func(unsigned long data)
{
	struct fsl_fifo_t *fsl_fifo=(struct fsl_fifo_t *)data;
	int id;
	int do_reschedule=0;

	/* Loop through each fsl fifo*/
	for(id=0;id<MAX_FSL_FIFO_COUNT;id++, fsl_fifo++)
	{
		unsigned flags;

		if(!fsl_fifo->exists)
			continue;

		save_flags_cli(flags);

		/* There must be at least "width" bytes in the buffer */
		if(fsl_fifo->tx_cnt < fsl_fifo->width)
		{
			restore_flags(flags);
			continue;
		}

		while(fsl_fifo->tx_cnt >= fsl_fifo->width)
		{
			unsigned fsl_status;
			unsigned int value;
			void *ptr = &(fsl_fifo->tx_buf[fsl_fifo->tx_tail]);

			switch(fsl_fifo->width)
			{
			case 1: value = (u32) *((u8 *)ptr);
				break;
			case 2: value = (u32) *((u16 *)ptr);
				break;
			case 4: value = (u32) *((u32 *)ptr);
				break;
			default:
				/* errors in a tasklet?  yuck! */
			}

			fsl_nput(fsl_fifo->id, value, fsl_status);

			if(fsl_nodata(fsl_status))
			{
				do_reschedule++;
				break;
			}
			else
			{
				/* Update the buffer data structure */
				fsl_fifo->tx_cnt-=fsl_fifo->width;
				fsl_fifo->tx_tail+= fsl_fifo->width;
				fsl_fifo->tx_tail &= FSL_FIFO_BUF_SIZE-1;
			}
		}
		restore_flags(flags);
	}

	if(do_reschedule)
		tasklet_schedule(&fsl_write_tasklet);

	/* Wake any processes blocked on FIFO write() operations */
	wake_up(&fsl_write_queue);
}


void read_tasklet_func(unsigned long data)
{
	struct fsl_fifo_t *fsl_fifo=(struct fsl_fifo_t *)data;
	int id;
	int do_reschedule=0;

	/* Loop through each fsl fifo*/
	for(id=0;id<MAX_FSL_FIFO_COUNT;id++, fsl_fifo++)
	{
		unsigned flags;

		if(!fsl_fifo->exists)
			continue;
	
		/* If rx_buf is full, don't even try */
		save_flags_cli(flags);
		if((fsl_fifo->rx_cnt + fsl_fifo->width) >= FSL_FIFO_BUF_SIZE)
		{
			restore_flags(flags);
			continue;
		}
		restore_flags(flags);

		while(1)
		{
			unsigned val;
			unsigned fsl_status;

			void *ptr=&(fsl_fifo->rx_buf[fsl_fifo->rx_head]);

			fsl_nget(fsl_fifo->id, val, fsl_status);
			if(fsl_nodata(fsl_status))
			{
				break;
			}

			save_flags_cli(flags);

			switch(fsl_fifo->width) {
			case 1:
				*((u8*)ptr) = val & 0xFF;
				break;
			case 2:
				*((u16*)ptr) = val & 0xFFFF;
				break;
			case 4:
				*((u32*)ptr) = val;
				break;
			default:
				/* bleugh, error in tasklet */
			}
			
			fsl_fifo->rx_head += fsl_fifo->width;
			fsl_fifo->rx_head &= FSL_FIFO_BUF_SIZE-1;
			fsl_fifo->rx_cnt += fsl_fifo->width;
			if(fsl_fifo->rx_cnt<FSL_FIFO_BUF_SIZE)
			{
				restore_flags(flags);
				do_reschedule++;
				break;
			}
			restore_flags(flags);
		}
	}

	if(do_reschedule)
	{
		tasklet_schedule(&fsl_read_tasklet);
	}

	wake_up(&fsl_read_queue);
}


/* Seek doesn't make a lot of sense! */
static loff_t fsl_llseek(struct file *f, loff_t off, int whence)
{
	return 0;
}

/* Put data into the tx_buffer and schedule write_tasklet */
static ssize_t fsl_write(struct file *f, const char *buf, 
				size_t count, loff_t *pos)
{
	struct fsl_fifo_t *fsl_fifo;
	int do_sched_tasklet=0;
	unsigned flags;
	int total=0;

	fsl_fifo = (struct fsl_fifo_t *)f->private_data;
	if(!fsl_fifo->exists)
		return -ENODEV;

	save_flags_cli(flags);

	/* Is the tx buffer full, and we block */
	while(fsl_fifo->tx_cnt==FSL_FIFO_BUF_SIZE)
	{
		restore_flags(flags);
		tasklet_schedule(&fsl_write_tasklet);
		interruptible_sleep_on(&fsl_write_queue);
		if(current->sigpending)
			return -EINTR;
		save_flags_cli(flags);
	}

	/* The following logic stolen from mcfserial.c */
	save_flags(flags);
	while(1)
	{
		int c;

		cli();

		/* How much can we write into the buffer? */
		c=MIN(count, MIN(FSL_FIFO_BUF_SIZE - fsl_fifo->tx_cnt-1,
				 FSL_FIFO_BUF_SIZE - fsl_fifo->tx_head));
		if(c<=0)
		{
			restore_flags(flags);
			break;
		}

		copy_from_user(fsl_fifo->tx_buf+fsl_fifo->tx_head, 
				buf, c);

		fsl_fifo->tx_head=(fsl_fifo->tx_head+c) & (FSL_FIFO_BUF_SIZE-1);
		fsl_fifo->tx_cnt += c;

		restore_flags(flags);

		do_sched_tasklet++;

		buf+=c;
		count-=c;
		total+=c;
	}

	if(do_sched_tasklet)
		tasklet_schedule(&fsl_write_tasklet);

	return total;
}

static ssize_t fsl_read(struct file *f, char *buf, 
				size_t count, loff_t *pos)
{
	struct fsl_fifo_t *fsl_fifo=(struct fsl_fifo_t *)f->private_data;
	unsigned flags;
	int total=0;

	if(!fsl_fifo->exists)
		return -ENODEV;

	save_flags_cli(flags);

	/* Block until there's some data in the buffer */
	while(!fsl_fifo->rx_cnt)
	{
		restore_flags(flags);
		tasklet_schedule(&fsl_read_tasklet);
		interruptible_sleep_on(&fsl_read_queue);
		if(current->sigpending)
		{
			return -EINTR;
		}
		save_flags_cli(flags);
	}
		
	while(1)
	{
		int c; 

		cli();
		c = MIN(count, MIN(fsl_fifo->rx_cnt,
				   FSL_FIFO_BUF_SIZE - fsl_fifo->rx_tail));

		if(c<=0)
		{
			restore_flags(flags);
			break;
		}

		copy_to_user(buf, fsl_fifo->rx_buf+fsl_fifo->rx_tail,c);

		/* Update the buffer data structure */
		fsl_fifo->rx_cnt-=c;
		fsl_fifo->rx_tail+=c;
		fsl_fifo->rx_tail &= FSL_FIFO_BUF_SIZE-1;

		restore_flags(flags);

		buf += c;
		count -= c;
		total += c;
	}

	if(count)
		tasklet_schedule(&fsl_read_tasklet);

	return total;
}

int fsl_flush_buffers(int id)
{
	unsigned flags;

	struct fsl_fifo_t *fsl_fifo=&fsl_fifo_table[id];

	if(!fsl_fifo->exists)
		return 0;

	save_flags_cli(flags);
	fsl_fifo->rx_cnt=fsl_fifo->rx_head=fsl_fifo->rx_tail=0;
	fsl_fifo->tx_cnt=fsl_fifo->tx_head=fsl_fifo->tx_tail=0;
	restore_flags(flags);

	return 0;
}

static int fsl_ioctl(struct inode *inode, struct file *f,
			unsigned int cmd, unsigned long arg)
{
	int fsl_id;
	struct fsl_fifo_t *fsl_fifo;
	unsigned flags, fsl_status;

	fsl_id=FSL_FIFO_ID(inode->i_rdev);

	if(fsl_id >= MAX_FSL_FIFO_COUNT)
		return -ENODEV;

	fsl_fifo=&fsl_fifo_table[fsl_id];

	if(!fsl_fifo->exists)
		return -ENODEV;

	switch(cmd)
	{
	case FSLFIFO_IOCRESET:		/* Reset */
		save_flags_cli(flags);
		fsl_flush_buffers(fsl_id);
		restore_flags(flags);
		return 0;

	case FSLFIFO_IOCTCONTROL:	/* Write control value */
	/* Note this jumps the queue, and is blatted directly to the FSL
	   port.  It does not get queued in the main SW buffer */
		save_flags_cli(flags);
		fsl_ncput(fsl_id, arg, fsl_status);
		restore_flags(flags);
		if(fsl_error(fsl_status))
			return -EIO;
		else if(fsl_nodata(fsl_status))
			return -EBUSY;
		return 0;

	case FSLFIFO_IOCQCONTROL:	/* Read control value */
	/* This bypasses the normal software buffers.  It is very unlikely
	   to work unless those buffers are empty, and the tasklets are
 	   idling */
		save_flags_cli(flags);
		fsl_ncget(fsl_id, arg, fsl_status);
		restore_flags(flags);
		if(fsl_error(fsl_status))	/* Non-control value from FSL */
			return -EIO;	
		else if(fsl_nodata(fsl_status))	/* Nothing from FSL */
			return -EBUSY;	
		return arg;
		
	case FSLFIFO_IOCTWIDTH:		/* set data width */
		if(arg==1 || arg==2 || arg==4)
		{
			save_flags_cli(flags);
			fsl_flush_buffers(fsl_id);
			fsl_fifo->width=arg;
			return 0;
		}
		else
			return -EINVAL;

	case FSLFIFO_IOCQWIDTH:		/* get data width */
		return fsl_fifo->width;

	default:			/* bleugh */
		return -EINVAL;
	}

	return 0;
}
static int fsl_ioctl(struct inode *inode, struct file *f,
			unsigned int cmd, unsigned long arg)
{
	int fsl_id;
	struct fsl_fifo_t *fsl_fifo;
	unsigned flags, fsl_status;

	fsl_id=FSLFIFO_ID(inode->i_rdev);

	if(fsl_id >= MAX_FSLFIFO_COUNT)
		return -ENODEV;

	fsl_fifo=&fsl_fifo_table[fsl_id];

	if(!fsl_fifo->exists)
		return -ENODEV;

	switch(cmd)
	{
	case FSLFIFO_IOCRESET:		/* Reset */
		save_flags_cli(flags);
		fsl_flush_buffers(fsl_id);
		restore_flags(flags);
		return 0;

	case FSLFIFO_IOCTCONTROL:	/* Write control value */
	/* Note this jumps the queue, and is blatted directly to the FSL
	   port.  It does not get queued in the main SW buffer */
		save_flags_cli(flags);
		fsl_ncput(fsl_id, arg, fsl_status);
		restore_flags(flags);
		if(fsl_error(fsl_status))
			return -EIO;
		else if(fsl_nodata(fsl_status))
			return -EBUSY;
		return 0;

	case FSLFIFO_IOCQCONTROL:	/* Read control value */
	/* This bypasses the normal software buffers.  It is very unlikely
	   to work unless those buffers are empty, and the tasklets are
 	   idling */
		save_flags_cli(flags);
		fsl_ncget(fsl_id, arg, fsl_status);
		restore_flags(flags);
		if(fsl_error(fsl_status))	/* Non-control value from FSL */
			return -EIO;	
		else if(fsl_nodata(fsl_status))	/* Nothing from FSL */
			return -EBUSY;	
		return arg;
		
	case FSLFIFO_IOCTWIDTH:		/* set data width */
		if(arg==1 || arg==2 || arg==4)
		{
			save_flags_cli(flags);
			fsl_flush_buffers(fsl_id);
			fsl_fifo->width=arg;
			return 0;
		}
		else
			return -EINVAL;

	case FSLFIFO_IOCQWIDTH:		/* get data width */
		return fsl_fifo->width;

	default:			/* bleugh */
		return -EINVAL;
	}

	return 0;
}