Linux裝置驅動探究第1天----spi驅動(1)

fulinux發表於2015-04-02

本文允許轉載,請註明出處:http://blog.csdn.net/fulinus

Linux核心程式碼實在太大了,一個小小的模組也會讓你手足無措,今天下午決心要把spi驅動好好看看。

首先分析spidev.c檔案,這個檔案中定義struct file_operations結構中的成員。成員有spidev_write、spidev_read和spidev_ioctl,前兩者實現半雙工通訊,後者實現全雙工通訊。當然還有open和release等相關的成員,先忽略吧。

spidev_write -------->spidev_sync

spidev_write -------->spidev_sync

spidev_ioctl  ------> spidev_message------->spidev_sync

詳細文件見:點選開啟連結


還有填充struct spi_drive資料結構體成員的函式,有spidev_probe,spidev_remove函式。以及裝置驅動的初始化和退出函式:spidev_init和spidev_exit

程式碼分析見:點選開啟連結

引用:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <asm/uaccess.h>

#define SPIDEV_MAJOR			153	//spidev主裝置號
#define N_SPI_MINORS			32	/* ... up to 256 */
static DECLARE_BITMAP(minors, N_SPI_MINORS);	//宣告次裝置點陣圖
#define SPI_MODE_MASK (SPI_CPHA|SPI_CPOL|SPI_CS_HIGH|SPI_LSB_FIRST|SPI_3WIRE|SPI_LOOP|SPI_NO_CS|SPI_READY)

struct spidev_data {
	dev_t	devt;				//裝置號
	spinlock_t	spi_lock;		//自旋鎖
	struct spi_device	*spi;	//spi裝置結構體
	struct list_head	device_entry;
	struct mutex	buf_lock;	//互斥鎖
	unsigned		users;		//使用者計數
	u8			*buffer;		//緩衝區
};

static LIST_HEAD(device_list);	//宣告spi裝置連結串列
static DEFINE_MUTEX(device_list_lock);	//定義互斥鎖
static unsigned bufsiz = 4096;	//最大傳輸緩衝區大小
module_param(bufsiz, uint, S_IRUGO);
MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message");

static void spidev_complete(void *arg)
{
	complete(arg);	//呼叫complete
}

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

	message->complete = spidev_complete;	//設定spi訊息的complete方法 回撥函式
	message->context = &done;

	spin_lock_irq(&spidev->spi_lock);
	if (spidev->spi == NULL)	//判斷是否有指定對應的spi裝置
		status = -ESHUTDOWN;
	else
		status = spi_async(spidev->spi, message);	//spi非同步同步
	spin_unlock_irq(&spidev->spi_lock);

	if (status == 0) {
		wait_for_completion(&done);	//等待傳輸完成
		status = message->status;	//獲取spi訊息傳輸事務狀態
		if (status == 0)
			status = message->actual_length;	//status等於傳輸的實際長度
	}
	return status;	//返回實際傳輸長度
}

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訊息(初始化spi傳遞事務佇列)
	spi_message_add_tail(&t, &m);	//新增spr傳遞到該佇列
	return spidev_sync(spidev, &m);	//同步讀寫
}

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);	//初始化spi訊息(初始化spi傳遞事務佇列)
	spi_message_add_tail(&t, &m);	//新增spr傳遞到該佇列
	return spidev_sync(spidev, &m);	//同步讀寫
}

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;

	if (count > bufsiz)	//傳輸資料大於緩衝區容量
		return -EMSGSIZE;
	spidev = filp->private_data;	//從檔案私有資料指標獲取spidev_data
	mutex_lock(&spidev->buf_lock);	//上互斥鎖
	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 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;

	if (count > bufsiz)	//傳輸資料大於緩衝區容量
		return -EMSGSIZE;
	spidev = filp->private_data;	//從檔案私有資料指標獲取spidev_data
	mutex_lock(&spidev->buf_lock);	//上互斥鎖
	missing = copy_from_user(spidev->buffer, buf, count);	//使用者空間複製到核心空間
	if (missing == 0) {	//傳輸失敗個數為0
		status = spidev_sync_write(spidev, count);	//同步寫,返回傳輸資料長度
	} 
	else
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);//解互斥鎖
	return status;	//返回寫資料的實際個數
}

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);	//初始化spi訊息(初始化spi傳遞事務佇列)
	k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);	//分配spi傳輸指標記憶體
	if (k_xfers == NULL)
		return -ENOMEM;
	buf = spidev->buffer;	//獲取spidev_data的緩衝區
	total = 0;
	//n=xfers為spi_ioc_transfer個數,u_tmp = u_xfers為要處理的spi_ioc_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) {	//資訊量超過bufsiz緩衝區最大容量
			status = -EMSGSIZE;
			goto done;
		}
		if (u_tmp->rx_buf) {	//接收緩衝區指標不為空
			k_tmp->rx_buf = 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;	//緩衝區指標指向buf
			if (copy_from_user(buf, (const u8 __user *)(uintptr_t) u_tmp->tx_buf,u_tmp->len))	//使用者空間複製資料到buf
				goto done;
		}
		buf += k_tmp->len;	//緩衝區指標移動一個傳輸資訊的長度
		k_tmp->cs_change = !!u_tmp->cs_change;	//設定cs_change
		k_tmp->bits_per_word = u_tmp->bits_per_word;	//設定bits_per_word 一個字多少位
		k_tmp->delay_usecs = u_tmp->delay_usecs;	//設定delay_usecs 毫秒級延時
		k_tmp->speed_hz = u_tmp->speed_hz;	//設定speed_hz 速率
