static void jffs2_erase_callback(struct erase_info *instr)
{
	struct erase_priv_struct *priv = (void *)instr->priv;

	if(instr->state != MTD_ERASE_DONE) {
		printk(KERN_WARNING "Erase at 0x%08x finished, but state != MTD_ERASE_DONE. State is 0x%x instead.\n", instr->addr, instr->state);
		spin_lock(&priv->c->erase_completion_lock);
		priv->c->erasing_size -= priv->c->sector_size;
		priv->c->bad_size += priv->c->sector_size;
		list_del(&priv->jeb->list);
		list_add(&priv->jeb->list, &priv->c->bad_list);
		priv->c->nr_erasing_blocks--;
		spin_unlock(&priv->c->erase_completion_lock);
		wake_up(&priv->c->erase_wait);
	} else {
		D1(printk(KERN_DEBUG "Erase completed successfully at 0x%08x\n", instr->addr));
		spin_lock(&priv->c->erase_completion_lock);
		list_del(&priv->jeb->list);
		list_add_tail(&priv->jeb->list, &priv->c->erase_complete_list);
		spin_unlock(&priv->c->erase_completion_lock);
	}	
	/* Make sure someone picks up the block off the erase_complete list */
	OFNI_BS_2SFFJ(priv->c)->s_dirt = 1;
	kfree(instr);
}
Example #2
0
void 
jffs2_stop_garbage_collect_thread(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     
     CYG_ASSERTC(sb->s_gc_thread_handle);
     
     D1(printk("jffs2_stop_garbage_collect_thread\n"));
     /* Stop the thread and wait for it if necessary */
     
     cyg_flag_setbits(&sb->s_gc_thread_flags,GC_THREAD_FLAG_STOP);
     
     D1(printk("jffs2_stop_garbage_collect_thread wait\n"));
     
     cyg_flag_wait(&sb->s_gc_thread_flags,
                   GC_THREAD_FLAG_HAS_EXIT,
                   CYG_FLAG_WAITMODE_OR| CYG_FLAG_WAITMODE_CLR);
     
     // Kill and free the resources ...  this is safe due to the flag
     // from the thread.
     cyg_thread_kill(sb->s_gc_thread_handle);
     cyg_thread_delete(sb->s_gc_thread_handle);
     
     cyg_mutex_destroy(&sb->s_lock);
     cyg_flag_destroy(&sb->s_gc_thread_flags);
}
Example #3
0
void
jffs2_stop_garbage_collect_thread(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_mtab_entry *mte;
	 rt_uint32_t  e;
	 
     //RT_ASSERT(sb->s_gc_thread_handle);

     D1(printk("jffs2_stop_garbage_collect_thread\n"));
     /* Stop the thread and wait for it if necessary */

     rt_event_send(&sb->s_gc_thread_flags,GC_THREAD_FLAG_STOP);

     D1(printk("jffs2_stop_garbage_collect_thread wait\n"));
	 
     rt_event_recv(&sb->s_gc_thread_flags,
                   GC_THREAD_FLAG_HAS_EXIT,
                   RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
				   RT_WAITING_FOREVER,  &e);

     // Kill and free the resources ...  this is safe due to the flag
     // from the thread.
     rt_thread_detach(&sb->s_gc_thread);
     rt_sem_detach(&sb->s_lock);
     rt_event_detach(&sb->s_gc_thread_flags);
}
Example #4
0
int jffs2_decompress(struct jffs2_sb_info *c, struct jffs2_inode_info *f,
		     uint16_t comprtype, unsigned char *cdata_in,
		     unsigned char *data_out, uint32_t cdatalen, uint32_t datalen)
{
	struct super_block *sb = OFNI_BS_2SFFJ(c);
	rtems_jffs2_compressor_control *cc = sb->s_compressor_control;

	/* Older code had a bug where it would write non-zero 'usercompr'
	   fields. Deal with it. */
	if ((comprtype & 0xff) <= JFFS2_COMPR_ZLIB)
		comprtype &= 0xff;

	switch (comprtype & 0xff) {
	case JFFS2_COMPR_NONE:
		/* This should be special-cased elsewhere, but we might as well deal with it */
		memcpy(data_out, cdata_in, datalen);
		break;
	case JFFS2_COMPR_ZERO:
		memset(data_out, 0, datalen);
		break;
	default:
		if (cc != NULL) {
			return (*cc->decompress)(cc, comprtype, cdata_in, data_out, cdatalen, datalen);
		} else {
			return -EIO;
		}
	}
	return 0;
}
Example #5
0
void
jffs2_start_garbage_collect_thread(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_mtab_entry *mte;
     int result;
	
     RT_ASSERT(c);
     //RT_ASSERT(!sb->s_gc_thread_handle);
	 
     mte=(cyg_dir *) sb->s_root;
     RT_ASSERT(mte);
	 
     rt_event_init(&sb->s_gc_thread_flags, "gc_event", RT_IPC_FLAG_FIFO);	 
     rt_mutex_init(&sb->s_lock, "gc_mutex", RT_IPC_FLAG_FIFO);
//     rt_mutex_init(&mte->fs->syncmode, "fs_lock", RT_IPC_FLAG_FIFO);
	 
     D1(printk("jffs2_start_garbage_collect_thread\n"));
     /* Start the thread. Doesn't matter if it fails -- it's only an
      * optimisation anyway */
     result =  rt_thread_init(&sb->s_gc_thread, 
	                   "jffs2_gc_thread",
                       jffs2_garbage_collect_thread,
                       (void *)c,
                       (void*)sb->s_gc_thread_stack,
                       sizeof(sb->s_gc_thread_stack),
					   CYGNUM_JFFS2_GC_THREAD_PRIORITY,
					   CYGNUM_JFFS2_GC_THREAD_TICKS
					   );
	 if (result != RT_EOK) {
		 rt_thread_startup(&sb->s_gc_thread);
		 /* how to deal with the following filed? */
		 /* sb->s_gc_thread_handle; */
	 }
}
Example #6
0
void jffs2_garbage_collect_trigger(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     
     /* Wake up the thread */
     D1(printk("jffs2_garbage_collect_trigger\n"));

     cyg_flag_setbits(&sb->s_gc_thread_flags,GC_THREAD_FLAG_TRIG);
}
Example #7
0
int jffs2_flash_erase(struct jffs2_sb_info * c,
		struct jffs2_eraseblock * jeb)
{
	rt_err_t result;
	struct super_block *sb = OFNI_BS_2SFFJ(c);

