基於S3C2440的嵌入式Linux驅動——SPI子系統解讀(四)

轉自: http://blog.csdn.net/yj4231/article/details/7755709

感謝yj4231博主的辛勤勞動!!!


本系列文章對Linux設備模型中的SPI子系統進行講解。SPI子系統的講解將分爲4個部分。

   第一部分,將對SPI子系統整體進行描述,同時給出SPI的相關數據結構,最後描述SPI總線的註冊。基於S3C2440的嵌入式Linux驅動——SPI子系統解讀(一)

   第二部分,該文將對SPI的主控制器(master)驅動進行描述。          基於S3C2440的嵌入式Linux驅動——SPI子系統解讀(二)

   第三部分,該文將對SPI設備驅動,也稱protocol 驅動,進行講解。基於S3C2440的嵌入式Linux驅動——SPI子系統解讀(三)

   第四部分,即本篇文章,通過SPI設備驅動留給用戶層的API,我們將從上到下描述數據是如何通過SPI的protocol 驅動,由bitbang 中轉,最後由master驅動將

                    數據傳輸出去。

本文屬於第部分。

7. write,read和ioctl綜述

      在spi設備驅動層提供了兩種數據傳輸方式。一種是半雙工方式,write方法提供了半雙工讀訪問,read方法提供了半雙工寫訪問。另一種就是全雙工方式,ioctl調用將同時完成數據的傳送與發送。

     在後面的描述中,我們將對write和ioctl方法做出詳細的描述,而read方法和write極其相似,將不多做介紹。

     接下來首先看看write方法是如何實現的。

8. write方法

8.1 spidev_write

 在用戶空間執行open打開設備文件以後,就可以執行write系統調用,該系統調用將會執行我們提供的write方法。代碼如下:

 下列代碼位於drivers/spi/spidev.c中。    

/* Write-only message with current device setup */
static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;
	unsigned long		missing;

	/* chipselect only toggles at start or end of operation */
	if (count > bufsiz)	/*數據大於4096字節*/
		return -EMSGSIZE;

	spidev = filp->private_data;

	mutex_lock(&spidev->buf_lock);
	/*將用戶層的數據拷貝至buffer中,buffer在open方法中分配*/
	missing = copy_from_user(spidev->buffer, buf, count); 
	if (missing == 0) {
		status = spidev_sync_write(spidev, count);
	} else
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);

	return status;
}

在這裏,做的事情很少,主要就是從用戶空間將需要發送的數據複製過來。然後調用spidev_sync_write。

8.2 spidev_sync_write

下列代碼位於drivers/spi/spidev.c中。  

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= spidev->buffer,
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spidev_sync(spidev, &m);
}

static inline void spi_message_init(struct spi_message *m)
{
    memset(m, 0, sizeof *m);
    INIT_LIST_HEAD(&m->transfers);    /*初始化鏈表頭*/
}

spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
    list_add_tail(&t->transfer_list, &m->transfers);/*添加transfer_list*/
}
在這裏,創建了transfer和message。spi_transfer包含了要發送數據的信息。然後初始化了message中的transfer鏈表頭,並將spi_transfer添加到了transfer鏈表中。也就是以spi_message的transfers爲鏈表頭的鏈表中,包含了transfer,而transfer正好包含了需要發送的數據。由此可見message其實是對transfer的封裝。
最後,調用了spidev_sync,並將創建的spi_message作爲參數傳入。

8.3  spidev_sync

下列代碼位於drivers/spi/spidev.c中。  

static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done);	/*創建completion*/
	int status;

	message->complete = spidev_complete;/*定義complete方法*/
	message->context = &done;			/*complete方法的參數*/

	spin_lock_irq(&spidev->spi_lock);
	if (spidev->spi == NULL)
		status = -ESHUTDOWN;
	else
		status = spi_async(spidev->spi, message);/*異步,用complete來完成同步*/
	spin_unlock_irq(&spidev->spi_lock);

	if (status == 0) {
		wait_for_completion(&done);	/*在bitbang_work中調用complete方法來喚醒*/
		status = message->status;
		if (status == 0)
			status = message->actual_length;	/*返回發送的字節數*/
	}
	return status;
}
在這裏,初始化了completion,這個東東將實現write系統調用的同步。在後面我們將會看到如何實現的。

隨後調用了spi_async,從名字上可以看出該函數是異步的,也就是說該函數返回後,數據並沒有被髮送出去。因此使用了wait_for_completion來等待數據的發送完成,達到同步的目的。


