From cd0bc12e8f2c6cdec550b03a324a5917e6441f1b Mon Sep 17 00:00:00 2001 From: wookey Date: Wed, 22 May 2002 12:38:56 +0000 Subject: [PATCH] Added docs and code for use with real MTD as well as emulation, and patched MTD interface --- Documentation/yaffs_on_mtd.html | 77 ++++ mtdemul/Makefile | 29 ++ mtdemul/nandemul.c | 747 ++++++++++++++++++++++++++++++++ mtdemul/nandemul.h | 32 ++ patches/mtdpart.c | 302 +++++++++++++ yaffs_fs.c | 200 +++++---- yaffsdev.proj | Bin 28672 -> 28672 bytes 7 files changed, 1294 insertions(+), 93 deletions(-) create mode 100644 Documentation/yaffs_on_mtd.html create mode 100644 mtdemul/Makefile create mode 100644 mtdemul/nandemul.c create mode 100644 mtdemul/nandemul.h create mode 100644 patches/mtdpart.c diff --git a/Documentation/yaffs_on_mtd.html b/Documentation/yaffs_on_mtd.html new file mode 100644 index 0000000..b994fba --- /dev/null +++ b/Documentation/yaffs_on_mtd.html @@ -0,0 +1,77 @@ + + + + + + + + + + + +

Running up YAFFS using the MTD interface

+

Here are the steps required to get YAFFS going with the +NANDemulation MTD that I have written.

+

Warning: +This is experimental stuff that plugs into the kernel. It has only +been lightly tested. Don't play with this on a box you are not +prepared to reboot.

+

There are a few things you need to do. This document assumes that +you're working from the 2.4.18 kernel code base.

+

Preparing the kernel

+

First off, you need to patch the mtdcore services.

+
    +
  1. Replace the devices/mtd/mtdpart.c file with the one enclosed. + This patches the problem where special NAND functions are not being + copied through the partition handler. The changes are marked with a + comment containing my initials cdhm.

    +
  2. Build the kernel including the following configurations to + support mtd.

    +
+

CONFIG_MTD=y Turn on MTD support

+

CONFIG_MTD_PARTITIONS=y Turn on partition +support

+

CONFIG_MTD_CHAR=y Need char drivers to +access the data from user space.

+

CONFIG_MTD_BLOCK=y Block driver interface +used only to find the device for mounting

+

may as well also set:

+

CONFIG_MTD_DEBUG=y

+
    +

    CONFIG_MTD_DEBUG_VERBOSE=3

    +

    +
+

Setting up yaffs

+
    +
  1. Run the mtd utility MAKEDEV to make the /dev/mtdxxx entries.

    +
  2. Load up the NANDemul MTD by typing
    #/sbin/insmod + mtdemul/nandemul.o
    You should now be able to see the + device in the mtd list by typing
    #cat + /proc/mtd
    If all is well, the device will now be + accessible as /dev/mtd0 and + /dev/mtdblock0 (or whatever).

    +
  3. Now load up the yaffs filesystem module by + typing
    #/sbin/insmod + mtdemul/yaffs.o
    You should now be able + to see the yaffs file systems by typing
    #cat + /proc/filesystems

    +
  4. Now create a mount point:
    #mkdir + /mnt/y

    +
  5. Mount the file system:
    #mount + -t yaffs /dev/mtdblock0 /mnt/y

    +
  6. Well if that all worked you have + YAFFS running on the /mnt/y mount point using the NANDemul mtd.

    +
+

What about real NAND?

+

You might want to try getting going on a +real NAND device.

+

I have not yet done this, but the +NANDemul tests out the mtd interface so in theory it should work on +real NAND too.

+

Note though that since YAFFS applies the +ECC, it does not expect the NAND device to be applying ECC. You +probably want to configure the NAND driver with ECC disabled.

+



+

+ + \ No newline at end of file diff --git a/mtdemul/Makefile b/mtdemul/Makefile new file mode 100644 index 0000000..1817228 --- /dev/null +++ b/mtdemul/Makefile @@ -0,0 +1,29 @@ +#Makefile for NANDemul MTD +# +# NB this is not yet suitable for putting into the kernel tree. +# YAFFS: Yet another FFS. A NAND-flash specific file system. +# +# Copyright (C) 2002 Aleph One Ltd. +# for Toby Churchill Ltd and Brightstar Engineering +# +# Created by Charles Manning +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. + +## Change or override KERNELDIR to your kernel +## comment out USE_xxxx if you don't want these features. + +KERNELDIR = /usr/src/kernel-headers-2.4.18 + +CFLAGS = -D__KERNEL__ -DMODULE -I$(KERNELDIR)/include -O2 -Wall + + +OBJS = nandemul.o + + + +$(OBJS): %.o: %.c + gcc -c $(CFLAGS) $< -o $@ + diff --git a/mtdemul/nandemul.c b/mtdemul/nandemul.c new file mode 100644 index 0000000..12fe39d --- /dev/null +++ b/mtdemul/nandemul.c @@ -0,0 +1,747 @@ +/* + * YAFFS: Yet another FFS. A NAND-flash specific file system. + * + * Copyright (C) 2002 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#define T(x) printk x +#define ALLOCATE(x) kmalloc(x,GFP_KERNEL) +#define FREE(x) kfree(x) + +#define DEFAULT_SIZE_IN_MB 16 + +#define NAND_SHIFT 9 + + +static struct mtd_info nandemul_mtd; + +typedef struct +{ + __u8 data[528]; // Data + spare + int count[3]; // The programming count for each area of + // the page (0..255,256..511,512..527 + int empty; // is this empty? +} nandemul_Page; + +typedef struct +{ + nandemul_Page page[32]; // The pages in the block + __u8 damaged; // Is the block damaged? + +} nandemul_Block; + + + +typedef struct +{ + nandemul_Block **block; + int nBlocks; +} nandemul_Device; + +nandemul_Device device; + +int sizeInMB = DEFAULT_SIZE_IN_MB; + +int nandemul_CalcNBlocks(void) +{ + switch(sizeInMB) + { + case 8: + case 16: + case 32: + case 64: + case 128: + case 256: + case 512: + break; + default: + sizeInMB = DEFAULT_SIZE_IN_MB; + } + return sizeInMB * 64; +} + + +static void nandemul_ReallyEraseBlock(int blockNumber) +{ + int i; + + nandemul_Block *theBlock = device.block[blockNumber]; + + for(i = 0; i < 32; i++) + { + memset(theBlock->page[i].data,0xff,528); + theBlock->page[i].count[0] = 0; + theBlock->page[i].count[1] = 0; + theBlock->page[i].count[2] = 0; + theBlock->page[i].empty = 1; + } + +} + +static int nandemul_DoErase(int blockNumber) +{ + if(blockNumber < 0 || nandemul_CalcNBlocks()) + { + T(("Attempt to erase non-existant block %d\n",blockNumber)); + } + else if(device.block[blockNumber]->damaged) + { + T(("Attempt to erase damaged block %d\n",blockNumber)); + } + else + { + nandemul_ReallyEraseBlock(blockNumber); + } + + return 1; + +} + + +int nandemul_Initialise(void) +{ + int i; + int fail = 0; + int nBlocks = nandemul_CalcNBlocks(); + int nAllocated = 0; + + device.block = ALLOCATE (sizeof(nandemul_Block *) * nBlocks); + + if(!device.block) return 0; + + for(i=0; i damaged = 0; + nAllocated++; + } + } + + if(fail) + { + for(i = 0; i < nAllocated; i++) + { + FREE(device.block[i]); + } + FREE(device.block); + + T(("Allocation failed, could only allocate %dMB of %dMB requested.\n", + nAllocated/64,sizeInMB)); + return 0; + } + + device.nBlocks = nBlocks; + return 1; +} + +int nandemul_DeInitialise(void) +{ + int i; + for(i = 0; i < device.nBlocks; i++) + { + FREE(device.block[i]); + device.block[i] = NULL; + } + + FREE(device.block); + device.block = NULL; + return 1; +} + +int nandemul_GetNumberOfBlocks(__u32 *nBlocks) +{ + *nBlocks = device.nBlocks; + + return 1; +} + +int nandemul_Reset(void) +{ + // Do nothing + return 1; +} + +int nandemul_Read(__u8 *buffer, __u32 pageAddress,__u32 pageOffset, __u32 nBytes) +{ + unsigned blockN, pageN; + + blockN = pageAddress/32; + pageN = pageAddress % 32; + + // TODO: Do tests for valid blockN, pageN, pageOffset + + memcpy(buffer,&device.block[blockN]->page[pageN].data[pageOffset],nBytes); + + return 1; + +} + +int nandemul_Program(const __u8 *buffer, __u32 pageAddress,__u32 pageOffset, __u32 nBytes) +{ + unsigned blockN, pageN, pageO; + + int p0, p1, p2; + int i; + + blockN = pageAddress/32; + pageN = pageAddress % 32; + p0 = 0; + p1 = 0; + p2 = 0; + + // TODO: Do tests for valid blockN, pageN, pageOffset + + + for(i = 0,pageO = pageOffset; i < nBytes; i++, pageO++) + { + device.block[blockN]->page[pageN].data[pageO] &= buffer[i]; + + if(pageO < 256) p0 = 1; + else if(pageO <512) p1 = 1; + else p2 = 1; + } + + device.block[blockN]->page[pageN].empty = 0; + device.block[blockN]->page[pageN].count[0] += p0; + device.block[blockN]->page[pageN].count[1] += p1; + device.block[blockN]->page[pageN].count[2] += p2; + + if(device.block[blockN]->page[pageN].count[0] > 1) + { + T(("block %d page %d first half programmed %d times\n", + blockN,pageN,device.block[blockN]->page[pageN].count[0])); + } + if(device.block[blockN]->page[pageN].count[1] > 1) + { + T(("block %d page %d second half programmed %d times\n", + blockN,pageN,device.block[blockN]->page[pageN].count[1])); + } + if(device.block[blockN]->page[pageN].count[2] > 3) + { + T(("block %d page %d spare programmed %d times\n", + blockN,pageN,device.block[blockN]->page[pageN].count[2])); + } + + return 1; + +} + +int nandemul_CauseBitErrors( __u32 pageAddress, __u32 pageOffset, __u8 xorPattern) +{ + unsigned blockN, pageN; + + + blockN = pageAddress/32; + pageN = pageAddress % 32; + + + // TODO: Do tests for valid blockN, pageN, pageOffset + + device.block[blockN]->page[pageN].data[pageOffset] ^= xorPattern; + + + return 1; + +} + + +int nandemul_BlockErase(__u32 pageAddress) +{ + unsigned blockN; + + blockN = pageAddress/32; + + // TODO: Do tests for valid blockN + // TODO: Test that the block has not failed + + return nandemul_DoErase(blockN); + +} + +int nandemul_FailBlock(__u32 pageAddress) +{ + unsigned blockN; + + blockN = pageAddress/32; + + // TODO: Do tests for valid blockN + // TODO: Test that the block has not failed + + nandemul_DoErase(blockN); + return 1; +} + +int nandemul_ReadId(__u8 *vendorId, __u8 *deviceId) +{ + *vendorId = 0xEC; + *deviceId = 0x75; + + return 1; +} + +int nandemul_CopyPage(__u32 fromPageAddress, __u32 toPageAddress) +{ + __u8 copyBuffer[528]; + + // TODO: Check the bitplane issue. + nandemul_Read(copyBuffer, fromPageAddress,0,528); + nandemul_Program(copyBuffer, toPageAddress,0,528); + + return 1; +} + +int nandemul_ReadStatus(__u8 *status) +{ + *status = 0; + return 1; +} + + +#ifdef CONFIG_MTD_NAND_ECC +#include +#endif + +/* + * NAND low-level MTD interface functions + */ +static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf); +static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf, u_char *ecc_code); +static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf); +static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf); +static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf, + u_char *ecc_code); +static int nand_write_oob (struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf); +static int nand_writev (struct mtd_info *mtd, const struct iovec *vecs, + unsigned long count, loff_t to, size_t *retlen); +static int nand_erase (struct mtd_info *mtd, struct erase_info *instr); +static void nand_sync (struct mtd_info *mtd); + + + +/* + * NAND read + */ +static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + return nand_read_ecc (mtd, from, len, retlen, buf, NULL); +} + +/* + * NAND read with ECC + */ +static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf, u_char *ecc_code) +{ + int start, page; + int n = len; + int nToCopy; + + + + /* Do not allow reads past end of device */ + if ((from + len) > mtd->size) { + *retlen = 0; + return -EINVAL; + } + + + /* Initialize return value */ + *retlen = 0; + + while(n > 0) + { + + /* First we calculate the starting page */ + page = from >> NAND_SHIFT; + + /* Get raw starting column */ + + start = from & (mtd->oobblock-1); + + // OK now check for the curveball where the start and end are in + // the same page + if((start + n) < mtd->oobblock) + { + nToCopy = n; + } + else + { + nToCopy = mtd->oobblock - start; + } + + nandemul_Read(buf, page, start, nToCopy); + + n -= nToCopy; + from += nToCopy; + buf += nToCopy; + *retlen += nToCopy; + + } + + + return 0; +} + +/* + * NAND read out-of-band + */ +static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + int col, page; + + DEBUG (MTD_DEBUG_LEVEL3, + "nand_read_oob: from = 0x%08x, len = %i\n", (unsigned int) from, + (int) len); + + /* Shift to get page */ + page = ((int) from) >> NAND_SHIFT; + + /* Mask to get column */ + col = from & 0x0f; + + /* Initialize return length value */ + *retlen = 0; + + /* Do not allow reads past end of device */ + if ((from + len) > mtd->size) { + DEBUG (MTD_DEBUG_LEVEL0, + "nand_read_oob: Attempt read beyond end of device\n"); + *retlen = 0; + return -EINVAL; + } + + nandemul_Read(buf,page,512 + col,len); + + /* Return happy */ + *retlen = len; + return 0; +} + +/* + * NAND write + */ +static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + return nand_write_ecc (mtd, to, len, retlen, buf, NULL); +} + +/* + * NAND write with ECC + */ +static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf, + u_char *ecc_code) +{ + + int start, page; + int n = len; + int nToCopy; + + + + /* Do not allow reads past end of device */ + if ((to + len) > mtd->size) { + *retlen = 0; + return -EINVAL; + } + + + /* Initialize return value */ + *retlen = 0; + + while(n > 0) + { + + /* First we calculate the starting page */ + page = to >> NAND_SHIFT; + + /* Get raw starting column */ + + start = to & (mtd->oobblock - 1); + + // OK now check for the curveball where the start and end are in + // the same page + if((start + n) < mtd->oobblock) + { + nToCopy = n; + } + else + { + nToCopy = mtd->oobblock - start; + } + + nandemul_Program(buf, page, start, nToCopy); + + n -= nToCopy; + to += nToCopy; + buf += nToCopy; + *retlen += nToCopy; + + } + + + return 0; +} + +/* + * NAND write out-of-band + */ +static int nand_write_oob (struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + int col, page; + + + DEBUG (MTD_DEBUG_LEVEL3, + "nand_read_oob: to = 0x%08x, len = %i\n", (unsigned int) to, + (int) len); + + /* Shift to get page */ + page = ((int) to) >> NAND_SHIFT; + + /* Mask to get column */ + col = to & 0x0f; + + /* Initialize return length value */ + *retlen = 0; + + /* Do not allow reads past end of device */ + if ((to + len) > mtd->size) { + DEBUG (MTD_DEBUG_LEVEL0, + "nand_read_oob: Attempt read beyond end of device\n"); + *retlen = 0; + return -EINVAL; + } + + nandemul_Program(buf,page,512 + col,len); + + /* Return happy */ + *retlen = len; + return 0; + +} + +/* + * NAND write with iovec + */ +static int nand_writev (struct mtd_info *mtd, const struct iovec *vecs, + unsigned long count, loff_t to, size_t *retlen) +{ + return -EINVAL; +} + +/* + * NAND erase a block + */ +static int nand_erase (struct mtd_info *mtd, struct erase_info *instr) +{ + int i, nBlocks,block; + + DEBUG (MTD_DEBUG_LEVEL3, + "nand_erase: start = 0x%08x, len = %i\n", + (unsigned int) instr->addr, (unsigned int) instr->len); + + /* Start address must align on block boundary */ + if (instr->addr & (mtd->erasesize - 1)) { + DEBUG (MTD_DEBUG_LEVEL0, + "nand_erase: Unaligned address\n"); + return -EINVAL; + } + + /* Length must align on block boundary */ + if (instr->len & (mtd->erasesize - 1)) { + DEBUG (MTD_DEBUG_LEVEL0, + "nand_erase: Length not block aligned\n"); + return -EINVAL; + } + + /* Do not allow erase past end of device */ + if ((instr->len + instr->addr) > mtd->size) { + DEBUG (MTD_DEBUG_LEVEL0, + "nand_erase: Erase past end of device\n"); + return -EINVAL; + } + + nBlocks = instr->len >> (NAND_SHIFT + 5); + block = instr->addr >> (NAND_SHIFT + 5); + + for(i = 0; i < nBlocks; i++) + { + nandemul_DoErase(block); + block++; + } + + + + return 0; + + +} + +/* + * NAND sync + */ +static void nand_sync (struct mtd_info *mtd) +{ + DEBUG (MTD_DEBUG_LEVEL3, "nand_sync: called\n"); + +} + +/* + * Scan for the NAND device + */ +int nand_scan (struct mtd_info *mtd) +{ + mtd->oobblock = 512; + mtd->oobsize = 16; + mtd->erasesize = 512 * 32; + mtd->size = sizeInMB * 1024*1024; + + + + /* Fill in remaining MTD driver data */ + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->module = THIS_MODULE; + mtd->ecctype = MTD_ECC_NONE; + mtd->erase = nand_erase; + mtd->point = NULL; + mtd->unpoint = NULL; + mtd->read = nand_read; + mtd->write = nand_write; + mtd->read_ecc = nand_read_ecc; + mtd->write_ecc = nand_write_ecc; + mtd->read_oob = nand_read_oob; + mtd->write_oob = nand_write_oob; + mtd->readv = NULL; + mtd->writev = nand_writev; + mtd->sync = nand_sync; + mtd->lock = NULL; + mtd->unlock = NULL; + mtd->suspend = NULL; + mtd->resume = NULL; + + /* Return happy */ + return 0; +} + +#if 0 +#ifdef MODULE +MODULE_PARM(sizeInMB, "i"); + +__setup("sizeInMB=",sizeInMB); +#endif +#endif + +/* + * Define partitions for flash devices + */ + +static struct mtd_partition nandemul_partition[] = +{ + { name: "NANDemul partition 1", + offset: 0, + size: 0 }, +}; + +static int nPartitions = sizeof(nandemul_partition)/sizeof(nandemul_partition[0]); + +/* + * Main initialization routine + */ +int __init nandemul_init (void) +{ + + // Do the nand init + + nand_scan(&nandemul_mtd); + + nandemul_Initialise(); + + // Build the partition table + + nandemul_partition[0].size = sizeInMB * 1024 * 1024; + + // Register the partition + add_mtd_partitions(&nandemul_mtd,nandemul_partition,nPartitions); + + return 0; + +} + +module_init(nandemul_init); + +/* + * Clean up routine + */ +#ifdef MODULE +static void __exit nandemul_cleanup (void) +{ + + nandemul_DeInitialise(); + + /* Unregister partitions */ + del_mtd_partitions(&nandemul_mtd); + + /* Unregister the device */ + del_mtd_device (&nandemul_mtd); + +} +module_exit(nandemul_cleanup); +#endif + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Charles Manning "); +MODULE_DESCRIPTION("NAND emulated in RAM"); + + + + diff --git a/mtdemul/nandemul.h b/mtdemul/nandemul.h new file mode 100644 index 0000000..1a89590 --- /dev/null +++ b/mtdemul/nandemul.h @@ -0,0 +1,32 @@ +#ifndef __NANDEMUL_H__ +#define __NANDEMUL_H__ + +typedef unsigned char __u8; +typedef unsigned short __u16; +typedef unsigned __u32; + +int nandemul_Initialise(void); +int nandemul_DeInitialise(void); + +int nandemul_GetNumberOfBlocks(__u32 *nBlocks); +int nandemul_Reset(void); +int nandemul_Read(__u8 *buffer, __u32 pageAddress, __u32 pageOffset, __u32 nBytes); +int nandemul_Program(__u8 *buffer, __u32 pageAddress, __u32 pageOffset, __u32 nBytes); + + + +int nandemul_BlockErase(__u32 address); + +int nandemul_ReadId(__u8 *vendorId, __u8 *deviceId); + +int nandemul_CopyPage(__u32 fromAddress, __u32 toAddress); + +int nandemul_ReadStatus(__u8 *status); + +int nandemul_FailBlock(__u32 pageAddress); +int nandemul_CauseBitErrors( __u32 pageAddress, __u32 pageOffset, __u8 xorPattern); + +#endif + + + diff --git a/patches/mtdpart.c b/patches/mtdpart.c new file mode 100644 index 0000000..e28fe39 --- /dev/null +++ b/patches/mtdpart.c @@ -0,0 +1,302 @@ +/* + * Simple MTD partitioning layer + * + * (C) 2000 Nicolas Pitre + * + * This code is GPL + * + * $Id: mtdpart.c,v 1.1 2002-05-22 12:38:56 wookey Exp $ + */ + +#include +#include +#include +#include +#include + +#include +#include + + +/* Our partition linked list */ +static LIST_HEAD(mtd_partitions); + +/* Our partition node structure */ +struct mtd_part { + struct mtd_info mtd; + struct mtd_info *master; + u_int32_t offset; + int index; + struct list_head list; +}; + +/* + * Given a pointer to the MTD object in the mtd_part structure, we can retrieve + * the pointer to that structure with this macro. + */ +#define PART(x) ((struct mtd_part *)(x)) + + +/* + * MTD methods which simply translate the effective address and pass through + * to the _real_ device. + */ + +static int part_read (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct mtd_part *part = PART(mtd); + if (from >= mtd->size) + len = 0; + else if (from + len > mtd->size) + len = mtd->size - from; + return part->master->read (part->master, from + part->offset, + len, retlen, buf); +} + +static int part_write (struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct mtd_part *part = PART(mtd); + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (to >= mtd->size) + len = 0; + else if (to + len > mtd->size) + len = mtd->size - to; + return part->master->write (part->master, to + part->offset, + len, retlen, buf); +} + +static int part_writev (struct mtd_info *mtd, const struct iovec *vecs, + unsigned long count, loff_t to, size_t *retlen) +{ + struct mtd_part *part = PART(mtd); + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + return part->master->writev (part->master, vecs, count, + to + part->offset, retlen); +} + +static int part_readv (struct mtd_info *mtd, struct iovec *vecs, + unsigned long count, loff_t from, size_t *retlen) +{ + struct mtd_part *part = PART(mtd); + return part->master->readv (part->master, vecs, count, + from + part->offset, retlen); +} + +static int part_erase (struct mtd_info *mtd, struct erase_info *instr) +{ + struct mtd_part *part = PART(mtd); + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (instr->addr >= mtd->size) + return -EINVAL; + instr->addr += part->offset; + return part->master->erase(part->master, instr); +} + +static int part_lock (struct mtd_info *mtd, loff_t ofs, size_t len) +{ + struct mtd_part *part = PART(mtd); + if ((len + ofs) > mtd->size) + return -EINVAL; + return part->master->lock(part->master, ofs + part->offset, len); +} + +static int part_unlock (struct mtd_info *mtd, loff_t ofs, size_t len) +{ + struct mtd_part *part = PART(mtd); + if ((len + ofs) > mtd->size) + return -EINVAL; + return part->master->unlock(part->master, ofs + part->offset, len); +} + +static void part_sync(struct mtd_info *mtd) +{ + struct mtd_part *part = PART(mtd); + part->master->sync(part->master); +} + +static int part_suspend(struct mtd_info *mtd) +{ + struct mtd_part *part = PART(mtd); + return part->master->suspend(part->master); +} + +static void part_resume(struct mtd_info *mtd) +{ + struct mtd_part *part = PART(mtd); + part->master->resume(part->master); +} + +/* + * This function unregisters and destroy all slave MTD objects which are + * attached to the given master MTD object. + */ + +int del_mtd_partitions(struct mtd_info *master) +{ + struct list_head *node; + struct mtd_part *slave; + + for (node = mtd_partitions.next; + node != &mtd_partitions; + node = node->next) { + slave = list_entry(node, struct mtd_part, list); + if (slave->master == master) { + struct list_head *prev = node->prev; + __list_del(prev, node->next); + del_mtd_device(&slave->mtd); + kfree(slave); + node = prev; + } + } + + return 0; +} + +/* + * This function, given a master MTD object and a partition table, creates + * and registers slave MTD objects which are bound to the master according to + * the partition definitions. + * (Q: should we register the master MTD object as well?) + */ + +int add_mtd_partitions(struct mtd_info *master, + struct mtd_partition *parts, + int nbparts) +{ + struct mtd_part *slave; + u_int32_t cur_offset = 0; + int i; + + printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name); + + for (i = 0; i < nbparts; i++) { + + /* allocate the partition structure */ + slave = kmalloc (sizeof(*slave), GFP_KERNEL); + if (!slave) { + printk ("memory allocation error while creating partitions for \"%s\"\n", + master->name); + del_mtd_partitions(master); + return -ENOMEM; + } + memset(slave, 0, sizeof(*slave)); + list_add(&slave->list, &mtd_partitions); + + /* set up the MTD object for this partition */ + slave->mtd.type = master->type; + slave->mtd.flags = master->flags & ~parts[i].mask_flags; + slave->mtd.size = parts[i].size; + slave->mtd.oobblock = master->oobblock; + slave->mtd.oobsize = master->oobsize; + slave->mtd.ecctype = master->ecctype; + slave->mtd.eccsize = master->eccsize; + + slave->mtd.name = parts[i].name; + slave->mtd.bank_size = master->bank_size; + + slave->mtd.module = master->module; + + slave->mtd.read = part_read; + slave->mtd.write = part_write; + if (master->sync) + slave->mtd.sync = part_sync; + if (!i && master->suspend && master->resume) { + slave->mtd.suspend = part_suspend; + slave->mtd.resume = part_resume; + } + + if (master->writev) + slave->mtd.writev = part_writev; + if (master->readv) + slave->mtd.readv = part_readv; + if (master->lock) + slave->mtd.lock = part_lock; + if (master->unlock) + slave->mtd.unlock = part_unlock; +/* cdhm: fix add oob functions to partitions */ + if (master->read_oob) + slave->mtd.read_oob = master->read_oob; + if (master->write_oob) + slave->mtd.write_oob = master->write_oob; +/* cdhm: end of fix */ + slave->mtd.erase = part_erase; + slave->master = master; + slave->offset = parts[i].offset; + slave->index = i; + + if (slave->offset == MTDPART_OFS_APPEND) + slave->offset = cur_offset; + if (slave->mtd.size == MTDPART_SIZ_FULL) + slave->mtd.size = master->size - slave->offset; + cur_offset = slave->offset + slave->mtd.size; + + printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset, + slave->offset + slave->mtd.size, slave->mtd.name); + + /* let's do some sanity checks */ + if (slave->offset >= master->size) { + /* let's register it anyway to preserve ordering */ + slave->offset = 0; + slave->mtd.size = 0; + printk ("mtd: partition \"%s\" is out of reach -- disabled\n", + parts[i].name); + } + if (slave->offset + slave->mtd.size > master->size) { + slave->mtd.size = master->size - slave->offset; + printk ("mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#x\n", + parts[i].name, master->name, slave->mtd.size); + } + if (master->numeraseregions>1) { + /* Deal with variable erase size stuff */ + int i; + struct mtd_erase_region_info *regions = master->eraseregions; + + /* Find the first erase regions which is part of this partition. */ + for (i=0; i < master->numeraseregions && slave->offset >= regions[i].offset; i++) + ; + + for (i--; i < master->numeraseregions && slave->offset + slave->mtd.size > regions[i].offset; i++) { + if (slave->mtd.erasesize < regions[i].erasesize) { + slave->mtd.erasesize = regions[i].erasesize; + } + } + } else { + /* Single erase size */ + slave->mtd.erasesize = master->erasesize; + } + + if ((slave->mtd.flags & MTD_WRITEABLE) && + (slave->offset % slave->mtd.erasesize)) { + /* Doesn't start on a boundary of major erase size */ + /* FIXME: Let it be writable if it is on a boundary of _minor_ erase size though */ + slave->mtd.flags &= ~MTD_WRITEABLE; + printk ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n", + parts[i].name); + } + if ((slave->mtd.flags & MTD_WRITEABLE) && + (slave->mtd.size % slave->mtd.erasesize)) { + slave->mtd.flags &= ~MTD_WRITEABLE; + printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n", + parts[i].name); + } + + /* register our partition */ + add_mtd_device(&slave->mtd); + } + + return 0; +} + +EXPORT_SYMBOL(add_mtd_partitions); +EXPORT_SYMBOL(del_mtd_partitions); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nicolas Pitre "); +MODULE_DESCRIPTION("Generic support for partitioning of MTD devices"); + diff --git a/yaffs_fs.c b/yaffs_fs.c index a0ada39..de11303 100644 --- a/yaffs_fs.c +++ b/yaffs_fs.c @@ -14,7 +14,7 @@ * This is the file system front-end to YAFFS that hooks it up to * the VFS. * - * Special notes: + * Special notes: * >> sb->u.generic_sbp points to the yaffs_Device associated with this superblock * >> inode->u.generic_ip points to the associated yaffs_Object. */ @@ -40,7 +40,7 @@ #include "yaffs_guts.h" #ifdef YAFFS_RAM_ENABLED -#include "yaffs_nandemul.h" +#include "yaffs_nandemul.h" // 2 MB of RAM for emulation #define YAFFS_RAM_EMULATION_SIZE 0x200000 #endif // YAFFS_RAM_ENABLED @@ -115,7 +115,7 @@ static struct inode_operations yaffs_dir_inode_operations = { create: yaffs_create, lookup: yaffs_lookup, link: yaffs_link, - unlink: yaffs_unlink, + unlink: yaffs_unlink, symlink: yaffs_symlink, mkdir: yaffs_mkdir, rmdir: yaffs_unlink, @@ -143,18 +143,18 @@ static struct dentry * yaffs_lookup(struct inode *dir, struct dentry *dentry) { yaffs_Object *obj; struct inode *inode; - - + + T((KERN_DEBUG"yaffs_lookup for %d:%s\n",yaffs_InodeToObject(dir)->objectId,dentry->d_name.name)); - + obj = yaffs_FindObjectByName(yaffs_InodeToObject(dir),dentry->d_name.name); - + if(obj) { T((KERN_DEBUG"yaffs_lookup found %d\n",obj->objectId)); - + inode = yaffs_get_inode(dir->i_sb, obj->st_mode,0,obj); - + if(inode) { T((KERN_DEBUG"yaffs_loookup looks good\n")); @@ -167,10 +167,10 @@ static struct dentry * yaffs_lookup(struct inode *dir, struct dentry *dentry) else { T((KERN_DEBUG"yaffs_lookup not found\n")); - + } return NULL; - + } // For now put inode is just for debugging @@ -212,7 +212,7 @@ static int yaffs_commit_write(struct file *file, struct page *page, unsigned off static void yaffs_FillInodeFromObject(struct inode *inode, yaffs_Object *obj) { - if (inode && obj) + if (inode && obj) { inode->i_ino = obj->objectId; inode->i_mode = obj->st_mode; @@ -227,16 +227,16 @@ static void yaffs_FillInodeFromObject(struct inode *inode, yaffs_Object *obj) inode->i_ctime = obj->st_ctime; inode->i_size = yaffs_GetObjectFileLength(obj); inode->i_nlink = yaffs_GetObjectLinkCount(obj); - + T((KERN_DEBUG"yaffs_FillInode mode %x uid %d gid %d size %d count %d\n", inode->i_mode, inode->i_uid, inode->i_gid, (int)inode->i_size, atomic_read(&inode->i_count))); - - switch (obj->st_mode & S_IFMT) + + switch (obj->st_mode & S_IFMT) { default: // init_special_inode(inode, mode, dev); break; - case S_IFREG: // file + case S_IFREG: // file inode->i_op = &yaffs_file_inode_operations; inode->i_fop = &yaffs_file_operations; break; @@ -248,10 +248,10 @@ static void yaffs_FillInodeFromObject(struct inode *inode, yaffs_Object *obj) inode->i_op = &page_symlink_inode_operations; break; } - - + + inode->u.generic_ip = obj; - + } else { @@ -263,7 +263,7 @@ static void yaffs_FillInodeFromObject(struct inode *inode, yaffs_Object *obj) struct inode *yaffs_get_inode(struct super_block *sb, int mode, int dev,yaffs_Object *obj) { struct inode * inode; - + T((KERN_DEBUG"yaffs_get_inode for object %d\n",obj->objectId)); inode = iget(sb,obj->objectId); @@ -279,13 +279,13 @@ static ssize_t yaffs_file_read(struct file *f, char *buf, size_t n, loff_t *pos) yaffs_Object *obj; int nRead,ipos; struct inode *inode; - + T((KERN_DEBUG"yaffs_file_read\n")); obj = yaffs_DentryToObject(f->f_dentry); inode = f->f_dentry->d_inode; - - if (*pos < inode->i_size) + + if (*pos < inode->i_size) { if (*pos + n > inode->i_size) { @@ -296,7 +296,7 @@ static ssize_t yaffs_file_read(struct file *f, char *buf, size_t n, loff_t *pos) { n = 0; } - + nRead = yaffs_ReadDataFromFile(obj,buf,*pos,n); if(nRead > 0) { @@ -305,7 +305,7 @@ static ssize_t yaffs_file_read(struct file *f, char *buf, size_t n, loff_t *pos) ipos = *pos; T((KERN_DEBUG"yaffs_file_read read %d bytes, %d read at %d\n",n,nRead,ipos)); return nRead; - + } static ssize_t yaffs_file_write(struct file *f, const char *buf, size_t n, loff_t *pos) @@ -313,7 +313,7 @@ static ssize_t yaffs_file_write(struct file *f, const char *buf, size_t n, loff_ yaffs_Object *obj; int nWritten,ipos; struct inode *inode; - + obj = yaffs_DentryToObject(f->f_dentry); inode = f->f_dentry->d_inode; nWritten = yaffs_WriteDataToFile(obj,buf,*pos,n); @@ -328,9 +328,9 @@ static ssize_t yaffs_file_write(struct file *f, const char *buf, size_t n, loff_ inode->i_size = *pos; T((KERN_DEBUG"yaffs_file_write size updated to %d\n",ipos)); } - + } - return nWritten; + return nWritten; } @@ -339,17 +339,17 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) yaffs_Object *obj; struct inode *inode = f->f_dentry->d_inode; unsigned long offset, curoffs; - struct list_head *i; + struct list_head *i; yaffs_Object *l; - + char name[YAFFS_MAX_NAME_LENGTH +1]; - + obj = yaffs_DentryToObject(f->f_dentry); - + offset = f->f_pos; - + T(("yaffs_readdir: starting at %d\n",(int)offset)); - + if(offset == 0) { T((KERN_DEBUG"yaffs_readdir: entry . ino %d \n",(int)inode->i_ino)); @@ -370,22 +370,22 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) offset++; f->f_pos++; } - + curoffs = 1; - + //down(&obj->sem); - + list_for_each(i,&obj->variant.directoryVariant.children) { curoffs++; if(curoffs >= offset) - { + { l = list_entry(i, yaffs_Object,siblings); - - yaffs_GetObjectName(l,name,YAFFS_MAX_NAME_LENGTH+1); + + yaffs_GetObjectName(l,name,YAFFS_MAX_NAME_LENGTH+1); T((KERN_DEBUG"yaffs_readdir: %s inode %d\n",name,yaffs_GetObjectInode(l))); - + if(filldir(dirent, name, strlen(name), @@ -396,16 +396,16 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) { goto up_and_out; } - + offset++; - f->f_pos++; + f->f_pos++; } } up_and_out: //up(&obj->sem); - + out: return 0; } @@ -417,10 +417,10 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, int dev) { struct inode *inode; - + yaffs_Object *obj = NULL; yaffs_Object *parent = yaffs_InodeToObject(dir); - + int error = -ENOSPC; if(parent) @@ -433,16 +433,16 @@ static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, int d T((KERN_DEBUG"yaffs_mknod: could not get parent object\n")); return -EPERM; } - + T(("yaffs_mknod: making oject for %s, mode %x\n", dentry->d_name.name, mode)); - switch (mode & S_IFMT) + switch (mode & S_IFMT) { default: - + break; - case S_IFREG: // file + case S_IFREG: // file T((KERN_DEBUG"yaffs_mknod: making file\n")); obj = yaffs_MknodFile(parent,dentry->d_name.name,mode,current->uid, current->gid); break; @@ -455,7 +455,7 @@ static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, int d obj = NULL; // Todo break; } - + if(obj) { inode = yaffs_get_inode(dir->i_sb, mode, dev, obj); @@ -471,7 +471,7 @@ static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, int d T((KERN_DEBUG"yaffs_mknod failed making object\n")); error = -ENOMEM; } - + return error; } @@ -500,9 +500,9 @@ static int yaffs_create(struct inode *dir, struct dentry *dentry, int mode) static int yaffs_unlink(struct inode * dir, struct dentry *dentry) { - + T((KERN_DEBUG"yaffs_unlink\n")); - + if(yaffs_Unlink(yaffs_InodeToObject(dir),dentry->d_name.name) == YAFFS_OK) { return 0; @@ -520,11 +520,11 @@ static int yaffs_unlink(struct inode * dir, struct dentry *dentry) static int yaffs_link(struct dentry *old_dentry, struct inode * dir, struct dentry * dentry) { struct inode *inode = old_dentry->d_inode; - + T((KERN_DEBUG"yaffs_link\n")); - + return -EPERM; //Todo - + if (S_ISDIR(inode->i_mode)) return -EPERM; @@ -537,10 +537,10 @@ static int yaffs_link(struct dentry *old_dentry, struct inode * dir, struct dent static int yaffs_symlink(struct inode * dir, struct dentry *dentry, const char * symname) { int error; - + T((KERN_DEBUG"yaffs_symlink\n")); - + return -ENOMEM; //Todo error = yaffs_mknod(dir, dentry, S_IFLNK | S_IRWXUGO, 0); @@ -560,8 +560,8 @@ static int yaffs_sync_object(struct file * file, struct dentry *dentry, int data */ static int yaffs_rename(struct inode * old_dir, struct dentry *old_dentry, struct inode * new_dir,struct dentry *new_dentry) { - - + + if( yaffs_RenameObject(yaffs_InodeToObject(old_dir),old_dentry->d_name.name, yaffs_InodeToObject(new_dir),new_dentry->d_name.name) == YAFFS_OK) { @@ -571,7 +571,7 @@ static int yaffs_rename(struct inode * old_dir, struct dentry *old_dentry, struc { return -ENOTEMPTY; } - + } @@ -579,12 +579,12 @@ static int yaffs_setattr(struct dentry *dentry, struct iattr *attr) { struct inode *inode = dentry->d_inode; int error; - + T((KERN_DEBUG"yaffs_setattr\n")); - + if((error = inode_change_ok(inode,attr)) == 0) { - + if(yaffs_SetAttributes(yaffs_InodeToObject(inode),attr) == YAFFS_OK) { error = 0; @@ -616,12 +616,12 @@ static int yaffs_statfs(struct super_block *sb, struct statfs *buf) static void yaffs_read_inode (struct inode *inode) { - yaffs_Object *obj ; - + yaffs_Object *obj ; + T((KERN_DEBUG"yaffs_read_inode for %d\n",(int)inode->i_ino)); obj = yaffs_FindObjectByNumber(yaffs_SuperToDevice(inode->i_sb),inode->i_ino); - + yaffs_FillInodeFromObject(inode,obj); } @@ -631,24 +631,25 @@ static struct super_block *yaffs_internal_read_super(int useRam, struct super_bl struct inode * inode; struct dentry * root; yaffs_Device *dev; + - - T(("yaffs_read_super:\n")); + T(("yaffs_read_super: %s\n", useRam ? "RAM" : "MTD")); sb->s_blocksize = YAFFS_BYTES_PER_CHUNK; sb->s_blocksize_bits = YAFFS_CHUNK_SIZE_SHIFT; sb->s_magic = YAFFS_MAGIC; sb->s_op = &yaffs_super_ops; - + if(!sb) - printk(KERN_INFO"sb is NULL\n"); + printk(KERN_INFO"yaffs: sb is NULL\n"); else if(!sb->s_dev) - printk(KERN_INFO"sb->s_dev is NULL\n"); + printk(KERN_INFO"yaffs: sb->s_dev is NULL\n"); else if(! kdevname(sb->s_dev)) - printk(KERN_INFO"kdevname is NULL\n"); + printk(KERN_INFO"yaffs: kdevname is NULL\n"); else - printk(KERN_INFO"dev is %d name is \"%s\"\n", sb->s_dev, kdevname(sb->s_dev)); - + printk(KERN_INFO"yaffs: dev is %d name is \"%s\"\n", sb->s_dev, kdevname(sb->s_dev)); + + if(useRam) { @@ -678,23 +679,36 @@ static struct super_block *yaffs_internal_read_super(int useRam, struct super_bl } else - { + { #ifdef YAFFS_MTD_ENABLED struct mtd_info *mtd; - + + printk(KERN_DEBUG "yaffs: Attempting MTD mount on %u.%u, \"%s\"\n", + MAJOR(sb->s_dev),MINOR(sb->s_dev),kdevname(sb->s_dev)); + // Hope it's a NAND mtd mtd = get_mtd_device(NULL, MINOR(sb->s_dev)); - if (!mtd) + if (!mtd) { printk(KERN_DEBUG "yaffs: MTD device #%u doesn't appear to exist\n", MINOR(sb->s_dev)); return NULL; } + if(mtd->type != MTD_NANDFLASH) { printk(KERN_DEBUG "yaffs: MTD device is not NAND it's type %d\n", mtd->type); return NULL; } + printk(KERN_DEBUG" erase %x\n",mtd->erase); + printk(KERN_DEBUG" read %x\n",mtd->read); + printk(KERN_DEBUG" write %x\n",mtd->write); + printk(KERN_DEBUG" readoob %x\n",mtd->read_oob); + printk(KERN_DEBUG" writeoob %x\n",mtd->write_oob); + printk(KERN_DEBUG" oobblock %x\n",mtd->oobblock); + printk(KERN_DEBUG" oobsize %x\n",mtd->oobsize); + + if(!mtd->erase || !mtd->read || !mtd->write || @@ -704,16 +718,16 @@ static struct super_block *yaffs_internal_read_super(int useRam, struct super_bl printk(KERN_DEBUG "yaffs: MTD device does not support required functions\n"); return NULL; } - + if(mtd->oobblock != YAFFS_BYTES_PER_CHUNK || mtd->oobsize != YAFFS_BYTES_PER_SPARE) { printk(KERN_DEBUG "yaffs: MTD device does not support have the right page sizes\n"); return NULL; } + - - // OK, so if we got here, we have an MTD that's NAND and looks + // OK, so if we got here, we have an MTD that's NAND and looks // like it has the right capabilities // Set the yaffs_Device up for ram emulation @@ -726,11 +740,11 @@ static struct super_block *yaffs_internal_read_super(int useRam, struct super_bl } memset(dev,0,sizeof(yaffs_Device)); - dev->genericDevice = mtd; + dev->genericDevice = mtd; // Set up the memory size parameters.... - - dev->nBlocks = mtd->size / (YAFFS_CHUNKS_PER_BLOCK * YAFFS_BYTES_PER_CHUNK); + + dev->nBlocks = YAFFS_RAM_EMULATION_SIZE / (YAFFS_CHUNKS_PER_BLOCK * YAFFS_BYTES_PER_CHUNK); dev->startBlock = 1; // Don't use block 0 dev->endBlock = dev->nBlocks - 1; @@ -739,7 +753,7 @@ static struct super_block *yaffs_internal_read_super(int useRam, struct super_bl dev->readChunkFromNAND = nandmtd_ReadChunkFromNAND; dev->eraseBlockInNAND = nandmtd_EraseBlockInNAND; dev->initialiseNAND = nandmtd_InitialiseNAND; - + #endif } @@ -754,7 +768,7 @@ static struct super_block *yaffs_internal_read_super(int useRam, struct super_bl return NULL; T(("yaffs_read_super: got root inode\n")); - + root = d_alloc_root(inode); @@ -808,9 +822,9 @@ static int yaffs_proc_read( if (offset > 0) return 0; /* Fill the buffer and get its length */ - sprintf( my_buffer, + sprintf( my_buffer, "YAFFS built:"__DATE__ " "__TIME__"\n" - + ); strcpy(page,my_buffer); @@ -820,7 +834,7 @@ static int yaffs_proc_read( static int __init init_yaffs_fs(void) { int error = 0; - + printk(KERN_DEBUG "yaffs " __DATE__ " " __TIME__ " Initialisation\n"); /* Install the proc_fs entry */ my_proc_entry = create_proc_read_entry("yaffs", @@ -860,12 +874,12 @@ static void __exit exit_yaffs_fs(void) printk(KERN_DEBUG "yaffs " __DATE__ " " __TIME__ " Clean up\n"); remove_proc_entry("yaffs",&proc_root); - + #ifdef YAFFS_RAM_ENABLED - unregister_filesystem(&yaffs_ram_fs_type); + unregister_filesystem(&yaffs_fs_type); #endif #ifdef YAFFS_MTD_ENABLED - unregister_filesystem(&yaffs_fs_type); + unregister_filesystem(&yaffs_ram_fs_type); #endif } diff --git a/yaffsdev.proj b/yaffsdev.proj index 233551187effaeeb5015185a415ac0c2bbd4a863..b6f80f5aa93e77e197762d84bdab35ebc16ac520 100644 GIT binary patch delta 318 zcmV-E0m1%&-~oW(0gyTZ``fWR#2*1PlK~)U2VxA+3?U4YvvD9I3>ABGZ*_8GWo}`1 zEifP>GJPO1GB7ohr7S2UdvkehVsaohHy|-MI3P1LF(5QKIUqAMF?cjNIV(0dD={@S zv-B)9BOx&aBm@!!1_T5I^#kPt)C1B3zyrPmu>-IJ0tf&AW&^OZNl;`10wVyEtW$yu zS8{1|Wgv86XLBHvE>x?t=Tu`0M~4i53~~$?1qlUT1XBc31VscT0}}%d0|Em81M~vo z0@MP`0>J{m060BX(zKXW#HbUu9%zbO2;!W?^+~bO2;x QUuAe>Y;|O1vyp6X1H%Yly8r+H delta 307 zcmZp8z}WDBae@`oALflVN8}mJCNnBzvLEA_%A?FPb#tMD5|2=EesO+jQF3ZtVwtXi zf}w$t!Q>S>8tQ5J4AsSzxk*I|rltyp#wH5JCWZ&%(^Y$nuH#5%Wdn^UQmhcQdbJUdznH!N8Erymqr^KnnBZ4Z)3*Z9-P_DFgv+ z%S}}X&CAKm%ijDvB$?a2l}CkDj5UWPfhC?Lh{cFmfmxcFpP84Lk@-E-L#CTd=a|kg z?PJ=`w2WynQ!i6DQ#Dg1QwCESQv_2mlQ)wmlO2;8(-KCr%@Y$=Gfw79(%2lHbb@K} z0%f+%U8&F6CM#siP2LyEJ~>TQX0m+_FObbPd1<;hFGEIVaY=qrWqfK%X36G>Ir+>0 Dxrtp_ -- 2.30.2