Blame view

drivers/mtd/mtdblock.c 9.22 KB
97894cda5   Thomas Gleixner   [MTD] core: Clean...
1
  /*
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
2
3
   * Direct MTD block device access
   *
97894cda5   Thomas Gleixner   [MTD] core: Clean...
4
   * $Id: mtdblock.c,v 1.68 2005/11/07 11:14:20 gleixner Exp $
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
5
6
7
8
   *
   * (C) 2000-2003 Nicolas Pitre <nico@cam.org>
   * (C) 1999-2003 David Woodhouse <dwmw2@infradead.org>
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
9
10
  #include <linux/fs.h>
  #include <linux/init.h>
15fdc52f3   Thomas Gleixner   [MTD] Tidy up Tim...
11
12
13
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/sched.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
14
  #include <linux/slab.h>
15fdc52f3   Thomas Gleixner   [MTD] Tidy up Tim...
15
  #include <linux/types.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
16
  #include <linux/vmalloc.h>
15fdc52f3   Thomas Gleixner   [MTD] Tidy up Tim...
17

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
18
19
  #include <linux/mtd/mtd.h>
  #include <linux/mtd/blktrans.h>
48b192686   Ingo Molnar   [PATCH] sem2mutex...
20
  #include <linux/mutex.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
21
22
23
24
  
  static struct mtdblk_dev {
  	struct mtd_info *mtd;
  	int count;
48b192686   Ingo Molnar   [PATCH] sem2mutex...
25
  	struct mutex cache_mutex;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
26
27
28
29
30
31
32
33
  	unsigned char *cache_data;
  	unsigned long cache_offset;
  	unsigned int cache_size;
  	enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;
  } *mtdblks[MAX_MTD_DEVICES];
  
  /*
   * Cache stuff...
97894cda5   Thomas Gleixner   [MTD] core: Clean...
34
   *
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
35
36
37
38
39
40
41
42
43
44
45
46
   * Since typical flash erasable sectors are much larger than what Linux's
   * buffer cache can handle, we must implement read-modify-write on flash
   * sectors for each block write requests.  To avoid over-erasing flash sectors
   * and to speed things up, we locally cache a whole flash sector while it is
   * being written to until a different sector is required.
   */
  
  static void erase_callback(struct erase_info *done)
  {
  	wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
  	wake_up(wait_q);
  }
97894cda5   Thomas Gleixner   [MTD] core: Clean...
47
  static int erase_write (struct mtd_info *mtd, unsigned long pos,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
  			int len, const char *buf)
  {
  	struct erase_info erase;
  	DECLARE_WAITQUEUE(wait, current);
  	wait_queue_head_t wait_q;
  	size_t retlen;
  	int ret;
  
  	/*
  	 * First, let's erase the flash block.
  	 */
  
  	init_waitqueue_head(&wait_q);
  	erase.mtd = mtd;
  	erase.callback = erase_callback;
  	erase.addr = pos;
  	erase.len = len;
  	erase.priv = (u_long)&wait_q;
  
  	set_current_state(TASK_INTERRUPTIBLE);
  	add_wait_queue(&wait_q, &wait);
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
69
  	ret = mtd->erase(mtd, &erase);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
  	if (ret) {
  		set_current_state(TASK_RUNNING);
  		remove_wait_queue(&wait_q, &wait);
  		printk (KERN_WARNING "mtdblock: erase of region [0x%lx, 0x%x] "
  				     "on \"%s\" failed
  ",
  			pos, len, mtd->name);
  		return ret;
  	}
  
  	schedule();  /* Wait for erase to finish. */
  	remove_wait_queue(&wait_q, &wait);
  
  	/*
  	 * Next, writhe data to flash.
  	 */
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
86
  	ret = mtd->write(mtd, pos, len, &retlen, buf);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
  	if (ret)
  		return ret;
  	if (retlen != len)
  		return -EIO;
  	return 0;
  }
  
  
  static int write_cached_data (struct mtdblk_dev *mtdblk)
  {
  	struct mtd_info *mtd = mtdblk->mtd;
  	int ret;
  
  	if (mtdblk->cache_state != STATE_DIRTY)
  		return 0;
  
  	DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for \"%s\" "
97894cda5   Thomas Gleixner   [MTD] core: Clean...
104
105
  			"at 0x%lx, size 0x%x
  ", mtd->name,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
106
  			mtdblk->cache_offset, mtdblk->cache_size);