8.4 spi_async

下列代碼位於drivers/spi/spi.h中

/**
 * spi_async - asynchronous SPI transfer
 * @spi: device with which data will be exchanged
 * @message: describes the data transfers, including completion callback
 * Context: any (irqs may be blocked, etc)
 *
 * This call may be used in_irq and other contexts which can't sleep,
 * as well as from task contexts which can sleep.
 *
 * The completion callback is invoked in a context which can't sleep.
 * Before that invocation, the value of message->status is undefined.
 * When the callback is issued, message->status holds either zero (to
 * indicate complete success) or a negative error code.  After that
 * callback returns, the driver which issued the transfer request may
 * deallocate the associated memory; it's no longer in use by any SPI
 * core or controller driver code.
 *
 * Note that although all messages to a spi_device are handled in
 * FIFO order, messages may go to different devices in other orders.
 * Some device might be higher priority, or have various "hard" access
 * time requirements, for example.
 *
 * On detection of any fault during the transfer, processing of
 * the entire message is aborted, and the device is deselected.
 * Until returning from the associated message completion callback,
 * no other spi_message queued to that device will be processed.
 * (This rule applies equally to all the synchronous transfer calls,
 * which are wrappers around this core asynchronous primitive.)
 */
static inline int
spi_async(struct spi_device *spi, struct spi_message *message)
{
    message->spi = spi;       /*指出執行transfer的SPI接口*/
    return spi->master->transfer(spi, message);    /*即調用spi_bitbang_transfer*/
}
這個函數僅僅保存了spi_device信息後,然後調用了master的transfer方法,該方法在spi_bitbang_start中定義爲spi_bitbang_transfer。


8.5 spi_bitbang_transfer

下列代碼位於drivers/spi/spi_bitbang.c中。

/**
 * spi_bitbang_transfer - default submit to transfer queue
 */
int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m)
{
	struct spi_bitbang	*bitbang;
	unsigned long		flags;
	int			status = 0;

	m->actual_length = 0;
	m->status = -EINPROGRESS;

	bitbang = spi_master_get_devdata(spi->master);

	spin_lock_irqsave(&bitbang->lock, flags);
	if (!spi->max_speed_hz)
		status = -ENETDOWN;
	else {
        /*下面的工作隊列和queue在spi_bitbang_start函數中初始化*/
        list_add_tail(&m->queue, &bitbang->queue);    /*將message添加到bitbang的queue鏈表中*/
        queue_work(bitbang->workqueue, &bitbang->work);    /*提交工作到工作隊列*/
	}
	spin_unlock_irqrestore(&bitbang->lock, flags);

	return status;
}
這裏將message添加到了bitbang的queue鏈表中。然後提交了一個工作到工作隊列,隨後函數返回到spi_async,又返回到spidev_sync中。爲方便將spidev_sync的部分代碼列出:
status = spi_async(spidev->spi, message);/*異步,用complete來完成同步*/
	spin_unlock_irq(&spidev->spi_lock);

	if (status == 0) {
		wait_for_completion(&done);	/*在bitbang_work中調用complete方法來喚醒*/
		status = message->status;
		if (status == 0)
			status = message->actual_length;	/*返回發送的字節數*/
	}
	return status;
 當spi_async函數返回後,需要發送的數據已經通過工作的形式添加到了工作隊列,在稍後的工作執行時,將完成數據的發送。隨後調用了wait_for_completion等待數據的發送完成。到此,可以看出completion的使用是用來完成同步I/O的


8.6 bitbang_work

  在上一節最後添加了工作bitbang->work到工作隊列中,在過一段時候後,內核將以進程執行該work。而work即爲在spi_bitbang_start中定義的bitbang_work函數。我們來看下這個函數。

  下列代碼位於drivers/spi/spi_bitbang.c中。 

/*
 * SECOND PART ... simple transfer queue runner.
 *
 * This costs a task context per controller, running the queue by
 * performing each transfer in sequence.  Smarter hardware can queue
 * several DMA transfers at once, and process several controller queues
 * in parallel; this driver doesn't match such hardware very well.
 *
 * Drivers can provide word-at-a-time i/o primitives, or provide
 * transfer-at-a-time ones to leverage dma or fifo hardware.
 */