	result = rt_mtd_nor_erase_block(RT_MTD_NOR_DEVICE(sb->s_dev), jeb->offset, c->sector_size);
	if (result != RT_EOK)
		return -EIO;

	return ENOERR;
}
Example #8
0
int jffs2_flash_write(struct jffs2_sb_info * c,
		uint32_t offset, const size_t size,
		size_t * return_size, unsigned char *buffer)
{
	uint32_t len;
	struct super_block *sb = OFNI_BS_2SFFJ(c);

	len = rt_mtd_nor_write(RT_MTD_NOR_DEVICE(sb->s_dev), offset, buffer, size);
	if (len != size)
		return -EIO;

	* return_size = len;
	return ENOERR;
}
Example #9
0
cyg_bool jffs2_flash_read(struct jffs2_sb_info * c,
			  cyg_uint32 read_buffer_offset, const size_t size,
			  size_t * return_size, unsigned char *write_buffer)
{
	Cyg_ErrNo err;
	cyg_uint32 len = size;
	struct super_block *sb = OFNI_BS_2SFFJ(c);

	//D2(printf("FLASH READ\n"));
	//D2(printf("read address = %x\n", CYGNUM_FS_JFFS2_BASE_ADDRESS + read_buffer_offset));
	//D2(printf("write address = %x\n", write_buffer));
	//D2(printf("size = %x\n", size));
	err = cyg_io_bread(sb->s_dev, write_buffer, &len, read_buffer_offset);

	*return_size = (size_t) len;
	return ((err == ENOERR) ? ENOERR : -EIO);
}
Example #10
0
/* jffs2_compress:
 * @data_in: Pointer to uncompressed data
 * @cpage_out: Pointer to returned pointer to buffer for compressed data
 * @datalen: On entry, holds the amount of data available for compression.
 *	On exit, expected to hold the amount of data actually compressed.
 * @cdatalen: On entry, holds the amount of space available for compressed
 *	data. On exit, expected to hold the actual size of the compressed
 *	data.
 *
 * Returns: Lower byte to be stored with data indicating compression type used.
 * Zero is used to show that the data could not be compressed - the
 * compressed version was actually larger than the original.
 * Upper byte will be used later. (soon)
 *
 * If the cdata buffer isn't large enough to hold all the uncompressed data,
 * jffs2_compress should compress as much as will fit, and should set
 * *datalen accordingly to show the amount of data which were compressed.
 */
