summaryrefslogtreecommitdiffstats
path: root/fs/freevxfs/vxfs_inode.c
blob: 6087ca703f128e39d857a89d7dcf2217f7e86c34 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
/*
 * Copyright (c) 2000-2001 Christoph Hellwig.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer,
 *    without modification.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL").
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Veritas filesystem driver - inode routines.
 */
#include <linux/fs.h>
#include <linux/buffer_head.h>
#include <linux/pagemap.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/namei.h>

#include "vxfs.h"
#include "vxfs_inode.h"
#include "vxfs_extern.h"


struct kmem_cache		*vxfs_inode_cachep;


#ifdef DIAGNOSTIC
/*
 * Dump inode contents (partially).
 */
void
vxfs_dumpi(struct vxfs_inode_info *vip, ino_t ino)
{
	printk(KERN_DEBUG "\n\n");
	if (ino)
		printk(KERN_DEBUG "dumping vxfs inode %ld\n", ino);
	else
		printk(KERN_DEBUG "dumping unknown vxfs inode\n");

	printk(KERN_DEBUG "---------------------------\n");
	printk(KERN_DEBUG "mode is %x\n", vip->vii_mode);
	printk(KERN_DEBUG "nlink:%u, uid:%u, gid:%u\n",
			vip->vii_nlink, vip->vii_uid, vip->vii_gid);
	printk(KERN_DEBUG "size:%Lx, blocks:%u\n",
			vip->vii_size, vip->vii_blocks);
	printk(KERN_DEBUG "orgtype:%u\n", vip->vii_orgtype);
}
#endif

static inline void dip2vip_cpy(struct vxfs_sb_info *sbi,
		struct vxfs_inode_info *vip, struct vxfs_dinode *dip)
{
	vip->vii_mode = fs32_to_cpu(sbi, dip->vdi_mode);
	vip->vii_nlink = fs32_to_cpu(sbi, dip->vdi_nlink);
	vip->vii_uid = fs32_to_cpu(sbi, dip->vdi_uid);
	vip->vii_gid = fs32_to_cpu(sbi, dip->vdi_gid);
	vip->vii_size = fs64_to_cpu(sbi, dip->vdi_size);
	vip->vii_atime = fs32_to_cpu(sbi, dip->vdi_atime);
	vip->vii_autime = fs32_to_cpu(sbi, dip->vdi_autime);
	vip->vii_mtime = fs32_to_cpu(sbi, dip->vdi_mtime);
	vip->vii_mutime = fs32_to_cpu(sbi, dip->vdi_mutime);
	vip->vii_ctime = fs32_to_cpu(sbi, dip->vdi_ctime);
	vip->vii_cutime = fs32_to_cpu(sbi, dip->vdi_cutime);
	vip->vii_orgtype = dip->vdi_orgtype;

	vip->vii_blocks = fs32_to_cpu(sbi, dip->vdi_blocks);
	vip->vii_gen = fs32_to_cpu(sbi, dip->vdi_gen);

	if (VXFS_ISDIR(vip))
		vip->vii_dotdot = fs32_to_cpu(sbi, dip->vdi_dotdot);
	else if (!VXFS_ISREG(vip) && !VXFS_ISLNK(vip))
		vip->vii_rdev = fs32_to_cpu(sbi, dip->vdi_rdev);

	/* don't endian swap the fields that differ by orgtype */
	memcpy(&vip->vii_org, &dip->vdi_org, sizeof(vip->vii_org));
}

/**
 * vxfs_blkiget - find inode based on extent #
 * @sbp:	superblock of the filesystem we search in
 * @extent:	number of the extent to search
 * @ino:	inode number to search
 *
 * Description:
 *  vxfs_blkiget searches inode @ino in the filesystem described by
 *  @sbp in the extent @extent.
 *  Returns the matching VxFS inode on success, else a NULL pointer.
 *
 * NOTE:
 *  While __vxfs_iget uses the pagecache vxfs_blkiget uses the
 *  buffercache.  This function should not be used outside the
 *  read_super() method, otherwise the data may be incoherent.
 */