static void bitbang_work(struct work_struct *work)
{
    struct spi_bitbang    *bitbang =
        container_of(work, struct spi_bitbang, work);    /*獲取spi_bitbang*/
    unsigned long        flags;

    spin_lock_irqsave(&bitbang->lock, flags);    /*自旋鎖加鎖*/
    bitbang->busy = 1;        /*bitbang忙碌*/
    while (!list_empty(&bitbang->queue)) {    /*有spi_message*/
        struct spi_message    *m;
        struct spi_device    *spi;
        unsigned        nsecs;
        struct spi_transfer    *t = NULL;
        unsigned        tmp;
        unsigned        cs_change;
        int            status;
        int            (*setup_transfer)(struct spi_device *,
                        struct spi_transfer *);

        m = container_of(bitbang->queue.next, struct spi_message,/*獲取spi_message*/
                queue);        
        list_del_init(&m->queue);        /*以獲取spi_message,刪除該spi_message*/
        spin_unlock_irqrestore(&bitbang->lock, flags);/*釋放自旋鎖*/

        /* FIXME this is made-up ... the correct value is known to
         * word-at-a-time bitbang code, and presumably chipselect()
         * should enforce these requirements too?
         */
        nsecs = 100;

        spi = m->spi;
        tmp = 0;
        cs_change = 1;
        status = 0;
        setup_transfer = NULL;

        /*遍歷,獲取所有的spi_transfer*/
        list_for_each_entry (t, &m->transfers, transfer_list) {    

            /* override or restore speed and wordsize */
            if (t->speed_hz || t->bits_per_word) { /*如果這兩個參數有任何一個已經設置了,本例中沒有定義*/
                setup_transfer = bitbang->setup_transfer;
                if (!setup_transfer) {
                    status = -ENOPROTOOPT;
                    break;
                }
            }
            if (setup_transfer) {        /*本例中爲NULL*/
                status = setup_transfer(spi, t);
                if (status < 0)
                    break;
            }

            /* set up default clock polarity, and activate chip;
             * this implicitly updates clock and spi modes as
             * previously recorded for this device via setup().
             * (and also deselects any other chip that might be
             * selected ...)
             */
            if (cs_change) {                                /*初值爲1*/
                bitbang->chipselect(spi, BITBANG_CS_ACTIVE);/*即調用s3c24xx_spi_chipsel,激活CS信號,寫寄存器,設置SPI模式*/
                ndelay(nsecs);                                /*延遲100納秒*/
            }
            cs_change = t->cs_change;                        /*保存cs_change*/                
            if (!t->tx_buf && !t->rx_buf && t->len) {        /*檢查參數*/
                status = -EINVAL;
                break;
            }

            /* transfer data.  the lower level code handles any
             * new dma mappings it needs. our caller always gave
             * us dma-safe buffers.
             */
            if (t->len) {
                /* REVISIT dma API still needs a designated
                 * DMA_ADDR_INVALID; ~0 might be better.
                 */
                if (!m->is_dma_mapped)
                    t->rx_dma = t->tx_dma = 0;            /*不使用DMA*/
                status = bitbang->txrx_bufs(spi, t);    /*即調用s3c24xx_spi_txrx,開始發送數據,status爲已發送數據的大小*/
            }
            if (status > 0)
                m->actual_length += status;    /*保存已發送字節*/
            if (status != t->len) {    /*要求發送和已發送的大小不同*/
                /* always report some kind of error */
                if (status >= 0)
                    status = -EREMOTEIO;
                break;
            }
            status = 0;

            /* protocol tweaks before next transfer */
            if (t->delay_usecs)
                udelay(t->delay_usecs);    /*延遲*/

            if (!cs_change)/*判斷是否需要禁止CS,爲1表示要求在兩次數據傳輸之間禁止CS*/
                continue;
            if (t->transfer_list.next == &m->transfers)    /*沒有transfer*/
                break;

            /* sometimes a short mid-message deselect of the chip
             * may be needed to terminate a mode or command
             */
            ndelay(nsecs);    /*延遲*/
            bitbang->chipselect(spi, BITBANG_CS_INACTIVE);    /*禁止CS*/
            ndelay(nsecs);
        }    /*遍歷spi_transfer結束*/

        m->status = status;
        m->complete(m->context); /*調用complete,一個message處理完畢*/

        /* restore speed and wordsize */
        if (setup_transfer)
            setup_transfer(spi, NULL);

        /* normally deactivate chipselect ... unless no error and
         * cs_change has hinted that the next message will probably
         * be for this chip too.
         */
        if (!(status == 0 && cs_change)) {
            ndelay(nsecs);
            bitbang->chipselect(spi, BITBANG_CS_INACTIVE);    /*禁止CS*/
            ndelay(nsecs);
        }

        spin_lock_irqsave(&bitbang->lock, flags);
    }
    bitbang->busy = 0;    
    spin_unlock_irqrestore(&bitbang->lock, flags);
}
本函數中,調用了兩個方法bibang->chipselect和bitbang->txrx_bufs,這兩個方法實際調用了s3c24xx_spi_chipsel和s3c24xx_spi_txrx函數,這兩個函數都是master驅動層提供的函數。s3c24xx_spi_chipsel已經在4.2.2節中給出,該函數設置控制寄存器並激活CS信號。s3c24xx_spi_txrx函數的實參t,即爲spi_transfer,函數完成該spi_transfer中數據的發送,並返回已發送的字節數。然後,判斷是否需要禁止CS。接着遍歷到下一個spi_transfer,再次發送數據。當所有spi_transfer發送完成以後,將調用complete方法,從而讓在spidev_sync函數中等待completion的函數返回。下面,先來來看下數據是怎麼發送出去的,也就是s3c24xx_spi_txrx函數。最後,看看complete方法。