uint16_t jffs2_compress(struct jffs2_sb_info *c, struct jffs2_inode_info *f,
			unsigned char *data_in, unsigned char **cpage_out,
			uint32_t *datalen, uint32_t *cdatalen)
{
	struct super_block *sb = OFNI_BS_2SFFJ(c);
	rtems_jffs2_compressor_control *cc = sb->s_compressor_control;
	int ret;

	if (cc != NULL) {
		*cpage_out = &cc->buffer[0];
		ret = (*cc->compress)(cc, data_in, *cpage_out, datalen, cdatalen);
	} else {
		ret = JFFS2_COMPR_NONE;
	}

	if (ret == JFFS2_COMPR_NONE) {
		*cpage_out = data_in;
		*datalen = *cdatalen;
	}
	return ret;
}
Example #11
0
cyg_bool jffs2_flash_erase(struct jffs2_sb_info * c,
			   struct jffs2_eraseblock * jeb)
{
	cyg_io_flash_getconfig_erase_t e;
	cyg_flashaddr_t err_addr;
	Cyg_ErrNo err;
	cyg_uint32 len = sizeof (e);
	struct super_block *sb = OFNI_BS_2SFFJ(c);

	e.offset = jeb->offset;
	e.len = c->sector_size;
	e.err_address = &err_addr;

	//        D2(printf("FLASH ERASE ENABLED!!!\n"));
	//        D2(printf("erase address = %x\n", CYGNUM_FS_JFFS2_BASE_ADDRESS + jeb->offset));
	//        D2(printf("size = %x\n", c->sector_size));

	err = cyg_io_get_config(sb->s_dev, CYG_IO_GET_CONFIG_FLASH_ERASE,
				&e, &len);