#ifdef VERBOSE
		dev_dbg(&spidev->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 ? : spidev->spi->bits_per_word,u_tmp->delay_usecs,u_tmp->speed_hz ? : spidev->spi->max_speed_hz);
#endif
		spi_message_add_tail(k_tmp, &msg);	//新增spr傳遞到該佇列
	}
	//for迴圈的作用是將spi_ioc_transfer批量轉換為spi傳遞結構體spi_transfer,然後新增進spi傳遞事務佇列
	status = spidev_sync(spidev, &msg);		//同步讀寫
	if (status < 0)
		goto done;
	buf = spidev->buffer;	//獲取spidev_data緩衝區指標
	for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {	//批量從核心空間複製spi_ioc_transfer到使用者空間
		if (u_tmp->rx_buf) {	//判斷是否存在接收緩衝區
			if (__copy_to_user((u8 __user *)(uintptr_t) u_tmp->rx_buf, buf,u_tmp->len)) {
				status = -EFAULT;
				goto done;
			}
		}
		buf += u_tmp->len;	//buf指標位置調整指向下一個spi_ioc_transfer
	}
	status = total;	//status等於實際傳輸的資料長度
done:
	kfree(k_xfers);	//釋放k_xfers
	return status;	//返回實際傳輸的資料長度
}

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;

	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)	//判斷控制命令的型別
		return -ENOTTY;
	if (_IOC_DIR(cmd) & _IOC_READ)	//判斷控制命令的方向是否為讀read
		err = !access_ok(VERIFY_WRITE,(void __user *)arg, _IOC_SIZE(cmd));	//判斷傳輸資料大小
	if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)	//判斷控制命令的方向是否為寫write
		err = !access_ok(VERIFY_READ,(void __user *)arg, _IOC_SIZE(cmd));	//判斷傳輸資料大小
	if (err)
		return -EFAULT;

	spidev = filp->private_data;	//從檔案私有資料中獲取spidev_data
	spin_lock_irq(&spidev->spi_lock);	//上自旋鎖
	spi = spi_dev_get(spidev->spi);			//獲取spi裝置
	spin_unlock_irq(&spidev->spi_lock);	//解自旋鎖
	if (spi == NULL)	//獲取spi裝置失敗
		return -ESHUTDOWN;	//則返回錯誤
	mutex_lock(&spidev->buf_lock);	//上互斥鎖
	
	switch (cmd) {
	case SPI_IOC_RD_MODE:	//設定spi讀模式
		retval = __put_user(spi->mode & SPI_MODE_MASK,(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_LSB_FIRST:	//設定spi讀最低有效位
		retval = __put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_BITS_PER_WORD:	//設定spi讀每個字含多個個位
		retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MAX_SPEED_HZ:	//設定spi讀最大速率
		retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);
		break;
	case SPI_IOC_WR_MODE:	//設定spi寫模式
		retval = __get_user(tmp, (u8 __user *)arg);
		if (retval == 0) {
			u8	save = spi->mode;	//獲取spi裝置模式

			if (tmp & ~SPI_MODE_MASK) {
				retval = -EINVAL;
				break;
			}

			tmp |= spi->mode & ~SPI_MODE_MASK;
			spi->mode = (u8)tmp;
			retval = spi_setup(spi);	//配置spi裝置
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "spi mode %02x\n", tmp);
		}
		break;
	case SPI_IOC_WR_LSB_FIRST:	//設定spi寫最低有效位
		retval = __get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u8	save = spi->mode;	//獲取spi裝置模式

			if (tmp)
				spi->mode |= SPI_LSB_FIRST;
			else
				spi->mode &= ~SPI_LSB_FIRST;
			retval = spi_setup(spi);	//配置spi裝置
			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:	//設定spi寫每個字含多個個位
		retval = __get_user(tmp, (__u8 __user *)arg);	//使用者空間獲取資料
		if (retval == 0) {
			u8	save = spi->bits_per_word;	//獲取spi裝置 每個字含多少位

			spi->bits_per_word = tmp;	//更新新的spi裝置 每個字含多少位
			retval = spi_setup(spi);	//配置spi裝置
			if (retval < 0)	//配置失敗
				spi->bits_per_word = save;	//還原spi裝置 每個字含多少位
			else
				dev_dbg(&spi->dev, "%d bits per word\n", tmp);
		}
		break;
	case SPI_IOC_WR_MAX_SPEED_HZ:		//設定spi寫最大速率
		retval = __get_user(tmp, (__u32 __user *)arg);	//使用者空間獲取資料
		if (retval == 0) {
			u32	save = spi->max_speed_hz;	//獲取spi裝置最大速率

			spi->max_speed_hz = tmp;	//更新新的spi裝置最大速率
			retval = spi_setup(spi);	//配置spi裝置
			if (retval < 0)	//配置失敗
				spi->max_speed_hz = save;	//還原spi裝置最大速率
			else
				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
		}
		break;

	default:
		//命令必須為寫方向的命令,且傳輸資料必須是SPI_IOC_MESSAGE()修飾的命令
		if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))|| _IOC_DIR(cmd) != _IOC_WRITE) {
			retval = -ENOTTY;
			break;
		}

		tmp = _IOC_SIZE(cmd);	//計算傳輸資料大小
		if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {	//判斷是否為spi_ioc_transfer對齊
			retval = -EINVAL;
			break;
		}
		n_ioc = tmp / sizeof(struct spi_ioc_transfer);	//計算出spi_ioc_transfer資料的個數
		if (n_ioc == 0)
			break;

		ioc = kmalloc(tmp, GFP_KERNEL);	//分配spi_ioc_transfer指標ioc記憶體
		if (!ioc) {
			retval = -ENOMEM;
			break;
		}
		if (__copy_from_user(ioc, (void __user *)arg, tmp)) {	//從使用者空間複製到核心空間
			kfree(ioc);	//複製失敗則釋放ioc記憶體
			retval = -EFAULT;
			break;
		}

		retval = spidev_message(spidev, ioc, n_ioc);	//spidev訊息處理
		kfree(ioc);	//釋放ioc記憶體
		break;
	}

	mutex_unlock(&spidev->buf_lock);	//解互斥鎖
	spi_dev_put(spi);	//增加spi裝置的引用計數
	return retval;
}