8.7 s3c24xx_spi_txrx 和s3c24xx_spi_irq

下列代碼位於deivers/spi/s3c24xx.c。

static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count)
{
    return hw->tx ? hw->tx[count] : 0;    /*發送緩衝區指針是否爲空,空則發送0*/
}

static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)/*bitbang.txrx_bufs方法*/
{
    struct s3c24xx_spi *hw = to_hw(spi);

    dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
        t->tx_buf, t->rx_buf, t->len);
    /*保存transfer相關數據到s3c24xx_sp結構中*/
    hw->tx = t->tx_buf;
    hw->rx = t->rx_buf;
    hw->len = t->len;
    hw->count = 0;

    init_completion(&hw->done);    /*初始化completion*/

    /* send the first byte */    /*發送第一個數據,tx[0]*/
    writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT);

    wait_for_completion(&hw->done);/*等待completion*/

    return hw->count;  /*返回發送的字節數*/
}

static irqreturn_t s3c24xx_spi_irq(int irq, void *dev)
{
    struct s3c24xx_spi *hw = dev;
    unsigned int spsta = readb(hw->regs + S3C2410_SPSTA);/*獲取狀態寄存器*/
    unsigned int count = hw->count;

    if (spsta & S3C2410_SPSTA_DCOL) {    /*發生錯誤*/
        dev_dbg(hw->dev, "data-collision\n");
        complete(&hw->done);    /*喚醒等待complete的進程*/
        goto irq_done;
    }

    if (!(spsta & S3C2410_SPSTA_READY)) {/*未就緒*/
        dev_dbg(hw->dev, "spi not ready for tx?\n");
        complete(&hw->done);    /*喚醒等待complete的進程*/
        goto irq_done;
    }

    hw->count++;/*增加計數*/

    if (hw->rx)
        hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT);/*讀取數據*/

    count++;    /*增加計數*/

    if (count < hw->len)         /*未發送完畢,則繼續發送*/
        writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT);
    else
        complete(&hw->done);    /*發送完畢,喚醒等待complete的進程*/

 irq_done:
    return IRQ_HANDLED;
} 
在s3c24xx_spi_txrx函數中,首先發送了待發送數據中的第一個字節,隨後就調用wait_for_completion來等待剩餘的數據發送完成。

NOTE:這裏的completion是master驅動層的,spi設備驅動也有一個completion,用於IO同步,不要混淆。

當第一個數據發送完成以後,SPI中斷產生,開始執行中斷服務程序。在中斷服務程序中,將判斷是否需要讀取數據,如果是則從寄存器中讀取數據。

NOTE:如果是使用read系統調用,那麼在此發送的數據將是0。

隨後發送下一個數據,直到數據發送完成。發送完成後調用complete,使在s3c24xx_spi_txrx的wait_for_completion得以返回。接着,s3c24xx_spi_txrx就將返回已發送的字節數。

NOTE:其實該中斷服務子程序實現了全雙工數據的傳輸,只不過特定於具體的系統調用,從而分爲了半雙工讀和寫。


8.8 complete方法

在8.6節的bitbang_work中,當一個message的所有數據發送完成以後,將會調用complete函數。該函數如下:

/*
 * We can't use the standard synchronous wrappers for file I/O; we
 * need to protect against async removal of the underlying spi_device.
 */
static void spidev_complete(void *arg)
{
	complete(arg);
}
該函數將使在spidev_sync函數中的wait_for_completion得以返回,從而完成了同步IO。

