linux mtd源码分析--mtdchar.c

Mtdchar.clinux下字符设备驱动程序的实现:

static const struct file_operations mtd_fops = {

       .owner           = THIS_MODULE,

       .llseek            = mtd_lseek,

       .read              = mtd_read,

       .write             = mtd_write,

       .ioctl              = mtd_ioctl,

       .open             = mtd_open,

       .release    = mtd_close,

       .mmap           = mtd_mmap,

#ifndef CONFIG_MMU

       .get_unmapped_area = mtd_get_unmapped_area,

#endif

};

1)加载和卸载驱动:

static int __init init_mtdchar(void)

{

       int status;

 

       status = register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops);

       if (status < 0) {

              printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",

                     MTD_CHAR_MAJOR);

       }

 

       return status;

}

 

static void __exit cleanup_mtdchar(void)

{

       unregister_chrdev(MTD_CHAR_MAJOR, "mtd");

}

 

2Open:

static int mtd_open(struct inode *inode, struct file *file)

{

       int minor = iminor(inode);

       int devnum = minor >> 1;                #<1>

       int ret = 0;

       struct mtd_info *mtd;

       struct mtd_file_info *mfi;

 

       DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");

 

       if (devnum >= MAX_MTD_DEVICES)

              return -ENODEV;

 

       /* You can't open the RO devices RW */

       if ((file->f_mode & FMODE_WRITE) && (minor & 1))  minor为奇数时是只读防问

              return -EACCES;

 

       lock_kernel();  内核锁

       mtd = get_mtd_device(NULL, devnum);   #<2>

 

       if (IS_ERR(mtd)) {

              ret = PTR_ERR(mtd);

              goto out;

       }

 

       if (mtd->type == MTD_ABSENT) {

              put_mtd_device(mtd);

              ret = -ENODEV;

              goto out;

       }

 

       if (mtd->backing_dev_info)

              file->f_mapping->backing_dev_info = mtd->backing_dev_info;

 

       /* You can't open it RW if it's not a writeable device */

       if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {

              put_mtd_device(mtd);

              ret = -EACCES;

              goto out;

       }

 

       mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);

       if (!mfi) {

              put_mtd_device(mtd);

              ret = -ENOMEM;

              goto out;

       }

       mfi->mtd = mtd;

       file->private_data = mfi;

 

out:

       unlock_kernel();

       return ret;

} /* mtd_open */

#<1> Documentations/devices.txt有关这个的说明:

90 char    Memory Technology Device (RAM, ROM, Flash)

                0 = /dev/mtd0            First MTD (rw)

                1 = /dev/mtdr0   First MTD (ro)

                  ...

               30 = /dev/mtd15   16th MTD (rw)

               31 = /dev/mtdr15  16th MTD (ro)

所以注意像”/dev/mtd2”实际访问的是第二个FLASH分区。

#<2> get_mtd_device函数代码在mtdcore.c中,它当参数num!=-1时是从取mtdtable[num]

put_mtd_deviceget_mtd_device是成对函数,它释放对mtdinfo的使用。

 

3mtd_lseek, mtd_close很简单。

 

4mtd_read:

#define MAX_KMALLOC_SIZE 0x20000

 

static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)

{

       struct mtd_file_info *mfi = file->private_data;

       struct mtd_info *mtd = mfi->mtd;

       size_t retlen=0;

       size_t total_retlen=0;

       int ret=0;

       int len;

       char *kbuf;

 

       DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");

 

       if (*ppos + count > mtd->size)

              count = mtd->size - *ppos;

 

       if (!count)

              return 0;

 

       /* FIXME: Use kiovec in 2.5 to lock down the user's buffers

          and pass them directly to the MTD functions */

 

#<1>

 

       if (count > MAX_KMALLOC_SIZE)

              kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);

       else

              kbuf=kmalloc(count, GFP_KERNEL);

 

 

       if (!kbuf)

              return -ENOMEM;

 

       while (count) {

 

              if (count > MAX_KMALLOC_SIZE)

                     len = MAX_KMALLOC_SIZE;

              else

                     len = count;

 

              switch (mfi->mode) {                       #<2>

              case MTD_MODE_OTP_FACTORY:

                     ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf);

                     break;

              case MTD_MODE_OTP_USER:

                     ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);

                     break;

              case MTD_MODE_RAW:

              {

                     struct mtd_oob_ops ops;

 

                     ops.mode = MTD_OOB_RAW;

                     ops.datbuf = kbuf;

                     ops.oobbuf = NULL;

                     ops.len = len;

 

                     ret = mtd->read_oob(mtd, *ppos, &ops);

                     retlen = ops.retlen;

                     break;

              }

              default:

                     ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);

              }

              /* Nand returns -EBADMSG on ecc errors, but it returns

               * the data. For our userspace tools it is important

               * to dump areas with ecc errors !

               * For kernel internal usage it also might return -EUCLEAN

               * to signal the caller that a bitflip has occured and has

               * been corrected by the ECC algorithm.

               * Userspace software which accesses NAND this way

               * must be aware of the fact that it deals with NAND

               */

              if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) {

                     *ppos += retlen;

                     if (copy_to_user(buf, kbuf, retlen)) {

                            kfree(kbuf);

                            return -EFAULT;

                     }

                     else

                            total_retlen += retlen;

 

                     count -= retlen;

                     buf += retlen;

                     if (retlen == 0)

                            count = 0;

              }

              else {

                     kfree(kbuf);

                     return ret;

              }

 

       }

 

       kfree(kbuf);

       return total_retlen;

} /* mtd_read */