97894cda5   Thomas Gleixner   [MTD] core: Clean...
107
108
  
  	ret = erase_write (mtd, mtdblk->cache_offset,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
109
110
111
112
113
114
  			   mtdblk->cache_size, mtdblk->cache_data);
  	if (ret)
  		return ret;
  
  	/*
  	 * Here we could argubly set the cache state to STATE_CLEAN.
97894cda5   Thomas Gleixner   [MTD] core: Clean...
115
116
  	 * However this could lead to inconsistency since we will not
  	 * be notified if this content is altered on the flash by other
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
117
118
119
120
121
122
  	 * means.  Let's declare it empty and leave buffering tasks to
  	 * the buffer cache instead.
  	 */
  	mtdblk->cache_state = STATE_EMPTY;
  	return 0;
  }
97894cda5   Thomas Gleixner   [MTD] core: Clean...
123
  static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
124
125
126
127
128
129
130
131
132
133
  			    int len, const char *buf)
  {
  	struct mtd_info *mtd = mtdblk->mtd;
  	unsigned int sect_size = mtdblk->cache_size;
  	size_t retlen;
  	int ret;
  
  	DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: write on \"%s\" at 0x%lx, size 0x%x
  ",
  		mtd->name, pos, len);
97894cda5   Thomas Gleixner   [MTD] core: Clean...
134

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
135
  	if (!sect_size)
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
136
  		return mtd->write(mtd, pos, len, &retlen, buf);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
137
138
139
140
141
  
  	while (len > 0) {
  		unsigned long sect_start = (pos/sect_size)*sect_size;
  		unsigned int offset = pos - sect_start;
  		unsigned int size = sect_size - offset;
97894cda5   Thomas Gleixner   [MTD] core: Clean...
142
  		if( size > len )
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
143
144
145
  			size = len;
  
  		if (size == sect_size) {
97894cda5   Thomas Gleixner   [MTD] core: Clean...
146
  			/*
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
147
148
149
150
151
152
153
154
155
156
157
158
159
  			 * We are covering a whole sector.  Thus there is no
  			 * need to bother with the cache while it may still be
  			 * useful for other partial writes.
  			 */
  			ret = erase_write (mtd, pos, size, buf);
  			if (ret)
  				return ret;
  		} else {
  			/* Partial sector: need to use the cache */
  
  			if (mtdblk->cache_state == STATE_DIRTY &&
  			    mtdblk->cache_offset != sect_start) {
  				ret = write_cached_data(mtdblk);
97894cda5   Thomas Gleixner   [MTD] core: Clean...
160
  				if (ret)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
161
162
163
164
165
166
167
  					return ret;
  			}
  
  			if (mtdblk->cache_state == STATE_EMPTY ||
  			    mtdblk->cache_offset != sect_start) {
  				/* fill the cache with the current sector */
  				mtdblk->cache_state = STATE_EMPTY;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
168
169
  				ret = mtd->read(mtd, sect_start, sect_size,
  						&retlen, mtdblk->cache_data);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  				if (ret)
  					return ret;
  				if (retlen != sect_size)
  					return -EIO;
  
  				mtdblk->cache_offset = sect_start;
  				mtdblk->cache_size = sect_size;
  				mtdblk->cache_state = STATE_CLEAN;
  			}
  
  			/* write data to our local cache */
  			memcpy (mtdblk->cache_data + offset, buf, size);
  			mtdblk->cache_state = STATE_DIRTY;
  		}
  
  		buf += size;
  		pos += size;
  		len -= size;
  	}
  
  	return 0;
  }
97894cda5   Thomas Gleixner   [MTD] core: Clean...
192
  static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