至此,整個write系統調用的流程均以講解完畢,在這其中也對在master和protocol中未曾給出的函數做出了一一講解,最後,對第8章進行小結。


8.9 小結

   從示意圖中,我們可以很清除看到函數的調用過程:先調用spi設備驅動層,隨後調用bitbang中間層,最後調用了master驅動層來完成數據的傳輸。


9. read方法

read方法和write方法基本差不多,關鍵的區別在於其發送的數據爲0,而在s3c24xx_spi_txrx中斷服務程序中將讀取數據寄存器。下面僅僅給出函數調用示意圖。


在這裏給出spidev_read和spidev_sync_read,方便讀者進行對比。

/* Read-only message with current device setup */
static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;

	/* chipselect only toggles at start or end of operation */
	if (count > bufsiz)	/*如果讀取的字節數大於緩衝區的大小,則報錯*/
		return -EMSGSIZE;

	spidev = filp->private_data;	/*獲取spidev*/

	mutex_lock(&spidev->buf_lock);	/*加鎖,對buffer進行互斥房屋內*/
	status = spidev_sync_read(spidev, count);
	if (status > 0) {
		unsigned long	missing;

		missing = copy_to_user(buf, spidev->buffer, status);
		if (missing == status)
			status = -EFAULT;
		else
			status = status - missing;
	}
	mutex_unlock(&spidev->buf_lock);

	return status;
}

static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
    struct spi_transfer    t = {
            .rx_buf        = spidev->buffer,
            .len        = len,
        };
    struct spi_message    m;

    spi_message_init(&m);    /*初始化message*/
    spi_message_add_tail(&t, &m);    /*添加transfer*/
    return spidev_sync(spidev, &m);
}


10. ioctl方法
   這一章節中,我們將看一下SPI子系統是如何使用ioctl系統調用來實現全雙工讀寫。

10.1 spi_ioc_transfer

   在使用ioctl時,用戶空間要使用一個數據結構來封裝需要傳輸的數據,該結構爲spi_ioc_transfe。而在write系統調用時,只是簡單的從用戶空間複製數據過來。該結構中的很多字段將被複制到spi_transfer結構中相應的字段。也就是說一個spi_ioc_transfer表示一個spi_transfer,用戶空間可以定義多個spi_ioc_transfe,最後以數組形式傳遞給ioctl。

   下面同時給出ioctl中cmd的值。其中SPI_IOC_MASSAGE用於實現全雙工IO,而其他的用於設置或者讀取某個特定值。

   下列數據結構位於:include/linux/spi/spidev.h。

/**
 * struct spi_ioc_transfer - describes a single SPI transfer
 * @tx_buf: Holds pointer to userspace buffer with transmit data, or null.
 *	If no data is provided, zeroes are shifted out.
 * @rx_buf: Holds pointer to userspace buffer for receive data, or null.
 * @len: Length of tx and rx buffers, in bytes.
 * @speed_hz: Temporary override of the device's bitrate.
 * @bits_per_word: Temporary override of the device's wordsize.
 * @delay_usecs: If nonzero, how long to delay after the last bit transfer
 *	before optionally deselecting the device before the next transfer.
 * @cs_change: True to deselect device before starting the next transfer.
 *
 * This structure is mapped directly to the kernel spi_transfer structure;
 * the fields have the same meanings, except of course that the pointers
 * are in a different address space (and may be of different sizes in some
 * cases, such as 32-bit i386 userspace over a 64-bit x86_64 kernel).
 * Zero-initialize the structure, including currently unused fields, to
 * accomodate potential future updates.
 *
 * SPI_IOC_MESSAGE gives userspace the equivalent of kernel spi_sync().
 * Pass it an array of related transfers, they'll execute together.
 * Each transfer may be half duplex (either direction) or full duplex.
 *
 *	struct spi_ioc_transfer mesg[4];
 *	...
 *	status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg);
 *
 * So for example one transfer might send a nine bit command (right aligned
 * in a 16-bit word), the next could read a block of 8-bit data before
 * terminating that command by temporarily deselecting the chip; the next
 * could send a different nine bit command (re-selecting the chip), and the
 * last transfer might write some register values.
 */
struct spi_ioc_transfer {
	__u64		tx_buf;
	__u64		rx_buf;

	__u32		len;
	__u32		speed_hz;

	__u16		delay_usecs;
	__u8		bits_per_word;
	__u8		cs_change;
	__u32		pad;

