[Linux-parport] ASYNC I/O Reason: Reverse transfers are _slow_

Johann Klammer klammerr at aon.at
Sat Jan 30 18:11:38 EST 2010


malcolm blaney wrote:
> Hi Johann,
> 
> Hopefully this will be of use to you:
> http://lists.infradead.org/pipermail/linux-parport/2007-October/000614.html
> 
> Malcolm.


Interesting...

Below is my version of the function (2.6.30). It uses Hardware ECP mode. FIFO recovery not implemented, so one should know how much to read before changing the mode afterwards. Still needs some testing with a real ECP device, as I don't know as to whether my uC program is really 1284 compliant. It does work with this function as well as with the default one, but that may not mean anything.

With the chipset I'm using(vt1211 superio) it seems necessary to initiate the first handshake manually by lowering PeriphClk. (the line with parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0);) Else it hangs.
I'll also try it on a more ancient intel mainboard later to see whether it is hardware specific.

---------------------------------------------------------------------->8

//not for atomic use(yet?)
static size_t parport_pc_ecp_read_block_dma (struct parport *port,
					      void *buf, size_t length, int flags)
{
	int ret = 0;
	size_t left = length;
	unsigned long dmaflag;
	const struct parport_pc_private *priv = port->physport->private_data;
	struct device *dev = port->physport->dev;
	dma_addr_t dma_addr, dma_handle = 0;
	size_t maxlen = 0x10000; /* max 64k per DMA transfer */
	char *bufp = buf;
	size_t count;

	if (port->ieee1284.phase != IEEE1284_PH_REV_IDLE)
	{//reverse here, else it may not work
		printk (KERN_DEBUG " ecp_forward_to_reverse (port)\n");
		//nicked from ieee1284_ops.c (it's static inline there)
		{
			int retval;

			/* Event 38: Set nAutoFd low */
			parport_frob_control (port,
					      PARPORT_CONTROL_AUTOFD,
					      PARPORT_CONTROL_AUTOFD);
					      
			//should this probably happen earlier?
			parport_data_reverse (port);
			ndelay (500);

			/* Event 39: Set nInit low to initiate bus reversal */
			parport_frob_control (port,
					      PARPORT_CONTROL_INIT,
					      0);

			/* Event 40: PError goes low */
			retval = parport_wait_peripheral (port,
							  PARPORT_STATUS_PAPEROUT, 0);

			if (!retval) {
				DPRINTK (KERN_DEBUG "%s: ECP direction: reverse\n",
					 port->name);
				port->ieee1284.phase = IEEE1284_PH_REV_IDLE;
			} else {
				DPRINTK (KERN_DEBUG "%s: ECP direction: failed to reverse\n",
					 port->name);
				port->ieee1284.phase = IEEE1284_PH_ECP_DIR_UNKNOWN;
				return -EIO;
			}
		}
	}

	//Better always use a bounce Buffer. Who knows, perhaps the buffer isn't contingous anyway, so it would be bad to try to DMA directly
	/* above 16 MB we use a bounce buffer as ISA-DMA is not
	   possible */
	maxlen   = PAGE_SIZE;          /* sizeof(priv->dma_buf) */
	dma_addr = priv->dma_handle;
	dma_handle = 0;

	port = port->physport;

	/* We don't want to be interrupted every character. */
	parport_pc_disable_irq (port);
	/* set ECP mode, disable(set) serviceIntr, disable dma, disable(set) err intr*/
	printk (KERN_DEBUG " frob_econtrol....\n");
	frob_econtrol (port, (7<<5)|(1<<3)|(1<<2)|(1<<4),(3<<5)|(1<<2)|(1<<4));

	/* Reverse mode. */
//	happened above parport_pc_data_reverse (port); /* Must be in PS2 mode */

	if (!left)
		goto out_no_data;

	while (left) {
		unsigned long expire = jiffies + port->physport->cad->timeout;

		count = left;

		if (count > maxlen)
			count = maxlen;

		dmaflag = claim_dma_lock();
		disable_dma(port->dma);
		clear_dma_ff(port->dma);
		set_dma_mode(port->dma, DMA_MODE_READ);
		set_dma_addr(port->dma, dma_addr);
		set_dma_count(port->dma, count);


		/* Set DMA mode */
		frob_econtrol (port, 1<<3, 1<<3);

		/* Clear serviceIntr */
		frob_econtrol (port, 1<<2, 0);

		enable_dma(port->dma);
		release_dma_lock(dmaflag);

		/*I guess this (probably) does the trick*/
		printk (KERN_DEBUG " autofd dwn\n");
		parport_frob_control (port,
				       PARPORT_CONTROL_AUTOFD,
				       0);

		/* Wait for interrupt. */
		dump_parport_state("about to wait", port);
false_alarm:
		ret = parport_wait_event (port, HZ);
		if (ret < 0) break;
		ret = 0;
		if (!time_before (jiffies, expire)) {
			/* Timed out. */
			printk (KERN_DEBUG "DMA read timed out\n");
			break;
		}
		/* Is serviceIntr set? */
		if (!(inb (ECONTROL (port)) & (1<<2))) {
			cond_resched();

			goto false_alarm;
		}

		dmaflag = claim_dma_lock();
		disable_dma(port->dma);
		clear_dma_ff(port->dma);
		count -= get_dma_residue(port->dma);
		release_dma_lock(dmaflag);

		cond_resched(); /* Can't yield the port. */

		/* Anyone else waiting for the port? */
		if (port->waithead) {
			printk (KERN_DEBUG "Somebody wants the port\n");
			break;
		}

		/* update for possible DMA residue ! */
		if (dma_handle)
			dma_addr += count;
		else
			memcpy(bufp, priv->dma_buf, count);
		bufp  += count;
		left -= count;
	}

	/* Maybe got here through break, so adjust for DMA residue! */
	if (left) {
		DPRINTK (KERN_DEBUG "exited on break\n");
		dmaflag = claim_dma_lock();
		disable_dma(port->dma);
		clear_dma_ff(port->dma);
		count -= get_dma_residue(port->dma);
		release_dma_lock(dmaflag);

		if (!dma_handle)
			memcpy(bufp, priv->dma_buf, count);
		left -= count;
	}

	dump_parport_state ("leave ecp_read_block_dma", port);
 out_no_data:

	frob_econtrol (port, 1<<2, 1<<2);
	/* Turn off DMA mode */
	frob_econtrol (port, 1<<3, 0);

	if (dma_handle)
		dma_unmap_single(dev, dma_handle, length, DMA_FROM_DEVICE);

	return length - left;
}



More information about the Linux-parport mailing list