struct vxfs_inode_info *
vxfs_blkiget(struct super_block *sbp, u_long extent, ino_t ino)
{
	struct buffer_head		*bp;
	u_long				block, offset;

	block = extent + ((ino * VXFS_ISIZE) / sbp->s_blocksize);
	offset = ((ino % (sbp->s_blocksize / VXFS_ISIZE)) * VXFS_ISIZE);
	bp = sb_bread(sbp, block);

	if (bp && buffer_mapped(bp)) {
		struct vxfs_inode_info	*vip;
		struct vxfs_dinode	*dip;

		if (!(vip = kmem_cache_alloc(vxfs_inode_cachep, GFP_KERNEL)))
			goto fail;
		dip = (struct vxfs_dinode *)(bp->b_data + offset);
		dip2vip_cpy(VXFS_SBI(sbp), vip, dip);
#ifdef DIAGNOSTIC
		vxfs_dumpi(vip, ino);
#endif
		brelse(bp);
		return (vip);
	}

fail:
	printk(KERN_WARNING "vxfs: unable to read block %ld\n", block);
	brelse(bp);
	return NULL;
}

/**
 * __vxfs_iget - generic find inode facility
 * @sbp:		VFS superblock
 * @ino:		inode number
 * @ilistp:		inode list
 *
 * Description:
 *  Search the for inode number @ino in the filesystem
 *  described by @sbp.  Use the specified inode table (@ilistp).
 *  Returns the matching VxFS inode on success, else an error code.
 */
static struct vxfs_inode_info *
__vxfs_iget(ino_t ino, struct inode *ilistp)
{
	struct page			*pp;
	u_long				offset;

	offset = (ino % (PAGE_SIZE / VXFS_ISIZE)) * VXFS_ISIZE;
	pp = vxfs_get_page(ilistp->i_mapping, ino * VXFS_ISIZE / PAGE_SIZE);

	if (!IS_ERR(pp)) {
		struct vxfs_inode_info	*vip;
		struct vxfs_dinode	*dip;
		caddr_t			kaddr = (char *)page_address(pp);

		if (!(vip = kmem_cache_alloc(vxfs_inode_cachep, GFP_KERNEL)))
			goto fail;
		dip = (struct vxfs_dinode *)(kaddr + offset);
		dip2vip_cpy(VXFS_SBI(ilistp->i_sb), vip, dip);
#ifdef DIAGNOSTIC
		vxfs_dumpi(vip, ino);
#endif
		vxfs_put_page(pp);
		return (vip);
	}

	printk(KERN_WARNING "vxfs: error on page %p\n", pp);
	return ERR_CAST(pp);

fail:
	printk(KERN_WARNING "vxfs: unable to read inode %ld\n", (unsigned long)ino);
	vxfs_put_page(pp);
	return ERR_PTR(-ENOMEM);
}

/**
 * vxfs_stiget - find inode using the structural inode list
 * @sbp:	VFS superblock
 * @ino:	inode #
 *
 * Description:
 *  Find inode @ino in the filesystem described by @sbp using
 *  the structural inode list.
 *  Returns the matching VxFS inode on success, else a NULL pointer.
 */
struct vxfs_inode_info *
vxfs_stiget(struct super_block *sbp, ino_t ino)
{
	struct vxfs_inode_info *vip;

	vip = __vxfs_iget(ino, VXFS_SBI(sbp)->vsi_stilist);
	return IS_ERR(vip) ? NULL : vip;
}

/**
 * vxfs_transmod - mode for a VxFS inode
 * @vip:	VxFS inode
 *
 * Description:
 *  vxfs_transmod returns a Linux mode_t for a given
 *  VxFS inode structure.
 */
static __inline__ umode_t
vxfs_transmod(struct vxfs_inode_info *vip)
{
	umode_t			ret = vip->vii_mode & ~VXFS_TYPE_MASK;

	if (VXFS_ISFIFO(vip))
		ret |= S_IFIFO;
	if (VXFS_ISCHR(vip))
		ret |= S_IFCHR;
	if (VXFS_ISDIR(vip))
		ret |= S_IFDIR;
	if (VXFS_ISBLK(vip))
		ret |= S_IFBLK;
	if (VXFS_ISLNK(vip))
		ret |= S_IFLNK;
	if (VXFS_ISREG(vip))
		ret |= S_IFREG;
	if (VXFS_ISSOC(vip))
		ret |= S_IFSOCK;

	return (ret);
}