	/* If the contents of 'struct spi_ioc_transfer' ever change
	 * incompatibly, then the ioctl number (currently 0) must change;
	 * ioctls with constant size fields get a bit more in the way of
	 * error checking than ones (like this) where that field varies.
	 *
	 * NOTE: struct layout is the same in 64bit and 32bit userspace.
	 */
};

/* not all platforms use <asm-generic/ioctl.h> or _IOC_TYPECHECK() ... */
#define SPI_MSGSIZE(N) \                /*SPI_MSGSIZE不能大於4KB*/
    ((((N)*(sizeof (struct spi_ioc_transfer))) < (1 << _IOC_SIZEBITS)) \
        ? ((N)*(sizeof (struct spi_ioc_transfer))) : 0)
#define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])


/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) */
#define SPI_IOC_RD_MODE            _IOR(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_WR_MODE            _IOW(SPI_IOC_MAGIC, 1, __u8)

/* Read / Write SPI bit justification */
#define SPI_IOC_RD_LSB_FIRST        _IOR(SPI_IOC_MAGIC, 2, __u8)
#define SPI_IOC_WR_LSB_FIRST        _IOW(SPI_IOC_MAGIC, 2, __u8)

/* Read / Write SPI device word length (1..N) */
#define SPI_IOC_RD_BITS_PER_WORD    _IOR(SPI_IOC_MAGIC, 3, __u8)
#define SPI_IOC_WR_BITS_PER_WORD    _IOW(SPI_IOC_MAGIC, 3, __u8)

/* Read / Write SPI device default max speed hz */
#define SPI_IOC_RD_MAX_SPEED_HZ        _IOR(SPI_IOC_MAGIC, 4, __u32)
#define SPI_IOC_WR_MAX_SPEED_HZ        _IOW(SPI_IOC_MAGIC, 4, __u32)

10.2 spidev_ioctl

在用戶空間執行ioctl系統調用時,將會執行spidev_ioctl方法,我們來看下。

下列代碼位於drivers/spi/spidev.c

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int			err = 0;
	int			retval = 0;
	struct spidev_data	*spidev;
	struct spi_device	*spi;
	u32			tmp;
	unsigned		n_ioc;
	struct spi_ioc_transfer	*ioc;

	/* Check type and command number */
	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)	/*如果幻數不想等,則報錯*/
		return -ENOTTY;

	/* Check access direction once here; don't repeat below.
	 * IOC_DIR is from the user perspective, while access_ok is
	 * from the kernel perspective; so they look reversed.
	 */
	 /*對用戶空間的指針進行檢查,分成讀寫兩部分檢查*/
	if (_IOC_DIR(cmd) & _IOC_READ、
		err = !access_ok(VERIFY_WRITE,	/*access_ok成功返回1*/
				(void __user *)arg, _IOC_SIZE(cmd));
	if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)
		err = !access_ok(VERIFY_READ,
				(void __user *)arg, _IOC_SIZE(cmd));
	if (err)
		return -EFAULT;

	/* guard against device removal before, or while,
	 * we issue this ioctl.
	 */
	spidev = filp->private_data;		/*獲取spidev*/
	spin_lock_irq(&spidev->spi_lock);
	spi = spi_dev_get(spidev->spi);		/*增加引用技術,並獲取spi_device*/
	spin_unlock_irq(&spidev->spi_lock);

	if (spi == NULL)
		return -ESHUTDOWN;

	/* use the buffer lock here for triple duty:
	 *  - prevent I/O (from us) so calling spi_setup() is safe;
	 *  - prevent concurrent SPI_IOC_WR_* from morphing
	 *    data fields while SPI_IOC_RD_* reads them;
	 *  - SPI_IOC_MESSAGE needs the buffer locked "normally".
	 */
	mutex_lock(&spidev->buf_lock);	/*加鎖互斥體*/

	switch (cmd) {
	/* read requests */		/*讀取請求*/
	case SPI_IOC_RD_MODE:
		retval = __put_user(spi->mode & SPI_MODE_MASK,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_LSB_FIRST:
		retval = __put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_BITS_PER_WORD:
		retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MAX_SPEED_HZ:
		retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);
		break;

	/* write requests */	/*寫請求*/
	case SPI_IOC_WR_MODE:	
		retval = __get_user(tmp, (u8 __user *)arg);
		if (retval == 0) {			/*__get_user調用成功*/
			u8	save = spi->mode;	/*保存原先的值*/

			if (tmp & ~SPI_MODE_MASK) {
				retval = -EINVAL;
				break;				/*模式有錯誤,則跳出switch*/
			}

			tmp |= spi->mode & ~SPI_MODE_MASK;/*這步貌似多此一舉????*/
			spi->mode = (u8)tmp;
			retval = spi_setup(spi);/*調用master->setup方法,即s3c24xx_spi_setup*/
			if (retval < 0)
				spi->mode = save;	/*調用不成功,恢復參數*/
			else
				dev_dbg(&spi->dev, "spi mode %02x\n", tmp);
		}
		break;
	case SPI_IOC_WR_LSB_FIRST:
		retval = __get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u8	save = spi->mode;

			if (tmp)				/*參數爲正整數,設置爲LSB*/
				spi->mode |= SPI_LSB_FIRST;
			else					/*參數爲0,設置爲非LSB*/
				spi->mode &= ~SPI_LSB_FIRST;
			retval = spi_setup(spi);/*調用master->setup方法,即s3c24xx_spi_setup?/	
			if (retval < 0)
				spi->mode = save; 	/*調用不成功,恢復參數*/
			else
				dev_dbg(&spi->dev, "%csb first\n",
						tmp ? 'l' : 'm');
		}
		break;
	case SPI_IOC_WR_BITS_PER_WORD:
		retval = __get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u8	save = spi->bits_per_word;

			spi->bits_per_word = tmp;
			retval = spi_setup(spi);	/*調用master->setup方法,即s3c24xx_spi_setup*/	
			if (retval < 0)
				spi->bits_per_word = save;
			else
				dev_dbg(&spi->dev, "%d bits per word\n", tmp);
		}
		break;
	case SPI_IOC_WR_MAX_SPEED_HZ:
		retval = __get_user(tmp, (__u32 __user *)arg);
		if (retval == 0) {
			u32	save = spi->max_speed_hz;

			spi->max_speed_hz = tmp;
			retval = spi_setup(spi);	/*調用master->setup方法,即s3c24xx_spi_setup*/	
			if (retval < 0)
				spi->max_speed_hz = save;
			else
				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
		}
		break;

	default:
		/* segmented and/or full-duplex I/O request */	/*全雙工,接受發送數據*/
		if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
				|| _IOC_DIR(cmd) != _IOC_WRITE) {
			retval = -ENOTTY;
			break;
		}

		tmp = _IOC_SIZE(cmd);		/*獲取參數的大小,參數爲spi_ioc_transfer數組*/
		if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {/*檢查tmp是否爲後者的整數倍*/
			retval = -EINVAL;
			break;
		}
		n_ioc = tmp / sizeof(struct spi_ioc_transfer); /*計算共有幾個spi_ioc_transfer*/
		if (n_ioc == 0)
			break;

		/* copy into scratch area */
		ioc = kmalloc(tmp, GFP_KERNEL);
		if (!ioc) {
			retval = -ENOMEM;
			break;
		}
		/*從用戶空間拷貝spi_ioc_transfer數組,不對用戶空間指針進行檢查*/
		if (__copy_from_user(ioc, (void __user *)arg, tmp)) {
			kfree(ioc);
			retval = -EFAULT;
			break;
		}

		/* translate to spi_message, execute */
		retval = spidev_message(spidev, ioc, n_ioc);
		kfree(ioc);
		break;
	}

	mutex_unlock(&spidev->buf_lock);
	spi_dev_put(spi);	/*減少引用計數*/
	return retval;
}
 在函數中,首先對cmd進行了一些列的檢查。隨後使用switch語句來判讀cmd,並執行相應的功能。cmd的第一部分爲讀請求,分別從寄存器讀取4個參數。第二部分爲寫請求,分別用於修改4個參數並寫入寄存器。剩餘的第三部分就是全雙工讀寫請求,這是會先計算共有多少個spi_ioc_transfer,然後分配空間,從用戶空間將spi_ioc_transfer數組拷貝過來,然後將該數組和數組個數作爲參數調用spidev_message。


10.3 spidev_message

