1. 程式人生 > >嵌入式Linux驅動——SPI子系統解讀(四)

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

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;
      
     //對使用者空間的指標進行檢查,分成讀寫兩部分檢查,IOC_DIR來自於使用者,access_ok來自核心   
    if(_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok(VERIFY_WRITE,(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;
        
    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;
        
    mutex_lock(&spidev->buf_lock);
    
    switch(cmd)
    {
        //讀請求
        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;
            
        //寫請求
        case SPI_IOC_WR_MODE:
            retval = __get_user(tmp,(u8 __user *)arg);
            if(retval == 0)
            {
                u8 save = spi->mode;    //儲存原先的值
                if(temp & ~SPI_MODE_MASK)
                {
                    retval == 0;
                    break;             //模式錯誤,則跳出switch
                }
                
                tmp |= spi->mode & ~SPI_MODE_MASK;
                spi->mode = (u8)tmp;
                retval = spi_setup(spi); //先呼叫spi_setup函式,然後呼叫s3c64xx_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); //先呼叫spi_setup函式,然後呼叫s3c64xx_spi_setup
                if(retval < 0)
                    spi->mode = save;    //呼叫不成功則恢復引數
                else
                    dev_dbg(&spi->dev,"%csb first\n",tmp?'l':'m');
            }
            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);  //先呼叫spi_setup函式,然後呼叫s3c64xx_spi_setup
                if(retval < 0)
                    spi->max_speed_hz = save;
                else    
                    dev_dbg(&spi->dev,"%d Hz (max)\n",tmp);
            }
            break;
            
        default:
        //分段或者全雙工IO要求(全雙工,接收發送資料)
            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是否為spi_ioc_transfer的整數倍
                retval = -EINVAL;
                break;
            }
            n_ioc = tmp / sizeof(struct spi_ioc_transfer);
            if(n_ioc == 0)        //計算傳進來的資料共有幾個spi_ioc_transfer
                break;
                
            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;
            }
            
            retval = spidev_message(spidev,ioc,n_ioc);  //傳遞給spi_message函式執行
            kfree(ioc);
            break;
    }
    mutex_unlock(&spidev->buf_lock);
    spi_dev_put(spi);   //減少引用計數
    return retval;
}