uClinux flash device mini howto
时间:2006-06-03 来源:rwen2012
uClinux flash device mini howto
*******************************
Author: Bernhard Kuhn, Lineo Inc.
Revision: 0.1
Abstract:
This document describes how to set up the Motorola M5272C3
evaluation board 'Tarifa' to boot from flash and how to
boot from a cramfs partition instead of an kernel-attached
romfs image. This paper presumes that you already know how
to boot Linux on Tarifa from RAM with an romfs image as
root filesystem attached to the kernel image.
Content:
o Choosing a bootloader
o The Flash Device Partition layout
o Installing Colilo
o Attaching the Linux-Image to Colilo, saving flash memory
o Other Boot strategies
o Generating a cramfs images
o Creating an empty romfs image
o Creating a compressed kernel for flashing
o System recovery using the BDM interface cable
o Unresoled Issues
o List of Resources
o (No) Warranty
o Thanks
Choosing a bootloader
=====================
The Tarifa board (Motorola M5272C3 evaluation board) is
shipped with a pre-installed bootloader called
"dBUG". This bootloader allows the user to download
images from a tftp server to memory (RAM) starting at 0x20000
(refer to the "dn" command in the dBUG manual) and to
execute the kernel from there ("go 20000"). Additionaly, downloaded
images can be moved to flash memory (7 x 256KB starting at
0xffe40000, see "upuser" in the Tarifa/dBUG manual).
Those who don't want to remove the pre-installed bootloader
can choose to install a bootloader-kernel-rootfs combination
in the topmost one megabyte (0xfff00000 to 0xffffffff) of the
flash device and after altering jumper 13 to position 2-3, the M5272C3
boots from there (but this option limits the available amount
of flash memory to only one megabyte: the area from 0xffe00000
to 0xffeffffff is just a mirror of the topmost one megabyte
in this mode). As dBUG (as pre-installed) cannot be configured
to automatically start the kernel, you will have to install
another bootloader to have a kernel-kickstarter and in order to
make fully use of the two megabytes of flash memory the Tarifa
board provides. The bootloader prefered in this document
is called "Colilo" and is available under the terms of the GPL.
Unfortnuatly, Colilo is not able to load images via the
tftp mechanism, so dBUG is probably the better
choice during development. For the first steps, you might
want to have dBUG booted by default (which is located
at the first 256KB of the flash memory from 0xffe00000 to
0xffe3ffff) and invoke colilo (that resides at the first 256KB
of the topmost one megabyte from 0xfff00000 to 0xfff3ffff)
by typing "go fff00400". So switching jumper 13 allows
you to either choose booting from dBUG (and subsequently
starting colilo) or colilo directly.
The Flash Device Partition layout
=================================
The uClinux-distribution (as is) from www.uclinux.org makes
use of the block memory device driver (linux/drivers/block/blkmem.c).
This driver already provides flashing algorithms that allows
the user to write to a broad range of flash device (refer to
blkmem.c, and the flashw/netflash utilities in the
uClinux-distribution user tree for more information
on that topic). But the Memory Technology Device drivers
(linux/drivers/mtd) are offering more options and probably
will be the better supported drivers for flash devices
in the future. This paper concentrates on MTD.
In this context, please note that the
journaling flash file system (jffs) apparently does only work in
combination with MTD drivers, but not with the blkmem driver.
The standard uClinux-2.4.x kernel (CVS version) can be
compiled that way that it recognises the 2 MByte flash
on the Tarifa board using the "physmap" driver
(start=0xffe0000, size=0x200000, buswidth=2).
But in this case, there is only one big 2MB partition.
This would make the process of flashing the
bootloader, kernel and rootfs seperatly more complex.
For this reason, i have introduces a dedicated mapping
driver for the Tarifa board [TARIFAMAP]. BTW.: generating
a mapping device driver file is rather easy: just copy "physmap.c",
add the partition table and replace the add_mtd_device()
call in init_physmap() by an apropriate
add_mtd_partitions()-call (compare physmap.c and tarifa.c
for details). Here is the list of the pre-defined partitions:
mtd0 / rom0: 0xffe00000-0xffe03fff : "boot (16K)"
mtd2 / rom1: 0xffe80000-0xffefffff : "kernel (512K)"
mtd4 / rom2: 0xfff00000-0xffffffff : "rootfs (1024K)"
mtd6 / rom3: 0xffe04000-0xffe05fff : "spare (8K)"
mtd8 / rom4: 0xffe06000-0xffe07fff : "spare (8K)"
mtd10 / rom5: 0xffe40000-0xffe7ffff : "spare (256K)"
mtd12 / rom6: 0xffe00000-0xffffffff : "complete (2048K)"
mtd14 / rom7: 0xfff00000-0xfff3ffff : "boot J13 (256K)"
mtd16 / rom8: 0xfff40000-0xfffbffff : "kernel J13 (512K)"
mtd18 / rom9: 0xfffc0000-0xffffffff : "rootfs J13 (256K)"
First of all, you may have recognized that the minor
numbers for the MTD character device files (major 90)
do not correspond to those of the mtdblock device files
(major 31, also known as /dev/rom*) for a reason i don't
know - maybe you can tell me :-)
Additionaly, the mapping looks a little bit fragmented.
This is due to the fact that the first 256KB memory block
on the flash device of the Tarifa board is fragmented in
in smaller eraseable flash blocks for bootloaders and
configuration space. BTW.: the 224KByte from 0xffe08000
to 0xfffe3ffff is not directly mappable (the MTD driver
will complain that the mapping is not block aligned),
but can be accessed via mtd12/rom6, if necessary.
mtd14, mtd16 and mtd18 are only necessary when playing
around with jumper 13: if you are just using the topmost
one megabyte of the flash, then you don't have the
need to delete the pre-installed dBUG bootloader
(if you don't have a BDM interface cable, this might be
the best option for you, since otherwise you probably cannot
restore a working bootloader once you have accidently flashed
a non-working bootloader, kernel or root filesystem).
Attention: overwritting flash partitions that already
contain data doesn't work. You will have
to check out the "erase <device> [<first block>] [<number of blocks>]"
command (loacted in mtd/utils) from [MTDUTILS] (or just download
a recent version of erase.c from [ERASESRC]).
Cross-compile erase.c manualy by typing somewhat like
# m68k-elf-gcc -m5200 -O2 -D__linux__ -D__uClinux__ -D__ELF__ -DNO_MM \
-o erase erase.c -Wl,-elf2flt,"-T /opt/uClinux/m68k-elf/lib/elf2flt.ld" -lc
Alternatively, you can download an executable FLAT file
for tarifa from [ERASECMD]. Copy the binary into your romfs/bin
directory.
It is a little bit confusing that if you are accessing
the flash via /dev/mtd12, then you have eight blocks of 256KB each,
instead of the fragmented layout decribed above. So to erase the
flash device completly, you have to type
# erase /dev/mtd12 0 8
(8 Blocks of 256KB each, staring at Block 0)
Installing Colilo
=================
Ensure that you have a m68k-elf-toolchain installed
on your host system. Then download Colilo from [COLILOSRC]
and patch it [COLILOPATCH] in order to compile Colilo
bootable from the topmost one megabyte by uncommenting
"TOP1MB = y" in the Makefile (this is the recommended
first step before overwritting the pre-installed dBUG
bootloader). The patch is also necessary if you like
to have Colilo located in the lower one megabyte, because
the unpatched version would search the (compressed)
image at 0xffe20000, while the pre-defined kernel partition
starts at 0xffe40000 according to the partition layout
descibed above - flash partitions always have to be
flash erase block aligned!
If you don't use the RPMs provided at [M68KRPMS]
for the compiler toolchain, then you also have to modifiy
the entries CF_ROOT and GCC_EXEC_PREFIX. Additonaly,
ensure that the option "CONFIG_M5272C3 = 1" is uncommented.
You also my like to uncomment "BOOTDEBUG = 1" to
get some serial output from Colilo during the boot
process. Additionaly, you may like to uncomment
"CONFIG_UI = 1". This will provide you with
a simple command line tool (but then, you are probably
better off with dBUG). As the (lower) bootloader partition
is only 16 KByte in size, you cannot flash Colilo
to /dev/mtd0 if it is bigger than that - in this case,
use /dev/mtd12 instead, but be aware that you might
not be able to use /dev/rom1 to /dev/rom2 any more,
because these partitions are overlapping with the
installed bootloader. These issues certainly don't
arrise when using the 256KB bootloader partition
on the topmost one megabyte ([COLILOIMAGEUI]).
Now, just type "make" and you should optain a
file named "colilo.bin". Build a kernel+romfs
that includes MTD support. MTD has to be without caching
or read only block device support compiled in, or there will
be conflicts with the blockmem device driver which
uses the same major number. Ensure that you have direct
character MTD support enabled in order to access the flash
partitions anyhow. Before building the kernel, patch
it in order to the flash partitions described
above in this document. BTW.: don't forget to have
MTD partitioning support, the CFI driver, the command
set for for AMD CFI flash devices and the mapping driver
for the Tarifa board enabled. If you are now a little
bit confused about which kernel configuration options
have to be enabled, then you can find an example
.config file at [CONFROMFS]. Alternativly, download
a precompiled image from [ROMFSIMAGE].
After downloading the image (kernel+romfs) to the target RAM
(via dBUG) and executing it from there as usual ("go 20000")
the system should start from the kernel-attached root filesystem (romfs).
After configuring the network and nfs-mounting the
directory where the precompiled colilo.bin resides,
you can move colilo.bin to it's destination partition
(/dev/mtd*) on the flash device using the command "dd"
(i.e. dd if=colilo.bin of=/dev/mtd14)
In case you didn't already generated the mtd character device files
in your romfs/dev directory (with i.e. touch "@mtd0,c,90,0"),
you can temporarily generate them, i.e. with "mknod /tmp/mtd0 c 90 0".
Attaching the Linux-Image to Colilo, saving flash memory
========================================================
You can edit Colilo in main.c (look for "#define IMAGE_ADDR 0xffe80000"
in line 172) so that it searches the (compressed) kernel at 0xffe04000,
using all the "spare" partitions. In this case, you have to
flash Colilo+Linux as a combined image to /dev/mtd12. Generate
this combined image with the following command sequence:
# BS=16 # alignement: 16 KByte
# dd if=/dev/zero bs=1024 count=$BS of=null.bin
# cat colilo.bin null.bin > colilo-null.bin
# dd if=colilo-null.bin bs=1024 count=$BS of=colilo-aligned.bin
# cat colilo-aligned.bin imagez.bin > imagez-moreflash.bin
If colilo grows bigger than 16 KByte then adjust
the "BS" value (i.e. to 32 KByte) and line 172 in
main.c (i.e. #define IMAGE_ADDR 0xffe08000).
On the target, delete the first 512 KB of flash memory
and transfer the combined image (don't forget to
setup the network and nfs-mount the directory where
the images resides):
# erase /dev/mtd12 0 2
# dd if=imagez-moreflash.bin of=/dev/mtd12
Now feel free to use the 512KB partition intended for
the kernel (/dev/rom1) for other purposes.
Other Boot strategies
=====================
There are a lot of options on how to boot the kernel
and on how to access to root filesystem. Among them,
i have noted down a few with their pros and cons:
1) Booting from dBUG and loading kernel+romfs to RAM
----------------------------------------------------
This is probably the best boot method during development,
because dBUG offers you to download the kernel+romfs
via ethernet from a tftp server. Find an sample image at
[ROMFSIMAGES] (unpack with bunzip2 before moving to /tftpboot)
2) Booting from Colilo and kernel+romfs decrompressed into RAM
--------------------------------------------------------------
This variant is simple to handle, but the romfs is always
decompressed to RAM. This may be critical if you have limited
resources.
3) Booting from Colilo, kernel decrompessed into RAM and cramfs in ROM
----------------------------------------------------------------------
As flash devices are usualy more expensive than DRAM, it is a
good idea to have the kernel stored compressed in the flash and
decompressed by colilo into DRAM. Also, executing the
kernel from the 32-bit wide DRAM is usualy faster than
executing it from the 16-bit wide flash memory.
Note: Having the cramfs in flash makes rootfs updates
rather difficult, because when erasing the old cramfs
(in order to flash the new one to the partition), the
kernel may go crasy, because the rootfs was mounted
at that time. I didn't yet worked out a strategy
on how to update a cramfs in place. If you intend
to have a simple flash update option, then you are probably
better off with boot strategy 2).
If you like, you can play arround with pre-compiled images
downloadable from [CRAMFSIMAGES].
4) Booting from Colilo, kernel executed from ROM and cramfs in ROM
-----------------------------------------------------------------
Although it is IMHO better to have the kernel decompressed
to RAM, you may like to run the kernel from ROM.
To archive this capability, you will have to patch
the kernel [ROMEXEC] and compile it with the option
"(ROM) kernel executes from". Please note, that this
patch currently only works with dBUG ("upuser" and
"go ffe40400"). To be able to automatically boot the kernel
from ROM, either colilo has to be adapted a little bit
(just jump to 0xffe40400 after controller initialisation),
or you could forget that bootloader at all and initialize the
controller in the file crt0_rom.S of the kernel
(would be pretty easy, but was not yet done).
BTW: If you are using an attached romfs instead of a seperate
cramfs partition, then even the user space binaries
are executable in place (XIP) thanks to the FLAT binaries.
Note: Having the kernel booted from ROM makes it
even harder to do remote updates, because deleting
the flash partition while the kernel is running from there
will lead you into serious trouble :-)
5) Using Jumper 13: options 2)-4) in topmost 1 MByte of ROM
-----------------------------------------------------------
If you don't wan't to delete dBUG, then you can also
boot from the topmost 1MB of the flash by setting
jumper 13 to position 2-3, or by typing "go fff00400"
after booting from dBUG (jumper 13 set to 1-2).
Have a look at [CRAMFSIMAGES1MB] for example images
(use default cramfs images).
Generating a cramfs images
==========================
In order to generate a big endian cramfs on an little
endian host, you have to patch mkcramfs.c (to be
found in i.e. linux-2.4.x/scripts/cramfs). A recent patch
against a recent mkcramfs.c [MKCRAMSRC] can be found
at [MKCRAMPATCH]. Patch and compile the sources for the host:
# patch mkcramfs-2.4.12.c < mkcramfs-2.4.12-endianessaware.c.patch
# gcc -O2 -o mkcramfs mkcramfs-2.4.12.c -lz
# cp mkcramfs /usr/local/bin
# chmod a+x /usr/local/bin
Now generate a romfs directory as usaly.
If you have done that from within the uClinux-distribution,
then the device files "@*" in the /dev directory
won't be suitable. You will either have to regenerate
them as real device nodes manualy (with "mknod"), or you can
use a simple shell script [CONVDEVNODES] that does the some
job automatically (just execute it in within the
romfs/dev directory. You will have to be root to do so).
Now generate the cramfs image by typing:
# mkcramfs -Nb romfs cramfs.img
This image can then be transfered to the flash partition
with the "dd" command: boot the target with the
"romfs-version" described above, setup the network,
nfs-mount to directory where the cramfs image resides
and type something like "dd if=cramfs.img of=/dev/mtd18".
Don't forget to erase the partition before flashing.
Creating an empty romfs image
=============================
Usualy, an romfs image is attached to the kernel image.
When using a root filesystem ontop of a cramfs in within an
flash partition, then you certainly don't need to
wast flash memory by attaching a romfs full of
binaries and data to the kernel.
But the problem is that crt0_ram.S for Coldfire always tries
to relocate an attached romfs after the .bss section. So the
kernel very likly crashes if no romfs is attached at
all (because it reads some nonsens romfs image size
information and tries to relocate megabytes of a
non-existent romfs image).
The solution is to just use a very small pseude-romfs image
file [EMPTYROMFS] (16 Bytes in size) generated from a simple
shell script [MKEMPTYROMFS].
Creating a compressed kernel for flashing
=========================================
If you are using the uClinux-distribution from uclinux.org, then
just replace "romfs.img" in the "images" directory with the empty
romfs image described above and type "make image" in the top level
directory of the distribution. Alternativly, you can create the
image manualy with the following complex command sequence
(in case you have set up your own build environement):
ADDR=`m68k-elf-objdump --headers $LINUXKERNEL_ELF | \
grep .init | cut -d' ' -f 13,15 | xargs printf "0x%s 0x%s\n" | \
xargs printf "%d + %d\n" |xargs expr |xargs printf "0x%x\n"`;\
m68k-elf-objcopy --add-section=.romfs=$ROMFS_IMG \
--adjust-section-vma=.romfs=$${ADDR} --no-adjust-warnings \
--set-section-flags=.romfs=alloc,load,data \
$LINUXKERNEL_ELF $IMAGE_ELF 2> /dev/null
m68k-elf-objcopy -O binary $LINUXKERNEL_ELF $LINUXKERNEL_BIN
cat $LINUXKERNEL_BIN $ROMFS_IMG > $IMAGE_BIN
../uClinux-distribution/tools/cksum -b -o 2 $IMAGE_BIN >> $IMAGE_BIN
gzip -c -9 $IMAGE_BIN > $IMAGEZ_BIN
../uClinux-distribution/tools/cksum -b -o 2 $IMAGEZ_BIN >> $IMAGEZ_BIN
You then can flash the compressed kernel (plus empty romfs image)
with i.e. "dd if=imagez.bin of=/dev/mtd16".
Don't forget to have the mtdblock device driver enabled
but the blkmem device driver disabled. [CONFCRAMFS] gives
you an example for a convenient kernel .config file.
Also, don't forget to change the root device accordingly
to where you have stored the cramfs image: invoke
the kernel configuration tool (i.e. "make menuconfig")
and change "Processor type and features ---> Kernel Boot Parameter"
to i.e. "CONSOLE=/dev/ttyS0,19200 root=1f09" in case you have
transfered the cramfs image to /dev/mtd18 (/dev/rom9).
System recovery using the BDM interface cable
=============================================
If you have accidently damaged the bootloader on
the flash, or the newly flashed kernel (or the
cramfs image) doesn't work as expected and you can't
re-flash the system from dBUG or a properly
running linux system any more, then you have
to recover the bootloader, the kernel or
the cramfs using an BDM interface cable:
First, generate a RAM-kernel with MTD support
and an attached romfs to boot from (described somewhere above)
Ensure that the kernel comes along without MTD block
device interface, not to interfere with the blkmem device
for the romfs - but ensure that you have direct character
flash device access compiled in. Alternativly, you may
use flashw/netflash in combination with the block memory device
driver - but in this case you will have to add some partition
information in blkmem.c manualy. Another alternative
is to download a pre-compiled ELF file from [ROMFSIMAGEELF].
With this image, you can again access all
flash partitions. If Colilo is ok, but the
kernel doesn't boot properly, the DRAM (where to
download kernel+romfs) was correctly initialised after
power up and you can begin downloading the
ELF file to the target using the BDM interface cable:
m68k-bdm-elf-gdb image.elf
[...]
(gdb) target bdm /dev/bdmcf0
(gdb) load
(gdb) set $pc=0x20000
(gdb) c
If the bootloader is broken and the DRAM was not
properly initialised during power on, then
you first have to execute a simple memory
initialisation program ([DRAMINIT],[DRAMINITELF])
using the builtin 4KB SRAM before you can download
and execute your kernel from DRAM.
m68k-bdm-elf-gdb image.elf
[...]
(gdb) target bdm /dev/bdmcf0
(gdb) set $rambar = 0x20000001
(gdb) set $mbar = 0x10000001
(gdb) load boot.elf
(gdb) set $pc=0x20000400
(gdb) c
[...]
(gdb) load
(gdb) set $pc=0x20000
(gdb) c
Please note, that this rescue procedure usualy doesn't work
out of the box! I always had to bother arround loading the
draminit routing, the kernel+romfs image and the draminit routine
again several times until the kernel was pleased enough to start. YMMV
Unresoled Issues
================
o minor numbers for MTD character device drivers don't match
those tolled in /proc/mtd (but those of the MTD block device
driver do)
o I didn't got jffs working conveniently: the kernel crashes
after writing something to the partition.
(the Partition was erased before mounting with "-t jffs").
List of Resources
=================
[TARIFAMAP]
Mapping driver for M5272C3
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/kernel/patches/uClinux-2.4.x-M5272C3-MTDpartitions.patch
[MTDUTILS]
MTD sources and utilities (CVS)
http://www.linux-mtd.infradead.org
[ERASECMD]
erase executable for MCF5272
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/mtd-erase/bin/erase
[ERASESRC]
erase sources for MTD
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/mtd-erase/erase.c
[COLILOSRC]
Colilo sources mirrored snapshot from 20011106
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/colilo/colilo-20011106.tar.gz
[COLILOPATCH]
Patch to search (compressed) image at 0xffe40000/0xfff40000
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/colilo/colilo-20011106-tarifa.patch
[M68KRPMS]
m68k-elf-toolchain RPMs for RedHat 6.2 (x86)
http://www.rcs.ei.tum.de/~kuhn/uclinux/uClinux-m68k-elf-toolchain/20010718/RPMS
[CONFROMFS]
kernel .config example for MTD support (using an attached romfs)
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/kernel/sample-config/config-romfs
[ROMEXEC]
Patch to run kernel in flash memory
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/kernel/patches/uClinux-2.4.x-M5272C3-bootfromrom.patch
[MKCRAMSRC]
Sources for mkcramfs (non-endianess aware)
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/mkcramfs/mkcramfs-2.4.12.c
[MKCRAMPATCH]
Patch to make mkcramfs endianess aware
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/mkcramfs/mkcramfs-2.4.12-endianessaware.c.patch
[CONVDEVNODES]
Script to convert bogus device files into real device files
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/utils/convdev.sh
[EMPTYROMFS]
Empty romfs image
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/emptyromfs/romfs.img
[MKEMPTYROMFS]
Script to generate an empty romfs image
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/emptyromfs/mkemptyromfs.sh
[ROMFSIMAGES]
Sample kernel+romfs with MTD driver for M5272C3
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/images/boot_romfs/image.bin.bz2
[ROMFSIMAGEELF]
Sample kernel+romfs with MTD driver for M5272C3, ELF version
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/images/boot_romfs/image.elf.bz2
[CRAMFSIMAGES]
Some sample colilo, kernel and cramfs images for M5272C3
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/images/boot_cramfs/default
[CRAMFSIMAGES1MB]
Some sample colilo, kernel and cramfs images for M5272C3 in topmost one megabyte
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/images/boot_cramfs/TOP1MB
[DRAMINIT]
Sources for initialising DRAM from SRAM
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/draminit/draminit-5272.tgz
[DRAMINITELF]
Binary for initialising DRAM from SRAM (use gdbinit.draminit)
http://www.rcs.ei.tum.de/~kuhn/uclinux/coldfire/tarifa/20011119/draminit/tarifa.elf
(NO) Warranty
=============
I certainly cannot provide any warranty. However this
document was put together with best intensions, i cannot
held responsible for any demage (physicaly or mentaly)
that my result by following the instructions given above.
Thanks
======
Many thanks to the developers at Snapgear, namely
David McCullough and Greg Ungerer for providing
the execelent uClinux-Distribution.
Thanks are also certainly going to Travis Griggs from
Key Technology Inc. for his version of Colilo for
the M5272C3. Additionaly, I definitly have to thank
Vipin Malik for providing the mtd-jffs-HOWTO, which
was a good starting point for understanding how
to handle MTD.
相关阅读 更多 +