static int spidev_message(struct spidev_data *spidev,
		struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
{
	struct spi_message	msg;
	struct spi_transfer	*k_xfers;
	struct spi_transfer	*k_tmp;
	struct spi_ioc_transfer *u_tmp;
	unsigned		n, total;
	u8			*buf;
	int			status = -EFAULT;
	
	spi_message_init(&msg);		/*初始化message*/
	k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);	/*分配內存,並清0*/
	if (k_xfers == NULL)
		return -ENOMEM;

	/* Construct spi_message, copying any tx data to bounce buffer.
	 * We walk the array of user-provided transfers, using each one
	 * to initialize a kernel version of the same transfer.
	 */
	buf = spidev->buffer;	/*所有的spi_transfer共享該buffer*/
	total = 0;	
	/*遍歷spi_ioc_transfer數組,拷貝相應的參數至spi_transfer數組*/
	for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
			n;
			n--, k_tmp++, u_tmp++) {
		k_tmp->len = u_tmp->len;

		total += k_tmp->len;
		if (total > bufsiz) {  /*緩衝區長度爲4096字節*/
			status = -EMSGSIZE;
			goto done;
		}

		if (u_tmp->rx_buf) {	/*需要接受收據*/
			k_tmp->rx_buf = buf;
			if (!access_ok(VERIFY_WRITE, (u8 __user *)	/*檢查指針*/
						(uintptr_t) u_tmp->rx_buf,
						u_tmp->len))
				goto done;
		}
		if (u_tmp->tx_buf) {	/*需要發送數據*/
			k_tmp->tx_buf = buf;
			if (copy_from_user(buf, (const u8 __user *)	/*將用戶空間待發送的數據拷貝至buf中*/
						(uintptr_t) u_tmp->tx_buf,
					u_tmp->len))
				goto done;
		}
		buf += k_tmp->len;		/*修改buf指針,指向下一個transfer的緩衝區首地址*/
		/*複製四個參數*/
		k_tmp->cs_change = !!u_tmp->cs_change;
		k_tmp->bits_per_word = u_tmp->bits_per_word;
		k_tmp->delay_usecs = u_tmp->delay_usecs;
		k_tmp->speed_hz = u_tmp->speed_hz;
#ifdef VERBOSE
		dev_dbg(&spi->dev,
			"  xfer len %zd %s%s%s%dbits %u usec %uHz\n",
			u_tmp->len,
			u_tmp->rx_buf ? "rx " : "",
			u_tmp->tx_buf ? "tx " : "",
			u_tmp->cs_change ? "cs " : "",
			u_tmp->bits_per_word ? : spi->bits_per_word,
			u_tmp->delay_usecs,
			u_tmp->speed_hz ? : spi->max_speed_hz);
#endif
		spi_message_add_tail(k_tmp, &msg); /*添加spi_transfer到message的鏈表中*/
	}

	/*spidev_sync->spi_async->spi_bitbang_transfer->bitbang_work->s3c24xx_spi_txrx*/
	status = spidev_sync(spidev, &msg);	
	if (status < 0)
		goto done;

	/* copy any rx data out of bounce buffer */
	buf = spidev->buffer;
	for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
		if (u_tmp->rx_buf) {
			if (__copy_to_user((u8 __user *)
					(uintptr_t) u_tmp->rx_buf, buf,	/*從buf緩衝區複製數據到用戶空間*/
					u_tmp->len)) {
				status = -EFAULT;
				goto done;
			}
		}
		buf += u_tmp->len;
	}
	status = total;

done:
	kfree(k_xfers);
	return status;
}
 首先,根據spi_ioc_transfer的個數,分配了同樣個數的spi_transfer,把spi_ioc_transfer中的信息複製給spi_transfer,然後將spi_transfer添加到spi_message的鏈

 表中。接着。執行了spidev_sync,這個東西似乎似曾相識,這個函數就 8.3  小結的函數。之後的過程就和前面的write、read一樣了。

 其實,這個函數的作用就是把所需要完成的數據傳輸任務轉換成spi_transfer,然後添加到message的連表中。

 從spidev_sync返回以後,數據傳輸完畢,將讀取到的數據,複製到用戶空間。至此,整個ioctl系統調用的過程就結束了。

10.4 小結


事實上,全速工io和半雙工io的執行過程基本一樣,只不過ioctl需要一個專用的結構體來封裝傳輸的任務,接着將該任務轉換成對應的spi_transfer,最後交給spidev_sync。


11. 結束語

   本系列文章先從最底層的master驅動開始講解,接着描述了高層的spi設備驅動,然後,通過系統調用接口,從上至下的講解了整個函數的調用過程。最終,

   我們可以很清除看到半雙工讀和半雙寫的區別和相似之處,以及半雙工IO和全雙工IO的區別和相似之處。

   最後,希望該系列文章能幫助你瞭解Linux的SPI子系統。









發佈了29 篇原創文章 · 獲贊 55 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章