	return (err != ENOERR || e.flasherr != 0);
}
Example #12
0
static void
jffs2_garbage_collect_thread(cyg_addrword_t data)
{
     struct jffs2_sb_info *c=(struct jffs2_sb_info *)data;
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_flag_value_t flag;
     cyg_mtab_entry *mte;
     
     D1(printk("jffs2_garbage_collect_thread START\n"));
     
     while(1) {
          flag=cyg_flag_timed_wait(&sb->s_gc_thread_flags,
                                   GC_THREAD_FLAG_TRIG|GC_THREAD_FLAG_STOP,
                                   CYG_FLAG_WAITMODE_OR| CYG_FLAG_WAITMODE_CLR,
                                   cyg_current_time()+
                                   CYGNUM_JFFS2_GS_THREAD_TICKS);
          
          if (flag & GC_THREAD_FLAG_STOP)
               break;
          
          D1(printk("jffs2: GC THREAD GC BEGIN\n"));

          mte=cyg_fs_root_lookup((cyg_dir *) sb->s_root);
          CYG_ASSERT(mte, "Bad mount point");
          cyg_fs_lock(mte, mte->fs->syncmode);
          
          if (jffs2_garbage_collect_pass(c) == -ENOSPC) {
               printf("No space for garbage collection. "
                      "Aborting JFFS2 GC thread\n");
               break;
          }
          cyg_fs_unlock(mte, mte->fs->syncmode);
          D1(printk("jffs2: GC THREAD GC END\n"));
     }
     
     D1(printk("jffs2_garbage_collect_thread EXIT\n"));
     cyg_flag_setbits(&sb->s_gc_thread_flags,GC_THREAD_FLAG_HAS_EXIT);
}
Example #13
0
static void
jffs2_garbage_collect_thread(unsigned long data)
{
     struct jffs2_sb_info *c=(struct jffs2_sb_info *)data;
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     cyg_mtab_entry *mte;
     rt_uint32_t flag = 0;
	 
     D1(printk("jffs2_garbage_collect_thread START\n"));

     while(1) {
          rt_event_recv(&sb->s_gc_thread_flags,
                        GC_THREAD_FLAG_TRIG | GC_THREAD_FLAG_STOP,
                        RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
				        cyg_current_time() + CYGNUM_JFFS2_GS_THREAD_TICKS,  
						&flag);

          if (flag & GC_THREAD_FLAG_STOP)
               break;

          D1(printk("jffs2: GC THREAD GC BEGIN\n"));

          mte=(cyg_dir *) sb->s_root;
          RT_ASSERT(mte != NULL);
//          rt_mutex_take(&mte->fs->syncmode, RT_WAITING_FOREVER);

          if (jffs2_garbage_collect_pass(c) == -ENOSPC) {
               printf("No space for garbage collection. "
                      "Aborting JFFS2 GC thread\n");
               break;
          }
//          rt_mutex_release(&mte->fs->syncmode);
          D1(printk("jffs2: GC THREAD GC END\n"));
     }

     D1(printk("jffs2_garbage_collect_thread EXIT\n"));
     rt_event_send(&sb->s_gc_thread_flags,GC_THREAD_FLAG_HAS_EXIT);	 
}
Example #14
0
void 
jffs2_start_garbage_collect_thread(struct jffs2_sb_info *c)
{
     struct super_block *sb=OFNI_BS_2SFFJ(c);
     
     CYG_ASSERTC(c);
     CYG_ASSERTC(!sb->s_gc_thread_handle);
     
     cyg_flag_init(&sb->s_gc_thread_flags);
     cyg_mutex_init(&sb->s_lock);
     
     D1(printk("jffs2_start_garbage_collect_thread\n"));
     /* Start the thread. Doesn't matter if it fails -- it's only an
      * optimisation anyway */
     cyg_thread_create(CYGNUM_JFFS2_GC_THREAD_PRIORITY,
                       jffs2_garbage_collect_thread, 
                       (cyg_addrword_t)c,"jffs2 gc thread",
                       (void*)sb->s_gc_thread_stack,
                       sizeof(sb->s_gc_thread_stack),
                       &sb->s_gc_thread_handle, 
                       &sb->s_gc_thread);
     
     cyg_thread_resume(sb->s_gc_thread_handle);
}
Example #15
0
struct jffs2_inode_info *jffs2_gc_fetch_inode(struct jffs2_sb_info *c,
						     int inum, int nlink)
{
	struct inode *inode;
	struct jffs2_inode_cache *ic;
	if (!nlink) {
		/* The inode has zero nlink but its nodes weren't yet marked
		   obsolete. This has to be because we're still waiting for
		   the final (close() and) iput() to happen.

		   There's a possibility that the final iput() could have
		   happened while we were contemplating. In order to ensure
		   that we don't cause a new read_inode() (which would fail)
		   for the inode in question, we use ilookup() in this case
		   instead of iget().

		   The nlink can't _become_ zero at this point because we're
		   holding the alloc_sem, and jffs2_do_unlink() would also
		   need that while decrementing nlink on any inode.
		*/
		inode = ilookup(OFNI_BS_2SFFJ(c), inum);
		if (!inode) {
			D1(printk(KERN_DEBUG "ilookup() failed for ino #%u; inode is probably deleted.\n",
				  inum));

			spin_lock(&c->inocache_lock);
			ic = jffs2_get_ino_cache(c, inum);
			if (!ic) {
				D1(printk(KERN_DEBUG "Inode cache for ino #%u is gone.\n", inum));
				spin_unlock(&c->inocache_lock);
				return NULL;
			}
			if (ic->state != INO_STATE_CHECKEDABSENT) {
				/* Wait for progress. Don't just loop */
				D1(printk(KERN_DEBUG "Waiting for ino #%u in state %d\n",
					  ic->ino, ic->state));
				sleep_on_spinunlock(&c->inocache_wq, &c->inocache_lock);
			} else {
				spin_unlock(&c->inocache_lock);
			}

			return NULL;
		}
	} else {
		/* Inode has links to it still; they're not going away because
		   jffs2_do_unlink() would need the alloc_sem and we have it.
		   Just iget() it, and if read_inode() is necessary that's OK.
		*/
		inode = iget(OFNI_BS_2SFFJ(c), inum);
		if (!inode)
			return ERR_PTR(-ENOMEM);
	}
	if (is_bad_inode(inode)) {
		printk(KERN_NOTICE "Eep. read_inode() failed for ino #%u. nlink %d\n",
		       inum, nlink);
		/* NB. This will happen again. We need to do something appropriate here. */
		iput(inode);
		return ERR_PTR(-EIO);
	}

	return JFFS2_INODE_INFO(inode);
}
Example #16
0
static int jffs2_scan_eraseblock (struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb) {
	struct jffs2_unknown_node node;
	__u32 ofs, prevofs;
	__u32 hdr_crc, nodetype;
	int err;
	int noise = 0;

	ofs = jeb->offset;
	prevofs = jeb->offset - 1;

	D1(printk(KERN_DEBUG "jffs2_scan_eraseblock(): Scanning block at 0x%x\n", ofs));

	err = jffs2_scan_empty(c, jeb, &ofs, &noise);
	if (err) return err;
	if (ofs == jeb->offset + c->sector_size) {
		D1(printk(KERN_DEBUG "Block at 0x%08x is empty (erased)\n", jeb->offset));
		return 1;	/* special return code */
	}
	
	noise = 10;

	while(ofs < jeb->offset + c->sector_size) {
		ssize_t retlen;
		unsigned char *buf = (unsigned char *) &node;
		ACCT_PARANOIA_CHECK(jeb);
		
		if (ofs & 3) {
			printk(KERN_WARNING "Eep. ofs 0x%08x not word-aligned!\n", ofs);
			ofs = (ofs+3)&~3;
			continue;
		}
		if (ofs == prevofs) {
			printk(KERN_WARNING "ofs 0x%08x has already been seen. Skipping\n", ofs);
			DIRTY_SPACE(4);
			ofs += 4;
			continue;
		}
		prevofs = ofs;
		
		if (jeb->offset + c->sector_size < ofs + sizeof(node)) {
			D1(printk(KERN_DEBUG "Fewer than %d bytes left to end of block. Not reading\n", sizeof(struct jffs2_unknown_node)));
			DIRTY_SPACE((jeb->offset + c->sector_size)-ofs);
			break;
		}

		err = c->mtd->read(c->mtd, ofs, sizeof(node), &retlen, buf);
		if ((buf[0] == 0xde) &&
			(buf[1] == 0xad) &&
			(buf[2] == 0xc0) &&
			(buf[3] == 0xde)) {
				
			/* end of filesystem. erase everything after this point */
			c->flags |= (1 << 7);
			printk("jffs2_scan_eraseblock(): End of filesystem marker found at 0x%x\n", jeb->offset);
		
			return 1;
		}
		if (err) {
			D1(printk(KERN_WARNING "mtd->read(0x%x bytes from 0x%x) returned %d\n", sizeof(node), ofs, err));
			return err;
		}
		if (retlen < sizeof(node)) {
			D1(printk(KERN_WARNING "Read at 0x%x gave only 0x%x bytes\n", ofs, retlen));
			DIRTY_SPACE(retlen);
			ofs += retlen;
			continue;
		}

		if (node.magic == JFFS2_EMPTY_BITMASK && node.nodetype == JFFS2_EMPTY_BITMASK) {
			D1(printk(KERN_DEBUG "Found empty flash at 0x%x\n", ofs));
			err = jffs2_scan_empty(c, jeb, &ofs, &noise);
			if (err) return err;
			continue;
		}

		if (ofs == jeb->offset && node.magic == KSAMTIB_CIGAM_2SFFJ) {
			printk(KERN_WARNING "Magic bitmask is backwards at offset 0x%08x. Wrong endian filesystem?\n", ofs);
			DIRTY_SPACE(4);
			ofs += 4;
			continue;
		}
		if (node.magic == JFFS2_DIRTY_BITMASK) {
			D1(printk(KERN_DEBUG "Empty bitmask at 0x%08x\n", ofs));
			DIRTY_SPACE(4);
			ofs += 4;
			continue;
		}
		if (node.magic == JFFS2_OLD_MAGIC_BITMASK) {
			printk(KERN_WARNING "Old JFFS2 bitmask found at 0x%08x\n", ofs);
			printk(KERN_WARNING "You cannot use older JFFS2 filesystems with newer kernels\n");
			DIRTY_SPACE(4);
			ofs += 4;
			continue;
		}
		if (node.magic != JFFS2_MAGIC_BITMASK) {
			/* OK. We're out of possibilities. Whinge and move on */
			noisy_printk(&noise, "jffs2_scan_eraseblock(): Magic bitmask 0x%04x not found at 0x%08x: 0x%04x instead\n", JFFS2_MAGIC_BITMASK, ofs, node.magic);
			DIRTY_SPACE(4);
			ofs += 4;
			continue;
		}
		/* We seem to have a node of sorts. Check the CRC */
		nodetype = node.nodetype;
		node.nodetype |= JFFS2_NODE_ACCURATE;
		hdr_crc = crc32(0, &node, sizeof(node)-4);
		node.nodetype = nodetype;
		if (hdr_crc != node.hdr_crc) {
			noisy_printk(&noise, "jffs2_scan_eraseblock(): Node at 0x%08x {0x%04x, 0x%04x, 0x%08x) has invalid CRC 0x%08x (calculated 0x%08x)\n",
				     ofs, node.magic, node.nodetype, node.totlen, node.hdr_crc, hdr_crc);
			DIRTY_SPACE(4);
			ofs += 4;
			continue;
		}

		if (ofs + node.totlen > jeb->offset + c->sector_size) {
			/* Eep. Node goes over the end of the erase block. */
			printk(KERN_WARNING "Node at 0x%08x with length 0x%08x would run over the end of the erase block\n",
			       ofs, node.totlen);
			printk(KERN_WARNING "Perhaps the file system was created with the wrong erase size?\n");
			DIRTY_SPACE(4);
			ofs += 4;
			continue;
		}

		switch(node.nodetype | JFFS2_NODE_ACCURATE) {
		case JFFS2_NODETYPE_INODE:
			err = jffs2_scan_inode_node(c, jeb, &ofs);
			if (err) return err;
			break;
			
		case JFFS2_NODETYPE_DIRENT:
			err = jffs2_scan_dirent_node(c, jeb, &ofs);
			if (err) return err;
			break;

		case JFFS2_NODETYPE_CLEANMARKER:
			if (node.totlen != sizeof(struct jffs2_unknown_node)) {
				printk(KERN_NOTICE "CLEANMARKER node found at 0x%08x has totlen 0x%x != normal 0x%x\n", 
				       ofs, node.totlen, sizeof(struct jffs2_unknown_node));
				DIRTY_SPACE(PAD(sizeof(struct jffs2_unknown_node)));
			} else if (jeb->first_node) {
				printk(KERN_NOTICE "CLEANMARKER node found at 0x%08x, not first node in block (0x%08x)\n", ofs, jeb->offset);
				DIRTY_SPACE(PAD(sizeof(struct jffs2_unknown_node)));
				ofs += PAD(sizeof(struct jffs2_unknown_node));
				continue;
			} else {
				struct jffs2_raw_node_ref *marker_ref = jffs2_alloc_raw_node_ref();
				if (!marker_ref) {
					printk(KERN_NOTICE "Failed to allocate node ref for clean marker\n");
					return -ENOMEM;
				}
				marker_ref->next_in_ino = NULL;
				marker_ref->next_phys = NULL;
				marker_ref->flash_offset = ofs;
				marker_ref->totlen = sizeof(struct jffs2_unknown_node);
				jeb->first_node = jeb->last_node = marker_ref;
			     
				USED_SPACE(PAD(sizeof(struct jffs2_unknown_node)));
			}
			ofs += PAD(sizeof(struct jffs2_unknown_node));
			break;

		default:
			switch (node.nodetype & JFFS2_COMPAT_MASK) {
			case JFFS2_FEATURE_ROCOMPAT:
				printk(KERN_NOTICE "Read-only compatible feature node (0x%04x) found at offset 0x%08x\n", node.nodetype, ofs);
			        c->flags |= JFFS2_SB_FLAG_RO;
				if (!(OFNI_BS_2SFFJ(c)->s_flags & MS_RDONLY))
					return -EROFS;
				DIRTY_SPACE(PAD(node.totlen));
				ofs += PAD(node.totlen);
				continue;

			case JFFS2_FEATURE_INCOMPAT:
				printk(KERN_NOTICE "Incompatible feature node (0x%04x) found at offset 0x%08x\n", node.nodetype, ofs);
				return -EINVAL;

			case JFFS2_FEATURE_RWCOMPAT_DELETE:
				printk(KERN_NOTICE "Unknown but compatible feature node (0x%04x) found at offset 0x%08x\n", node.nodetype, ofs);
				DIRTY_SPACE(PAD(node.totlen));
				ofs += PAD(node.totlen);
				break;

			case JFFS2_FEATURE_RWCOMPAT_COPY:
				printk(KERN_NOTICE "Unknown but compatible feature node (0x%04x) found at offset 0x%08x\n", node.nodetype, ofs);
				USED_SPACE(PAD(node.totlen));
				ofs += PAD(node.totlen);
				break;
			}
		}
	}
	D1(printk(KERN_DEBUG "Block at 0x%08x: free 0x%08x, dirty 0x%08x, used 0x%08x\n", jeb->offset, 
		  jeb->free_size, jeb->dirty_size, jeb->used_size));
	return 0;
}