Commit a8fbc767 authored by Mikhail Karpenko's avatar Mikhail Karpenko
Browse files

Share disk access between driver and system

parent ee11ae90
Loading
Loading
Loading
Loading
+56 −8
Original line number Original line Diff line number Diff line
@@ -13,6 +13,8 @@
 * more details.
 * more details.
 */
 */


#define CONFIG_PRINK

#include <linux/errno.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/module.h>
@@ -123,6 +125,7 @@ static void elphel_defer_load(struct device *dev)


static irqreturn_t elphel_irq_handler(int irq, void * dev_instance)
static irqreturn_t elphel_irq_handler(int irq, void * dev_instance)
{
{
	unsigned long irq_flags;
	irqreturn_t handled;
	irqreturn_t handled;
	struct ata_host *host = dev_instance;
	struct ata_host *host = dev_instance;
	struct ahci_host_priv *hpriv = host->private_data;
	struct ahci_host_priv *hpriv = host->private_data;
@@ -133,7 +136,7 @@ static irqreturn_t elphel_irq_handler(int irq, void * dev_instance)




	if (dpriv->flags & IRQ_SIMPLE) {
	if (dpriv->flags & IRQ_SIMPLE) {
		/* handle interrupt */
		/* handle interrupt from internal command */
		host_irq_stat = readl(hpriv->mmio + HOST_IRQ_STAT);
		host_irq_stat = readl(hpriv->mmio + HOST_IRQ_STAT);
		if (!host_irq_stat)
		if (!host_irq_stat)
			return IRQ_NONE;
			return IRQ_NONE;
@@ -143,24 +146,27 @@ static irqreturn_t elphel_irq_handler(int irq, void * dev_instance)
		dev_dbg(host->dev, "irq_stat = 0x%x, host irq_stat = 0x%x, time stamp: %u\n", irq_stat, host_irq_stat, get_rtc_usec());
		dev_dbg(host->dev, "irq_stat = 0x%x, host irq_stat = 0x%x, time stamp: %u\n", irq_stat, host_irq_stat, get_rtc_usec());


		writel(irq_stat, port_mmio + PORT_IRQ_STAT);
		writel(irq_stat, port_mmio + PORT_IRQ_STAT);
//		writel(0xffffffff, port_mmio + PORT_IRQ_STAT);


		writel(host_irq_stat, hpriv->mmio + HOST_IRQ_STAT);
		writel(host_irq_stat, hpriv->mmio + HOST_IRQ_STAT);
		handled = IRQ_HANDLED;
		handled = IRQ_HANDLED;


		if (process_cmd(host->dev, dpriv, host->ports[0]) == 0) {
		if (process_cmd(host->dev, dpriv, host->ports[DEFAULT_PORT_NUM]) == 0) {
			finish_cmd(host->dev, dpriv);
			finish_cmd(host->dev, dpriv);
			if (move_head(dpriv) != -1) {
			if (move_head(dpriv) != -1) {
				process_cmd(host->dev, dpriv, host->ports[0]);
				process_cmd(host->dev, dpriv, host->ports[DEFAULT_PORT_NUM]);
			} else {
			} else {
				if (dpriv->flags & DELAYED_FINISH) {
				if (dpriv->flags & DELAYED_FINISH) {
					dpriv->flags &= ~DELAYED_FINISH;
					dpriv->flags &= ~DELAYED_FINISH;
					finish_rec(host->dev, dpriv, port);
					finish_rec(host->dev, dpriv, port);
				} else {
					/* all commands have been processed; flag reset here does not need spin lock protection */
					dpriv->flags &= ~INTERNAL_CMD;
				}
				}
			}
			}
		}
		}
	} else {
	} else {
		/* pass handling to AHCI level */
		/* pass handling to AHCI level; flag reset here does not need spin lock protection */
		dpriv->flags &= ~NATIVE_CMD;
		handled = ahci_single_irq_intr(irq, dev_instance);
		handled = ahci_single_irq_intr(irq, dev_instance);
	}
	}


@@ -269,6 +275,8 @@ static int elphel_drv_probe(struct platform_device *pdev)
	if (!dpriv)
	if (!dpriv)
		return -ENOMEM;
		return -ENOMEM;


	spin_lock_init(&dpriv->flags_lock);

	for (i = 0; i < MAX_CMD_SLOTS; i++) {
	for (i = 0; i < MAX_CMD_SLOTS; i++) {
		ret = init_buffers(dev, &dpriv->fbuffs[i]);
		ret = init_buffers(dev, &dpriv->fbuffs[i]);
		if (ret != 0)
		if (ret != 0)
@@ -783,7 +791,6 @@ static void align_frame(struct device *dev, struct elphel_ahci_priv *dpriv)
	dma_sync_single_for_cpu(dev, fbuffs->common_buff.iov_dma, fbuffs->common_buff.iov_len, DMA_TO_DEVICE);
	dma_sync_single_for_cpu(dev, fbuffs->common_buff.iov_dma, fbuffs->common_buff.iov_len, DMA_TO_DEVICE);


	/* copy remainder of previous frame to the beginning of common buffer */
	/* copy remainder of previous frame to the beginning of common buffer */
	printk(KERN_DEBUG "current command slot: %u, prev slot: %u\n", cmd_slot, prev_slot);
	if (likely(rbuff->iov_len != 0)) {
	if (likely(rbuff->iov_len != 0)) {
		len = rbuff->iov_len;
		len = rbuff->iov_len;
		dev_dbg(dev, "copy %u bytes from REM #%u to common buffer\n", len, prev_slot);
		dev_dbg(dev, "copy %u bytes from REM #%u to common buffer\n", len, prev_slot);
@@ -1152,6 +1159,7 @@ static int process_cmd(struct device *dev, struct elphel_ahci_priv *dpriv, struc
static void finish_cmd(struct device *dev, struct elphel_ahci_priv *dpriv)
static void finish_cmd(struct device *dev, struct elphel_ahci_priv *dpriv)
{
{
	int all;
	int all;
	unsigned long irq_flags;


	dpriv->lba_ptr.wr_count = 0;
	dpriv->lba_ptr.wr_count = 0;
	if ((dpriv->flags & LAST_BLOCK) == 0) {
	if ((dpriv->flags & LAST_BLOCK) == 0) {
@@ -1162,6 +1170,7 @@ static void finish_cmd(struct device *dev, struct elphel_ahci_priv *dpriv)
	}
	}
	printk(KERN_DEBUG "reset chunks in slot %u, all = %d\n", dpriv->head_ptr, all);
	printk(KERN_DEBUG "reset chunks in slot %u, all = %d\n", dpriv->head_ptr, all);
	reset_chunks(dpriv->data_chunks[dpriv->head_ptr], all);
	reset_chunks(dpriv->data_chunks[dpriv->head_ptr], all);
	/* flag reset here does not need spin lock protection */
	dpriv->flags &= ~PROC_CMD;
	dpriv->flags &= ~PROC_CMD;
	dpriv->curr_cmd = 0;
	dpriv->curr_cmd = 0;
	dpriv->max_data_sz = 0;
	dpriv->max_data_sz = 0;
@@ -1253,6 +1262,7 @@ static ssize_t rawdev_write(struct device *dev, ///<
		size_t buff_sz)                          ///<
		size_t buff_sz)                          ///<
{
{
	int i;
	int i;
	unsigned long irq_flags;
	struct ata_host *host = dev_get_drvdata(dev);
	struct ata_host *host = dev_get_drvdata(dev);
	struct ata_port *port = host->ports[DEFAULT_PORT_NUM];
	struct ata_port *port = host->ports[DEFAULT_PORT_NUM];
	struct elphel_ahci_priv *dpriv = dev_get_dpriv(dev);
	struct elphel_ahci_priv *dpriv = dev_get_dpriv(dev);
@@ -1262,12 +1272,25 @@ static ssize_t rawdev_write(struct device *dev, ///<
	struct fvec *chunks;
	struct fvec *chunks;
	static int dont_process = 0;
	static int dont_process = 0;


	/* simple check if we've got the right command */
	if (buff_sz != sizeof(struct frame_data)) {
	if (buff_sz != sizeof(struct frame_data)) {
		dev_err(dev, "the size of the data buffer is incorrect, should be equal to sizeof(struct frame_data)\n");
		dev_err(dev, "the size of the data buffer is incorrect, should be equal to sizeof(struct frame_data)\n");
		return -EINVAL;
		return -EINVAL;
	}
	}
	memcpy(&fdata, buff, sizeof(struct frame_data));
	memcpy(&fdata, buff, sizeof(struct frame_data));


	/* check if we can issue internal command */
	spin_lock_irqsave(&dpriv->flags_lock, irq_flags);
	if ((dpriv->flags & NATIVE_CMD) == 0) {
		dpriv->flags |= INTERNAL_CMD;
	} else {
		spin_unlock_irqrestore(&dpriv->flags_lock, irq_flags);
		printk_ratelimited(KERN_DEBUG, "system command is in progress, defer Elphel command\n");
//		dev_dbg(dev, "system command is in progress, defer Elphel command\n");
		return -EAGAIN;
	}
	spin_unlock_irqrestore(&dpriv->flags_lock, irq_flags);

	if (fdata.cmd == DRV_CMD_FINISH) {
	if (fdata.cmd == DRV_CMD_FINISH) {
		if (!(dpriv->flags & PROC_CMD)) {
		if (!(dpriv->flags & PROC_CMD)) {
			finish_rec(dev, dpriv, port);
			finish_rec(dev, dpriv, port);
@@ -1278,10 +1301,9 @@ static ssize_t rawdev_write(struct device *dev, ///<
	}
	}


	if ((move_tail(dpriv, 1) == -1) || dont_process) {
	if ((move_tail(dpriv, 1) == -1) || dont_process) {
		// we are not ready yet
		/* we are not ready yet because command queue is full */
		return -EAGAIN;
		return -EAGAIN;
	}
	}
	printk(KERN_DEBUG "set current tail slot: %u\n", dpriv->tail_ptr);
	chunks = &dpriv->data_chunks[dpriv->tail_ptr];
	chunks = &dpriv->data_chunks[dpriv->tail_ptr];
	buffs = &dpriv->fbuffs[dpriv->tail_ptr];
	buffs = &dpriv->fbuffs[dpriv->tail_ptr];


@@ -1499,6 +1521,31 @@ static void elphel_cmd_issue(struct ata_port *ap,///< device port for which the
	writel(1 << slot_num, port_mmio + PORT_CMD_ISSUE);
	writel(1 << slot_num, port_mmio + PORT_CMD_ISSUE);
}
}


static int elphel_qc_defer(struct ata_queued_cmd *qc)
{
	int ret;
	unsigned long irq_flags;
	struct elphel_ahci_priv *dpriv = dev_get_dpriv(qc->ap->dev);

	/* First apply the usual rules */
	ret = ata_std_qc_defer(qc);
	if (ret != 0)
		return ret;

	/* And now check if internal command is in progress */
	spin_lock_irqsave(&dpriv->flags_lock, irq_flags);
	if (dpriv->flags & INTERNAL_CMD) {
		ret = ATA_DEFER_LINK;
		printk_ratelimited(KERN_DEBUG "defer system command\n");
	} else {
		printk_ratelimited(KERN_DEBUG "proceed to system command\n");
		dpriv->flags |= NATIVE_CMD;
	}
	spin_unlock_irqrestore(&dpriv->flags_lock, irq_flags);

	return ret;
}

static ssize_t lba_start_read(struct device *dev, struct device_attribute *attr, char *buff)
static ssize_t lba_start_read(struct device *dev, struct device_attribute *attr, char *buff)
{
{
	struct ata_host *host = dev_get_drvdata(dev);
	struct ata_host *host = dev_get_drvdata(dev);
@@ -1596,6 +1643,7 @@ static struct ata_port_operations ahci_elphel_ops = {
		.inherits		= &ahci_ops,
		.inherits		= &ahci_ops,
		.port_start		= elphel_port_start,
		.port_start		= elphel_port_start,
		.qc_prep		= elphel_qc_prep,
		.qc_prep		= elphel_qc_prep,
		.qc_defer       = elphel_qc_defer,
};
};


static const struct ata_port_info ahci_elphel_port_info = {
static const struct ata_port_info ahci_elphel_port_info = {
+18 −6
Original line number Original line Diff line number Diff line
@@ -24,16 +24,22 @@
#ifndef _AHCI_ELPHEL_EXT
#ifndef _AHCI_ELPHEL_EXT
#define _AHCI_ELPHEL_EXT
#define _AHCI_ELPHEL_EXT


/** Flag indicating that IRQ should not be processed in ahci_port_interrupt */
/** Flag indicating that IRQ should not be processed in ahci_handle_port_interrupt */
#define IRQ_SIMPLE                (1 << 0)
#define IRQ_SIMPLE                (1 << 0)
/** Processing command in progress */
/** Flag indicating that driver's internal command is in progress. This flag is set when
#define PROC_CMD                  (1 << 1)
 * driver performs write on its own and is mutually exclusive with #NATIVE_CMD flag */
#define INTERNAL_CMD              (1 << 1)
/** Flag indicating that libahci command is in progress. This flag is set when
 * driver performs write from the system calls and is mutually exclusive with #INTERNAL_CMD flag */
#define NATIVE_CMD                (1 << 2)
/** Processing driver's internal command is in progress */
#define PROC_CMD                  (1 << 3)
/** Flag indicating that the remaining chunk of data will be recorder */
/** Flag indicating that the remaining chunk of data will be recorder */
#define LAST_BLOCK                (1 << 2)
#define LAST_BLOCK                (1 << 4)
/** Flag indicating that recording should be stopped right after the last chunk of data
/** Flag indicating that recording should be stopped right after the last chunk of data
 * is written */
 * is written */
#define DELAYED_FINISH            (1 << 3)
#define DELAYED_FINISH            (1 << 5)
#define LOCK_TAIL                 (1 << 4)
#define LOCK_TAIL                 (1 << 6)
/** The length of a command FIS in double words */
/** The length of a command FIS in double words */
#define CMD_FIS_LEN               5
#define CMD_FIS_LEN               5
/** This is used to get 28-bit address from 64-bit value */
/** This is used to get 28-bit address from 64-bit value */
@@ -119,6 +125,12 @@ struct elphel_ahci_priv {
	size_t curr_data_offset;                     ///< offset of the last byte in a data chunk pointed to by @e curr_data_chunk
	size_t curr_data_offset;                     ///< offset of the last byte in a data chunk pointed to by @e curr_data_chunk
	size_t head_ptr;                             ///< pointer to command slot which will be written next
	size_t head_ptr;                             ///< pointer to command slot which will be written next
	size_t tail_ptr;                             ///< pointer to next free command slot
	size_t tail_ptr;                             ///< pointer to next free command slot
	spinlock_t flags_lock;                       ///< controls access to two flags in @e flags variable:
                                                 ///< #INTERNAL_CMD and #NATIVE_CMD. These flags control access to disk
                                                 ///< write operations either from the the driver itself or from the system and
                                                 ///< thus are mutually exclusive. Only flags set operation should be
                                                 ///> protected by this spin lock. Mutex is not used because these flags are accessed
                                                 ///> from interrupt context
};
};


#endif /* _AHCI_ELPHEL_EXT */
#endif /* _AHCI_ELPHEL_EXT */