#<1> 这里只申请一小段内存也可以实现大量数据的访问,是一个良好的习惯,以后写驱动也应类似处理。

 

#<2> mfi->mode的值可以在mtd_ioctl中被改变。这几个宏主要是提供对一些保护数据的访问或正常区域的数据访问。下面是它们在mtdinfo结构中的原型说明:

       /*

        * Methods to access the protection register area, present in some

        * flash devices. The user data is one time programmable but the

        * factory data is read only.

        */

       int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);

       int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);

       int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);

       int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);

       int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);

       int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);

5mtd_writemtd_read代码相似。

6mtd_ioctl

常用的几个cmd说明:

cmd

arg

说明

MEMGETREGIONCOUNT

返回得到的信息

mtd->numeraseregions

MEMGETREGIONINFO

ur->regionindex指定区域序号。

返回得到的信息

返回struct region_info_user

MEMGETINFO

返回得到的信息

返回struct mtd_info_user

MEMERASE

传入struct erase_info_user

擦除区域

MEMWRITEOOB

传入的struct mtd_oob_buf信息

写入OOB数据,OOB是指FLASH中每个页的附加数据区域。

MEMREADOOB

传入的struct mtd_oob_buf信息,并写入

读取OOB数据

MEMGETBADBLOCK

测试块的地址

测试是否是坏块

MEMSETBADBLOCK

块的地址

标记是一个坏块

ECCGETLAYOUT

返回struct nand_ecclayout

获取ECC的信息

ECCGETSTATS

返回struct mtd_ecc_stats

获取ECC的状态

MTDFILEMODE

传入的读写模式:

MTD_MODE_OTP_FACTORY

MTD_MODE_OTP_USER

MTD_MODE_RAW

MTD_MODE_NORMAL

设置读写模式

 

擦除FLASH的时间可能会有点长。NOR flash的擦除时间比Nandflash时间长,可能会有一两秒,所以程序中进入了系统调度。

       case MEMERASE:

       {

              struct erase_info *erase;

 

              if(!(file->f_mode & FMODE_WRITE))

                     return -EPERM;

 

              erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);

              if (!erase)

                     ret = -ENOMEM;

              else {

                     struct erase_info_user einfo;

 

                     wait_queue_head_t waitq;

                     DECLARE_WAITQUEUE(wait, current);

 

                     init_waitqueue_head(&waitq);

 

                     if (copy_from_user(&einfo, argp,

                                sizeof(struct erase_info_user))) {

                            kfree(erase);

                            return -EFAULT;

                     }

                     erase->addr = einfo.start;

                     erase->len = einfo.length;

                     erase->mtd = mtd;

                     erase->callback = mtdchar_erase_callback;

                     erase->priv = (unsigned long)&waitq;

 

                     /*

                       FIXME: Allow INTERRUPTIBLE. Which means

                       not having the wait_queue head on the stack.

 

                       If the wq_head is on the stack, and we

                       leave because we got interrupted, then the

                       wq_head is no longer there when the

                       callback routine tries to wake us up.

                     */

                     ret = mtd->erase(mtd, erase);

                     if (!ret) {

                            set_current_state(TASK_UNINTERRUPTIBLE);

                            add_wait_queue(&waitq, &wait);

                            if (erase->state != MTD_ERASE_DONE &&

                                erase->state != MTD_ERASE_FAILED)

                                   schedule();

                            remove_wait_queue(&waitq, &wait);

                            set_current_state(TASK_RUNNING);

 

                            ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;

                     }

                     kfree(erase);

              }

              break;

       }

擦除完成后,mtd会调用erase->callback来唤醒进程:

static void mtdchar_erase_callback (struct erase_info *instr)

{

       wake_up((wait_queue_head_t *)instr->priv);

}

 

你可能感兴趣的:([linux])