193
194
195
196
197
198
  			   int len, char *buf)
  {
  	struct mtd_info *mtd = mtdblk->mtd;
  	unsigned int sect_size = mtdblk->cache_size;
  	size_t retlen;
  	int ret;
97894cda5   Thomas Gleixner   [MTD] core: Clean...
199
200
  	DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: read on \"%s\" at 0x%lx, size 0x%x
  ",
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
201
  			mtd->name, pos, len);
97894cda5   Thomas Gleixner   [MTD] core: Clean...
202

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
203
  	if (!sect_size)
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
204
  		return mtd->read(mtd, pos, len, &retlen, buf);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
205
206
207
208
209
  
  	while (len > 0) {
  		unsigned long sect_start = (pos/sect_size)*sect_size;
  		unsigned int offset = pos - sect_start;
  		unsigned int size = sect_size - offset;
97894cda5   Thomas Gleixner   [MTD] core: Clean...
210
  		if (size > len)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
211
212
213
214
215
216
217
218
219
220
221
222
  			size = len;
  
  		/*
  		 * Check if the requested data is already cached
  		 * Read the requested amount of data from our internal cache if it
  		 * contains what we want, otherwise we read the data directly
  		 * from flash.
  		 */
  		if (mtdblk->cache_state != STATE_EMPTY &&
  		    mtdblk->cache_offset == sect_start) {
  			memcpy (buf, mtdblk->cache_data + offset, size);
  		} else {
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
223
  			ret = mtd->read(mtd, pos, size, &retlen, buf);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
  			if (ret)
  				return ret;
  			if (retlen != size)
  				return -EIO;
  		}
  
  		buf += size;
  		pos += size;
  		len -= size;
  	}
  
  	return 0;
  }
  
  static int mtdblock_readsect(struct mtd_blktrans_dev *dev,
  			      unsigned long block, char *buf)
  {
  	struct mtdblk_dev *mtdblk = mtdblks[dev->devnum];
  	return do_cached_read(mtdblk, block<<9, 512, buf);
  }
  
  static int mtdblock_writesect(struct mtd_blktrans_dev *dev,
  			      unsigned long block, char *buf)
  {
  	struct mtdblk_dev *mtdblk = mtdblks[dev->devnum];
  	if (unlikely(!mtdblk->cache_data && mtdblk->cache_size)) {
  		mtdblk->cache_data = vmalloc(mtdblk->mtd->erasesize);
  		if (!mtdblk->cache_data)
  			return -EINTR;
  		/* -EINTR is not really correct, but it is the best match
  		 * documented in man 2 write for all cases.  We could also
  		 * return -EAGAIN sometimes, but why bother?
  		 */
  	}
  	return do_cached_write(mtdblk, block<<9, 512, buf);
  }
  
  static int mtdblock_open(struct mtd_blktrans_dev *mbd)
  {
  	struct mtdblk_dev *mtdblk;
  	struct mtd_info *mtd = mbd->mtd;
  	int dev = mbd->devnum;
  
  	DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open
  ");
97894cda5   Thomas Gleixner   [MTD] core: Clean...
269

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
270
271
272
273
  	if (mtdblks[dev]) {
  		mtdblks[dev]->count++;
  		return 0;
  	}
97894cda5   Thomas Gleixner   [MTD] core: Clean...
274

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
275
  	/* OK, it's not open. Create cache info for it */
95b93a0cd   Burman Yan   [MTD] replace kma...
276
  	mtdblk = kzalloc(sizeof(struct mtdblk_dev), GFP_KERNEL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
277
278
  	if (!mtdblk)
  		return -ENOMEM;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
279
280
  	mtdblk->count = 1;
  	mtdblk->mtd = mtd;
48b192686   Ingo Molnar   [PATCH] sem2mutex...
281
  	mutex_init(&mtdblk->cache_mutex);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
282
  	mtdblk->cache_state = STATE_EMPTY;
92cbfdcc3   Joern Engel   [MTD] replace MTD...
283
  	if ( !(mtdblk->mtd->flags & MTD_NO_ERASE) && mtdblk->mtd->erasesize) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
284
285
286
287
288
  		mtdblk->cache_size = mtdblk->mtd->erasesize;
  		mtdblk->cache_data = NULL;
  	}
  
  	mtdblks[dev] = mtdblk;
97894cda5   Thomas Gleixner   [MTD] core: Clean...
289

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
290
291
292
293
294
295
296
297
298
299
300
301
302
  	DEBUG(MTD_DEBUG_LEVEL1, "ok
  ");
  
  	return 0;
  }
  
  static int mtdblock_release(struct mtd_blktrans_dev *mbd)
  {
  	int dev = mbd->devnum;
  	struct mtdblk_dev *mtdblk = mtdblks[dev];
  
     	DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release
  ");
48b192686   Ingo Molnar   [PATCH] sem2mutex...
303
  	mutex_lock(&mtdblk->cache_mutex);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
304
  	write_cached_data(mtdblk);
48b192686   Ingo Molnar   [PATCH] sem2mutex...
305
  	mutex_unlock(&mtdblk->cache_mutex);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
306
307
308
309
310
311
312
313
314
315
316
317
318
  
  	if (!--mtdblk->count) {
  		/* It was the last usage. Free the device */
  		mtdblks[dev] = NULL;
  		if (mtdblk->mtd->sync)
  			mtdblk->mtd->sync(mtdblk->mtd);
  		vfree(mtdblk->cache_data);
  		kfree(mtdblk);
  	}
  	DEBUG(MTD_DEBUG_LEVEL1, "ok
  ");
  
  	return 0;
97894cda5   Thomas Gleixner   [MTD] core: Clean...
319
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
320
321
322
323
  
  static int mtdblock_flush(struct mtd_blktrans_dev *dev)
  {
  	struct mtdblk_dev *mtdblk = mtdblks[dev->devnum];
48b192686   Ingo Molnar   [PATCH] sem2mutex...
324
  	mutex_lock(&mtdblk->cache_mutex);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
325
  	write_cached_data(mtdblk);
48b192686   Ingo Molnar   [PATCH] sem2mutex...
326
  	mutex_unlock(&mtdblk->cache_mutex);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
327
328
329
330
331
332
333
334
  
  	if (mtdblk->mtd->sync)
  		mtdblk->mtd->sync(mtdblk->mtd);
  	return 0;
  }
  
  static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
  {
95b93a0cd   Burman Yan   [MTD] replace kma...
335
  	struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
336
337
338
  
  	if (!dev)
  		return;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
339
340
  	dev->mtd = mtd;
  	dev->devnum = mtd->index;
191876729   Richard Purdie   [MTD] Allow varia...
341

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
  	dev->size = mtd->size >> 9;
  	dev->tr = tr;
  
  	if (!(mtd->flags & MTD_WRITEABLE))
  		dev->readonly = 1;
  
  	add_mtd_blktrans_dev(dev);
  }
  
  static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
  {
  	del_mtd_blktrans_dev(dev);
  	kfree(dev);
  }
  
  static struct mtd_blktrans_ops mtdblock_tr = {
  	.name		= "mtdblock",
  	.major		= 31,
  	.part_bits	= 0,
191876729   Richard Purdie   [MTD] Allow varia...
361
  	.blksize 	= 512,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
  	.open		= mtdblock_open,
  	.flush		= mtdblock_flush,
  	.release	= mtdblock_release,
  	.readsect	= mtdblock_readsect,
  	.writesect	= mtdblock_writesect,
  	.add_mtd	= mtdblock_add_mtd,
  	.remove_dev	= mtdblock_remove_dev,
  	.owner		= THIS_MODULE,
  };
  
  static int __init init_mtdblock(void)
  {
  	return register_mtd_blktrans(&mtdblock_tr);
  }
  
  static void __exit cleanup_mtdblock(void)
  {
  	deregister_mtd_blktrans(&mtdblock_tr);
  }
  
  module_init(init_mtdblock);
  module_exit(cleanup_mtdblock);
  
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Nicolas Pitre <nico@cam.org> et al.");
  MODULE_DESCRIPTION("Caching read/erase/writeback block device emulation access to MTD devices");