/**
 * vxfs_iinit- helper to fill inode fields
 * @ip:		VFS inode
 * @vip:	VxFS inode
 *
 * Description:
 *  vxfs_instino is a helper function to fill in all relevant
 *  fields in @ip from @vip.
 */
static void
vxfs_iinit(struct inode *ip, struct vxfs_inode_info *vip)
{

	ip->i_mode = vxfs_transmod(vip);
	i_uid_write(ip, (uid_t)vip->vii_uid);
	i_gid_write(ip, (gid_t)vip->vii_gid);

	set_nlink(ip, vip->vii_nlink);
	ip->i_size = vip->vii_size;

	ip->i_atime.tv_sec = vip->vii_atime;
	ip->i_ctime.tv_sec = vip->vii_ctime;
	ip->i_mtime.tv_sec = vip->vii_mtime;
	ip->i_atime.tv_nsec = 0;
	ip->i_ctime.tv_nsec = 0;
	ip->i_mtime.tv_nsec = 0;

	ip->i_blocks = vip->vii_blocks;
	ip->i_generation = vip->vii_gen;

	ip->i_private = vip;
	
}

/**
 * vxfs_get_fake_inode - get fake inode structure
 * @sbp:		filesystem superblock
 * @vip:		fspriv inode
 *
 * Description:
 *  vxfs_fake_inode gets a fake inode (not in the inode hash) for a
 *  superblock, vxfs_inode pair.
 *  Returns the filled VFS inode.
 */
struct inode *
vxfs_get_fake_inode(struct super_block *sbp, struct vxfs_inode_info *vip)
{
	struct inode			*ip = NULL;

	if ((ip = new_inode(sbp))) {
		ip->i_ino = get_next_ino();
		vxfs_iinit(ip, vip);
		ip->i_mapping->a_ops = &vxfs_aops;
	}
	return (ip);
}

/**
 * vxfs_iget - get an inode
 * @sbp:	the superblock to get the inode for
 * @ino:	the number of the inode to get
 *
 * Description:
 *  vxfs_read_inode creates an inode, reads the disk inode for @ino and fills
 *  in all relevant fields in the new inode.
 */
struct inode *
vxfs_iget(struct super_block *sbp, ino_t ino)
{
	struct vxfs_inode_info		*vip;
	const struct address_space_operations	*aops;
	struct inode *ip;

	ip = iget_locked(sbp, ino);
	if (!ip)
		return ERR_PTR(-ENOMEM);
	if (!(ip->i_state & I_NEW))
		return ip;

	vip = __vxfs_iget(ino, VXFS_SBI(sbp)->vsi_ilist);
	if (IS_ERR(vip)) {
		iget_failed(ip);
		return ERR_CAST(vip);
	}

	vxfs_iinit(ip, vip);

	if (VXFS_ISIMMED(vip))
		aops = &vxfs_immed_aops;
	else
		aops = &vxfs_aops;

	if (S_ISREG(ip->i_mode)) {
		ip->i_fop = &generic_ro_fops;
		ip->i_mapping->a_ops = aops;
	} else if (S_ISDIR(ip->i_mode)) {
		ip->i_op = &vxfs_dir_inode_ops;
		ip->i_fop = &vxfs_dir_operations;
		ip->i_mapping->a_ops = aops;
	} else if (S_ISLNK(ip->i_mode)) {
		if (!VXFS_ISIMMED(vip)) {
			ip->i_op = &page_symlink_inode_operations;
			inode_nohighmem(ip);
			ip->i_mapping->a_ops = &vxfs_aops;
		} else {
			ip->i_op = &simple_symlink_inode_operations;
			ip->i_link = vip->vii_immed.vi_immed;
			nd_terminate_link(ip->i_link, ip->i_size,
					  sizeof(vip->vii_immed.vi_immed) - 1);
		}
	} else
		init_special_inode(ip, ip->i_mode, old_decode_dev(vip->vii_rdev));

	unlock_new_inode(ip);
	return ip;
}

static void vxfs_i_callback(struct rcu_head *head)
{
	struct inode *inode = container_of(head, struct inode, i_rcu);
	kmem_cache_free(vxfs_inode_cachep, inode->i_private);
}

/**
 * vxfs_evict_inode - remove inode from main memory
 * @ip:		inode to discard.
 *
 * Description:
 *  vxfs_evict_inode() is called on the final iput and frees the private
 *  inode area.
 */