static int spidev_open(struct inode *inode, struct file *filp)
{
	struct spidev_data	*spidev;
	int	status = -ENXIO;

	mutex_lock(&device_list_lock);	//上互斥鎖
	list_for_each_entry(spidev, &device_list, device_entry) {	//遍歷device_list
		if (spidev->devt == inode->i_rdev) {	//判斷裝置號找到對應的裝置
			status = 0;	//設定狀態為0
			break;
		}
	}
	if (status == 0) {	//找得到對應的裝置
		if (!spidev->buffer) {	//spidev_data緩衝區為空
			spidev->buffer = kmalloc(bufsiz, GFP_KERNEL);	//則分配記憶體
			if (!spidev->buffer) {	//還空
				dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");	//除錯了
				status = -ENOMEM;
			}
		}
		if (status == 0) {	//找得到對應的裝置
			spidev->users++;	//spidev_data使用者計數++
			filp->private_data = spidev;	//spidev_data放在檔案的私有資料裡
			nonseekable_open(inode, filp);	//設定檔案的開啟模式(檔案讀寫指標不會跟隨讀寫操作移動)
		}
	} 
	else
		pr_debug("spidev: nothing for minor %d\n", iminor(inode));
	mutex_unlock(&device_list_lock);	//接互斥鎖
	return status;
}

static int spidev_release(struct inode *inode, struct file *filp)
{
	struct spidev_data	*spidev;
	int	status = 0;

	mutex_lock(&device_list_lock);
	spidev = filp->private_data;	//獲取spidev_data
	filp->private_data = NULL;		//清除檔案的私有資料指標
	spidev->users--;				//使用者個數--
	if (!spidev->users) {	//如果使用者個數為0
		int		dofree;
		kfree(spidev->buffer);	//釋放spidev_data的緩衝區記憶體
		spidev->buffer = NULL;	//清除spidev_data緩衝區指標
		spin_lock_irq(&spidev->spi_lock);	//上自旋鎖
		dofree = (spidev->spi == NULL);	//判斷spi裝置是否與spidev_data解綁了
		spin_unlock_irq(&spidev->spi_lock);	//解自旋鎖
		if (dofree)			//沒有捆綁的spi裝置
			kfree(spidev);	//則是否spidev_data記憶體
	}
	mutex_unlock(&device_list_lock);
	return status;
}

static const struct file_operations spidev_fops = {		//檔案操作函式集
	.owner =	THIS_MODULE,
	.write =	spidev_write,		//寫write
	.read =		spidev_read,		//讀read
	.unlocked_ioctl = spidev_ioctl,	//控制ioctl
	.open =		spidev_open,		//開啟open
	.release =	spidev_release,		//釋放release
	.llseek =	no_llseek,			//檔案指標移動 no_llseek表示沒有移動
};

static struct class *spidev_class;

static int __devinit spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	int	status;
	unsigned long	minor;

	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);	//分配spidev_data記憶體
	if (!spidev)
		return -ENOMEM;
	spidev->spi = spi;	//設定spidev_data->spi(spi裝置)
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);
	INIT_LIST_HEAD(&spidev->device_entry);	//初始化spidev_data入口連結串列
	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, N_SPI_MINORS);	//查詢次裝置點陣圖分配次裝置號
	if (minor < N_SPI_MINORS) {
		struct device *dev;
		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);	//計算出裝置號
		//建立裝置/dev/spidev%d.%d(spidev匯流排號.片選號)
		dev = device_create(spidev_class, &spi->dev, spidev->devt,spidev, "spidev%d.%d",spi->master->bus_num, spi->chip_select);
		status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
	} 
	else {
		dev_dbg(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {	//分配裝置號成功
		set_bit(minor, minors);	//更新次裝置點陣圖
		list_add(&spidev->device_entry, &device_list);	//新增進裝置連結串列
	}
	mutex_unlock(&device_list_lock);

	if (status == 0)
		spi_set_drvdata(spi, spidev);	//spi->dev->p->driver_data=spidev 
	else
		kfree(spidev);

	return status;
}

static int __devexit spidev_remove(struct spi_device *spi)
{
	struct spidev_data	*spidev = spi_get_drvdata(spi);		//根據spi裝置獲取spidev_data
	spin_lock_irq(&spidev->spi_lock);			//上自旋鎖
	spidev->spi = NULL;								//清空spidev_data->spi指標
	spi_set_drvdata(spi, NULL);						//spi->dev->p->driver_data=NULL
	spin_unlock_irq(&spidev->spi_lock);			//解自旋鎖
	mutex_lock(&device_list_lock);				//上互斥鎖
	list_del(&spidev->device_entry);				//刪除spidev_data入口連結串列
	device_destroy(spidev_class, spidev->devt);		//銷燬/dev/spidev%d.%d
	clear_bit(MINOR(spidev->devt), minors);			//清除次裝置點陣圖對應位
	if (spidev->users == 0)							//使用者個數為0
		kfree(spidev);								//釋放spidev_data記憶體
	mutex_unlock(&device_list_lock);			//解互斥鎖
	return 0;
}

static struct spi_driver spidev_spi_driver = {	//spi裝置驅動
	.driver = {
		.name =		"spidev",
		.owner =	THIS_MODULE,
	},
	.probe =	spidev_probe,	//spidev的probe方法(當註冊了modalias域為"spidev"的spi裝置或板級裝置,則會呼叫probe方法)
	.remove =	__devexit_p(spidev_remove),	//spidev的remove方法
};

static int __init spidev_init(void)		//spidev介面初始化
{
	int status;
	BUILD_BUG_ON(N_SPI_MINORS > 256);
	//註冊字元裝置,主裝置號SPIDEV_MAJOR=153,捆綁的裝置操作函式集為spidev_fops
	status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
	if (status < 0)
		return status;
	spidev_class = class_create(THIS_MODULE, "spidev");	//建立裝置類spidev_class
	if (IS_ERR(spidev_class)) {
		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
		return PTR_ERR(spidev_class);
	}
	status = spi_register_driver(&spidev_spi_driver);	//註冊spi裝置驅動spidev_spi_driver
	if (status < 0) {
		class_destroy(spidev_class);
		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
	}
	return status;
}
module_init(spidev_init);	//宣告初始化入口

static void __exit spidev_exit(void)			//spidev介面銷燬
{
	spi_unregister_driver(&spidev_spi_driver);	//登出spi裝置驅動spidev_spi_driver
	class_destroy(spidev_class);				//登出裝置類spidev_class
	unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);	//登出字元裝置
}
module_exit(spidev_exit);	//宣告初始化出口

MODULE_AUTHOR("Andrea Paterniani, <a.paterniani@swapp-eng.it>");
MODULE_DESCRIPTION("User mode SPI device interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:spidev");

這裡整理下ioctl的命令:

SPI_IOC_RD_MODE		//讀 模式
SPI_IOC_RD_LSB_FIRST	//讀 LSB
SPI_IOC_RD_BITS_PER_WORD	//讀 每字多少位
SPI_IOC_RD_MAX_SPEED_HZ	//讀 最大速率
SPI_IOC_WR_MODE		//寫 模式
SPI_IOC_WR_LSB_FIRST	//寫 LSB
SPI_IOC_WR_BITS_PER_WORD	//寫 每字多少位
SPI_IOC_WR_MAX_SPEED_HZ	//寫 最大速率
SPI_IOC_MESSAGE(n)		//傳輸n個資料包






相關文章