| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 2 | * $Id: mtdchar.c,v 1.76 2005/11/07 11:14:20 gleixner Exp $ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 3 | * | 
|  | 4 | * Character-device access to raw MTD devices. | 
|  | 5 | * | 
|  | 6 | */ | 
|  | 7 |  | 
|  | 8 | #include <linux/config.h> | 
| Thomas Gleixner | 15fdc52 | 2005-11-07 00:14:42 +0100 | [diff] [blame] | 9 | #include <linux/device.h> | 
|  | 10 | #include <linux/fs.h> | 
|  | 11 | #include <linux/init.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 12 | #include <linux/kernel.h> | 
|  | 13 | #include <linux/module.h> | 
| Thomas Gleixner | 15fdc52 | 2005-11-07 00:14:42 +0100 | [diff] [blame] | 14 | #include <linux/slab.h> | 
|  | 15 | #include <linux/sched.h> | 
|  | 16 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 17 | #include <linux/mtd/mtd.h> | 
|  | 18 | #include <linux/mtd/compatmac.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 19 |  | 
| Thomas Gleixner | 15fdc52 | 2005-11-07 00:14:42 +0100 | [diff] [blame] | 20 | #include <asm/uaccess.h> | 
| Todd Poynor | 9bc7b38 | 2005-06-30 01:23:27 +0100 | [diff] [blame] | 21 |  | 
|  | 22 | static struct class *mtd_class; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 23 |  | 
|  | 24 | static void mtd_notify_add(struct mtd_info* mtd) | 
|  | 25 | { | 
|  | 26 | if (!mtd) | 
|  | 27 | return; | 
|  | 28 |  | 
| Greg Kroah-Hartman | 53f4654 | 2005-10-27 22:25:43 -0700 | [diff] [blame] | 29 | class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2), | 
| Todd Poynor | 9bc7b38 | 2005-06-30 01:23:27 +0100 | [diff] [blame] | 30 | NULL, "mtd%d", mtd->index); | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 31 |  | 
| Greg Kroah-Hartman | 53f4654 | 2005-10-27 22:25:43 -0700 | [diff] [blame] | 32 | class_device_create(mtd_class, NULL, | 
| Todd Poynor | 9bc7b38 | 2005-06-30 01:23:27 +0100 | [diff] [blame] | 33 | MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1), | 
|  | 34 | NULL, "mtd%dro", mtd->index); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 35 | } | 
|  | 36 |  | 
|  | 37 | static void mtd_notify_remove(struct mtd_info* mtd) | 
|  | 38 | { | 
|  | 39 | if (!mtd) | 
|  | 40 | return; | 
| Todd Poynor | 9bc7b38 | 2005-06-30 01:23:27 +0100 | [diff] [blame] | 41 |  | 
|  | 42 | class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2)); | 
|  | 43 | class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 44 | } | 
|  | 45 |  | 
|  | 46 | static struct mtd_notifier notifier = { | 
|  | 47 | .add	= mtd_notify_add, | 
|  | 48 | .remove	= mtd_notify_remove, | 
|  | 49 | }; | 
|  | 50 |  | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 51 | /* | 
|  | 52 | * We use file->private_data to store a pointer to the MTDdevice. | 
|  | 53 | * Since alighment is at least 32 bits, we have 2 bits free for OTP | 
|  | 54 | * modes as well. | 
|  | 55 | */ | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 56 |  | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 57 | #define TO_MTD(file) (struct mtd_info *)((long)((file)->private_data) & ~3L) | 
|  | 58 |  | 
|  | 59 | #define MTD_MODE_OTP_FACT	1 | 
|  | 60 | #define MTD_MODE_OTP_USER	2 | 
|  | 61 | #define MTD_MODE(file)		((long)((file)->private_data) & 3) | 
|  | 62 |  | 
|  | 63 | #define SET_MTD_MODE(file, mode) \ | 
|  | 64 | do { long __p = (long)((file)->private_data); \ | 
|  | 65 | (file)->private_data = (void *)((__p & ~3L) | mode); } while (0) | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 66 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 67 | static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) | 
|  | 68 | { | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 69 | struct mtd_info *mtd = TO_MTD(file); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 70 |  | 
|  | 71 | switch (orig) { | 
|  | 72 | case 0: | 
|  | 73 | /* SEEK_SET */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 74 | break; | 
|  | 75 | case 1: | 
|  | 76 | /* SEEK_CUR */ | 
| Todd Poynor | 8b491d7 | 2005-08-04 02:05:51 +0100 | [diff] [blame] | 77 | offset += file->f_pos; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 78 | break; | 
|  | 79 | case 2: | 
|  | 80 | /* SEEK_END */ | 
| Todd Poynor | 8b491d7 | 2005-08-04 02:05:51 +0100 | [diff] [blame] | 81 | offset += mtd->size; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 82 | break; | 
|  | 83 | default: | 
|  | 84 | return -EINVAL; | 
|  | 85 | } | 
|  | 86 |  | 
| Todd Poynor | 8b491d7 | 2005-08-04 02:05:51 +0100 | [diff] [blame] | 87 | if (offset >= 0 && offset < mtd->size) | 
|  | 88 | return file->f_pos = offset; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 89 |  | 
| Todd Poynor | 8b491d7 | 2005-08-04 02:05:51 +0100 | [diff] [blame] | 90 | return -EINVAL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 91 | } | 
|  | 92 |  | 
|  | 93 |  | 
|  | 94 |  | 
|  | 95 | static int mtd_open(struct inode *inode, struct file *file) | 
|  | 96 | { | 
|  | 97 | int minor = iminor(inode); | 
|  | 98 | int devnum = minor >> 1; | 
|  | 99 | struct mtd_info *mtd; | 
|  | 100 |  | 
|  | 101 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n"); | 
|  | 102 |  | 
|  | 103 | if (devnum >= MAX_MTD_DEVICES) | 
|  | 104 | return -ENODEV; | 
|  | 105 |  | 
|  | 106 | /* You can't open the RO devices RW */ | 
|  | 107 | if ((file->f_mode & 2) && (minor & 1)) | 
|  | 108 | return -EACCES; | 
|  | 109 |  | 
|  | 110 | mtd = get_mtd_device(NULL, devnum); | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 111 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 112 | if (!mtd) | 
|  | 113 | return -ENODEV; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 114 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 115 | if (MTD_ABSENT == mtd->type) { | 
|  | 116 | put_mtd_device(mtd); | 
|  | 117 | return -ENODEV; | 
|  | 118 | } | 
|  | 119 |  | 
|  | 120 | file->private_data = mtd; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 121 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 122 | /* You can't open it RW if it's not a writeable device */ | 
|  | 123 | if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) { | 
|  | 124 | put_mtd_device(mtd); | 
|  | 125 | return -EACCES; | 
|  | 126 | } | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 127 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 128 | return 0; | 
|  | 129 | } /* mtd_open */ | 
|  | 130 |  | 
|  | 131 | /*====================================================================*/ | 
|  | 132 |  | 
|  | 133 | static int mtd_close(struct inode *inode, struct file *file) | 
|  | 134 | { | 
|  | 135 | struct mtd_info *mtd; | 
|  | 136 |  | 
|  | 137 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); | 
|  | 138 |  | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 139 | mtd = TO_MTD(file); | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 140 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 141 | if (mtd->sync) | 
|  | 142 | mtd->sync(mtd); | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 143 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 144 | put_mtd_device(mtd); | 
|  | 145 |  | 
|  | 146 | return 0; | 
|  | 147 | } /* mtd_close */ | 
|  | 148 |  | 
|  | 149 | /* FIXME: This _really_ needs to die. In 2.5, we should lock the | 
|  | 150 | userspace buffer down and use it directly with readv/writev. | 
|  | 151 | */ | 
|  | 152 | #define MAX_KMALLOC_SIZE 0x20000 | 
|  | 153 |  | 
|  | 154 | static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) | 
|  | 155 | { | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 156 | struct mtd_info *mtd = TO_MTD(file); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 157 | size_t retlen=0; | 
|  | 158 | size_t total_retlen=0; | 
|  | 159 | int ret=0; | 
|  | 160 | int len; | 
|  | 161 | char *kbuf; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 162 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 163 | DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n"); | 
|  | 164 |  | 
|  | 165 | if (*ppos + count > mtd->size) | 
|  | 166 | count = mtd->size - *ppos; | 
|  | 167 |  | 
|  | 168 | if (!count) | 
|  | 169 | return 0; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 170 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 171 | /* FIXME: Use kiovec in 2.5 to lock down the user's buffers | 
|  | 172 | and pass them directly to the MTD functions */ | 
|  | 173 | while (count) { | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 174 | if (count > MAX_KMALLOC_SIZE) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 175 | len = MAX_KMALLOC_SIZE; | 
|  | 176 | else | 
|  | 177 | len = count; | 
|  | 178 |  | 
|  | 179 | kbuf=kmalloc(len,GFP_KERNEL); | 
|  | 180 | if (!kbuf) | 
|  | 181 | return -ENOMEM; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 182 |  | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 183 | switch (MTD_MODE(file)) { | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 184 | case MTD_MODE_OTP_FACT: | 
|  | 185 | ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf); | 
|  | 186 | break; | 
|  | 187 | case MTD_MODE_OTP_USER: | 
|  | 188 | ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); | 
|  | 189 | break; | 
|  | 190 | default: | 
|  | 191 | ret = MTD_READ(mtd, *ppos, len, &retlen, kbuf); | 
|  | 192 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 193 | /* Nand returns -EBADMSG on ecc errors, but it returns | 
|  | 194 | * the data. For our userspace tools it is important | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 195 | * to dump areas with ecc errors ! | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 196 | * Userspace software which accesses NAND this way | 
|  | 197 | * must be aware of the fact that it deals with NAND | 
|  | 198 | */ | 
|  | 199 | if (!ret || (ret == -EBADMSG)) { | 
|  | 200 | *ppos += retlen; | 
|  | 201 | if (copy_to_user(buf, kbuf, retlen)) { | 
|  | 202 | kfree(kbuf); | 
|  | 203 | return -EFAULT; | 
|  | 204 | } | 
|  | 205 | else | 
|  | 206 | total_retlen += retlen; | 
|  | 207 |  | 
|  | 208 | count -= retlen; | 
|  | 209 | buf += retlen; | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 210 | if (retlen == 0) | 
|  | 211 | count = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 212 | } | 
|  | 213 | else { | 
|  | 214 | kfree(kbuf); | 
|  | 215 | return ret; | 
|  | 216 | } | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 217 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 218 | kfree(kbuf); | 
|  | 219 | } | 
|  | 220 |  | 
|  | 221 | return total_retlen; | 
|  | 222 | } /* mtd_read */ | 
|  | 223 |  | 
|  | 224 | static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) | 
|  | 225 | { | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 226 | struct mtd_info *mtd = TO_MTD(file); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 227 | char *kbuf; | 
|  | 228 | size_t retlen; | 
|  | 229 | size_t total_retlen=0; | 
|  | 230 | int ret=0; | 
|  | 231 | int len; | 
|  | 232 |  | 
|  | 233 | DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n"); | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 234 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 235 | if (*ppos == mtd->size) | 
|  | 236 | return -ENOSPC; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 237 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 238 | if (*ppos + count > mtd->size) | 
|  | 239 | count = mtd->size - *ppos; | 
|  | 240 |  | 
|  | 241 | if (!count) | 
|  | 242 | return 0; | 
|  | 243 |  | 
|  | 244 | while (count) { | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 245 | if (count > MAX_KMALLOC_SIZE) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 246 | len = MAX_KMALLOC_SIZE; | 
|  | 247 | else | 
|  | 248 | len = count; | 
|  | 249 |  | 
|  | 250 | kbuf=kmalloc(len,GFP_KERNEL); | 
|  | 251 | if (!kbuf) { | 
|  | 252 | printk("kmalloc is null\n"); | 
|  | 253 | return -ENOMEM; | 
|  | 254 | } | 
|  | 255 |  | 
|  | 256 | if (copy_from_user(kbuf, buf, len)) { | 
|  | 257 | kfree(kbuf); | 
|  | 258 | return -EFAULT; | 
|  | 259 | } | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 260 |  | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 261 | switch (MTD_MODE(file)) { | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 262 | case MTD_MODE_OTP_FACT: | 
|  | 263 | ret = -EROFS; | 
|  | 264 | break; | 
|  | 265 | case MTD_MODE_OTP_USER: | 
|  | 266 | if (!mtd->write_user_prot_reg) { | 
|  | 267 | ret = -EOPNOTSUPP; | 
|  | 268 | break; | 
|  | 269 | } | 
|  | 270 | ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); | 
|  | 271 | break; | 
|  | 272 | default: | 
|  | 273 | ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); | 
|  | 274 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 275 | if (!ret) { | 
|  | 276 | *ppos += retlen; | 
|  | 277 | total_retlen += retlen; | 
|  | 278 | count -= retlen; | 
|  | 279 | buf += retlen; | 
|  | 280 | } | 
|  | 281 | else { | 
|  | 282 | kfree(kbuf); | 
|  | 283 | return ret; | 
|  | 284 | } | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 285 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 286 | kfree(kbuf); | 
|  | 287 | } | 
|  | 288 |  | 
|  | 289 | return total_retlen; | 
|  | 290 | } /* mtd_write */ | 
|  | 291 |  | 
|  | 292 | /*====================================================================== | 
|  | 293 |  | 
|  | 294 | IOCTL calls for getting device parameters. | 
|  | 295 |  | 
|  | 296 | ======================================================================*/ | 
|  | 297 | static void mtdchar_erase_callback (struct erase_info *instr) | 
|  | 298 | { | 
|  | 299 | wake_up((wait_queue_head_t *)instr->priv); | 
|  | 300 | } | 
|  | 301 |  | 
|  | 302 | static int mtd_ioctl(struct inode *inode, struct file *file, | 
|  | 303 | u_int cmd, u_long arg) | 
|  | 304 | { | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 305 | struct mtd_info *mtd = TO_MTD(file); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 306 | void __user *argp = (void __user *)arg; | 
|  | 307 | int ret = 0; | 
|  | 308 | u_long size; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 309 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 310 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n"); | 
|  | 311 |  | 
|  | 312 | size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; | 
|  | 313 | if (cmd & IOC_IN) { | 
|  | 314 | if (!access_ok(VERIFY_READ, argp, size)) | 
|  | 315 | return -EFAULT; | 
|  | 316 | } | 
|  | 317 | if (cmd & IOC_OUT) { | 
|  | 318 | if (!access_ok(VERIFY_WRITE, argp, size)) | 
|  | 319 | return -EFAULT; | 
|  | 320 | } | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 321 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 322 | switch (cmd) { | 
|  | 323 | case MEMGETREGIONCOUNT: | 
|  | 324 | if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int))) | 
|  | 325 | return -EFAULT; | 
|  | 326 | break; | 
|  | 327 |  | 
|  | 328 | case MEMGETREGIONINFO: | 
|  | 329 | { | 
|  | 330 | struct region_info_user ur; | 
|  | 331 |  | 
|  | 332 | if (copy_from_user(&ur, argp, sizeof(struct region_info_user))) | 
|  | 333 | return -EFAULT; | 
|  | 334 |  | 
|  | 335 | if (ur.regionindex >= mtd->numeraseregions) | 
|  | 336 | return -EINVAL; | 
|  | 337 | if (copy_to_user(argp, &(mtd->eraseregions[ur.regionindex]), | 
|  | 338 | sizeof(struct mtd_erase_region_info))) | 
|  | 339 | return -EFAULT; | 
|  | 340 | break; | 
|  | 341 | } | 
|  | 342 |  | 
|  | 343 | case MEMGETINFO: | 
|  | 344 | if (copy_to_user(argp, mtd, sizeof(struct mtd_info_user))) | 
|  | 345 | return -EFAULT; | 
|  | 346 | break; | 
|  | 347 |  | 
|  | 348 | case MEMERASE: | 
|  | 349 | { | 
|  | 350 | struct erase_info *erase; | 
|  | 351 |  | 
|  | 352 | if(!(file->f_mode & 2)) | 
|  | 353 | return -EPERM; | 
|  | 354 |  | 
|  | 355 | erase=kmalloc(sizeof(struct erase_info),GFP_KERNEL); | 
|  | 356 | if (!erase) | 
|  | 357 | ret = -ENOMEM; | 
|  | 358 | else { | 
|  | 359 | wait_queue_head_t waitq; | 
|  | 360 | DECLARE_WAITQUEUE(wait, current); | 
|  | 361 |  | 
|  | 362 | init_waitqueue_head(&waitq); | 
|  | 363 |  | 
|  | 364 | memset (erase,0,sizeof(struct erase_info)); | 
|  | 365 | if (copy_from_user(&erase->addr, argp, | 
|  | 366 | sizeof(struct erase_info_user))) { | 
|  | 367 | kfree(erase); | 
|  | 368 | return -EFAULT; | 
|  | 369 | } | 
|  | 370 | erase->mtd = mtd; | 
|  | 371 | erase->callback = mtdchar_erase_callback; | 
|  | 372 | erase->priv = (unsigned long)&waitq; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 373 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 374 | /* | 
|  | 375 | FIXME: Allow INTERRUPTIBLE. Which means | 
|  | 376 | not having the wait_queue head on the stack. | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 377 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 378 | If the wq_head is on the stack, and we | 
|  | 379 | leave because we got interrupted, then the | 
|  | 380 | wq_head is no longer there when the | 
|  | 381 | callback routine tries to wake us up. | 
|  | 382 | */ | 
|  | 383 | ret = mtd->erase(mtd, erase); | 
|  | 384 | if (!ret) { | 
|  | 385 | set_current_state(TASK_UNINTERRUPTIBLE); | 
|  | 386 | add_wait_queue(&waitq, &wait); | 
|  | 387 | if (erase->state != MTD_ERASE_DONE && | 
|  | 388 | erase->state != MTD_ERASE_FAILED) | 
|  | 389 | schedule(); | 
|  | 390 | remove_wait_queue(&waitq, &wait); | 
|  | 391 | set_current_state(TASK_RUNNING); | 
|  | 392 |  | 
|  | 393 | ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0; | 
|  | 394 | } | 
|  | 395 | kfree(erase); | 
|  | 396 | } | 
|  | 397 | break; | 
|  | 398 | } | 
|  | 399 |  | 
|  | 400 | case MEMWRITEOOB: | 
|  | 401 | { | 
|  | 402 | struct mtd_oob_buf buf; | 
|  | 403 | void *databuf; | 
|  | 404 | ssize_t retlen; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 405 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 406 | if(!(file->f_mode & 2)) | 
|  | 407 | return -EPERM; | 
|  | 408 |  | 
|  | 409 | if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) | 
|  | 410 | return -EFAULT; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 411 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 412 | if (buf.length > 0x4096) | 
|  | 413 | return -EINVAL; | 
|  | 414 |  | 
|  | 415 | if (!mtd->write_oob) | 
|  | 416 | ret = -EOPNOTSUPP; | 
|  | 417 | else | 
|  | 418 | ret = access_ok(VERIFY_READ, buf.ptr, | 
|  | 419 | buf.length) ? 0 : EFAULT; | 
|  | 420 |  | 
|  | 421 | if (ret) | 
|  | 422 | return ret; | 
|  | 423 |  | 
|  | 424 | databuf = kmalloc(buf.length, GFP_KERNEL); | 
|  | 425 | if (!databuf) | 
|  | 426 | return -ENOMEM; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 427 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 428 | if (copy_from_user(databuf, buf.ptr, buf.length)) { | 
|  | 429 | kfree(databuf); | 
|  | 430 | return -EFAULT; | 
|  | 431 | } | 
|  | 432 |  | 
|  | 433 | ret = (mtd->write_oob)(mtd, buf.start, buf.length, &retlen, databuf); | 
|  | 434 |  | 
|  | 435 | if (copy_to_user(argp + sizeof(uint32_t), &retlen, sizeof(uint32_t))) | 
|  | 436 | ret = -EFAULT; | 
|  | 437 |  | 
|  | 438 | kfree(databuf); | 
|  | 439 | break; | 
|  | 440 |  | 
|  | 441 | } | 
|  | 442 |  | 
|  | 443 | case MEMREADOOB: | 
|  | 444 | { | 
|  | 445 | struct mtd_oob_buf buf; | 
|  | 446 | void *databuf; | 
|  | 447 | ssize_t retlen; | 
|  | 448 |  | 
|  | 449 | if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) | 
|  | 450 | return -EFAULT; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 451 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 452 | if (buf.length > 0x4096) | 
|  | 453 | return -EINVAL; | 
|  | 454 |  | 
|  | 455 | if (!mtd->read_oob) | 
|  | 456 | ret = -EOPNOTSUPP; | 
|  | 457 | else | 
|  | 458 | ret = access_ok(VERIFY_WRITE, buf.ptr, | 
|  | 459 | buf.length) ? 0 : -EFAULT; | 
|  | 460 |  | 
|  | 461 | if (ret) | 
|  | 462 | return ret; | 
|  | 463 |  | 
|  | 464 | databuf = kmalloc(buf.length, GFP_KERNEL); | 
|  | 465 | if (!databuf) | 
|  | 466 | return -ENOMEM; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 467 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 468 | ret = (mtd->read_oob)(mtd, buf.start, buf.length, &retlen, databuf); | 
|  | 469 |  | 
|  | 470 | if (put_user(retlen, (uint32_t __user *)argp)) | 
|  | 471 | ret = -EFAULT; | 
|  | 472 | else if (retlen && copy_to_user(buf.ptr, databuf, retlen)) | 
|  | 473 | ret = -EFAULT; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 474 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 475 | kfree(databuf); | 
|  | 476 | break; | 
|  | 477 | } | 
|  | 478 |  | 
|  | 479 | case MEMLOCK: | 
|  | 480 | { | 
|  | 481 | struct erase_info_user info; | 
|  | 482 |  | 
|  | 483 | if (copy_from_user(&info, argp, sizeof(info))) | 
|  | 484 | return -EFAULT; | 
|  | 485 |  | 
|  | 486 | if (!mtd->lock) | 
|  | 487 | ret = -EOPNOTSUPP; | 
|  | 488 | else | 
|  | 489 | ret = mtd->lock(mtd, info.start, info.length); | 
|  | 490 | break; | 
|  | 491 | } | 
|  | 492 |  | 
|  | 493 | case MEMUNLOCK: | 
|  | 494 | { | 
|  | 495 | struct erase_info_user info; | 
|  | 496 |  | 
|  | 497 | if (copy_from_user(&info, argp, sizeof(info))) | 
|  | 498 | return -EFAULT; | 
|  | 499 |  | 
|  | 500 | if (!mtd->unlock) | 
|  | 501 | ret = -EOPNOTSUPP; | 
|  | 502 | else | 
|  | 503 | ret = mtd->unlock(mtd, info.start, info.length); | 
|  | 504 | break; | 
|  | 505 | } | 
|  | 506 |  | 
|  | 507 | case MEMSETOOBSEL: | 
|  | 508 | { | 
|  | 509 | if (copy_from_user(&mtd->oobinfo, argp, sizeof(struct nand_oobinfo))) | 
|  | 510 | return -EFAULT; | 
|  | 511 | break; | 
|  | 512 | } | 
|  | 513 |  | 
|  | 514 | case MEMGETOOBSEL: | 
|  | 515 | { | 
|  | 516 | if (copy_to_user(argp, &(mtd->oobinfo), sizeof(struct nand_oobinfo))) | 
|  | 517 | return -EFAULT; | 
|  | 518 | break; | 
|  | 519 | } | 
|  | 520 |  | 
|  | 521 | case MEMGETBADBLOCK: | 
|  | 522 | { | 
|  | 523 | loff_t offs; | 
| Thomas Gleixner | 97894cd | 2005-11-07 11:15:26 +0000 | [diff] [blame] | 524 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 525 | if (copy_from_user(&offs, argp, sizeof(loff_t))) | 
|  | 526 | return -EFAULT; | 
|  | 527 | if (!mtd->block_isbad) | 
|  | 528 | ret = -EOPNOTSUPP; | 
|  | 529 | else | 
|  | 530 | return mtd->block_isbad(mtd, offs); | 
|  | 531 | break; | 
|  | 532 | } | 
|  | 533 |  | 
|  | 534 | case MEMSETBADBLOCK: | 
|  | 535 | { | 
|  | 536 | loff_t offs; | 
|  | 537 |  | 
|  | 538 | if (copy_from_user(&offs, argp, sizeof(loff_t))) | 
|  | 539 | return -EFAULT; | 
|  | 540 | if (!mtd->block_markbad) | 
|  | 541 | ret = -EOPNOTSUPP; | 
|  | 542 | else | 
|  | 543 | return mtd->block_markbad(mtd, offs); | 
|  | 544 | break; | 
|  | 545 | } | 
|  | 546 |  | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 547 | #ifdef CONFIG_MTD_OTP | 
|  | 548 | case OTPSELECT: | 
|  | 549 | { | 
|  | 550 | int mode; | 
|  | 551 | if (copy_from_user(&mode, argp, sizeof(int))) | 
|  | 552 | return -EFAULT; | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 553 | SET_MTD_MODE(file, 0); | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 554 | switch (mode) { | 
|  | 555 | case MTD_OTP_FACTORY: | 
|  | 556 | if (!mtd->read_fact_prot_reg) | 
|  | 557 | ret = -EOPNOTSUPP; | 
|  | 558 | else | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 559 | SET_MTD_MODE(file, MTD_MODE_OTP_FACT); | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 560 | break; | 
|  | 561 | case MTD_OTP_USER: | 
|  | 562 | if (!mtd->read_fact_prot_reg) | 
|  | 563 | ret = -EOPNOTSUPP; | 
|  | 564 | else | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 565 | SET_MTD_MODE(file, MTD_MODE_OTP_USER); | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 566 | break; | 
|  | 567 | default: | 
|  | 568 | ret = -EINVAL; | 
|  | 569 | case MTD_OTP_OFF: | 
|  | 570 | break; | 
|  | 571 | } | 
| Nicolas Pitre | 81dba48 | 2005-04-01 16:36:15 +0100 | [diff] [blame] | 572 | file->f_pos = 0; | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 573 | break; | 
|  | 574 | } | 
|  | 575 |  | 
|  | 576 | case OTPGETREGIONCOUNT: | 
|  | 577 | case OTPGETREGIONINFO: | 
|  | 578 | { | 
|  | 579 | struct otp_info *buf = kmalloc(4096, GFP_KERNEL); | 
|  | 580 | if (!buf) | 
|  | 581 | return -ENOMEM; | 
|  | 582 | ret = -EOPNOTSUPP; | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 583 | switch (MTD_MODE(file)) { | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 584 | case MTD_MODE_OTP_FACT: | 
|  | 585 | if (mtd->get_fact_prot_info) | 
|  | 586 | ret = mtd->get_fact_prot_info(mtd, buf, 4096); | 
|  | 587 | break; | 
|  | 588 | case MTD_MODE_OTP_USER: | 
|  | 589 | if (mtd->get_user_prot_info) | 
|  | 590 | ret = mtd->get_user_prot_info(mtd, buf, 4096); | 
|  | 591 | break; | 
|  | 592 | } | 
|  | 593 | if (ret >= 0) { | 
|  | 594 | if (cmd == OTPGETREGIONCOUNT) { | 
|  | 595 | int nbr = ret / sizeof(struct otp_info); | 
|  | 596 | ret = copy_to_user(argp, &nbr, sizeof(int)); | 
|  | 597 | } else | 
|  | 598 | ret = copy_to_user(argp, buf, ret); | 
|  | 599 | if (ret) | 
|  | 600 | ret = -EFAULT; | 
|  | 601 | } | 
|  | 602 | kfree(buf); | 
|  | 603 | break; | 
|  | 604 | } | 
|  | 605 |  | 
|  | 606 | case OTPLOCK: | 
|  | 607 | { | 
|  | 608 | struct otp_info info; | 
|  | 609 |  | 
| Nicolas Pitre | 045e9a5 | 2005-02-08 19:12:53 +0000 | [diff] [blame] | 610 | if (MTD_MODE(file) != MTD_MODE_OTP_USER) | 
| Nicolas Pitre | 31f4233 | 2005-02-08 17:45:55 +0000 | [diff] [blame] | 611 | return -EINVAL; | 
|  | 612 | if (copy_from_user(&info, argp, sizeof(info))) | 
|  | 613 | return -EFAULT; | 
|  | 614 | if (!mtd->lock_user_prot_reg) | 
|  | 615 | return -EOPNOTSUPP; | 
|  | 616 | ret = mtd->lock_user_prot_reg(mtd, info.start, info.length); | 
|  | 617 | break; | 
|  | 618 | } | 
|  | 619 | #endif | 
|  | 620 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 621 | default: | 
|  | 622 | ret = -ENOTTY; | 
|  | 623 | } | 
|  | 624 |  | 
|  | 625 | return ret; | 
|  | 626 | } /* memory_ioctl */ | 
|  | 627 |  | 
|  | 628 | static struct file_operations mtd_fops = { | 
|  | 629 | .owner		= THIS_MODULE, | 
|  | 630 | .llseek		= mtd_lseek, | 
|  | 631 | .read		= mtd_read, | 
|  | 632 | .write		= mtd_write, | 
|  | 633 | .ioctl		= mtd_ioctl, | 
|  | 634 | .open		= mtd_open, | 
|  | 635 | .release	= mtd_close, | 
|  | 636 | }; | 
|  | 637 |  | 
|  | 638 | static int __init init_mtdchar(void) | 
|  | 639 | { | 
|  | 640 | if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) { | 
|  | 641 | printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", | 
|  | 642 | MTD_CHAR_MAJOR); | 
|  | 643 | return -EAGAIN; | 
|  | 644 | } | 
|  | 645 |  | 
| Todd Poynor | 9bc7b38 | 2005-06-30 01:23:27 +0100 | [diff] [blame] | 646 | mtd_class = class_create(THIS_MODULE, "mtd"); | 
|  | 647 |  | 
|  | 648 | if (IS_ERR(mtd_class)) { | 
|  | 649 | printk(KERN_ERR "Error creating mtd class.\n"); | 
|  | 650 | unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); | 
| Coywolf Qi Hunt | 3a7a882 | 2005-07-04 12:15:28 -0500 | [diff] [blame] | 651 | return PTR_ERR(mtd_class); | 
| Todd Poynor | 9bc7b38 | 2005-06-30 01:23:27 +0100 | [diff] [blame] | 652 | } | 
|  | 653 |  | 
|  | 654 | register_mtd_user(¬ifier); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 655 | return 0; | 
|  | 656 | } | 
|  | 657 |  | 
|  | 658 | static void __exit cleanup_mtdchar(void) | 
|  | 659 | { | 
| Todd Poynor | 9bc7b38 | 2005-06-30 01:23:27 +0100 | [diff] [blame] | 660 | unregister_mtd_user(¬ifier); | 
|  | 661 | class_destroy(mtd_class); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 662 | unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); | 
|  | 663 | } | 
|  | 664 |  | 
|  | 665 | module_init(init_mtdchar); | 
|  | 666 | module_exit(cleanup_mtdchar); | 
|  | 667 |  | 
|  | 668 |  | 
|  | 669 | MODULE_LICENSE("GPL"); | 
|  | 670 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | 
|  | 671 | MODULE_DESCRIPTION("Direct character-device access to MTD devices"); |