void
vxfs_evict_inode(struct inode *ip)
{
	truncate_inode_pages_final(&ip->i_data);
	clear_inode(ip);
	call_rcu(&ip->i_rcu, vxfs_i_callback);
}
s="hl opt">->nand.IO_ADDR_W); /* wait until buffer is available for write */ do { status = gpmc_read_status(GPMC_STATUS_BUFFER); } while (!status); } } /** * omap_read_buf_pref - read data from NAND controller into buffer * @mtd: MTD device structure * @buf: buffer to store date * @len: number of bytes to read */ static void omap_read_buf_pref(struct mtd_info *mtd, u_char *buf, int len) { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); uint32_t r_count = 0; int ret = 0; u32 *p = (u32 *)buf; /* take care of subpage reads */ if (len % 4) { if (info->nand.options & NAND_BUSWIDTH_16) omap_read_buf16(mtd, buf, len % 4); else omap_read_buf8(mtd, buf, len % 4); p = (u32 *) (buf + len % 4); len -= len % 4; } /* configure and start prefetch transfer */ ret = gpmc_prefetch_enable(info->gpmc_cs, 0x0, len, 0x0); if (ret) { /* PFPW engine is busy, use cpu copy method */ if (info->nand.options & NAND_BUSWIDTH_16) omap_read_buf16(mtd, buf, len); else omap_read_buf8(mtd, buf, len); } else { p = (u32 *) buf; do { r_count = gpmc_read_status(GPMC_PREFETCH_FIFO_CNT); r_count = r_count >> 2; ioread32_rep(info->nand.IO_ADDR_R, p, r_count); p += r_count; len -= r_count << 2; } while (len); /* disable and stop the PFPW engine */ gpmc_prefetch_reset(info->gpmc_cs); } } /** * omap_write_buf_pref - write buffer to NAND controller * @mtd: MTD device structure * @buf: data buffer * @len: number of bytes to write */ static void omap_write_buf_pref(struct mtd_info *mtd, const u_char *buf, int len) { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); uint32_t pref_count = 0, w_count = 0; int i = 0, ret = 0; u16 *p; /* take care of subpage writes */ if (len % 2 != 0) { writeb(*buf, info->nand.IO_ADDR_W); p = (u16 *)(buf + 1); len--; } /* configure and start prefetch transfer */ ret = gpmc_prefetch_enable(info->gpmc_cs, 0x0, len, 0x1); if (ret) { /* PFPW engine is busy, use cpu copy method */ if (info->nand.options & NAND_BUSWIDTH_16) omap_write_buf16(mtd, buf, len); else omap_write_buf8(mtd, buf, len); } else { p = (u16 *) buf; while (len) { w_count = gpmc_read_status(GPMC_PREFETCH_FIFO_CNT); w_count = w_count >> 1; for (i = 0; (i < w_count) && len; i++, len -= 2) iowrite16(*p++, info->nand.IO_ADDR_W); } /* wait for data to flushed-out before reset the prefetch */ do { pref_count = gpmc_read_status(GPMC_PREFETCH_COUNT); } while (pref_count); /* disable and stop the PFPW engine */ gpmc_prefetch_reset(info->gpmc_cs); } } #ifdef CONFIG_MTD_NAND_OMAP_PREFETCH_DMA /* * omap_nand_dma_cb: callback on the completion of dma transfer * @lch: logical channel * @ch_satuts: channel status * @data: pointer to completion data structure */ static void omap_nand_dma_cb(int lch, u16 ch_status, void *data) { complete((struct completion *) data); } /* * omap_nand_dma_transfer: configer and start dma transfer * @mtd: MTD device structure * @addr: virtual address in RAM of source/destination * @len: number of data bytes to be transferred * @is_write: flag for read/write operation */ static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, unsigned int len, int is_write) { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); uint32_t prefetch_status = 0; enum dma_data_direction dir = is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE; dma_addr_t dma_addr; int ret; /* The fifo depth is 64 bytes. We have a sync at each frame and frame * length is 64 bytes. */ int buf_len = len >> 6; if (addr >= high_memory) { struct page *p1; if (((size_t)addr & PAGE_MASK) != ((size_t)(addr + len - 1) & PAGE_MASK)) goto out_copy; p1 = vmalloc_to_page(addr); if (!p1) goto out_copy; addr = page_address(p1) + ((size_t)addr & ~PAGE_MASK); } dma_addr = dma_map_single(&info->pdev->dev, addr, len, dir); if (dma_mapping_error(&info->pdev->dev, dma_addr)) { dev_err(&info->pdev->dev, "Couldn't DMA map a %d byte buffer\n", len); goto out_copy; } if (is_write) { omap_set_dma_dest_params(info->dma_ch, 0, OMAP_DMA_AMODE_CONSTANT, info->phys_base, 0, 0); omap_set_dma_src_params(info->dma_ch, 0, OMAP_DMA_AMODE_POST_INC, dma_addr, 0, 0); omap_set_dma_transfer_params(info->dma_ch, OMAP_DMA_DATA_TYPE_S32, 0x10, buf_len, OMAP_DMA_SYNC_FRAME, OMAP24XX_DMA_GPMC, OMAP_DMA_DST_SYNC); } else { omap_set_dma_src_params(info->dma_ch, 0, OMAP_DMA_AMODE_CONSTANT, info->phys_base, 0, 0); omap_set_dma_dest_params(info->dma_ch, 0, OMAP_DMA_AMODE_POST_INC, dma_addr, 0, 0); omap_set_dma_transfer_params(info->dma_ch, OMAP_DMA_DATA_TYPE_S32, 0x10, buf_len, OMAP_DMA_SYNC_FRAME, OMAP24XX_DMA_GPMC, OMAP_DMA_SRC_SYNC); } /* configure and start prefetch transfer */ ret = gpmc_prefetch_enable(info->gpmc_cs, 0x1, len, is_write); if (ret) /* PFPW engine is busy, use cpu copy methode */ goto out_copy; init_completion(&info->comp); omap_start_dma(info->dma_ch); /* setup and start DMA using dma_addr */ wait_for_completion(&info->comp); do { prefetch_status = gpmc_read_status(GPMC_PREFETCH_COUNT); } while (prefetch_status); /* disable and stop the PFPW engine */ gpmc_prefetch_reset(info->gpmc_cs); dma_unmap_single(&info->pdev->dev, dma_addr, len, dir); return 0; out_copy: if (info->nand.options & NAND_BUSWIDTH_16) is_write == 0 ? omap_read_buf16(mtd, (u_char *) addr, len) : omap_write_buf16(mtd, (u_char *) addr, len); else is_write == 0 ? omap_read_buf8(mtd, (u_char *) addr, len) : omap_write_buf8(mtd, (u_char *) addr, len); return 0; } #else static void omap_nand_dma_cb(int lch, u16 ch_status, void *data) {} static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, unsigned int len, int is_write) { return 0; } #endif /** * omap_read_buf_dma_pref - read data from NAND controller into buffer * @mtd: MTD device structure * @buf: buffer to store date * @len: number of bytes to read */ static void omap_read_buf_dma_pref(struct mtd_info *mtd, u_char *buf, int len) { if (len <= mtd->oobsize) omap_read_buf_pref(mtd, buf, len); else /* start transfer in DMA mode */ omap_nand_dma_transfer(mtd, buf, len, 0x0); } /** * omap_write_buf_dma_pref - write buffer to NAND controller * @mtd: MTD device structure * @buf: data buffer * @len: number of bytes to write */ static void omap_write_buf_dma_pref(struct mtd_info *mtd, const u_char *buf, int len) { if (len <= mtd->oobsize) omap_write_buf_pref(mtd, buf, len); else /* start transfer in DMA mode */ omap_nand_dma_transfer(mtd, (u_char *) buf, len, 0x1); } /** * omap_verify_buf - Verify chip data against buffer * @mtd: MTD device structure * @buf: buffer containing the data to compare * @len: number of bytes to compare */ static int omap_verify_buf(struct mtd_info *mtd, const u_char * buf, int len) { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); u16 *p = (u16 *) buf; len >>= 1; while (len--) { if (*p++ != cpu_to_le16(readw(info->nand.IO_ADDR_R))) return -EFAULT; } return 0; } #ifdef CONFIG_MTD_NAND_OMAP_HWECC /** * gen_true_ecc - This function will generate true ECC value * @ecc_buf: buffer to store ecc code * * This generated true ECC value can be used when correcting * data read from NAND flash memory core */ static void gen_true_ecc(u8 *ecc_buf) { u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8); ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp)); ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp)); ecc_buf[2] = ~(P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | P1e(tmp) | P2048o(tmp) | P2048e(tmp)); } /** * omap_compare_ecc - Detect (2 bits) and correct (1 bit) error in data * @ecc_data1: ecc code from nand spare area * @ecc_data2: ecc code from hardware register obtained from hardware ecc * @page_data: page data * * This function compares two ECC's and indicates if there is an error. * If the error can be corrected it will be corrected to the buffer. */ static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ u8 *ecc_data2, /* read from register */ u8 *page_data) { uint i; u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8]; u8 comp0_bit[8], comp1_bit[8], comp2_bit[8]; u8 ecc_bit[24]; u8 ecc_sum = 0; u8 find_bit = 0; uint find_byte = 0; int isEccFF; isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF); gen_true_ecc(ecc_data1); gen_true_ecc(ecc_data2); for (i = 0; i <= 2; i++) { *(ecc_data1 + i) = ~(*(ecc_data1 + i)); *(ecc_data2 + i) = ~(*(ecc_data2 + i)); } for (i = 0; i < 8; i++) { tmp0_bit[i] = *ecc_data1 % 2; *ecc_data1 = *ecc_data1 / 2; } for (i = 0; i < 8; i++) { tmp1_bit[i] = *(ecc_data1 + 1) % 2; *(ecc_data1 + 1) = *(ecc_data1 + 1) / 2; } for (i = 0; i < 8; i++) { tmp2_bit[i] = *(ecc_data1 + 2) % 2; *(ecc_data1 + 2) = *(ecc_data1 + 2) / 2; } for (i = 0; i < 8; i++) { comp0_bit[i] = *ecc_data2 % 2; *ecc_data2 = *ecc_data2 / 2; } for (i = 0; i < 8; i++) { comp1_bit[i] = *(ecc_data2 + 1) % 2; *(ecc_data2 + 1) = *(ecc_data2 + 1) / 2; } for (i = 0; i < 8; i++) { comp2_bit[i] = *(ecc_data2 + 2) % 2; *(ecc_data2 + 2) = *(ecc_data2 + 2) / 2; } for (i = 0; i < 6; i++) ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2]; for (i = 0; i < 8; i++) ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i]; for (i = 0; i < 8; i++) ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i]; ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0]; ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1]; for (i = 0; i < 24; i++) ecc_sum += ecc_bit[i]; switch (ecc_sum) { case 0: /* Not reached because this function is not called if * ECC values are equal */ return 0; case 1: /* Uncorrectable error */ DEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n"); return -1; case 11: /* UN-Correctable error */ DEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR B\n"); return -1; case 12: /* Correctable error */ find_byte = (ecc_bit[23] << 8) + (ecc_bit[21] << 7) + (ecc_bit[19] << 6) + (ecc_bit[17] << 5) + (ecc_bit[15] << 4) + (ecc_bit[13] << 3) + (ecc_bit[11] << 2) + (ecc_bit[9] << 1) + ecc_bit[7]; find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1]; DEBUG(MTD_DEBUG_LEVEL0, "Correcting single bit ECC error at " "offset: %d, bit: %d\n", find_byte, find_bit); page_data[find_byte] ^= (1 << find_bit); return 0; default: if (isEccFF) { if (ecc_data2[0] == 0 && ecc_data2[1] == 0 && ecc_data2[2] == 0) return 0; } DEBUG(MTD_DEBUG_LEVEL0, "UNCORRECTED_ERROR default\n"); return -1; } } /** * omap_correct_data - Compares the ECC read with HW generated ECC * @mtd: MTD device structure * @dat: page data * @read_ecc: ecc read from nand flash * @calc_ecc: ecc read from HW ECC registers * * Compares the ecc read from nand spare area with ECC registers values * and if ECC's mismached, it will call 'omap_compare_ecc' for error detection * and correction. */ static int omap_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); int blockCnt = 0, i = 0, ret = 0; /* Ex NAND_ECC_HW12_2048 */ if ((info->nand.ecc.mode == NAND_ECC_HW) && (info->nand.ecc.size == 2048)) blockCnt = 4; else blockCnt = 1; for (i = 0; i < blockCnt; i++) { if (memcmp(read_ecc, calc_ecc, 3) != 0) { ret = omap_compare_ecc(read_ecc, calc_ecc, dat); if (ret < 0) return ret; } read_ecc += 3; calc_ecc += 3; dat += 512; } return 0; } /** * omap_calcuate_ecc - Generate non-inverted ECC bytes. * @mtd: MTD device structure * @dat: The pointer to data on which ecc is computed * @ecc_code: The ecc_code buffer * * Using noninverted ECC can be considered ugly since writing a blank * page ie. padding will clear the ECC bytes. This is no problem as long * nobody is trying to write data on the seemingly unused page. Reading * an erased page will produce an ECC mismatch between generated and read * ECC bytes that has to be dealt with separately. */ static int omap_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); return gpmc_calculate_ecc(info->gpmc_cs, dat, ecc_code); } /** * omap_enable_hwecc - This function enables the hardware ecc functionality * @mtd: MTD device structure * @mode: Read/Write mode */ static void omap_enable_hwecc(struct mtd_info *mtd, int mode) { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); struct nand_chip *chip = mtd->priv; unsigned int dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0; gpmc_enable_hwecc(info->gpmc_cs, mode, dev_width, info->nand.ecc.size); } #endif /** * omap_wait - wait until the command is done * @mtd: MTD device structure * @chip: NAND Chip structure * * Wait function is called during Program and erase operations and * the way it is called from MTD layer, we should wait till the NAND * chip is ready after the programming/erase operation has completed. * * Erase can take up to 400ms and program up to 20ms according to * general NAND and SmartMedia specs */ static int omap_wait(struct mtd_info *mtd, struct nand_chip *chip) { struct nand_chip *this = mtd->priv; struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); unsigned long timeo = jiffies; int status = NAND_STATUS_FAIL, state = this->state; if (state == FL_ERASING) timeo += (HZ * 400) / 1000; else timeo += (HZ * 20) / 1000; gpmc_nand_write(info->gpmc_cs, GPMC_NAND_COMMAND, (NAND_CMD_STATUS & 0xFF)); while (time_before(jiffies, timeo)) { status = gpmc_nand_read(info->gpmc_cs, GPMC_NAND_DATA); if (status & NAND_STATUS_READY) break; cond_resched(); } return status; } /** * omap_dev_ready - calls the platform specific dev_ready function * @mtd: MTD device structure */ static int omap_dev_ready(struct mtd_info *mtd) { unsigned int val = 0; struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); val = gpmc_read_status(GPMC_GET_IRQ_STATUS); if ((val & 0x100) == 0x100) { /* Clear IRQ Interrupt */ val |= 0x100; val &= ~(0x0); gpmc_cs_configure(info->gpmc_cs, GPMC_SET_IRQ_STATUS, val); } else { unsigned int cnt = 0; while (cnt++ < 0x1FF) { if ((val & 0x100) == 0x100) return 0; val = gpmc_read_status(GPMC_GET_IRQ_STATUS); } } return 1; } static int __devinit omap_nand_probe(struct platform_device *pdev) { struct omap_nand_info *info; struct omap_nand_platform_data *pdata; int err; pdata = pdev->dev.platform_data; if (pdata == NULL) { dev_err(&pdev->dev, "platform data missing\n"); return -ENODEV; } info = kzalloc(sizeof(struct omap_nand_info), GFP_KERNEL); if (!info) return -ENOMEM; platform_set_drvdata(pdev, info); spin_lock_init(&info->controller.lock); init_waitqueue_head(&info->controller.wq); info->pdev = pdev; info->gpmc_cs = pdata->cs; info->phys_base = pdata->phys_base; info->mtd.priv = &info->nand; info->mtd.name = dev_name(&pdev->dev); info->mtd.owner = THIS_MODULE; info->nand.options |= pdata->devsize ? NAND_BUSWIDTH_16 : 0; info->nand.options |= NAND_SKIP_BBTSCAN; /* NAND write protect off */ gpmc_cs_configure(info->gpmc_cs, GPMC_CONFIG_WP, 0); if (!request_mem_region(info->phys_base, NAND_IO_SIZE, pdev->dev.driver->name)) { err = -EBUSY; goto out_free_info; } info->nand.IO_ADDR_R = ioremap(info->phys_base, NAND_IO_SIZE); if (!info->nand.IO_ADDR_R) { err = -ENOMEM; goto out_release_mem_region; } info->nand.controller = &info->controller; info->nand.IO_ADDR_W = info->nand.IO_ADDR_R; info->nand.cmd_ctrl = omap_hwcontrol; /* * If RDY/BSY line is connected to OMAP then use the omap ready * funcrtion and the generic nand_wait function which reads the status * register after monitoring the RDY/BSY line.Otherwise use a standard * chip delay which is slightly more than tR (AC Timing) of the NAND * device and read status register until you get a failure or success */ if (pdata->dev_ready) { info->nand.dev_ready = omap_dev_ready; info->nand.chip_delay = 0; } else { info->nand.waitfunc = omap_wait; info->nand.chip_delay = 50; } if (use_prefetch) { info->nand.read_buf = omap_read_buf_pref; info->nand.write_buf = omap_write_buf_pref; if (use_dma) { err = omap_request_dma(OMAP24XX_DMA_GPMC, "NAND", omap_nand_dma_cb, &info->comp, &info->dma_ch); if (err < 0) { info->dma_ch = -1; printk(KERN_WARNING "DMA request failed." " Non-dma data transfer mode\n"); } else { omap_set_dma_dest_burst_mode(info->dma_ch, OMAP_DMA_DATA_BURST_16); omap_set_dma_src_burst_mode(info->dma_ch, OMAP_DMA_DATA_BURST_16); info->nand.read_buf = omap_read_buf_dma_pref; info->nand.write_buf = omap_write_buf_dma_pref; } } } else { if (info->nand.options & NAND_BUSWIDTH_16) { info->nand.read_buf = omap_read_buf16; info->nand.write_buf = omap_write_buf16; } else { info->nand.read_buf = omap_read_buf8; info->nand.write_buf = omap_write_buf8; } } info->nand.verify_buf = omap_verify_buf; #ifdef CONFIG_MTD_NAND_OMAP_HWECC info->nand.ecc.bytes = 3; info->nand.ecc.size = 512; info->nand.ecc.calculate = omap_calculate_ecc; info->nand.ecc.hwctl = omap_enable_hwecc; info->nand.ecc.correct = omap_correct_data; info->nand.ecc.mode = NAND_ECC_HW; #else info->nand.ecc.mode = NAND_ECC_SOFT; #endif /* DIP switches on some boards change between 8 and 16 bit * bus widths for flash. Try the other width if the first try fails. */ if (nand_scan(&info->mtd, 1)) { info->nand.options ^= NAND_BUSWIDTH_16; if (nand_scan(&info->mtd, 1)) { err = -ENXIO; goto out_release_mem_region; } } #ifdef CONFIG_MTD_PARTITIONS err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0); if (err > 0) add_mtd_partitions(&info->mtd, info->parts, err); else if (pdata->parts) add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts); else #endif add_mtd_device(&info->mtd); platform_set_drvdata(pdev, &info->mtd); return 0; out_release_mem_region: release_mem_region(info->phys_base, NAND_IO_SIZE); out_free_info: kfree(info); return err; } static int omap_nand_remove(struct platform_device *pdev) { struct mtd_info *mtd = platform_get_drvdata(pdev); struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); platform_set_drvdata(pdev, NULL); if (use_dma) omap_free_dma(info->dma_ch); /* Release NAND device, its internal structures and partitions */ nand_release(&info->mtd); iounmap(info->nand.IO_ADDR_R); kfree(&info->mtd); return 0; } static struct platform_driver omap_nand_driver = { .probe = omap_nand_probe, .remove = omap_nand_remove, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, }, }; static int __init omap_nand_init(void) { printk(KERN_INFO "%s driver initializing\n", DRIVER_NAME); /* This check is required if driver is being * loaded run time as a module */ if ((1 == use_dma) && (0 == use_prefetch)) { printk(KERN_INFO"Wrong parameters: 'use_dma' can not be 1 " "without use_prefetch'. Prefetch will not be" " used in either mode (mpu or dma)\n"); } return platform_driver_register(&omap_nand_driver); } static void __exit omap_nand_exit(void) { platform_driver_unregister(&omap_nand_driver); } module_init(omap_nand_init); module_exit(omap_nand_exit); MODULE_ALIAS(DRIVER_NAME); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards");