From a96e96064a7772e1e500c414d46832daa84d1578 Mon Sep 17 00:00:00 2001 From: Qubot <1445788683@qq.com> Date: Fri, 20 Oct 2023 19:54:56 +0800 Subject: [PATCH] first commit --- .travis.yml | 50 + LICENSE.md | 361 +++++++ Makefile | 211 ++++ README.md | 158 +++ adb-devprobe.sh | 50 + autoversion.sh | 18 + bin/fel-sdboot.sunxi | Bin 0 -> 8192 bytes bin/jtag-loop.sunxi | Bin 0 -> 512 bytes bin/ramboot.scr | Bin 0 -> 246 bytes bin/ramboot.uboot-sh | 7 + bin/uart0-helloworld-sdboot.sunxi | Bin 0 -> 8192 bytes boot_head.S | 42 + boot_head.lds | 28 + bootinfo.c | 384 +++++++ common.h | 48 + fel-gpio | 44 + fel-remotefunc-compiler.rb | 149 +++ fel-remotefunc-spi-data-transfer.c | 187 ++++ fel-remotefunc-spi-data-transfer.h | 147 +++ fel-sdboot.S | 69 ++ fel-sdboot.lds | 28 + fel-spiflash.c | 606 +++++++++++ fel-spiflash.h | 34 + fel.c | 1494 ++++++++++++++++++++++++++++ fel_lib.c | 873 ++++++++++++++++ fel_lib.h | 92 ++ fexc.c | 338 +++++++ fexc.h | 30 + find-arm-gcc.sh | 13 + fit_image.c | 282 ++++++ fit_image.h | 32 + include/list.h | 84 ++ include/portable_endian.h | 125 +++ include/types.h | 46 + jtag-loop.S | 41 + jtag-loop.c | 36 + jtag-loop.lds | 28 + meminfo.c | 791 +++++++++++++++ nand-common.h | 29 + nand-image-builder.c | 1111 +++++++++++++++++++++ nand-part-a10.h | 81 ++ nand-part-a20.h | 83 ++ nand-part-main.c | 117 +++ nand-part.c | 327 ++++++ phoenix_info.c | 173 ++++ pio.c | 432 ++++++++ progress.c | 165 +++ progress.h | 41 + script.c | 268 +++++ script.h | 120 +++ script_bin.c | 356 +++++++ script_bin.h | 60 ++ script_extractor.c | 42 + script_fex.c | 379 +++++++ script_fex.h | 23 + script_uboot.c | 256 +++++ script_uboot.h | 22 + soc_info.c | 602 +++++++++++ soc_info.h | 145 +++ sunxi-fel.1 | 270 +++++ tests/Makefile | 62 ++ tests/fextest.sh | 35 + tests/test_all_fex.sh | 23 + tests/test_bin2fex_corner_cases.sh | 28 + tests/test_fex2bin_corner_cases.sh | 89 ++ tests/unify-fex.c | 148 +++ thunks/Makefile | 35 + thunks/README.md | 20 + thunks/clrsetbits.S | 17 + thunks/clrsetbits.h | 9 + thunks/fel-to-spl-thunk.S | 162 +++ thunks/fel-to-spl-thunk.h | 86 ++ thunks/memcpy.S | 70 ++ thunks/memcpy.h | 55 + thunks/objdump_to_h.awk | 33 + thunks/readl_writel.S | 41 + thunks/readl_writel.h | 24 + thunks/rmr-thunk.S | 26 + thunks/rmr-thunk.h | 13 + thunks/sid_read_root.S | 72 ++ thunks/sid_read_root.h | 28 + uart0-helloworld-sdboot.c | 712 +++++++++++++ uart0-helloworld-sdboot.lds | 29 + 83 files changed, 13845 insertions(+) create mode 100644 .travis.yml create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100755 adb-devprobe.sh create mode 100755 autoversion.sh create mode 100644 bin/fel-sdboot.sunxi create mode 100644 bin/jtag-loop.sunxi create mode 100644 bin/ramboot.scr create mode 100644 bin/ramboot.uboot-sh create mode 100644 bin/uart0-helloworld-sdboot.sunxi create mode 100644 boot_head.S create mode 100644 boot_head.lds create mode 100644 bootinfo.c create mode 100644 common.h create mode 100755 fel-gpio create mode 100755 fel-remotefunc-compiler.rb create mode 100644 fel-remotefunc-spi-data-transfer.c create mode 100644 fel-remotefunc-spi-data-transfer.h create mode 100644 fel-sdboot.S create mode 100644 fel-sdboot.lds create mode 100644 fel-spiflash.c create mode 100644 fel-spiflash.h create mode 100644 fel.c create mode 100644 fel_lib.c create mode 100644 fel_lib.h create mode 100644 fexc.c create mode 100644 fexc.h create mode 100755 find-arm-gcc.sh create mode 100644 fit_image.c create mode 100644 fit_image.h create mode 100644 include/list.h create mode 100644 include/portable_endian.h create mode 100644 include/types.h create mode 100644 jtag-loop.S create mode 100644 jtag-loop.c create mode 100644 jtag-loop.lds create mode 100644 meminfo.c create mode 100644 nand-common.h create mode 100644 nand-image-builder.c create mode 100644 nand-part-a10.h create mode 100644 nand-part-a20.h create mode 100644 nand-part-main.c create mode 100644 nand-part.c create mode 100644 phoenix_info.c create mode 100644 pio.c create mode 100644 progress.c create mode 100644 progress.h create mode 100644 script.c create mode 100644 script.h create mode 100644 script_bin.c create mode 100644 script_bin.h create mode 100644 script_extractor.c create mode 100644 script_fex.c create mode 100644 script_fex.h create mode 100644 script_uboot.c create mode 100644 script_uboot.h create mode 100644 soc_info.c create mode 100644 soc_info.h create mode 100644 sunxi-fel.1 create mode 100644 tests/Makefile create mode 100755 tests/fextest.sh create mode 100755 tests/test_all_fex.sh create mode 100755 tests/test_bin2fex_corner_cases.sh create mode 100755 tests/test_fex2bin_corner_cases.sh create mode 100644 tests/unify-fex.c create mode 100644 thunks/Makefile create mode 100644 thunks/README.md create mode 100644 thunks/clrsetbits.S create mode 100644 thunks/clrsetbits.h create mode 100644 thunks/fel-to-spl-thunk.S create mode 100644 thunks/fel-to-spl-thunk.h create mode 100644 thunks/memcpy.S create mode 100644 thunks/memcpy.h create mode 100644 thunks/objdump_to_h.awk create mode 100644 thunks/readl_writel.S create mode 100644 thunks/readl_writel.h create mode 100644 thunks/rmr-thunk.S create mode 100644 thunks/rmr-thunk.h create mode 100644 thunks/sid_read_root.S create mode 100644 thunks/sid_read_root.h create mode 100644 uart0-helloworld-sdboot.c create mode 100644 uart0-helloworld-sdboot.lds diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..57a64c8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,50 @@ +# use container-based infrastructure +sudo: false + +language: c + +# treat all warnings as errors, fake cross-toolchain (build everything on host) +env: + - CFLAGS="-g -O2 -Werror" CROSS_COMPILE="" + +os: + - linux + - osx +compiler: + - gcc + - clang + +# OSX uses Apple's flavor of clang anyway, so there's no point in trying "gcc". +# This excludes the "gcc" compiler from the build matrix for OSX: +matrix: + exclude: + - os: osx + compiler: gcc + +# take care of the libusb dependency for Linux +addons: + apt: + packages: + - libusb-1.0-0-dev + +# take care of the libusb dependency for Mac OS X; select make/install target +before_install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + brew update; + brew install libusb; + export TARGET=tools; + else + export TARGET=all; + fi + +# build (and test) using the Makefile, with a single overall status +script: + - make ${TARGET} && make misc && make check + +# run/simulate a test install +after_success: + - make install-${TARGET} install-misc DESTDIR=/tmp PREFIX=/sunxi-tools + +# turn off email notifications +notifications: + - email: false diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..af5153d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,361 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +### Preamble + +The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, +we want its recipients to know that what they have is not the +original, so that any problems introduced by others will not reflect +on the original authors' reputations. + +Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at +all. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +**0.** This License applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work +based on the Program" means either the Program or any derivative work +under copyright law: that is to say, a work containing the Program or +a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is +included without limitation in the term "modification".) Each licensee +is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether that +is true depends on what the Program does. + +**1.** You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a +fee. + +**2.** You may modify your copy or copies of the Program or any +portion of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + +**a)** You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. + + +**b)** You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any part +thereof, to be licensed as a whole at no charge to all third parties +under the terms of this License. + + +**c)** If the modified program normally reads commands interactively +when run, you must cause it, when started running for such interactive +use in the most ordinary way, to print or display an announcement +including an appropriate copyright notice and a notice that there is +no warranty (or else, saying that you provide a warranty) and that +users may redistribute the program under these conditions, and telling +the user how to view a copy of this License. (Exception: if the +Program itself is interactive but does not normally print such an +announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +**3.** You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + +**a)** Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections 1 +and 2 above on a medium customarily used for software interchange; or, + + +**b)** Accompany it with a written offer, valid for at least three +years, to give any third party, for a charge no more than your cost of +physically performing source distribution, a complete machine-readable +copy of the corresponding source code, to be distributed under the +terms of Sections 1 and 2 above on a medium customarily used for +software interchange; or, + + +**c)** Accompany it with the information you received as to the offer +to distribute corresponding source code. (This alternative is allowed +only for noncommercial distribution and only if you received the +program in object code or executable form with such an offer, in +accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +**4.** You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and +will automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + +**5.** You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +**6.** Each time you redistribute the Program (or any work based on +the Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +**7.** If, as a consequence of a court judgment or allegation of +patent infringement or for any other reason (not limited to patent +issues), conditions are imposed on you (whether by court order, +agreement or otherwise) that contradict the conditions of this +License, they do not excuse you from the conditions of this License. +If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, +then as a consequence you may not distribute the Program at all. For +example, if a patent license would not permit royalty-free +redistribution of the Program by all those who receive copies directly +or indirectly through you, then the only way you could satisfy both it +and this License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +**8.** If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +**9.** The Free Software Foundation may publish revised and/or new +versions of the General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + +**10.** If you wish to incorporate parts of the Program into other +free programs whose distribution conditions are different, write to +the author to ask for permission. For software which is copyrighted by +the Free Software Foundation, write to the Free Software Foundation; +we sometimes make exceptions for this. Our decision will be guided by +the two goals of preserving the free status of all derivatives of our +free software and of promoting the sharing and reuse of software +generally. + +**NO WARRANTY** + +**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +### END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + one line to give the program's name and an idea of what it does. + Copyright (C) yyyy name of author + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper +mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details + type `show w'. This is free software, and you are welcome + to redistribute it under certain conditions; type `show c' + for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than \`show w' and +\`show c'; they could even be mouse-clicks or menu items--whatever +suits your program. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the program, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright + interest in the program `Gnomovision' + (which makes passes at compilers) written + by James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, +you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +[GNU Lesser General Public +License](http://www.gnu.org/licenses/lgpl.html) instead of this +License. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3d89718 --- /dev/null +++ b/Makefile @@ -0,0 +1,211 @@ +# Copyright (C) 2012 Alejandro Mery +# Copyright (C) 2012,2013 Henrik Nordstrom +# Copyright (C) 2013 Patrick Wood +# Copyright (C) 2013 Pat Wood +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Windows predefines OS in the environment (to "Windows_NT"), otherwise use uname +OS ?= $(shell uname) + +CC ?= gcc +DEFAULT_CFLAGS := -std=c99 +DEFAULT_CFLAGS += -Wall -Wextra -Wno-unused-result + +DEFAULT_CFLAGS += -D_POSIX_C_SOURCE=200112L +# Define _BSD_SOURCE, necessary to expose all endian conversions properly. +# See http://linux.die.net/man/3/endian +DEFAULT_CFLAGS += -D_BSD_SOURCE +# glibc 2.20+ also requires _DEFAULT_SOURCE +DEFAULT_CFLAGS += -D_DEFAULT_SOURCE +ifeq ($(OS),NetBSD) +# add explicit _NETBSD_SOURCE, see https://github.com/linux-sunxi/sunxi-tools/pull/22 +DEFAULT_CFLAGS += -D_NETBSD_SOURCE +endif + +DEFAULT_CFLAGS += -Iinclude/ + +PKG_CONFIG ?= pkg-config + +# Tools useful on host and target +TOOLS = sunxi-fexc sunxi-bootinfo sunxi-fel sunxi-nand-part sunxi-pio + +# Symlinks to sunxi-fexc +FEXC_LINKS = bin2fex fex2bin + +# Tools which are only useful on the target +TARGET_TOOLS = sunxi-meminfo + +# Misc tools (of more "exotic" nature) not part of our default build / install +MISC_TOOLS = phoenix_info sunxi-nand-image-builder + +# ARM binaries and images +# Note: To use this target, set/adjust CROSS_COMPILE and MKSUNXIBOOT if needed +BINFILES = jtag-loop.sunxi fel-sdboot.sunxi uart0-helloworld-sdboot.sunxi + +MKSUNXIBOOT ?= mksunxiboot +PATH_DIRS := $(shell echo $$PATH | sed -e 's/:/ /g') +# Try to guess a suitable default ARM cross toolchain +CROSS_DEFAULT := arm-none-eabi- +CROSS_COMPILE ?= $(or $(shell ./find-arm-gcc.sh),$(CROSS_DEFAULT)) +CROSS_CC := $(CROSS_COMPILE)gcc + +DESTDIR ?= +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +MANDIR ?= $(PREFIX)/share/man/man1 + +.PHONY: all clean tools target-tools install install-tools install-target-tools +.PHONY: check + +tools: $(TOOLS) $(FEXC_LINKS) +target-tools: $(TARGET_TOOLS) + +all: tools target-tools + +misc: $(MISC_TOOLS) + +binfiles: $(BINFILES) + +install: install-tools +install-all: install-tools install-target-tools + +install-tools: $(TOOLS) + install -d $(DESTDIR)$(BINDIR) + @set -ex ; for t in $^ ; do \ + install -m0755 $$t $(DESTDIR)$(BINDIR)/$$t ; \ + done + @set -ex ; for l in $(FEXC_LINKS) ; do \ + ln -nfs sunxi-fexc $(DESTDIR)$(BINDIR)/$$l ; \ + done + install -D -m0644 -t $(DESTDIR)$(MANDIR) sunxi-fel.1 + +install-target-tools: $(TARGET_TOOLS) + install -d $(DESTDIR)$(BINDIR) + @set -ex ; for t in $^ ; do \ + install -m0755 $$t $(DESTDIR)$(BINDIR)/$$t ; \ + done + +install-misc: $(MISC_TOOLS) + install -d $(DESTDIR)$(BINDIR) + @set -ex ; for t in $^ ; do \ + install -m0755 $$t $(DESTDIR)$(BINDIR)/$$t ; \ + done + + +clean: + make -C tests/ clean + @rm -vf $(TOOLS) $(FEXC_LINKS) $(TARGET_TOOLS) $(MISC_TOOLS) + @rm -vf version.h *.o *.elf *.sunxi *.bin *.nm *.orig + +$(TOOLS) $(TARGET_TOOLS) $(MISC_TOOLS): Makefile common.h version.h + +fex2bin bin2fex: sunxi-fexc + ln -nsf $< $@ + +sunxi-fexc: fexc.h script.h script.c \ + script_uboot.h script_uboot.c \ + script_bin.h script_bin.c \ + script_fex.h script_fex.c + +LIBUSB = libusb-1.0 +LIBUSB_CFLAGS ?= `$(PKG_CONFIG) --cflags $(LIBUSB)` +LIBUSB_LIBS ?= `$(PKG_CONFIG) --libs $(LIBUSB)` + +ZLIB = zlib +ZLIB_CFLAGS ?= `$(PKG_CONFIG) --cflags $(ZLIB)` +ZLIB_LIBS ?= `$(PKG_CONFIG) --libs $(ZLIB)` + +ifeq ($(OS),Windows_NT) + # Windows lacks mman.h / mmap() + DEFAULT_CFLAGS += -DNO_MMAP + # portable_endian.h relies on winsock2 + LIBS += -lws2_32 +endif + +HOST_CFLAGS = $(DEFAULT_CFLAGS) $(CFLAGS) + +PROGRESS := progress.c progress.h +SOC_INFO := soc_info.c soc_info.h +FEL_LIB := fel_lib.c fel_lib.h +SPI_FLASH:= fel-spiflash.c fel-spiflash.h fel-remotefunc-spi-data-transfer.h + +sunxi-fel: fel.c fit_image.c thunks/fel-to-spl-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB) $(SPI_FLASH) + $(CC) $(HOST_CFLAGS) $(LIBUSB_CFLAGS) $(ZLIB_CFLAGS) $(LDFLAGS) -o $@ \ + $(filter %.c,$^) $(LIBS) $(LIBUSB_LIBS) $(ZLIB_LIBS) -lfdt + +sunxi-nand-part: nand-part-main.c nand-part.c nand-part-a10.h nand-part-a20.h + $(CC) $(HOST_CFLAGS) -c -o nand-part-main.o nand-part-main.c + $(CC) $(HOST_CFLAGS) -c -o nand-part-a10.o nand-part.c -D A10 + $(CC) $(HOST_CFLAGS) -c -o nand-part-a20.o nand-part.c -D A20 + $(CC) $(LDFLAGS) -o $@ nand-part-main.o nand-part-a10.o nand-part-a20.o $(LIBS) + +sunxi-%: %.c + $(CC) $(HOST_CFLAGS) $(LDFLAGS) -o $@ $(filter %.c,$^) $(LIBS) +phoenix_info: phoenix_info.c + $(CC) $(HOST_CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) + +%.bin: %.elf + $(CROSS_COMPILE)objcopy -O binary $< $@ + +%.sunxi: %.bin + $(MKSUNXIBOOT) $< $@ + +ARM_ELF_FLAGS = -Os -marm -fpic -Wall +ARM_ELF_FLAGS += -fno-common -fno-builtin -ffreestanding -nostdinc -fno-strict-aliasing +ARM_ELF_FLAGS += -mno-thumb-interwork -fno-stack-protector -fno-toplevel-reorder +ARM_ELF_FLAGS += -Wstrict-prototypes -Wno-format-nonliteral -Wno-format-security + +jtag-loop.elf: jtag-loop.c jtag-loop.lds + $(CROSS_CC) -g $(ARM_ELF_FLAGS) $< -nostdlib -o $@ -T jtag-loop.lds -Wl,-N + +fel-sdboot.elf: fel-sdboot.S fel-sdboot.lds + $(CROSS_CC) -march=armv5te -g $(ARM_ELF_FLAGS) $< -nostdlib -o $@ -T fel-sdboot.lds -Wl,-N + +uart0-helloworld-sdboot.elf: uart0-helloworld-sdboot.c uart0-helloworld-sdboot.lds + $(CROSS_CC) -march=armv5te -g $(ARM_ELF_FLAGS) $< -nostdlib -o $@ -T uart0-helloworld-sdboot.lds -Wl,-N + +boot_head_sun3i.elf: boot_head.S boot_head.lds + $(CROSS_CC) -g $(ARM_ELF_FLAGS) $< -nostdlib -o $@ -T boot_head.lds -Wl,-N -DMACHID=0x1094 + +boot_head_sun4i.elf: boot_head.S boot_head.lds + $(CROSS_CC) -g $(ARM_ELF_FLAGS) $< -nostdlib -o $@ -T boot_head.lds -Wl,-N -DMACHID=0x1008 + +boot_head_sun5i.elf: boot_head.S boot_head.lds + $(CROSS_CC) -g $(ARM_ELF_FLAGS) $< -nostdlib -o $@ -T boot_head.lds -Wl,-N -DMACHID=0x102A + +sunxi-bootinfo: bootinfo.c + +# "preprocessed" .h files for inclusion of ARM thunk code +headers: + make -C thunks/ CROSS_COMPILE=$(CROSS_COMPILE) + + +# target tools +TARGET_CFLAGS = $(DEFAULT_CFLAGS) -static $(CFLAGS) +sunxi-meminfo: meminfo.c + $(CROSS_CC) $(TARGET_CFLAGS) -o $@ $< +sunxi-script_extractor: script_extractor.c + $(CROSS_CC) $(TARGET_CFLAGS) -o $@ $< + +version.h: + @./autoversion.sh > $@ + +.gitignore: Makefile + @for x in $(TOOLS) $(FEXC_LINKS) $(TARGET_TOOLS) version.h '*.o' '*.swp'; do \ + echo "$$x"; \ + done | sort -V > $@ + +check: $(FEXC_LINKS) + make -C tests/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..82771ce --- /dev/null +++ b/README.md @@ -0,0 +1,158 @@ +# sunxi-tools +[![License](http://img.shields.io/badge/License-GPL-green.svg)](LICENSE.md) +[![Build Status](https://travis-ci.org/linux-sunxi/sunxi-tools.svg?branch=master)](https://travis-ci.org/linux-sunxi/sunxi-tools) +[![Releases](https://img.shields.io/github/release/linux-sunxi/sunxi-tools.svg)](https://github.com/linux-sunxi/sunxi-tools/releases) +[![Commits](https://img.shields.io/github/commits-since/linux-sunxi/sunxi-tools/v1.4.svg)](https://github.com/linux-sunxi/sunxi-tools/compare/v1.4...master) + +Copyright (C) 2012 Alejandro Mery +
For a full list of contributors, see +[this link](https://github.com/linux-sunxi/sunxi-tools/contributors) +or use the command `git shortlog -se --no-merges`. + +Command line utilities to work with devices based on [Allwinner SoC]s: +sun4i, sun5i, ... - that's why the 'x' in the package name. + +### sunxi-fexc +`.fex` file (de)compiler + + Usage: ./sunxi-fexc [-vq] [-I ] [-O ] [ []] + + infmt: fex, bin (default:fex) + outfmt: fex, bin (default:bin) + +### bin2fex +compatibility shortcut to call `sunxi-fexc` to decompile a _script.bin_ +blob back into `.fex` format used by Allwinner's SDK to configure +the boards. + +### fex2bin +compatiblity shortcut to call `sunxi-fexc` to compile a `.fex` file +into the binary form used by the legacy 3.4 kernel ("linux-sunxi"). + +### sunxi-fel +script interface for USB communication with the FEL handler built in to +the CPU. You usually activate [FEL mode] by pushing the _uboot_ / _recovery_ +button at poweron, or by having your device "fail over" to FEL when no other +boot option is available. See http://linux-sunxi.org/FEL/USBBoot for a detailed +usage guide. + +When called with no arguments, _sunxi-fel_ will display a short usage summary. + +_Note:_ Unless you select a specific device using the `--dev` or `--sid` +options, the tool will access the first Allwinner device (in FEL mode) that it +finds. You can print a list of all FEL devices currently connected/detected +with `./sunxi-fel --list --verbose`. + +### fel-gpio +Simple wrapper (script) around `sunxi-pio` and `sunxi-fel` +to allow GPIO manipulations via FEL + +### fel-sdboot +ARM native sdcard bootloader forcing the device into FEL mode + +### uart0-helloworld-sdboot +ARM native sdcard bootloader, which is only printing a short "hello" +message to the UART0 serial console. Because it relies on runtime +SoC type detection, this single image is bootable on a wide range of +Allwinner devices and can be used for testing. Additionally, it may +serve as a template/example for developing simple bare metal code +(LED blinking and other similar GPIO related things). + +### sunxi-pio +Manipulate PIO registers/dumps + +### sunxi-nand-part +Tool for manipulating Allwinner NAND partition tables + +### sunxi-nand-image-builder +Tool used to create raw NAND images (including boot0 images) + +### jtag-loop.sunxi +ARM native boot helper to force the SD port into JTAG and then stop, +to ease debugging of bootloaders. + +### sunxi-bootinfo +Dump information from Allwinner boot files (_boot0_ / _boot1_) + + --type=sd include SD boot info + --type=nand include NAND boot info (not implemented) + +### phoenix_info +gives information about a phoenix image created by the +phoenixcard utility and optionally extracts the embedded boot +code & firmware file from their hidden partitions. + +### sunxi-meminfo +Tool for reading DRAM settings from registers. Compiled as a +static binary for use on android and other OSes. +To build this, get a toolchain and run: + + make CROSS_COMPILE=arm-linux-gnueabihf- sunxi-meminfo + +### sunxi-script_extractor +A simple tool, which can be executed on a rooted Android device +to dump the _script.bin_ blob from RAM via reading _/dev/mem_. +To build this, get a toolchain and run: + + make CROSS_COMPILE=arm-linux-gnueabihf- sunxi-script_extractor +--- + +## Building + +Compilation requires the development version of *libusb-1.0* (include header +and library) to be installed for `sunxi-fel`. Unless you explicitly pass +*LIBUSB_CFLAGS* and *LIBUSB_LIBS* to the make utility, `pkg-config` is also +needed. Development versions of zlib and libfdt are also required. + +To install the dependencies on Ubuntu 20.04 using package manager: + +```bash +sudo apt install libusb-1.0-0-dev libz-dev libfdt-dev +``` + +Available build targets: + +* `make tools` +builds tools that are useful on the host. This is what most people will want, +and our default target (when simply using `make`). + +* `make target-tools` +builds tools that are intended for the target (Allwinner SoC), using a +cross-compiler. The Makefile will try to auto-detect a suitable toolchain +prefix, and falls back to `arm-none-eabi-` otherwise. +If needed, you may override this by explicitly setting *CROSS_COMPILE*. +
_Hint:_ When compiling 'natively' on the target platform you may +simply use an empty toolchain prefix here (`make target-tools CROSS_COMPILE=` +or `make all CROSS_COMPILE=`). + +* `make all` +builds both *tools* and *target-tools*. + +* `make install-tools` +builds *tools* and then copies/installs them to a filesystem location. The +destination is affected by settings for `DESTDIR`, `PREFIX` and possibly +`BINDIR`. For details, please refer to the *Makefile*. +You may use `make install` as a shortcut for this. + +* `make install-target-tools` +builds *target-tools* and then copies/installs them to a filesystem location +selected by `DESTDIR`, `PREFIX` and possibly `BINDIR` - see `make install-tools` +above. + +* `make install-all` +builds and installs both *tools* and *target-tools*. + +* `make misc` +builds miscellaneous (host) utilities that are not part of our 'standard' suite. +Currently this means `phoenix_info` and `sunxi-nand-image-builder`. + +* `make install-misc` +builds *misc* and installs the resulting binaries. + +## License +This software is licensed under the terms of GPLv2+ as defined by the +Free Software Foundation, details can be read in the [LICENSE.md](LICENSE.md) +file. + +[allwinner soc]: http://linux-sunxi.org/Allwinner_SoC_Family +[fel mode]: http://linux-sunxi.org/FEL diff --git a/adb-devprobe.sh b/adb-devprobe.sh new file mode 100755 index 0000000..dae6175 --- /dev/null +++ b/adb-devprobe.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Copyright (C) 2012 Henrik Nordstrom +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +adb shell insmod /vendor/modules/sunxi-dbgreg.ko >/dev/null + +dump_io() +{ + module=$1 + addr=$2 + len=$3 + + for ((i = 0; i < len; i+=4)) { + printf "%x %s " $((addr + i)) $module + adb shell "echo `printf %x $((addr + i))` > /sys/devices/virtual/misc/sunxi-reg/rw/address; cat /sys/devices/virtual/misc/sunxi-reg/rw/value" + echo + } +} + +dump_io SRAM 0xf1c00000 0x100 +dump_io DRAM 0xf1c01000 0x400 +dump_io CCM 0xf1c20000 0x400 +dump_io PIO 0xf1c20800 0x400 + +dump_pmu() +{ + for ((i = 0; i <0x100; i+=2)) { + adb shell "echo `printf 0x%x $i` > /sys/bus/i2c/devices/0-0034/axp20_reg; cat /sys/bus/i2c/devices/0-0034/axp20_regs" + } +} + +dump_pmu diff --git a/autoversion.sh b/autoversion.sh new file mode 100755 index 0000000..06f52d6 --- /dev/null +++ b/autoversion.sh @@ -0,0 +1,18 @@ +# +# This script auto-updates a VERSION string definition. +# It outputs informational messages to stderr, while the actual +# output (on stdout) can easily be redirected to a file. +# + +LATEST_RELEASE="v1.4.2" + +if VER=`git describe --tags --dirty --always`; then + echo "Setting version information: ${VER}" >&2 +else + VER=${LATEST_RELEASE} + echo "Unable to determine current version (using \"${VER}\" as fallback)" >&2 +fi +echo >&2 + +echo "/* Auto-generated information. DO NOT EDIT */" +echo "#define VERSION \"${VER}\"" diff --git a/bin/fel-sdboot.sunxi b/bin/fel-sdboot.sunxi new file mode 100644 index 0000000000000000000000000000000000000000..7b94004cecd1973e0d9e11630cbcdb25bf9989a8 GIT binary patch literal 8192 zcmeIuJq|=*6ouh~M4|9gXwBE@W~@P@5hP{{)7p;K(uhtWcJPgYEvVj`oZRA^>hY!R zSGCQ%{1#(42Er6`qwi2*O*V3_}uLty?>27!jB|Nj4fwT0slql=3RUOl6d HgEs^Kf*cST literal 0 HcmV?d00001 diff --git a/bin/ramboot.scr b/bin/ramboot.scr new file mode 100644 index 0000000000000000000000000000000000000000..944aa5837726e04cdc07001c545c1acfefe3047b GIT binary patch literal 246 zcmY#ql?*GWost|l`$;MT1H(2T27@2dS#Gm3vB9|b=yf1TWra}Pr2PC6g&;>?1u&yH zxhS)sgsUhqHzl(;+mEDDccrlbUTj!hOeCOM&7-7XZzjv~4-HED56Mrvn} zimF4CV`YDFb9F~KA>(e~MoCm^tCAL-gZTHb!= z`MNx3+LyGyCR@5X=l<^bxc8iU&w0L=Xd(6B_D41h4C`k;I-MkqC>bA=>3{Uwtk8nv z`T5NCHie8*+i9iTrx$I-EMAQ;)e9!+Gq%xTluq&p<(d;CFsPwd!C1_rY6%pk5+pgMa!A8e*Eex z*l#DJL^Nb6dNx4%8A;X;e|DDhcM`oW#ul$_-lr^^-D=V91}>=uvl=T>U$7-TkNHv8 zei-)o9PQ4=(iCisVhm$&d?*Uvs$rdQoEtvKsnJu{t~k!Zq*#;U(8ca-PAb9R)nWzvt>vff<&#Bo|j>8Ksmb2j@YawK(?`o~>b z(cT5!)pyCJLPM}itvfK^0o*-@mUrbjUyaCrjDTyh`G8uqi4vtKa7i};q|MlJSI#2H z>(J%9B5MV6O&`uBd^*U_iYB7t%ZWDMiwmsE%2(hpNT;PBk+KuG4tol6#IaOhe;M`@ z1mD;=W3TX2`Hn9~%S8Li3K?eQ9EQa-2;H@8Q=gYyUD)85u#E=T(7ZPM@v=llvsd55 zzN-4({C;zJ()t|qIX9fAAkKdhIdG82JjJa6iZ{a+o`KQKcaiTt+cjX1qHjt6&Q?L# zf*lL?*j6)aC|+B`!d45;Fo8HrojGd)alX`1;V*Smazr#Qu}o7Ja4lywb>%BO+Zc&^R|ll-JW?6^G45{z*F--?tRX)cT`jB9wNUC-CpGG&0Ee5=D!5~4eR#D5`}mgdeLwgeLZA5`LqF!(E1$;%zFqh$fgcp|%;SwMv{lTEsqlA~(w*m&4jB$T6P>-zIdJe+)XiJ$v9ib9|a1!~8F?ehWjxo8ou$ zD8|QrqMH~d#v0VcA&e>TyD@fRTtQI_1A88$1#ghw;Tq^oW6tk#HRfFJK7o&{h8Up+ zRQp;JY7YVPfi4H-ny-K|&UNd^9Yk#&`McG8nY0(skM6S2hv2GKBQ>0Ea{8r zCrJzXjegMOsDXcQ9GvK)eFJyvZ#dsY*u03E!u{bq_@y=J1JApD4Ru<@oNfLLvOca` zZVf^WQ^BjKVcZ`XQ>T?DJbV_qRs30(AF@#M0+Zk;;on`;4T*C?D*7DyCJxo=1J8Yv z=as&7!97z0-m-#yaF0OWQn(IFROu74pgzQcdm`t9=DWC`yI^?B><`poN;aO=@5?99T3n^)wnA!t}H3EP}g z-sfTWJjoKa*Lo0Z2R-r!gzVZc8W!I00>@@j$YVbNj)4rDqaK^wSJ>a5As+`H_t@ia zw)So7F~xHP=KyH+&PTz=FoL|+I4^aA`eUhyiH!E- z-ptb)^-oOfA5W)Kd$s_q-?sz^*Uo#ED1<{B`@^9nO1+U9rC86p+Ig+b*ruA* z{@x|ay*10R-cT)&2={ERkw`>p_c#)+-QGxFI8>uB(zmJBWnxoL-^Srks1_aJeLj=k zlg{iM*I5L1~>R=Ad{I&?WzQR+qSKGx0Xsz zrS`JmSXvuTKRKQrpGs*Hnat!8>16z&|3bRDucuSH#>f7(6RtJ=pE@_xfug-0HKh#x zZPYiPjQYU;L#SZ>9&^%;*^43EQ`820p10nV_{|s03s`#(Lqvf{L2fIwCMbVAp_=e#|~5dejlAh?`rWGHT@y1a?qh z_@?k55ZJQ!vpM`!@U|R%s23SO3y1?E9)+w-{ zzF#uxkP{fSCfjEHHcn_%90V zyuk3^$bXh9xtc-!I4$@T^1=O)djjWiOw3;u^KXm!96n^ued;*eFHeD9htl()hcLK* zejk*3=5wH2r@sffA7dW|_t5Wva?MVGa@`sO<(|3|lxx|j)8Lj#ockXVSE>(fbnw-RJQuB9)WrU>Jg|%pdNvG1nLo}N1z^odIahbs7Ih4 LfqDe~&m!=5B??*h literal 0 HcmV?d00001 diff --git a/boot_head.S b/boot_head.S new file mode 100644 index 0000000..8404ed5 --- /dev/null +++ b/boot_head.S @@ -0,0 +1,42 @@ +/* + * Boot header to work around broken Allwinner A1x boot loaders + * + * Copyright (C) 2012 Henrik Nordstrom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * + * This file is a workaround to broken bootloaders on Allwinner A1x + * platform who do not provide correct machid or atags address + * + * Usage: + * load the header at 0x40007000 and change the entry point of your + * boot process to 0x40007000 + * + * Detailed memory map: + * 0x40000100 atags + * 0x40007000 boot_head (entry point) + * 0x40008000 kernel + * 0x43000000 script.bin + * If you have a ramdisk then load it at some higher address + */ + +_start: + ldr r0, =0 + ldr r1, =MACHID + ldr r2, =0x40000100 + ldr lr, =0x40008000 + bx lr diff --git a/boot_head.lds b/boot_head.lds new file mode 100644 index 0000000..4fdbc6d --- /dev/null +++ b/boot_head.lds @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2012 Henrik Nordstrom + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +SECTIONS +{ + . = 0x40007000; + .text : { *(.text) } + /DISCARD/ : { *(.dynstr*) } + /DISCARD/ : { *(.dynamic*) } + /DISCARD/ : { *(.plt*) } + /DISCARD/ : { *(.interp*) } + /DISCARD/ : { *(.gnu*) } + /DISCARD/ : { *(.note*) } +} diff --git a/bootinfo.c b/bootinfo.c new file mode 100644 index 0000000..93b94d7 --- /dev/null +++ b/bootinfo.c @@ -0,0 +1,384 @@ +/* + * (C) Copyright 2012 Henrik Nordstrom + * + * display information about sunxi boot headers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include "common.h" +#include "types.h" + +/* boot_file_head copied from mksunxiboot */ +/* boot head definition from sun4i boot code */ +typedef struct boot_file_head +{ + u32 jump_instruction; // one intruction jumping to real code + u8 magic[8]; // ="eGON.BT0" or "eGON.BT1", not C-style string. + u32 check_sum; // generated by PC + u32 length; // generated by PC + u32 pub_head_size; // the size of boot_file_head_t + u8 pub_head_vsn[4]; // the version of boot_file_head_t + u8 file_head_vsn[4]; // the version of boot0_file_head_t or boot1_file_head_t + u8 Boot_vsn[4]; // Boot version + u8 eGON_vsn[4]; // eGON version + u8 platform[8]; // platform information +} boot_file_head_t; + +typedef struct brom_file_head +{ + u32 jump_instruction; // one intruction jumping to real code + u8 magic[8]; // ="eGON.BRM", not C-style string. + u32 length; // generated by PC + u8 Boot_vsn[4]; // Boot version + u8 eGON_vsn[4]; // eGON version + u8 platform[8]; // platform information +} brom_file_head_t; + +typedef struct _boot_dram_para_t { + __u32 dram_baseaddr; + __u32 dram_clk; + __u32 dram_type; + __u32 dram_rank_num; + __u32 dram_chip_density; + __u32 dram_io_width; + __u32 dram_bus_width; + __u32 dram_cas; + __u32 dram_zq; + __u32 dram_odt_en; + __u32 dram_size; + __u32 dram_tpr0; + __u32 dram_tpr1; + __u32 dram_tpr2; + __u32 dram_tpr3; + __u32 dram_tpr4; + __u32 dram_tpr5; + __u32 dram_emr1; + __u32 dram_emr2; + __u32 dram_emr3; +} boot_dram_para_t; + +typedef struct _normal_gpio_cfg { + __u8 port; + __u8 port_num; + __u8 mul_sel; + __u8 pull; + __u8 drv_level; + __u8 data; + __u8 reserved[2]; +} normal_gpio_cfg; + +typedef struct _boot0_private_head_t { + __u32 prvt_head_size; + char prvt_head_vsn[4]; + boot_dram_para_t dram_para; + __s32 uart_port; + normal_gpio_cfg uart_ctrl[2]; + __s32 enable_jtag; + normal_gpio_cfg jtag_gpio[5]; + normal_gpio_cfg storage_gpio[32]; + __u8 storage_data[256]; +} boot0_private_head_t; + +typedef struct _boot0_file_head_t { + boot_file_head_t boot_head; + boot0_private_head_t prvt_head; +} boot0_file_head_t; + +typedef struct _boot_core_para_t { + __u32 user_set_clock; + __u32 user_set_core_vol; + __u32 vol_threshold; +} boot_core_para_t; + +typedef struct _boot1_private_head_t { + __u32 prvt_head_size; + __u8 prvt_head_vsn[4]; + __s32 uart_port; + normal_gpio_cfg uart_ctrl[2]; + boot_dram_para_t dram_para; + char script_buf[32768]; + boot_core_para_t core_para; + __s32 twi_port; + normal_gpio_cfg twi_ctrl[2]; + __s32 debug_enable; + __s32 hold_key_min; + __s32 hold_key_max; + __u32 work_mode; + __u32 storage_type; + normal_gpio_cfg storage_gpio[32]; + __u8 storage_data[256]; +} boot1_private_head_t; + +typedef struct _boot1_file_head_t { + boot_file_head_t boot_head; + boot1_private_head_t prvt_head; +} boot1_file_head_t; + +/* STORAGE DATA on SD loaders */ +typedef struct _boot_sdcard_info_t { + __s32 card_ctrl_num; + __s32 boot_offset; + __s32 card_no[4]; + __s32 speed_mode[4]; + __s32 line_sel[4]; + __s32 line_count[4]; +} boot_sdcard_info_t; + +#define BROM_MAGIC "eGON.BRM" +#define BOOT0_MAGIC "eGON.BT0" +#define BOOT1_MAGIC "eGON.BT1" + +union { + boot_file_head_t boot; + boot0_file_head_t boot0; + boot1_file_head_t boot1; + brom_file_head_t brom; +} boot_hdr; + +typedef enum { + ALLWINNER_UNKNOWN_LOADER=0, + ALLWINNER_SD_LOADER, + ALLWINNER_NAND_LOADER +} loader_type; + +void fail(char *msg) { + perror(msg); + exit(1); +} + +void pprintf(void *addr, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + printf("%8x:\t", (unsigned)((char *)addr - (char *)&boot_hdr)); + vprintf(fmt, ap); + va_end(ap); +} + +void print_brom_file_head(brom_file_head_t *hdr) +{ + pprintf(&hdr->magic, "Magic : %.8s\n", hdr->magic); + pprintf(&hdr->length, "Length : %u\n", hdr->length); + pprintf(&hdr->Boot_vsn, "BOOT ver : %.4s\n", hdr->Boot_vsn); + pprintf(&hdr->eGON_vsn, "eGON ver : %.4s\n", hdr->eGON_vsn); + pprintf(&hdr->platform, "Chip? : %.8s\n", hdr->platform); +} + +void print_boot_file_head(boot_file_head_t *hdr) +{ + pprintf(&hdr->magic, "Magic : %.8s\n", hdr->magic); + pprintf(&hdr->length, "Length : %u\n", hdr->length); + pprintf(&hdr->pub_head_size, "HSize : %u\n", hdr->pub_head_size); + pprintf(&hdr->pub_head_vsn, "HEAD ver : %.4s\n", hdr->pub_head_vsn); + pprintf(&hdr->file_head_vsn, "FILE ver : %.4s\n", hdr->file_head_vsn); + pprintf(&hdr->Boot_vsn, "BOOT ver : %.4s\n", hdr->Boot_vsn); + pprintf(&hdr->eGON_vsn, "eGON ver : %.4s\n", hdr->eGON_vsn); + pprintf(&hdr->platform, "platform : %c%c%c%c%c%c%c%c\n", hdr->platform[0], hdr->platform[1], hdr->platform[2], hdr->platform[3], hdr->platform[4], hdr->platform[5], hdr->platform[6], hdr->platform[7]); +} + +void print_boot_dram_para(boot_dram_para_t *dram) +{ + pprintf(&dram->dram_baseaddr, "DRAM base : %p\n", (void *)(uintptr_t)dram->dram_baseaddr); + pprintf(&dram->dram_clk, "DRAM clk : %d\n", dram->dram_clk); + pprintf(&dram->dram_type, "DRAM type : %d\n", dram->dram_type); + pprintf(&dram->dram_rank_num, "DRAM rank : %d\n", dram->dram_rank_num); + pprintf(&dram->dram_chip_density,"DRAM den : %d\n", dram->dram_chip_density); + pprintf(&dram->dram_io_width, "DRAM iow : %d\n", dram->dram_io_width); + pprintf(&dram->dram_bus_width, "DRAM busw : %d\n", dram->dram_bus_width); + pprintf(&dram->dram_cas, "DRAM cas : %d\n", dram->dram_cas); + pprintf(&dram->dram_zq, "DRAM zq : %d\n", dram->dram_zq); + pprintf(&dram->dram_odt_en, "DRAM odt : 0x%x\n", dram->dram_odt_en); + pprintf(&dram->dram_size, "DRAM size : %d\n", dram->dram_size); + pprintf(&dram->dram_tpr0, "DRAM tpr0 : 0x%x\n", dram->dram_tpr0); + pprintf(&dram->dram_tpr1, "DRAM tpr1 : 0x%x\n", dram->dram_tpr1); + pprintf(&dram->dram_tpr2, "DRAM tpr2 : 0x%x\n", dram->dram_tpr2); + pprintf(&dram->dram_tpr3, "DRAM tpr3 : 0x%x\n", dram->dram_tpr3); + pprintf(&dram->dram_tpr4, "DRAM tpr4 : 0x%x\n", dram->dram_tpr4); + pprintf(&dram->dram_tpr5, "DRAM tpr5 : 0x%x\n", dram->dram_tpr5); + pprintf(&dram->dram_emr1, "DRAM emr1 : 0x%x\n", dram->dram_emr1); + pprintf(&dram->dram_emr2, "DRAM emr2 : 0x%x\n", dram->dram_emr2); + pprintf(&dram->dram_emr3, "DRAM emr3 : 0x%x\n", dram->dram_emr3); +} + +void print_normal_gpio_cfg(normal_gpio_cfg *gpio, int count) +{ + int i; + for (i = 0; i < count; i++) { + if (gpio[i].port) + pprintf(&gpio[i], " GPIO %d : port=%c%d, sel=%d, pull=%d, drv=%d, data=%d, reserved=%02x,%02x\n", i, 'A'+gpio[i].port-1, gpio[i].port_num, gpio[i].mul_sel, gpio[i].pull, gpio[i].drv_level, gpio[i].data, gpio[i].reserved[0], gpio[i].reserved[1]); + } +} + +void print_boot_sdcard_info(boot_sdcard_info_t *info) +{ + pprintf(&info->card_ctrl_num, " CARD Ctrl Num: %d\n", info->card_ctrl_num); + pprintf(&info->boot_offset, " BOOT Offset: %08x\n", info->boot_offset); + + for (int i = 0; i < 4; i++) { + if (info->card_no[i] == -1) + continue; + pprintf(&info->card_no[i], " CARD No : %d (%d)\n", info->card_no[i], i); + pprintf(&info->speed_mode[i], " Speed : %d\n", info->speed_mode[i]); + pprintf(&info->line_sel[i], " Line sel: %d\n", info->line_sel[i]); + pprintf(&info->line_count[i], " Line cnt: %d\n", info->line_count[i]); + } +} + +void print_boot0_private_head(boot0_private_head_t *hdr, loader_type type) +{ + pprintf(&hdr->prvt_head_size, "FHSize : %u\n", hdr->prvt_head_size); + pprintf(&hdr->prvt_head_vsn, "FILE ver : %.4s\n", hdr->prvt_head_vsn); + print_boot_dram_para(&hdr->dram_para); + pprintf(&hdr->uart_port, "UART port : %d\n", hdr->uart_port); + print_normal_gpio_cfg(hdr->uart_ctrl, 2); + pprintf(&hdr->enable_jtag, "JTAG en : %d\n", hdr->enable_jtag); + print_normal_gpio_cfg(hdr->jtag_gpio, 5); + pprintf(&hdr->storage_gpio, "STORAGE :\n"); + print_normal_gpio_cfg(hdr->storage_gpio, 32); + int i = 0; + if (type == ALLWINNER_SD_LOADER) { + print_boot_sdcard_info((boot_sdcard_info_t *)hdr->storage_data); + i = sizeof(boot_sdcard_info_t); + } + for (int n = 0; i < 256; i++, n++) { + if (n % 16 == 0) { + if (n) { + printf("\n"); + } + pprintf(&hdr->storage_data[i], " DATA %02x :", i); + } + printf(" %02x", hdr->storage_data[i]); + } + printf("\n"); +} + +void print_script(void *UNUSED(script)) +{ +} + +void print_core_para(boot_core_para_t *core) +{ + pprintf(&core->user_set_clock, "Set Clock : %d\n", core->user_set_clock); + pprintf(&core->user_set_core_vol, "Set Core Vol: %d\n", core->user_set_core_vol); + pprintf(&core->vol_threshold, "Vol Threshold: %d\n", core->vol_threshold); +} + +void print_boot1_private_head(boot1_private_head_t *hdr, loader_type type) +{ + pprintf(&hdr->prvt_head_size, "FHSize : %u\n", hdr->prvt_head_size); + pprintf(&hdr->prvt_head_vsn, "FILE ver : %.4s\n", hdr->prvt_head_vsn); + pprintf(&hdr->uart_port, "UART port : %d\n", hdr->uart_port); + print_normal_gpio_cfg(hdr->uart_ctrl, 2); + print_boot_dram_para(&hdr->dram_para); + print_script(&hdr->script_buf); + print_core_para(&hdr->core_para); + pprintf(&hdr->twi_port, "TWI port : %d\n", hdr->twi_port); + print_normal_gpio_cfg(hdr->twi_ctrl, 2); + pprintf(&hdr->debug_enable, "Debug : %d\n", hdr->debug_enable); + pprintf(&hdr->hold_key_min, "Hold key min : %d\n", hdr->hold_key_min); + pprintf(&hdr->hold_key_max, "Hold key max : %d\n", hdr->hold_key_max); + pprintf(&hdr->work_mode, "Work mode : %d\n", hdr->work_mode); + pprintf(&hdr->storage_type, "STORAGE :\n"); + pprintf(&hdr->storage_type, " type : %d\n", hdr->storage_type); + print_normal_gpio_cfg(hdr->storage_gpio, 32); + int i = 0; + if (type == ALLWINNER_SD_LOADER) { + print_boot_sdcard_info((boot_sdcard_info_t *)hdr->storage_data); + i = sizeof(boot_sdcard_info_t); + } + for (int n = 0; i < 256; i++, n++) { + if (n % 16 == 0) { + if (n) { + printf("\n"); + } + pprintf(&hdr->storage_data[i], " DATA %02x :", i); + } + printf(" %02x", hdr->storage_data[i]); + } + printf("\n"); +} + +void print_boot0_file_head(boot0_file_head_t *hdr, loader_type type) +{ + print_boot_file_head(&hdr->boot_head); + if (strncmp((char *)hdr->boot_head.file_head_vsn, "1230", 4) == 0) + print_boot0_private_head(&hdr->prvt_head, type); + else + printf("Unknown boot0 header version\n"); +} + +void print_boot1_file_head(boot1_file_head_t *hdr, loader_type type) +{ + print_boot_file_head(&hdr->boot_head); + if (strncmp((char *)hdr->boot_head.file_head_vsn, "1230", 4) == 0) + print_boot1_private_head(&hdr->prvt_head, type); + else + printf("Unknown boot0 header version\n"); +} + +static void usage(const char *cmd) +{ + puts("sunxi-bootinfo " VERSION "\n"); + printf("Usage: %s []\n", cmd); + printf(" With no given, will read from stdin instead\n"); +} + +int main(int argc, char * argv[]) +{ + FILE *in = stdin; + loader_type type = ALLWINNER_UNKNOWN_LOADER; + if (argc > 1 && strcmp(argv[1], "--type=sd") == 0) { + type = ALLWINNER_SD_LOADER; + argc--; + argv++; + } + if (argc > 1 && strcmp(argv[1], "--type=nand") == 0) { + type = ALLWINNER_NAND_LOADER; + argc--; + argv++; + } + if (argc > 1) { + in = fopen(argv[1], "rb"); + if (!in) { + if (*argv[1] == '-') + usage(argv[0]); + fail("open input"); + } + } + int len; + + len = fread(&boot_hdr, 1, sizeof(boot_hdr), in); + if (len < (int)sizeof(boot_file_head_t)) + fail("Failed to read header:"); + if (strncmp((char *)boot_hdr.boot.magic, BOOT0_MAGIC, strlen(BOOT0_MAGIC)) == 0) { + print_boot0_file_head(&boot_hdr.boot0, type); + } else if (strncmp((char *)boot_hdr.boot.magic, BOOT1_MAGIC, strlen(BOOT1_MAGIC)) == 0) { + print_boot1_file_head(&boot_hdr.boot1, type); + } else if (strncmp((char *)boot_hdr.boot.magic, BROM_MAGIC, strlen(BROM_MAGIC)) == 0) { + print_brom_file_head(&boot_hdr.brom); + } else { + fail("Invalid magic\n"); + } + + return 0; +} diff --git a/common.h b/common.h new file mode 100644 index 0000000..746eea4 --- /dev/null +++ b/common.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUNXI_TOOLS_COMMON_H +#define _SUNXI_TOOLS_COMMON_H + +#include /* offsetof */ + +#include "version.h" /* auto-generated VERSION string */ + +/** flag function argument as unused */ +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#else +# define UNUSED(x) UNUSED_ ## x +#endif + +/** finds the parent of an struct member */ +#ifndef container_of +#define container_of(P,T,M) (T *)((char *)(P) - offsetof(T, M)) +#endif + +/** calculate number of elements of an array */ +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +/** shortcut to "printf to stderr" */ +#define pr_error(...) fprintf(stderr, __VA_ARGS__) +/** like pr_error(), but also exit program */ +#define pr_fatal(...) \ + do { pr_error(__VA_ARGS__); exit(EXIT_FAILURE); } while (0); + +#endif /* _SUNXI_TOOLS_COMMON_H */ diff --git a/fel-gpio b/fel-gpio new file mode 100755 index 0000000..bc231d0 --- /dev/null +++ b/fel-gpio @@ -0,0 +1,44 @@ +#!/bin/sh -e + +# Copyright (C) 2012,2013 Henrik Nordstrom +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +pio_base=0x01c20800 +pio_size=0x228 +sram_addr=0x3000 + +# read PIO +./sunxi-fel memmove $sram_addr $pio_base $pio_size +./sunxi-fel read $sram_addr $pio_size pio.reg +./sunxi-pio -i pio.reg print > pio.old +cat pio.old | fgrep -v '<0><0><0><0>' + +while read cmd; do + ./sunxi-pio -i pio.reg -o pio.reg $cmd + # write PIO + ./sunxi-fel write $sram_addr pio.reg + ./sunxi-fel memmove $pio_base $sram_addr $pio_size + # (re-)read PIO + ./sunxi-fel memmove $sram_addr $pio_base $pio_size + ./sunxi-fel read $sram_addr $pio_size pio.reg + ./sunxi-pio -i pio.reg print > pio.new + diff -U0 pio.old pio.new || true + mv -f pio.new pio.old +done diff --git a/fel-remotefunc-compiler.rb b/fel-remotefunc-compiler.rb new file mode 100755 index 0000000..2afbbfd --- /dev/null +++ b/fel-remotefunc-compiler.rb @@ -0,0 +1,149 @@ +#!/usr/bin/env ruby +# +# (C) Copyright 2016 Siarhei Siamashka +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +if ARGV.size < 2 + printf("Usage: #{$PROGRAM_NAME} [c_source_input] [marshalled_header_output]\n\n") + + printf("This script uses an ARM toolchain to compile native ARM code, and then\n") + printf("automatically generates the necessary wrapper code for calling it from\n") + printf("the sunxi-fel tool. Executing such compiled pieces of code natively on\n") + printf("the device may be needed for the performance critical parts.\n") + + printf("\nExample input file:\n\n") + printf(" unsigned sum(unsigned a, unsigned b)\n") + printf(" {\n") + printf(" return a + b;\n") + printf(" }\n") + printf("\n") + printf("Using this example code inside of sunxi-fel:\n") + printf("\n") + printf(" uint32_t a = 1, b = 2, c;\n") + printf(" aw_fel_remotefunc_prepare_sum(dev, a, b);\n") + printf(" aw_fel_remotefunc_execute(dev, &c);\n") + printf(" printf(\"%%d + %%d = %%d\\n\", a, b, c);\n\n") + + printf("If the returned result is not needed (a void function), then the second\n") + printf("argument to the 'aw_fel_remotefunc_execute' function can be NULL.\n\n") + exit(1) +end + +def tool_exists(tool_name) + `which #{tool_name} > /dev/null 2>&1` + return $?.to_i == 0 +end + +def parse_stack_usage(filename) + return unless File.exists?(filename) + File.read(filename).strip.split("\n").map do |l| + if l =~ /\:([^\:\s]+)\s+(\d+)\s+(\S+)/ + if $3 != "static" + abort sprintf("Non-static stack usage for function '%s'\n", $1) + end + {function_name: $1, stack_usage: $2.to_i} + else + abort sprintf("Failed to parse stack usage information '%s'\n", l.strip) + end + end +end + +toolchains = [ + "arm-none-eabi-", + "arm-linux-gnueabihf-", + "arm-none-linux-gnueabi-", + "armv7a-hardfloat-linux-gnueabi-", +] + +toolchain = toolchains.find { |toolchain| tool_exists("#{toolchain}gcc") } +abort "Can't find any usable ARM crosscompiler.\n" unless toolchain + +# Compile the source file +system("#{toolchain}gcc -c -Os -marm -march=armv5te -mfloat-abi=soft -fstack-usage -fpic -o #{ARGV[0]}.o #{ARGV[0]}") +exit($?.to_i) if $?.to_i != 0 + +# Read the stack usage information +stack_usage = parse_stack_usage("#{ARGV[0]}.su") +if stack_usage.size != 1 + abort sprintf("Expected only one function in the source file, but got %s.\n", + stack_usage.map {|a| "'" + a[:function_name] + "()'" }.join(", ")) +end + +`#{toolchain}size -A #{ARGV[0]}.o`.each_line do |l| + if l =~ /(\S+)\s+(\S+)/ + if ($1 == ".data" || $1 == ".bss" || $1 == ".rodata") && $2.to_i > 0 + abort "Can't have non-empty '.data', '.bss' or '.rodata' section." + end + end +end + +`#{toolchain}objdump -t #{ARGV[0]}.o`.each_line do |l| + if l =~ /\*UND\*/ + abort "External references are not allowed: '#{l.strip}'.\n" + end +end + +function_name = stack_usage[0][:function_name] + +# Read the source file and strip multiline C comments +sourcefile = File.read(ARGV[0]).gsub(/\/\*.*?\*\//m, "") + +# Try to find the function and its arguments +unless sourcefile =~ /#{function_name}\((.*?)\)/m + abort sprintf("Can't find the function '%s()' in the source file.\n", + function_name) +end + +# Extract the function argument names +function_args = $1.split(",").map {|a| if a.strip =~ /([^\*\s]+)$/ then $1 end } + +# Check if there is any return value +have_retval = !(sourcefile =~ /void\s+#{function_name}/m) + +############################################################################### +# Generate output file +############################################################################### + +out = File.open(ARGV[1], "w") + +out.printf("/* Automatically generated, do not edit! */\n\n") + +out.printf("static void\n") +funcdecl = sprintf("aw_fel_remotefunc_prepare_#{function_name}(feldev_handle *dev,") +out.printf("%s\n", funcdecl) +out.printf("%s", function_args.map {|a| + " " * funcdecl.index("(") + " uint32_t " + a }.join(",\n")) +out.printf(")\n{\n") + +out.printf("\tstatic uint8_t arm_code[] = {\n") +`#{toolchain}objdump -d #{ARGV[0]}.o`.each_line {|l| + next unless l =~ /(\h+)\:\s+(\h+)\s+(\S+)\s+([^;]*)/ + addr = $1 + opcode = $2 + p1 = $3 + p2 = $4.strip + opcode = opcode.scan(/../).map {|a| "0x" + a }.reverse.join(", ") + out.printf("\t\t%s, /* %4s: %-8s %-34s \x2a/\n", opcode, addr, p1, p2) +} +out.printf("\t};\n") + +out.printf("\tuint32_t args[] = {\n\t\t") +out.printf("%s\n\t};\n", function_args.join(",\n\t\t")) + +out.printf("\taw_fel_remotefunc_prepare(dev, %d, arm_code, sizeof(arm_code), %d, args);\n", + stack_usage[0][:stack_usage], function_args.size) + +out.printf("}\n") diff --git a/fel-remotefunc-spi-data-transfer.c b/fel-remotefunc-spi-data-transfer.c new file mode 100644 index 0000000..b640dc6 --- /dev/null +++ b/fel-remotefunc-spi-data-transfer.c @@ -0,0 +1,187 @@ +/* + * Copyright © 2016 Siarhei Siamashka + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +typedef unsigned int u32; +typedef unsigned char u8; + +#define readl(addr) (*((volatile u32 *)(addr))) +#define writel(v, addr) (*((volatile u32 *)(addr)) = (u32)(v)) +#define readb(addr) (*((volatile u8 *)(addr))) +#define writeb(v, addr) (*((volatile u8 *)(addr)) = (u8)(v)) + +/* + * This is a basic full-duplex SPI data transfer function (we are sending a + * block of data and receiving the same amount of data back), doing the job + * without any help from DMA. And because we can be running in some rather + * adverse conditions (with default PMIC settings, low CPU clock speed and + * CPU caches disabled), it is necessary to use 32-bit accesses to read/write + * the FIFO buffers. As a result, Allwinner A13 with the default 408MHz CPU + * clock speed can successfully handle at least 12 MHz SPI clock speed. + * + * Supports both sun4i and sun6i variants of the SPI controller (they only + * need different hardware register addresses passed as arguments). + */ +static void inline __attribute((always_inline)) spi_data_transfer(void *buf, + u32 bufsize, + void *spi_ctl_reg, + u32 spi_ctl_xch_bitmask, + void *spi_fifo_reg, + void *spi_tx_reg, + void *spi_rx_reg, + void *spi_bc_reg, + void *spi_tc_reg, + void *spi_bcc_reg) +{ + u32 cnt; + u32 rxsize = bufsize; + u32 txsize = bufsize; + u8 *rxbuf8 = buf; + u8 *txbuf8 = buf; + u32 *rxbuf; + u32 *txbuf; + u32 cpsr; + + /* sun6i uses 3 registers, sun4i only needs 2 */ + writel(bufsize, spi_bc_reg); + writel(bufsize, spi_tc_reg); + if (spi_bcc_reg) + writel(bufsize, spi_bcc_reg); + + /* Fill the TX buffer with some initial data */ + cnt = (-(u32)txbuf8 & 3) + 60; + if (cnt > txsize) + cnt = txsize; + while (cnt-- > 0) { + writeb(*txbuf8++, spi_tx_reg); + txsize--; + } + + /* Temporarily disable IRQ & FIQ */ + asm volatile("mrs %0, cpsr" : "=r" (cpsr)); + asm volatile("msr cpsr_c, %0" :: "r" (cpsr | 0xC0)); + + /* Start the data transfer */ + writel(readl(spi_ctl_reg) | spi_ctl_xch_bitmask, spi_ctl_reg); + + /* Read the initial unaligned part of the data */ + cnt = (-(u32)rxbuf8 & 3); + if (cnt > rxsize) + cnt = rxsize; + while (cnt > 0) { + u32 fiforeg = readl(spi_fifo_reg); + int rxfifo = fiforeg & 0x7F; + if (rxfifo > 0) { + *rxbuf8++ = readb(spi_rx_reg); + cnt--; + rxsize--; + } + } + + /* Fast processing of the aligned part (read/write 32-bit at a time) */ + rxbuf = (u32 *)rxbuf8; + txbuf = (u32 *)txbuf8; + while (rxsize >= 4) { + u32 fiforeg = readl(spi_fifo_reg); + int rxfifo = fiforeg & 0x7F; + int txfifo = (fiforeg >> 16) & 0x7F; + if (rxfifo >= 4) { + *rxbuf++ = readl(spi_rx_reg); + rxsize -= 4; + } + if (txfifo < 60 && txsize >= 4) { + writel(*txbuf++, spi_tx_reg); + txsize -= 4; + } + } + + /* Handle the trailing part pf the data */ + rxbuf8 = (u8 *)rxbuf; + txbuf8 = (u8 *)txbuf; + while (rxsize >= 1) { + u32 fiforeg = readl(spi_fifo_reg); + int rxfifo = fiforeg & 0x7F; + int txfifo = (fiforeg >> 16) & 0x7F; + if (rxfifo >= 1) { + *rxbuf8++ = readb(spi_rx_reg); + rxsize -= 1; + } + if (txfifo < 60 && txsize >= 1) { + writeb(*txbuf8++, spi_tx_reg); + txsize -= 1; + } + } + + /* Restore CPSR */ + asm volatile("msr cpsr_c, %0" :: "r" (cpsr)); +} + +void spi_batch_data_transfer(u8 *buf, + void *spi_ctl_reg, + u32 spi_ctl_xch_bitmask, + void *spi_fifo_reg, + void *spi_tx_reg, + void *spi_rx_reg, + void *spi_bc_reg, + void *spi_tc_reg, + void *spi_bcc_reg) +{ + u8 wait_for_completion_cmd[2]; + u8 *backup_buf; + u32 bufsize; + + while (1) { + u32 code = (buf[0] << 8) | buf[1]; + + /* End of data */ + if (code == 0) + return; + + if (code == 0xFFFF) { + /* Wait for completion, part 1 */ + backup_buf = buf; + buf = wait_for_completion_cmd; + wait_for_completion_cmd[0] = 0x05; + bufsize = 2; + } else { + /* Normal buffer */ + buf += 2; + bufsize = code; + } + + spi_data_transfer(buf, bufsize, spi_ctl_reg, spi_ctl_xch_bitmask, + spi_fifo_reg, spi_tx_reg, spi_rx_reg, + spi_bc_reg, spi_tc_reg, spi_bcc_reg); + buf += bufsize; + + if (code == 0xFFFF) { + /* Wait for completion, part 2 */ + buf = backup_buf; + if (wait_for_completion_cmd[1] & 1) { + /* Still busy */ + continue; + } + /* Advance to the next code */ + buf = backup_buf + 2; + } + } +} diff --git a/fel-remotefunc-spi-data-transfer.h b/fel-remotefunc-spi-data-transfer.h new file mode 100644 index 0000000..673808c --- /dev/null +++ b/fel-remotefunc-spi-data-transfer.h @@ -0,0 +1,147 @@ +/* Automatically generated, do not edit! */ + +static void +aw_fel_remotefunc_prepare_spi_batch_data_transfer(feldev_handle *dev, + uint32_t buf, + uint32_t spi_ctl_reg, + uint32_t spi_ctl_xch_bitmask, + uint32_t spi_fifo_reg, + uint32_t spi_tx_reg, + uint32_t spi_rx_reg, + uint32_t spi_bc_reg, + uint32_t spi_tc_reg, + uint32_t spi_bcc_reg) +{ + static uint8_t arm_code[] = { + 0xf0, 0x4f, 0x2d, 0xe9, /* 0: push {r4, r5, r6, r7, r8, r9, sl, fp, lr} */ + 0xc8, 0x91, 0x9f, 0xe5, /* 4: ldr r9, [pc, #456] */ + 0x14, 0xd0, 0x4d, 0xe2, /* 8: sub sp, sp, #20 */ + 0x00, 0x20, 0x8d, 0xe5, /* c: str r2, [sp] */ + 0x0c, 0x20, 0x8d, 0xe2, /* 10: add r2, sp, #12 */ + 0x04, 0x20, 0x8d, 0xe5, /* 14: str r2, [sp, #4] */ + 0x01, 0x60, 0xd0, 0xe5, /* 18: ldrb r6, [r0, #1] */ + 0x00, 0x20, 0xd0, 0xe5, /* 1c: ldrb r2, [r0] */ + 0x06, 0x24, 0x82, 0xe1, /* 20: orr r2, r2, r6, lsl #8 */ + 0x22, 0x64, 0xa0, 0xe1, /* 24: lsr r6, r2, #8 */ + 0x02, 0x64, 0x86, 0xe1, /* 28: orr r6, r6, r2, lsl #8 */ + 0x06, 0x68, 0xa0, 0xe1, /* 2c: lsl r6, r6, #16 */ + 0x26, 0x68, 0xa0, 0xe1, /* 30: lsr r6, r6, #16 */ + 0x00, 0x00, 0x56, 0xe3, /* 34: cmp r6, #0 */ + 0x63, 0x00, 0x00, 0x0a, /* 38: beq 1cc */ + 0x09, 0x00, 0x56, 0xe1, /* 3c: cmp r6, r9 */ + 0x05, 0x20, 0xa0, 0x03, /* 40: moveq r2, #5 */ + 0x0c, 0x20, 0xcd, 0x05, /* 44: strbeq r2, [sp, #12] */ + 0x40, 0x20, 0x9d, 0xe5, /* 48: ldr r2, [sp, #64] */ + 0x06, 0x50, 0xa0, 0x11, /* 4c: movne r5, r6 */ + 0x02, 0x50, 0xa0, 0x03, /* 50: moveq r5, #2 */ + 0x00, 0x50, 0x82, 0xe5, /* 54: str r5, [r2] */ + 0x44, 0x20, 0x9d, 0xe5, /* 58: ldr r2, [sp, #68] */ + 0x00, 0x70, 0xa0, 0x01, /* 5c: moveq r7, r0 */ + 0x00, 0x50, 0x82, 0xe5, /* 60: str r5, [r2] */ + 0x48, 0x20, 0x9d, 0xe5, /* 64: ldr r2, [sp, #72] */ + 0x04, 0x00, 0x9d, 0x05, /* 68: ldreq r0, [sp, #4] */ + 0x02, 0x00, 0x80, 0x12, /* 6c: addne r0, r0, #2 */ + 0x00, 0x00, 0x52, 0xe3, /* 70: cmp r2, #0 */ + 0x00, 0x50, 0x82, 0x15, /* 74: strne r5, [r2] */ + 0x00, 0x20, 0x60, 0xe2, /* 78: rsb r2, r0, #0 */ + 0x03, 0x20, 0x02, 0xe2, /* 7c: and r2, r2, #3 */ + 0x3c, 0x40, 0x82, 0xe2, /* 80: add r4, r2, #60 */ + 0x04, 0x00, 0x55, 0xe1, /* 84: cmp r5, r4 */ + 0x05, 0x40, 0xa0, 0x31, /* 88: movcc r4, r5 */ + 0x04, 0xe0, 0x80, 0xe0, /* 8c: add lr, r0, r4 */ + 0x00, 0xc0, 0xa0, 0xe1, /* 90: mov ip, r0 */ + 0x0e, 0x00, 0x5c, 0xe1, /* 94: cmp ip, lr */ + 0x1b, 0x00, 0x00, 0x1a, /* 98: bne 10c */ + 0x04, 0x40, 0x45, 0xe0, /* 9c: sub r4, r5, r4 */ + 0x00, 0xa0, 0x0f, 0xe1, /* a0: mrs sl, CPSR */ + 0xc0, 0xc0, 0x8a, 0xe3, /* a4: orr ip, sl, #192 */ + 0x0c, 0xf0, 0x21, 0xe1, /* a8: msr CPSR_c, ip */ + 0x00, 0xc0, 0x91, 0xe5, /* ac: ldr ip, [r1] */ + 0x00, 0x80, 0x9d, 0xe5, /* b0: ldr r8, [sp] */ + 0x02, 0x00, 0x55, 0xe1, /* b4: cmp r5, r2 */ + 0x0c, 0xc0, 0x88, 0xe1, /* b8: orr ip, r8, ip */ + 0x05, 0x20, 0xa0, 0x31, /* bc: movcc r2, r5 */ + 0x00, 0xc0, 0x81, 0xe5, /* c0: str ip, [r1] */ + 0x02, 0x80, 0x80, 0xe0, /* c4: add r8, r0, r2 */ + 0x00, 0xc0, 0xa0, 0xe1, /* c8: mov ip, r0 */ + 0x0c, 0x00, 0x58, 0xe1, /* cc: cmp r8, ip */ + 0x11, 0x00, 0x00, 0x1a, /* d0: bne 11c */ + 0x02, 0x20, 0x45, 0xe0, /* d4: sub r2, r5, r2 */ + 0x03, 0x00, 0x52, 0xe3, /* d8: cmp r2, #3 */ + 0x14, 0x00, 0x00, 0x8a, /* dc: bhi 134 */ + 0x00, 0x00, 0x52, 0xe3, /* e0: cmp r2, #0 */ + 0x25, 0x00, 0x00, 0x1a, /* e4: bne 180 */ + 0x0a, 0xf0, 0x21, 0xe1, /* e8: msr CPSR_c, sl */ + 0x09, 0x00, 0x56, 0xe1, /* ec: cmp r6, r9 */ + 0x05, 0x00, 0x80, 0x10, /* f0: addne r0, r0, r5 */ + 0xc7, 0xff, 0xff, 0x1a, /* f4: bne 18 */ + 0x0d, 0x20, 0xdd, 0xe5, /* f8: ldrb r2, [sp, #13] */ + 0x01, 0x00, 0x12, 0xe3, /* fc: tst r2, #1 */ + 0x02, 0x00, 0x87, 0x02, /* 100: addeq r0, r7, #2 */ + 0x07, 0x00, 0xa0, 0x11, /* 104: movne r0, r7 */ + 0xc2, 0xff, 0xff, 0xea, /* 108: b 18 */ + 0x38, 0xa0, 0x9d, 0xe5, /* 10c: ldr sl, [sp, #56] */ + 0x01, 0x80, 0xdc, 0xe4, /* 110: ldrb r8, [ip], #1 */ + 0x00, 0x80, 0xca, 0xe5, /* 114: strb r8, [sl] */ + 0xdd, 0xff, 0xff, 0xea, /* 118: b 94 */ + 0x00, 0xb0, 0x93, 0xe5, /* 11c: ldr fp, [r3] */ + 0x7f, 0x00, 0x1b, 0xe3, /* 120: tst fp, #127 */ + 0x3c, 0xb0, 0x9d, 0x15, /* 124: ldrne fp, [sp, #60] */ + 0x00, 0xb0, 0xdb, 0x15, /* 128: ldrbne fp, [fp] */ + 0x01, 0xb0, 0xcc, 0x14, /* 12c: strbne fp, [ip], #1 */ + 0xe5, 0xff, 0xff, 0xea, /* 130: b cc */ + 0x00, 0xb0, 0x93, 0xe5, /* 134: ldr fp, [r3] */ + 0x7c, 0x00, 0x1b, 0xe3, /* 138: tst fp, #124 */ + 0x2b, 0x88, 0xa0, 0xe1, /* 13c: lsr r8, fp, #16 */ + 0x3c, 0xb0, 0x9d, 0x15, /* 140: ldrne fp, [sp, #60] */ + 0x7f, 0x80, 0x08, 0xe2, /* 144: and r8, r8, #127 */ + 0x00, 0xb0, 0x9b, 0x15, /* 148: ldrne fp, [fp] */ + 0x04, 0xb0, 0x8c, 0x14, /* 14c: strne fp, [ip], #4 */ + 0x04, 0x20, 0x42, 0x12, /* 150: subne r2, r2, #4 */ + 0x3b, 0x00, 0x58, 0xe3, /* 154: cmp r8, #59 */ + 0x00, 0x80, 0xa0, 0xc3, /* 158: movgt r8, #0 */ + 0x01, 0x80, 0xa0, 0xd3, /* 15c: movle r8, #1 */ + 0x03, 0x00, 0x54, 0xe3, /* 160: cmp r4, #3 */ + 0x00, 0x80, 0xa0, 0x93, /* 164: movls r8, #0 */ + 0x00, 0x00, 0x58, 0xe3, /* 168: cmp r8, #0 */ + 0x38, 0xb0, 0x9d, 0x15, /* 16c: ldrne fp, [sp, #56] */ + 0x04, 0x80, 0x9e, 0x14, /* 170: ldrne r8, [lr], #4 */ + 0x04, 0x40, 0x44, 0x12, /* 174: subne r4, r4, #4 */ + 0x00, 0x80, 0x8b, 0x15, /* 178: strne r8, [fp] */ + 0xd5, 0xff, 0xff, 0xea, /* 17c: b d8 */ + 0x00, 0xb0, 0x93, 0xe5, /* 180: ldr fp, [r3] */ + 0x7f, 0x00, 0x1b, 0xe3, /* 184: tst fp, #127 */ + 0x2b, 0x88, 0xa0, 0xe1, /* 188: lsr r8, fp, #16 */ + 0x3c, 0xb0, 0x9d, 0x15, /* 18c: ldrne fp, [sp, #60] */ + 0x7f, 0x80, 0x08, 0xe2, /* 190: and r8, r8, #127 */ + 0x00, 0xb0, 0xdb, 0x15, /* 194: ldrbne fp, [fp] */ + 0x01, 0xb0, 0xcc, 0x14, /* 198: strbne fp, [ip], #1 */ + 0x01, 0x20, 0x42, 0x12, /* 19c: subne r2, r2, #1 */ + 0x3b, 0x00, 0x58, 0xe3, /* 1a0: cmp r8, #59 */ + 0x00, 0x80, 0xa0, 0xc3, /* 1a4: movgt r8, #0 */ + 0x01, 0x80, 0xa0, 0xd3, /* 1a8: movle r8, #1 */ + 0x00, 0x00, 0x54, 0xe3, /* 1ac: cmp r4, #0 */ + 0x00, 0x80, 0xa0, 0x03, /* 1b0: moveq r8, #0 */ + 0x00, 0x00, 0x58, 0xe3, /* 1b4: cmp r8, #0 */ + 0x38, 0xb0, 0x9d, 0x15, /* 1b8: ldrne fp, [sp, #56] */ + 0x01, 0x80, 0xde, 0x14, /* 1bc: ldrbne r8, [lr], #1 */ + 0x01, 0x40, 0x44, 0x12, /* 1c0: subne r4, r4, #1 */ + 0x00, 0x80, 0xcb, 0x15, /* 1c4: strbne r8, [fp] */ + 0xc4, 0xff, 0xff, 0xea, /* 1c8: b e0 */ + 0x14, 0xd0, 0x8d, 0xe2, /* 1cc: add sp, sp, #20 */ + 0xf0, 0x8f, 0xbd, 0xe8, /* 1d0: pop {r4, r5, r6, r7, r8, r9, sl, fp, pc} */ + 0xff, 0xff, 0x00, 0x00, /* 1d4: .word 0x0000ffff */ + }; + uint32_t args[] = { + buf, + spi_ctl_reg, + spi_ctl_xch_bitmask, + spi_fifo_reg, + spi_tx_reg, + spi_rx_reg, + spi_bc_reg, + spi_tc_reg, + spi_bcc_reg + }; + aw_fel_remotefunc_prepare(dev, 56, arm_code, sizeof(arm_code), 9, args); +} diff --git a/fel-sdboot.S b/fel-sdboot.S new file mode 100644 index 0000000..1ad1bd0 --- /dev/null +++ b/fel-sdboot.S @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 Bernhard Nortmann + * + * Based on previous works + * Copyright (C) 2016 Siarhei Siamashka + * Copyright (C) 2012 Henrik Nordstrom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +/* + * This file is a utility stub (bootloader code) to force the device into + * FEL mode, by jumping directly to the corresponding (N-)BROM entry point. + * + * Build instructions: + * make fel-sdboot.sunxi + * + * If needed, adjust CROSS_COMPILE and MKSUNXIBOOT according to your + * toolchain, e.g. + * make fel-sdboot.sunxi CROSS_COMPILE=armv7a-hardfloat-linux-gnueabi- \ + * MKSUNXIBOOT=/usr/local/bin/mksunxiboot + * + * + * Install instructions: + * dd if=fel-sdboot.sunxi of=/dev/sdX bs=1024 seek=8 + */ + +SCTRL .req r0 +.equ V_BIT, (1 << 13) + +.equ BROM_ENTRY_LOW, 0x00000020 +.equ BROM_ENTRY_HIGH, 0xFFFF0020 + +/* + * In cases where insufficient padding is added by an old mksunxiboot, + * _start may be 0x20, which means that the instruction at 0x28 could get + * corrupted by the BROM - see https://patchwork.ozlabs.org/patch/622173/ + * + * Apply a workaround to avoid (= skip over) that memory location. + * _main would be at 0x30 in that particular case. With newer (properly + * fixed) versions of mksunxiboot, this code ends up at higher addresses + * and will be moot, but harmless. + */ +_start: + b _main + nop + nop + nop + +_main: + mrc p15, 0, SCTRL, c1, c0, 0 + tst SCTRL, #V_BIT @ test SCTRL.V + moveq lr, #BROM_ENTRY_LOW + ldrne lr, =BROM_ENTRY_HIGH + bx lr diff --git a/fel-sdboot.lds b/fel-sdboot.lds new file mode 100644 index 0000000..9ba718d --- /dev/null +++ b/fel-sdboot.lds @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2012 Henrik Nordstrom + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +SECTIONS +{ + . = 0x0030; + .text : { *(.text) } + /DISCARD/ : { *(.dynstr*) } + /DISCARD/ : { *(.dynamic*) } + /DISCARD/ : { *(.plt*) } + /DISCARD/ : { *(.interp*) } + /DISCARD/ : { *(.gnu*) } + /DISCARD/ : { *(.note*) } +} diff --git a/fel-spiflash.c b/fel-spiflash.c new file mode 100644 index 0000000..95cab87 --- /dev/null +++ b/fel-spiflash.c @@ -0,0 +1,606 @@ +/* + * (C) Copyright 2016 Siarhei Siamashka + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "fel_lib.h" +#include "progress.h" + +#include "fel-remotefunc-spi-data-transfer.h" + +/*****************************************************************************/ + +typedef struct { + uint32_t id; + uint8_t write_enable_cmd; + uint8_t large_erase_cmd; + uint32_t large_erase_size; + uint8_t small_erase_cmd; + uint32_t small_erase_size; + uint8_t program_cmd; + uint32_t program_size; + char *text_description; +} spi_flash_info_t; + +spi_flash_info_t spi_flash_info[] = { + { .id = 0xEF40, .write_enable_cmd = 0x6, + .large_erase_cmd = 0xD8, .large_erase_size = 64 * 1024, + .small_erase_cmd = 0x20, .small_erase_size = 4 * 1024, + .program_cmd = 0x02, .program_size = 256, + .text_description = "Winbond W25Qxx" }, + { .id = 0xC220, .write_enable_cmd = 0x6, + .large_erase_cmd = 0xD8, .large_erase_size = 64 * 1024, + .small_erase_cmd = 0x20, .small_erase_size = 4 * 1024, + .program_cmd = 0x02, .program_size = 256, + .text_description = "Macronix MX25Lxxxx" }, + { .id = 0x1C70, .write_enable_cmd = 0x6, + .large_erase_cmd = 0xD8, .large_erase_size = 64 * 1024, + .small_erase_cmd = 0x20, .small_erase_size = 4 * 1024, + .program_cmd = 0x02, .program_size = 256, + .text_description = "Eon EN25QHxx" }, +}; + +spi_flash_info_t default_spi_flash_info = { + .id = 0x0000, .write_enable_cmd = 0x6, + .large_erase_cmd = 0xD8, .large_erase_size = 64 * 1024, + .small_erase_cmd = 0x20, .small_erase_size = 4 * 1024, + .program_cmd = 0x02, .program_size = 256, + .text_description = "Unknown", +}; + +/*****************************************************************************/ + +uint32_t fel_readl(feldev_handle *dev, uint32_t addr); +void fel_writel(feldev_handle *dev, uint32_t addr, uint32_t val); +#define readl(addr) fel_readl(dev, (addr)) +#define writel(val, addr) fel_writel(dev, (addr), (val)) + +#define PA (0) +#define PB (1) +#define PC (2) + +#define CCM_SPI0_CLK (0x01C20000 + 0xA0) +#define CCM_AHB_GATING0 (0x01C20000 + 0x60) +#define CCM_AHB_GATE_SPI0 (1 << 20) +#define SUN6I_BUS_SOFT_RST_REG0 (0x01C20000 + 0x2C0) +#define SUN6I_SPI0_RST (1 << 20) +#define SUNIV_PLL6_CTL (0x01c20000 + 0x28) +#define SUNIV_AHB_APB_CFG (0x01c20000 + 0x54) + +#define H6_CCM_SPI0_CLK (0x03001000 + 0x940) +#define H6_CCM_SPI_BGR (0x03001000 + 0x96C) +#define H6_CCM_SPI0_GATE_RESET (1 << 0 | 1 << 16) + +#define SUNIV_GPC_SPI0 (2) +#define SUNXI_GPC_SPI0 (3) +#define SUN50I_GPC_SPI0 (4) + +#define SUN4I_CTL_ENABLE (1 << 0) +#define SUN4I_CTL_MASTER (1 << 1) +#define SUN4I_CTL_TF_RST (1 << 8) +#define SUN4I_CTL_RF_RST (1 << 9) +#define SUN4I_CTL_XCH (1 << 10) + +#define SUN6I_TCR_XCH (1U << 31) + +#define SUN4I_SPI0_CCTL (spi_base(dev) + 0x1C) +#define SUN4I_SPI0_CTL (spi_base(dev) + 0x08) +#define SUN4I_SPI0_RX (spi_base(dev) + 0x00) +#define SUN4I_SPI0_TX (spi_base(dev) + 0x04) +#define SUN4I_SPI0_FIFO_STA (spi_base(dev) + 0x28) +#define SUN4I_SPI0_BC (spi_base(dev) + 0x20) +#define SUN4I_SPI0_TC (spi_base(dev) + 0x24) + +#define SUN6I_SPI0_CCTL (spi_base(dev) + 0x24) +#define SUN6I_SPI0_GCR (spi_base(dev) + 0x04) +#define SUN6I_SPI0_TCR (spi_base(dev) + 0x08) +#define SUN6I_SPI0_FIFO_STA (spi_base(dev) + 0x1C) +#define SUN6I_SPI0_MBC (spi_base(dev) + 0x30) +#define SUN6I_SPI0_MTC (spi_base(dev) + 0x34) +#define SUN6I_SPI0_BCC (spi_base(dev) + 0x38) +#define SUN6I_SPI0_TXD (spi_base(dev) + 0x200) +#define SUN6I_SPI0_RXD (spi_base(dev) + 0x300) + +#define CCM_SPI0_CLK_DIV_BY_2 (0x1000) +#define CCM_SPI0_CLK_DIV_BY_4 (0x1001) +#define CCM_SPI0_CLK_DIV_BY_6 (0x1002) +#define CCM_SPI0_CLK_DIV_BY_32 (0x100f) + +static uint32_t gpio_base(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + switch (soc_info->soc_id) { + case 0x1816: /* V536 */ + case 0x1817: /* V831 */ + case 0x1728: /* H6 */ + case 0x1823: /* H616 */ + return 0x0300B000; + default: + return 0x01C20800; + } +} + +static uint32_t spi_base(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + switch (soc_info->soc_id) { + case 0x1623: /* A10 */ + case 0x1625: /* A13 */ + case 0x1651: /* A20 */ + case 0x1663: /* F1C100s */ + case 0x1701: /* R40 */ + return 0x01C05000; + case 0x1816: /* V536 */ + case 0x1817: /* V831 */ + case 0x1728: /* H6 */ + case 0x1823: /* H616 */ + return 0x05010000; + default: + return 0x01C68000; + } +} + +/* + * Configure pin function on a GPIO port + */ +static void gpio_set_cfgpin(feldev_handle *dev, int port_num, int pin_num, + int val) +{ + uint32_t port_base = gpio_base(dev) + port_num * 0x24; + uint32_t cfg_reg = port_base + 4 * (pin_num / 8); + uint32_t pin_idx = pin_num % 8; + uint32_t x = readl(cfg_reg); + x &= ~(0x7 << (pin_idx * 4)); + x |= val << (pin_idx * 4); + writel(x, cfg_reg); +} + +static bool spi_is_sun6i(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + switch (soc_info->soc_id) { + case 0x1623: /* A10 */ + case 0x1625: /* A13 */ + case 0x1651: /* A20 */ + return false; + default: + return true; + } +} + +static bool soc_is_h6_style(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + switch (soc_info->soc_id) { + case 0x1816: /* V536 */ + case 0x1817: /* V831 */ + case 0x1728: /* H6 */ + case 0x1823: /* H616 */ + return true; + default: + return false; + } +} + +/* + * Init the SPI0 controller and setup pins muxing. + */ +static bool spi0_init(feldev_handle *dev) +{ + uint32_t reg_val; + soc_info_t *soc_info = dev->soc_info; + if (!soc_info) { + printf("Unable to fetch device information. " + "Possibly unknown device.\n"); + return false; + } + + /* Setup SPI0 pins muxing */ + switch (soc_info->soc_id) { + case 0x1663: /* Allwinner F1C100s/F1C600/R6/F1C100A/F1C500 */ + gpio_set_cfgpin(dev, PC, 0, SUNIV_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 1, SUNIV_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 2, SUNIV_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 3, SUNIV_GPC_SPI0); + break; + case 0x1625: /* Allwinner A13 */ + case 0x1680: /* Allwinner H3 */ + case 0x1681: /* Allwinner V3s */ + case 0x1718: /* Allwinner H5 */ + gpio_set_cfgpin(dev, PC, 0, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 1, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 2, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 3, SUNXI_GPC_SPI0); + break; + case 0x1623: /* Allwinner A10 */ + case 0x1651: /* Allwinner A20 */ + case 0x1701: /* Allwinner R40 */ + gpio_set_cfgpin(dev, PC, 0, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 1, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 2, SUNXI_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 23, SUNXI_GPC_SPI0); + break; + case 0x1689: /* Allwinner A64 */ + gpio_set_cfgpin(dev, PC, 0, SUN50I_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 1, SUN50I_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 2, SUN50I_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 3, SUN50I_GPC_SPI0); + break; + case 0x1816: /* Allwinner V536 */ + case 0x1817: /* Allwinner V831 */ + gpio_set_cfgpin(dev, PC, 1, SUN50I_GPC_SPI0); /* SPI0-CS */ + /* fall-through */ + case 0x1728: /* Allwinner H6 */ + gpio_set_cfgpin(dev, PC, 0, SUN50I_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 2, SUN50I_GPC_SPI0); + gpio_set_cfgpin(dev, PC, 3, SUN50I_GPC_SPI0); + /* PC5 is SPI0-CS on the H6, and SPI0-HOLD on the V831 */ + gpio_set_cfgpin(dev, PC, 5, SUN50I_GPC_SPI0); + break; + case 0x1823: /* Allwinner H616 */ + gpio_set_cfgpin(dev, PC, 0, SUN50I_GPC_SPI0); /* SPI0_CLK */ + gpio_set_cfgpin(dev, PC, 2, SUN50I_GPC_SPI0); /* SPI0_MOSI */ + gpio_set_cfgpin(dev, PC, 3, SUN50I_GPC_SPI0); /* SPI0_CS0 */ + gpio_set_cfgpin(dev, PC, 4, SUN50I_GPC_SPI0); /* SPI0_MISO */ + break; + default: /* Unknown/Unsupported SoC */ + printf("SPI support not implemented yet for %x (%s)!\n", + soc_info->soc_id, soc_info->name); + return false; + } + + if (soc_is_h6_style(dev)) { + reg_val = readl(H6_CCM_SPI_BGR); + reg_val |= H6_CCM_SPI0_GATE_RESET; + writel(reg_val, H6_CCM_SPI_BGR); + } else { + if (spi_is_sun6i(dev)) { + /* Deassert SPI0 reset */ + reg_val = readl(SUN6I_BUS_SOFT_RST_REG0); + reg_val |= SUN6I_SPI0_RST; + writel(reg_val, SUN6I_BUS_SOFT_RST_REG0); + } + + reg_val = readl(CCM_AHB_GATING0); + reg_val |= CCM_AHB_GATE_SPI0; + writel(reg_val, CCM_AHB_GATING0); + } + + if (soc_info->soc_id == 0x1663) { /* suniv F1C100s */ + /* + * suniv doesn't have a module clock for SPI0 and the clock + * source is always the AHB clock. Setup AHB to 200 MHz by + * setting PLL6 to 600 MHz with a divider of 3, then program + * the internal SPI dividier to 32. + */ + + /* Set PLL6 to 600MHz */ + writel(0x80041801, SUNIV_PLL6_CTL); + /* PLL6:AHB:APB = 6:2:1 */ + writel(0x00003180, SUNIV_AHB_APB_CFG); + /* divide by 32 */ + writel(CCM_SPI0_CLK_DIV_BY_32, SUN6I_SPI0_CCTL); + } else { + /* divide 24MHz OSC by 4 */ + writel(CCM_SPI0_CLK_DIV_BY_4, + spi_is_sun6i(dev) ? SUN6I_SPI0_CCTL : SUN4I_SPI0_CCTL); + /* Choose 24MHz from OSC24M and enable clock */ + writel(1U << 31, + soc_is_h6_style(dev) ? H6_CCM_SPI0_CLK : CCM_SPI0_CLK); + } + + if (spi_is_sun6i(dev)) { + /* Enable SPI in the master mode and do a soft reset */ + reg_val = readl(SUN6I_SPI0_GCR); + reg_val |= (1U << 31) | 3; + writel(reg_val, SUN6I_SPI0_GCR); + /* Wait for completion */ + while (readl(SUN6I_SPI0_GCR) & (1U << 31)) {} + } else { + reg_val = readl(SUN4I_SPI0_CTL); + reg_val |= SUN4I_CTL_MASTER; + reg_val |= SUN4I_CTL_ENABLE | SUN4I_CTL_TF_RST | SUN4I_CTL_RF_RST; + writel(reg_val, SUN4I_SPI0_CTL); + } + + return true; +} + +/* + * Backup/restore the initial portion of the SRAM, which can be used as + * a temporary data buffer. + */ +static void *backup_sram(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + size_t bufsize = soc_info->scratch_addr - soc_info->spl_addr; + void *buf = malloc(bufsize); + aw_fel_read(dev, soc_info->spl_addr, buf, bufsize); + return buf; +} + +static void restore_sram(feldev_handle *dev, void *buf) +{ + soc_info_t *soc_info = dev->soc_info; + size_t bufsize = soc_info->scratch_addr - soc_info->spl_addr; + aw_fel_write(dev, buf, soc_info->spl_addr, bufsize); + free(buf); +} + +static void prepare_spi_batch_data_transfer(feldev_handle *dev, uint32_t buf) +{ + if (spi_is_sun6i(dev)) { + aw_fel_remotefunc_prepare_spi_batch_data_transfer(dev, + buf, + SUN6I_SPI0_TCR, + SUN6I_TCR_XCH, + SUN6I_SPI0_FIFO_STA, + SUN6I_SPI0_TXD, + SUN6I_SPI0_RXD, + SUN6I_SPI0_MBC, + SUN6I_SPI0_MTC, + SUN6I_SPI0_BCC); + } else { + aw_fel_remotefunc_prepare_spi_batch_data_transfer(dev, + buf, + SUN4I_SPI0_CTL, + SUN4I_CTL_XCH, + SUN4I_SPI0_FIFO_STA, + SUN4I_SPI0_TX, + SUN4I_SPI0_RX, + SUN4I_SPI0_BC, + SUN4I_SPI0_TC, + 0); + } +} + +/* + * Read data from the SPI flash. Use the first 4KiB of SRAM as the data buffer. + */ +void aw_fel_spiflash_read(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + progress_cb_t progress) +{ + soc_info_t *soc_info = dev->soc_info; + void *backup = backup_sram(dev); + uint8_t *buf8 = (uint8_t *)buf; + size_t max_chunk_size = soc_info->scratch_addr - soc_info->spl_addr; + if (max_chunk_size > 0x1000) + max_chunk_size = 0x1000; + uint8_t *cmdbuf = malloc(max_chunk_size); + memset(cmdbuf, 0, max_chunk_size); + aw_fel_write(dev, cmdbuf, soc_info->spl_addr, max_chunk_size); + + if (!spi0_init(dev)) + return; + + prepare_spi_batch_data_transfer(dev, soc_info->spl_addr); + + progress_start(progress, len); + while (len > 0) { + size_t chunk_size = len; + if (chunk_size > max_chunk_size - 8) + chunk_size = max_chunk_size - 8; + + memset(cmdbuf, 0, max_chunk_size); + cmdbuf[0] = (chunk_size + 4) >> 8; + cmdbuf[1] = (chunk_size + 4); + cmdbuf[2] = 3; + cmdbuf[3] = offset >> 16; + cmdbuf[4] = offset >> 8; + cmdbuf[5] = offset; + + if (chunk_size == max_chunk_size - 8) + aw_fel_write(dev, cmdbuf, soc_info->spl_addr, 6); + else + aw_fel_write(dev, cmdbuf, soc_info->spl_addr, chunk_size + 8); + aw_fel_remotefunc_execute(dev, NULL); + aw_fel_read(dev, soc_info->spl_addr + 6, buf8, chunk_size); + + len -= chunk_size; + offset += chunk_size; + buf8 += chunk_size; + progress_update(chunk_size); + } + + free(cmdbuf); + restore_sram(dev, backup); +} + +/* + * Write data to the SPI flash. Use the first 4KiB of SRAM as the data buffer. + */ + +#define CMD_WRITE_ENABLE 0x06 + +void aw_fel_spiflash_write_helper(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + size_t erase_size, uint8_t erase_cmd, + size_t program_size, uint8_t program_cmd) +{ + soc_info_t *soc_info = dev->soc_info; + uint8_t *buf8 = (uint8_t *)buf; + size_t max_chunk_size = soc_info->scratch_addr - soc_info->spl_addr; + size_t cmd_idx; + + if (max_chunk_size > 0x1000) + max_chunk_size = 0x1000; + uint8_t *cmdbuf = malloc(max_chunk_size); + cmd_idx = 0; + + prepare_spi_batch_data_transfer(dev, soc_info->spl_addr); + + while (len > 0) { + while (len > 0 && max_chunk_size - cmd_idx > program_size + 64) { + if (offset % erase_size == 0) { + /* Emit write enable command */ + cmdbuf[cmd_idx++] = 0; + cmdbuf[cmd_idx++] = 1; + cmdbuf[cmd_idx++] = CMD_WRITE_ENABLE; + /* Emit erase command */ + cmdbuf[cmd_idx++] = 0; + cmdbuf[cmd_idx++] = 4; + cmdbuf[cmd_idx++] = erase_cmd; + cmdbuf[cmd_idx++] = offset >> 16; + cmdbuf[cmd_idx++] = offset >> 8; + cmdbuf[cmd_idx++] = offset; + /* Emit wait for completion */ + cmdbuf[cmd_idx++] = 0xFF; + cmdbuf[cmd_idx++] = 0xFF; + } + /* Emit write enable command */ + cmdbuf[cmd_idx++] = 0; + cmdbuf[cmd_idx++] = 1; + cmdbuf[cmd_idx++] = CMD_WRITE_ENABLE; + /* Emit page program command */ + size_t write_count = program_size; + if (write_count > len) + write_count = len; + cmdbuf[cmd_idx++] = (4 + write_count) >> 8; + cmdbuf[cmd_idx++] = 4 + write_count; + cmdbuf[cmd_idx++] = program_cmd; + cmdbuf[cmd_idx++] = offset >> 16; + cmdbuf[cmd_idx++] = offset >> 8; + cmdbuf[cmd_idx++] = offset; + memcpy(cmdbuf + cmd_idx, buf8, write_count); + cmd_idx += write_count; + buf8 += write_count; + len -= write_count; + offset += write_count; + /* Emit wait for completion */ + cmdbuf[cmd_idx++] = 0xFF; + cmdbuf[cmd_idx++] = 0xFF; + } + /* Emit the end marker */ + cmdbuf[cmd_idx++] = 0; + cmdbuf[cmd_idx++] = 0; + + /* Flush */ + aw_fel_write(dev, cmdbuf, soc_info->spl_addr, cmd_idx); + aw_fel_remotefunc_execute(dev, NULL); + cmd_idx = 0; + } + + free(cmdbuf); +} + +void aw_fel_spiflash_write(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + progress_cb_t progress) +{ + void *backup = backup_sram(dev); + uint8_t *buf8 = (uint8_t *)buf; + + spi_flash_info_t *flash_info = &default_spi_flash_info; /* FIXME */ + + if ((offset % flash_info->small_erase_size) != 0) { + fprintf(stderr, "aw_fel_spiflash_write: 'addr' must be %d bytes aligned\n", + flash_info->small_erase_size); + exit(1); + } + + if (!spi0_init(dev)) + return; + + progress_start(progress, len); + while (len > 0) { + size_t write_count; + if ((offset % flash_info->large_erase_size) != 0 || + len < flash_info->large_erase_size) { + + write_count = flash_info->small_erase_size; + if (write_count > len) + write_count = len; + aw_fel_spiflash_write_helper(dev, offset, buf8, + write_count, + flash_info->small_erase_size, flash_info->small_erase_cmd, + flash_info->program_size, flash_info->program_cmd); + } else { + write_count = flash_info->large_erase_size; + if (write_count > len) + write_count = len; + aw_fel_spiflash_write_helper(dev, offset, buf8, + write_count, + flash_info->large_erase_size, flash_info->large_erase_cmd, + flash_info->program_size, flash_info->program_cmd); + } + + len -= write_count; + offset += write_count; + buf8 += write_count; + progress_update(write_count); + } + + restore_sram(dev, backup); +} + +/* + * Use the read JEDEC ID (9Fh) command. + */ +void aw_fel_spiflash_info(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + const char *manufacturer; + unsigned char buf[] = { 0, 4, 0x9F, 0, 0, 0, 0x0, 0x0 }; + void *backup = backup_sram(dev); + + if (!spi0_init(dev)) + return; + + aw_fel_write(dev, buf, soc_info->spl_addr, sizeof(buf)); + prepare_spi_batch_data_transfer(dev, soc_info->spl_addr); + aw_fel_remotefunc_execute(dev, NULL); + aw_fel_read(dev, soc_info->spl_addr, buf, sizeof(buf)); + + restore_sram(dev, backup); + + /* Assume that the MISO pin is either pulled up or down */ + if (buf[5] == 0x00 || buf[5] == 0xFF) { + printf("No SPI flash detected.\n"); + return; + } + + switch (buf[3]) { + case 0xEF: + manufacturer = "Winbond"; + break; + case 0xC2: + manufacturer = "Macronix"; + break; + case 0x1C: + manufacturer = "Eon"; + break; + default: + manufacturer = "Unknown"; + break; + } + + printf("Manufacturer: %s (%02Xh), model: %02Xh, size: %d bytes.\n", + manufacturer, buf[3], buf[4], (1U << buf[5])); +} + +/* + * Show a help message about the available "spiflash-*" commands. + */ +void aw_fel_spiflash_help(void) +{ + printf(" spiflash-info Retrieves basic information\n" + " spiflash-read addr length file Write SPI flash contents into file\n" + " spiflash-write addr file Store file contents into SPI flash\n"); +} diff --git a/fel-spiflash.h b/fel-spiflash.h new file mode 100644 index 0000000..03a9dd6 --- /dev/null +++ b/fel-spiflash.h @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2016 Siarhei Siamashka + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _SUNXI_TOOLS_FEL_SPIFLASH_H +#define _SUNXI_TOOLS_FEL_SPIFLASH_H + +#include "fel_lib.h" +#include "progress.h" + +void aw_fel_spiflash_read(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + progress_cb_t progress); +void aw_fel_spiflash_write(feldev_handle *dev, + uint32_t offset, void *buf, size_t len, + progress_cb_t progress); +void aw_fel_spiflash_info(feldev_handle *dev); +void aw_fel_spiflash_help(void); +void aw_fel_spi0_init(feldev_handle *dev); + +#endif diff --git a/fel.c b/fel.c new file mode 100644 index 0000000..c150331 --- /dev/null +++ b/fel.c @@ -0,0 +1,1494 @@ +/* + * Copyright (C) 2012 Henrik Nordstrom + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.h" +#include "portable_endian.h" +#include "fel_lib.h" +#include "fel-spiflash.h" +#include "fit_image.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool verbose = false; /* If set, makes the 'fel' tool more talkative */ +static uint32_t uboot_entry = 0; /* entry point (address) of U-Boot */ +static uint32_t uboot_size = 0; /* size of U-Boot binary */ +static bool enter_in_aarch64 = false; + +/* printf-style output, but only if "verbose" flag is active */ +#define pr_info(...) \ + do { if (verbose) printf(__VA_ARGS__); } while (0); + +/* Constants taken from ${U-BOOT}/include/image.h */ +#define IH_MAGIC 0x27051956 /* Image Magic Number */ +#define IH_ARCH_ARM 2 /* ARM */ +#define IH_TYPE_INVALID 0 /* Invalid Image */ +#define IH_TYPE_FIRMWARE 5 /* Firmware Image */ +#define IH_TYPE_SCRIPT 6 /* Script file */ +#define IH_TYPE_FLATDT 8 /* DTB or FIT image */ +#define IH_NMLEN 32 /* Image Name Length */ + +/* Additional error codes, newly introduced for get_image_type() */ +#define IH_TYPE_ARCH_MISMATCH -1 + +/* + * Legacy format image U-Boot header, + * all data in network byte order (aka natural aka bigendian). + * Taken from ${U-BOOT}/include/image.h + */ +typedef struct image_header { + uint32_t ih_magic; /* Image Header Magic Number */ + uint32_t ih_hcrc; /* Image Header CRC Checksum */ + uint32_t ih_time; /* Image Creation Timestamp */ + uint32_t ih_size; /* Image Data Size */ + uint32_t ih_load; /* Data Load Address */ + uint32_t ih_ep; /* Entry Point Address */ + uint32_t ih_dcrc; /* Image Data CRC Checksum */ + uint8_t ih_os; /* Operating System */ + uint8_t ih_arch; /* CPU architecture */ + uint8_t ih_type; /* Image Type */ + uint8_t ih_comp; /* Compression Type */ + uint8_t ih_name[IH_NMLEN]; /* Image Name */ +} image_header_t; + +#define HEADER_NAME_OFFSET offsetof(image_header_t, ih_name) +#define HEADER_SIZE sizeof(image_header_t) + +/* + * Utility function to determine the image type from a mkimage-compatible + * header at given buffer (address). + * + * For invalid headers (insufficient size or 'magic' mismatch) the function + * will return IH_TYPE_INVALID. Negative return values might indicate + * special error conditions, e.g. IH_TYPE_ARCH_MISMATCH signals that the + * image doesn't match the expected (ARM) architecture. + * Otherwise the function will return the "ih_type" field for valid headers. + */ +int get_image_type(const uint8_t *buf, size_t len) +{ + image_header_t *hdr = (image_header_t *)buf; + + if (len <= HEADER_SIZE) /* insufficient length/size */ + return IH_TYPE_INVALID; + + if (be32toh(hdr->ih_magic) == 0xd00dfeed) + return IH_TYPE_FLATDT; + if (be32toh(hdr->ih_magic) != IH_MAGIC) /* signature mismatch */ + return IH_TYPE_INVALID; + /* For sunxi, we always expect ARM architecture here */ + if (hdr->ih_arch != IH_ARCH_ARM) + return IH_TYPE_ARCH_MISMATCH; + + /* assume a valid header, and return ih_type */ + return hdr->ih_type; +} + +void aw_fel_print_version(feldev_handle *dev) +{ + struct aw_fel_version buf = dev->soc_version; + const char *soc_name = dev->soc_name; + + if (soc_name[0] == '0') /* hexadecimal ID -> unknown SoC */ + soc_name = "unknown"; + + printf("%.8s soc=%08x(%s) %08x ver=%04x %02x %02x scratchpad=%08x %08x %08x\n", + buf.signature, buf.soc_id, soc_name, buf.unknown_0a, + buf.protocol, buf.unknown_12, buf.unknown_13, + buf.scratchpad, buf.pad[0], buf.pad[1]); +} + +/* + * This wrapper for the FEL write functionality safeguards against overwriting + * an already loaded U-Boot binary. + * The return value represents elapsed time in seconds (needed for execution). + */ +double aw_write_buffer(feldev_handle *dev, void *buf, uint32_t offset, + size_t len, bool progress) +{ + /* safeguard against overwriting an already loaded U-Boot binary */ + if (uboot_size > 0 && offset <= uboot_entry + uboot_size + && offset + len >= uboot_entry) + pr_fatal("ERROR: Attempt to overwrite U-Boot! " + "Request 0x%08X-0x%08X overlaps 0x%08X-0x%08X.\n", + offset, (uint32_t)(offset + len), + uboot_entry, uboot_entry + uboot_size); + + double start = gettime(); + aw_fel_write_buffer(dev, buf, offset, len, progress); + return gettime() - start; +} + +void hexdump(void *data, uint32_t offset, size_t size) +{ + size_t j; + unsigned char *buf = data; + for (j = 0; j < size; j+=16) { + size_t i; + printf("%08zx: ", offset + j); + for (i = 0; i < 16; i++) { + if (j + i < size) + printf("%02x ", buf[j+i]); + else + printf("__ "); + } + putchar(' '); + for (i = 0; i < 16; i++) { + if (j + i >= size) + putchar('.'); + else + putchar(isprint(buf[j+i]) ? buf[j+i] : '.'); + } + putchar('\n'); + } +} + +unsigned int file_size(const char *filename) +{ + struct stat st; + if (stat(filename, &st) != 0) + pr_fatal("stat() error on file \"%s\": %s\n", filename, + strerror(errno)); + if (!S_ISREG(st.st_mode)) + pr_fatal("error: \"%s\" is not a regular file\n", filename); + + return st.st_size; +} + +int save_file(const char *name, void *data, size_t size) +{ + FILE *out = fopen(name, "wb"); + int rc; + if (!out) { + perror("Failed to open output file"); + exit(1); + } + rc = fwrite(data, size, 1, out); + fclose(out); + return rc; +} + +void *load_file(const char *name, size_t *size) +{ + size_t offset = 0, bufsize = 8192; + char *buf = malloc(bufsize); + FILE *in; + if (strcmp(name, "-") == 0) + in = stdin; + else + in = fopen(name, "rb"); + if (!in) { + perror("Failed to open input file"); + exit(1); + } + + while (true) { + size_t len = bufsize - offset; + size_t n = fread(buf+offset, 1, len, in); + offset += n; + if (n < len) + break; + bufsize *= 2; + buf = realloc(buf, bufsize); + if (!buf) { + perror("Failed to resize load_file() buffer"); + exit(1); + } + } + if (size) + *size = offset; + if (in != stdin) + fclose(in); + return buf; +} + +void aw_fel_hexdump(feldev_handle *dev, uint32_t offset, size_t size) +{ + if (size > 0) { + unsigned char buf[size]; + aw_fel_read(dev, offset, buf, size); + hexdump(buf, offset, size); + } +} + +void aw_fel_dump(feldev_handle *dev, uint32_t offset, size_t size) +{ + if (size > 0) { + unsigned char buf[size]; + aw_fel_read(dev, offset, buf, size); + fwrite(buf, size, 1, stdout); + } +} +void aw_fel_fill(feldev_handle *dev, uint32_t offset, size_t size, unsigned char value) +{ + if (size > 0) { + unsigned char buf[size]; + memset(buf, value, size); + aw_write_buffer(dev, buf, offset, size, false); + } +} + +/* + * Upload a function (implemented in native ARM code) to the device and + * prepare for executing it. Use a subset of 32-bit ARM AAPCS calling + * conventions: all arguments are integer 32-bit values, and an optional + * return value is a 32-bit integer too. The function code needs to be + * compiled in the ARM mode (Thumb2 is not supported), it also must be + * a position independent leaf function (have no calls to anything else) + * and have no references to any global variables. + * + * 'stack_size' - the required stack size for the function (can be + * calculated using the '-fstack-usage' GCC option) + * 'arm_code' - a pointer to the memory buffer with the function code + * 'arm_code_size' - the size of the function code + * 'num_args' - the number of 32-bit function arguments + * 'args' - an array with the function argument values + * + * Note: once uploaded, the function can be executed multiple times with + * exactly the same arguments. If some internal state needs to be + * updated between function calls, then it's best to pass a pointer + * to some state structure located elsewhere in SRAM as one of the + * function arguments. + */ + +bool aw_fel_remotefunc_prepare(feldev_handle *dev, + size_t stack_size, + void *arm_code, + size_t arm_code_size, + size_t num_args, + uint32_t *args) +{ + size_t idx, i; + size_t tmp_buf_size; + soc_info_t *soc_info = dev->soc_info; + uint32_t *tmp_buf; + uint32_t new_sp, num_args_on_stack = (num_args <= 4 ? 0 : num_args - 4); + uint32_t entry_code[] = { + htole32(0xe58fe040), /* 0: str lr, [pc, #64] */ + htole32(0xe58fd040), /* 4: str sp, [pc, #64] */ + htole32(0xe59fd040), /* 8: ldr sp, [pc, #64] */ + htole32(0xe28fc040), /* c: add ip, pc, #64 */ + htole32(0xe1a0200d), /* 10: mov r2, sp */ + htole32(0xe49c0004), /* 14: ldr r0, [ip], #4 */ + htole32(0xe3500000), /* 18: cmp r0, #0 */ + htole32(0x0a000003), /* 1c: beq 30 */ + htole32(0xe49c1004), /* 20: ldr r1, [ip], #4 */ + htole32(0xe4821004), /* 24: str r1, [r2], #4 */ + htole32(0xe2500001), /* 28: subs r0, r0, #1 */ + htole32(0x1afffffb), /* 2c: bne 20 */ + htole32(0xe8bc000f), /* 30: ldm ip!, {r0, r1, r2, r3} */ + htole32(0xe12fff3c), /* 34: blx ip */ + htole32(0xe59fe008), /* 38: ldr lr, [pc, #8] */ + htole32(0xe59fd008), /* 3c: ldr sp, [pc, #8] */ + htole32(0xe58f0000), /* 40: str r0, [pc] */ + htole32(0xe12fff1e), /* 44: bx lr */ + htole32(0x00000000), /* 48: .word 0x00000000 */ + htole32(0x00000000), /* 4c: .word 0x00000000 */ + }; + + if (!soc_info) + return false; + + /* Calculate the stack location */ + new_sp = soc_info->scratch_addr + + sizeof(entry_code) + + 2 * 4 + + num_args_on_stack * 4 + + 4 * 4 + + arm_code_size + + stack_size; + new_sp = (new_sp + 7) & ~7; + + tmp_buf_size = new_sp - soc_info->scratch_addr; + tmp_buf = calloc(tmp_buf_size, 1); + memcpy(tmp_buf, entry_code, sizeof(entry_code)); + idx = sizeof(entry_code) / 4; + tmp_buf[idx++] = htole32(new_sp); + tmp_buf[idx++] = htole32(num_args_on_stack); + for (i = num_args - num_args_on_stack; i < num_args; i++) + tmp_buf[idx++] = htole32(args[i]); + for (i = 0; i < 4; i++) + tmp_buf[idx++] = (i < num_args ? htole32(args[i]) : 0); + memcpy(tmp_buf + idx, arm_code, arm_code_size); + + aw_fel_write(dev, tmp_buf, soc_info->scratch_addr, tmp_buf_size); + free(tmp_buf); + return true; +} + +/* + * Execute the previously uploaded function. The 'result' pointer allows to + * retrieve the return value. + */ +bool aw_fel_remotefunc_execute(feldev_handle *dev, uint32_t *result) +{ + soc_info_t *soc_info = dev->soc_info; + if (!soc_info) + return false; + aw_fel_execute(dev, soc_info->scratch_addr); + if (result) { + aw_fel_read(dev, soc_info->scratch_addr + 0x48, result, sizeof(uint32_t)); + *result = le32toh(*result); + } + return true; +} + +static uint32_t fel_to_spl_thunk[] = { + #include "thunks/fel-to-spl-thunk.h" +}; + +#define DRAM_BASE 0x40000000 +#define DRAM_SIZE 0x80000000 + +uint32_t aw_read_arm_cp_reg(feldev_handle *dev, soc_info_t *soc_info, + uint32_t coproc, uint32_t opc1, uint32_t crn, + uint32_t crm, uint32_t opc2) +{ + uint32_t val = 0; + uint32_t opcode = 0xEE000000 | (1 << 20) | (1 << 4) + | ((opc1 & 0x7) << 21) | ((crn & 0xF) << 16) + | ((coproc & 0xF) << 8) | ((opc2 & 0x7) << 5) + | (crm & 0xF); + uint32_t arm_code[] = { + htole32(opcode), /* mrc coproc, opc1, r0, crn, crm, opc2 */ + htole32(0xe58f0000), /* str r0, [pc] */ + htole32(0xe12fff1e), /* bx lr */ + }; + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); + aw_fel_read(dev, soc_info->scratch_addr + 12, &val, sizeof(val)); + return le32toh(val); +} + +void aw_write_arm_cp_reg(feldev_handle *dev, soc_info_t *soc_info, + uint32_t coproc, uint32_t opc1, uint32_t crn, + uint32_t crm, uint32_t opc2, uint32_t val) +{ + uint32_t opcode = 0xEE000000 | (0 << 20) | (1 << 4) + | ((opc1 & 0x7) << 21) | ((crn & 0xF) << 16) + | ((coproc & 0xF) << 8) | ((opc2 & 7) << 5) + | (crm & 0xF); + uint32_t arm_code[] = { + htole32(0xe59f000c), /* ldr r0, [pc, #12] */ + htole32(opcode), /* mcr coproc, opc1, r0, crn, crm, opc2 */ + htole32(0xf57ff04f), /* dsb sy */ + htole32(0xf57ff06f), /* isb sy */ + htole32(0xe12fff1e), /* bx lr */ + htole32(val) + }; + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); +} + +/* "readl" of a single value */ +uint32_t fel_readl(feldev_handle *dev, uint32_t addr) +{ + uint32_t val; + fel_readl_n(dev, addr, &val, 1); + return val; +} + +/* "writel" of a single value */ +void fel_writel(feldev_handle *dev, uint32_t addr, uint32_t val) +{ + fel_writel_n(dev, addr, &val, 1); +} + +void aw_fel_print_sid(feldev_handle *dev, bool force_workaround) +{ + uint32_t key[4]; + soc_info_t *soc_info = dev->soc_info; + + if (!soc_info->sid_base) { + printf("SID registers for your SoC (%s) are unknown or inaccessible.\n", + dev->soc_name); + return; + } + + if (soc_info->sid_fix || force_workaround) { + pr_info("Read SID key via registers, base = 0x%08X\n", + soc_info->sid_base); + } else { + pr_info("SID key (e-fuses) at 0x%08X\n", + soc_info->sid_base + soc_info->sid_offset); + } + fel_read_sid(dev, key, 0, sizeof(key), force_workaround); + + /* output SID in "xxxxxxxx:xxxxxxxx:xxxxxxxx:xxxxxxxx" format */ + for (unsigned i = 0; i <= 3; i++) + printf("%08x%c", key[i], i < 3 ? ':' : '\n'); +} + +void aw_fel_dump_sid(feldev_handle *dev) +{ + uint32_t buffer[2048 / sizeof(uint32_t)]; /* total SID size is 2K */ + soc_info_t *soc_info = dev->soc_info; + + if (!soc_info->sid_base || !soc_info->sid_sections) { + printf("SID memory maps for your SoC (%s) are unknown.\n", + dev->soc_name); + return; + } + + for (const sid_section *s = soc_info->sid_sections; s->name; s++) { + uint32_t count = s->size_bits / 32; + + if (fel_read_sid(dev, buffer, s->offset, count * 4, false)) { + fprintf(stderr, "Read sid:%s failed\n", s->name); + return; + } + + printf("%-15s", s->name); + for (uint32_t i = 0; i < count; i++) { + if (i > 0 && ((i % 8) == 0)) + printf("\n%-15s", ""); + printf(" %08x", buffer[i]); + } + putchar('\n'); + } +} + +void aw_enable_l2_cache(feldev_handle *dev, soc_info_t *soc_info) +{ + uint32_t arm_code[] = { + htole32(0xee112f30), /* mrc 15, 0, r2, cr1, cr0, {1} */ + htole32(0xe3822002), /* orr r2, r2, #2 */ + htole32(0xee012f30), /* mcr 15, 0, r2, cr1, cr0, {1} */ + htole32(0xe12fff1e), /* bx lr */ + }; + + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); +} + +void aw_get_stackinfo(feldev_handle *dev, soc_info_t *soc_info, + uint32_t *sp_irq, uint32_t *sp) +{ + uint32_t results[2] = { 0 }; +#if 0 + /* Does not work on Cortex-A8 (needs Virtualization Extensions) */ + uint32_t arm_code[] = { + htole32(0xe1010300), /* mrs r0, SP_irq */ + htole32(0xe58f0004), /* str r0, [pc, #4] */ + htole32(0xe58fd004), /* str sp, [pc, #4] */ + htole32(0xe12fff1e), /* bx lr */ + }; + + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); + aw_fel_read(dev, soc_info->scratch_addr + 0x10, results, 8); +#else + /* Works everywhere */ + uint32_t arm_code[] = { + htole32(0xe10f0000), /* mrs r0, CPSR */ + htole32(0xe3c0101f), /* bic r1, r0, #31 */ + htole32(0xe3811012), /* orr r1, r1, #18 */ + htole32(0xe121f001), /* msr CPSR_c, r1 */ + htole32(0xe1a0100d), /* mov r1, sp */ + htole32(0xe121f000), /* msr CPSR_c, r0 */ + htole32(0xe58f1004), /* str r1, [pc, #4] */ + htole32(0xe58fd004), /* str sp, [pc, #4] */ + htole32(0xe12fff1e), /* bx lr */ + }; + + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); + aw_fel_read(dev, soc_info->scratch_addr + 0x24, results, 8); +#endif + *sp_irq = le32toh(results[0]); + *sp = le32toh(results[1]); +} + +uint32_t aw_get_ttbr0(feldev_handle *dev, soc_info_t *soc_info) +{ + return aw_read_arm_cp_reg(dev, soc_info, 15, 0, 2, 0, 0); +} + +uint32_t aw_get_ttbcr(feldev_handle *dev, soc_info_t *soc_info) +{ + return aw_read_arm_cp_reg(dev, soc_info, 15, 0, 2, 0, 2); +} + +uint32_t aw_get_dacr(feldev_handle *dev, soc_info_t *soc_info) +{ + return aw_read_arm_cp_reg(dev, soc_info, 15, 0, 3, 0, 0); +} + +uint32_t aw_get_sctlr(feldev_handle *dev, soc_info_t *soc_info) +{ + return aw_read_arm_cp_reg(dev, soc_info, 15, 0, 1, 0, 0); +} + +void aw_set_ttbr0(feldev_handle *dev, soc_info_t *soc_info, + uint32_t ttbr0) +{ + return aw_write_arm_cp_reg(dev, soc_info, 15, 0, 2, 0, 0, ttbr0); +} + +void aw_set_ttbcr(feldev_handle *dev, soc_info_t *soc_info, + uint32_t ttbcr) +{ + return aw_write_arm_cp_reg(dev, soc_info, 15, 0, 2, 0, 2, ttbcr); +} + +void aw_set_dacr(feldev_handle *dev, soc_info_t *soc_info, + uint32_t dacr) +{ + aw_write_arm_cp_reg(dev, soc_info, 15, 0, 3, 0, 0, dacr); +} + +void aw_set_sctlr(feldev_handle *dev, soc_info_t *soc_info, + uint32_t sctlr) +{ + aw_write_arm_cp_reg(dev, soc_info, 15, 0, 1, 0, 0, sctlr); +} + +/* + * Issue a "smc #0" instruction. This brings a SoC booted in "secure boot" + * state from the default non-secure FEL into secure FEL. + * This crashes on devices using "non-secure boot", as the BROM does not + * provide a handler address in MVBAR. So we have a runtime check. + */ +void aw_apply_smc_workaround(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + uint32_t val; + uint32_t arm_code[] = { + htole32(0xe1600070), /* smc #0 */ + htole32(0xe12fff1e), /* bx lr */ + }; + + /* Return if the SoC does not need this workaround */ + if (!soc_info->needs_smc_workaround_if_zero_word_at_addr) + return; + + /* This has less overhead than fel_readl_n() and may be good enough */ + aw_fel_read(dev, soc_info->needs_smc_workaround_if_zero_word_at_addr, + &val, sizeof(val)); + + /* Return if the workaround is not needed or has been already applied */ + if (val != 0) + return; + + pr_info("Applying SMC workaround... "); + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); + pr_info(" done.\n"); +} + +/* + * Reconstruct the same MMU translation table as used by the A20 BROM. + * We are basically reverting the changes, introduced in newer SoC + * variants. This works fine for the SoC variants with the memory + * layout similar to A20 (the SRAM is in the first megabyte of the + * address space and the BROM is in the last megabyte of the address + * space). + */ +uint32_t *aw_generate_mmu_translation_table(void) +{ + uint32_t *tt = malloc(4096 * sizeof(uint32_t)); + uint32_t i; + + /* + * Direct mapping using 1MB sections with TEXCB=00000 (Strongly + * ordered) for all memory except the first and the last sections, + * which have TEXCB=00100 (Normal). Domain bits are set to 1111 + * and AP bits are set to 11, but this is mostly irrelevant. + */ + for (i = 0; i < 4096; i++) + tt[i] = 0x00000DE2 | (i << 20); + tt[0x000] |= 0x1000; + tt[0xFFF] |= 0x1000; + + return tt; +} + +uint32_t *aw_backup_and_disable_mmu(feldev_handle *dev, + soc_info_t *soc_info) +{ + uint32_t *tt = NULL; + uint32_t sctlr, ttbr0, ttbcr, dacr; + uint32_t i; + + uint32_t arm_code[] = { + /* Disable I-cache, MMU and branch prediction */ + htole32(0xee110f10), /* mrc 15, 0, r0, cr1, cr0, {0} */ + htole32(0xe3c00001), /* bic r0, r0, #1 */ + htole32(0xe3c00b06), /* bic r0, r0, #0x1800 */ + htole32(0xee010f10), /* mcr 15, 0, r0, cr1, cr0, {0} */ + /* Return back to FEL */ + htole32(0xe12fff1e), /* bx lr */ + }; + + /* + * Below are some checks for the register values, which are known + * to be initialized in this particular way by the existing BROM + * implementations. We don't strictly need them to exactly match, + * but still have these safety guards in place in order to detect + * and review any potential configuration changes in future SoC + * variants (if one of these checks fails, then it is not a serious + * problem but more likely just an indication that one of these + * checks needs to be relaxed). + */ + + /* + * Basically, ignore M/Z/I/V/UNK bits and expect no TEX remap. + * Bits [23:22] are Read-As-One on ARMv7, but Should-Be-Zero + * on ARMv5, so ignore them. + * We need the RES1 bits[18,16,4,3] and CP15BEN[5]. + */ + sctlr = aw_get_sctlr(dev, soc_info); + if ((sctlr & ~((0x3 << 22) | (0x7 << 11) | (1 << 6) | 1)) != 0x00050038) + pr_fatal("Unexpected SCTLR (%08X)\n", sctlr); + + if (!(sctlr & 1)) { + pr_info("MMU is not enabled by BROM\n"); + return NULL; + } + + dacr = aw_get_dacr(dev, soc_info); + if (dacr != 0x55555555) + pr_fatal("Unexpected DACR (%08X)\n", dacr); + + ttbcr = aw_get_ttbcr(dev, soc_info); + if (ttbcr != 0x00000000) + pr_fatal("Unexpected TTBCR (%08X)\n", ttbcr); + + ttbr0 = aw_get_ttbr0(dev, soc_info); + if (ttbr0 & 0x3FFF) + pr_fatal("Unexpected TTBR0 (%08X)\n", ttbr0); + + tt = malloc(16 * 1024); + pr_info("Reading the MMU translation table from 0x%08X\n", ttbr0); + aw_fel_read(dev, ttbr0, tt, 16 * 1024); + for (i = 0; i < 4096; i++) + tt[i] = le32toh(tt[i]); + + /* Basic sanity checks to be sure that this is a valid table */ + for (i = 0; i < 4096; i++) { + if (((tt[i] >> 1) & 1) != 1 || ((tt[i] >> 18) & 1) != 0) + pr_fatal("MMU: not a section descriptor\n"); + if ((tt[i] >> 20) != i) + pr_fatal("MMU: not a direct mapping\n"); + } + + pr_info("Disabling I-cache, MMU and branch prediction..."); + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); + pr_info(" done.\n"); + + return tt; +} + +void aw_restore_and_enable_mmu(feldev_handle *dev, + soc_info_t *soc_info, + uint32_t *tt) +{ + uint32_t i; + uint32_t ttbr0 = aw_get_ttbr0(dev, soc_info); + + uint32_t arm_code[] = { + /* Invalidate I-cache, TLB and BTB */ + htole32(0xe3a00000), /* mov r0, #0 */ + htole32(0xee080f17), /* mcr 15, 0, r0, cr8, cr7, {0} */ + htole32(0xee070f15), /* mcr 15, 0, r0, cr7, cr5, {0} */ + htole32(0xee070fd5), /* mcr 15, 0, r0, cr7, cr5, {6} */ + htole32(0xf57ff04f), /* dsb sy */ + htole32(0xf57ff06f), /* isb sy */ + /* Enable I-cache, MMU and branch prediction */ + htole32(0xee110f10), /* mrc 15, 0, r0, cr1, cr0, {0} */ + htole32(0xe3800001), /* orr r0, r0, #1 */ + htole32(0xe3800b06), /* orr r0, r0, #0x1800 */ + htole32(0xee010f10), /* mcr 15, 0, r0, cr1, cr0, {0} */ + /* Return back to FEL */ + htole32(0xe12fff1e), /* bx lr */ + }; + + pr_info("Setting write-combine mapping for DRAM.\n"); + for (i = (DRAM_BASE >> 20); i < ((DRAM_BASE + DRAM_SIZE) >> 20); i++) { + /* Clear TEXCB bits */ + tt[i] &= ~((7 << 12) | (1 << 3) | (1 << 2)); + /* Set TEXCB to 00100 (Normal uncached mapping) */ + tt[i] |= (1 << 12); + } + + pr_info("Setting cached mapping for BROM.\n"); + /* Clear TEXCB bits first */ + tt[0xFFF] &= ~((7 << 12) | (1 << 3) | (1 << 2)); + /* Set TEXCB to 00111 (Normal write-back cached mapping) */ + tt[0xFFF] |= (1 << 12) | /* TEX */ + (1 << 3) | /* C */ + (1 << 2); /* B */ + + pr_info("Writing back the MMU translation table.\n"); + for (i = 0; i < 4096; i++) + tt[i] = htole32(tt[i]); + aw_fel_write(dev, tt, ttbr0, 16 * 1024); + + pr_info("Enabling I-cache, MMU and branch prediction..."); + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); + pr_info(" done.\n"); + + free(tt); +} + +/* Minimum offset of the main U-Boot image within u-boot-sunxi-with-spl.bin. */ +#define SPL_MIN_OFFSET 0x8000 + +uint32_t aw_fel_write_and_execute_spl(feldev_handle *dev, uint8_t *buf, size_t len) +{ + soc_info_t *soc_info = dev->soc_info; + sram_swap_buffers *swap_buffers; + char header_signature[9] = { 0 }; + size_t i, thunk_size; + uint32_t *thunk_buf; + uint32_t sp, sp_irq; + uint32_t spl_checksum, spl_len, spl_len_limit; + uint32_t *buf32 = (uint32_t *)buf; + uint32_t cur_addr = soc_info->spl_addr; + uint32_t *tt = NULL; + + if (!soc_info || !soc_info->swap_buffers) + pr_fatal("SPL: Unsupported SoC type\n"); + if (len < 32 || memcmp(buf + 4, "eGON.BT0", 8) != 0) + pr_fatal("SPL: eGON header is not found\n"); + + spl_checksum = 2 * le32toh(buf32[3]) - 0x5F0A6C39; + spl_len = le32toh(buf32[4]); + + if (spl_len > len || (spl_len % 4) != 0) + pr_fatal("SPL: bad length in the eGON header\n"); + + len = spl_len; + for (i = 0; i < len / 4; i++) + spl_checksum -= le32toh(buf32[i]); + + if (spl_checksum != 0) + pr_fatal("SPL: checksum check failed\n"); + + if (soc_info->needs_l2en) { + pr_info("Enabling the L2 cache\n"); + aw_enable_l2_cache(dev, soc_info); + } + + aw_get_stackinfo(dev, soc_info, &sp_irq, &sp); + pr_info("Stack pointers: sp_irq=0x%08X, sp=0x%08X\n", sp_irq, sp); + + tt = aw_backup_and_disable_mmu(dev, soc_info); + if (!tt && soc_info->mmu_tt_addr) { + if (soc_info->mmu_tt_addr & 0x3FFF) + pr_fatal("SPL: 'mmu_tt_addr' must be 16K aligned\n"); + pr_info("Generating the new MMU translation table at 0x%08X\n", + soc_info->mmu_tt_addr); + /* + * These settings are used by the BROM in A10/A13/A20 and + * we replicate them here when enabling the MMU. The DACR + * value 0x55555555 means that accesses are checked against + * the permission bits in the translation tables for all + * domains. The TTBCR value 0x00000000 means that the short + * descriptor translation table format is used, TTBR0 is used + * for all the possible virtual addresses (N=0) and that the + * translation table must be aligned at a 16K boundary. + */ + aw_set_dacr(dev, soc_info, 0x55555555); + aw_set_ttbcr(dev, soc_info, 0x00000000); + aw_set_ttbr0(dev, soc_info, soc_info->mmu_tt_addr); + tt = aw_generate_mmu_translation_table(); + } + + spl_len_limit = soc_info->sram_size; + + swap_buffers = soc_info->swap_buffers; + for (i = 0; swap_buffers[i].size; i++) { + if ((swap_buffers[i].buf2 >= soc_info->spl_addr) && + (swap_buffers[i].buf2 < soc_info->spl_addr + spl_len_limit)) + spl_len_limit = swap_buffers[i].buf2 - soc_info->spl_addr; + if (len > 0 && cur_addr < swap_buffers[i].buf1) { + uint32_t tmp = swap_buffers[i].buf1 - cur_addr; + if (tmp > len) + tmp = len; + aw_fel_write(dev, buf, cur_addr, tmp); + cur_addr += tmp; + buf += tmp; + len -= tmp; + } + if (len > 0 && cur_addr == swap_buffers[i].buf1) { + uint32_t tmp = swap_buffers[i].size; + if (tmp > len) + tmp = len; + aw_fel_write(dev, buf, swap_buffers[i].buf2, tmp); + cur_addr += tmp; + buf += tmp; + len -= tmp; + } + } + + /* Clarify the SPL size limitations, and bail out if they are not met */ + if (soc_info->thunk_addr - soc_info->spl_addr < spl_len_limit) + spl_len_limit = soc_info->thunk_addr - soc_info->spl_addr; + + if (spl_len > spl_len_limit) + pr_fatal("SPL: too large (need %u, have %u)\n", + spl_len, spl_len_limit); + + /* Write the remaining part of the SPL */ + if (len > 0) + aw_fel_write(dev, buf, cur_addr, len); + + thunk_size = sizeof(fel_to_spl_thunk) + sizeof(soc_info->spl_addr) + + (i + 1) * sizeof(*swap_buffers); + + if (thunk_size > soc_info->thunk_size) + pr_fatal("SPL: bad thunk size (need %d, have %d)\n", + (int)sizeof(fel_to_spl_thunk), soc_info->thunk_size); + + thunk_buf = malloc(thunk_size); + memcpy(thunk_buf, fel_to_spl_thunk, sizeof(fel_to_spl_thunk)); + memcpy(thunk_buf + sizeof(fel_to_spl_thunk) / sizeof(uint32_t), + &soc_info->spl_addr, sizeof(soc_info->spl_addr)); + memcpy(thunk_buf + sizeof(fel_to_spl_thunk) / sizeof(uint32_t) + 1, + swap_buffers, (i + 1) * sizeof(*swap_buffers)); + + for (i = 0; i < thunk_size / sizeof(uint32_t); i++) + thunk_buf[i] = htole32(thunk_buf[i]); + + pr_info("=> Executing the SPL..."); + aw_fel_write(dev, thunk_buf, soc_info->thunk_addr, thunk_size); + aw_fel_execute(dev, soc_info->thunk_addr); + pr_info(" done.\n"); + + free(thunk_buf); + + /* TODO: Try to find and fix the bug, which needs this workaround */ + struct timespec req = { .tv_nsec = 250000000 }; /* 250ms */ + nanosleep(&req, NULL); + + /* Read back the result and check if everything was fine */ + aw_fel_read(dev, soc_info->spl_addr + 4, header_signature, 8); + if (strcmp(header_signature, "eGON.FEL") != 0) + pr_fatal("SPL: failure code '%s'\n", header_signature); + + /* re-enable the MMU if it was enabled by BROM */ + if (tt != NULL) + aw_restore_and_enable_mmu(dev, soc_info, tt); + + return spl_len; +} + +/* + * This function tests a given buffer address and length for a valid U-Boot + * image. Upon success, the image data gets transferred to the default memory + * address stored within the image header; and the function preserves the + * U-Boot entry point (offset) and size values. + */ +static void aw_fel_write_uboot_image(feldev_handle *dev, uint8_t *buf, + size_t len, const char *dt_name) +{ + if (len <= HEADER_SIZE) + return; /* Insufficient size (no actual data), just bail out */ + + image_header_t hdr = *(image_header_t *)buf; + + /* Check for a valid mkimage header */ + int image_type = get_image_type(buf, len); + if (image_type <= IH_TYPE_INVALID) { + switch (image_type) { + case IH_TYPE_INVALID: + pr_error("Invalid U-Boot image: bad size or signature\n"); + break; + case IH_TYPE_ARCH_MISMATCH: + pr_error("Invalid U-Boot image: wrong architecture\n"); + break; + default: + pr_error("Invalid U-Boot image: error code %d\n", + image_type); + } + exit(1); + } + if (image_type == IH_TYPE_FLATDT) { /* FIT image */ + uboot_entry = load_fit_images(dev, buf, dt_name, + &enter_in_aarch64); + uboot_size = 4; /* dummy value to pass check below */ + return; + } + + if (image_type != IH_TYPE_FIRMWARE) + pr_fatal("U-Boot image type mismatch: " + "expected IH_TYPE_FIRMWARE, got %02X\n", image_type); + + /* The CRC is calculated on the whole header but the CRC itself */ + uint32_t hcrc = be32toh(hdr.ih_hcrc); + hdr.ih_hcrc = 0; + uint32_t computed_hcrc = crc32(0, (const uint8_t *) &hdr, HEADER_SIZE); + if (hcrc != computed_hcrc) + pr_fatal("U-Boot header CRC mismatch: expected %x, got %x\n", + hcrc, computed_hcrc); + + uint32_t data_size = be32toh(hdr.ih_size); /* Image Data Size */ + uint32_t load_addr = be32toh(hdr.ih_load); /* Data Load Address */ + if (data_size > len - HEADER_SIZE) + pr_fatal("U-Boot image data trucated: " + "expected %zu bytes, got %u\n", + len - HEADER_SIZE, data_size); + + uint32_t dcrc = be32toh(hdr.ih_dcrc); + uint32_t computed_dcrc = crc32(0, buf + HEADER_SIZE, data_size); + if (dcrc != computed_dcrc) + pr_fatal("U-Boot data CRC mismatch: expected %x, got %x\n", + dcrc, computed_dcrc); + + /* If we get here, we're "good to go" (i.e. actually write the data) */ + pr_info("Writing image \"%.*s\", %u bytes @ 0x%08X.\n", + IH_NMLEN, buf + HEADER_NAME_OFFSET, data_size, load_addr); + + aw_write_buffer(dev, buf + HEADER_SIZE, load_addr, data_size, false); + + /* keep track of U-Boot memory region in global vars */ + uboot_entry = load_addr; + uboot_size = data_size; +} + +static const char *spl_get_dtb_name(uint8_t *spl_buf) +{ + uint32_t dt_offset; + + if (memcmp(spl_buf + 4, "eGON.BT0", 8)) + return NULL; + + if (memcmp(spl_buf + 0x14, "SPL", 3)) + return NULL; + + if (spl_buf[0x17] < 0x2) /* only since v0.2 */ + return NULL; + + memcpy(&dt_offset, spl_buf + 0x20, 4); + dt_offset = le32toh(dt_offset); + + if (verbose) + printf("found DT name in SPL header: %s\n", spl_buf + dt_offset); + + return (char *)spl_buf + dt_offset; +} + +/* + * This function handles the common part of both "spl" and "uboot" commands. + */ +void aw_fel_process_spl_and_uboot(feldev_handle *dev, const char *filename) +{ + size_t size; + uint32_t offset; + /* load file into memory buffer */ + uint8_t *buf = load_file(filename, &size); + const char *dt_name = spl_get_dtb_name(buf); + + /* write and execute the SPL from the buffer */ + offset = aw_fel_write_and_execute_spl(dev, buf, size); + + /* check for optional main U-Boot binary (and transfer it, if applicable) */ + if (size > offset) { + /* U-Boot pads to at least 32KB */ + if (offset < SPL_MIN_OFFSET) + offset = SPL_MIN_OFFSET; + aw_fel_write_uboot_image(dev, buf + offset, size - offset, + dt_name); + } + free(buf); +} + +/* + * Test the SPL header for our "sunxi" variant. We want to make sure that + * we can safely use specific header fields to pass information to U-Boot. + * In case of a missing signature (e.g. Allwinner boot0) or header version + * mismatch, this function will return "false". If all seems fine, + * the result is "true". + */ +#define SPL_SIGNATURE "SPL" /* marks "sunxi" header */ +#define SPL_MAJOR_BITS 3 +#define SPL_MINOR_BITS 5 +#define SPL_VERSION(maj, min) \ + ((((maj) & ((1U << SPL_MAJOR_BITS) - 1)) << SPL_MINOR_BITS) | \ + ((min) & ((1U << SPL_MINOR_BITS) - 1))) +#define SPL_MIN_VERSION SPL_VERSION(0, 1) +#define SPL_MAX_VERSION SPL_VERSION(0, 31) +bool have_sunxi_spl(feldev_handle *dev, uint32_t spl_addr) +{ + uint8_t spl_signature[4]; + + aw_fel_read(dev, spl_addr + 0x14, + &spl_signature, sizeof(spl_signature)); + + if (memcmp(spl_signature, SPL_SIGNATURE, 3) != 0) + return false; /* signature mismatch, no "sunxi" SPL */ + + if (spl_signature[3] < SPL_MIN_VERSION) { + pr_error("sunxi SPL version mismatch: " + "found 0x%02X < required minimum 0x%02X\n", + spl_signature[3], SPL_MIN_VERSION); + pr_error("You need to update your U-Boot (mksunxiboot) to a more recent version.\n"); + return false; + } + if (spl_signature[3] > SPL_MAX_VERSION) { + pr_error("sunxi SPL version mismatch: " + "found 0x%02X > maximum supported 0x%02X\n", + spl_signature[3], SPL_MAX_VERSION); + pr_error("You need a more recent version of this (sunxi-tools) fel utility.\n"); + return false; + } + return true; /* sunxi SPL and suitable version */ +} + +/* + * Pass information to U-Boot via specialized fields in the SPL header + * (see "boot_file_head" in ${U-BOOT}/arch/arm/include/asm/arch-sunxi/spl.h), + * providing the boot script address (DRAM location of boot.scr). + */ +void pass_fel_information(feldev_handle *dev, + uint32_t script_address, uint32_t uEnv_length) +{ + soc_info_t *soc_info = dev->soc_info; + + /* write something _only_ if we have a suitable SPL header */ + if (have_sunxi_spl(dev, soc_info->spl_addr)) { + pr_info("Passing boot info via sunxi SPL: " + "script address = 0x%08X, uEnv length = %u\n", + script_address, uEnv_length); + uint32_t transfer[] = { + htole32(script_address), + htole32(uEnv_length) + }; + aw_fel_write(dev, transfer, + soc_info->spl_addr + 0x18, sizeof(transfer)); + } +} + +/* + * This function stores a given entry point to the RVBAR address for CPU0, + * and then writes the Reset Management Register to request a warm boot. + * It is useful with some AArch64 transitions, e.g. when passing control to + * ARM Trusted Firmware (ATF) during the boot process of Pine64. + * + * The code was inspired by + * https://github.com/apritzel/u-boot/commit/fda6bd1bf285c44f30ea15c7e6231bf53c31d4a8 + */ +void aw_rmr_request(feldev_handle *dev, uint32_t entry_point, bool aarch64) +{ + soc_info_t *soc_info = dev->soc_info; + if (!soc_info->rvbar_reg) { + pr_error("ERROR: Can't issue RMR request!\n" + "RVBAR is not supported or unknown for your SoC (%s).\n", + dev->soc_name); + return; + } + + uint32_t rmr_mode = (1 << 1) | (aarch64 ? 1 : 0); /* RR, AA64 flag */ + uint32_t arm_code[] = { + htole32(0xe59f0028), /* ldr r0, [rvbar_reg] */ + htole32(0xe59f1028), /* ldr r1, [entry_point] */ + htole32(0xe5801000), /* str r1, [r0] */ + htole32(0xf57ff04f), /* dsb sy */ + htole32(0xf57ff06f), /* isb sy */ + + htole32(0xe59f101c), /* ldr r1, [rmr_mode] */ + htole32(0xee1c0f50), /* mrc 15, 0, r0, cr12, cr0, {2}*/ + htole32(0xe1800001), /* orr r0, r0, r1 */ + htole32(0xee0c0f50), /* mcr 15, 0, r0, cr12, cr0, {2}*/ + htole32(0xf57ff06f), /* isb sy */ + + htole32(0xe320f003), /* loop: wfi */ + htole32(0xeafffffd), /* b */ + + htole32(soc_info->rvbar_reg), + htole32(entry_point), + htole32(rmr_mode) + }; + /* scratch buffer setup: transfers ARM code and parameter values */ + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + /* execute the thunk code (triggering a warm reset on the SoC) */ + pr_info("Store entry point 0x%08X to RVBAR 0x%08X, " + "and request warm reset with RMR mode %u...", + entry_point, soc_info->rvbar_reg, rmr_mode); + aw_fel_execute(dev, soc_info->scratch_addr); + pr_info(" done.\n"); +} + +/* Use the watchdog to simply reboot. Useful to get out of fel without + * power cycling or plugging. + */ +void aw_wd_reset(feldev_handle *dev) +{ + const watchdog_info *wd = dev->soc_info->watchdog; + if (!wd) { + pr_error("No watchdog information available (yet) for soc: %s\n", dev->soc_info->name); + return; + } + fel_writel(dev, wd->reg_mode, wd->reg_mode_value); + pr_info("Requested watchdog reset\n"); +} + +/* check buffer for magic "#=uEnv", indicating uEnv.txt compatible format */ +static bool is_uEnv(void *buffer, size_t size) +{ + if (size <= 6) + return false; /* insufficient size */ + return memcmp(buffer, "#=uEnv", 6) == 0; +} + +/* private helper function, gets used for "write*" and "multi*" transfers */ +static unsigned int file_upload(feldev_handle *dev, size_t count, + size_t argc, char **argv, progress_cb_t callback) +{ + if (argc < count * 2) + pr_fatal("error: too few arguments for uploading %zu files\n", + count); + + /* get all file sizes, keeping track of total bytes */ + size_t size = 0; + unsigned int i; + for (i = 0; i < count; i++) + size += file_size(argv[i * 2 + 1]); + + progress_start(callback, size); /* set total size and progress callback */ + + /* now transfer each file in turn */ + for (i = 0; i < count; i++) { + void *buf = load_file(argv[i * 2 + 1], &size); + if (size > 0) { + uint32_t offset = strtoul(argv[i * 2], NULL, 0); + aw_write_buffer(dev, buf, offset, size, callback != NULL); + + /* If we transferred a script, try to inform U-Boot about its address. */ + if (get_image_type(buf, size) == IH_TYPE_SCRIPT) + pass_fel_information(dev, offset, 0); + if (is_uEnv(buf, size)) /* uEnv-style data */ + pass_fel_information(dev, offset, size); + } + free(buf); + } + + return i; /* return number of files that were processed */ +} + +static void felusb_list_devices(void) +{ + size_t devices; /* FEL device count */ + feldev_list_entry *list, *entry; + + list = list_fel_devices(&devices); + for (entry = list; entry->soc_version.soc_id; entry++) { + printf("USB device %03d:%03d Allwinner %-8s", + entry->busnum, entry->devnum, entry->soc_name); + /* output SID only if non-zero */ + if (entry->SID[0] | entry->SID[1] | entry->SID[2] | entry->SID[3]) + printf("%08x:%08x:%08x:%08x", + entry->SID[0], entry->SID[1], entry->SID[2], entry->SID[3]); + putchar('\n'); + } + free(list); + + if (verbose && devices == 0) + pr_error("No Allwinner devices in FEL mode detected.\n"); + + feldev_done(NULL); + exit(devices > 0 ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static void select_by_sid(const char *sid_arg, int *busnum, int *devnum) +{ + char sid[36]; + feldev_list_entry *list, *entry; + + list = list_fel_devices(NULL); + for (entry = list; entry->soc_version.soc_id; entry++) { + snprintf(sid, sizeof(sid), "%08x:%08x:%08x:%08x", + entry->SID[0], entry->SID[1], entry->SID[2], entry->SID[3]); + if (strcmp(sid, sid_arg) == 0) { + *busnum = entry->busnum; + *devnum = entry->devnum; + break; + } + } + free(list); +} + +void usage(const char *cmd) { + puts("sunxi-fel " VERSION "\n"); + printf("Usage: %s [options] command arguments... [command...]\n" + " -h, --help Print this usage summary and exit\n" + " -v, --verbose Verbose logging\n" + " -p, --progress \"write\" transfers show a progress bar\n" + " -l, --list Enumerate all (USB) FEL devices and exit\n" + " -d, --dev bus:devnum Use specific USB bus and device number\n" + " --sid SID Select device by SID key (exact match)\n" + "\n" + " spl file Load and execute U-Boot SPL\n" + " If file additionally contains a main U-Boot binary\n" + " (u-boot-sunxi-with-spl.bin), this command also transfers that\n" + " to memory (default address from image), but won't execute it.\n" + "\n" + " uboot file-with-spl like \"spl\", but actually starts U-Boot\n" + " U-Boot execution will take place when the fel utility exits.\n" + " This allows combining \"uboot\" with further \"write\" commands\n" + " (to transfer other files needed for the boot).\n" + "\n" + " hex[dump] address length Dumps memory region in hex\n" + " dump address length Binary memory dump\n" + " exe[cute] address Call function address\n" + " reset64 address RMR request for AArch64 warm boot\n" + " wdreset Reboot via watchdog\n" + " memmove dest source size Copy bytes within device memory\n" + " readl address Read 32-bit value from device memory\n" + " writel address value Write 32-bit value to device memory\n" + " read address length file Write memory contents into file\n" + " write address file Store file contents into memory\n" + " write-with-progress addr file \"write\" with progress bar\n" + " write-with-gauge addr file Output progress for \"dialog --gauge\"\n" + " write-with-xgauge addr file Extended gauge output (updates prompt)\n" + " multi[write] # addr file ... \"write-with-progress\" multiple files,\n" + " sharing a common progress status\n" + " multi[write]-with-gauge ... like their \"write-with-*\" counterpart,\n" + " multi[write]-with-xgauge ... but following the 'multi' syntax:\n" + " <#> addr file [addr file [...]]\n" + " echo-gauge \"some text\" Update prompt/caption for gauge output\n" + " ver[sion] Show BROM version\n" + " sid Retrieve and output 128-bit SID key\n" + " sid-registers Retrieve and output 128-bit SID key,\n" + " using the MMIO register read method\n" + " sid-dump Dump the content of all the SID eFuses\n" + " clear address length Clear memory\n" + " fill address length value Fill memory\n" + , cmd); + printf("\n"); + aw_fel_spiflash_help(); + exit(0); +} + +int main(int argc, char **argv) +{ + bool uboot_autostart = false; /* flag for "uboot" command = U-Boot autostart */ + bool pflag_active = false; /* -p switch, causing "write" to output progress */ + bool device_list = false; /* -l switch, prints device list and exits */ + feldev_handle *handle; + int busnum = -1, devnum = -1; + char *sid_arg = NULL; + + if (argc <= 1) + usage(argv[0]); + + /* process all "prefix"-type arguments first */ + while (argc > 1) { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) + usage(argv[0]); + else if (strcmp(argv[1], "--verbose") == 0 || strcmp(argv[1], "-v") == 0) + verbose = true; + else if (strcmp(argv[1], "--progress") == 0 || strcmp(argv[1], "-p") == 0) + pflag_active = true; + else if (strcmp(argv[1], "--list") == 0 || strcmp(argv[1], "-l") == 0 + || strcmp(argv[1], "list") == 0) + device_list = true; + else if (strncmp(argv[1], "--dev", 5) == 0 || strncmp(argv[1], "-d", 2) == 0) { + char *dev_arg = argv[1]; + dev_arg += strspn(dev_arg, "-dev="); /* skip option chars, ignore '=' */ + if (*dev_arg == 0 && argc > 2) { /* at end of argument, use the next one instead */ + dev_arg = argv[2]; + argc -= 1; + argv += 1; + } + if (sscanf(dev_arg, "%d:%d", &busnum, &devnum) != 2 + || busnum <= 0 || devnum <= 0) + pr_fatal("ERROR: Expected 'bus:devnum', got '%s'.\n", dev_arg); + pr_info("Selecting USB Bus %03d Device %03d\n", busnum, devnum); + } + else if (strcmp(argv[1], "--sid") == 0 && argc > 2) { + sid_arg = argv[2]; + argc -= 1; + argv += 1; + } else + break; /* no valid (prefix) option detected, exit loop */ + argc -= 1; + argv += 1; + } + + /* + * If any option-style arguments remain (starting with '-') we know that + * we won't recognize them later (at best yielding "Invalid command"). + * However this would only happen _AFTER_ trying to open a FEL device, + * which might fail with "Allwinner USB FEL device not found". To avoid + * confusing the user, bail out here - with a more descriptive message. + */ + int i; + for (i = 1; i < argc; i++) + if (*argv[i] == '-') + pr_fatal("Invalid option %s\n", argv[i]); + + /* Process options that don't require a FEL device handle */ + if (device_list) + felusb_list_devices(); /* and exit program afterwards */ + if (sid_arg) { + /* try to set busnum and devnum according to "--sid" option */ + select_by_sid(sid_arg, &busnum, &devnum); + if (busnum <= 0 || devnum <= 0) + pr_fatal("No matching FEL device found for SID '%s'\n", + sid_arg); + pr_info("Selecting FEL device %03d:%03d by SID\n", busnum, devnum); + } + + /* + * Open FEL device - either specified by busnum:devnum, or + * the first one matching the given USB vendor/procduct ID. + */ + handle = feldev_open(busnum, devnum, AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID); + + /* Some SoCs need the SMC workaround to enter the secure boot mode */ + aw_apply_smc_workaround(handle); + + /* Handle command-style arguments, in order of appearance */ + while (argc > 1 ) { + int skip = 1; + + if (strncmp(argv[1], "hex", 3) == 0 && argc > 3) { + aw_fel_hexdump(handle, strtoul(argv[2], NULL, 0), strtoul(argv[3], NULL, 0)); + skip = 3; + } else if (strncmp(argv[1], "dump", 4) == 0 && argc > 3) { + aw_fel_dump(handle, strtoul(argv[2], NULL, 0), strtoul(argv[3], NULL, 0)); + skip = 3; + } else if (strcmp(argv[1], "memmove") == 0 && argc > 4) { + /* three parameters: destination addr, source addr, byte count */ + fel_memmove(handle, strtoul(argv[2], NULL, 0), + strtoul(argv[3], NULL, 0), strtoul(argv[4], NULL, 0)); + skip = 4; + } else if (strcmp(argv[1], "readl") == 0 && argc > 2) { + printf("0x%08x\n", fel_readl(handle, strtoul(argv[2], NULL, 0))); + skip = 2; + } else if (strcmp(argv[1], "writel") == 0 && argc > 3) { + fel_writel(handle, strtoul(argv[2], NULL, 0), strtoul(argv[3], NULL, 0)); + skip = 3; + } else if (strncmp(argv[1], "exe", 3) == 0 && argc > 2) { + aw_fel_execute(handle, strtoul(argv[2], NULL, 0)); + skip=3; + } else if (strcmp(argv[1], "reset64") == 0 && argc > 2) { + aw_rmr_request(handle, strtoul(argv[2], NULL, 0), true); + /* Cancel U-Boot autostart, and stop processing args */ + uboot_autostart = false; + break; + } else if (strcmp(argv[1], "wdreset") == 0) { + aw_wd_reset(handle); + } else if (strncmp(argv[1], "ver", 3) == 0) { + aw_fel_print_version(handle); + } else if (strcmp(argv[1], "sid") == 0) { + aw_fel_print_sid(handle, false); + } else if (strcmp(argv[1], "sid-registers") == 0) { + aw_fel_print_sid(handle, true); /* enforce register access */ + } else if (strcmp(argv[1], "sid-dump") == 0) { + aw_fel_dump_sid(handle); + } else if (strcmp(argv[1], "write") == 0 && argc > 3) { + skip += 2 * file_upload(handle, 1, argc - 2, argv + 2, + pflag_active ? progress_bar : NULL); + } else if (strcmp(argv[1], "write-with-progress") == 0 && argc > 3) { + skip += 2 * file_upload(handle, 1, argc - 2, argv + 2, + progress_bar); + } else if (strcmp(argv[1], "write-with-gauge") == 0 && argc > 3) { + skip += 2 * file_upload(handle, 1, argc - 2, argv + 2, + progress_gauge); + } else if (strcmp(argv[1], "write-with-xgauge") == 0 && argc > 3) { + skip += 2 * file_upload(handle, 1, argc - 2, argv + 2, + progress_gauge_xxx); + } else if ((strcmp(argv[1], "multiwrite") == 0 || + strcmp(argv[1], "multi") == 0) && argc > 4) { + size_t count = strtoul(argv[2], NULL, 0); /* file count */ + skip = 2 + 2 * file_upload(handle, count, argc - 3, + argv + 3, progress_bar); + } else if ((strcmp(argv[1], "multiwrite-with-gauge") == 0 || + strcmp(argv[1], "multi-with-gauge") == 0) && argc > 4) { + size_t count = strtoul(argv[2], NULL, 0); /* file count */ + skip = 2 + 2 * file_upload(handle, count, argc - 3, + argv + 3, progress_gauge); + } else if ((strcmp(argv[1], "multiwrite-with-xgauge") == 0 || + strcmp(argv[1], "multi-with-xgauge") == 0) && argc > 4) { + size_t count = strtoul(argv[2], NULL, 0); /* file count */ + skip = 2 + 2 * file_upload(handle, count, argc - 3, + argv + 3, progress_gauge_xxx); + } else if ((strcmp(argv[1], "echo-gauge") == 0) && argc > 2) { + skip = 2; + printf("XXX\n0\n%s\nXXX\n", argv[2]); + fflush(stdout); + } else if (strcmp(argv[1], "read") == 0 && argc > 4) { + size_t size = strtoul(argv[3], NULL, 0); + void *buf = malloc(size); + aw_fel_read(handle, strtoul(argv[2], NULL, 0), buf, size); + save_file(argv[4], buf, size); + free(buf); + skip=4; + } else if (strcmp(argv[1], "clear") == 0 && argc > 2) { + aw_fel_fill(handle, strtoul(argv[2], NULL, 0), strtoul(argv[3], NULL, 0), 0); + skip=3; + } else if (strcmp(argv[1], "fill") == 0 && argc > 3) { + aw_fel_fill(handle, strtoul(argv[2], NULL, 0), strtoul(argv[3], NULL, 0), (unsigned char)strtoul(argv[4], NULL, 0)); + skip=4; + } else if (strcmp(argv[1], "spl") == 0 && argc > 2) { + aw_fel_process_spl_and_uboot(handle, argv[2]); + skip=2; + } else if (strcmp(argv[1], "uboot") == 0 && argc > 2) { + aw_fel_process_spl_and_uboot(handle, argv[2]); + uboot_autostart = (uboot_entry > 0 && uboot_size > 0); + if (!uboot_autostart) + printf("Warning: \"uboot\" command failed to detect image! Can't execute U-Boot.\n"); + skip=2; + } else if (strcmp(argv[1], "spiflash-info") == 0) { + aw_fel_spiflash_info(handle); + } else if (strcmp(argv[1], "spiflash-read") == 0 && argc > 4) { + size_t size = strtoul(argv[3], NULL, 0); + void *buf = malloc(size); + aw_fel_spiflash_read(handle, strtoul(argv[2], NULL, 0), buf, size, + pflag_active ? progress_bar : NULL); + save_file(argv[4], buf, size); + free(buf); + skip=4; + } else if (strcmp(argv[1], "spiflash-write") == 0 && argc > 3) { + size_t size; + void *buf = load_file(argv[3], &size); + aw_fel_spiflash_write(handle, strtoul(argv[2], NULL, 0), buf, size, + pflag_active ? progress_bar : NULL); + free(buf); + skip=3; + } else { + pr_fatal("Invalid command %s\n", argv[1]); + } + argc-=skip; + argv+=skip; + } + + /* auto-start U-Boot if requested (by the "uboot" command) */ + if (uboot_autostart) { + pr_info("Starting U-Boot (0x%08X).\n", uboot_entry); + if (enter_in_aarch64) + aw_rmr_request(handle, uboot_entry, true); + else + aw_fel_execute(handle, uboot_entry); + } + + feldev_done(handle); + + return 0; +} diff --git a/fel_lib.c b/fel_lib.c new file mode 100644 index 0000000..dfb8e98 --- /dev/null +++ b/fel_lib.c @@ -0,0 +1,873 @@ +/* + * Copyright (C) 2012 Henrik Nordstrom + * Copyright (C) 2015 Siarhei Siamashka + * Copyright (C) 2016 Bernhard Nortmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/********************************************************************** + * USB library and helper functions for the FEL utility + **********************************************************************/ + +#include "portable_endian.h" +#include "fel_lib.h" +#include + +#include +#include +#include +#include +#include + +#define USB_TIMEOUT 10000 /* 10 seconds */ + +static bool fel_lib_initialized = false; + +/* This is out 'private' data type that will be part of a "FEL device" handle */ +struct _felusb_handle { + libusb_device_handle *handle; + int endpoint_out, endpoint_in; + bool iface_detached; + bool icache_hacked; +}; + +/* a helper function to report libusb errors */ +static void usb_error(int rc, const char *caption, int exitcode) +{ + if (caption) + fprintf(stderr, "%s ", caption); + +#if defined(LIBUSBX_API_VERSION) && (LIBUSBX_API_VERSION >= 0x01000102) + fprintf(stderr, "ERROR %d: %s\n", rc, libusb_strerror(rc)); +#else + /* assume that libusb_strerror() is missing in the libusb API */ + fprintf(stderr, "ERROR %d\n", rc); +#endif + + if (exitcode != 0) + exit(exitcode); +} + +/* + * AW_USB_MAX_BULK_SEND and the timeout constant USB_TIMEOUT are related. + * Both need to be selected in a way that transferring the maximum chunk size + * with (SoC-specific) slow transfer speed won't time out. + * + * The 512 KiB here are chosen based on the assumption that we want a 10 seconds + * timeout, and "slow" transfers take place at approx. 64 KiB/sec - so we can + * expect the maximum chunk being transmitted within 8 seconds or less. + */ +static const int AW_USB_MAX_BULK_SEND = 512 * 1024; /* 512 KiB per bulk request */ + +static void usb_bulk_send(libusb_device_handle *usb, int ep, const void *data, + size_t length, bool progress) +{ + /* + * With no progress notifications, we'll use the maximum chunk size. + * Otherwise, it's useful to lower the size (have more chunks) to get + * more frequent status updates. 128 KiB per request seem suitable. + * (Worst case of "slow" transfers -> one update every two seconds.) + */ + size_t max_chunk = progress ? 128 * 1024 : AW_USB_MAX_BULK_SEND; + + size_t chunk; + int rc, sent; + while (length > 0) { + chunk = length < max_chunk ? length : max_chunk; + rc = libusb_bulk_transfer(usb, ep, (void *)data, chunk, + &sent, USB_TIMEOUT); + if (rc != 0) + usb_error(rc, "usb_bulk_send()", 2); + length -= sent; + data += sent; + + if (progress) + progress_update(sent); /* notification after each chunk */ + } +} + +static void usb_bulk_recv(libusb_device_handle *usb, int ep, void *data, + int length) +{ + int rc, recv; + while (length > 0) { + rc = libusb_bulk_transfer(usb, ep, data, length, + &recv, USB_TIMEOUT); + if (rc != 0) + usb_error(rc, "usb_bulk_recv()", 2); + length -= recv; + data += recv; + } +} + +struct aw_usb_request { + char signature[8]; + uint32_t length; + uint32_t unknown1; /* 0x0c000000 */ + uint16_t request; + uint32_t length2; /* Same as length */ + char pad[10]; +} __attribute__((packed)); + +#define AW_USB_READ 0x11 +#define AW_USB_WRITE 0x12 + +struct aw_fel_request { + uint32_t request; + uint32_t address; + uint32_t length; + uint32_t pad; +}; + +/* FEL request types */ +#define AW_FEL_VERSION 0x001 +#define AW_FEL_1_WRITE 0x101 +#define AW_FEL_1_EXEC 0x102 +#define AW_FEL_1_READ 0x103 + +static void aw_send_usb_request(feldev_handle *dev, int type, int length) +{ + struct aw_usb_request req = { + .signature = "AWUC", + .request = htole16(type), + .length = htole32(length), + .unknown1 = htole32(0x0c000000) + }; + req.length2 = req.length; + usb_bulk_send(dev->usb->handle, dev->usb->endpoint_out, + &req, sizeof(req), false); +} + +static void aw_read_usb_response(feldev_handle *dev) +{ + char buf[13]; + usb_bulk_recv(dev->usb->handle, dev->usb->endpoint_in, + buf, sizeof(buf)); + assert(strcmp(buf, "AWUS") == 0); +} + +static void aw_usb_write(feldev_handle *dev, const void *data, size_t len, + bool progress) +{ + aw_send_usb_request(dev, AW_USB_WRITE, len); + usb_bulk_send(dev->usb->handle, dev->usb->endpoint_out, + data, len, progress); + aw_read_usb_response(dev); +} + +static void aw_usb_read(feldev_handle *dev, void *data, size_t len) +{ + aw_send_usb_request(dev, AW_USB_READ, len); + usb_bulk_recv(dev->usb->handle, dev->usb->endpoint_in, data, len); + aw_read_usb_response(dev); +} + +static void aw_send_fel_request(feldev_handle *dev, int type, + uint32_t addr, uint32_t length) +{ + struct aw_fel_request req = { + .request = htole32(type), + .address = htole32(addr), + .length = htole32(length) + }; + aw_usb_write(dev, &req, sizeof(req), false); +} + +static void aw_read_fel_status(feldev_handle *dev) +{ + char buf[8]; + aw_usb_read(dev, buf, sizeof(buf)); +} + +/* AW_FEL_VERSION request */ +static void aw_fel_get_version(feldev_handle *dev, struct aw_fel_version *buf) +{ + aw_send_fel_request(dev, AW_FEL_VERSION, 0, 0); + aw_usb_read(dev, buf, sizeof(*buf)); + aw_read_fel_status(dev); + + buf->soc_id = (le32toh(buf->soc_id) >> 8) & 0xFFFF; + buf->unknown_0a = le32toh(buf->unknown_0a); + buf->protocol = le32toh(buf->protocol); + buf->scratchpad = le16toh(buf->scratchpad); + buf->pad[0] = le32toh(buf->pad[0]); + buf->pad[1] = le32toh(buf->pad[1]); +} + +/* AW_FEL_1_READ request */ +void aw_fel_read(feldev_handle *dev, uint32_t offset, void *buf, size_t len) +{ + aw_send_fel_request(dev, AW_FEL_1_READ, offset, len); + aw_usb_read(dev, buf, len); + aw_read_fel_status(dev); +} + +/* AW_FEL_1_WRITE request */ +static void aw_fel_write_raw(feldev_handle *dev, const void *buf, uint32_t offset, size_t len) +{ + if (len == 0) + return; + + aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len); + aw_usb_write(dev, buf, len, false); + aw_read_fel_status(dev); +} + +/* AW_FEL_1_EXEC request */ +void aw_fel_execute(feldev_handle *dev, uint32_t offset) +{ + aw_send_fel_request(dev, AW_FEL_1_EXEC, offset, 0); + aw_read_fel_status(dev); +} + +static void aw_disable_icache(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + uint32_t arm_code[] = { + /* Clear SCTLR.I */ + htole32(0xee110f10), /* mrc 15, 0, r0, cr1, cr0, {0} ;SCTLR */ + htole32(0xe3c00a01), /* bic r0, r0, #0x1000 */ + htole32(0xee010f10), /* mcr 15, 0, r0, cr1, cr0, {0} ;SCTLR */ + /* Invalidate I-Cache */ + htole32(0xee070f15), /* mcr 15, 0, r0, cr7, cr5, {0} ;ICIALLU */ + /* Barrier to force instruction refetching */ + htole32(0xf57ff06f), /* isb sy */ + htole32(0xe12fff1e), /* bx lr */ + }; + aw_fel_write_raw(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); +} + +void aw_fel_write(feldev_handle *dev, const void *buf, uint32_t offset, size_t len) +{ + if (dev->soc_info->icache_fix && !dev->usb->icache_hacked) { + aw_disable_icache(dev); + dev->usb->icache_hacked = true; + } + aw_fel_write_raw(dev, buf, offset, len); +} + +/* + * This function is a higher-level wrapper for the FEL write functionality. + * Unlike aw_fel_write() above - which is reserved for internal use - this + * routine optionally allows progress callbacks. + */ +void aw_fel_write_buffer(feldev_handle *dev, const void *buf, uint32_t offset, + size_t len, bool progress) +{ + if (len == 0) + return; + + aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len); + aw_usb_write(dev, buf, len, progress); + aw_read_fel_status(dev); +} + +/* + * We don't want the scratch code/buffer to exceed a maximum size of 0x400 bytes + * (256 32-bit words) on readl_n/writel_n transfers. To guarantee this, we have + * to account for the amount of space the ARM code uses. + */ +#define LCODE_ARM_WORDS 12 /* word count of the [read/write]l_n scratch code */ +#define LCODE_ARM_SIZE (LCODE_ARM_WORDS << 2) /* code size in bytes */ +#define LCODE_MAX_TOTAL 0x100 /* max. words in buffer */ +#define LCODE_MAX_WORDS (LCODE_MAX_TOTAL - LCODE_ARM_WORDS) /* data words */ + +/* multiple "readl" from sequential addresses to a destination buffer */ +static void aw_fel_readl_n(feldev_handle *dev, uint32_t addr, + uint32_t *dst, size_t count) +{ + if (count == 0) return; + if (count > LCODE_MAX_WORDS) { + fprintf(stderr, + "ERROR: Max. word count exceeded, truncating aw_fel_readl_n() transfer\n"); + count = LCODE_MAX_WORDS; + } + + assert(LCODE_MAX_WORDS < 256); /* protect against corruption of ARM code */ + uint32_t arm_code[] = { + htole32(0xe59f0020), /* ldr r0, [pc, #32] ; ldr r0,[read_addr] */ + htole32(0xe28f1024), /* add r1, pc, #36 ; adr r1, read_data */ + htole32(0xe59f201c), /* ldr r2, [pc, #28] ; ldr r2,[read_count] */ + htole32(0xe3520000 + LCODE_MAX_WORDS), /* cmp r2, #LCODE_MAX_WORDS */ + htole32(0xc3a02000 + LCODE_MAX_WORDS), /* movgt r2, #LCODE_MAX_WORDS */ + /* read_loop: */ + htole32(0xe2522001), /* subs r2, r2, #1 ; r2 -= 1 */ + htole32(0x412fff1e), /* bxmi lr ; return if (r2 < 0) */ + htole32(0xe4903004), /* ldr r3, [r0], #4 ; load and post-inc */ + htole32(0xe4813004), /* str r3, [r1], #4 ; store and post-inc */ + htole32(0xeafffffa), /* b read_loop */ + htole32(addr), /* read_addr */ + htole32(count) /* read_count */ + /* read_data (buffer) follows, i.e. values go here */ + }; + assert(sizeof(arm_code) == LCODE_ARM_SIZE); + + /* scratch buffer setup: transfers ARM code, including addr and count */ + aw_fel_write(dev, arm_code, dev->soc_info->scratch_addr, sizeof(arm_code)); + /* execute code, read back the result */ + aw_fel_execute(dev, dev->soc_info->scratch_addr); + uint32_t buffer[count]; + aw_fel_read(dev, dev->soc_info->scratch_addr + LCODE_ARM_SIZE, + buffer, sizeof(buffer)); + /* extract values to destination buffer */ + uint32_t *val = buffer; + while (count-- > 0) + *dst++ = le32toh(*val++); +} + +/* + * aw_fel_readl_n() wrapper that can handle large transfers. If necessary, + * those will be done in separate 'chunks' of no more than LCODE_MAX_WORDS. + */ +void fel_readl_n(feldev_handle *dev, uint32_t addr, uint32_t *dst, size_t count) +{ + while (count > 0) { + size_t n = count > LCODE_MAX_WORDS ? LCODE_MAX_WORDS : count; + aw_fel_readl_n(dev, addr, dst, n); + addr += n * sizeof(uint32_t); + dst += n; + count -= n; + } +} + +/* multiple "writel" from a source buffer to sequential addresses */ +static void aw_fel_writel_n(feldev_handle *dev, uint32_t addr, + uint32_t *src, size_t count) +{ + if (count == 0) return; + if (count > LCODE_MAX_WORDS) { + fprintf(stderr, + "ERROR: Max. word count exceeded, truncating aw_fel_writel_n() transfer\n"); + count = LCODE_MAX_WORDS; + } + + assert(LCODE_MAX_WORDS < 256); /* protect against corruption of ARM code */ + /* + * We need a fixed array size to allow for (partial) initialization, + * so we'll claim the maximum total number of words (0x100) here. + */ + uint32_t arm_code[LCODE_MAX_TOTAL] = { + htole32(0xe59f0020), /* ldr r0, [pc, #32] ; ldr r0,[write_addr] */ + htole32(0xe28f1024), /* add r1, pc, #36 ; adr r1, write_data */ + htole32(0xe59f201c), /* ldr r2, [pc, #28] ; ldr r2,[write_count]*/ + htole32(0xe3520000 + LCODE_MAX_WORDS), /* cmp r2, #LCODE_MAX_WORDS */ + htole32(0xc3a02000 + LCODE_MAX_WORDS), /* movgt r2, #LCODE_MAX_WORDS */ + /* write_loop: */ + htole32(0xe2522001), /* subs r2, r2, #1 ; r2 -= 1 */ + htole32(0x412fff1e), /* bxmi lr ; return if (r2 < 0) */ + htole32(0xe4913004), /* ldr r3, [r1], #4 ; load and post-inc */ + htole32(0xe4803004), /* str r3, [r0], #4 ; store and post-inc */ + htole32(0xeafffffa), /* b write_loop */ + htole32(addr), /* write_addr */ + htole32(count) /* write_count */ + /* write_data (buffer) follows, i.e. values taken from here */ + }; + + /* copy values from source buffer */ + size_t i; + for (i = 0; i < count; i++) + arm_code[LCODE_ARM_WORDS + i] = htole32(*src++); + /* scratch buffer setup: transfers ARM code and data */ + aw_fel_write(dev, arm_code, dev->soc_info->scratch_addr, + (LCODE_ARM_WORDS + count) * sizeof(uint32_t)); + /* execute, and we're done */ + aw_fel_execute(dev, dev->soc_info->scratch_addr); +} + +/* + * aw_fel_writel_n() wrapper that can handle large transfers. If necessary, + * those will be done in separate 'chunks' of no more than LCODE_MAX_WORDS. + */ +void fel_writel_n(feldev_handle *dev, uint32_t addr, uint32_t *src, size_t count) +{ + while (count > 0) { + size_t n = count > LCODE_MAX_WORDS ? LCODE_MAX_WORDS : count; + aw_fel_writel_n(dev, addr, src, n); + addr += n * sizeof(uint32_t); + src += n; + count -= n; + } +} + +/* + * move (arbitrary byte count) data between addresses within SoC memory + * + * These functions try to copy as many bytes as possible using 32-bit word + * transfers, and handle any unaligned bytes ('head' and 'tail') separately. + * + * This is useful for the same reasons that "readl"/"writel" were introduced: + * Byte-oriented transfers ("string" copy) might not give the expected results + * when accessing hardware registers, like e.g. the (G)PIO config/state. + * + * We have two different low-level functions, where the copy operation moves + * upwards or downwards respectively. This allows a non-destructive "memmove" + * wrapper to select the suitable one in case of memory overlap. + */ + +static void fel_memcpy_up(feldev_handle *dev, + uint32_t dst_addr, uint32_t src_addr, size_t size) +{ + if (size == 0) return; + /* + * copy "upwards", increasing destination and source addresses + */ + uint32_t arm_code[] = { + htole32(0xe59f0054), /* ldr r0, [pc, #84] ; ldr r0, [dst_addr] */ + htole32(0xe59f1054), /* ldr r1, [pc, #84] ; ldr r1, [src_addr] */ + htole32(0xe59f2054), /* ldr r2, [pc, #84] ; ldr r2, [size] */ + htole32(0xe0413000), /* sub r3, r1, r0 ; r3 = r1 - r0 */ + htole32(0xe3130003), /* tst r3, #3 ; test lower bits */ + htole32(0x1a00000b), /* bne copyup_tail ; unaligned copying */ + /* copyup_head: */ + htole32(0xe3110003), /* tst r1, #3 ; word-aligned? */ + htole32(0x0a000004), /* beq copyup_loop */ + htole32(0xe4d13001), /* ldrb r3, [r1], #1 ; load and post-inc */ + htole32(0xe4c03001), /* strb r3, [r0], #1 ; store and post-inc */ + htole32(0xe2522001), /* subs r2, r2, #1 ; r2 -= 1 */ + htole32(0x5afffff9), /* bpl copyup_head ; while (r2 >= 0) */ + htole32(0xe12fff1e), /* bx lr ; early return */ + /* copyup_loop: */ + htole32(0xe2522004), /* subs r2, r2, #4 ; r2 -= 4 */ + htole32(0x54913004), /* ldrpl r3, [r1], #4 ; load and post-inc */ + htole32(0x54803004), /* strpl r3, [r0], #4 ; store and post-inc */ + htole32(0x5afffffb), /* bpl copyup_loop ; while (r2 >= 0) */ + htole32(0xe2822004), /* add r2, r2, #4 ; remaining bytes */ + /* copyup_tail: */ + htole32(0xe2522001), /* subs r2, r2, #1 ; r2 -= 1 */ + htole32(0x412fff1e), /* bxmi lr ; return if (r2 < 0) */ + htole32(0xe4d13001), /* ldrb r3, [r1], #1 ; load and post-inc */ + htole32(0xe4c03001), /* strb r3, [r0], #1 ; store and post-inc */ + htole32(0xeafffffa), /* b copyup_tail */ + + htole32(dst_addr), /* destination address */ + htole32(src_addr), /* source address */ + htole32(size), /* size (= byte count) */ + }; + aw_fel_write(dev, arm_code, dev->soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, dev->soc_info->scratch_addr); +} + +static void fel_memcpy_down(feldev_handle *dev, + uint32_t dst_addr, uint32_t src_addr, size_t size) +{ + if (size == 0) return; + /* + * This ARM code makes use of decreasing values in r2 + * for memory indexing relative to the base addresses in r0 and r1. + */ + uint32_t arm_code[] = { + htole32(0xe59f0058), /* ldr r0, [pc, #88] ; ldr r0, [dst_addr] */ + htole32(0xe59f1058), /* ldr r1, [pc, #88] ; ldr r1, [src_addr] */ + htole32(0xe59f2058), /* ldr r2, [pc, #88] ; ldr r2, [size] */ + htole32(0xe0403001), /* sub r3, r0, r1 ; r3 = r0 - r1 */ + htole32(0xe3130003), /* tst r3, #3 ; test lower bits */ + htole32(0x1a00000c), /* bne copydn_tail ; unaligned copying */ + /* copydn_head: */ + htole32(0xe0813002), /* add r3, r1, r2 ; r3 = r1 + r2 */ + htole32(0xe3130003), /* tst r3, #3 ; word-aligned? */ + htole32(0x0a000004), /* beq copydn_loop */ + htole32(0xe2522001), /* subs r2, r2, #1 ; r2 -= 1 */ + htole32(0x412fff1e), /* bxmi lr ; early return */ + htole32(0xe7d13002), /* ldrb r3, [r1, r2] ; load byte */ + htole32(0xe7c03002), /* strb r3, [r0, r2] ; store byte */ + htole32(0xeafffff7), /* b copydn_head */ + /* copydn_loop: */ + htole32(0xe2522004), /* subs r2, r2, #4 ; r2 -= 4 */ + htole32(0x57913002), /* ldrpl r3, [r1, r2] ; load word */ + htole32(0x57803002), /* strpl r3, [r0, r2] ; store word */ + htole32(0x5afffffb), /* bpl copydn_loop ; while (r2 >= 0) */ + htole32(0xe2822004), /* add r2, r2, #4 ; remaining bytes */ + /* copydn_tail: */ + htole32(0xe2522001), /* subs r2, r2, #1 ; r2 -= 1 */ + htole32(0x412fff1e), /* bxmi lr ; return if (r2 < 0) */ + htole32(0xe7d13002), /* ldrb r3, [r1, r2] ; load byte */ + htole32(0xe7c03002), /* strb r3, [r0, r2] ; store byte */ + htole32(0xeafffffa), /* b copydn_tail */ + + htole32(dst_addr), /* destination address */ + htole32(src_addr), /* source address */ + htole32(size), /* size (= byte count) */ + }; + aw_fel_write(dev, arm_code, dev->soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, dev->soc_info->scratch_addr); +} + +void fel_memmove(feldev_handle *dev, + uint32_t dst_addr, uint32_t src_addr, size_t size) +{ + /* + * To ensure non-destructive operation, we need to select "downwards" + * copying if the destination overlaps the source region. + */ + if (dst_addr >= src_addr && dst_addr < (src_addr + size)) + fel_memcpy_down(dev, dst_addr, src_addr, size); + else + fel_memcpy_up(dev, dst_addr, src_addr, size); +} + +/* + * Bitwise manipulation of a 32-bit word at given address, via bit masks that + * specify which bits to clear and which to set. + */ +void fel_clrsetbits_le32(feldev_handle *dev, + uint32_t addr, uint32_t clrbits, uint32_t setbits) +{ + uint32_t arm_code[] = { + htole32(0xe59f0018), /* 0: ldr r0, [addr] */ + htole32(0xe5901000), /* 4: ldr r1, [r0] */ + htole32(0xe59f2014), /* 8: ldr r2, [clrbits] */ + htole32(0xe1c11002), /* c: bic r1, r1, r2 */ + htole32(0xe59f2010), /* 10: ldr r2, [setbits] */ + htole32(0xe1811002), /* 14: orr r1, r1, r2 */ + htole32(0xe5801000), /* 18: str r1, [r0] */ + htole32(0xe12fff1e), /* 1c: bx lr */ + + htole32(addr), /* address */ + htole32(clrbits), /* bits to clear */ + htole32(setbits), /* bits to set */ + }; + aw_fel_write(dev, arm_code, dev->soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, dev->soc_info->scratch_addr); +} + +/* + * Memory access to the SID (root) keys proved to be unreliable for certain + * SoCs. This function uses an alternative, register-based approach to retrieve + * the values. + */ +static void fel_get_sid_registers(feldev_handle *dev, uint32_t *result, + uint32_t offset, uint32_t length) +{ + uint32_t arm_code[] = { + /* : */ + htole32(0xe59f0044), /* 0: ldr r0, [pc, #68] */ + htole32(0xe59f1044), /* 4: ldr r1, [pc, #68] */ + htole32(0xe28f3048), /* 8: add r3, pc, #72 */ + /* : */ + htole32(0xe1a02801), /* c: lsl r2, r1, #16 */ + htole32(0xe3822b2b), /* 10: orr r2, r2, #44032 */ + htole32(0xe3822002), /* 14: orr r2, r2, #2 */ + htole32(0xe5802040), /* 18: str r2, [r0, #64] */ + /* : */ + htole32(0xe5902040), /* 1c: ldr r2, [r0, #64] */ + htole32(0xe3120002), /* 20: tst r2, #2 */ + htole32(0x1afffffc), /* 24: bne 1c */ + htole32(0xe5902060), /* 28: ldr r2, [r0, #96] */ + htole32(0xe4832004), /* 2c: str r2, [r3], #4 */ + htole32(0xe2811004), /* 30: add r1, r1, #4 */ + htole32(0xe59f2018), /* 34: ldr r2, [pc, #24] */ + htole32(0xe1510002), /* 38: cmp r1, r2 */ + htole32(0x3afffff2), /* 3c: bcc c */ + htole32(0xe3a02000), /* 40: mov r2, #0 */ + htole32(0xe5802040), /* 44: str r2, [r0, #64] */ + htole32(0xe12fff1e), /* 48: bx lr */ + /* : */ + htole32(dev->soc_info->sid_base), /* SID base addr */ + /* : */ + htole32(offset), /* first word to read */ + /* : */ + htole32(offset + length), /* where to stop to read */ + /* retrieved SID values go here */ + }; + /* write and execute code */ + aw_fel_write(dev, arm_code, dev->soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute(dev, dev->soc_info->scratch_addr); + /* read back the result */ + aw_fel_read(dev, dev->soc_info->scratch_addr + sizeof(arm_code), + result, length); + for (unsigned i = 0; i < length / 4; i++) + result[i] = le32toh(result[i]); +} + +/** + * fel_read_sid() - Read the content of the SID eFuses. + * @dev: device handle for the FEL device + * @result: pointer of a buffer receiving the content of the eFuses + * @offset: beginning of the eFuses area to read, in bytes + * @length: length of the eFuses area to read, in bytes + * @force_workaround: whether to use the MMIO register based read method + * + * Read the contents of the non-volatile eFuses stored in the SoC. The size + * and supposed usage layout differs between SoCs, but the "root" key + * (containing some unique serial number) is always in the first 128 bits. + * + * Return: 0 if the operation was successful, a negative error code otherwise. + */ +int fel_read_sid(feldev_handle *dev, uint32_t *result, + unsigned int offset, unsigned int length, + bool force_workaround) +{ + const soc_info_t *soc = dev->soc_info; + + if (!soc->sid_base) /* SID unavailable */ + return -2; + if ((offset & 3) || (length & 3)) /* needs to be 32-bit aligned */ + return -3; + + if (soc->sid_fix || force_workaround) + /* Work around SID issues by using ARM thunk code */ + fel_get_sid_registers(dev, result, offset, length); + else + /* Read SID directly from memory */ + fel_readl_n(dev, soc->sid_base + soc->sid_offset + offset, + result, length); + return 0; +} + +/* general functions, "FEL device" management */ + +static int feldev_get_endpoint(feldev_handle *dev) +{ + struct libusb_device *usb = libusb_get_device(dev->usb->handle); + struct libusb_config_descriptor *config; + int if_idx, set_idx, ep_idx, ret; + const struct libusb_interface *iface; + const struct libusb_interface_descriptor *setting; + const struct libusb_endpoint_descriptor *ep; + + ret = libusb_get_active_config_descriptor(usb, &config); + if (ret) + return ret; + + for (if_idx = 0; if_idx < config->bNumInterfaces; if_idx++) { + iface = config->interface + if_idx; + + for (set_idx = 0; set_idx < iface->num_altsetting; set_idx++) { + setting = iface->altsetting + set_idx; + + for (ep_idx = 0; ep_idx < setting->bNumEndpoints; ep_idx++) { + ep = setting->endpoint + ep_idx; + + /* Test for bulk transfer endpoint */ + if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) + != LIBUSB_TRANSFER_TYPE_BULK) + continue; + + if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_IN) + dev->usb->endpoint_in = ep->bEndpointAddress; + else + dev->usb->endpoint_out = ep->bEndpointAddress; + } + } + } + + libusb_free_config_descriptor(config); + return LIBUSB_SUCCESS; +} + +/* claim USB interface associated with the libusb handle for a FEL device */ +static void feldev_claim(feldev_handle *dev) +{ + int rc = libusb_claim_interface(dev->usb->handle, 0); +#if defined(__linux__) + if (rc != LIBUSB_SUCCESS) { + libusb_detach_kernel_driver(dev->usb->handle, 0); + dev->usb->iface_detached = true; + rc = libusb_claim_interface(dev->usb->handle, 0); + } +#endif + if (rc) + usb_error(rc, "libusb_claim_interface()", 1); + + rc = feldev_get_endpoint(dev); + if (rc) + usb_error(rc, "FAILED to get FEL mode endpoint addresses!", 1); +} + +/* release USB interface associated with the libusb handle for a FEL device */ +static void feldev_release(feldev_handle *dev) +{ + libusb_release_interface(dev->usb->handle, 0); +#if defined(__linux__) + if (dev->usb->iface_detached) + libusb_attach_kernel_driver(dev->usb->handle, 0); +#endif +} + +/* open handle to desired FEL device */ +feldev_handle *feldev_open(int busnum, int devnum, + uint16_t vendor_id, uint16_t product_id) +{ + if (!fel_lib_initialized) /* if not already done: auto-initialize */ + feldev_init(); + feldev_handle *result = calloc(1, sizeof(feldev_handle)); + if (!result) { + fprintf(stderr, "FAILED to allocate feldev_handle memory.\n"); + exit(1); + } + result->usb = calloc(1, sizeof(felusb_handle)); + if (!result->usb) { + fprintf(stderr, "FAILED to allocate felusb_handle memory.\n"); + free(result); + exit(1); + } + + if (busnum < 0 || devnum < 0) { + /* With the default values (busnum -1, devnum -1) we don't care + * for a specific USB device; so let libusb open the first + * device that matches VID/PID. + */ + result->usb->handle = libusb_open_device_with_vid_pid(NULL, vendor_id, product_id); + if (!result->usb->handle) { + switch (errno) { + case EACCES: + fprintf(stderr, "ERROR: You don't have permission to access Allwinner USB FEL device\n"); + break; + default: + fprintf(stderr, "ERROR: Allwinner USB FEL device not found!\n"); + break; + } + exit(1); + } + } else { + /* look for specific bus and device number */ + bool found = false; + ssize_t rc, i; + libusb_device **list; + + rc = libusb_get_device_list(NULL, &list); + if (rc < 0) + usb_error(rc, "libusb_get_device_list()", 1); + for (i = 0; i < rc; i++) { + if (libusb_get_bus_number(list[i]) == busnum + && libusb_get_device_address(list[i]) == devnum) { + found = true; /* bus:devnum matched */ + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(list[i], &desc); + if (desc.idVendor != vendor_id + || desc.idProduct != product_id) { + fprintf(stderr, "ERROR: Bus %03d Device %03d not a FEL device " + "(expected %04x:%04x, got %04x:%04x)\n", busnum, devnum, + vendor_id, product_id, desc.idVendor, desc.idProduct); + exit(1); + } + /* open handle to this specific device (incrementing its refcount) */ + rc = libusb_open(list[i], &result->usb->handle); + if (rc != 0) + usb_error(rc, "libusb_open()", 1); + break; + } + } + libusb_free_device_list(list, true); + + if (!found) { + fprintf(stderr, "ERROR: Bus %03d Device %03d not found in libusb device list\n", + busnum, devnum); + exit(1); + } + } + + feldev_claim(result); /* claim interface, detect USB endpoints */ + + /* retrieve BROM version and SoC information */ + aw_fel_get_version(result, &result->soc_version); + get_soc_name_from_id(result->soc_name, result->soc_version.soc_id); + result->soc_info = get_soc_info_from_version(&result->soc_version); + + return result; +} + +/* close FEL device (optional, dev may be NULL) */ +void feldev_close(feldev_handle *dev) +{ + if (dev) { + if (dev->usb->handle) { + feldev_release(dev); + libusb_close(dev->usb->handle); + } + free(dev->usb); /* release memory allocated for felusb_handle */ + } +} + +void feldev_init(void) +{ + int rc = libusb_init(NULL); + if (rc != 0) + usb_error(rc, "libusb_init()", 1); + fel_lib_initialized = true; +} + +void feldev_done(feldev_handle *dev) +{ + feldev_close(dev); + free(dev); + if (fel_lib_initialized) libusb_exit(NULL); +} + +/* + * Enumerate (all) FEL devices. Allocates a list (array of feldev_list_entry) + * and optionally returns the number of elements via "count". You may + * alternatively detect the end of the list by checking the entry's soc_version + * for a zero ID. + * It's your responsibility to call free() on the result later. + */ +feldev_list_entry *list_fel_devices(size_t *count) +{ + feldev_list_entry *list, *entry; + ssize_t rc, i; + libusb_context *ctx; + libusb_device **usb; + struct libusb_device_descriptor desc; + feldev_handle *dev; + size_t devices = 0; + + libusb_init(&ctx); + rc = libusb_get_device_list(ctx, &usb); + if (rc < 0) + usb_error(rc, "libusb_get_device_list()", 1); + + /* + * Size our array to hold entries for every USB device, + * plus an empty one at the end (for list termination). + */ + list = calloc(rc + 1, sizeof(feldev_list_entry)); + if (!list) { + fprintf(stderr, "list_fel_devices() FAILED to allocate list memory.\n"); + exit(1); + } + + for (i = 0; i < rc; i++) { + libusb_get_device_descriptor(usb[i], &desc); + if (desc.idVendor != AW_USB_VENDOR_ID + || desc.idProduct != AW_USB_PRODUCT_ID) + continue; /* not an Allwinner FEL device */ + + entry = list + devices; /* pointer to current feldev_list_entry */ + devices += 1; + + entry->busnum = libusb_get_bus_number(usb[i]); + entry->devnum = libusb_get_device_address(usb[i]); + dev = feldev_open(entry->busnum, entry->devnum, + AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID); + + /* copy relevant fields */ + entry->soc_version = dev->soc_version; + entry->soc_info = dev->soc_info; + strncpy(entry->soc_name, dev->soc_name, sizeof(soc_name_t)); + + /* retrieve SID bits */ + fel_read_sid(dev, entry->SID, 0, 16, false); + + feldev_close(dev); + free(dev); + } + libusb_free_device_list(usb, true); + libusb_exit(ctx); + + if (count) *count = devices; + return list; +} diff --git a/fel_lib.h b/fel_lib.h new file mode 100644 index 0000000..68b1ab8 --- /dev/null +++ b/fel_lib.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 Bernhard Nortmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUNXI_TOOLS_FEL_LIB_H +#define _SUNXI_TOOLS_FEL_LIB_H + +#include +#include +#include "progress.h" +#include "soc_info.h" + +/* USB identifiers for Allwinner device in FEL mode */ +#define AW_USB_VENDOR_ID 0x1F3A +#define AW_USB_PRODUCT_ID 0xEFE8 + +typedef struct _felusb_handle felusb_handle; /* opaque data type */ + +/* More general FEL "device" handle, including version data and SoC info */ +typedef struct { + felusb_handle *usb; + struct aw_fel_version soc_version; + soc_name_t soc_name; + soc_info_t *soc_info; +} feldev_handle; + +/* list_fel_devices() will return an array of this type */ +typedef struct { + int busnum, devnum; + struct aw_fel_version soc_version; + soc_name_t soc_name; + soc_info_t *soc_info; + uint32_t SID[4]; +} feldev_list_entry; + +/* FEL device management */ + +void feldev_init(void); +void feldev_done(feldev_handle *dev); + +feldev_handle *feldev_open(int busnum, int devnum, + uint16_t vendor_id, uint16_t product_id); +void feldev_close(feldev_handle *dev); + +feldev_list_entry *list_fel_devices(size_t *count); + +/* FEL functions */ + +void aw_fel_read(feldev_handle *dev, uint32_t offset, void *buf, size_t len); +void aw_fel_write(feldev_handle *dev, const void *buf, uint32_t offset, size_t len); +void aw_fel_write_buffer(feldev_handle *dev, const void *buf, uint32_t offset, + size_t len, bool progress); +void aw_fel_execute(feldev_handle *dev, uint32_t offset); + +void fel_readl_n(feldev_handle *dev, uint32_t addr, uint32_t *dst, size_t count); +void fel_writel_n(feldev_handle *dev, uint32_t addr, uint32_t *src, size_t count); + +void fel_memmove(feldev_handle *dev, + uint32_t dst_addr, uint32_t src_addr, size_t size); + +void fel_clrsetbits_le32(feldev_handle *dev, + uint32_t addr, uint32_t clrbits, uint32_t setbits); +#define fel_clrbits_le32(dev, addr, value) \ + fel_clrsetbits_le32(dev, addr, value, 0) +#define fel_setbits_le32(dev, addr, value) \ + fel_clrsetbits_le32(dev, addr, 0, value) + +int fel_read_sid(feldev_handle *dev, uint32_t *result, + unsigned int offset, unsigned int length, + bool force_workaround); + +bool aw_fel_remotefunc_prepare(feldev_handle *dev, + size_t stack_size, + void *arm_code, + size_t arm_code_size, + size_t num_args, + uint32_t *args); +bool aw_fel_remotefunc_execute(feldev_handle *dev, uint32_t *result); + +#endif /* _SUNXI_TOOLS_FEL_LIB_H */ diff --git a/fexc.c b/fexc.c new file mode 100644 index 0000000..5e715d7 --- /dev/null +++ b/fexc.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fexc.h" + +#include +#include +#include +#include +#ifndef NO_MMAP + #include +#endif +#include +#include +#include + +#define pr_info(...) pr_error("fexc: " __VA_ARGS__) +#define pr_err(...) pr_error("E: fexc: " __VA_ARGS__) + +enum script_format { + FEX_SCRIPT_FORMAT, + BIN_SCRIPT_FORMAT, + UBOOT_HEADER_FORMAT, +}; + +/* + */ +static inline char *read_all(int fd, const char *filename, size_t *size) +{ + size_t buf_size = 4096, count = 0; + char *p, *buf = malloc(buf_size); + if (!buf) { + pr_err("%s: %s\n", "malloc", strerror(errno)); + return NULL; + } + p = buf; + while (1) { + ssize_t rc = read(fd, p, buf_size-count); + if (rc == 0) + break; + else if (rc > 0) { + count += rc; + p += rc; + + if (count == buf_size) { + char *new; + buf_size *= 2; + new = realloc(buf, buf_size); + if (!new) { + pr_err("%s: %s\n", "realloc", + strerror(errno)); + free(buf); + return NULL; + } else if (new != buf) { + buf = new; + p = buf + count; + } + } + } else if (errno != EAGAIN && errno != EINTR) { + pr_err("%s: %s: %s\n", filename, + "read", strerror(errno)); + free(buf); + return NULL; + } + } + + *size = count; + return buf; +} + +/* + */ +static inline int script_parse(enum script_format format, + const char *filename, + struct script *script) +{ + int ret = 0; + switch (format) { + case FEX_SCRIPT_FORMAT: { + FILE *in = stdin; + if (!filename) + filename = ""; + else if ((in = fopen(filename, "r")) == NULL) { + pr_err("%s: %s\n", filename, strerror(errno)); + break; + } + ret = script_parse_fex(in, filename, script); + fclose(in); + }; break; + case BIN_SCRIPT_FORMAT: { + int in = 0; /* stdin */ + struct stat sb; + void *bin = NULL; + size_t bin_size; + int allocated = 1; + + if (!filename) + filename = ""; + else if ((in = open(filename, O_RDONLY)) < 0) { + pr_err("%s: %s\n", filename, strerror(errno)); + break; + } + + if (fstat(in, &sb) == -1) { + pr_err("%s: %s: %s\n", filename, + "fstat", strerror(errno)); + goto bin_close; +#ifndef NO_MMAP + } else if (S_ISREG(sb.st_mode)) { + /* regular file, mmap it */ + bin = mmap(0, sb.st_size, PROT_READ, MAP_SHARED, in, 0); + if (bin == MAP_FAILED) { + pr_err("%s: %s: %s\n", filename, + "mmap", strerror(errno)); + goto bin_close; + } + bin_size = sb.st_size; + allocated = 0; +#endif + } else { + /* something else... just read it all! */ + bin = read_all(in, filename, &bin_size); + if (bin == NULL) + goto bin_close; + allocated = 1; + } + + ret = script_decompile_bin(bin, bin_size, filename, script); + if (allocated) + free(bin); +#ifndef NO_MMAP + else if (munmap(bin, bin_size) == -1) { + pr_err("%s: %s: %s\n", filename, + "munmap", strerror(errno)); + } +#endif +bin_close: + close(in); + }; break; + case UBOOT_HEADER_FORMAT: /* not valid input */ + ; + } + return ret; +} +static inline int script_generate(enum script_format format, + const char *filename, + struct script *script) +{ + int ret = 0; + static int (*text_gen[3]) (FILE *, const char *, struct script *) = { + [FEX_SCRIPT_FORMAT] = script_generate_fex, + [UBOOT_HEADER_FORMAT] = script_generate_uboot, + }; + + if (text_gen[format]) { + FILE *out = stdout; + + if (!filename) + filename = ""; + else if ((out = fopen(filename, "w")) == NULL) { + pr_err("%s: %s\n", filename, strerror(errno)); + goto done; + } + + ret = text_gen[format](out, filename, script); + fclose(out); + } else { + int out = 1; /* stdout */ + size_t sections, entries, bin_size; + void *bin; + + if (!filename) + filename = ""; + else if ((out = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) { + pr_err("%s: %s\n", filename, strerror(errno)); + goto done; + } + + bin_size = script_bin_size(script, §ions, &entries); + bin = calloc(1, bin_size); + if (!bin) + pr_err("%s: %s\n", "malloc", strerror(errno)); + else if (script_generate_bin(bin, bin_size, script, sections, entries)) { + char *p = bin; + while(bin_size) { + ssize_t wc = write(out, p, bin_size); + + if (wc>0) { + p += wc; + bin_size -= wc; + } else if (wc < 0 && errno != EINTR) { + pr_err("%s: %s: %s\n", filename, + "write", strerror(errno)); + break; + } + } + ret = (bin_size == 0); + } + free(bin); + close(out); + } +done: + return ret; +} + +/* + */ +static inline void app_usage(const char *arg0, int mode) +{ + fputs("sunxi-fexc " VERSION "\n\n", stderr); + pr_error("Usage: %s [-vq]%s[ []]\n", arg0, + mode ? " " : " [-I ] [-O ] "); + + if (mode == 0) + fputs("\ninfmt: fex, bin (default:fex)" + "\noutfmt: fex, bin, uboot (default:bin)\n", + stderr); +} + +static inline int app_choose_mode(char *arg0) +{ + const char *name = basename(arg0); + if (strcmp(name, "fex2bin") == 0) + return 1; + else if (strcmp(name, "bin2fex") == 0) + return 2; + else + return 0; +} + +/* + */ +int main(int argc, char *argv[]) +{ + static const char *formats[] = { "fex", "bin", "uboot", NULL }; + enum script_format infmt=FEX_SCRIPT_FORMAT; + enum script_format outfmt=BIN_SCRIPT_FORMAT; + const char *filename[] = { NULL /*stdin*/, NULL /*stdout*/}; + struct script *script; + + int app_mode = app_choose_mode(argv[0]); + + const char *opt_string = "I:O:vq?"; + if (app_mode != 0) opt_string += 4; /* disallow -I and -O */ + int opt, ret = 1; + int verbose = 0; + + if (app_mode == 2) { /* bin2fex */ + infmt = BIN_SCRIPT_FORMAT; + outfmt = FEX_SCRIPT_FORMAT; + } + + while ((opt = getopt(argc, argv, opt_string)) != -1) { + switch (opt) { + case 'I': + infmt=0; + for (const char **f = formats; *f; f++, infmt++) { + if (strcmp(*f, optarg) == 0) + break; + } + switch (infmt) { + case FEX_SCRIPT_FORMAT: + case BIN_SCRIPT_FORMAT: + break; + default: + pr_error("%s: invalid format -- \"%s\"\n", + argv[0], optarg); + goto show_usage; + } + break; + case 'O': + outfmt=0; + for (const char **f = formats; *f; f++, outfmt++) { + if (strcmp(*f, optarg) == 0) + break; + } + if (!formats[outfmt]) { + pr_error("%s: invalid format -- \"%s\"\n", + argv[0], optarg); + goto show_usage; + } + break; + case 'v': + verbose++; + break; + case 'q': + verbose--; + break; + default: +show_usage: + app_usage(argv[0], app_mode); + goto done; + } + } + + switch (argc - optind) { + case 2: + filename[1] = argv[optind+1]; /* out */ + /* fall-through */ + case 1: + if (strcmp(argv[optind], "-") != 0) + filename[0] = argv[optind]; /* in */ + case 0: + break; + default: + goto show_usage; + } + + if (verbose>0) + pr_error("%s: from %s:%s to %s:%s\n", argv[0], + formats[infmt], filename[0]?filename[0]:"", + formats[outfmt], filename[1]?filename[1]:""); + + if ((script = script_new()) == NULL) { + perror("malloc"); + goto done; + } else if (script_parse(infmt, filename[0], script) && + script_generate(outfmt, filename[1], script)) { + ret = 0; + } + script_delete(script); +done: + return ret; +} diff --git a/fexc.h b/fexc.h new file mode 100644 index 0000000..c5b32a9 --- /dev/null +++ b/fexc.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUNXI_TOOLS_FEXC_H +#define _SUNXI_TOOLS_FEXC_H + +#include "common.h" + +#include +#include + +#include "script.h" +#include "script_bin.h" +#include "script_fex.h" +#include "script_uboot.h" + +#endif diff --git a/find-arm-gcc.sh b/find-arm-gcc.sh new file mode 100755 index 0000000..ae7bf4b --- /dev/null +++ b/find-arm-gcc.sh @@ -0,0 +1,13 @@ +# +# Try to locate suitable ARM cross compilers available via $PATH +# If any are found, this function will output them as a TAB-delimited list +# +scan_path () { +IFS=":" +for path in $PATH; do + find "$path" -maxdepth 1 -executable -name 'arm*-gcc' -printf '%f\t' 2>/dev/null +done +} + +# Use only the first field from result, and convert it to a toolchain prefix +scan_path | cut -f 1 | sed -e 's/-gcc/-/' diff --git a/fit_image.c b/fit_image.c new file mode 100644 index 0000000..fcf32f1 --- /dev/null +++ b/fit_image.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2018-2020 Andre Przywara + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; under version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "common.h" +#include "fel_lib.h" +#include "fit_image.h" + +/* defined in fel.c */ +extern bool verbose; + +#define IH_ARCH_INVALID 0 +#define IH_ARCH_ARM 2 +#define IH_ARCH_ARM64 22 + +#define IH_OS_INVALID 0 +#define IH_OS_LINUX 5 +#define IH_OS_U_BOOT 17 +#define IH_OS_ARM_TRUSTED_FIRMWARE 25 +#define IH_OS_EFI 28 + +struct fit_image_info { + const char *description; + const char *data; + uint32_t data_size; + uint32_t load_addr; + uint32_t entry_point; + uint8_t os; + uint8_t arch; +}; + +static int fit_parse_os(const char *value) +{ + if (!value || !*value) + return IH_OS_INVALID; + + if (!strcmp(value, "u-boot")) + return IH_OS_U_BOOT; + if (!strcmp(value, "linux")) + return IH_OS_LINUX; + if (!strcmp(value, "arm-trusted-firmware")) + return IH_OS_ARM_TRUSTED_FIRMWARE; + if (!strcmp(value, "efi")) + return IH_OS_EFI; + + return IH_OS_INVALID; +} + +static int fit_parse_arch(const char *value) +{ + if (!value || !*value) + return IH_ARCH_INVALID; + + if (!strcmp(value, "arm")) + return IH_ARCH_ARM; + if (!strcmp(value, "arm64")) + return IH_ARCH_ARM64; + + return IH_ARCH_INVALID; +} + +static uint32_t fdt_getprop_u32(const void *fdt, int node, const char *name) +{ + const fdt32_t *val; + + val = fdt_getprop(fdt, node, name, NULL); + if (!val) + return ~0U; + + return fdt32_to_cpu(*val); +} + +static const char *fdt_getprop_str(const void *fdt, int node, const char *name) +{ + const struct fdt_property *prop; + + prop = fdt_get_property(fdt, node, name, NULL); + if (!prop) + return NULL; + + return prop->data; +} + +/* + * Find the image with the given name under the /images node, and parse + * its information into the fit_image_info struct. + * Returns 0 on success, and a negative error value otherwise. + */ +static int fit_get_image_info(const void *fit, const char *name, + struct fit_image_info *info) +{ + int node; + const char *str; + uint32_t data_offset; + + node = fdt_path_offset(fit, "/images"); + if (node < 0) + return -1; + node = fdt_subnode_offset(fit, node, name); + if (node < 0) + return -1; + + info->load_addr = fdt_getprop_u32(fit, node, "load"); + info->entry_point = fdt_getprop_u32(fit, node, "entry"); + info->description = fdt_getprop_str(fit, node, "description"); + /* properties used for FIT images with external data */ + info->data_size = fdt_getprop_u32(fit, node, "data-size"); + data_offset = fdt_getprop_u32(fit, node, "data-offset"); + + /* check for embedded data (when invalid external data properties) */ + if (info->data_size == ~0U || data_offset == ~0U) { + const struct fdt_property *prop; + int len; + + prop = fdt_get_property(fit, node, "data", &len); + info->data_size = len; + info->data = prop->data; + } else { + /* external data is appended at the end of the FIT DTB blob */ + info->data = (const char *)fit + ((fdt_totalsize(fit) + 3) & ~3U); + info->data += data_offset; + } + + info->os = fit_parse_os(fdt_getprop_str(fit, node, "os")); + info->arch = fit_parse_arch(fdt_getprop_str(fit, node, "arch")); + + str = fdt_getprop_str(fit, node, "compression"); + /* The current SPL does not support compression either. */ + if (str && strcmp(str, "none")) { + printf("compression \"%s\" not supported for image \"%s\"\n", + str, info->description); + return -2; + } + + return 0; +} + +static int entry_arch; +static uint32_t dtb_addr; + +/* + * Upload the image described by its fit_image_info struct to the board. + * Detect if an image contains an entry point and return that. + * Set entry_arch to arm or arm64 on the way. Also detect the image + * containing U-Boot and record its end address, so that the DTB can be + * appended later on. + * Returns the entry point if any is specified, or 0 otherwise. + */ +static uint32_t fit_load_image(feldev_handle *dev, struct fit_image_info *img) +{ + uint32_t ret = 0; + + if (verbose) + printf("loading image \"%s\" (%d bytes) to 0x%x\n", + img->description, img->data_size, img->load_addr); + aw_fel_write_buffer(dev, img->data, + img->load_addr, img->data_size, true); + + if (img->entry_point != ~0U) { + ret = img->entry_point; + entry_arch = img->arch; + } + /* either explicitly marked as U-Boot, or the first invalid one */ + if (img->os == IH_OS_U_BOOT || + (!dtb_addr && img->os == IH_OS_INVALID)) + dtb_addr = img->load_addr + img->data_size; + + return ret; +} + +uint32_t load_fit_images(feldev_handle *dev, const void *fit, + const char *dt_name, bool *use_aarch64) +{ + const struct fdt_property *prop; + struct fit_image_info img; + const char *str; + int node, len; + uint32_t entry_point = 0; + + node = fdt_path_offset(fit, "/configurations"); + if (node < 0) { + pr_error("invalid FIT image, no /configurations node\n"); + return 0; + } + + /* + * Find the right configuration node, either by using the provided + * DT name as an identifier, falling back to the node titled "default", + * or by using just the first node. + */ + if (dt_name) { + for (node = fdt_first_subnode(fit, node); + node >= 0; + node = fdt_next_subnode(fit, node)) { + prop = fdt_get_property(fit, node, "description", NULL); + if (prop && !strcmp(prop->data, dt_name)) + break; + } + if (node < 0) { + pr_error("no matching FIT configuration node for \"%s\"\n", + dt_name); + return 0; + } + } else { + prop = fdt_get_property(fit, node, "default", NULL); + if (!prop) + node = fdt_first_subnode(fit, node); + else + node = fdt_subnode_offset(fit, node, prop->data); + if (node < 0) { + pr_error("no default FIT configuration node\n"); + return 0; + } + } + + entry_arch = IH_ARCH_INVALID; + dtb_addr = 0; + + /* Load the image described as "firmware". */ + str = fdt_getprop_str(fit, node, "firmware"); + if (str && !fit_get_image_info(fit, str, &img)) { + uint32_t addr = fit_load_image(dev, &img); + + if (addr != 0) + entry_point = addr; + } else { + printf("WARNING: no valid \"firmware\" image entry in FIT\n"); + } + + /* load all loadables, at their respective load addresses */ + prop = fdt_get_property(fit, node, "loadables", &len); + for (str = prop ? prop->data : NULL; + prop && (str - prop->data) < len && *str; + str += strlen(str) + 1) { + uint32_t addr; + + if (fit_get_image_info(fit, str, &img)) { + printf("Can't load loadable \"%s\", skipping.\n", str); + continue; + } + addr = fit_load_image(dev, &img); + if (addr != 0) + entry_point = addr; + } + + if (use_aarch64) + *use_aarch64 = (entry_arch == IH_ARCH_ARM64); + + if (!dtb_addr) { + printf("Warning: no U-Boot image found, not loading DTB\n"); + return entry_point; + } + + /* load .dtb right after the U-Boot image (appended DTB) */ + str = fdt_getprop_str(fit, node, "fdt"); + if (!str || fit_get_image_info(fit, str, &img)) { + printf("Warning: no FDT found in FIT image\n"); + return entry_point; + } + if (verbose) + printf("loading DTB \"%s\" (%d bytes)\n", img.description, + img.data_size); + aw_fel_write_buffer(dev, img.data, dtb_addr, img.data_size, false); + + return entry_point; +} diff --git a/fit_image.h b/fit_image.h new file mode 100644 index 0000000..30813a3 --- /dev/null +++ b/fit_image.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018-2020 Andre Przywara + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; under version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __FIT_IMAGE_H__ +#define __FIT_IMAGE_H__ + +#include +#include "fel_lib.h" + +/* + * Load all images referenced in the given U-Boot FIT image. @dt_name will + * be used to select one of the configurations. @use_aarch64 contains the + * target architecture of the entry point. + * Returns the entry point address of the image to be started. + */ +uint32_t load_fit_images(feldev_handle *dev, const void *fit, + const char *dt_name, bool *use_aarch64); + +#endif diff --git a/include/list.h b/include/list.h new file mode 100644 index 0000000..88185ce --- /dev/null +++ b/include/list.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUNXI_TOOLS_LIST_H +#define _SUNXI_TOOLS_LIST_H + +/* + * list + */ + +/** a list hook */ +struct list_entry { + struct list_entry *prev; + struct list_entry *next; +}; + +/** initialize an empty list hook */ +static inline void list_init(struct list_entry *self) +{ + self->prev = self->next = self; +} + +/** puts an entry between two other on a list */ +static inline void list_inject(struct list_entry *l, + struct list_entry *prev, + struct list_entry *next) +{ + l->prev = prev; + l->next = next; + + next->prev = l; + prev->next = l; +} + +#define list_insert(H, E) list_inject((E), (H), (H)->next) +#define list_append(H, E) list_inject((E), (H)->prev, (H)) + +/** removes an entry for the list where it's contained */ +static inline void list_remove(struct list_entry *l) +{ + struct list_entry *prev = l->prev, *next = l->next; + next->prev = prev; + prev->next = next; +} + +/** returns first element of a list */ +static inline struct list_entry *list_first(struct list_entry *l) +{ + return (l->next == l) ? NULL : l->next; +} + +/** returns last element of a list */ +static inline struct list_entry *list_last(struct list_entry *l) +{ + return (l->prev == l) ? NULL : l->prev; +} + +/** returns next element on a list */ +static inline struct list_entry *list_next(struct list_entry *l, + struct list_entry *e) +{ + return (e->next == l) ? NULL : e->next; +} + +/** is list empty? */ +static inline int list_empty(struct list_entry *l) +{ + return (l->prev == l); +} + +#endif /* _SUNXI_TOOLS_LIST_H */ diff --git a/include/portable_endian.h b/include/portable_endian.h new file mode 100644 index 0000000..3f1d532 --- /dev/null +++ b/include/portable_endian.h @@ -0,0 +1,125 @@ +// "License": Public Domain +// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like. +// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to +// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it +// an example on how to get the endian conversion functions on different platforms. + +#ifndef PORTABLE_ENDIAN_H__ +#define PORTABLE_ENDIAN_H__ + +#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__) + +# define __WINDOWS__ + +#endif + +#if defined(__linux__) || defined(__CYGWIN__) + +# include + +#elif defined(__APPLE__) + +# include + +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htole16(x) OSSwapHostToLittleInt16(x) +# define be16toh(x) OSSwapBigToHostInt16(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) + +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) + +# define htobe64(x) OSSwapHostToBigInt64(x) +# define htole64(x) OSSwapHostToLittleInt64(x) +# define be64toh(x) OSSwapBigToHostInt64(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + +# include + +/* For functions still missing, try to substitute 'historic' OpenBSD names */ +#ifndef be16toh +# define be16toh(x) betoh16(x) +#endif +#ifndef le16toh +# define le16toh(x) letoh16(x) +#endif +#ifndef be32toh +# define be32toh(x) betoh32(x) +#endif +#ifndef le32toh +# define le32toh(x) letoh32(x) +#endif +#ifndef be64toh +# define be64toh(x) betoh64(x) +#endif +#ifndef le64toh +# define le64toh(x) letoh64(x) +#endif + +#elif defined(__WINDOWS__) + +# include +# include + +# if BYTE_ORDER == LITTLE_ENDIAN + +# define htobe16(x) htons(x) +# define htole16(x) (x) +# define be16toh(x) ntohs(x) +# define le16toh(x) (x) + +# define htobe32(x) htonl(x) +# define htole32(x) (x) +# define be32toh(x) ntohl(x) +# define le32toh(x) (x) + +# define htobe64(x) htonll(x) +# define htole64(x) (x) +# define be64toh(x) ntohll(x) +# define le64toh(x) (x) + +# elif BYTE_ORDER == BIG_ENDIAN + + /* that would be xbox 360 */ +# define htobe16(x) (x) +# define htole16(x) __builtin_bswap16(x) +# define be16toh(x) (x) +# define le16toh(x) __builtin_bswap16(x) + +# define htobe32(x) (x) +# define htole32(x) __builtin_bswap32(x) +# define be32toh(x) (x) +# define le32toh(x) __builtin_bswap32(x) + +# define htobe64(x) (x) +# define htole64(x) __builtin_bswap64(x) +# define be64toh(x) (x) +# define le64toh(x) __builtin_bswap64(x) + +# else + +# error byte order not supported + +# endif + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#else + +# error platform not supported + +#endif + +#endif diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..de37eae --- /dev/null +++ b/include/types.h @@ -0,0 +1,46 @@ +/* + * (C) Copyright 2012 Henrik Nordstrom + * (C) Copyright 2012 Alejandro Mery + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef SUNXI_TYPES_H +#define SUNXI_TYPES_H + +#include + +#define __s8 int8_t +#define __s16 int16_t +#define __s32 int32_t +#define __s64 int64_t + +#define s8 int8_t +#define s16 int16_t +#define s32 int32_t +#define s64 int64_t + +#define __u8 uint8_t +#define __u16 uint16_t +#define __u32 uint32_t +#define __u64 uint64_t + +#define u8 uint8_t +#define u16 uint16_t +#define u32 uint32_t +#define u64 uint64_t + +#endif diff --git a/jtag-loop.S b/jtag-loop.S new file mode 100644 index 0000000..0727fdc --- /dev/null +++ b/jtag-loop.S @@ -0,0 +1,41 @@ +/* + * (C) Copyright 2012 Jens Andersen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* + +Build instructions: + +arm-none-linux-gnueabi-gcc -g -fno-common -ffixed-r8 -msoft-float -fno-builtin -ffreestanding -nostdinc -mno-thumb-interwork -Wall -Wstrict-prototypes -fno-stack-protector -Wno-format-nonliteral -Wno-format-security -fno-toplevel-reorder jtag-loop.S -c + +arm-none-linux-gnueabi-objcopy -O binary jtag-loop.o jtag-loop.bin + +mksunxiboot jtag-loop.bin jtag-loop.sunxi +*/ + +.file "fel-loop.S" +.global entry +.text +.code 32 +.section ".start", "ax" + +entry: + ldr r0,=0x01c208b4 + ldr r1,=0x00444444 + str r1, [r0] + b . diff --git a/jtag-loop.c b/jtag-loop.c new file mode 100644 index 0000000..1110e48 --- /dev/null +++ b/jtag-loop.c @@ -0,0 +1,36 @@ +/* + * (C) Copyright 2012 Jens Andersen + * (C) Copyright 2012 Henrik Nordstrom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* + +Build instructions: + +arm-none-linux-gnueabi-gcc -g -fno-common -ffixed-r8 -msoft-float -fno-builtin -ffreestanding -nostdinc -mno-thumb-interwork -Wall -Wstrict-prototypes -fno-stack-protector -Wno-format-nonliteral -Wno-format-security -fno-toplevel-reorder -Os jtag-loop.c -c + +arm-none-linux-gnueabi-objcopy -O binary jtag-loop.o jtag-loop.bin + +mksunxiboot jtag-loop.bin jtag-loop.sunxi +*/ + +void _start(void) +{ + *(volatile unsigned long *)0x01c208b4 = 0x00444444; + while(1); +} diff --git a/jtag-loop.lds b/jtag-loop.lds new file mode 100644 index 0000000..9ba718d --- /dev/null +++ b/jtag-loop.lds @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2012 Henrik Nordstrom + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +SECTIONS +{ + . = 0x0030; + .text : { *(.text) } + /DISCARD/ : { *(.dynstr*) } + /DISCARD/ : { *(.dynamic*) } + /DISCARD/ : { *(.plt*) } + /DISCARD/ : { *(.interp*) } + /DISCARD/ : { *(.gnu*) } + /DISCARD/ : { *(.note*) } +} diff --git a/meminfo.c b/meminfo.c new file mode 100644 index 0000000..3b3a5df --- /dev/null +++ b/meminfo.c @@ -0,0 +1,791 @@ +/* + * Copyright (C) 2012 Floris Bos + * Copyright (c) 2014 Luc Verhaegen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +typedef uint32_t u32; + +/* from u-boot code: */ +struct sun4i_dram_para { + u32 baseaddr; + u32 clock; + u32 type; + u32 rank_num; + u32 density; + u32 io_width; + u32 bus_width; + u32 cas; + u32 zq; + u32 odt_en; + u32 size; + u32 tpr0; + u32 tpr1; + u32 tpr2; + u32 tpr3; + u32 tpr4; + u32 tpr5; + u32 emr1; + u32 emr2; + u32 emr3; +}; + +#define DEVMEM_FILE "/dev/mem" +static int devmem_fd; + +enum sunxi_soc_version { + SUNXI_SOC_SUN4I = 0x1623, /* A10 */ + SUNXI_SOC_SUN5I = 0x1625, /* A13, A10s */ + SUNXI_SOC_SUN6I = 0x1633, /* A31 */ + SUNXI_SOC_SUN7I = 0x1651, /* A20 */ + SUNXI_SOC_SUN8I = 0x1650, /* A23 */ + SUNXI_SOC_SUN9I = 0x1667, /* A33 */ + SUNXI_SOC_SUN10I = 0x1635, /* A80 */ +}; + +static enum sunxi_soc_version soc_version; + +/* + * Libv's favourite register handling calls. + */ +unsigned int +sunxi_io_read(void *base, int offset) +{ + return *(volatile unsigned int*) (base + offset); +} + +void +sunxi_io_write(void *base, int offset, unsigned int value) +{ + *(volatile unsigned int*) (base + offset) = value; +} + +void +sunxi_io_mask(void *base, int offset, unsigned int value, unsigned int mask) +{ + unsigned int tmp = sunxi_io_read(base, offset); + + tmp &= ~mask; + tmp |= value & mask; + + sunxi_io_write(base, offset, tmp); +} + + +/* + * Find out exactly which SoC we are dealing with. + */ +#define SUNXI_IO_SRAM_BASE 0x01C00000 +#define SUNXI_IO_SRAM_SIZE 0x00001000 + +#define SUNXI_IO_SRAM_VERSION 0x24 + +static int +soc_version_read(void) +{ + void *base; + unsigned int restore; + + base = mmap(NULL, SUNXI_IO_SRAM_SIZE, PROT_READ|PROT_WRITE, + MAP_SHARED, devmem_fd, SUNXI_IO_SRAM_BASE); + if (base == MAP_FAILED) { + fprintf(stderr, "Failed to map sram registers: %s\n", + strerror(errno)); + return errno; + } + + restore = sunxi_io_read(base, SUNXI_IO_SRAM_VERSION); + + sunxi_io_mask(base, SUNXI_IO_SRAM_VERSION, 0x8000, 0x8000); + + soc_version = sunxi_io_read(base, SUNXI_IO_SRAM_VERSION) >> 16; + + sunxi_io_mask(base, SUNXI_IO_SRAM_VERSION, restore, 0x8000); + + munmap(base, SUNXI_IO_SRAM_SIZE); + + return 0; +} + +/* + * Read DRAM clock. + */ +#define SUNXI_IO_CCM_BASE 0x01C20000 +#define SUNXI_IO_CCM_SIZE 0x00001000 + +#define SUNXI_IO_CCM_PLL5_CFG 0x20 + +static int +sunxi_dram_clock_read(unsigned int *clock) +{ + void *base; + unsigned int tmp; + int n, k, m; + + base = mmap(NULL, SUNXI_IO_CCM_SIZE, PROT_READ, + MAP_SHARED, devmem_fd, SUNXI_IO_CCM_BASE); + if (base == MAP_FAILED) { + fprintf(stderr, "Failed to map ccm registers: %s\n", + strerror(errno)); + return errno; + } + + tmp = sunxi_io_read(base, SUNXI_IO_CCM_PLL5_CFG); + + munmap(base, SUNXI_IO_CCM_SIZE); + + n = (tmp >> 8) & 0x1F; + k = ((tmp >> 4) & 0x03) + 1; + m = (tmp & 0x03) + 1; + + switch (soc_version) { + case SUNXI_SOC_SUN6I: + case SUNXI_SOC_SUN8I: + n++; + break; + default: + break; + } + + *clock = (24 * n * k) / m; + + return 0; +} + +struct regs { + int offset; + char *name; +}; + +static int +dram_registers_print(unsigned int address, int size, const struct regs *regs, + const char *description, const char *prefix) +{ + void *base; + int i, j; + + base = mmap(NULL, size, PROT_READ, MAP_SHARED, devmem_fd, address); + if (base == MAP_FAILED) { + fprintf(stderr, "Failed to map %s registers: %s\n", + description, strerror(errno)); + return errno; + } + + printf("/*\n"); + printf(" * %s Registers\n", description); + printf(" */\n"); + + for (i = 0; i < size; i += 4) { + unsigned int reg = sunxi_io_read(base, i); + + for (j = 0; regs[j].name; j++) + if (i == regs[j].offset) { + printf("%s = 0x%08x;\n", regs[j].name, reg); + } + + if (reg && !regs[j].name) + printf("%s_%03X = 0x%08x;\n", prefix, i, reg); + } + + printf("\n"); + + munmap(base, size); + + return 0; +} + +static int +dram_register_range_print(unsigned int address, int size, + const char *description, const char *prefix) +{ + void *base; + int i; + + base = mmap(NULL, size, PROT_READ, MAP_SHARED, devmem_fd, address); + if (base == MAP_FAILED) { + fprintf(stderr, "Failed to map %s registers: %s\n", + description, strerror(errno)); + return errno; + } + + printf("/*\n"); + printf(" * %s Registers\n", description); + printf(" */\n"); + + for (i = 0; i < size; i += 4) { + unsigned int reg = sunxi_io_read(base, i); + + if (reg) + printf("%s_%03X = 0x%08x;\n", prefix, i, reg); + } + + printf("\n"); + + munmap(base, size); + + return 0; +} + +/* + * Read DRAM parameters. + */ +#define SUN4I_IO_DRAM_BASE 0x01C01000 +#define SUN4I_IO_DRAM_SIZE 0x00001000 + +#define SUN4I_IO_DRAM_CCR 0x000 /* controller configuration register */ +#define SUN4I_IO_DRAM_DCR 0x004 /* dram configuration */ +#define SUN4I_IO_DRAM_IOCR 0x008 /* i/o configuration */ + +#define SUN4I_IO_DRAM_TPR0 0x014 /* dram timing parameters register 0 */ +#define SUN4I_IO_DRAM_TPR1 0x018 /* dram timing parameters register 1 */ +#define SUN4I_IO_DRAM_TPR2 0x01C /* dram timing parameters register 2 */ + +#define SUN4I_IO_DRAM_ZQCR0 0x0A8 /* zq control register 0 */ +#define SUN4I_IO_DRAM_ZQCR1 0x0AC /* zq control register 1 */ + +#define SUN4I_IO_DRAM_MR 0x1F0 /* mode register */ +#define SUN4I_IO_DRAM_EMR 0x1F4 /* extended mode register */ +#define SUN4I_IO_DRAM_EMR2 0x1F8 /* extended mode register */ +#define SUN4I_IO_DRAM_EMR3 0x1FC /* extended mode register */ + +#define SUN4I_IO_DRAM_DLLCR0 0x204 /* dll control register 0(byte 0) */ +#define SUN4I_IO_DRAM_DLLCR1 0x208 /* dll control register 1(byte 1) */ +#define SUN4I_IO_DRAM_DLLCR2 0x20C /* dll control register 2(byte 2) */ +#define SUN4I_IO_DRAM_DLLCR3 0x210 /* dll control register 3(byte 3) */ +#define SUN4I_IO_DRAM_DLLCR4 0x214 /* dll control register 4(byte 4) */ + +static int +sun4i_dram_parameters_read(struct sun4i_dram_para *dram_para) +{ + void *base; + unsigned int zqcr0, dcr; + unsigned int dllcr0, dllcr1, dllcr2, dllcr3, dllcr4; + + base = mmap(NULL, SUN4I_IO_DRAM_SIZE, PROT_READ, + MAP_SHARED, devmem_fd, SUN4I_IO_DRAM_BASE); + if (base == MAP_FAILED) { + fprintf(stderr, "Failed to map dram registers: %s\n", + strerror(errno)); + return errno; + } + + dram_para->tpr0 = sunxi_io_read(base, SUN4I_IO_DRAM_TPR0); + dram_para->tpr1 = sunxi_io_read(base, SUN4I_IO_DRAM_TPR1); + dram_para->tpr2 = sunxi_io_read(base, SUN4I_IO_DRAM_TPR2); + + dllcr0 = (sunxi_io_read(base, SUN4I_IO_DRAM_DLLCR0) >> 6) & 0x3F; + dllcr1 = (sunxi_io_read(base, SUN4I_IO_DRAM_DLLCR1) >> 14) & 0x0F; + dllcr2 = (sunxi_io_read(base, SUN4I_IO_DRAM_DLLCR2) >> 14) & 0x0F; + dllcr3 = (sunxi_io_read(base, SUN4I_IO_DRAM_DLLCR3) >> 14) & 0x0F; + dllcr4 = (sunxi_io_read(base, SUN4I_IO_DRAM_DLLCR4) >> 14) & 0x0F; + + dram_para->tpr3 = (dllcr0 << 16) | + (dllcr4 << 12) | (dllcr3 << 8) | (dllcr2 << 4) | dllcr1; + + if (soc_version == SUNXI_SOC_SUN7I) { + if (sunxi_io_read(base, SUN4I_IO_DRAM_CCR) & 0x20) + dram_para->tpr4 |= 0x01; + if (!(sunxi_io_read(base, SUN4I_IO_DRAM_ZQCR1) & 0x01000000)) + dram_para->tpr4 |= 0x02; + } + + dram_para->cas = (sunxi_io_read(base, SUN4I_IO_DRAM_MR) >> 4) & 0x0F; + dram_para->emr1 = sunxi_io_read(base, SUN4I_IO_DRAM_EMR); + dram_para->emr2 = sunxi_io_read(base, SUN4I_IO_DRAM_EMR2); + dram_para->emr3 = sunxi_io_read(base, SUN4I_IO_DRAM_EMR3); + + dram_para->odt_en = sunxi_io_read(base, SUN4I_IO_DRAM_IOCR) & 0x03; + zqcr0 = sunxi_io_read(base, SUN4I_IO_DRAM_ZQCR0); + dram_para->zq = (zqcr0 & 0xf0000000) | + ((zqcr0 >> 20) & 0xff) | + ((zqcr0 & 0xfffff) << 8); + + dcr = sunxi_io_read(base, SUN4I_IO_DRAM_DCR); + if (dcr & 0x01) { + dram_para->cas += 4; + dram_para->type = 3; + } else + dram_para->type = 2; + + dram_para->density = (1 << ((dcr >> 3) & 0x07)) * 256; + dram_para->rank_num = ((dcr >> 10) & 0x03) + 1; + dram_para->io_width = ((dcr >> 1) & 0x03) * 8; + dram_para->bus_width = (((dcr >> 6) & 3) + 1) * 8; + + munmap(base, SUN4I_IO_DRAM_SIZE); + + return 0; +} + +/* + * Print a dram.c that can be stuck immediately into u-boot. + */ +void +sun4i_dram_para_print_uboot(struct sun4i_dram_para *dram_para) +{ + printf("// place this file in board/sunxi/ in u-boot\n"); + printf("/* this file is generated, don't edit it yourself */\n"); + printf("\n"); + printf("#include \"common.h\"\n"); + printf("#include \n"); + printf("\n"); + printf("static struct dram_para dram_para = {\n"); + printf("\t.clock = %d,\n", dram_para->clock); + printf("\t.type = %d,\n", dram_para->type); + printf("\t.rank_num = %d,\n", dram_para->rank_num); + printf("\t.density = %d,\n", dram_para->density); + printf("\t.io_width = %d,\n", dram_para->io_width); + printf("\t.bus_width = %d,\n", dram_para->bus_width); + printf("\t.cas = %d,\n", dram_para->cas); + printf("\t.zq = 0x%02x,\n", dram_para->zq); + printf("\t.odt_en = %d,\n", dram_para->odt_en); + printf("\t.size = !!! FIXME !!!, /* in MiB */\n"); + printf("\t.tpr0 = 0x%08x,\n", dram_para->tpr0); + printf("\t.tpr1 = 0x%04x,\n", dram_para->tpr1); + printf("\t.tpr2 = 0x%05x,\n", dram_para->tpr2); + printf("\t.tpr3 = 0x%02x,\n", dram_para->tpr3); + printf("\t.tpr4 = 0x%02x,\n", dram_para->tpr4); + printf("\t.tpr5 = 0x%02x,\n", dram_para->tpr5); + printf("\t.emr1 = 0x%02x,\n", dram_para->emr1); + printf("\t.emr2 = 0x%02x,\n", dram_para->emr2); + printf("\t.emr3 = 0x%02x,\n", dram_para->emr3); + printf("};\n"); + printf("\n"); + printf("unsigned long sunxi_dram_init(void)\n"); + printf("{\n"); + printf("\treturn dramc_init(&dram_para);\n"); + printf("}\n"); +} + +/* + * Print output matching the .fex output, so it can be stuck in a + * fex file directly. + */ +void +sun4i_dram_para_print_fex(struct sun4i_dram_para *dram_para) +{ + printf("; Insert this section into your .fex file\n"); + printf("[dram_para]\n"); + printf("dram_baseaddr = 0x40000000\n"); + printf("dram_clk = %d\n", dram_para->clock); + printf("dram_type = %d\n", dram_para->type); + printf("dram_rank_num = %d\n", dram_para->rank_num); + printf("dram_chip_density = %d\n", dram_para->density); + printf("dram_io_width = %d\n", dram_para->io_width); + printf("dram_bus_width = %d\n", dram_para->bus_width); + printf("dram_cas = %d\n", dram_para->cas); + printf("dram_zq = 0x%02x\n", dram_para->zq); + printf("dram_odt_en = %d\n", dram_para->odt_en); + printf("dram_size = !!! FIXME !!!\n"); + printf("dram_tpr0 = 0x%08x\n", dram_para->tpr0); + printf("dram_tpr1 = 0x%04x\n", dram_para->tpr1); + printf("dram_tpr2 = 0x%05x\n", dram_para->tpr2); + printf("dram_tpr3 = 0x%02x\n", dram_para->tpr3); + printf("dram_tpr4 = 0x%02x\n", dram_para->tpr4); + printf("dram_tpr5 = 0x%02x\n", dram_para->tpr5); + printf("dram_emr1 = 0x%02x\n", dram_para->emr1); + printf("dram_emr2 = 0x%02x\n", dram_para->emr2); + printf("dram_emr3 = 0x%02x\n", dram_para->emr3); +} + +static int +sun4i_dram_para_print(bool uboot) +{ + struct sun4i_dram_para dram_para = { .baseaddr = 0 }; + int ret; + + ret = sunxi_dram_clock_read(&dram_para.clock); + if (ret) + return ret; + + ret = sun4i_dram_parameters_read(&dram_para); + if (ret) + return ret; + + if (uboot) + sun4i_dram_para_print_uboot(&dram_para); + else + sun4i_dram_para_print_fex(&dram_para); + + return 0; +} + +/* + * + */ +#define SUN6I_IO_DRAMCOM_BASE 0x01C62000 +#define SUN6I_IO_DRAMCOM_SIZE 0x0300 +#define SUN6I_IO_DRAMCTL_BASE 0x01C63000 +#define SUN6I_IO_DRAMCTL_SIZE 0x0400 +#define SUN6I_IO_DRAMPHY_BASE 0x01C65000 +#define SUN6I_IO_DRAMPHY_SIZE 0x0400 + +static struct regs +sun6i_dramcom_regs[] = { + {0x00, "SDR_COM_CR"}, + {0x04, "SDR_COM_CCR"}, + {0x10, "SDR_COM_MFACR"}, + {0x30, "SDR_COM_MSACR"}, + {0x50, "SDR_COM_MBACR"}, + {0, NULL} +}; + +static struct regs +sun6i_dramctl_regs[] = { + {0x004, "SDR_SCTL"}, + {0x008, "SDR_SSTAT"}, + {0x040, "SDR_MCMD"}, + {0x04c, "SDR_CMDSTAT"}, + {0x050, "SDR_CMDSTATEN"}, + {0x060, "SDR_MRRCFG0"}, + {0x064, "SDR_MRRSTAT0"}, + {0x068, "SDR_MRRSTAT1"}, + {0x07c, "SDR_MCFG1"}, + {0x080, "SDR_MCFG"}, + {0x084, "SDR_PPCFG"}, + {0x088, "SDR_MSTAT"}, + {0x08c, "SDR_LP2ZQCFG"}, + {0x094, "SDR_DTUSTAT"}, + {0x098, "SDR_DTUNA"}, + {0x09c, "SDR_DTUNE"}, + {0x0a0, "SDR_DTUPRD0"}, + {0x0a4, "SDR_DTUPRD1"}, + {0x0a8, "SDR_DTUPRD2"}, + {0x0ac, "SDR_DTUPRD3"}, + {0x0b0, "SDR_DTUAWDT"}, + {0x0c0, "SDR_TOGCNT1U"}, + {0x0cc, "SDR_TOGCNT100N"}, + {0x0d0, "SDR_TREFI"}, + {0x0d4, "SDR_TMRD"}, + {0x0d8, "SDR_TRFC"}, + {0x0dc, "SDR_TRP"}, + {0x0e0, "SDR_TRTW"}, + {0x0e4, "SDR_TAL"}, + {0x0e8, "SDR_TCL"}, + {0x0ec, "SDR_TCWL"}, + {0x0f0, "SDR_TRAS"}, + {0x0f4, "SDR_TRC"}, + {0x0f8, "SDR_TRCD"}, + {0x0fc, "SDR_TRRD"}, + {0x100, "SDR_TRTP"}, + {0x104, "SDR_TWR"}, + {0x108, "SDR_TWTR"}, + {0x10c, "SDR_TEXSR"}, + {0x110, "SDR_TXP"}, + {0x114, "SDR_TXPDLL"}, + {0x118, "SDR_TZQCS"}, + {0x11c, "SDR_TZQCSI"}, + {0x120, "SDR_TDQS"}, + {0x124, "SDR_TCKSRE"}, + {0x128, "SDR_TCKSRX"}, + {0x12c, "SDR_TCKE"}, + {0x130, "SDR_TMOD"}, + {0x134, "SDR_TRSTL"}, + {0x138, "SDR_TZQCL"}, + {0x13c, "SDR_TMRR"}, + {0x140, "SDR_TCKESR"}, + {0x144, "SDR_TDPD"}, + {0x200, "SDR_DTUWACTL"}, + {0x204, "SDR_DTURACTL"}, + {0x208, "SDR_DTUCFG"}, + {0x20c, "SDR_DTUECTL"}, + {0x210, "SDR_DTUWD0"}, + {0x214, "SDR_DTUWD1"}, + {0x218, "SDR_DTUWD2"}, + {0x21c, "SDR_DTUWD3"}, + {0x220, "SDR_DTUWDM"}, + {0x224, "SDR_DTURD0"}, + {0x224, "SDR_DTURD1"}, + {0x22c, "SDR_DTURD2"}, + {0x230, "SDR_DTURD3"}, + {0x234, "SDR_DTULFSRWD"}, + {0x238, "SDR_DTULFSRRD"}, + {0x23c, "SDR_DTUEAF"}, + {0x240, "SDR_DFITCTLDLY"}, + {0x244, "SDR_DFIODTCFG"}, + {0x248, "SDR_DFIODTCFG1"}, + {0x24c, "SDR_DFIODTRMAP"}, + {0x250, "SDR_DFITPHYWRD"}, + {0x254, "SDR_DFITPHYWRL"}, + {0x260, "SDR_DFITRDDEN"}, + {0x264, "SDR_DFITPHYRDL"}, + {0x270, "SDR_DFITPHYUPDTYPE0"}, + {0x274, "SDR_DFITPHYUPDTYPE1"}, + {0x278, "SDR_DFITPHYUPDTYPE2"}, + {0x27c, "SDR_DFITPHYUPDTYPE3"}, + {0x280, "SDR_DFITCTRLUPDMIN"}, + {0x284, "SDR_DFITCTRLUPDMAX"}, + {0x288, "SDR_DFITCTRLUPDDLY"}, + {0x290, "SDR_DFIUPDCFG"}, + {0x294, "SDR_DFITREFMSKI"}, + {0x298, "SDR_DFITCRLUPDI"}, + {0x2ac, "SDR_DFITRCFG0"}, + {0x2b0, "SDR_DFITRSTAT0"}, + {0x2b4, "SDR_DFITRWRLVLEN"}, + {0x2b8, "SDR_DFITRRDLVLEN"}, + {0x2bc, "SDR_DFITRRDLVLGATEEN"}, + {0x2c4, "SDR_DFISTCFG0"}, + {0x2c8, "SDR_DFISTCFG1"}, + {0x2d0, "SDR_DFITDRAMCLKEN"}, + {0x2d4, "SDR_DFITDRAMCLKDIS"}, + {0x2f0, "SDR_DFILPCFG0"}, + {0, NULL} +}; + +static struct regs +sun6i_dramphy_regs[] = { + {0x004, "SDR_PIR"}, + {0x008, "SDR_PGCR"}, + {0x00c, "SDR_PGSR"}, + {0x010, "SDR_DLLGCR"}, + {0x014, "SDR_ACDLLCR"}, + {0x018, "SDR_PTR0"}, + {0x01c, "SDR_PTR1"}, + {0x020, "SDR_PTR2"}, + {0x024, "SDR_ACIOCR"}, + {0x028, "SDR_DXCCR"}, + {0x02c, "SDR_DSGCR"}, + {0x030, "SDR_DCR"}, + {0x034, "SDR_DTPR0"}, + {0x038, "SDR_DTPR1"}, + {0x03c, "SDR_DTPR2"}, + {0x040, "SDR_MR0"}, + {0x044, "SDR_MR1"}, + {0x048, "SDR_MR2"}, + {0x04c, "SDR_MR3"}, + {0x050, "SDR_ODTCR"}, + {0x054, "SDR_DTAR"}, + {0x058, "SDR_DTDT0"}, + {0x05c, "SDR_DTDT1"}, + {0x0c0, "SDR_DCUAR"}, + {0x0c4, "SDR_DCUDR"}, + {0x0c8, "SDR_DCURR"}, + {0x0cc, "SDR_DCULR"}, + {0x0d0, "SDR_DCUGCR"}, + {0x0d4, "SDR_DCUTPR"}, + {0x0d8, "SDR_DCUSR0"}, + {0x0dc, "SDR_DCUSR1"}, + {0x100, "SDR_BISTRR"}, + {0x104, "SDR_BISTMSKR0"}, + {0x108, "SDR_BISTMSKR1"}, + {0x10c, "SDR_BISTWCR"}, + {0x110, "SDR_BISTLSR"}, + {0x114, "SDR_BISTAR0"}, + {0x118, "SDR_BISTAR1"}, + {0x11c, "SDR_BISTAR2"}, + {0x120, "SDR_BISTUDPR"}, + {0x124, "SDR_BISTGSR"}, + {0x128, "SDR_BISTWER"}, + {0x12c, "SDR_BISTBER0"}, + {0x130, "SDR_BISTBER1"}, + {0x134, "SDR_BISTBER2"}, + {0x138, "SDR_BISTWCSR"}, + {0x13c, "SDR_BISTFWR0"}, + {0x140, "SDR_BISTFWR1"}, + {0x180, "SDR_ZQ0CR0"}, + {0x184, "SDR_ZQ0CR1"}, + {0x188, "SDR_ZQ0SR0"}, + {0x18c, "SDR_ZQ0SR1"}, + {0x1c0, "SDR_DX0GCR"}, + {0x1c4, "SDR_DX0GSR0"}, + {0x1c8, "SDR_DX0GSR1"}, + {0x1cc, "SDR_DX0DLLCR"}, + {0x1d0, "SDR_DX0DQTR"}, + {0x1d4, "SDR_DX0DQSTR"}, + {0x200, "SDR_DX1GCR"}, + {0x204, "SDR_DX1GSR0"}, + {0x208, "SDR_DX1GSR1"}, + {0x20c, "SDR_DX1DLLCR"}, + {0x210, "SDR_DX1DQTR"}, + {0x214, "SDR_DX1DQSTR"}, + {0x240, "SDR_DX2GCR"}, + {0x244, "SDR_DX2GSR0"}, + {0x248, "SDR_DX2GSR1"}, + {0x24c, "SDR_DX2DLLCR"}, + {0x250, "SDR_DX2DQTR"}, + {0x254, "SDR_DX2DQSTR"}, + {0x280, "SDR_DX3GCR"}, + {0x284, "SDR_DX3GSR0"}, + {0x288, "SDR_DX3GSR1"}, + {0x28c, "SDR_DX3DLLCR"}, + {0x290, "SDR_DX3DQTR"}, + {0x294, "SDR_DX3DQSTR"}, + {0, NULL} +}; + +static int +sun6i_dram_regs_print(void) +{ + unsigned int clock; + int ret; + + ret = sunxi_dram_clock_read(&clock); + if (ret) + return ret; + + printf("DRAM Clock: %dMHz\n", clock); + + ret = dram_registers_print(SUN6I_IO_DRAMCOM_BASE, + SUN6I_IO_DRAMCOM_SIZE, + &sun6i_dramcom_regs[0], + "DRAM COM", "SDR_COM"); + if (ret) + return ret; + + ret = dram_registers_print(SUN6I_IO_DRAMCTL_BASE, + SUN6I_IO_DRAMCTL_SIZE, + &sun6i_dramctl_regs[0], + "DRAM CTL", "SDR_CTL"); + if (ret) + return ret; + + ret = dram_registers_print(SUN6I_IO_DRAMPHY_BASE, + SUN6I_IO_DRAMPHY_SIZE, + &sun6i_dramphy_regs[0], + "DRAM PHY", "SDR_PHY"); + if (ret) + return ret; + + return 0; +} + +/* + * + */ +static int +sun8i_dram_regs_print(void) +{ + unsigned int clock; + int ret; + + ret = sunxi_dram_clock_read(&clock); + if (ret) + return ret; + + printf("DRAM Clock: %dMHz\n", clock); + + ret = dram_register_range_print(SUN6I_IO_DRAMCOM_BASE, + SUN6I_IO_DRAMCOM_SIZE, + "DRAM COM", "SDR_COM"); + if (ret) + return ret; + + + ret = dram_register_range_print(SUN6I_IO_DRAMCTL_BASE, + SUN6I_IO_DRAMCTL_SIZE, + "DRAM CTL", "SDR_CTL"); + if (ret) + return ret; + + ret = dram_register_range_print(SUN6I_IO_DRAMPHY_BASE, + SUN6I_IO_DRAMPHY_SIZE, + "DRAM PHY", "SDR_PHY"); + if (ret) + return ret; + + return 0; +} + +static void +print_usage(const char *name) +{ + puts("sunxi-meminfo " VERSION "\n"); + printf("Utility to retrieve DRAM information from registers on " + "Allwinner SoCs.\n"); + printf("\n"); + printf("This is part of the sunxi-tools package from the sunxi " + "project. "); + printf("For more \ninformation visit " + "http://linux-sunxi.org/Sunxi-tools.\n"); + printf("\n"); + printf("Usage: %s [OPTION]\n", name); + printf("\n"); + printf("Options:\n"); + printf(" -f: print in FEX format (default).\n"); + printf(" -u: print in sunxi U-Boot dram.c file format.\n"); + printf(" -h: print this usage information.\n"); +} + +int +main(int argc, char *argv[]) +{ + bool uboot; + int ret; + + if (argc == 2) { + if (argv[1][0] == '-') { + if (argv[1][1] == 'f') + uboot = false; + else if (argv[1][1] == 'u') + uboot = true; + else if (argv[1][1] == 'h') + goto help; + else if ((argv[1][1] == '-') && (argv[1][2] == 'h')) + goto help; + else + goto usage; + + if (argv[1][2] != 0) + goto usage; + } else + goto usage; + } else if (argc == 1) + uboot = false; + else + goto usage; + + devmem_fd = open(DEVMEM_FILE, O_RDWR); + if (devmem_fd == -1) { + fprintf(stderr, "Error: failed to open %s: %s\n", DEVMEM_FILE, + strerror(errno)); + return errno; + } + + ret = soc_version_read(); + if (ret) + return ret; + switch (soc_version) { + case SUNXI_SOC_SUN4I: + case SUNXI_SOC_SUN5I: + case SUNXI_SOC_SUN7I: + return sun4i_dram_para_print(uboot); + case SUNXI_SOC_SUN6I: + return sun6i_dram_regs_print(); + case SUNXI_SOC_SUN8I: + return sun8i_dram_regs_print(); + default: + fprintf(stderr, "Error: unknown or unhandled Soc: 0x%04X\n", + soc_version); + return -1; + } + + usage: + fprintf(stderr, "Error: wrong argument(s).\n"); + print_usage(argv[0]); + return EINVAL; + help: + print_usage(argv[0]); + return 0; +} diff --git a/nand-common.h b/nand-common.h new file mode 100644 index 0000000..56767e5 --- /dev/null +++ b/nand-common.h @@ -0,0 +1,29 @@ +/* + * (C) Copyright 2013 + * Patrick H Wood, All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +#include "types.h" + +extern int nand_part_a10 (int argc, char **argv, const char *cmd, int fd, int force); +extern int nand_part_a20 (int argc, char **argv, const char *cmd, int fd, int force); +extern int checkmbrs_a10 (int fd); +extern int checkmbrs_a20 (int fd); +extern void usage (const char *cmd); +extern __u32 calc_crc32(void * buffer, __u32 length); diff --git a/nand-image-builder.c b/nand-image-builder.c new file mode 100644 index 0000000..964a142 --- /dev/null +++ b/nand-image-builder.c @@ -0,0 +1,1111 @@ +/* + * Generic binary BCH encoding/decoding library + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * For the BCH implementation: + * + * Copyright © 2011 Parrot S.A. + * + * Author: Ivan Djelic + * + * See also: + * http://lxr.free-electrons.com/source/lib/bch.c + * + * For the randomizer and image builder implementation: + * + * Copyright © 2016 NextThing Co. + * Copyright © 2016 Free Electrons + * + * Author: Boris Brezillon + * + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "portable_endian.h" + +#if defined(CONFIG_BCH_CONST_PARAMS) +#define GF_M(_p) (CONFIG_BCH_CONST_M) +#define GF_T(_p) (CONFIG_BCH_CONST_T) +#define GF_N(_p) ((1 << (CONFIG_BCH_CONST_M))-1) +#else +#define GF_M(_p) ((_p)->m) +#define GF_T(_p) ((_p)->t) +#define GF_N(_p) ((_p)->n) +#endif + +#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) + +#define BCH_ECC_WORDS(_p) DIV_ROUND_UP(GF_M(_p)*GF_T(_p), 32) +#define BCH_ECC_BYTES(_p) DIV_ROUND_UP(GF_M(_p)*GF_T(_p), 8) + +#ifndef dbg +#define dbg(_fmt, args...) do {} while (0) +#endif + +#define cpu_to_be32 htobe32 +#define kfree free + +#define BCH_PRIMITIVE_POLY 0x5803 + +struct image_info { + int ecc_strength; + int ecc_step_size; + int page_size; + int oob_size; + int usable_page_size; + int eraseblock_size; + int scramble; + int boot0; + off_t offset; + const char *source; + const char *dest; +}; + +/** + * struct bch_control - BCH control structure + * @m: Galois field order + * @n: maximum codeword size in bits (= 2^m-1) + * @t: error correction capability in bits + * @ecc_bits: ecc exact size in bits, i.e. generator polynomial degree (<=m*t) + * @ecc_bytes: ecc max size (m*t bits) in bytes + * @a_pow_tab: Galois field GF(2^m) exponentiation lookup table + * @a_log_tab: Galois field GF(2^m) log lookup table + * @mod8_tab: remainder generator polynomial lookup tables + * @ecc_buf: ecc parity words buffer + * @ecc_buf2: ecc parity words buffer + * @xi_tab: GF(2^m) base for solving degree 2 polynomial roots + * @syn: syndrome buffer + * @cache: log-based polynomial representation buffer + * @elp: error locator polynomial + * @poly_2t: temporary polynomials of degree 2t + */ +struct bch_control { + unsigned int m; + unsigned int n; + unsigned int t; + unsigned int ecc_bits; + unsigned int ecc_bytes; +/* private: */ + uint16_t *a_pow_tab; + uint16_t *a_log_tab; + uint32_t *mod8_tab; + uint32_t *ecc_buf; + uint32_t *ecc_buf2; + unsigned int *xi_tab; + unsigned int *syn; + int *cache; + struct gf_poly *elp; + struct gf_poly *poly_2t[4]; +}; + +static int fls(int x) +{ + int r = 32; + + if (!x) + return 0; + if (!(x & 0xffff0000u)) { + x <<= 16; + r -= 16; + } + if (!(x & 0xff000000u)) { + x <<= 8; + r -= 8; + } + if (!(x & 0xf0000000u)) { + x <<= 4; + r -= 4; + } + if (!(x & 0xc0000000u)) { + x <<= 2; + r -= 2; + } + if (!(x & 0x80000000u)) { + x <<= 1; + r -= 1; + } + return r; +} + +/* + * represent a polynomial over GF(2^m) + */ +struct gf_poly { + unsigned int deg; /* polynomial degree */ + unsigned int c[0]; /* polynomial terms */ +}; + +/* given its degree, compute a polynomial size in bytes */ +#define GF_POLY_SZ(_d) (sizeof(struct gf_poly)+((_d)+1)*sizeof(unsigned int)) + +/* polynomial of degree 1 */ +struct gf_poly_deg1 { + struct gf_poly poly; + unsigned int c[2]; +}; + +/* + * same as encode_bch(), but process input data one byte at a time + */ +static void encode_bch_unaligned(struct bch_control *bch, + const unsigned char *data, unsigned int len, + uint32_t *ecc) +{ + int i; + const uint32_t *p; + const int l = BCH_ECC_WORDS(bch)-1; + + while (len--) { + p = bch->mod8_tab + (l+1)*(((ecc[0] >> 24)^(*data++)) & 0xff); + + for (i = 0; i < l; i++) + ecc[i] = ((ecc[i] << 8)|(ecc[i+1] >> 24))^(*p++); + + ecc[l] = (ecc[l] << 8)^(*p); + } +} + +/* + * convert ecc bytes to aligned, zero-padded 32-bit ecc words + */ +static void load_ecc8(struct bch_control *bch, uint32_t *dst, + const uint8_t *src) +{ + uint8_t pad[4] = {0, 0, 0, 0}; + unsigned int i, nwords = BCH_ECC_WORDS(bch)-1; + + for (i = 0; i < nwords; i++, src += 4) + dst[i] = (src[0] << 24)|(src[1] << 16)|(src[2] << 8)|src[3]; + + memcpy(pad, src, BCH_ECC_BYTES(bch)-4*nwords); + dst[nwords] = (pad[0] << 24)|(pad[1] << 16)|(pad[2] << 8)|pad[3]; +} + +/* + * convert 32-bit ecc words to ecc bytes + */ +static void store_ecc8(struct bch_control *bch, uint8_t *dst, + const uint32_t *src) +{ + uint8_t pad[4]; + unsigned int i, nwords = BCH_ECC_WORDS(bch)-1; + + for (i = 0; i < nwords; i++) { + *dst++ = (src[i] >> 24); + *dst++ = (src[i] >> 16) & 0xff; + *dst++ = (src[i] >> 8) & 0xff; + *dst++ = (src[i] >> 0) & 0xff; + } + pad[0] = (src[nwords] >> 24); + pad[1] = (src[nwords] >> 16) & 0xff; + pad[2] = (src[nwords] >> 8) & 0xff; + pad[3] = (src[nwords] >> 0) & 0xff; + memcpy(dst, pad, BCH_ECC_BYTES(bch)-4*nwords); +} + +/** + * encode_bch - calculate BCH ecc parity of data + * @bch: BCH control structure + * @data: data to encode + * @len: data length in bytes + * @ecc: ecc parity data, must be initialized by caller + * + * The @ecc parity array is used both as input and output parameter, in order to + * allow incremental computations. It should be of the size indicated by member + * @ecc_bytes of @bch, and should be initialized to 0 before the first call. + * + * The exact number of computed ecc parity bits is given by member @ecc_bits of + * @bch; it may be less than m*t for large values of t. + */ +static void encode_bch(struct bch_control *bch, const uint8_t *data, + unsigned int len, uint8_t *ecc) +{ + const unsigned int l = BCH_ECC_WORDS(bch)-1; + unsigned int i, mlen; + unsigned long m; + uint32_t w, r[l+1]; + const uint32_t * const tab0 = bch->mod8_tab; + const uint32_t * const tab1 = tab0 + 256*(l+1); + const uint32_t * const tab2 = tab1 + 256*(l+1); + const uint32_t * const tab3 = tab2 + 256*(l+1); + const uint32_t *pdata, *p0, *p1, *p2, *p3; + + if (ecc) { + /* load ecc parity bytes into internal 32-bit buffer */ + load_ecc8(bch, bch->ecc_buf, ecc); + } else { + memset(bch->ecc_buf, 0, sizeof(r)); + } + + /* process first unaligned data bytes */ + m = ((uintptr_t)data) & 3; + if (m) { + mlen = (len < (4-m)) ? len : 4-m; + encode_bch_unaligned(bch, data, mlen, bch->ecc_buf); + data += mlen; + len -= mlen; + } + + /* process 32-bit aligned data words */ + pdata = (uint32_t *)data; + mlen = len/4; + data += 4*mlen; + len -= 4*mlen; + memcpy(r, bch->ecc_buf, sizeof(r)); + + /* + * split each 32-bit word into 4 polynomials of weight 8 as follows: + * + * 31 ...24 23 ...16 15 ... 8 7 ... 0 + * xxxxxxxx yyyyyyyy zzzzzzzz tttttttt + * tttttttt mod g = r0 (precomputed) + * zzzzzzzz 00000000 mod g = r1 (precomputed) + * yyyyyyyy 00000000 00000000 mod g = r2 (precomputed) + * xxxxxxxx 00000000 00000000 00000000 mod g = r3 (precomputed) + * xxxxxxxx yyyyyyyy zzzzzzzz tttttttt mod g = r0^r1^r2^r3 + */ + while (mlen--) { + /* input data is read in big-endian format */ + w = r[0]^cpu_to_be32(*pdata++); + p0 = tab0 + (l+1)*((w >> 0) & 0xff); + p1 = tab1 + (l+1)*((w >> 8) & 0xff); + p2 = tab2 + (l+1)*((w >> 16) & 0xff); + p3 = tab3 + (l+1)*((w >> 24) & 0xff); + + for (i = 0; i < l; i++) + r[i] = r[i+1]^p0[i]^p1[i]^p2[i]^p3[i]; + + r[l] = p0[l]^p1[l]^p2[l]^p3[l]; + } + memcpy(bch->ecc_buf, r, sizeof(r)); + + /* process last unaligned bytes */ + if (len) + encode_bch_unaligned(bch, data, len, bch->ecc_buf); + + /* store ecc parity bytes into original parity buffer */ + if (ecc) + store_ecc8(bch, ecc, bch->ecc_buf); +} + +static inline int modulo(struct bch_control *bch, unsigned int v) +{ + const unsigned int n = GF_N(bch); + while (v >= n) { + v -= n; + v = (v & n) + (v >> GF_M(bch)); + } + return v; +} + +/* + * shorter and faster modulo function, only works when v < 2N. + */ +static inline int mod_s(struct bch_control *bch, unsigned int v) +{ + const unsigned int n = GF_N(bch); + return (v < n) ? v : v-n; +} + +static inline int deg(unsigned int poly) +{ + /* polynomial degree is the most-significant bit index */ + return fls(poly)-1; +} + +/* Galois field basic operations: multiply, divide, inverse, etc. */ + +static inline unsigned int gf_mul(struct bch_control *bch, unsigned int a, + unsigned int b) +{ + return (a && b) ? bch->a_pow_tab[mod_s(bch, bch->a_log_tab[a]+ + bch->a_log_tab[b])] : 0; +} + +static inline unsigned int gf_sqr(struct bch_control *bch, unsigned int a) +{ + return a ? bch->a_pow_tab[mod_s(bch, 2*bch->a_log_tab[a])] : 0; +} + +static inline unsigned int a_pow(struct bch_control *bch, int i) +{ + return bch->a_pow_tab[modulo(bch, i)]; +} + +static inline int a_log(struct bch_control *bch, unsigned int x) +{ + return bch->a_log_tab[x]; +} + +/* + * generate Galois field lookup tables + */ +static int build_gf_tables(struct bch_control *bch, unsigned int poly) +{ + unsigned int i, x = 1; + const unsigned int k = 1 << deg(poly); + + /* primitive polynomial must be of degree m */ + if (k != (1u << GF_M(bch))) + return -1; + + for (i = 0; i < GF_N(bch); i++) { + bch->a_pow_tab[i] = x; + bch->a_log_tab[x] = i; + if (i && (x == 1)) + /* polynomial is not primitive (a^i=1 with 0a_pow_tab[GF_N(bch)] = 1; + bch->a_log_tab[0] = 0; + + return 0; +} + +/* + * compute generator polynomial remainder tables for fast encoding + */ +static void build_mod8_tables(struct bch_control *bch, const uint32_t *g) +{ + int i, j, b, d; + uint32_t data, hi, lo, *tab; + const int l = BCH_ECC_WORDS(bch); + const int plen = DIV_ROUND_UP(bch->ecc_bits+1, 32); + const int ecclen = DIV_ROUND_UP(bch->ecc_bits, 32); + + memset(bch->mod8_tab, 0, 4*256*l*sizeof(*bch->mod8_tab)); + + for (i = 0; i < 256; i++) { + /* p(X)=i is a small polynomial of weight <= 8 */ + for (b = 0; b < 4; b++) { + /* we want to compute (p(X).X^(8*b+deg(g))) mod g(X) */ + tab = bch->mod8_tab + (b*256+i)*l; + data = i << (8*b); + while (data) { + d = deg(data); + /* subtract X^d.g(X) from p(X).X^(8*b+deg(g)) */ + data ^= g[0] >> (31-d); + for (j = 0; j < ecclen; j++) { + hi = (d < 31) ? g[j] << (d+1) : 0; + lo = (j+1 < plen) ? + g[j+1] >> (31-d) : 0; + tab[j] ^= hi|lo; + } + } + } + } +} + +/* + * build a base for factoring degree 2 polynomials + */ +static int build_deg2_base(struct bch_control *bch) +{ + const int m = GF_M(bch); + int i, j, r; + unsigned int sum, x, y, remaining, ak = 0, xi[m]; + + /* find k s.t. Tr(a^k) = 1 and 0 <= k < m */ + for (i = 0; i < m; i++) { + for (j = 0, sum = 0; j < m; j++) + sum ^= a_pow(bch, i*(1 << j)); + + if (sum) { + ak = bch->a_pow_tab[i]; + break; + } + } + /* find xi, i=0..m-1 such that xi^2+xi = a^i+Tr(a^i).a^k */ + remaining = m; + memset(xi, 0, sizeof(xi)); + + for (x = 0; (x <= GF_N(bch)) && remaining; x++) { + y = gf_sqr(bch, x)^x; + for (i = 0; i < 2; i++) { + r = a_log(bch, y); + if (y && (r < m) && !xi[r]) { + bch->xi_tab[r] = x; + xi[r] = 1; + remaining--; + dbg("x%d = %x\n", r, x); + break; + } + y ^= ak; + } + } + /* should not happen but check anyway */ + return remaining ? -1 : 0; +} + +static void *bch_alloc(size_t size, int *err) +{ + void *ptr; + + ptr = malloc(size); + if (ptr == NULL) + *err = 1; + return ptr; +} + +/* + * compute generator polynomial for given (m,t) parameters. + */ +static uint32_t *compute_generator_polynomial(struct bch_control *bch) +{ + const unsigned int m = GF_M(bch); + const unsigned int t = GF_T(bch); + int n, err = 0; + unsigned int i, j, nbits, r, word, *roots; + struct gf_poly *g; + uint32_t *genpoly; + + g = bch_alloc(GF_POLY_SZ(m*t), &err); + roots = bch_alloc((bch->n+1)*sizeof(*roots), &err); + genpoly = bch_alloc(DIV_ROUND_UP(m*t+1, 32)*sizeof(*genpoly), &err); + + if (err) { + kfree(genpoly); + genpoly = NULL; + goto finish; + } + + /* enumerate all roots of g(X) */ + memset(roots , 0, (bch->n+1)*sizeof(*roots)); + for (i = 0; i < t; i++) { + for (j = 0, r = 2*i+1; j < m; j++) { + roots[r] = 1; + r = mod_s(bch, 2*r); + } + } + /* build generator polynomial g(X) */ + g->deg = 0; + g->c[0] = 1; + for (i = 0; i < GF_N(bch); i++) { + if (roots[i]) { + /* multiply g(X) by (X+root) */ + r = bch->a_pow_tab[i]; + g->c[g->deg+1] = 1; + for (j = g->deg; j > 0; j--) + g->c[j] = gf_mul(bch, g->c[j], r)^g->c[j-1]; + + g->c[0] = gf_mul(bch, g->c[0], r); + g->deg++; + } + } + /* store left-justified binary representation of g(X) */ + n = g->deg+1; + i = 0; + + while (n > 0) { + nbits = (n > 32) ? 32 : n; + for (j = 0, word = 0; j < nbits; j++) { + if (g->c[n-1-j]) + word |= 1u << (31-j); + } + genpoly[i++] = word; + n -= nbits; + } + bch->ecc_bits = g->deg; + +finish: + kfree(g); + kfree(roots); + + return genpoly; +} + +/** + * free_bch - free the BCH control structure + * @bch: BCH control structure to release + */ +static void free_bch(struct bch_control *bch) +{ + unsigned int i; + + if (bch) { + kfree(bch->a_pow_tab); + kfree(bch->a_log_tab); + kfree(bch->mod8_tab); + kfree(bch->ecc_buf); + kfree(bch->ecc_buf2); + kfree(bch->xi_tab); + kfree(bch->syn); + kfree(bch->cache); + kfree(bch->elp); + + for (i = 0; i < ARRAY_SIZE(bch->poly_2t); i++) + kfree(bch->poly_2t[i]); + + kfree(bch); + } +} + +/** + * init_bch - initialize a BCH encoder/decoder + * @m: Galois field order, should be in the range 5-15 + * @t: maximum error correction capability, in bits + * @prim_poly: user-provided primitive polynomial (or 0 to use default) + * + * Returns: + * a newly allocated BCH control structure if successful, NULL otherwise + * + * This initialization can take some time, as lookup tables are built for fast + * encoding/decoding; make sure not to call this function from a time critical + * path. Usually, init_bch() should be called on module/driver init and + * free_bch() should be called to release memory on exit. + * + * You may provide your own primitive polynomial of degree @m in argument + * @prim_poly, or let init_bch() use its default polynomial. + * + * Once init_bch() has successfully returned a pointer to a newly allocated + * BCH control structure, ecc length in bytes is given by member @ecc_bytes of + * the structure. + */ +static struct bch_control *init_bch(int m, int t, unsigned int prim_poly) +{ + int err = 0; + unsigned int i, words; + uint32_t *genpoly; + struct bch_control *bch = NULL; + + const int min_m = 5; + const int max_m = 15; + + /* default primitive polynomials */ + static const unsigned int prim_poly_tab[] = { + 0x25, 0x43, 0x83, 0x11d, 0x211, 0x409, 0x805, 0x1053, 0x201b, + 0x402b, 0x8003, + }; + +#if defined(CONFIG_BCH_CONST_PARAMS) + if ((m != (CONFIG_BCH_CONST_M)) || (t != (CONFIG_BCH_CONST_T))) { + printk(KERN_ERR "bch encoder/decoder was configured to support " + "parameters m=%d, t=%d only!\n", + CONFIG_BCH_CONST_M, CONFIG_BCH_CONST_T); + goto fail; + } +#endif + if ((m < min_m) || (m > max_m)) + /* + * values of m greater than 15 are not currently supported; + * supporting m > 15 would require changing table base type + * (uint16_t) and a small patch in matrix transposition + */ + goto fail; + + /* sanity checks */ + if ((t < 1) || (m*t >= ((1 << m)-1))) + /* invalid t value */ + goto fail; + + /* select a primitive polynomial for generating GF(2^m) */ + if (prim_poly == 0) + prim_poly = prim_poly_tab[m-min_m]; + + bch = malloc(sizeof(*bch)); + if (bch == NULL) + goto fail; + + memset(bch, 0, sizeof(*bch)); + + bch->m = m; + bch->t = t; + bch->n = (1 << m)-1; + words = DIV_ROUND_UP(m*t, 32); + bch->ecc_bytes = DIV_ROUND_UP(m*t, 8); + bch->a_pow_tab = bch_alloc((1+bch->n)*sizeof(*bch->a_pow_tab), &err); + bch->a_log_tab = bch_alloc((1+bch->n)*sizeof(*bch->a_log_tab), &err); + bch->mod8_tab = bch_alloc(words*1024*sizeof(*bch->mod8_tab), &err); + bch->ecc_buf = bch_alloc(words*sizeof(*bch->ecc_buf), &err); + bch->ecc_buf2 = bch_alloc(words*sizeof(*bch->ecc_buf2), &err); + bch->xi_tab = bch_alloc(m*sizeof(*bch->xi_tab), &err); + bch->syn = bch_alloc(2*t*sizeof(*bch->syn), &err); + bch->cache = bch_alloc(2*t*sizeof(*bch->cache), &err); + bch->elp = bch_alloc((t+1)*sizeof(struct gf_poly_deg1), &err); + + for (i = 0; i < ARRAY_SIZE(bch->poly_2t); i++) + bch->poly_2t[i] = bch_alloc(GF_POLY_SZ(2*t), &err); + + if (err) + goto fail; + + err = build_gf_tables(bch, prim_poly); + if (err) + goto fail; + + /* use generator polynomial for computing encoding tables */ + genpoly = compute_generator_polynomial(bch); + if (genpoly == NULL) + goto fail; + + build_mod8_tables(bch, genpoly); + kfree(genpoly); + + err = build_deg2_base(bch); + if (err) + goto fail; + + return bch; + +fail: + free_bch(bch); + return NULL; +} + +static void swap_bits(uint8_t *buf, int len) +{ + int i, j; + + for (j = 0; j < len; j++) { + uint8_t byte = buf[j]; + + buf[j] = 0; + for (i = 0; i < 8; i++) { + if (byte & (1 << i)) + buf[j] |= (1 << (7 - i)); + } + } +} + +static uint16_t lfsr_step(uint16_t state, int count) +{ + state &= 0x7fff; + while (count--) + state = ((state >> 1) | + ((((state >> 0) ^ (state >> 1)) & 1) << 14)) & 0x7fff; + + return state; +} + +static uint16_t default_scrambler_seeds[] = { + 0x2b75, 0x0bd0, 0x5ca3, 0x62d1, 0x1c93, 0x07e9, 0x2162, 0x3a72, + 0x0d67, 0x67f9, 0x1be7, 0x077d, 0x032f, 0x0dac, 0x2716, 0x2436, + 0x7922, 0x1510, 0x3860, 0x5287, 0x480f, 0x4252, 0x1789, 0x5a2d, + 0x2a49, 0x5e10, 0x437f, 0x4b4e, 0x2f45, 0x216e, 0x5cb7, 0x7130, + 0x2a3f, 0x60e4, 0x4dc9, 0x0ef0, 0x0f52, 0x1bb9, 0x6211, 0x7a56, + 0x226d, 0x4ea7, 0x6f36, 0x3692, 0x38bf, 0x0c62, 0x05eb, 0x4c55, + 0x60f4, 0x728c, 0x3b6f, 0x2037, 0x7f69, 0x0936, 0x651a, 0x4ceb, + 0x6218, 0x79f3, 0x383f, 0x18d9, 0x4f05, 0x5c82, 0x2912, 0x6f17, + 0x6856, 0x5938, 0x1007, 0x61ab, 0x3e7f, 0x57c2, 0x542f, 0x4f62, + 0x7454, 0x2eac, 0x7739, 0x42d4, 0x2f90, 0x435a, 0x2e52, 0x2064, + 0x637c, 0x66ad, 0x2c90, 0x0bad, 0x759c, 0x0029, 0x0986, 0x7126, + 0x1ca7, 0x1605, 0x386a, 0x27f5, 0x1380, 0x6d75, 0x24c3, 0x0f8e, + 0x2b7a, 0x1418, 0x1fd1, 0x7dc1, 0x2d8e, 0x43af, 0x2267, 0x7da3, + 0x4e3d, 0x1338, 0x50db, 0x454d, 0x764d, 0x40a3, 0x42e6, 0x262b, + 0x2d2e, 0x1aea, 0x2e17, 0x173d, 0x3a6e, 0x71bf, 0x25f9, 0x0a5d, + 0x7c57, 0x0fbe, 0x46ce, 0x4939, 0x6b17, 0x37bb, 0x3e91, 0x76db, +}; + +static uint16_t brom_scrambler_seeds[] = { 0x4a80 }; + +static void scramble(const struct image_info *info, + int page, uint8_t *data, int datalen) +{ + uint16_t state; + int i; + + /* Boot0 is always scrambled no matter the command line option. */ + if (info->boot0) { + state = brom_scrambler_seeds[0]; + } else { + unsigned seedmod = info->eraseblock_size / info->page_size; + + /* Bail out earlier if the user didn't ask for scrambling. */ + if (!info->scramble) + return; + + if (seedmod > ARRAY_SIZE(default_scrambler_seeds)) + seedmod = ARRAY_SIZE(default_scrambler_seeds); + + state = default_scrambler_seeds[page % seedmod]; + } + + /* Prepare the initial state... */ + state = lfsr_step(state, 15); + + /* and start scrambling data. */ + for (i = 0; i < datalen; i++) { + data[i] ^= state; + state = lfsr_step(state, 8); + } +} + +static int write_page(const struct image_info *info, uint8_t *buffer, + FILE *src, FILE *rnd, FILE *dst, + struct bch_control *bch, int page) +{ + int steps = info->usable_page_size / info->ecc_step_size; + int eccbytes = DIV_ROUND_UP(info->ecc_strength * 14, 8); + off_t pos = ftell(dst); + size_t pad, cnt; + int i; + + if (eccbytes % 2) + eccbytes++; + + memset(buffer, 0xff, info->page_size + info->oob_size); + cnt = fread(buffer, 1, info->usable_page_size, src); + if (!cnt) { + if (!feof(src)) { + fprintf(stderr, + "Failed to read data from the source\n"); + return -1; + } else { + return 0; + } + } + + fwrite(buffer, info->page_size + info->oob_size, 1, dst); + + for (i = 0; i < info->usable_page_size; i++) { + if (buffer[i] != 0xff) + break; + } + + /* We leave empty pages at 0xff. */ + if (i == info->usable_page_size) + return 0; + + /* Restore the source pointer to read it again. */ + fseek(src, -cnt, SEEK_CUR); + + /* Randomize unused space if scrambling is required. */ + if (info->scramble) { + int offs; + + if (info->boot0) { + offs = steps * (info->ecc_step_size + eccbytes + 4); + cnt = info->page_size + info->oob_size - offs; + fread(buffer + offs, 1, cnt, rnd); + } else { + offs = info->page_size + (steps * (eccbytes + 4)); + cnt = info->page_size + info->oob_size - offs; + memset(buffer + offs, 0xff, cnt); + scramble(info, page, buffer + offs, cnt); + } + fseek(dst, pos + offs, SEEK_SET); + fwrite(buffer + offs, cnt, 1, dst); + } + + for (i = 0; i < steps; i++) { + int ecc_offs, data_offs; + uint8_t *ecc; + + memset(buffer, 0xff, info->ecc_step_size + eccbytes + 4); + ecc = buffer + info->ecc_step_size + 4; + if (info->boot0) { + data_offs = i * (info->ecc_step_size + eccbytes + 4); + ecc_offs = data_offs + info->ecc_step_size + 4; + } else { + data_offs = i * info->ecc_step_size; + ecc_offs = info->page_size + 4 + (i * (eccbytes + 4)); + } + + cnt = fread(buffer, 1, info->ecc_step_size, src); + if (!cnt && !feof(src)) { + fprintf(stderr, + "Failed to read data from the source\n"); + return -1; + } + + pad = info->ecc_step_size - cnt; + if (pad) { + if (info->scramble && info->boot0) + fread(buffer + cnt, 1, pad, rnd); + else + memset(buffer + cnt, 0xff, pad); + } + + memset(ecc, 0, eccbytes); + swap_bits(buffer, info->ecc_step_size + 4); + encode_bch(bch, buffer, info->ecc_step_size + 4, ecc); + swap_bits(buffer, info->ecc_step_size + 4); + swap_bits(ecc, eccbytes); + scramble(info, page, buffer, info->ecc_step_size + 4 + eccbytes); + + fseek(dst, pos + data_offs, SEEK_SET); + fwrite(buffer, info->ecc_step_size, 1, dst); + fseek(dst, pos + ecc_offs - 4, SEEK_SET); + fwrite(ecc - 4, eccbytes + 4, 1, dst); + } + + /* Fix BBM. */ + fseek(dst, pos + info->page_size, SEEK_SET); + memset(buffer, 0xff, 2); + fwrite(buffer, 2, 1, dst); + + /* Make dst pointer point to the next page. */ + fseek(dst, pos + info->page_size + info->oob_size, SEEK_SET); + + return 0; +} + +static int create_image(const struct image_info *info) +{ + off_t page = info->offset / info->page_size; + struct bch_control *bch; + FILE *src, *dst, *rnd; + uint8_t *buffer; + + bch = init_bch(14, info->ecc_strength, BCH_PRIMITIVE_POLY); + if (!bch) { + fprintf(stderr, "Failed to init the BCH engine\n"); + return -1; + } + + buffer = malloc(info->page_size + info->oob_size); + if (!buffer) { + fprintf(stderr, "Failed to allocate the NAND page buffer\n"); + return -1; + } + + memset(buffer, 0xff, info->page_size + info->oob_size); + + src = fopen(info->source, "r"); + if (!src) { + fprintf(stderr, "Failed to open source file (%s)\n", + info->source); + return -1; + } + + dst = fopen(info->dest, "w"); + if (!dst) { + fprintf(stderr, "Failed to open dest file (%s)\n", info->dest); + return -1; + } + + rnd = fopen("/dev/urandom", "r"); + if (!rnd) { + fprintf(stderr, "Failed to open /dev/urandom\n"); + return -1; + } + + while (!feof(src)) { + int ret; + + ret = write_page(info, buffer, src, rnd, dst, bch, page++); + if (ret) + return ret; + } + + return 0; +} + +static void display_help(int status) +{ + fprintf(status == EXIT_SUCCESS ? stdout : stderr, + "sunxi-nand-image-builder %s\n" + "\n" + "Usage: sunxi-nand-image-builder [OPTIONS] source-image output-image\n" + "\n" + "Creates a raw NAND image that can be read by the sunxi NAND controller.\n" + "\n" + "-h --help Display this help and exit\n" + "-c / --ecc=/ ECC config (strength/step-size)\n" + "-p --page= Page size\n" + "-o --oob= OOB size\n" + "-u --usable= Usable page size\n" + "-e --eraseblock= Erase block size\n" + "-b --boot0 Build a boot0 image.\n" + "-s --scramble Scramble data\n" + "-a --address= Where the image will be programmed.\n" + "\n" + "Notes:\n" + "All the information you need to pass to this tool should be part of\n" + "the NAND datasheet.\n" + "\n" + "The NAND controller only supports the following ECC configs\n" + " Valid ECC strengths: 16, 24, 28, 32, 40, 48, 56, 60 and 64\n" + " Valid ECC step size: 512 and 1024\n" + "\n" + "If you are building a boot0 image, you'll have specify extra options.\n" + "These options should be chosen based on the layouts described here:\n" + " http://linux-sunxi.org/NAND#More_information_on_BROM_NAND\n" + "\n" + " --usable should be assigned the 'Hardware page' value\n" + " --ecc should be assigned the 'ECC capacity'/'ECC page' values\n" + " --usable should be smaller than --page\n" + "\n" + "The --address option is only required for non-boot0 images that are \n" + "meant to be programmed at a non eraseblock aligned offset.\n" + "\n" + "Examples:\n" + " The H27UCG8T2BTR-BC NAND exposes\n" + " * 16k pages\n" + " * 1280 OOB bytes per page\n" + " * 4M eraseblocks\n" + " * requires data scrambling\n" + " * expects a minimum ECC of 40bits/1024bytes\n" + "\n" + " A normal image can be generated with\n" + " sunxi-nand-image-builder -p 16384 -o 1280 -e 0x400000 -s -c 40/1024\n" + " A boot0 image can be generated with\n" + " sunxi-nand-image-builder -p 16384 -o 1280 -e 0x400000 -s -b -u 4096 -c 64/1024\n", + VERSION); + exit(status); +} + +static int check_image_info(struct image_info *info) +{ + static int valid_ecc_strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 }; + int eccbytes, eccsteps; + unsigned i; + + if (!info->page_size) { + fprintf(stderr, "--page is missing\n"); + return -EINVAL; + } + + if (!info->page_size) { + fprintf(stderr, "--oob is missing\n"); + return -EINVAL; + } + + if (!info->eraseblock_size) { + fprintf(stderr, "--eraseblock is missing\n"); + return -EINVAL; + } + + if (info->ecc_step_size != 512 && info->ecc_step_size != 1024) { + fprintf(stderr, "Invalid ECC step argument: %d\n", + info->ecc_step_size); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(valid_ecc_strengths); i++) { + if (valid_ecc_strengths[i] == info->ecc_strength) + break; + } + + if (i == ARRAY_SIZE(valid_ecc_strengths)) { + fprintf(stderr, "Invalid ECC strength argument: %d\n", + info->ecc_strength); + return -EINVAL; + } + + eccbytes = DIV_ROUND_UP(info->ecc_strength * 14, 8); + if (eccbytes % 2) + eccbytes++; + eccbytes += 4; + + eccsteps = info->usable_page_size / info->ecc_step_size; + + if (info->page_size + info->oob_size < + info->usable_page_size + (eccsteps * eccbytes)) { + fprintf(stderr, + "ECC bytes do not fit in the NAND page, choose a weaker ECC\n"); + return -EINVAL; + } + + return 0; +} + +int main(int argc, char **argv) +{ + struct image_info info; + + memset(&info, 0, sizeof(info)); + /* + * Process user arguments + */ + for (;;) { + int option_index = 0; + char *endptr = NULL; + static const struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"ecc", required_argument, 0, 'c'}, + {"page", required_argument, 0, 'p'}, + {"oob", required_argument, 0, 'o'}, + {"usable", required_argument, 0, 'u'}, + {"eraseblock", required_argument, 0, 'e'}, + {"boot0", no_argument, 0, 'b'}, + {"scramble", no_argument, 0, 's'}, + {"address", required_argument, 0, 'a'}, + {0, 0, 0, 0}, + }; + + int c = getopt_long(argc, argv, "c:p:o:u:e:ba:sh", + long_options, &option_index); + if (c == EOF) + break; + + switch (c) { + case 'h': + display_help(0); + break; + case 's': + info.scramble = 1; + break; + case 'c': + info.ecc_strength = strtol(optarg, &endptr, 0); + if (endptr || *endptr == '/') + info.ecc_step_size = strtol(endptr + 1, NULL, 0); + break; + case 'p': + info.page_size = strtol(optarg, NULL, 0); + break; + case 'o': + info.oob_size = strtol(optarg, NULL, 0); + break; + case 'u': + info.usable_page_size = strtol(optarg, NULL, 0); + break; + case 'e': + info.eraseblock_size = strtol(optarg, NULL, 0); + break; + case 'b': + info.boot0 = 1; + break; + case 'a': + info.offset = strtoull(optarg, NULL, 0); + break; + case '?': + display_help(-1); + break; + } + } + + if ((argc - optind) != 2) + display_help(-1); + + info.source = argv[optind]; + info.dest = argv[optind + 1]; + + if (!info.boot0) { + info.usable_page_size = info.page_size; + } else if (!info.usable_page_size) { + if (info.page_size > 8192) + info.usable_page_size = 8192; + else if (info.page_size > 4096) + info.usable_page_size = 4096; + else + info.usable_page_size = 1024; + } + + if (check_image_info(&info)) + display_help(-1); + + return create_image(&info); +} diff --git a/nand-part-a10.h b/nand-part-a10.h new file mode 100644 index 0000000..b4cfe3d --- /dev/null +++ b/nand-part-a10.h @@ -0,0 +1,81 @@ +/* + * drivers/block/sun4i_nand/nfd/mbr.h + * + * (C) Copyright 2007-2012 + * Allwinner Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __MBR_H__ +#define __MBR_H__ + +#include "types.h" + +#define MBR_MAGIC "softw311" +#define MBR_VERSION 0x100 +#define nand_part nand_part_a10 +#define checkmbrs checkmbrs_a10 + +#define MAX_PART_COUNT 15 //max part count +#define MBR_COPY_NUM 4 //mbr backup count + +#define MBR_START_ADDRESS 0x0 //mbr start address +#define MBR_SIZE 1024 //mbr size +#define MBR_RESERVED (MBR_SIZE - 20 - (MAX_PART_COUNT * 64)) //mbr reserved space + +// extern struct __NandDriverGlobal_t NandDriverInfo; + +// extern struct __NandStorageInfo_t NandStorageInfo; + +#define DiskSize (SECTOR_CNT_OF_SINGLE_PAGE * PAGE_CNT_OF_PHY_BLK * BLOCK_CNT_OF_DIE * \ + DIE_CNT_OF_CHIP * NandStorageInfo.ChipCnt / 1024 * DATA_BLK_CNT_OF_ZONE) + + +struct nand_disk{ + unsigned long size; + unsigned long offset; + unsigned char type; +}; + +/* part info */ +typedef struct tag_PARTITION{ + __u32 addrhi; //start address high 32 bit + __u32 addrlo; //start address low 32 bit + __u32 lenhi; //size high 32 bit + __u32 lenlo; //size low 32 bit + __u8 classname[12]; //major device name + __u8 name[12]; //minor device name + unsigned int user_type; //标志当前盘符所属于的用户 + unsigned int ro; //标志当前盘符的读写属性 + __u8 res[16]; //reserved +}PARTITION; + +/* mbr info */ +typedef struct tag_MBR{ + __u32 crc32; // crc, from byte 4 to mbr tail + __u32 version; // version + __u8 magic[8]; // magic number + __u8 copy; // mbr backup count + __u8 index; // current part no + __u16 PartCount; // part counter + PARTITION array[MAX_PART_COUNT];// part info + __u8 res[MBR_RESERVED]; // reserved space +}MBR; + +int mbr2disks(struct nand_disk* disk_array); + +#endif //__MBR_H__ diff --git a/nand-part-a20.h b/nand-part-a20.h new file mode 100644 index 0000000..e603ceb --- /dev/null +++ b/nand-part-a20.h @@ -0,0 +1,83 @@ +/* + * drivers/block/sun4i_nand/nfd/mbr.h + * + * (C) Copyright 2007-2012 + * Allwinner Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __MBR_H__ +#define __MBR_H__ + +#include "types.h" + +#define MBR_MAGIC "softw411" +#define MBR_VERSION 0x200 +#define nand_part nand_part_a20 +#define checkmbrs checkmbrs_a20 + +#define MAX_PART_COUNT 120 //max part count +#define MBR_COPY_NUM 4 //mbr backup count + +#define MBR_START_ADDRESS 0x0 //mbr start address +#define MBR_SIZE 1024*16 //mbr size +#define MBR_RESERVED (MBR_SIZE - 32 - (MAX_PART_COUNT * 128)) //mbr reserved space + +// extern struct __NandDriverGlobal_t NandDriverInfo; + +// extern struct __NandStorageInfo_t NandStorageInfo; + +#define DiskSize (SECTOR_CNT_OF_SINGLE_PAGE * PAGE_CNT_OF_PHY_BLK * BLOCK_CNT_OF_DIE * \ + DIE_CNT_OF_CHIP * NandStorageInfo.ChipCnt / 1024 * DATA_BLK_CNT_OF_ZONE) + + +struct nand_disk{ + unsigned long size; + unsigned long offset; + unsigned char type; +}; + +/* part info */ +typedef struct nand_tag_PARTITION{ + unsigned int addrhi; //起始地址, 以扇区为单位 + unsigned int addrlo; // + unsigned int lenhi; //长度 + unsigned int lenlo; // + unsigned char classname[16]; //次设备名 + unsigned char name[16]; //主设备名 + unsigned int user_type; //用户类型 + unsigned int keydata; //关键数据,要求量产不丢失 + unsigned int ro; //读写属性 + unsigned char res[68]; //保留数据,匹配分区信息128字节 +}__attribute__ ((packed))PARTITION; + +/* mbr info */ +typedef struct nand_tag_MBR{ + unsigned int crc32; // crc 1k - 4 + unsigned int version; // 版本信息, 0x00000100 + unsigned char magic[8]; //"softw411" + unsigned int copy; //分数 + unsigned int index; //第几个MBR备份 + unsigned int PartCount; //分区个数 + unsigned int stamp[1]; //对齐 + PARTITION array[MAX_PART_COUNT]; // + unsigned char res[MBR_RESERVED]; +}__attribute__ ((packed)) MBR; + +int mbr2disks(struct nand_disk* disk_array); + +#endif //__MBR_H__ diff --git a/nand-part-main.c b/nand-part-main.c new file mode 100644 index 0000000..18d10f2 --- /dev/null +++ b/nand-part-main.c @@ -0,0 +1,117 @@ +/* + * (C) Copyright 2013 + * Patrick H Wood, All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "nand-common.h" +#include "common.h" + +void usage(const char *cmd) +{ + puts("sunxi-nand-part " VERSION "\n"); + printf("usage: %s [-f a10|a20] nand-device\n", cmd); + printf(" %s nand-device 'name2 len2 [usertype2]' ['name3 len3 [usertype3]'] ...\n", cmd); + printf(" %s [-f a10|a20] nand-device start1 'name1 len1 [usertype1]' ['name2 len2 [usertype2]'] ...\n", cmd); +} + +typedef struct tag_CRC32_DATA +{ + __u32 CRC; //int的大小是32位 + __u32 CRC_32_Tbl[256]; //用来保存码表 +}CRC32_DATA_t; + +__u32 calc_crc32(void * buffer, __u32 length) +{ + __u32 i, j; + CRC32_DATA_t crc32; // + __u32 CRC32 = 0xffffffff; //设置初始值 + crc32.CRC = 0; + + for( i = 0; i < 256; ++i)//用++i以提高效率 + { + crc32.CRC = i; + for( j = 0; j < 8 ; ++j) + { + //这个循环实际上就是用"计算法"来求取CRC的校验码 + if(crc32.CRC & 1) + crc32.CRC = (crc32.CRC >> 1) ^ 0xEDB88320; + else //0xEDB88320就是CRC-32多项表达式的值 + crc32.CRC >>= 1; + } + crc32.CRC_32_Tbl[i] = crc32.CRC; + } + + CRC32 = 0xffffffff; //设置初始值 + for( i = 0; i < length; ++i) + { + CRC32 = crc32.CRC_32_Tbl[(CRC32^((unsigned char*)buffer)[i]) & 0xff] ^ (CRC32>>8); + } + //return CRC32; + return CRC32^0xffffffff; +} + +int main (int argc, char **argv) +{ + char *nand = "/dev/nand"; + const char *cmd = argv[0]; + int fd; + int force = 0; // force write even if magics and CRCs don't match + + argc--; + argv++; + + if (argc > 1) { + if (!strcmp(argv[0], "-f")) { + if (!strcasecmp(argv[1], "a10")) + force = 10; + else if (!strcasecmp(argv[1], "a20")) + force = 20; + else { + usage(cmd); + return -1; + } + argc -= 2; + argv += 2; + } + } + + if (argc > 0) { + nand = argv[0]; + argc--; + argv++; + } + fd = open(nand, O_RDWR); + if (fd < 0) { + usage(cmd); + return -2; + } + if (force == 10) + return nand_part_a10 (argc, argv, cmd, fd, force); + if (force == 20) + return nand_part_a20 (argc, argv, cmd, fd, force); + + if (checkmbrs_a10(fd)) + return nand_part_a10 (argc, argv, cmd, fd, force); + if (checkmbrs_a20(fd)) + return nand_part_a20 (argc, argv, cmd, fd, force); +} diff --git a/nand-part.c b/nand-part.c new file mode 100644 index 0000000..af2169d --- /dev/null +++ b/nand-part.c @@ -0,0 +1,327 @@ +/* + * mbr.c + * (C) Copyright 2012 + * Patrick H Wood, All rights reserved. + * Heavily modified from the Allwinner file drivers/block/sun4i_nand/nfd/mbr.c. + * (Allwinner copyright block retained below.) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +/* + * drivers/block/sun4i_nand/nfd/mbr.c + * (C) Copyright 2007-2012 + * Allwinner Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef __linux__ +# include +# include /* BLKRRPART */ +#endif +#include "nand-common.h" + +// so far, only known formats are for A10 and A20 +#if defined(A10) +# include "nand-part-a10.h" +#elif defined(A20) +# include "nand-part-a20.h" +#endif + +#define MAX_NAME 16 + +static void printmbrheader(MBR *mbr) +{ + printf("mbr: version 0x%08x, magic %8.8s\n", mbr->version, mbr->magic); +} + +static MBR *_get_mbr(int fd, int mbr_num, int force) +{ + MBR *mbr; + + /*request mbr space*/ + mbr = malloc(sizeof(MBR)); + if(mbr == NULL) + { + printf("%s : request memory fail\n",__FUNCTION__); + return NULL; + } + + /*get mbr from nand device*/ + lseek(fd,MBR_START_ADDRESS + MBR_SIZE*mbr_num,SEEK_SET); + if(read(fd,mbr,MBR_SIZE) == MBR_SIZE) + { + /*checksum*/ + printf("check partition table copy %d: ", mbr_num); + printmbrheader(mbr); + if (force) { + memcpy(mbr->magic, MBR_MAGIC, 8); + mbr->version = MBR_VERSION; + return mbr; + } + if(strncmp((char *)mbr->magic, MBR_MAGIC, 8)) + { + printf("magic %8.8s is not %8s\n", mbr->magic, MBR_MAGIC); + return NULL; + } + if(mbr->version != MBR_VERSION) + { + printf("version 0x%08x is not 0x%08x\n", mbr->version, MBR_VERSION); + return NULL; + } + if(*(__u32 *)mbr == calc_crc32((__u32 *)mbr + 1,MBR_SIZE - 4)) + { + printf("OK\n"); + return mbr; + } + printf("BAD!\n"); + } + return NULL; +} + +static __s32 _free_mbr(MBR *mbr) +{ + if(mbr) + { + free(mbr); + mbr = 0; + } + + return 0; +} + +static void printmbr(MBR *mbr) +{ + unsigned int part_cnt; + + printmbrheader(mbr); + printf("%d partitions\n", mbr->PartCount); + for(part_cnt = 0; part_cnt < mbr->PartCount && part_cnt < MAX_PART_COUNT; part_cnt++) + { + printf("partition %2d: class = %12s, name = %12s, partition start = %8d, partition size = %8d user_type=%d\n", + part_cnt + 1, + mbr->array[part_cnt].classname, + mbr->array[part_cnt].name, + mbr->array[part_cnt].addrlo, + mbr->array[part_cnt].lenlo, + mbr->array[part_cnt].user_type); + } +} +int checkmbrs(int fd) +{ + int i; + MBR *mbrs[MBR_COPY_NUM]; + MBR *mbr = NULL; + + memset((void *) mbrs, 0, sizeof(mbrs)); + for (i = 0; i < MBR_COPY_NUM; i++) { + mbrs[i] = _get_mbr(fd, i, 0); + if (mbrs[i]) + mbr = mbrs[i]; + } + if (!mbr) { + printf("all partition tables are bad!\n"); + for (i = 0; i < MBR_COPY_NUM; i++) { + if (mbrs[i]) + _free_mbr(mbrs[i]); + } + return 0; + } + + printmbr(mbr); + for (i = 0; i < MBR_COPY_NUM; i++) { + if (mbrs[i]) + _free_mbr(mbrs[i]); + } + return 1; +} + +static int writembrs(int fd, char names[][MAX_NAME], __u32 start, __u32 *lens, unsigned int *user_types, int nparts, int partoffset, int force) +{ + unsigned int part_cnt = 0; + int i; + char yn = 'n'; + MBR *mbrs[MBR_COPY_NUM]; + MBR *mbr = NULL; + FILE *backup; + + memset((void *) mbrs, 0, sizeof(mbrs)); + for (i = 0; i < MBR_COPY_NUM; i++) { + mbrs[i] = _get_mbr(fd, i, force); + if (mbrs[i]) + mbr = mbrs[i]; + } + if (!mbr) { + printf("all partition tables are bad!\n"); + for (i = 0; i < MBR_COPY_NUM; i++) { + if (mbrs[i]) + _free_mbr(mbrs[i]); + } + return 0; + } + // back up mbr data + backup = fopen("nand_mbr.backup", "w"); + if (!backup) { + printf("can't open nand_mbr.backup to back up mbr data\n"); + for (i = 0; i < MBR_COPY_NUM; i++) { + if (mbrs[i]) + _free_mbr(mbrs[i]); + } + return 0; + } + + fprintf(backup, "%d ", mbr->array[0].addrlo); + for(part_cnt = 0; part_cnt < mbr->PartCount && part_cnt < MAX_PART_COUNT; part_cnt++) + { + fprintf(backup, "'%s %d %d' ", mbr->array[part_cnt].name, + mbr->array[part_cnt].lenlo, mbr->array[part_cnt].user_type); + } + fprintf(backup, "\n"); + fclose(backup); + + mbr->PartCount = nparts + partoffset; + if (partoffset) + start = mbr->array[0].addrlo + mbr->array[0].lenlo; + for(i = 0; i < nparts; i++) { + strcpy((char *)mbr->array[i+partoffset].name, names[i]); + strcpy((char *)mbr->array[i+partoffset].classname, "DISK"); + memset((void *) mbr->array[i+partoffset].res, 0, sizeof(mbr->array[i+partoffset].res)); + mbr->array[i+partoffset].user_type = user_types[i]; + mbr->array[i+partoffset].ro = 0; + mbr->array[i+partoffset].addrhi = 0; + mbr->array[i+partoffset].lenhi = 0; + mbr->array[i+partoffset].addrlo = start; + mbr->array[i+partoffset].lenlo = lens[i]; + start += lens[i]; + } + + printf("\nready to write new partition tables:\n"); + printmbr(mbr); + for (i = 0; i < MBR_COPY_NUM; i++) { + if (mbrs[i]) + _free_mbr(mbrs[i]); + } + printf("\nwrite new partition tables? (Y/N)\n"); + read(0, &yn, 1); + if (yn != 'Y' && yn != 'y') { + printf("aborting\n"); + return 0; + } + + for (i = 0; i < MBR_COPY_NUM; i++) { + mbr->index = i; + // calculate new checksum + *(__u32 *)mbr = calc_crc32((__u32 *)mbr + 1,MBR_SIZE - 4); + lseek(fd,MBR_START_ADDRESS + MBR_SIZE*i,SEEK_SET); + write(fd,mbr,MBR_SIZE); + } + +#ifdef __linux__ + if (ioctl(fd, BLKRRPART, NULL)) + perror("Failed rereading partition table"); +#endif + + return 1; +} + +int nand_part (int argc, char **argv, const char *cmd, int fd, int force) +{ + int partoffset = 0; + int i; + char names[MAX_PART_COUNT][MAX_NAME]; + __u32 lens[MAX_PART_COUNT]; + unsigned int user_types[MAX_PART_COUNT]; + __u32 start; + + + // parse name/len arguments + memset((void *) user_types, 0, sizeof(user_types)); + if (argc > 0) { + if (sscanf(argv[0], "%u", &start) != 1) { + partoffset++; + if (force) { + printf("if using -f, must set info for first partition\n"); + usage(cmd); + close(fd); + return -3; + } + } + else { + argc--; + argv++; + } + + if (start < MBR_SIZE * MBR_COPY_NUM / 512) { + printf("Partition 1 starting offset must be at least %d\n", MBR_SIZE * MBR_COPY_NUM / 512); + close(fd); + return -3; + } + + for (i = 0; i < argc; i++) { + if (sscanf(argv[i], "%s %d %d", names[i], &lens[i], &user_types[i]) < 2) { + printf("bad 'name len' argument\n"); + usage(cmd); + close(fd); + return -3; + } + } + } + + checkmbrs(fd); + + if (argc > MAX_PART_COUNT - partoffset) { + printf("too many partitions specified (MAX 14)\n"); + usage(cmd); + close(fd); + return -2; + } + + + if (argc > 0) { + if (writembrs(fd, names, start, lens, user_types, argc, partoffset, force)) { + printf("\nverifying new partition tables:\n"); + checkmbrs(fd); +#ifdef __linux__ + printf("rereading partition table... returned %d\n", ioctl(fd, BLKRRPART, 0)); +#endif + } + } + close(fd); + + return 0; +} diff --git a/phoenix_info.c b/phoenix_info.c new file mode 100644 index 0000000..4faded5 --- /dev/null +++ b/phoenix_info.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2012 Henrik Nordstrom + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "common.h" +#include "portable_endian.h" + +struct phoenix_ptable { + char signature[16]; /* "PHOENIX_CARD_IMG" */ + unsigned int unknown1; /* 0x00200100 */ + unsigned short parts; /* Number of partitions */ + unsigned short unknown2; /* 0x0001 */ + unsigned char pad[8]; + struct phoenix_entry { + unsigned int start; /* 512 bytes blocks */ + unsigned int size; /* bytes */ + unsigned int unknown; /* ???? */ + unsigned int sig; /* "add\0" */ + } part[62]; +} ptable; + +static int save_part(struct phoenix_ptable *ptable, int part, const char *dest, FILE *in) +{ + int l = strlen(dest) + 16; + char outname[l]; + FILE *out = stdout; + char *buf = NULL; + int ret = 0; + snprintf(outname, l, dest, part); + if (part > ptable->parts) { + fprintf(stderr, "ERROR: Part index out of range\n"); + return -1; + } + buf = malloc(ptable->part[part].size); + if (!buf) + goto err; + if (strcmp(outname, "-") != 0) + out = fopen(outname, "wb"); + if (!out) + goto err; + if (fseek(in, ptable->part[part].start * 0x200, SEEK_SET) == -1) + goto err; + if (fread(buf, ptable->part[part].size, 1, in) != 1) + goto err; + if (fwrite(buf, ptable->part[part].size, 1, out) != 1) + goto err; + ret = 0; +_exit: + if (buf) + free(buf); + if (out != stdout) + fclose(out); + return ret; +err: + perror(NULL); + ret = -1; + goto _exit; +} + +static void usage(char **argv) +{ + puts("phoenix-info " VERSION "\n"); + printf("Usage: %s [options] [phoenix_image]\n" + " -v verbose\n" + " -q quiet\n" + " -p N part number\n" + " -o X destination directory, file or pattern (%%d for part number)\n" + " -s save all parts\n" + , argv[0] + ); +} + +int main(int argc, char **argv) +{ + int i; + FILE *in = stdin; + int verbose = 1; + int save_parts = 0; + int part = -1; + int opt; + const char *dest = "%d.img"; + + while ((opt = getopt(argc, argv, "vqso:p:?")) != -1) { + switch(opt) { + case 'v': + verbose++; + break; + case 'q': + if (verbose) + verbose--; + break; + case 'o': + dest = optarg; + save_parts = 1; + break; + case 'p': + save_parts = 1; + part = atoi(optarg); + break; + case 's': + save_parts = 1; + break; + default: + usage(argv); + exit(1); + break; + } + } + if (save_parts && !strchr(dest, '%')) { + const char *t = dest; + if (!*t) + t = "./"; + if (t[strlen(t)-1] == '/' || !part) { + int l = strlen(t) + strlen("/%d.img") + 1; + char *tmp = malloc(l); + snprintf(tmp, l, "%s/%%d.img", optarg); + t = tmp; + } + dest = t; + } + if (argc > optind + 1) { + usage(argv); + exit(1); + } + if (optind < argc ) { + in = fopen(argv[optind], "rb"); + } + fseek(in, 0x1C00, SEEK_CUR); + fread(&ptable, 1, 0x400, in); + if (strncmp(ptable.signature, "PHOENIX_CARD_IMG", 16) != 0) { + fprintf(stderr, "ERROR: Not a phoenix image\n"); + exit(1); + } + if (verbose > 1) { + printf("???? : %08x\n", le32toh(ptable.unknown1)); + printf("Parts : %d\n", le16toh(ptable.parts)); + printf("???? : %08x\n", le16toh(ptable.unknown2)); + printf("pad : %02x%02x%02x%02x%02x%02x%02x%02x\n", ptable.pad[0], ptable.pad[1], ptable.pad[2], ptable.pad[3], ptable.pad[4], ptable.pad[5], ptable.pad[6], ptable.pad[7]); + printf("\n"); + } + for (i = 0; i < le16toh(ptable.parts); i++) { + if (verbose && (part == -1 || part == i)) { + printf("part %d:\n", i); + printf("\tstart: 0x%08x (%u / 0x%08x)\n", le32toh(ptable.part[i].start)*512, le32toh(ptable.part[i].start), le32toh(ptable.part[i].start)); + printf("\tsize : %u\n", le32toh(ptable.part[i].size)); + printf("\t?????: %08x\n", le32toh(ptable.part[i].unknown)); + if (verbose > 1 || le32toh(ptable.part[i].sig) != 0x00646461) + printf("\tsig??: %08x\n", le32toh(ptable.part[i].sig)); + printf("\n"); + } + if (save_parts && (part == -1 || part == i)) { + save_part(&ptable, i, dest, in); + } + } +} diff --git a/pio.c b/pio.c new file mode 100644 index 0000000..09cfaf8 --- /dev/null +++ b/pio.c @@ -0,0 +1,432 @@ +/* + * (C) Copyright 2011 Henrik Nordstrom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#ifndef NO_MMAP + #include +#endif +#include +#include +#include + +#include "common.h" +#include "portable_endian.h" + +#define PIO_REG_SIZE 0x228 /*0x300*/ +#define PIO_PORT_SIZE 0x24 + +struct pio_status { + int mul_sel; + int pull; + int drv_level; + int data; +}; + +#define PIO_REG_CFG(B, N, I) ((B) + (N)*0x24 + ((I)<<2) + 0x00) +#define PIO_REG_DLEVEL(B, N, I) ((B) + (N)*0x24 + ((I)<<2) + 0x14) +#define PIO_REG_PULL(B, N, I) ((B) + (N)*0x24 + ((I)<<2) + 0x1C) +#define PIO_REG_DATA(B, N) ((B) + (N)*0x24 + 0x10) +#define PIO_NR_PORTS 9 /* A-I */ + +#define LE32TOH(X) le32toh(*((uint32_t*)(X))) + +static int pio_get(const char *buf, uint32_t port, uint32_t port_num, struct pio_status *pio) +{ + uint32_t val; + uint32_t port_num_func, port_num_pull; + uint32_t offset_func, offset_pull; + + port_num_func = port_num >> 3; + offset_func = ((port_num & 0x07) << 2); + + port_num_pull = port_num >> 4; + offset_pull = ((port_num & 0x0f) << 1); + + /* func */ + val = LE32TOH(PIO_REG_CFG(buf, port, port_num_func)); + pio->mul_sel = (val>>offset_func) & 0x07; + + /* pull */ + val = LE32TOH(PIO_REG_PULL(buf, port, port_num_pull)); + pio->pull = (val>>offset_pull) & 0x03; + + /* dlevel */ + val = LE32TOH(PIO_REG_DLEVEL(buf, port, port_num_pull)); + pio->drv_level = (val>>offset_pull) & 0x03; + + /* i/o data */ + if (pio->mul_sel > 1) + pio->data = -1; + else { + val = LE32TOH(PIO_REG_DATA(buf, port)); + pio->data = (val >> port_num) & 0x01; + } + return 1; +} + +static int pio_set(char *buf, uint32_t port, uint32_t port_num, struct pio_status *pio) +{ + uint32_t *addr, val; + uint32_t port_num_func, port_num_pull; + uint32_t offset_func, offset_pull; + + port_num_func = port_num >> 3; + offset_func = ((port_num & 0x07) << 2); + + port_num_pull = port_num >> 4; + offset_pull = ((port_num & 0x0f) << 1); + + /* func */ + if (pio->mul_sel >= 0) { + addr = (uint32_t*)PIO_REG_CFG(buf, port, port_num_func); + val = le32toh(*addr); + val &= ~(0x07 << offset_func); + val |= (pio->mul_sel & 0x07) << offset_func; + *addr = htole32(val); + } + + /* pull */ + if (pio->pull >= 0) { + addr = (uint32_t*)PIO_REG_PULL(buf, port, port_num_pull); + val = le32toh(*addr); + val &= ~(0x03 << offset_pull); + val |= (pio->pull & 0x03) << offset_pull; + *addr = htole32(val); + } + + /* dlevel */ + if (pio->drv_level >= 0) { + addr = (uint32_t*)PIO_REG_DLEVEL(buf, port, port_num_pull); + val = le32toh(*addr); + val &= ~(0x03 << offset_pull); + val |= (pio->drv_level & 0x03) << offset_pull; + *addr = htole32(val); + } + + /* data */ + if (pio->data >= 0) { + addr = (uint32_t*)PIO_REG_DATA(buf, port); + val = le32toh(*addr); + if (pio->data) + val |= (0x01 << port_num); + else + val &= ~(0x01 << port_num); + *addr = htole32(val); + } + + return 1; +} + +static void pio_print(int port, int port_nr, struct pio_status *pio) +{ + printf("P%c%d", 'A'+port, port_nr); + printf("<%x>", pio->mul_sel); + printf("<%x>", pio->pull); + printf("<%x>", pio->drv_level); + if (pio->data >= 0) + printf("<%x>", pio->data); + fputc('\n', stdout); +} + +static void print(const char *buf) +{ + int port, i; + struct pio_status pio; + for (port=0; port < PIO_NR_PORTS; port++) { + for (i=0; i<32; i++) { + if (pio_get(buf, port, i, &pio)) { + pio_print(port, i, &pio); + } + } + } +} + +static const char *argv0; + +static __attribute__((noreturn)) void usage(int rc ) +{ + fputs("sunxi-pio " VERSION "\n\n", stderr); + fprintf(stderr, "usage: %s -m|-i input [-o output] pin..\n", argv0); + fprintf(stderr," -m mmap - read pin state from system\n"); + fprintf(stderr," -i read pin state from file\n"); + fprintf(stderr," -o save pin state data to file\n"); + fprintf(stderr," print Show all pins\n"); + fprintf(stderr," Pxx Show pin\n"); + fprintf(stderr," Pxx Configure pin\n"); + fprintf(stderr," Pxx=data,drive Configure GPIO output\n"); + fprintf(stderr," Pxx*count Oscillate GPIO output (mmap mode only)\n"); + fprintf(stderr," Pxx?pull Configure GPIO input\n"); + fprintf(stderr," clean Clean input pins\n"); + fprintf(stderr, "\n mode 0-7, 0=input, 1=output, 2-7 I/O function\n"); + fprintf(stderr, " pull 0=none, 1=up, 2=down\n"); + fprintf(stderr, " drive 0-3, I/O drive level\n"); + + exit(rc); +} + +static void parse_pin(int *port, int *pin, const char *name) +{ + if (*name == 'P') name++; + *port = *name++ - 'A'; + *pin = atoi(name); +} + +static void cmd_show_pin(char *buf, const char *pin) +{ + int port, port_nr; + struct pio_status pio; + parse_pin(&port, &port_nr, pin); + if (!pio_get(buf, port, port_nr, &pio)) + usage(1); + pio_print(port, port_nr, &pio); +} + +static int parse_int(int *dst, const char *in) +{ + int value; + char *next; + errno = 0; + value = strtol(in, &next, 0); + if (!errno && next != in) { + *dst = value; + return 0; + } + return -1; +} + +static void cmd_set_pin(char *buf, const char *pin) +{ + int port, port_nr; + const char *t = pin; + struct pio_status pio; + parse_pin(&port, &port_nr, pin); + if (!pio_get(buf, port, port_nr, &pio)) + usage(1); + if ((t = strchr(pin, '='))) { + pio.mul_sel = 1; + if (t) { + t++; + parse_int(&pio.data, t); + } + if (t) + t = strchr(t, ','); + if (t) { + t++; + parse_int(&pio.drv_level, t); + } + } else if ((t = strchr(pin, '?'))) { + pio.mul_sel = 0; + pio.data = 0; + pio.drv_level = 0; + if (t) { + t++; + parse_int(&pio.pull, t); + } + } else if ((t = strchr(pin, '<'))) { + if (t) { + t++; + parse_int(&pio.mul_sel, t); + } + if (t) + t = strchr(t, '<'); + if (t) { + t++; + parse_int(&pio.pull, t); + } + if (t) + t = strchr(t, '<'); + if (t) { + t++; + parse_int(&pio.drv_level, t); + } + if (t) + t = strchr(t, '<'); + if (t) { + t++; + parse_int(&pio.data, t); + } + } + pio_set(buf, port, port_nr, &pio); +} + +static void cmd_oscillate(char *buf, const char *pin) +{ + int port, port_nr; + const char *t = pin; + int i, n = 0; + uint32_t *addr, val; + + parse_pin(&port, &port_nr, pin); + { + struct pio_status pio; + if (!pio_get(buf, port, port_nr, &pio)) + usage(1); + pio.mul_sel = 1; + pio_set(buf, port, port_nr, &pio); + } + + addr = (uint32_t*)PIO_REG_DATA(buf, port); + t = strchr(pin, '*'); + parse_int(&n, t+1); + val = le32toh(*addr); + for (i = 0; i < n; i++) { + val ^= 1 << port_nr; + *addr = htole32(val); + } +} + +static void cmd_clean(char *buf) +{ + int port, i; + struct pio_status pio; + for (port=0; port < PIO_NR_PORTS; port++) { + for (i=0; i<32; i++) { + if (pio_get(buf, port, i, &pio)) { + if (pio.mul_sel == 0) { + pio.data = 0; + pio_set(buf, port, i, &pio); + } + } + } + } +} + +static int do_command(char *buf, const char **args, int UNUSED(argc)) +{ + const char *command = args[0]; + if (*command == 'P') { + if (strchr(command, '<')) + cmd_set_pin(buf, command); + else if (strchr(command, '=')) + cmd_set_pin(buf, command); + else if (strchr(command, '?')) + cmd_set_pin(buf, command); + else if (strchr(command, '*')) + cmd_oscillate(buf, command); + else + cmd_show_pin(buf, command); + } + else if (strcmp(command, "print") == 0) + print(buf); + else if (strcmp(command, "clean") == 0) + cmd_clean(buf); + else usage(1); + return 1; +} + +int main(int argc, char **argv) +{ + int opt; + FILE *in = NULL; + FILE *out = NULL; + const char *in_name = NULL; + const char *out_name = NULL; + char buf_[PIO_REG_SIZE]; + char *buf = buf_; + int do_mmap = 0; + + argv0 = argv[0]; + + while ((opt = getopt(argc, argv, "i:o:m")) != -1) { + switch(opt) { + case '?': + usage(0); + case 'm': + do_mmap = 1; + break; + case 'i': + in_name = optarg; + break; + case 'o': + out_name = optarg; + break; + } + } + if (!in_name && !do_mmap) + usage(1); + if (do_mmap) { +#ifdef NO_MMAP + errno = ENOSYS; /* Function not implemented */ + perror("mmap PIO"); +#else + int pagesize = sysconf(_SC_PAGESIZE); + int fd = open("/dev/mem",O_RDWR); + int addr = 0x01c20800 & ~(pagesize-1); + int offset = 0x01c20800 & (pagesize-1); + if (fd == -1) { + perror("open /dev/mem"); + exit(1); + } + buf = mmap(NULL, (0x800 + pagesize - 1) & ~(pagesize-1), PROT_WRITE|PROT_READ, MAP_SHARED, fd, addr); + if (!buf) { + perror("mmap PIO"); + exit(1); + } + close(fd); + buf += offset; +#endif + } + if (in_name) { + if (strcmp(in_name, "-") == 0) { + in = stdin; + } else { + in = fopen(in_name, "rb"); + if (!in) { + perror("open input"); + exit(1); + } + } + } + if (in) { + if (fread(buf, PIO_REG_SIZE, 1, in) != 1) { + perror("read input"); + exit(1); + } + if (in != stdin) + fclose(in); + } + + while(optind < argc) { + optind += do_command(buf, (const char **)(argv + optind), argc - optind); + } + + if (out_name) { + if (strcmp(out_name, "-") == 0) { + out = stdout; + } else { + out = fopen(out_name, "wb"); + if (!out) { + perror("open output"); + exit(1); + } + } + if (fwrite(buf, PIO_REG_SIZE, 1, out) != 1) { + perror("write output"); + exit(1); + } + } + + return 0; +} diff --git a/progress.c b/progress.c new file mode 100644 index 0000000..47ea893 --- /dev/null +++ b/progress.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015 Bernhard Nortmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "progress.h" + +#include +#include +#include + +/* Less reliable than clock_gettime, but does not require linking with -lrt */ +inline double gettime(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + (double)tv.tv_usec / 1000000.; +} + +/* Calculate transfer rate (in bytes per second) */ +inline double rate(size_t transferred, double elapsed) +{ + if (elapsed > 0) + return (double)transferred / elapsed; + return 0.; +} + +/* Estimate remaining time ("ETA") for given transfer rate */ +inline double estimate(size_t remaining, double rate) +{ + if (rate > 0) + return (double)remaining / rate; + return 0.; +} + +/* Return ETA (in seconds) as string, formatted to minutes and seconds */ +const char *format_ETA(double remaining) +{ + static char result[6] = ""; + + int seconds = remaining + 0.5; /* simplistic round() */ + if (seconds >= 0 && seconds < 6000) { + snprintf(result, sizeof(result), + "%02d:%02d", seconds / 60, seconds % 60); + return result; + } + return "--:--"; +} + +/* Private progress state variable */ + +typedef struct { + progress_cb_t callback; + size_t total; + size_t done; + double start; /* start point (timestamp) for rate and ETA calculation */ +} progress_private_t; + +static progress_private_t progress = { + .callback = NULL, + .start = 0. +}; + +/* 'External' API */ + +void progress_start(progress_cb_t callback, size_t expected_total) +{ + progress.callback = callback; + progress.total = expected_total; + progress.done = 0; + progress.start = gettime(); /* reset start time */ +} + +/* Update progress status, passing information to the callback function. */ +void progress_update(size_t bytes_done) +{ + progress.done += bytes_done; + if (progress.callback) + progress.callback(progress.total, progress.done); +} + +/* Return relative / "elapsed" time, since progress_start() */ +static inline double progress_elapsed(void) +{ + if (progress.start != 0.) + return gettime() - progress.start; + return 0.; +} + +/* Callback function implementing a simple progress bar written to stdout */ +void progress_bar(size_t total, size_t done) +{ + static const int WIDTH = 48; /* # of characters to use for progress bar */ + + float ratio = total > 0 ? (float)done / total : 0; + int i, pos = WIDTH * ratio; + double speed = rate(done, progress_elapsed()); + double eta = estimate(total - done, speed); + + printf("\r%3.0f%% [", ratio * 100); /* current percentage */ + for (i = 0; i < pos; i++) putchar('='); + for (i = pos; i < WIDTH; i++) putchar(' '); + if (done < total) + printf("]%6.1f kB/s, ETA %s ", kilo(speed), format_ETA(eta)); + else + /* transfer complete, output totals plus a newline */ + printf("] %5.0f kB, %6.1f kB/s\n", kilo(done), kilo(speed)); + + fflush(stdout); +} + +/* + * Progress callback that emits percentage numbers, each on a separate line. + * The output is suitable for piping it into "dialog --gauge". + * + * sunxi-fel multiwrite-with-gauge <...> \ + * | dialog --title "FEL upload progress" \ + * --gauge "" 5 70 + */ +void progress_gauge(size_t total, size_t done) +{ + if (total > 0) { + printf("%.0f\n", (float)done / total * 100); + fflush(stdout); + } +} + +/* + * A more sophisticated version of progress_gauge() that also updates the + * prompt (caption) with additional information. This uses a feature of + * the dialog utility that parses "XXX" delimiters - see 'man dialog'. + * + * sunxi-fel multiwrite-with-xgauge <...> \ + * | dialog --title "FEL upload progress" \ + * --backtitle "Please wait..." \ + * --gauge "" 6 70 + */ +void progress_gauge_xxx(size_t total, size_t done) +{ + if (total > 0) { + double speed = rate(done, progress_elapsed()); + double eta = estimate(total - done, speed); + printf("XXX\n"); + printf("%.0f\n", (float)done / total * 100); + if (done < total) + printf("%zu of %zu, %.1f kB/s, ETA %s\n", + done, total, kilo(speed), format_ETA(eta)); + else + printf("Done: %.1f kB, at %.1f kB/s\n", + kilo(done), kilo(speed)); + printf("XXX\n"); + fflush(stdout); + } +} diff --git a/progress.h b/progress.h new file mode 100644 index 0000000..b5c97aa --- /dev/null +++ b/progress.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 Bernhard Nortmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUNXI_TOOLS_PROGRESS_H +#define _SUNXI_TOOLS_PROGRESS_H + +#include + +/* function pointer type for a progress callback / notification */ +typedef void (*progress_cb_t)(size_t total, size_t done); + +/* conversion helper macros */ +#define kilo(value) ((double)(value) / 1000.) /* SI prefix "k" */ +#define kibi(value) ((double)(value) / 1024.) /* binary prefix "Ki", "K" */ + +double gettime(void); +double rate(size_t transferred, double elapsed); +double estimate(size_t remaining, double rate); + +void progress_start(progress_cb_t callback, size_t expected_total); +void progress_update(size_t bytes_done); + +/* progress callback implementations for various display styles */ +void progress_bar(size_t total, size_t done); +void progress_gauge(size_t total, size_t done); +void progress_gauge_xxx(size_t total, size_t done); + +#endif /* _SUNXI_TOOLS_PROGRESS_H */ diff --git a/script.c b/script.c new file mode 100644 index 0000000..3a9c7e7 --- /dev/null +++ b/script.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.h" + +#include +#include +#include +#include + +#include "script.h" + +/* + */ +struct script *script_new(void) +{ + struct script *script; + if ((script = malloc(sizeof(*script)))) + list_init(&script->sections); + return script; +} + +void script_delete(struct script *script) +{ + struct list_entry *o; + + assert(script); + + while ((o = list_last(&script->sections))) { + struct script_section *section = container_of(o, + struct script_section, sections); + + script_section_delete(section); + } + + free(script); +} + +/* + */ +struct script_section *script_section_new(struct script *script, + const char *name) +{ + struct script_section *section; + + assert(script); + assert(name && *name); + + if ((section = malloc(sizeof(*section)))) { + size_t l = strlen(name); + if (l>31) /* truncate */ + l=31; + memcpy(section->name, name, l); + section->name[l] = '\0'; + + list_init(§ion->entries); + list_append(&script->sections, §ion->sections); + } + return section; +} + +void script_section_delete(struct script_section *section) +{ + struct list_entry *o; + + assert(section); + + while ((o = list_last(§ion->entries))) { + struct script_entry *entry = container_of(o, + struct script_entry, entries); + + script_entry_delete(entry); + } + + if (!list_empty(§ion->sections)) + list_remove(§ion->sections); +} + +struct script_section *script_find_section(struct script *script, + const char *name) +{ + struct list_entry *o; + struct script_section *section; + + assert(script); + assert(name); + + for (o = list_first(&script->sections); o; + o = list_next(&script->sections, o)) { + section = container_of(o, struct script_section, sections); + + if (strcmp(section->name, name) == 0) + return section; + } + + return NULL; +} + +/* + */ +static inline void script_entry_append(struct script_section *section, + struct script_entry *entry, + enum script_value_type type, + const char *name) +{ + size_t l; + + assert(section); + assert(entry); + assert(name); + + l = strlen(name); + if (l>31) /* truncate */ + l=31; + memcpy(entry->name, name, l); + entry->name[l] = '\0'; + + entry->type = type; + + list_append(§ion->entries, &entry->entries); +} + +void script_entry_delete(struct script_entry *entry) +{ + void *container; + + assert(entry); + assert(entry->type == SCRIPT_VALUE_TYPE_SINGLE_WORD || + entry->type == SCRIPT_VALUE_TYPE_STRING || + entry->type == SCRIPT_VALUE_TYPE_GPIO || + entry->type == SCRIPT_VALUE_TYPE_NULL); + + if (!list_empty(&entry->entries)) + list_remove(&entry->entries); + + switch(entry->type) { + case SCRIPT_VALUE_TYPE_SINGLE_WORD: + container = container_of(entry, struct script_single_entry, entry); + break; + case SCRIPT_VALUE_TYPE_STRING: + container = container_of(entry, struct script_string_entry, entry); + break; + case SCRIPT_VALUE_TYPE_GPIO: + container = container_of(entry, struct script_gpio_entry, entry); + break; + case SCRIPT_VALUE_TYPE_NULL: + container = container_of(entry, struct script_null_entry, entry); + break; + default: + abort(); + } + + free(container); +} + +struct script_null_entry *script_null_entry_new(struct script_section *section, + const char *name) +{ + struct script_null_entry *entry; + + assert(section); + assert(name && *name); + + if ((entry = malloc(sizeof(*entry)))) { + script_entry_append(section, &entry->entry, + SCRIPT_VALUE_TYPE_NULL, name); + } + + return entry; +} + +struct script_single_entry *script_single_entry_new(struct script_section *section, + const char *name, + uint32_t value) +{ + struct script_single_entry *entry; + + assert(section); + assert(name && *name); + + if ((entry = malloc(sizeof(*entry)))) { + entry->value = value; + + script_entry_append(section, &entry->entry, + SCRIPT_VALUE_TYPE_SINGLE_WORD, name); + } + + return entry; +} + +struct script_string_entry *script_string_entry_new(struct script_section *section, + const char *name, + size_t l, const char *s) +{ + struct script_string_entry *entry; + + assert(section); + assert(name); + assert(s); + + if ((entry = malloc(sizeof(*entry)+l+1))) { + entry->l = l; + memcpy(entry->string, s, l); + entry->string[l] = '\0'; + + script_entry_append(section, &entry->entry, + SCRIPT_VALUE_TYPE_STRING, name); + } + + return entry; +} + +struct script_gpio_entry *script_gpio_entry_new(struct script_section *section, + const char *name, + unsigned port, unsigned num, + int32_t data[4]) +{ + struct script_gpio_entry *entry; + + assert(section); + assert(name && *name); + + if ((entry = malloc(sizeof(*entry)))) { + entry->port = port; + entry->port_num = num; + for (int i=0; i<4; i++) + entry->data[i] = data[i]; + + script_entry_append(section, &entry->entry, + SCRIPT_VALUE_TYPE_GPIO, name); + } + + return entry; +} + +struct script_entry *script_find_entry(struct script_section *section, + const char *name) +{ + struct list_entry *o; + struct script_entry *ep; + + assert(section); + assert(name); + + for (o = list_first(§ion->entries); o; + o = list_next(§ion->entries, o)) { + ep = container_of(o, struct script_entry, entries); + + if (strcmp(ep->name, name) == 0) + return ep; + } + + return NULL; +} diff --git a/script.h b/script.h new file mode 100644 index 0000000..7df3151 --- /dev/null +++ b/script.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUNXI_TOOLS_SCRIPT_H +#define _SUNXI_TOOLS_SCRIPT_H + +#include "list.h" + +#define GPIO_BANK_MAX 14 /* N, (zero-based) index 13 */ + +/** head of the data tree */ +struct script { + struct list_entry sections; +}; + +/** head of each section */ +struct script_section { + char name[32]; + + struct list_entry sections; + struct list_entry entries; +}; + +/** types of values */ +enum script_value_type { + SCRIPT_VALUE_TYPE_SINGLE_WORD = 1, + SCRIPT_VALUE_TYPE_STRING, + SCRIPT_VALUE_TYPE_MULTI_WORD, + SCRIPT_VALUE_TYPE_GPIO, + SCRIPT_VALUE_TYPE_NULL, +}; + +/** generic entry */ +struct script_entry { + char name[32]; + enum script_value_type type; + + struct list_entry entries; +}; + +/** null entry */ +struct script_null_entry { + struct script_entry entry; +}; + +/** entry with 32b value */ +struct script_single_entry { + struct script_entry entry; + + uint32_t value; +}; + +/** entry with string value */ +struct script_string_entry { + struct script_entry entry; + + size_t l; + char string[]; +}; + +/** entry describing a GPIO */ +struct script_gpio_entry { + struct script_entry entry; + + unsigned port, port_num; + int32_t data[4]; +}; + +/** create a new script tree */ +struct script *script_new(void); +/** deletes a tree recursively */ +void script_delete(struct script *); + +/** create a new section appended to a given tree */ +struct script_section *script_section_new(struct script *script, + const char *name); +/** deletes a section recursvely and removes it from the script */ +void script_section_delete(struct script_section *section); + +/** find existing section */ +struct script_section *script_find_section(struct script *script, + const char *name); + +/** deletes an entry and removes it from the section */ +void script_entry_delete(struct script_entry *entry); + +/** create a new empty/null entry appended to a section */ +struct script_null_entry *script_null_entry_new(struct script_section *section, + const char *name); +/** create a new single word entry appended to a section */ +struct script_single_entry *script_single_entry_new(struct script_section *section, + const char *name, + uint32_t value); +/** create a new string entry appended to a section */ +struct script_string_entry *script_string_entry_new(struct script_section *section, + const char *name, + size_t l, const char *s); +/** create a new GPIO entry appended to a section */ +struct script_gpio_entry *script_gpio_entry_new(struct script_section *script, + const char *name, + unsigned port, unsigned num, + int32_t data[4]); + +/** find existing entry in a giving section */ +struct script_entry *script_find_entry(struct script_section *section, + const char *name); +#endif diff --git a/script_bin.c b/script_bin.c new file mode 100644 index 0000000..ebed175 --- /dev/null +++ b/script_bin.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.h" + +#include +#include +#include +#include +#include +#include + +#include "script.h" +#include "script_bin.h" + +#define pr_info(...) pr_error("fexc-bin: " __VA_ARGS__) +#define pr_err(...) pr_error("E: fexc-bin: " __VA_ARGS__) + +#ifdef DEBUG +#define pr_debug(...) pr_error("D: fexc-bin: " __VA_ARGS__) +#else +#define pr_debug(...) +#endif + +#define PTR(B, OFF) (void*)((char*)(B)+(OFF)) +#define WORDS(S) (((S)+(sizeof(uint32_t)-1))/(sizeof(uint32_t))) + +/* + * generator + */ +size_t script_bin_size(struct script *script, + size_t *sections, size_t *entries) +{ + size_t words = 0, bin_size = 0; + struct list_entry *ls, *le; + struct script_section *section; + struct script_entry *entry; + struct script_string_entry *string; + + *sections = *entries = 0; + + /* count */ + for (ls = list_first(&script->sections); ls; + ls = list_next(&script->sections, ls)) { + section = container_of(ls, struct script_section, sections); + size_t c = 0; + + for (le = list_first(§ion->entries); le; + le = list_next(§ion->entries, le)) { + size_t size = 0; + entry = container_of(le, struct script_entry, entries); + c++; + + switch(entry->type) { + case SCRIPT_VALUE_TYPE_NULL: + case SCRIPT_VALUE_TYPE_SINGLE_WORD: + size = sizeof(uint32_t); + break; + case SCRIPT_VALUE_TYPE_STRING: + string = container_of(entry, struct script_string_entry, + entry); + size = string->l; + break; + case SCRIPT_VALUE_TYPE_GPIO: + size = sizeof(struct script_bin_gpio_value); + break; + default: + abort(); + } + words += WORDS(size); + } + *sections += 1; + *entries += c; + } + + bin_size = sizeof(struct script_bin_head) + + (*sections)*sizeof(struct script_bin_section) + + (*entries)*sizeof(struct script_bin_entry) + + words*sizeof(uint32_t); + pr_debug("sections:%zu entries:%zu data:%zu/%zu -> %zu\n", + *sections, *entries, words, words*sizeof(uint32_t), + bin_size); + return bin_size; +} + +int script_generate_bin(void *bin, size_t bin_size, + struct script *script, + size_t sections, size_t entries) +{ + struct script_bin_head *head; + struct script_bin_section *section; + struct script_bin_entry *entry; + void *data; + + struct list_entry *ls, *le; + + head = bin; + section = head->section; + entry = (void*)section+sections*sizeof(*section); + data = (void*)entry+entries*sizeof(*entry); + + pr_debug("head....:%p\n", head); + pr_debug("section.:%p (offset:%zu, each:%zu)\n", section, + (void*)section-bin, sizeof(*section)); + pr_debug("entry...:%p (offset:%zu, each:%zu)\n", entry, + (void*)entry-bin, sizeof(*entry)); + pr_debug("data....:%p (offset:%zu)\n", data, + (void*)data-bin); + + head->sections = sections; + head->filesize = bin_size; + head->version[0] = 1; + head->version[1] = 2; + + for (ls = list_first(&script->sections); ls; + ls = list_next(&script->sections, ls)) { + struct script_section *s; + size_t c = 0; + s = container_of(ls, struct script_section, sections); + + memcpy(section->name, s->name, strlen(s->name)); + section->offset = ((void*)entry-bin)>>2; + + for (le = list_first(&s->entries); le; + le = list_next(&s->entries, le)) { + struct script_entry *e; + e = container_of(le, struct script_entry, entries); + size_t size = 0; + + memcpy(entry->name, e->name, strlen(e->name)); + entry->offset = ((void*)data-bin)>>2; + entry->pattern = (e->type<<16); + + switch(e->type) { + case SCRIPT_VALUE_TYPE_SINGLE_WORD: { + struct script_single_entry *single; + int32_t *bdata = data; + single = container_of(e, struct script_single_entry, entry); + + *bdata = single->value; + size = sizeof(*bdata); + }; break; + case SCRIPT_VALUE_TYPE_STRING: { + struct script_string_entry *string; + string = container_of(e, struct script_string_entry, entry); + size = string->l; + memcpy(data, string->string, size); + /* align */ + size += sizeof(uint32_t)-1; + size /= sizeof(uint32_t); + size *= sizeof(uint32_t); + }; break; + case SCRIPT_VALUE_TYPE_MULTI_WORD: + abort(); + case SCRIPT_VALUE_TYPE_GPIO: { + struct script_gpio_entry *gpio; + struct script_bin_gpio_value *bdata = data; + gpio = container_of(e, struct script_gpio_entry, entry); + bdata->port = gpio->port; + bdata->port_num = gpio->port_num; + bdata->mul_sel = gpio->data[0]; + bdata->pull = gpio->data[1]; + bdata->drv_level = gpio->data[2]; + bdata->data = gpio->data[3]; + size = sizeof(*bdata); + }; break; + case SCRIPT_VALUE_TYPE_NULL: + size = sizeof(uint32_t); + break; + } + + data += size; + entry->pattern |= (size>>2); + pr_debug("%s.%s <%p> (type:%d, words:%d (%zu), offset:%d)\n", + section->name, entry->name, entry, + (entry->pattern>>16) & 0xffff, + (entry->pattern>>0) & 0xffff, size, + entry->offset); + c++; + entry++; + } + + section->length = c; + pr_debug("%s <%p> (length:%d, offset:%d)\n", + section->name, section, section->length, section->offset); + + section++; + } + return 1; +} + +/* + * decompiler + */ +static int decompile_section(void *bin, size_t bin_size, + const char *filename, + struct script_bin_section *section, + struct script *script) +{ + struct script_bin_entry *entry; + struct script_section *s; + int size; + + if ((section->offset < 0) || (section->offset > (int)(bin_size / 4))) { + pr_err("Malformed data: invalid section offset: %d\n", + section->offset); + return 0; + } + + size = bin_size - 4 * section->offset; + + if ((section->length < 0) || + (section->length > (size / (int)sizeof(struct script_bin_entry)))) { + pr_err("Malformed data: invalid section length: %d\n", + section->length); + return 0; + } + + if ((s = script_section_new(script, section->name)) == NULL) + goto malloc_error; + + entry = PTR(bin, section->offset<<2); + + for (int i = section->length; i--; entry++) { + void *data = PTR(bin, entry->offset<<2); + unsigned type, words; + type = (entry->pattern >> 16) & 0xffff; + words = (entry->pattern >> 0) & 0xffff; + + for (char *p = entry->name; *p; p++) + if (!(isalnum(*p) || *p == '_' || *p == '-')) { + pr_info("Warning: Malformed entry key \"%s\"\n", + entry->name); + break; + } + + switch(type) { + case SCRIPT_VALUE_TYPE_SINGLE_WORD: { + uint32_t *v = data; + if (words != 1) { + pr_err("%s: %s.%s: invalid length %d (assuming %d)\n", + filename, section->name, entry->name, words, 1); + } + if (!script_single_entry_new(s, entry->name, *v)) + goto malloc_error; + }; break; + case SCRIPT_VALUE_TYPE_STRING: { + size_t bytes = words << 2; + const char *p, *pe, *v = data; + + for(p=v, pe=v+bytes; *p && p!=pe; p++) + ; /* seek end-of-string */ + + if (!script_string_entry_new(s, entry->name, p-v, v)) + goto malloc_error; + }; break; + case SCRIPT_VALUE_TYPE_GPIO: { + struct script_bin_gpio_value *gpio = data; + int32_t v[4]; + if (words != 6) { + pr_err("%s: %s.%s: invalid length %d (assuming %d)\n", + filename, section->name, entry->name, words, 6); + } else if (gpio->port == 0xffff) { + ; /* port:power */ + } else if (gpio->port < 1 || gpio->port > GPIO_BANK_MAX) { + pr_err("%s: %s.%s: unknown GPIO port bank ", + filename, section->name, entry->name); + char c = 'A' + gpio->port - 1; + if (c >= 'A' && c <= 'Z') + pr_err("%c ", c); + pr_err("(%u)\n", gpio->port); + goto failure; + } + v[0] = gpio->mul_sel; + v[1] = gpio->pull; + v[2] = gpio->drv_level; + v[3] = gpio->data; + + if (!script_gpio_entry_new(s, entry->name, + gpio->port, gpio->port_num, + v)) + goto malloc_error; + }; break; + case SCRIPT_VALUE_TYPE_NULL: + if (!*entry->name) { + pr_err("%s: empty entry in section: %s\n", filename, section->name); + } else if (!script_null_entry_new(s, entry->name)) { + goto malloc_error; + } + break; + default: + pr_err("%s: %s.%s: unknown type %d\n", + filename, section->name, entry->name, type); + goto failure; + } + } + return 1; + +malloc_error: + pr_err("%s: %s\n", "malloc", strerror(errno)); +failure: + return 0; +} + +#define SCRIPT_BIN_VERSION_LIMIT 0x10 +#define SCRIPT_BIN_SECTION_LIMIT 0x100 + +int script_decompile_bin(void *bin, size_t bin_size, + const char *filename, + struct script *script) +{ + unsigned int i; + struct script_bin_head *head = bin; + + if ((head->version[0] > SCRIPT_BIN_VERSION_LIMIT) || + (head->version[1] > SCRIPT_BIN_VERSION_LIMIT)) { + pr_err("Malformed data: version %u.%u.\n", + head->version[0], head->version[1]); + return 0; + } + + if (head->sections > SCRIPT_BIN_SECTION_LIMIT) { + pr_err("Malformed data: too many sections (%u).\n", + head->sections); + return 0; + } + + pr_info("%s: version: %u.%u\n", filename, + head->version[0], head->version[1]); + pr_info("%s: size: %zu (%u sections), header value: %u\n", filename, + bin_size, head->sections, head->filesize); + + /* TODO: SANITY: compare head.sections with bin_size */ + for (i=0; i < head->sections; i++) { + struct script_bin_section *section = &head->section[i]; + + if (!decompile_section(bin, bin_size, filename, + section, script)) + return 0; + } + return 1; +} diff --git a/script_bin.h b/script_bin.h new file mode 100644 index 0000000..7448275 --- /dev/null +++ b/script_bin.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUNXI_TOOLS_SCRIPT_BIN_H +#define _SUNXI_TOOLS_SCRIPT_BIN_H + +/** binary representation of the head of a section */ +struct script_bin_section { + char name[32]; + int32_t length; + int32_t offset; +}; + +/** binary representation of the head of the script file */ +struct script_bin_head { + uint32_t sections; + uint32_t filesize; + uint32_t version[2]; + struct script_bin_section section[]; +}; + +/** binary representation of the head of an entry */ +struct script_bin_entry { + char name[32]; + int32_t offset; + int32_t pattern; +}; + +/** binary representation of a GPIO */ +struct script_bin_gpio_value { + int32_t port; + int32_t port_num; + int32_t mul_sel; + int32_t pull; + int32_t drv_level; + int32_t data; +}; + +size_t script_bin_size(struct script *script, + size_t *sections, size_t *entries); + +int script_generate_bin(void *bin, size_t bin_size, struct script *script, + size_t sections, size_t entries); +int script_decompile_bin(void *bin, size_t bin_size, + const char *filename, + struct script *script); +#endif diff --git a/script_extractor.c b/script_extractor.c new file mode 100644 index 0000000..25d537c --- /dev/null +++ b/script_extractor.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Olliver Schinagl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define SCRIPT_START 0x43000000 +#define SCRIPT_SIZE 0x20000 + +int main(void) { + char *addr; + int fd; + int i; + + fd = open("/dev/mem", O_RDONLY); + addr = (char *)mmap(NULL, SCRIPT_SIZE, PROT_READ, MAP_SHARED, fd, SCRIPT_START); + for (i = 0; i < SCRIPT_SIZE; i++) + putchar(addr[i]); + munmap(addr, SCRIPT_SIZE); + close(fd); + + return 0; +} diff --git a/script_fex.c b/script_fex.c new file mode 100644 index 0000000..ec251c2 --- /dev/null +++ b/script_fex.c @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "common.h" + +#include +#include +#include +#include +#include + +#include "script.h" +#include "script_fex.h" + +#define MAX_LINE 255 + +#define pr_info(...) pr_error("fexc-fex: " __VA_ARGS__) +#define pr_err(...) pr_error("E: fexc-fex: " __VA_ARGS__) + +#ifdef DEBUG +#define pr_debug(...) pr_error("D: fexc-fex: " __VA_ARGS__) +#else +#define pr_debug(...) +#endif + +/* + * generator + */ +static inline size_t strlen2(const char *s) +{ + size_t l = strlen(s); + const char *p = &s[l-1]; + while (l && *p >= '0' && *p <= '9') { + l--; + p--; + } + return l; +} + +static int find_full_match(const char *s, size_t l, const char **list) +{ + while (*list) { + if (memcmp(s, *list, l) == 0) + return 1; + list++; + } + + return 0; +} + +/** + */ +static int decompile_single_mode(const char *name) +{ + static const char *hexa_entries[] = { + "dram_baseaddr", "dram_zq", "dram_tpr", "dram_emr", + "g2d_size", + "rtp_press_threshold", "rtp_sensitive_level", + "ctp_twi_addr", "csi_twi_addr", "csi_twi_addr_b", "tkey_twi_addr", + "lcd_gamma_tbl_", + "gsensor_twi_addr", + NULL }; + size_t l = strlen2(name); + + if (find_full_match(name, l, hexa_entries)) + return 0; + else + return -1; +} + +int script_generate_fex(FILE *out, const char *UNUSED(filename), + struct script *script) +{ + struct list_entry *ls, *le; + struct script_section *section; + struct script_entry *entry; + + for (ls = list_first(&script->sections); ls; + ls = list_next(&script->sections, ls)) { + section = container_of(ls, struct script_section, sections); + + fprintf(out, "[%s]\n", section->name); + for (le = list_first(§ion->entries); le; + le = list_next(§ion->entries, le)) { + entry = container_of(le, struct script_entry, entries); + + switch(entry->type) { + case SCRIPT_VALUE_TYPE_SINGLE_WORD: { + int mode = decompile_single_mode(entry->name); + struct script_single_entry *single; + single = container_of(entry, struct script_single_entry, entry); + + fprintf(out, "%s = ", entry->name); + if (mode < 0) + fprintf(out, "%d", single->value); + else if (mode > 0) + fprintf(out, "0x%0*x", mode, single->value); + else + fprintf(out, "0x%x", single->value); + fputc('\n', out); + }; break; + case SCRIPT_VALUE_TYPE_STRING: { + struct script_string_entry *string; + string = container_of(entry, struct script_string_entry, entry); + fprintf(out, "%s = \"%.*s\"\n", entry->name, + (int)string->l, string->string); + }; break; + case SCRIPT_VALUE_TYPE_MULTI_WORD: + abort(); + case SCRIPT_VALUE_TYPE_GPIO: { + char port = 'A'-1; + struct script_gpio_entry *gpio; + gpio = container_of(entry, struct script_gpio_entry, entry); + + if (gpio->port == 0xffff) { + fprintf(out, "%s = port:power%u", entry->name, + gpio->port_num); + } else { + port += gpio->port; + fprintf(out, "%s = port:P%c%02u", entry->name, + port, gpio->port_num); + } + for (const int *p = gpio->data, *pe = p+4; p != pe; p++) { + if (*p == -1) + fputs("", out); + else + fprintf(out, "<%d>", *p); + } + fputc('\n', out); + }; break; + case SCRIPT_VALUE_TYPE_NULL: + fprintf(out, "%s =\n", entry->name); + break; + } + } + fputc('\n', out); + } + return 1; +} + +/* + * parser + */ + +/** find first not blank char */ +static inline char *skip_blank(char *p) +{ + while(isblank(*p)) + p++; + return p; +} + +/** trim out blank chars at the end of a string */ +static inline char *rtrim(const char *s, char *p) +{ + if (p>s) { + while (p!=s && isblank(*--p)) + ; + *++p='\0'; + } + return p; +} + +/** + */ +int script_parse_fex(FILE *in, const char *filename, struct script *script) +{ + char buffer[MAX_LINE+1]; + int ok = 1; + struct script_section *last_section = NULL; + + /* TODO: deal with longer lines correctly (specially in comments) */ + for(size_t line = 1; ok && fgets(buffer, sizeof(buffer), in); line++) { + char *s = skip_blank(buffer); /* beginning */ + char *pe = s; /* \0... to be found */ + + if (*pe) while (*++pe) + ; + + if (pe>s && pe[-1] == '\n') { + if (pe>s+1 && pe[-2] == '\r') + pe -= 2; + else + pe -= 1; + *pe = '\0'; + } + + pe = rtrim(s, pe); + + /* Some lines end in a trailing semicolon. */ + if (pe > s && pe[-1] == ';') + *--pe = '\0'; + + if (pe == s || *s == ';' || *s == '#') + continue; /* empty */ + if (*s == ':') { + /* see https://github.com/linux-sunxi/sunxi-boards/issues/50 */ + pr_error("Warning: %s:%zu: invalid line, suspecting typo/malformed comment.\n", + filename, line); + continue; /* ignore this line */ + } + if (*s == '[') { + /* section */ + char *p = ++s; + while (isalnum(*p) || *p == '_' || *p == '-' || *p == '/') + p++; + + if (*p == ']' && *(p+1) == '\0') { + *p = '\0'; + if ((last_section = script_section_new(script, s))) + continue; + + perror("malloc"); + } else if (*p) { + pr_error("E: %s:%zu: invalid character at %zu.\n", + filename, line, p-buffer+1); + } else { + pr_error("E: %s:%zu: incomplete section declaration.\n", + filename, line); + } + ok = 0; + } else { + /* key = value */ + const char *key = s; + char *mark, *p = s; + + if (!last_section) { + pr_error("E: %s:%zu: data must follow a section.\n", + filename, line); + goto parse_error; + }; + + while (isalnum(*p) || *p == '_' || *p == '-') + p++; + mark = p; + p = skip_blank(p); + if (*p != '=') + goto invalid_char_at_p; + *mark = '\0'; /* truncate key */ + p = skip_blank(p+1); + + if (*p == '\0') { + /* NULL */ + if (script_null_entry_new(last_section, key)) + continue; + perror("malloc"); + } else if (pe > p+1 && *p == '"' && pe[-1] == '"') { + /* string */ + p++; *--pe = '\0'; + if (script_string_entry_new(last_section, key, pe-p, p)) { + pr_debug("%s.%s = \"%.*s\"\n", + last_section->name, key, + (int)(pe-p), p); + continue; + } + perror("malloc"); + } else if (memcmp("port:", p, 5) == 0) { + /* GPIO */ + p += 5; + if (p[0] == 'P' && + (p[1] < 'A' || p[1] > ('A' + GPIO_BANK_MAX))) + ; + else if (*p != 'P' && + memcmp(p, "power", 5) != 0) + ; + else { + char *end; + int port; + long v; + + if (*p == 'P') { + /* port:PXN */ + port = p[1] - 'A' + 1; + p += 2; + } else { + /* port:powerN */ + port = 0xffff; + p += 5; + } + + v = strtol(p, &end, 10); + if (end == p) + goto invalid_char_at_p; + else if (v<0 || v>255) { + pr_error("E: %s:%zu: port out of range at %zu (%ld).\n", + filename, line, p-buffer+1, v); + } else { + int data[] = {-1,-1,-1,-1}; + int port_num = v; + p = end; + for (int i=0; *p && i<4; i++) { + if (memcmp(p, "", 9) == 0) { + p += 9; + continue; + } else if (*p == '<') { + v = strtol(++p, &end, 10); + if (end == p) { + ; + } else if (v<0 || v>INT32_MAX) { + pr_error("E: %s:%zu: value out of range at %zu (%ld).\n", + filename, line, p-buffer+1, v); + goto parse_error; + } else if (*end != '>') { + p = end; + } else { + p = end+1; + data[i] = v; + continue; + } + } + break; + } + if (*p) + goto invalid_char_at_p; + if (script_gpio_entry_new(last_section, key, + port, port_num, data)) { + pr_debug("%s.%s = GPIO %d.%d (%d,%d,%d,%d)\n", + last_section->name, key, + port, port_num, + data[0], data[1], data[2], data[3]); + continue; + } + perror("malloc"); + } + } + } else if (isdigit(*p) || (*p == '-' && isdigit(*(p+1)))) { + long long v = 0; + char *end; + v = strtoll(p, &end, 0); + p = end; + if (p != pe) { + goto invalid_char_at_p; + } else if (v > UINT32_MAX) { + pr_error("E: %s:%zu: value out of range %lld.\n", + filename, line, v); + } else if (script_single_entry_new(last_section, key, v)) { + pr_debug("%s.%s = %lld\n", + last_section->name, key, v); + continue; + } + } else { + /* goto invalid_char_at_p; */ + pr_error("Warning: %s:%zu: unquoted value '%s', assuming string\n", + filename, line, p); + if (script_string_entry_new(last_section, key, pe-p, p)) { + pr_debug("%s.%s = \"%s\"\n", + last_section->name, key, p); + continue; + } + perror("malloc"); + } + pr_error("E: %s:%zu: parse error at %zu.\n", + filename, line, p-buffer+1); + goto parse_error; +invalid_char_at_p: + pr_error("E: %s:%zu: invalid character at %zu.\n", + filename, line, p-buffer+1); +parse_error: + ok = 0; + } + }; + + if (ferror(in)) + ok = 0; + return ok; +} diff --git a/script_fex.h b/script_fex.h new file mode 100644 index 0000000..0ada86d --- /dev/null +++ b/script_fex.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUBXI_TOOLS_SCRIPT_FEX_H +#define _SUBXI_TOOLS_SCRIPT_FEX_H + +int script_parse_fex(FILE *in, const char *filename, struct script *script); +int script_generate_fex(FILE *out, const char *filename, struct script *script); + +#endif diff --git a/script_uboot.c b/script_uboot.c new file mode 100644 index 0000000..db5e636 --- /dev/null +++ b/script_uboot.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "common.h" + +#include +#include +#include + +#include "script.h" +#include "script_uboot.h" + +#define pr_info(...) pr_error("fexc-uboot: " __VA_ARGS__) +#define pr_err(...) pr_error("E: fexc-uboot: " __VA_ARGS__) + +#ifdef DEBUG +#define pr_debug(...) pr_error("D: fexc-uboot: " __VA_ARGS__) +#else +#define pr_debug(...) +#endif + +struct members { + const char *name; + const char *translation; + int mode; +}; +#define foreach_member(I, T) for (const struct members *I = T; \ + I < T+ARRAY_SIZE(T); I++) + +/* + */ +static inline void out_u32_member(FILE *out, const char *key, int hexa, + struct script_single_entry *val) +{ + const char *fmt; + if (hexa) + fmt = "\t.%s = %#x,\n"; + else + fmt = "\t.%s = %u,\n"; + + fprintf(out, fmt, key, val->value); +} + +static inline void out_gpio_member(FILE *out, const char *key, + struct script_gpio_entry *gpio) +{ + fprintf(out, "\t.%s = ", key); + + if (gpio->port == 0xffff) + fprintf(out, "GPIO_AXP_CFG(%u", gpio->port_num); + else + fprintf(out, "GPIO_CFG(%u, %u", gpio->port, gpio->port_num); + + for (const int *p = gpio->data, *pe = p+4; p != pe; p++) { + if (*p == -1) + fputs(", 0xff", out); + else + fprintf(out, ", %u", *p); + } + + fputs("),\n", out); +} + +static inline void out_null_member(FILE *out, const char *key) +{ + fprintf(out, "\t/* %s is NULL */\n", key); +} + +static inline int out_member(FILE *out, const char *key, int mode, + struct script_entry *ep) +{ + switch (ep->type) { + case SCRIPT_VALUE_TYPE_SINGLE_WORD: + out_u32_member(out, key, mode, + container_of(ep, struct script_single_entry, entry)); + break; + case SCRIPT_VALUE_TYPE_NULL: + out_null_member(out, key); + break; + case SCRIPT_VALUE_TYPE_GPIO: + out_gpio_member(out, key, + container_of(ep, struct script_gpio_entry, entry)); + break; + default: + return 0; + } + return 1; +} + +/* + * DRAM + */ +static struct members dram_members[] = { + { .name="dram_clock" }, + { .name="dram_clk", .translation="clock" }, + { .name="dram_type" }, + { .name="dram_rank_num" }, + { .name="dram_density" }, + { .name="dram_chip_density", .translation="density" }, + { .name="dram_io_width" }, + { .name="dram_bus_width" }, + { .name="dram_cas" }, + { .name="dram_zq" }, + { .name="dram_odt_en" }, + { .name="dram_size" }, + { .name="dram_tpr0", .mode=1 }, + { .name="dram_tpr1", .mode=1 }, + { .name="dram_tpr2", .mode=1 }, + { .name="dram_tpr3", .mode=1 }, + { .name="dram_tpr4", .mode=1 }, + { .name="dram_tpr5", .mode=1 }, + { .name="dram_emr1", .mode=1 }, + { .name="dram_emr2", .mode=1 }, + { .name="dram_emr3", .mode=1 }, +}; + +static int generate_dram_struct(FILE *out, struct script_section *sp) +{ + struct script_entry *ep; + const char *key; + int ret = 1; + + fprintf(out, "static struct dram_para dram_para = {\n"); + foreach_member(mp, dram_members) { + ep = script_find_entry(sp, mp->name); + if (!ep) + continue; + + key = (mp->translation) ? mp->translation : mp->name+5; + if (!out_member(out, key, mp->mode, ep)) { + pr_err("dram_para: %s: invalid field\n", ep->name); + ret = 0; + } + + } + fprintf(out, "};\n"); + fputs("\nunsigned long sunxi_dram_init(void)\n" + "{\n\treturn dramc_init(&dram_para);\n}\n", + out); + + return ret; +} + +#if 0 +/* + * PMU + */ +static struct members pmu_members[] = { + { .name = "pmu_used2" }, + { .name = "pmu_para" }, + { .name = "pmu_adpdet" }, + { .name = "pmu_shutdown_chgcur" }, + { .name = "pmu_shutdown_chgcur2" }, + { .name = "pmu_pwroff_vol" }, + { .name = "pmu_pwron_vol" }, +}; + +static int generate_pmu_struct(FILE *out, struct script_section *target, + struct script_section *pmu_para) +{ + struct list_entry *le; + struct script_section *sp; + struct script_entry *ep; + const char *key; + int ret = 1; + + fputs("\nstatic struct pmu_para pmu_para = {\n", out); + + sp = target; + for (le = list_first(&sp->entries); le; + le = list_next(&sp->entries, le)) { + ep = container_of(le, struct script_entry, entries); + + if (!out_member(out, ep->name, 0, ep)) { + pr_err("target: %s: invalid field\n", ep->name); + ret = 0; + } + } + + foreach_member(mp, pmu_members) { + ep = script_find_entry(pmu_para, mp->name); + if (!ep) + continue; + + key = (mp->translation) ? mp->translation : mp->name+4; + if (!out_member(out, key, mp->mode, ep)) { + pr_err("pmu_para: %s: invalid field\n", mp->name); + ret = 0; + } + } + + fputs("};\n", out); + fputs("\nint sunxi_pmu_init(void)\n" + "{\n\treturn PMU_init(&pmu_para);\n}\n", + out); + return ret; + + (void) pmu_para; +} +#endif + +int script_generate_uboot(FILE *out, const char *UNUSED(filename), + struct script *script) +{ + struct { + const char *name; + struct script_section *sp; + } sections[] = { + { "dram_para", NULL }, +#if 0 + { "target", NULL }, + { "pmu_para", NULL }, +#endif + }; + + for (unsigned i=0; i\n" +#if 0 + "#include \n" +#endif + "#include \n\n", + out); + + generate_dram_struct(out, sections[0].sp); +#if 0 + generate_pmu_struct(out, sections[1].sp, sections[2].sp); +#endif + + return 1; +} diff --git a/script_uboot.h b/script_uboot.h new file mode 100644 index 0000000..abfca14 --- /dev/null +++ b/script_uboot.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 Alejandro Mery + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUBXI_TOOLS_SCRIPT_UBOOT_H +#define _SUBXI_TOOLS_SCRIPT_UBOOT_H + +int script_generate_uboot(FILE *out, const char *filename, struct script *script); + +#endif diff --git a/soc_info.c b/soc_info.c new file mode 100644 index 0000000..66074ab --- /dev/null +++ b/soc_info.c @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2012 Henrik Nordstrom + * Copyright (C) 2015 Siarhei Siamashka + * Copyright (C) 2016 Bernhard Nortmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/********************************************************************** + * SoC information and retrieval of soc_sram_info + **********************************************************************/ +#include "soc_info.h" + +#include +#include + +/* + * The FEL code from BROM in A10/A13/A20 sets up two stacks for itself. One + * at 0x2000 (and growing down) for the IRQ handler. And another one at 0x7000 + * (and also growing down) for the regular code. In order to use the whole + * 32 KiB in the A1/A2 sections of SRAM, we need to temporarily move these + * stacks elsewhere. And the addresses 0x7D00-0x7FFF contain something + * important too (overwriting them kills FEL). On A10/A13/A20 we can use + * the SRAM sections A3/A4 (0x8000-0xBFFF) for this purpose. + */ +sram_swap_buffers a10_a13_a20_sram_swap_buffers[] = { + /* 0x1C00-0x1FFF (IRQ stack) */ + { .buf1 = 0x1C00, .buf2 = 0xA400, .size = 0x0400 }, + /* 0x5C00-0x6FFF (Stack) */ + { .buf1 = 0x5C00, .buf2 = 0xA800, .size = 0x1400 }, + /* 0x7C00-0x7FFF (Something important) */ + { .buf1 = 0x7C00, .buf2 = 0xBC00, .size = 0x0400 }, + { .size = 0 } /* End of the table */ +}; + +/* + * A31 is very similar to A10/A13/A20, except that it has no SRAM at 0x8000. + * So we use the SRAM section B at 0x20000-0x2FFFF instead. In the FEL mode, + * the MMU translation table is allocated by the BROM at 0x20000. But we can + * also safely use it as the backup storage because the MMU is temporarily + * disabled during the time of the SPL execution. + */ +sram_swap_buffers a31_sram_swap_buffers[] = { + { .buf1 = 0x1800, .buf2 = 0x20000, .size = 0x800 }, + { .buf1 = 0x5C00, .buf2 = 0x20800, .size = 0x8000 - 0x5C00 }, + { .size = 0 } /* End of the table */ +}; + +/* + * A64 has 32KiB of SRAM A at 0x10000 and a large SRAM C at 0x18000. SRAM A + * and SRAM C reside in the address space back-to-back without any gaps, thus + * representing a singe large contiguous area. The BROM FEL code memory areas + * are the same as on A10/A13/A20, but just shifted by 0x10000. + * We put the backup buffers towards the end of SRAM C, in a location that + * is also available on the H5. + */ +sram_swap_buffers a64_sram_swap_buffers[] = { + /* 0x11C00-0x11FFF (IRQ stack) */ + { .buf1 = 0x11C00, .buf2 = 0x31400, .size = 0x0400 }, + /* 0x15C00-0x16FFF (Stack) */ + { .buf1 = 0x15C00, .buf2 = 0x31800, .size = 0x1400 }, + /* 0x17C00-0x17FFF (Something important) */ + { .buf1 = 0x17C00, .buf2 = 0x32c00, .size = 0x0400 }, + { .size = 0 } /* End of the table */ +}; + +/* + * Use the SRAM section at 0x44000 as the backup storage. This is the memory, + * which is normally shared with the OpenRISC core (should we do an extra check + * to ensure that this core is powered off and can't interfere?). + */ +sram_swap_buffers ar100_abusing_sram_swap_buffers[] = { + { .buf1 = 0x1800, .buf2 = 0x44000, .size = 0x800 }, + { .buf1 = 0x5C00, .buf2 = 0x44800, .size = 0x8000 - 0x5C00 }, + { .size = 0 } /* End of the table */ +}; + +/* + * A80 has 40KiB SRAM A1 at 0x10000 where the SPL has to be loaded to. The + * secure SRAM B at 0x20000 is used as backup area for FEL stacks and data. + */ +sram_swap_buffers a80_sram_swap_buffers[] = { + { .buf1 = 0x11800, .buf2 = 0x20000, .size = 0x800 }, + { .buf1 = 0x15400, .buf2 = 0x20800, .size = 0x18000 - 0x15400 }, + { .size = 0 } /* End of the table */ +}; + +/* + * H6 has 32KiB of SRAM A at 0x20000 and a large SRAM C at 0x28000. SRAM A + * and SRAM C reside in the address space back-to-back without any gaps, thus + * representing a singe large contiguous area. Everything is the same as on + * A10/A13/A20, but just shifted by 0x20000. + */ +sram_swap_buffers h6_sram_swap_buffers[] = { + /* 0x21C00-0x21FFF (IRQ stack) */ + { .buf1 = 0x21C00, .buf2 = 0x42400, .size = 0x0400 }, + /* 0x25C00-0x26FFF (Stack) */ + { .buf1 = 0x25C00, .buf2 = 0x42800, .size = 0x1400 }, + /* 0x27C00-0x27FFF (Something important) */ + { .buf1 = 0x27C00, .buf2 = 0x43c00, .size = 0x0400 }, + { .size = 0 } /* End of the table */ +}; + +/* + * V831 has 96KiB SRAM A1 at 0x20000 where the SPL has to be loaded to. + * SRAM C is continuous with SRAM A1, and both SRAMs are tried to be used + * by BROM. Memory space is allocated both from the start of SRAM A1 and + * the end of SRAM C. + * The start of SRAM C is in between these areas, and can serve as backup + * of IRQ stack, which is inside the first 32KiB of SRAM A1. Other areas + * that are critical on older SoCs seem to be already in SRAM C, which + * we do not need to preserve. + */ +sram_swap_buffers v831_sram_swap_buffers[] = { + { .buf1 = 0x21000, .buf2 = 0x38000, .size = 0x1000 }, + { .size = 0 } /* End of the table */ +}; + +/* H616 situation is the same as V831 one, except it has 32 KiB of SRAM A1. */ +sram_swap_buffers h616_sram_swap_buffers[] = { + { .buf1 = 0x21000, .buf2 = 0x52a00, .size = 0x1000 }, + { .size = 0 } /* End of the table */ +}; + +/* + * R329 has no SRAM A1, but a huge SRAM A2 at 0x100000. SPL and BROM uses + * this SRAM A2's first part like how other SoCs use SRAM A1. The sp and + * sp_irq values checked with thunk are 0x13c2c8 and 0x101400, which looks + * similar to the situation of V831, in which the stack is quite high. + */ +sram_swap_buffers r329_sram_swap_buffers[] = { + /* 0x101000-0x101400 (IRQ stack) */ + { .buf1 = 0x101000, .buf2 = 0x13bc00, .size = 0x0400 }, + { .size = 0 } /* End of the table */ +}; + +/* + * The FEL code from BROM in F1C100s also uses SRAM A in a similar way + * with A10/A13/A20. + * Unfortunately the SRAM layout of F1C100s is not documented at all, so + * we can only try by r/w under FEL mode. + * The result is that there's a contingous SRAM zone from 0x8800 to 0xb5ff. + */ +sram_swap_buffers f1c100s_sram_swap_buffers[] = { + /* 0x1C00-0x1FFF (IRQ stack) */ + { .buf1 = 0x1C00, .buf2 = 0x9000, .size = 0x0400 }, + /* 0x5C00-0x6FFF (Stack) */ + { .buf1 = 0x5C00, .buf2 = 0x9400, .size = 0x1400 }, + /* 0x7C00-0x7FFF (Something important) */ + { .buf1 = 0x7C00, .buf2 = 0xa800, .size = 0x0400 }, + { .size = 0 } /* End of the table */ +}; + +const watchdog_info wd_a10_compat = { + .reg_mode = 0x01C20C94, + .reg_mode_value = 3, +}; + +const watchdog_info wd_h3_compat = { + .reg_mode = 0x01C20Cb8, + .reg_mode_value = 1, +}; + +const watchdog_info wd_a80 = { + .reg_mode = 0x06000CB8, + .reg_mode_value = 1, +}; + +const watchdog_info wd_h6_compat = { + .reg_mode = 0x030090b8, + .reg_mode_value = 1, +}; + +const watchdog_info wd_v853_compat = { + .reg_mode = 0x020500b8, + .reg_mode_value = 0x16aa0001, +}; + +static const sid_section r40_sid_maps[] = { + SID_SECTION("chipid", 0x00, 128), + SID_SECTION("in", 0x10, 256), + SID_SECTION("ssk", 0x30, 128), + SID_SECTION("thermal", 0x40, 32), + SID_SECTION("ft_zone", 0x44, 64), + SID_SECTION("tvout", 0x4c, 128), + SID_SECTION("rssk", 0x5c, 256), + SID_SECTION("hdcp_hash",0x7c, 128), + SID_SECTION("reserved", 0x90, 896), + SID_SECTION(NULL, 0, 0), +}; + +static const sid_section h3_sid_maps[] = { + SID_SECTION("chipid", 0x00, 128), + SID_SECTION("oem_program", 0x10, 32), + SID_SECTION("nv1", 0x14, 32), + SID_SECTION("nv2", 0x18, 64), + SID_SECTION("rsakey_hash", 0x20, 160), + SID_SECTION("thermal", 0x34, 64), + SID_SECTION("renewability", 0x3c, 64), + SID_SECTION("huk", 0x44, 256), + SID_SECTION("rotpk_hash", 0x64, 256), + SID_SECTION("ssk", 0x84, 128), + SID_SECTION("rssk", 0x94, 256), + SID_SECTION("hdcp_hash", 0xb4, 128), + SID_SECTION("ek_hash", 0xc4, 128), + SID_SECTION("sn", 0xd4, 192), + SID_SECTION("nv2_backup", 0xec, 64), + SID_SECTION("lcjs", 0xf4, 32), + SID_SECTION("debug", 0xf8, 32), + SID_SECTION("chip_config", 0xfc, 32), + SID_SECTION(NULL, 0, 0), +}; + +static const sid_section h6_sid_maps[] = { + SID_SECTION("chipid", 0x00, 128), + SID_SECTION("brom_config", 0x10, 32), + SID_SECTION("thermal", 0x14, 64), + SID_SECTION("tf_zone", 0x1c, 128), + SID_SECTION("oem_program", 0x2c, 96), + SID_SECTION("mac-addr", 0x38, 64), + SID_SECTION("write_protect", 0x40, 32), + SID_SECTION("read-protect", 0x44, 32), + SID_SECTION("lcjs", 0x48, 32), + SID_SECTION("attr", 0x4c, 32), + SID_SECTION("huk", 0x50, 96), + SID_SECTION("vendor_id", 0x5c, 32), + SID_SECTION("huk2", 0x60, 128), + SID_SECTION("rotpk_hash", 0x70, 256), + SID_SECTION("ssk", 0x90, 128), + SID_SECTION("rssk", 0xa0, 256), + SID_SECTION("hdcp_hash", 0xc0, 128), + SID_SECTION("ek_hash", 0xd0, 128), + SID_SECTION("sn", 0xe0, 192), + SID_SECTION("nv1", 0xf8, 32), + SID_SECTION("nv2", 0xfc, 224), + SID_SECTION("hdcp_pkf", 0x118, 128), + SID_SECTION("hdcp_duk", 0x128, 128), + SID_SECTION("backup_key", 0x138, 576), + SID_SECTION("sck0", 0x180, 256), + SID_SECTION("sck0_mask", 0x1a0, 256), + SID_SECTION("sck1", 0x1c0, 256), + SID_SECTION("sck1_mask", 0x1e0, 256), + SID_SECTION(NULL, 0, 0) +}; + +/* Placeholder for SoCs without a known SID map */ +static const sid_section generic_2k_sid_maps[] = { + SID_SECTION("chipid", 0x00, 128), + SID_SECTION("unknown", 0x10, 1920), + SID_SECTION(NULL, 0, 0), +}; + +soc_info_t soc_info_table[] = { + { + .soc_id = 0x1623, /* Allwinner A10 */ + .name = "A10", + .scratch_addr = 0x1000, + .thunk_addr = 0xA200, .thunk_size = 0x200, + .swap_buffers = a10_a13_a20_sram_swap_buffers, + .sram_size = 48 * 1024, + .needs_l2en = true, + .sid_base = 0x01C23800, + .watchdog = &wd_a10_compat, + },{ + .soc_id = 0x1625, /* Allwinner A10s, A13, R8 */ + .name = "A13", + .scratch_addr = 0x1000, + .thunk_addr = 0xA200, .thunk_size = 0x200, + .swap_buffers = a10_a13_a20_sram_swap_buffers, + .sram_size = 48 * 1024, + .needs_l2en = true, + .sid_base = 0x01C23800, + .watchdog = &wd_a10_compat, + },{ + .soc_id = 0x1651, /* Allwinner A20 */ + .name = "A20", + .scratch_addr = 0x1000, + .thunk_addr = 0xA200, .thunk_size = 0x200, + .swap_buffers = a10_a13_a20_sram_swap_buffers, + .sram_size = 48 * 1024, + .sid_base = 0x01C23800, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_a10_compat, + },{ + .soc_id = 0x1650, /* Allwinner A23 */ + .name = "A23", + .scratch_addr = 0x1000, + .thunk_addr = 0x46E00, .thunk_size = 0x200, + .swap_buffers = ar100_abusing_sram_swap_buffers, + .sram_size = 64 * 1024, + .sid_base = 0x01C23800, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1633, /* Allwinner A31 */ + .name = "A31", + .scratch_addr = 0x1000, + .thunk_addr = 0x22E00, .thunk_size = 0x200, + .swap_buffers = a31_sram_swap_buffers, + .sram_size = 32 * 1024, + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1667, /* Allwinner A33, R16 */ + .name = "A33", + .scratch_addr = 0x1000, + .thunk_addr = 0x46E00, .thunk_size = 0x200, + .swap_buffers = ar100_abusing_sram_swap_buffers, + .sram_size = 32 * 1024, + .sid_base = 0x01C23800, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1689, /* Allwinner A64 */ + .name = "A64", + .spl_addr = 0x10000, + .scratch_addr = 0x11000, + .thunk_addr = 0x31200, .thunk_size = 0x200, + .swap_buffers = a64_sram_swap_buffers, + .sram_size = 140 * 1024, + .sid_base = 0x01C14000, + .sid_offset = 0x200, + .sid_sections = h3_sid_maps, + .rvbar_reg = 0x017000A0, + /* Check L.NOP in the OpenRISC reset vector */ + .needs_smc_workaround_if_zero_word_at_addr = 0x40004, + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1639, /* Allwinner A80 */ + .name = "A80", + .spl_addr = 0x10000, + .scratch_addr = 0x11000, + .thunk_addr = 0x23400, .thunk_size = 0x200, + .swap_buffers = a80_sram_swap_buffers, + .sram_size = 40 * 1024, + .sid_base = 0X01C0E000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_a80, + },{ + .soc_id = 0x1663, /* Allwinner F1C100s (all new sun3i?) */ + .name = "F1C100s", + .scratch_addr = 0x1000, + .thunk_addr = 0xb400, .thunk_size = 0x200, + .swap_buffers = f1c100s_sram_swap_buffers, + .sram_size = 32 * 1024, + /* No SID */ + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1673, /* Allwinner A83T */ + .name = "A83T", + .scratch_addr = 0x1000, + .mmu_tt_addr = 0x44000, + .thunk_addr = 0x46E00, .thunk_size = 0x200, + .swap_buffers = ar100_abusing_sram_swap_buffers, + .sram_size = 32 * 1024, + .sid_base = 0x01C14000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1680, /* Allwinner H3, H2+ */ + .name = "H3", + .scratch_addr = 0x1000, + .mmu_tt_addr = 0x8000, + .thunk_addr = 0xA200, .thunk_size = 0x200, + .swap_buffers = a10_a13_a20_sram_swap_buffers, + .sram_size = 108 * 1024, + .sid_base = 0x01C14000, + .sid_offset = 0x200, + .sid_fix = true, + .sid_sections = h3_sid_maps, + /* Check L.NOP in the OpenRISC reset vector */ + .needs_smc_workaround_if_zero_word_at_addr = 0x40004, + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1681, /* Allwinner V3s */ + .name = "V3s", + .scratch_addr = 0x1000, + .mmu_tt_addr = 0x8000, + .thunk_addr = 0xA200, .thunk_size = 0x200, + .swap_buffers = a10_a13_a20_sram_swap_buffers, + .sram_size = 60 * 1024, + .sid_base = 0x01C23800, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1718, /* Allwinner H5 */ + .name = "H5", + .spl_addr = 0x10000, + .scratch_addr = 0x11000, + .thunk_addr = 0x31200, .thunk_size = 0x200, + .swap_buffers = a64_sram_swap_buffers, + .sram_size = 140 * 1024, + .sid_base = 0x01C14000, + .sid_offset = 0x200, + .sid_sections = h3_sid_maps, + .rvbar_reg = 0x017000A0, + /* Check L.NOP in the OpenRISC reset vector */ + .needs_smc_workaround_if_zero_word_at_addr = 0x40004, + .watchdog = &wd_h3_compat, + },{ + .soc_id = 0x1701, /* Allwinner R40 */ + .name = "R40", + .scratch_addr = 0x1000, + .thunk_addr = 0xA200, .thunk_size = 0x200, + .swap_buffers = a10_a13_a20_sram_swap_buffers, + .sram_size = 48 * 1024, + .sid_base = 0x01C1B000, + .sid_offset = 0x200, + .sid_sections = r40_sid_maps, + .watchdog = &wd_a10_compat, + },{ + .soc_id = 0x1719, /* Allwinner A63 */ + .name = "A63", + .spl_addr = 0x20000, + .scratch_addr = 0x21000, + .thunk_addr = 0x42200, .thunk_size = 0x200, + .swap_buffers = h6_sram_swap_buffers, + .sram_size = 144 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .rvbar_reg = 0x09010040, + .watchdog = &wd_h6_compat, + },{ + .soc_id = 0x1728, /* Allwinner H6 */ + .name = "H6", + .spl_addr = 0x20000, + .scratch_addr = 0x21000, + .thunk_addr = 0x42200, .thunk_size = 0x200, + .swap_buffers = h6_sram_swap_buffers, + .sram_size = 144 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = h6_sid_maps, + .rvbar_reg = 0x09010040, + /* Check L.NOP in the OpenRISC reset vector */ + .needs_smc_workaround_if_zero_word_at_addr = 0x100004, + .watchdog = &wd_h6_compat, + },{ + .soc_id = 0x1816, /* Allwinner V536 */ + .name = "V536", + .spl_addr = 0x20000, + .scratch_addr = 0x21000, + .thunk_addr = 0x2A200, .thunk_size = 0x200, + .swap_buffers = v831_sram_swap_buffers, + .sram_size = 228 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_h6_compat, + },{ + .soc_id = 0x1817, /* Allwinner V831 */ + .name = "V831", + .spl_addr = 0x20000, + .scratch_addr = 0x21000, + .thunk_addr = 0x2A200, .thunk_size = 0x200, + .swap_buffers = v831_sram_swap_buffers, + .sram_size = 228 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_h6_compat, + },{ + .soc_id = 0x1823, /* Allwinner H616 */ + .name = "H616", + .spl_addr = 0x20000, + .scratch_addr = 0x21000, + .thunk_addr = 0x53a00, .thunk_size = 0x200, + .swap_buffers = h616_sram_swap_buffers, + .sram_size = 207 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .rvbar_reg = 0x09010040, + .watchdog = &wd_h6_compat, + },{ + .soc_id = 0x1851, /* Allwinner R329 */ + .name = "R329", + .spl_addr = 0x100000, + .scratch_addr = 0x101000, + .mmu_tt_addr = 0x130000, + .thunk_addr = 0x13ba00, .thunk_size = 0x200, + .swap_buffers = r329_sram_swap_buffers, + .sram_size = 1856 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .rvbar_reg = 0x08100040, + .watchdog = &wd_h6_compat, + },{ + .soc_id = 0x1886, /* Allwinner V853 */ + .name = "V853", + .spl_addr = 0x20000, + .scratch_addr = 0x21000, + .thunk_addr = 0x3A200, .thunk_size = 0x200, + .swap_buffers = v831_sram_swap_buffers, + .sram_size = 132 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .icache_fix = true, + .watchdog = &wd_v853_compat, + },{ + .soc_id = 0x1859, /* Allwinner D1/D1s/R528/T113-S3 */ + .name = "R528", + .spl_addr = 0x20000, + .scratch_addr = 0x21000, + .thunk_addr = 0x3a200, .thunk_size = 0x200, + .swap_buffers = v831_sram_swap_buffers, + .sram_size = 160 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .icache_fix = true, + .watchdog = &wd_v853_compat, + },{ + .soc_id = 0x1721, /* Allwinner V5 */ + .name = "V5", + .spl_addr = 0x20000, + .scratch_addr = 0x21000, + .thunk_addr = 0x42200, .thunk_size = 0x200, + .swap_buffers = h6_sram_swap_buffers, + .sram_size = 136 * 1024, + .sid_base = 0x03006000, + .sid_offset = 0x200, + .sid_sections = generic_2k_sid_maps, + .watchdog = &wd_h6_compat, + },{ + .swap_buffers = NULL /* End of the table */ + } +}; + +/* + * This generic record assumes BROM with similar properties to A10/A13/A20/A31, + * but no extra SRAM sections beyond 0x8000. It also assumes that the IRQ + * handler stack usage never exceeds 0x400 bytes. + * + * The users may or may not hope that the 0x7000-0x8000 area is also unused + * by the BROM and re-purpose it for the SPL stack. + * + * The size limit for the ".text + .data" sections is ~21 KiB. + */ +sram_swap_buffers generic_sram_swap_buffers[] = { + { .buf1 = 0x1C00, .buf2 = 0x5800, .size = 0x400 }, + { .size = 0 } /* End of the table */ +}; + +soc_info_t generic_soc_info = { + .scratch_addr = 0x1000, + .thunk_addr = 0x5680, .thunk_size = 0x180, + .swap_buffers = generic_sram_swap_buffers, +}; + +/* functions to retrieve SoC information */ + +soc_info_t *get_soc_info_from_id(uint32_t soc_id) +{ + soc_info_t *soc, *result = NULL; + + for (soc = soc_info_table; soc->swap_buffers; soc++) + if (soc->soc_id == soc_id) { + result = soc; + break; + } + + if (!result) { + printf("Warning: no 'soc_sram_info' data for your SoC (id=%04X)\n", + soc_id); + result = &generic_soc_info; + } + return result; +} + +soc_info_t *get_soc_info_from_version(struct aw_fel_version *buf) +{ + return get_soc_info_from_id(buf->soc_id); +} + +void get_soc_name_from_id(soc_name_t buffer, uint32_t soc_id) +{ + soc_info_t *soc; + for (soc = soc_info_table; soc->swap_buffers; soc++) + if (soc->soc_id == soc_id && soc->name != NULL) { + strncpy(buffer, soc->name, sizeof(soc_name_t) - 1); + return; + } + + /* unknown SoC (or name string missing), use the hexadecimal ID */ + snprintf(buffer, sizeof(soc_name_t) - 1, "0x%04X", soc_id); +} diff --git a/soc_info.h b/soc_info.h new file mode 100644 index 0000000..a7ae83e --- /dev/null +++ b/soc_info.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2015 Siarhei Siamashka + * Copyright (C) 2016 Bernhard Nortmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _SUNXI_TOOLS_SOC_INFO_H +#define _SUNXI_TOOLS_SOC_INFO_H + +#include +#include + +/* SoC version information, as retrieved by the FEL protocol */ +struct aw_fel_version { + char signature[8]; + uint32_t soc_id; /* 0x00162300 */ + uint32_t unknown_0a; /* 1 */ + uint16_t protocol; /* 1 */ + uint8_t unknown_12; /* 0x44 */ + uint8_t unknown_13; /* 0x08 */ + uint32_t scratchpad; /* 0x7e00 */ + uint32_t pad[2]; /* unused */ +} __attribute__((packed)); + +/* + * Buffer for a SoC name string. We want at least 6 + 1 characters, to store + * the hexadecimal ID "0xABCD" for unknown SoCs, plus the terminating NUL. + */ +typedef char soc_name_t[8]; + +/* + * The 'sram_swap_buffers' structure is used to describe information about + * pairwise memory regions in SRAM, the content of which needs to be exchanged + * before calling the U-Boot SPL code and then exchanged again before returning + * control back to the FEL code from the BROM. + */ +typedef struct { + uint32_t buf1; /* BROM buffer */ + uint32_t buf2; /* backup storage location */ + uint32_t size; /* buffer size */ +} sram_swap_buffers; + +/* + * Contains information on the watchdog peripheral, to enable reset + */ +typedef struct { + /* Register that needs to be written to */ + uint32_t reg_mode; + /* Value to write to trigger a reset */ + uint32_t reg_mode_value; +} watchdog_info; + +/* + * sunxi sid sections + */ +typedef struct { + const char *name; + uint32_t offset; + uint32_t size_bits; +} sid_section; + +#define SID_SECTION(_name, _offset, _size_bits) { \ + .name = _name, \ + .offset = _offset, \ + .size_bits = _size_bits, \ +} + +/* + * Each SoC variant may have its own list of memory buffers to be exchanged + * and the information about the placement of the thunk code, which handles + * the transition of execution from the BROM FEL code to the U-Boot SPL and + * back. + * + * Note: the entries in the 'swap_buffers' tables need to be sorted by 'buf1' + * addresses. And the 'buf1' addresses are the BROM data buffers, while 'buf2' + * addresses are the intended backup locations. + * + * Also for performance reasons, we optionally want to have MMU enabled with + * optimal section attributes configured (the code from the BROM should use + * I-cache, writing data to the DRAM area should use write combining). The + * reason is that the BROM FEL protocol implementation moves data using the + * CPU somewhere on the performance critical path when transferring data over + * USB. The older SoC variants (A10/A13/A20/A31/A23) already have MMU enabled + * and we only need to adjust section attributes. The BROM in newer SoC variants + * (A33/A83T/H3) doesn't enable MMU any more, so we need to find some 16K of + * spare space in SRAM to place the translation table there and specify it as + * the 'mmu_tt_addr' field in the 'soc_sram_info' structure. The 'mmu_tt_addr' + * address must be 16K aligned. + * + * If an SoC has the "secure boot" fuse burned, it will enter FEL mode in + * non-secure state, so with the SCR.NS bit set. Since in this mode the + * secure/non-secure state restrictions are actually observed, we suffer + * from several restrictions: + * - No access to the SID information (both via memory mapped and "register"). + * - No access to secure SRAM (SRAM A2 on H3/A64/H5). + * - No access to the secure side of the GIC, so it can't be configured to + * be accessible from non-secure world. + * - No RMR trigger on ARMv8 cores to bring the core into AArch64. + * However it has been found out that a simple "smc" call will immediately + * return from monitor mode, but with the NS bit cleared, so access to all + * secure peripherals is suddenly possible. + * The 'needs_smc_workaround_if_zero_word_at_addr' field can be used to + * have a check for this condition (reading from restricted addresses + * typically returns zero) and then activate the SMC workaround if needed. + */ +typedef struct { + uint32_t soc_id; /* ID of the SoC */ + const char *name; /* human-readable SoC name string */ + uint32_t spl_addr; /* SPL load address */ + uint32_t scratch_addr; /* A safe place to upload & run code */ + uint32_t thunk_addr; /* Address of the thunk code */ + uint32_t thunk_size; /* Maximal size of the thunk code */ + bool needs_l2en; /* Set the L2EN bit */ + uint32_t mmu_tt_addr; /* MMU translation table address */ + uint32_t sid_base; /* base address for SID registers */ + uint32_t sid_offset; /* offset for SID_KEY[0-3], "root key" */ + const sid_section *sid_sections; /* sid memory maps */ + uint32_t rvbar_reg; /* MMIO address of RVBARADDR0_L register */ + const watchdog_info *watchdog; /* Used for reset */ + bool sid_fix; /* Use SID workaround (read via register) */ + /* Use I$ workaround (disable I$ before first write to prevent stale thunk */ + bool icache_fix; + /* Use SMC workaround (enter secure mode) if can't read from this address */ + uint32_t needs_smc_workaround_if_zero_word_at_addr; + uint32_t sram_size; /* Usable contiguous SRAM at spl_addr */ + sram_swap_buffers *swap_buffers; +} soc_info_t; + + +void get_soc_name_from_id(soc_name_t buffer, uint32_t soc_id); +soc_info_t *get_soc_info_from_id(uint32_t soc_id); +soc_info_t *get_soc_info_from_version(struct aw_fel_version *buf); + +#endif /* _SUNXI_TOOLS_SOC_INFO_H */ diff --git a/sunxi-fel.1 b/sunxi-fel.1 new file mode 100644 index 0000000..42aed74 --- /dev/null +++ b/sunxi-fel.1 @@ -0,0 +1,270 @@ +.\" Manpage for sunxi-fel +.\" Copyright (C) 2018 by Andre Przywara +.TH sunxi-fel 1 "14 Jan 2022" "1.5" "sunxi-fel man page" +.SH NAME +sunxi-fel \- controlling USB BootROM protocol for Allwinner CPUs +.SH SYNOPSIS +sunxi-fel [OPTIONS] COMMAND [ARGS] +.SH DESCRIPTION +sunxi-fel is a script interface for USB communication with the BootROM of +Allwinner CPUs. + +On explicit request (typically by pressing a button often labeled "uboot" +or "recovery"), or when all other booting methods fail, the CPU's early ROM +code enters the so called FEL mode, where it will wait for USB commands sent +by some host to the Allwinner CPU's USB OTG interface. sunxi-fel implements +this FEL protocol and communicates with the ROM code, to download or upload +data and to start code execution. + +Besides simply allowing to write to or to read from device memory, sunxi-fel +also helps with more complex things, which involve uploading code, executing +it and then returning to FEL mode, to allow further data transfer or inspection. +In particular it supports loading and executing U-Boot, including the primary +SPL stage. +.SH "OPTIONS" +Those options affect general execution and should be put first, before any +actual commands. +.sp +.B \-h, \-\-help +.RS 4 +Print a help message and exit. +.RE +.sp +.B \-v, \-\-verbose +.RS 4 +Enable verbose logging. +.RE +.sp +.B \-p, \-\-progress +.RS 4 +"write" transfers show a progress bar. +.RE +.sp +.B \-l, \-\-list +.RS 4 +Enumerate all (USB) FEL devices and exit. +.RE +.sp +.B \-d, \-\-dev bus:devnum +.RS 4 +Use specific USB bus and device number +.RE +.sp +.B \-\-sid SID +.RS 4 +Select a device by its SID key (exact match). The SID key of a particular +device can be queried using the "sid" command. +.RE +.SH "SUNXI-FEL COMMANDS" +sunxi-fel can take several commands, each followed by their parameters, and +will execute them in order. The only exception is the "uboot" command, +which will delay launching U-Boot until all other commands have been executed. +.sp +Please note that accessing any part of the DRAM will not work until the +DRAM controller has been initialized. This can be achieved by uploading and +executing a suitable U-Boot SPL image, using the "spl" command. Trying to +access DRAM before that will most likely hang. +.sp +Any numbers given as parameters to those commands can be prefixed with "0x" +to denote hexadecimal notation or "0" to start an octal number. +They are interpretated as decimal numbers otherwise. +.PP +.B spl +.RS 4 +Load and execute U-Boot SPL. +.sp +Upload the given binary file to the appropriate SRAM location, carefully +moving the FEL stack out of the way in the process. The binary is then +executed. When it returns, the FEL stack is restored and execution is +transferred back to the BootROM's FEL routine. +.sp +If the file additionally contains a main U-Boot binary +(u-boot-sunxi-with-spl.bin), this command also transfers that +to memory, using the load address stored in the image file, but won't execute +it. +.RE +.PP +.B uboot +.RS 4 +like "spl", but actually starts U-Boot. U-Boot execution will take place +when the fel utility exits. This allows combining "uboot" with further "write" +commands, to transfer other files possibly needed for the boot. +.RE +.PP +.B hex[dump]
+.RS 4 +Hexadecimal memory dump. Dumps bytes of the memory region starting at +
. The context will be displayed as a hexdump, suitable for human +inspection. +.RE +.PP +.B dump
+.RS 4 +Binary memory dump. Dumps bytes of the memory region starting at +
. The context will be dumped "as is" to the standard output, so it +can be redirected to a file and processed as binary data. +.RE +.PP +.B exe[cute]
+.RS 4 +Start executing code at
in memory on the device. +.RE +.PP +.B reset64
+.RS 4 +Request a warm reset of the core, starting execution in the 64-bit AArch64 +execution state, at
. +.RE +.PP +.B wdreset +.RS 4 +Reset the device by triggering the watchdog with the shortest possible period. +This will reset the whole SoC, including all on-SoC peripherals, but might not +affect on-board devices like PMICs or PHYs. +.RE +.PP +.B memmove +.RS 4 +Copy bytes within device memory, from to . +.RE +.PP +.B readl
+.RS 4 +Read a 32-bit value from device memory at
. The value will be output +as a hexadecimal number, prefixed with "0x". The address needs to be 4-byte +aligned. +.RE +.PP +.B writel
+.RS 4 +Write the given 32-bit to device memory at
. +.RE +.PP +.B read
+.RS 4 +Write memory contents into file. Reads bytes from memory at
+and writes the content into . +.RE +.PP +.B write
+.RS 4 +Store file contents into memory. Writes the entire content of into +memory at
. +.RE +.PP +.B write-with-progress +.RS 4 +Display a textual progress bar while writing to the device. Same as "write" +with the "-p" parameter. +.RE +.PP +.B write-with-gauge +.RS 4 +Same as write, but write the progress in percentages to the standard output. +This can be piped to any "dialog" compatible program, when using the --gauge +widget. +.RE +.PP +.B write-with-xgauge +.RS 4 +Same as write-with-gauge, but with extended gauge output. This can be piped to +any "dialog" compatible program, using the --gauge widget. Aside from updating +the current progress in percents, it also updates the number of bytes written +and gives an estimated time to completion. +.RE +.PP +.B multi[write] # ... +.RS 4 +Like "write-with-progress", but with multiple load adddresses and files, +all sharing the same progress bar. +.RE +.PP +.B multi[write]-with-gauge ... +.RS 4 +Like "write-with-gauge", but with multiple load adddresses and files, +all sharing the same progress bar. +.RE +.PP +.B multi[write]-with-xgauge ... +.RS 4 +Like "write-with-xgauge", but with multiple load adddresses and files, +all sharing the same progress bar. +.RE +.PP +.B echo-gauge "some text" +.RS 4 +Update prompt/caption for gauge output. This outputs a command to be +interpreted by "dialog" to change the caption text. +.RE +.PP +.B ver[sion] +.RS 4 +Show the BROM version. This prints some static data, among other things +containing the detected SoC. Can be used to verify a FEL connection is working. +.RE +.PP +.B sid +.RS 4 +Retrieve and output the 128-bit SID key. This key contains some form of serial +number, which should be unique to each chip (although there have been reports +of same SIDs for particular batches of chips). +.RE +.PP +.B sid-registers +.RS 4 +As the "sid" command above, but use the alternative MMIO register access method +on the device. There are SoCs that require this method due to bugs in the SID +implementation, those known will automatically choose this workaround when using +the "sid" command. This command here is to test new SoCs for compliance. +.RE +.PP +.B sid-dump +.RS 4 +Read the entire SID eFuses array and dump its content. For SoCs with a known +eFuses layout, this will annotate the known regions. +.RE +.PP +.B clear
+.RS 4 +Clear bytes of memory starting at
(filling with zeroes). +.RE +.PP +.B fill
+.RS 4 +Fills bytes of memory starting at
with the byte . +.RE +.PP +.B spiflash-info +.RS 4 +Retrieves basic information about a SPI flash chip attached to the SPI0 pins. +This is using the same method as the BootROM does, to accesses the same storage +that the device could boot from. + +Prints the manufacturer of the flash chip and +its capacity. Should also be used to detect the presence of a SPI flash chip. +.RE +.PP +.B spiflash-read +.RS 4 +Reads bytes starting from offset of a SPI flash chip, storing +the result into . +.RE +.PP +.B spiflash-write +.RS 4 +Reads and stores its content in the SPI flash, starting at offset . +.RE +.SH EXAMPLES +.RS 4 +\fB$\fR sunxi-fel -v -p ver +.RE +.sp +.RS 4 +\fB$\fR sunxi-fel uboot u-boot-sunxi-with-spl.bin +.RE +.sp +.RS 4 +\fB$\fR sunxi-fel -v -p spl sunxi-spl.bin write 0x44000 bl31.bin write 0x4a000000 u-boot.bin reset64 0x44000 +.RE +.SH AUTHOR +Andre Przywara diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..45d780e --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,62 @@ +# +# tests/Makefile +# +# Copyright (C) 2016 Bernhard Nortmann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +BOARDS_URL := https://github.com/linux-sunxi/sunxi-boards/archive/master.zip +BOARDS_DIR := sunxi-boards + +check: check_all_fex coverage + +# Conversion cycle (.fex -> .bin -> .fex) test for all sunxi-boards +check_all_fex: $(BOARDS_DIR)/README unify-fex + ./test_all_fex.sh $(BOARDS_DIR) + +coverage: $(BOARDS_DIR)/README + # Usage help / invocation with no args + ../sunxi-fexc -? 2> /dev/null ; exit 0 + # Improve code coverage for corner cases (e.g. erroneous parameters) + ./test_fex2bin_corner_cases.sh + ./test_bin2fex_corner_cases.sh + +# Retrieve and extract sunxi-boards archive (containing all .fex) +$(BOARDS_DIR).zip: + curl -fLsS -o $@ $(BOARDS_URL) +$(BOARDS_DIR)/README: $(BOARDS_DIR).zip + @echo Extracting $< ... + unzip -q $< + mv sunxi-boards-master $(BOARDS_DIR) + touch -r $(BOARDS_DIR) $< + cat patches/*.patch | patch -p1 + +unify-fex: unify-fex.c + $(CC) -Wall -Werror -o $@ $< + +clean: + rm -rf $(BOARDS_DIR).zip $(BOARDS_DIR) unify-fex + +# +# Dedicated rule for Travis CI test of sunxi-boards. This assumes that the +# sunxi-tools source (archive) was extracted into a subdir below the working +# directory, meaning that BOARDS_DIR should be "../.." +# +sunxi-boards_CI: unify-fex + # compile sunxi-fexc, link bin2fex and fex2bin + make -C .. bin2fex fex2bin + # apply patches to BOARDS_DIR, ignore mismatches + cat patches/*.patch | patch --forward -r- -p2 -d $(BOARDS_DIR) || true + # and finally run the tests + ./test_all_fex.sh $(BOARDS_DIR) diff --git a/tests/fextest.sh b/tests/fextest.sh new file mode 100755 index 0000000..ed68f60 --- /dev/null +++ b/tests/fextest.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Copyright (C) 2016 Bernhard Nortmann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +echo $0 $* +FEX2BIN=../fex2bin +BIN2FEX=../bin2fex +FEX=$1 +BIN=${FEX/%.fex/.bin} +REVERSE=${FEX/%.fex/.new} +${FEX2BIN} ${FEX} ${BIN} +${BIN2FEX} ${BIN} ${REVERSE} +# preprocess .fex, compare it to the bin2fex output +if ./unify-fex ${FEX} | diff -uwB - ${REVERSE}; then + # if successful, clean up the output files + rm -f ${BIN} ${REVERSE} +else + echo '***' + echo "*** ERROR processing ${FEX}" + echo '***' + exit 1 +fi diff --git a/tests/test_all_fex.sh b/tests/test_all_fex.sh new file mode 100755 index 0000000..2ac3367 --- /dev/null +++ b/tests/test_all_fex.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright (C) 2016 Bernhard Nortmann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +FEXFILES=fexfiles.lst +find $1 -name '*.fex' > ${FEXFILES} +while read fex; do + ./fextest.sh ${fex} || exit +done <${FEXFILES} +rm -f ${FEXFILES} diff --git a/tests/test_bin2fex_corner_cases.sh b/tests/test_bin2fex_corner_cases.sh new file mode 100755 index 0000000..d4f1724 --- /dev/null +++ b/tests/test_bin2fex_corner_cases.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# +# === Test errors / corner cases of "bin2fex", improving on code coverage === +# +# Copyright (C) 2016 Bernhard Nortmann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +BIN2FEX=../bin2fex +TESTFILE=sunxi-boards/sys_config/a10/a10-olinuxino-lime +# use sunxi-fexc in "fex2bin" mode, testing explicit parameters at the same time +FEX2BIN="../sunxi-fexc -v -q -I fex -O bin" + +${FEX2BIN} ${TESTFILE}.fex ${TESTFILE}.bin +# have bin2fex explicitly read /dev/stdin, to force use of fexc.c's "read_all()" +cat ${TESTFILE}.bin | ${BIN2FEX} /dev/stdin > /dev/null +rm -f ${TESTFILE}.bin diff --git a/tests/test_fex2bin_corner_cases.sh b/tests/test_fex2bin_corner_cases.sh new file mode 100755 index 0000000..98f4b5d --- /dev/null +++ b/tests/test_fex2bin_corner_cases.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# +# === Test errors / corner cases of "fex2bin", improving on code coverage === +# +# Copyright (C) 2016 Bernhard Nortmann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +FEX2BIN=../fex2bin + +function expect () { + OUT=`${FEX2BIN} 2>&1` + if (! echo ${OUT} | grep -q "$1"); then + echo ERROR: Expected substring \"$1\" not found in output: + echo ${OUT} + exit 1 + fi + #echo ${OUT} +} + +# missing section, CRLF line ending +echo -e "foobar\r\n" | expect "data must follow a section" + +# malformed sections +expect "incomplete section declaration" <<-EOF + [foobar +EOF +expect "invalid character at 5" <<-EOF + [foo#bar] +EOF + +# invalid entry +expect "invalid character at 4" <<-EOF + [foo] + bar +EOF + +# bad port specifiers +expect "parse error at 12" <<-EOF + [foo] + bar = port:P@0 +EOF +expect "invalid character at 14" <<-EOF + [foo] + bar = port:PA* +EOF +expect "port out of range at 14" <<-EOF + [foo] + bar = port:PA666 +EOF +expect "value out of range at 17" <<-EOF + [foo] + bar = port:PA00<-1> +EOF +expect "invalid character at 18" <<-EOF + [foo] + bar = port:PA00<0 > +EOF + +# bad = pairs +expect "invalid character at 8" <<-EOF + [foo] + bar = 0* +EOF +expect "value out of range" <<-EOF + [foo] + bar = 4294967296 +EOF +expect "unquoted value 'bad', assuming string" <<-EOF + [foo] + bar = bad +EOF + +# test truncation of very long identifiers +${FEX2BIN} > /dev/null <<-EOF + [an_overly_long_section_name_to_truncate] + an_overly_long_entry_name_to_truncate = 0 +EOF diff --git a/tests/unify-fex.c b/tests/unify-fex.c new file mode 100644 index 0000000..57da1c2 --- /dev/null +++ b/tests/unify-fex.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016 Bernhard Nortmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * unify-fex.c + * + * A utility program to do some standard transformations on .fex files, + * to allow simpler (diff) comparison with the output of bin2fex. + */ + +#include +#include +#include +#include +#include + +/* string macro to determine if str starts with a given literal */ +#define starts(str, literal) \ + (strncmp(str, "" literal, sizeof(literal) - 1) == 0) + +int main(int argc, char **argv) +{ + FILE *input = stdin; + char line[1024]; + char *c, *p; + int64_t num; + + if (argc >= 2) { + input = fopen(argv[1], "r"); + if (!input) { + perror("failed to open input file"); + exit(EXIT_FAILURE); + } + } + + /* loop over all input lines, output goes to stdout */ + while (fgets(line, sizeof(line), input)) { + + /* strip all whitespace (even CR/LF) from the input line */ + for (c = p = line; *p; p++) { + if (!isspace(*p)) + *c++ = *p; + } + *c = '\0'; + + if (*line == ';' || *line == '#') + /* This is a comment line, simply ignore it */ + continue; + if (*line == ':') + continue; /* suspect malformed comment, ignore */ + + if ((p = strchr(line, '='))) { + /* This is a = line, reformat it */ + c = strdup(p + 1); + sprintf(p, " = %s", c); + free(c); + p += 3; /* have p point to the beginning of */ + + if (starts(p, "port:")) { + if (p[5] == 'P') { /* port:P... */ + /* get pin number (including bank) */ + num = ((p[6] - 'A') << 5) + strtoll(p + 7, &c, 10); + c = strdup(c); + sprintf(p, "port:P%c%02" PRId64 "%s", 'A' + (int)(num >> 5), num & 0x1F, c); + free(c); + + /* check angle brackets to determine options count */ + num = 0; + for (c = p + 9; *c; c++) { + if (*c == '<') + num++; + } + /* append "" for missing options */ + c = strrchr(p, '\0'); + while (num < 4) { + c += sprintf(c, ""); + num++; + } + } + } else { + /* + * fix formatting of numeric values, depending on the keyword + * these are a bit nasty, since they vary wildly between hex + * and decimal - see decompile_single_mode() in script_fex.c + */ + num = strtoll(p, NULL, 0); + if (num || *p == '0') { + int hex = starts(line, "csi_twi_addr"); + hex |= starts(line, "ctp_twi_addr"); + hex |= starts(line, "dram_baseaddr"); + hex |= starts(line, "dram_emr"); + hex |= starts(line, "dram_tpr"); + hex |= starts(line, "dram_zq"); + hex |= starts(line, "g2d_size"); + hex |= starts(line, "gsensor_twi_addr"); + hex |= starts(line, "lcd_gamma_tbl_"); + hex |= starts(line, "rtp_press_threshold "); + hex |= starts(line, "rtp_sensitive_level"); + hex |= starts(line, "tkey_twi_addr"); + + /* large decimals will be decompiled as negative */ + if (!hex && num >= 2147483648LL) + num -= 4294967296LL; + + sprintf(p, hex ? "0x%" PRIx64 : "%" PRId64, num); + } else { + /* + * We expect all other (= non-numeric) values + * to be strings, always quote them. + */ + if (*p && (*p != '"')) { + c = strdup(p); + sprintf(p, "\"%s\"", c); + free(c); + } + /* Remove a trailing semicolon. */ + c = strchr(p, 0); + if (*--c == ';') + *c = '\0'; + } + } + } + + puts(line); + } + + if (ferror(input)) { + perror("file read error"); + exit(EXIT_FAILURE); + } + + fclose(input); + return EXIT_SUCCESS; +} diff --git a/thunks/Makefile b/thunks/Makefile new file mode 100644 index 0000000..27ba55d --- /dev/null +++ b/thunks/Makefile @@ -0,0 +1,35 @@ +# +# build "preprocessed" .h files for inclusion of ARM scratch code +# + +SPL_THUNK := fel-to-spl-thunk.h +THUNKS := clrsetbits.h +THUNKS += memcpy.h +THUNKS += readl_writel.h +THUNKS += rmr-thunk.h +THUNKS += sid_read_root.h + +all: $(SPL_THUNK) $(THUNKS) +# clean up object files afterwards + rm -f *.o + +# This empty prerequisite enforces a rebuild of all the headers on every run +FORCE: + +# If not specified explicitly: try to guess a suitable ARM toolchain prefix +CROSS_COMPILE ?= $(shell ../find-arm-gcc.sh) + +AS := $(CROSS_COMPILE)as +OBJDUMP := $(CROSS_COMPILE)objdump + +AWK_O_TO_H := LC_ALL=C awk -f objdump_to_h.awk + +# The SPL thunk requires a different output format. The "style" variable for +# awk controls this, and causes the htole32() conversion to be omitted. +fel-to-spl-thunk.h: fel-to-spl-thunk.S FORCE + $(AS) -o $(subst .S,.o,$<) -march=armv5te $< + $(OBJDUMP) -d $(subst .S,.o,$<) | $(AWK_O_TO_H) -v style=old > $@ + +$(THUNKS): %.h: %.S FORCE + $(AS) -o $(subst .S,.o,$<) -march=armv5te $< + $(OBJDUMP) -d $(subst .S,.o,$<) | $(AWK_O_TO_H) > $@ diff --git a/thunks/README.md b/thunks/README.md new file mode 100644 index 0000000..e9ca949 --- /dev/null +++ b/thunks/README.md @@ -0,0 +1,20 @@ + +# thunks/README.md + +This directory contains assembly sources for ARM [thunk] code, and +a corresponding _Makefile_. The idea is that the resulting binary routines +can be transferred to a suitable target device and then executed 'remotely', +usually via `sunxi-fel`. + +Normally you don't need to change or (re)build anything within this folder. +Currently our main build process (via the parent directory's _Makefile_) +only includes `fel-to-spl-thunk.h` directly. Other _.h_ files are provided +**just for reference**. The main purpose of this folder is simply keeping +track of _.S_ sources, to help with possible future maintenance of the +various code snippets. + +Please note that any files lacking explicit license information are intended +to be covered by the project's [overall license](../LICENSE.md) (GPLv2). + + +[thunk]: https://en.wikipedia.org/wiki/Thunk#Interoperability diff --git a/thunks/clrsetbits.S b/thunks/clrsetbits.S new file mode 100644 index 0000000..d148d3c --- /dev/null +++ b/thunks/clrsetbits.S @@ -0,0 +1,17 @@ +/* + * Thunk code to assist with bitwise operations (set/clear) via FEL + */ + +fel_clrsetbits_le32: + ldr r0, 1f /* address */ + ldr r1, [r0] /* load value */ + ldr r2, 2f /* clrbits mask */ + bic r1, r2 /* clear bits, post-increment r1 */ + ldr r2, 3f /* setbits mask */ + orr r1, r2 /* set bits (logical "or") */ + str r1, [r0] /* store result */ + bx lr + +1: .word 0 /* addr */ +2: .word 0 /* clrbits (= bits to clear) */ +3: .word 0 /* setbits (= bits to set) */ diff --git a/thunks/clrsetbits.h b/thunks/clrsetbits.h new file mode 100644 index 0000000..4f76ae1 --- /dev/null +++ b/thunks/clrsetbits.h @@ -0,0 +1,9 @@ + /* : */ + htole32(0xe59f0018), /* 0: ldr r0, [pc, #24] */ + htole32(0xe5901000), /* 4: ldr r1, [r0] */ + htole32(0xe59f2014), /* 8: ldr r2, [pc, #20] */ + htole32(0xe1c11002), /* c: bic r1, r1, r2 */ + htole32(0xe59f2010), /* 10: ldr r2, [pc, #16] */ + htole32(0xe1811002), /* 14: orr r1, r1, r2 */ + htole32(0xe5801000), /* 18: str r1, [r0] */ + htole32(0xe12fff1e), /* 1c: bx lr */ diff --git a/thunks/fel-to-spl-thunk.S b/thunks/fel-to-spl-thunk.S new file mode 100644 index 0000000..577dd73 --- /dev/null +++ b/thunks/fel-to-spl-thunk.S @@ -0,0 +1,162 @@ +/* + * Copyright © 2015 Siarhei Siamashka + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +.arm + +BUF1 .req r0 +BUF2 .req r1 +TMP1 .req r2 +TMP2 .req r3 +SWAPTBL .req r4 +FULLSIZE .req r5 +BUFSIZE .req r6 +CHECKSUM .req r7 +SPL_ADDR .req r8 + +entry_point: + b setup_stack + +stack_begin: + .space 32, 0xff +stack_end: + nop + + /* A function, which walks the table and swaps all buffers */ +swap_all_buffers: + adr SWAPTBL, appended_data + 4 +swap_next_buffer: + ldr BUF1, [SWAPTBL], #4 + ldr BUF2, [SWAPTBL], #4 + ldr BUFSIZE, [SWAPTBL], #4 + cmp BUFSIZE, #0 + bxeq lr +swap_next_word: + ldr TMP1, [BUF1] + ldr TMP2, [BUF2] + subs BUFSIZE, BUFSIZE, #4 + str TMP1, [BUF2], #4 + str TMP2, [BUF1], #4 + bne swap_next_word + b swap_next_buffer + +setup_stack: /* Save the original SP, LR and CPSR to stack */ + ldr SPL_ADDR, appended_data + adr BUF1, stack_end + str sp, [BUF1, #-4]! + mov sp, BUF1 + mrs TMP1, cpsr + push {TMP1, lr} + + /* Disable IRQ and FIQ */ + orr TMP1, #0xc0 + msr cpsr_c, TMP1 + + /* Check if the instructions or data cache is enabled */ + mrc p15, 0, TMP1, c1, c0, 0 + tst TMP1, #(1 << 2) + tsteq TMP1, #(1 << 12) + bne cache_is_unsupported + + bl swap_all_buffers + +verify_checksum: + ldr CHECKSUM, checksum_seed + mov BUF1, SPL_ADDR + ldr FULLSIZE, [BUF1, #16] +check_next_word: + ldr TMP1, [BUF1], #4 + subs FULLSIZE, FULLSIZE, #4 + add CHECKSUM, CHECKSUM, TMP1 + bne check_next_word + + ldr TMP1, [SPL_ADDR, #12] + subs CHECKSUM, CHECKSUM, TMP1, lsl #1 + bne checksum_is_bad + + /* Change 'eGON.BT0' -> 'eGON.FEL' */ + ldr TMP1, egon_fel_str + str TMP1, [SPL_ADDR, #8] + + /* + * Call the SPL code, but before that make sure the CPU sees the + * recently uploaded code. This requires a DSB and ISB. + * The "dsb" and "isb" *instructions* are not available in ARMv5TE, + * but at least for DSB we can use the CP15 register encoding. This + * works for ARMv7 and v8 as well, because we have checked our SCTLR + * before (in fel.c), so we know that CP15BEN is set. + * The ARM926 core does not implement ISB, instead the TRM recommends + * just a branch to achieve the same "flush the pipeline" effect. + * As just this is not sufficient for later cores, check the MIDR + * register, and do the DSB only for ARMv6 or later. + * The input register for the CP15 instruction is ignored. + */ + mcr p15, 0, TMP1, c7, c10, 4 /* CP15DSB */ + mrc p15, 0, TMP1, c0, c0, 0 /* read MIDR */ + and TMP1, TMP1, #(0xf << 16) /* architecture */ + cmp TMP1, #(0x6 << 16) /* ARMv5TEJ */ + mcrgt p15, 0, TMP1, c7, c5, 4 /* CP15ISB, if > ARMv5TEJ */ + blx SPL_ADDR + + /* Return back to FEL */ + b return_to_fel + +cache_is_unsupported: + /* Bail out if cache is enabled and change 'eGON.BT0' -> 'eGON.???' */ + ldr TMP1, cache_enabled_str + str TMP1, [SPL_ADDR, #8] + b return_to_fel_noswap + +checksum_is_bad: + /* The checksum test failed, so change 'eGON.BT0' -> 'eGON.BAD' */ + ldr TMP1, checksum_failed_str + str TMP1, [SPL_ADDR, #8] + +return_to_fel: + bl swap_all_buffers +return_to_fel_noswap: + pop {TMP1, lr} + msr cpsr_c, TMP1 /* Restore the original CPSR */ + ldr sp, [sp] + bx lr + +checksum_seed: + .word 0x5f0a6c39 +egon_fel_str: + .ascii ".FEL" +cache_enabled_str: + .ascii ".???" +checksum_failed_str: + .ascii ".BAD" + +appended_data: +/* + * The appended data uses the following format: + * + * struct { + * uint32_t spl_addr; + * sram_swap_buffers swaptbl[]; + * }; + * + * More details about the 'spl_addr' variable and the 'sram_swap_buffers' + * struct can be found in the 'fel.c' source file. + */ diff --git a/thunks/fel-to-spl-thunk.h b/thunks/fel-to-spl-thunk.h new file mode 100644 index 0000000..47fe276 --- /dev/null +++ b/thunks/fel-to-spl-thunk.h @@ -0,0 +1,86 @@ + /* : */ + 0xea000015, /* 0: b 5c */ + /* : */ + 0xffffffff, /* 4: .word 0xffffffff */ + 0xffffffff, /* 8: .word 0xffffffff */ + 0xffffffff, /* c: .word 0xffffffff */ + 0xffffffff, /* 10: .word 0xffffffff */ + 0xffffffff, /* 14: .word 0xffffffff */ + 0xffffffff, /* 18: .word 0xffffffff */ + 0xffffffff, /* 1c: .word 0xffffffff */ + 0xffffffff, /* 20: .word 0xffffffff */ + /* : */ + 0xe1a00000, /* 24: nop */ + /* : */ + 0xe28f40e8, /* 28: add r4, pc, #232 */ + /* : */ + 0xe4940004, /* 2c: ldr r0, [r4], #4 */ + 0xe4941004, /* 30: ldr r1, [r4], #4 */ + 0xe4946004, /* 34: ldr r6, [r4], #4 */ + 0xe3560000, /* 38: cmp r6, #0 */ + 0x012fff1e, /* 3c: bxeq lr */ + /* : */ + 0xe5902000, /* 40: ldr r2, [r0] */ + 0xe5913000, /* 44: ldr r3, [r1] */ + 0xe2566004, /* 48: subs r6, r6, #4 */ + 0xe4812004, /* 4c: str r2, [r1], #4 */ + 0xe4803004, /* 50: str r3, [r0], #4 */ + 0x1afffff9, /* 54: bne 40 */ + 0xeafffff3, /* 58: b 2c */ + /* : */ + 0xe59f80b0, /* 5c: ldr r8, [pc, #176] */ + 0xe24f0044, /* 60: sub r0, pc, #68 */ + 0xe520d004, /* 64: str sp, [r0, #-4]! */ + 0xe1a0d000, /* 68: mov sp, r0 */ + 0xe10f2000, /* 6c: mrs r2, CPSR */ + 0xe92d4004, /* 70: push {r2, lr} */ + 0xe38220c0, /* 74: orr r2, r2, #192 */ + 0xe121f002, /* 78: msr CPSR_c, r2 */ + 0xee112f10, /* 7c: mrc 15, 0, r2, cr1, cr0, {0} */ + 0xe3120004, /* 80: tst r2, #4 */ + 0x03120a01, /* 84: tsteq r2, #4096 */ + 0x1a000013, /* 88: bne dc */ + 0xebffffe5, /* 8c: bl 28 */ + /* : */ + 0xe59f706c, /* 90: ldr r7, [pc, #108] */ + 0xe1a00008, /* 94: mov r0, r8 */ + 0xe5905010, /* 98: ldr r5, [r0, #16] */ + /* : */ + 0xe4902004, /* 9c: ldr r2, [r0], #4 */ + 0xe2555004, /* a0: subs r5, r5, #4 */ + 0xe0877002, /* a4: add r7, r7, r2 */ + 0x1afffffb, /* a8: bne 9c */ + 0xe598200c, /* ac: ldr r2, [r8, #12] */ + 0xe0577082, /* b0: subs r7, r7, r2, lsl #1 */ + 0x1a00000b, /* b4: bne e8 */ + 0xe59f2048, /* b8: ldr r2, [pc, #72] */ + 0xe5882008, /* bc: str r2, [r8, #8] */ + 0xee072f9a, /* c0: mcr 15, 0, r2, cr7, cr10, {4} */ + 0xee102f10, /* c4: mrc 15, 0, r2, cr0, cr0, {0} */ + 0xe202280f, /* c8: and r2, r2, #983040 */ + 0xe3520806, /* cc: cmp r2, #393216 */ + 0xce072f95, /* d0: mcrgt 15, 0, r2, cr7, cr5, {4} */ + 0xe12fff38, /* d4: blx r8 */ + 0xea000004, /* d8: b f0 */ + /* : */ + 0xe59f2028, /* dc: ldr r2, [pc, #40] */ + 0xe5882008, /* e0: str r2, [r8, #8] */ + 0xea000002, /* e4: b f4 */ + /* : */ + 0xe59f2020, /* e8: ldr r2, [pc, #32] */ + 0xe5882008, /* ec: str r2, [r8, #8] */ + /* : */ + 0xebffffcc, /* f0: bl 28 */ + /* : */ + 0xe8bd4004, /* f4: pop {r2, lr} */ + 0xe121f002, /* f8: msr CPSR_c, r2 */ + 0xe59dd000, /* fc: ldr sp, [sp] */ + 0xe12fff1e, /* 100: bx lr */ + /* : */ + 0x5f0a6c39, /* 104: .word 0x5f0a6c39 */ + /* : */ + 0x4c45462e, /* 108: .word 0x4c45462e */ + /* : */ + 0x3f3f3f2e, /* 10c: .word 0x3f3f3f2e */ + /* : */ + 0x4441422e, /* 110: .word 0x4441422e */ diff --git a/thunks/memcpy.S b/thunks/memcpy.S new file mode 100644 index 0000000..e280f41 --- /dev/null +++ b/thunks/memcpy.S @@ -0,0 +1,70 @@ +/* + * copy "upwards", increasing destination and source addresses + */ +fel_memcpy_up: + ldr r0, 1f /* dst_addr */ + ldr r1, 2f /* src_addr */ + ldr r2, 3f /* bytes */ + sub r3, r1, r0 + tst r3, #3 /* test LSB for word alignment */ + bne copyup_tail /* unaligned access, copy byte-wise */ +copyup_head: + tst r1, #3 /* word boundary? */ + beq copyup_loop + ldrb r3, [r1], #1 /* load and post-inc */ + strb r3, [r0], #1 /* store and post-inc */ + subs r2, #1 /* r2 -= 1 */ + bpl copyup_head + bx lr /* early return on small byte count (r2 < 0) */ +copyup_loop: + subs r2, #4 /* r2 -= 4 */ + ldrpl r3, [r1], #4 /* load and post-inc */ + strpl r3, [r0], #4 /* store and post-inc */ + bpl copyup_loop /* while (r2 >= 0) */ + add r2, #4 /* r2 = remaining byte count */ +copyup_tail: + subs r2, #1 /* r2 -= 1 */ + bxmi lr /* return on (r2 < 0) */ + ldrb r3, [r1], #1 /* load and post-inc */ + strb r3, [r0], #1 /* store and post-inc */ + b copyup_tail + +1: .word 0 /* dst_addr */ +2: .word 0 /* src_addr */ +3: .word 0 /* bytes */ + +/* + * copy "downwards", using base-relative indexing + */ +fel_memcpy_down: + ldr r0, 1f /* dst_addr */ + ldr r1, 2f /* src_addr */ + ldr r2, 3f /* bytes */ + sub r3, r0, r1 + tst r3, #3 /* test LSB for word alignment */ + bne copydn_tail /* unaligned access, copy byte-wise */ +copydn_head: + add r3, r1, r2 /* r3 = r1 + r2, for alignment check */ + tst r3, #3 /* word boundary? */ + beq copydn_loop + subs r2, #1 /* r2 -= 1 */ + bxmi lr /* early return on small byte count (r2 < 0) */ + ldrb r3, [r1, r2] /* load byte */ + strb r3, [r0, r2] /* store byte */ + b copydn_head +copydn_loop: + subs r2, #4 /* r2 -= 4 */ + ldrpl r3, [r1, r2] /* load word */ + strpl r3, [r0, r2] /* store word */ + bpl copydn_loop /* while (r2 >= 0) */ + add r2, #4 /* r2 = remaining byte count */ +copydn_tail: + subs r2, #1 /* r2 -= 1 */ + bxmi lr /* return on (r2 < 0) */ + ldrb r3, [r1, r2] /* load byte */ + strb r3, [r0, r2] /* store byte */ + b copydn_tail + +1: .word 0 /* dst_addr */ +2: .word 0 /* src_addr */ +3: .word 0 /* bytes */ diff --git a/thunks/memcpy.h b/thunks/memcpy.h new file mode 100644 index 0000000..ddfeaaf --- /dev/null +++ b/thunks/memcpy.h @@ -0,0 +1,55 @@ + /* : */ + htole32(0xe59f0054), /* 0: ldr r0, [pc, #84] */ + htole32(0xe59f1054), /* 4: ldr r1, [pc, #84] */ + htole32(0xe59f2054), /* 8: ldr r2, [pc, #84] */ + htole32(0xe0413000), /* c: sub r3, r1, r0 */ + htole32(0xe3130003), /* 10: tst r3, #3 */ + htole32(0x1a00000b), /* 14: bne 48 */ + /* : */ + htole32(0xe3110003), /* 18: tst r1, #3 */ + htole32(0x0a000004), /* 1c: beq 34 */ + htole32(0xe4d13001), /* 20: ldrb r3, [r1], #1 */ + htole32(0xe4c03001), /* 24: strb r3, [r0], #1 */ + htole32(0xe2522001), /* 28: subs r2, r2, #1 */ + htole32(0x5afffff9), /* 2c: bpl 18 */ + htole32(0xe12fff1e), /* 30: bx lr */ + /* : */ + htole32(0xe2522004), /* 34: subs r2, r2, #4 */ + htole32(0x54913004), /* 38: ldrpl r3, [r1], #4 */ + htole32(0x54803004), /* 3c: strpl r3, [r0], #4 */ + htole32(0x5afffffb), /* 40: bpl 34 */ + htole32(0xe2822004), /* 44: add r2, r2, #4 */ + /* : */ + htole32(0xe2522001), /* 48: subs r2, r2, #1 */ + htole32(0x412fff1e), /* 4c: bxmi lr */ + htole32(0xe4d13001), /* 50: ldrb r3, [r1], #1 */ + htole32(0xe4c03001), /* 54: strb r3, [r0], #1 */ + htole32(0xeafffffa), /* 58: b 48 */ + /* : */ + htole32(0xe59f0058), /* 68: ldr r0, [pc, #88] */ + htole32(0xe59f1058), /* 6c: ldr r1, [pc, #88] */ + htole32(0xe59f2058), /* 70: ldr r2, [pc, #88] */ + htole32(0xe0403001), /* 74: sub r3, r0, r1 */ + htole32(0xe3130003), /* 78: tst r3, #3 */ + htole32(0x1a00000c), /* 7c: bne b4 */ + /* : */ + htole32(0xe0813002), /* 80: add r3, r1, r2 */ + htole32(0xe3130003), /* 84: tst r3, #3 */ + htole32(0x0a000004), /* 88: beq a0 */ + htole32(0xe2522001), /* 8c: subs r2, r2, #1 */ + htole32(0x412fff1e), /* 90: bxmi lr */ + htole32(0xe7d13002), /* 94: ldrb r3, [r1, r2] */ + htole32(0xe7c03002), /* 98: strb r3, [r0, r2] */ + htole32(0xeafffff7), /* 9c: b 80 */ + /* : */ + htole32(0xe2522004), /* a0: subs r2, r2, #4 */ + htole32(0x57913002), /* a4: ldrpl r3, [r1, r2] */ + htole32(0x57803002), /* a8: strpl r3, [r0, r2] */ + htole32(0x5afffffb), /* ac: bpl a0 */ + htole32(0xe2822004), /* b0: add r2, r2, #4 */ + /* : */ + htole32(0xe2522001), /* b4: subs r2, r2, #1 */ + htole32(0x412fff1e), /* b8: bxmi lr */ + htole32(0xe7d13002), /* bc: ldrb r3, [r1, r2] */ + htole32(0xe7c03002), /* c0: strb r3, [r0, r2] */ + htole32(0xeafffffa), /* c4: b b4 */ diff --git a/thunks/objdump_to_h.awk b/thunks/objdump_to_h.awk new file mode 100644 index 0000000..bab0a35 --- /dev/null +++ b/thunks/objdump_to_h.awk @@ -0,0 +1,33 @@ +# labels +/[[:xdigit:]]+ <\w+>:/ { + # (Note: using $0 instead of $2 would also include the address) + if (style=="old") + printf "\t/* %s */\n", $2 + else + printf "\t\t/* %s */\n", $2 +} + +# disassembly lines +/[[:xdigit:]]+:/ { + if (style=="old") + printf "\t0x%s, /* %9s %-10s", $2, $1, $3 + else + printf "\t\thtole32(0x%s), /* %5s %-5s", $2, $1, $3 + + for (i = 4; i <= NF; i++) + if ($i == ";") { + # strip comment (anything after and including ';') + NF = i - 1 + break + } + # clear $1 to $3, which re-calculates $0 (= remainder of line) + $3 = "" + $2 = "" + $1 = "" + gsub("^\\s+", "") # strip leading whitespace + + if (style=="old") + printf " %-28s */\n", $0 + else + printf " %-23s */\n", $0 +} diff --git a/thunks/readl_writel.S b/thunks/readl_writel.S new file mode 100644 index 0000000..ebe7d0c --- /dev/null +++ b/thunks/readl_writel.S @@ -0,0 +1,41 @@ +/* + * Thunk code for buffered 'long' (i.e. 32-bit) read and write operations + */ + +.equ MAX_WORDS, 0x100 - 12 + +fel_readl_n: + ldr r0, 1f /* read_addr */ + adr r1, 3f /* read_data */ + ldr r2, 2f /* read_count */ + /* limit word count to a maximum value */ + cmp r2, #MAX_WORDS + movgt r2, #MAX_WORDS +read_loop: + subs r2, #1 + bxmi lr + ldr r3, [r0], #4 + str r3, [r1], #4 + b read_loop + +1: .word 0 /* read_addr */ +2: .word 0 /* read_count */ +3: .word 0 /* read_data */ + +fel_writel_n: + ldr r0, 1f /* write_addr */ + adr r1, 3f /* write_data */ + ldr r2, 2f /* write_count */ + /* limit word count to a maximum value */ + cmp r2, #MAX_WORDS + movgt r2, #MAX_WORDS +write_loop: + subs r2, #1 + bxmi lr + ldr r3, [r1], #4 + str r3, [r0], #4 + b write_loop + +1: .word 0 /* write_addr */ +2: .word 0 /* write_count */ +3: .word 0 /* write_data */ diff --git a/thunks/readl_writel.h b/thunks/readl_writel.h new file mode 100644 index 0000000..d50b5ac --- /dev/null +++ b/thunks/readl_writel.h @@ -0,0 +1,24 @@ + /* : */ + htole32(0xe59f0020), /* 0: ldr r0, [pc, #32] */ + htole32(0xe28f1024), /* 4: add r1, pc, #36 */ + htole32(0xe59f201c), /* 8: ldr r2, [pc, #28] */ + htole32(0xe35200f4), /* c: cmp r2, #244 */ + htole32(0xc3a020f4), /* 10: movgt r2, #244 */ + /* : */ + htole32(0xe2522001), /* 14: subs r2, r2, #1 */ + htole32(0x412fff1e), /* 18: bxmi lr */ + htole32(0xe4903004), /* 1c: ldr r3, [r0], #4 */ + htole32(0xe4813004), /* 20: str r3, [r1], #4 */ + htole32(0xeafffffa), /* 24: b 14 */ + /* : */ + htole32(0xe59f0020), /* 34: ldr r0, [pc, #32] */ + htole32(0xe28f1024), /* 38: add r1, pc, #36 */ + htole32(0xe59f201c), /* 3c: ldr r2, [pc, #28] */ + htole32(0xe35200f4), /* 40: cmp r2, #244 */ + htole32(0xc3a020f4), /* 44: movgt r2, #244 */ + /* : */ + htole32(0xe2522001), /* 48: subs r2, r2, #1 */ + htole32(0x412fff1e), /* 4c: bxmi lr */ + htole32(0xe4913004), /* 50: ldr r3, [r1], #4 */ + htole32(0xe4803004), /* 54: str r3, [r0], #4 */ + htole32(0xeafffffa), /* 58: b 48 */ diff --git a/thunks/rmr-thunk.S b/thunks/rmr-thunk.S new file mode 100644 index 0000000..7d67ccc --- /dev/null +++ b/thunks/rmr-thunk.S @@ -0,0 +1,26 @@ +/* + * Request AArch32/AArch64 warm reset, using RVBAR and Reset Management Register + * This is used on ARMv8 cores only, so force v7 code to allow dsb and isb. + */ +.arch armv7-a + +rmr_request: + ldr r0, 1f /* RVBAR register address */ + ldr r1, 2f /* desired entry point (reset vector) */ + str r1, [r0] + dsb + isb /* make sure we write the address */ + + ldr r1, 3f /* RMR mode: bit 1 = RR, bit 0 = AA64 */ + mrc p15, 0, r0, c12, c0, 2 /* read RMR */ + orr r0, r0, r1 /* request warm reset (according to rmr_mode) */ + mcr p15, 0, r0, c12, c0, 2 /* write RMR, trigger reset */ + + isb +0: + wfi + b 0b /* loop */ + +1: .word 0 /* rvbar_reg */ +2: .word 0 /* entry_point */ +3: .word 0 /* rmr_mode (2 = AArch32, 3 = AArch64) */ diff --git a/thunks/rmr-thunk.h b/thunks/rmr-thunk.h new file mode 100644 index 0000000..1e6fcd9 --- /dev/null +++ b/thunks/rmr-thunk.h @@ -0,0 +1,13 @@ + /* : */ + htole32(0xe59f0028), /* 0: ldr r0, [pc, #40] */ + htole32(0xe59f1028), /* 4: ldr r1, [pc, #40] */ + htole32(0xe5801000), /* 8: str r1, [r0] */ + htole32(0xf57ff04f), /* c: dsb sy */ + htole32(0xf57ff06f), /* 10: isb sy */ + htole32(0xe59f101c), /* 14: ldr r1, [pc, #28] */ + htole32(0xee1c0f50), /* 18: mrc 15, 0, r0, cr12, cr0, {2} */ + htole32(0xe1800001), /* 1c: orr r0, r0, r1 */ + htole32(0xee0c0f50), /* 20: mcr 15, 0, r0, cr12, cr0, {2} */ + htole32(0xf57ff06f), /* 24: isb sy */ + htole32(0xe320f003), /* 28: wfi */ + htole32(0xeafffffd), /* 2c: b 28 */ diff --git a/thunks/sid_read_root.S b/thunks/sid_read_root.S new file mode 100644 index 0000000..3d8d719 --- /dev/null +++ b/thunks/sid_read_root.S @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 Bernhard Nortmann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* + * ARM thunk code to read the SID root key using register-based access. + * + * This is necessary for certain SoCs (e.g. H3), as the values read via + * memory mapping might not be consistent. For background information see + * https://groups.google.com/forum/#!topic/linux-sunxi/ynyIP8c61Qs + */ + +SID_BASE .req r0 +sid_key_index .req r1 + +.set SID_PRCTL, 0x40 /* SID program/read control register */ +.set SID_PRKEY, 0x50 /* SID program key value register */ +.set SID_RDKEY, 0x60 /* SID read key value register */ + +.set SID_OP_LOCK, 0xAC /* Efuse operation lock value */ +.set SID_READ_START, (1 << 1) /* bit 1 of SID_PRCTL, Software Read Start */ +.set SID_PG_START, (1 << 0) /* bit 0 of SID_PRCTL, Software Program Start */ + +sid_read_root_key: + ldr SID_BASE, sid_base + ldr sid_key_index, offset + adr r3, sid_result /* result pointer */ +sid_read_loop: + mov r2, sid_key_index, lsl #16 /* PG_INDEX value */ + orr r2, #SID_OP_LOCK << 8 /* OP_LOCK to enable SID_READ_START */ + orr r2, #SID_READ_START + str r2, [SID_BASE, #SID_PRCTL] /* write SID_PRCTL */ +sid_read_wait: + ldr r2, [SID_BASE, #SID_PRCTL] /* read SID_PRCTL */ + tst r2, #SID_READ_START /* check if read operation completed */ + bne sid_read_wait /* loop while bit 1 still set */ + + ldr r2, [SID_BASE, #SID_RDKEY] /* read SID key value */ + str r2, [r3], #4 /* store SID value */ + + add sid_key_index, #4 + ldr r2, end + cmp sid_key_index, r2 + blo sid_read_loop /* loop while (sid_key_index < 0x10) */ + + mov r2, #0 + str r2, [SID_BASE, #SID_PRCTL] /* clear SID_PRCTL */ + bx lr + +sid_base: .word 0 +offset: .word 0 +end: .word 0 +sid_result: /* receives the values read from the SID registers */ diff --git a/thunks/sid_read_root.h b/thunks/sid_read_root.h new file mode 100644 index 0000000..fc11e14 --- /dev/null +++ b/thunks/sid_read_root.h @@ -0,0 +1,28 @@ + /* : */ + htole32(0xe59f0044), /* 0: ldr r0, [pc, #68] */ + htole32(0xe59f1044), /* 4: ldr r1, [pc, #68] */ + htole32(0xe28f3048), /* 8: add r3, pc, #72 */ + /* : */ + htole32(0xe1a02801), /* c: lsl r2, r1, #16 */ + htole32(0xe3822b2b), /* 10: orr r2, r2, #44032 */ + htole32(0xe3822002), /* 14: orr r2, r2, #2 */ + htole32(0xe5802040), /* 18: str r2, [r0, #64] */ + /* : */ + htole32(0xe5902040), /* 1c: ldr r2, [r0, #64] */ + htole32(0xe3120002), /* 20: tst r2, #2 */ + htole32(0x1afffffc), /* 24: bne 1c */ + htole32(0xe5902060), /* 28: ldr r2, [r0, #96] */ + htole32(0xe4832004), /* 2c: str r2, [r3], #4 */ + htole32(0xe2811004), /* 30: add r1, r1, #4 */ + htole32(0xe59f2018), /* 34: ldr r2, [pc, #24] */ + htole32(0xe1510002), /* 38: cmp r1, r2 */ + htole32(0x3afffff2), /* 3c: bcc c */ + htole32(0xe3a02000), /* 40: mov r2, #0 */ + htole32(0xe5802040), /* 44: str r2, [r0, #64] */ + htole32(0xe12fff1e), /* 48: bx lr */ + /* : */ + htole32(0x00000000), /* 4c: .word 0x00000000 */ + /* : */ + htole32(0x00000000), /* 50: .word 0x00000000 */ + /* : */ + htole32(0x00000000), /* 54: .word 0x00000000 */ diff --git a/uart0-helloworld-sdboot.c b/uart0-helloworld-sdboot.c new file mode 100644 index 0000000..1339a3d --- /dev/null +++ b/uart0-helloworld-sdboot.c @@ -0,0 +1,712 @@ +/* + * Copyright (C) 2016 Siarhei Siamashka + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Partially based on the uart code from ar100-info + * + * (C) Copyright 2013 Stefan Kristiansson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* + * Partially based on the sunxi gpio code from U-Boot + * + * (C) Copyright 2012 Henrik Nordstrom + * + * Based on earlier arch/arm/cpu/armv7/sunxi/gpio.c: + * + * (C) Copyright 2007-2011 + * Allwinner Technology Co., Ltd. + * Tom Cubie + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +typedef unsigned int u32; + +#define set_wbit(addr, v) (*((volatile unsigned long *)(addr)) |= (unsigned long)(v)) +#define readl(addr) (*((volatile unsigned long *)(addr))) +#define writel(v, addr) (*((volatile unsigned long *)(addr)) = (unsigned long)(v)) + +#define SUNXI_UART0_BASE 0x01C28000 +#define SUNXI_PIO_BASE 0x01C20800 +#define AW_CCM_BASE 0x01c20000 +#define AW_SRAMCTRL_BASE 0x01c00000 + +#define H6_UART0_BASE 0x05000000 +#define H6_PIO_BASE 0x0300B000 +#define H6_CCM_BASE 0x03001000 +#define H6_SRAMCTRL_BASE 0x03000000 + +#define R329_UART0_BASE 0x02500000 +#define R329_PIO_BASE 0x02000400 +#define R329_CCM_BASE 0x02001000 + +#define V853_PIO_BASE 0x02000000 + +#define SUNIV_UART0_BASE 0x01c25000 +/***************************************************************************** + * GPIO code, borrowed from U-Boot * + *****************************************************************************/ + +#define SUNXI_GPIO_A 0 +#define SUNXI_GPIO_B 1 +#define SUNXI_GPIO_C 2 +#define SUNXI_GPIO_D 3 +#define SUNXI_GPIO_E 4 +#define SUNXI_GPIO_F 5 +#define SUNXI_GPIO_G 6 +#define SUNXI_GPIO_H 7 +#define SUNXI_GPIO_I 8 + +#define GPIO_BANK(pin) ((pin) >> 5) +#define GPIO_NUM(pin) ((pin) & 0x1F) + +#define GPIO_CFG_BASE(bank) ((u32 *)(pio_base + (bank) * pio_bank_size)) +#define GPIO_CFG_INDEX(pin) (((pin) & 0x1F) >> 3) +#define GPIO_CFG_OFFSET(pin) ((((pin) & 0x1F) & 0x7) << 2) + +#define GPIO_PULL_BASE(bank) ((u32 *)(pio_base + (bank) * pio_bank_size + pio_pull_off)) +#define GPIO_PULL_INDEX(pin) (((pin) & 0x1f) >> 4) +#define GPIO_PULL_OFFSET(pin) ((((pin) & 0x1f) & 0xf) << 1) + +#define GPIO_DAT_BASE(bank) ((u32 *)(pio_base + (bank) * pio_bank_size + pio_dat_off)) + +/* GPIO bank sizes */ +#define SUNXI_GPIO_A_NR (32) +#define SUNXI_GPIO_B_NR (32) +#define SUNXI_GPIO_C_NR (32) +#define SUNXI_GPIO_D_NR (32) +#define SUNXI_GPIO_E_NR (32) +#define SUNXI_GPIO_F_NR (32) +#define SUNXI_GPIO_G_NR (32) +#define SUNXI_GPIO_H_NR (32) +#define SUNXI_GPIO_I_NR (32) + +#define SUNXI_GPIO_NEXT(__gpio) ((__gpio##_START) + (__gpio##_NR) + 0) + +enum sunxi_gpio_number { + SUNXI_GPIO_A_START = 0, + SUNXI_GPIO_B_START = SUNXI_GPIO_NEXT(SUNXI_GPIO_A), + SUNXI_GPIO_C_START = SUNXI_GPIO_NEXT(SUNXI_GPIO_B), + SUNXI_GPIO_D_START = SUNXI_GPIO_NEXT(SUNXI_GPIO_C), + SUNXI_GPIO_E_START = SUNXI_GPIO_NEXT(SUNXI_GPIO_D), + SUNXI_GPIO_F_START = SUNXI_GPIO_NEXT(SUNXI_GPIO_E), + SUNXI_GPIO_G_START = SUNXI_GPIO_NEXT(SUNXI_GPIO_F), + SUNXI_GPIO_H_START = SUNXI_GPIO_NEXT(SUNXI_GPIO_G), + SUNXI_GPIO_I_START = SUNXI_GPIO_NEXT(SUNXI_GPIO_H), +}; + +/* SUNXI GPIO number definitions */ +#define SUNXI_GPA(_nr) (SUNXI_GPIO_A_START + (_nr)) +#define SUNXI_GPB(_nr) (SUNXI_GPIO_B_START + (_nr)) +#define SUNXI_GPC(_nr) (SUNXI_GPIO_C_START + (_nr)) +#define SUNXI_GPD(_nr) (SUNXI_GPIO_D_START + (_nr)) +#define SUNXI_GPE(_nr) (SUNXI_GPIO_E_START + (_nr)) +#define SUNXI_GPF(_nr) (SUNXI_GPIO_F_START + (_nr)) +#define SUNXI_GPG(_nr) (SUNXI_GPIO_G_START + (_nr)) +#define SUNXI_GPH(_nr) (SUNXI_GPIO_H_START + (_nr)) +#define SUNXI_GPI(_nr) (SUNXI_GPIO_I_START + (_nr)) + +/* GPIO pin function config */ +#define SUNXI_GPIO_INPUT (0) +#define SUNXI_GPIO_OUTPUT (1) +#define SUNIV_GPE_UART0 (5) +#define SUN4I_GPB_UART0 (2) +#define SUN5I_GPB_UART0 (2) +#define SUN6I_GPH_UART0 (2) +#define SUN8I_H3_GPA_UART0 (2) +#define SUN8I_R528_GPE_UART0 (6) +#define SUN8I_V3S_GPB_UART0 (3) +#define SUN8I_V5_GPB_UART0 (2) +#define SUN8I_V831_GPH_UART0 (5) +#define SUN8I_V853_GPH_UART0 (5) +#define SUN50I_H5_GPA_UART0 (2) +#define SUN50I_H6_GPH_UART0 (2) +#define SUN50I_H616_GPH_UART0 (2) +#define SUN50I_R329_GPB_UART0 (2) +#define SUN50I_A64_GPB_UART0 (4) +#define SUNXI_GPF_UART0 (4) + +/* GPIO pin pull-up/down config */ +#define SUNXI_GPIO_PULL_DISABLE (0) +#define SUNXI_GPIO_PULL_UP (1) +#define SUNXI_GPIO_PULL_DOWN (2) + +static u32 pio_base; +static u32 pio_bank_size, pio_dat_off, pio_pull_off; + +int sunxi_gpio_set_cfgpin(u32 pin, u32 val) +{ + u32 cfg; + u32 bank = GPIO_BANK(pin); + u32 index = GPIO_CFG_INDEX(pin); + u32 offset = GPIO_CFG_OFFSET(pin); + u32 *addr = GPIO_CFG_BASE(bank) + index; + cfg = readl(addr); + cfg &= ~(0xf << offset); + cfg |= val << offset; + writel(cfg, addr); + return 0; +} + +int sunxi_gpio_set_pull(u32 pin, u32 val) +{ + u32 cfg; + u32 bank = GPIO_BANK(pin); + u32 index = GPIO_PULL_INDEX(pin); + u32 offset = GPIO_PULL_OFFSET(pin); + u32 *addr = GPIO_PULL_BASE(bank) + index; + cfg = readl(addr); + cfg &= ~(0x3 << offset); + cfg |= val << offset; + writel(cfg, addr); + return 0; +} + +int sunxi_gpio_output(u32 pin, u32 val) +{ + u32 dat; + u32 bank = GPIO_BANK(pin); + u32 num = GPIO_NUM(pin); + u32 *addr = GPIO_DAT_BASE(bank); + dat = readl(addr); + if(val) + dat |= 1 << num; + else + dat &= ~(1 << num); + writel(dat, addr); + return 0; +} + +int sunxi_gpio_input(u32 pin) +{ + u32 dat; + u32 bank = GPIO_BANK(pin); + u32 num = GPIO_NUM(pin); + u32 *addr = GPIO_DAT_BASE(bank); + dat = readl(addr); + dat >>= num; + return (dat & 0x1); +} + +int gpio_direction_input(unsigned gpio) +{ + sunxi_gpio_set_cfgpin(gpio, SUNXI_GPIO_INPUT); + return sunxi_gpio_input(gpio); +} + +int gpio_direction_output(unsigned gpio, int value) +{ + sunxi_gpio_set_cfgpin(gpio, SUNXI_GPIO_OUTPUT); + return sunxi_gpio_output(gpio, value); +} + +/***************************************************************************** + * Nearly all the Allwinner SoCs are using the same VER_REG register for * + * runtime SoC type identification. For additional details see: * + * * + * https://linux-sunxi.org/SRAM_Controller_Register_Guide * + * * + * Allwinner A80 is an oddball and has a non-standard address of the VER_REG * + * * + * Allwinner A10s and A13 are using the same SoC type id, but they can be * + * differentiated using a certain part of the SID register. * + * * + * Allwinner H6 has its memory map totally reworked, but the SRAM controller * + * remains similar; the base of it is moved to 0x03000000. * + *****************************************************************************/ + +#define VER_REG (AW_SRAMCTRL_BASE + 0x24) +#define H6_VER_REG (H6_SRAMCTRL_BASE + 0x24) +#define SUN4I_SID_BASE 0x01C23800 +#define SUN8I_SID_BASE 0x01C14000 + +#define SID_PRCTL 0x40 /* SID program/read control register */ +#define SID_RDKEY 0x60 /* SID read key value register */ + +#define SID_OP_LOCK 0xAC /* Efuse operation lock value */ +#define SID_READ_START (1 << 1) /* bit 1 of SID_PRCTL, Software Read Start */ + +u32 sid_read_key(u32 sid_base, u32 offset) +{ + u32 reg_val; + + reg_val = (offset & 0x1FF) << 16; /* PG_INDEX value */ + reg_val |= (SID_OP_LOCK << 8) | SID_READ_START; /* request read access */ + writel(reg_val, sid_base + SID_PRCTL); + + while (readl(sid_base + SID_PRCTL) & SID_READ_START) ; /* wait while busy */ + + reg_val = readl(sid_base + SID_RDKEY); /* read SID key value */ + writel(0, sid_base + SID_PRCTL); /* clear SID_PRCTL (removing SID_OP_LOCK) */ + + return reg_val; +} + +static u32 soc_id; + +void soc_detection_init(void) +{ + u32 midr; + asm volatile("mrc p15, 0, %0, c0, c0, 0" : "=r" (midr)); + + if (((midr >> 4) & 0xFFF) == 0xC0F) { + soc_id = 0x1639; /* ARM Cortex-A15, so likely Allwinner A80 */ + } else { + u32 reg; + + /* + * This register is GICD_IIDR on H6, but unmapped according to + * other known SoCs' user manuals. + */ + reg = readl(0x03021008); + + if ((reg & 0xfff) == 0x43b) /* Found GICv2 here, so it's a H6 */ + reg = H6_VER_REG; + else + reg = VER_REG; + + set_wbit(reg, 1 << 15); + soc_id = readl(reg) >> 16; + } +} + +/* Most SoCs can reliably be distinguished by simply checking their ID value */ + +#define soc_is_a10() (soc_id == 0x1623) +#define soc_is_a20() (soc_id == 0x1651) +#define soc_is_a31() (soc_id == 0x1633) +#define soc_is_a80() (soc_id == 0x1639) +#define soc_is_a64() (soc_id == 0x1689) +#define soc_is_h5() (soc_id == 0x1718) +#define soc_is_a63() (soc_id == 0x1719) +#define soc_is_h6() (soc_id == 0x1728) +#define soc_is_h616() (soc_id == 0x1823) +#define soc_is_r329() (soc_id == 0x1851) +#define soc_is_r40() (soc_id == 0x1701) +#define soc_is_v3s() (soc_id == 0x1681) +#define soc_is_v831() (soc_id == 0x1817) +#define soc_is_v853() (soc_id == 0x1886) +#define soc_is_r528() (soc_id == 0x1859) +#define soc_is_v5() (soc_id == 0x1721) +#define soc_is_suniv() (soc_id == 0x1663) + +/* A10s and A13 share the same ID, so we need a little more effort on those */ + +int soc_is_a10s(void) +{ + return soc_id == 0x1625 && + (readl(SUN4I_SID_BASE + 8) & 0xf000) == 0x7000; +} + +int soc_is_a13(void) +{ + return soc_id == 0x1625 && + (readl(SUN4I_SID_BASE + 8) & 0xf000) != 0x7000; +} + +/* H2+ and H3 share the same ID, we can differentiate them by SID_RKEY0 */ + +int soc_is_h2_plus(void) +{ + if (soc_id != 0x1680) return 0; + + u32 sid0 = sid_read_key(SUN8I_SID_BASE, 0); + return (sid0 & 0xff) == 0x42 || (sid0 & 0xff) == 0x83; +} + +int soc_is_h3(void) +{ + if (soc_id != 0x1680) return 0; + + u32 sid0 = sid_read_key(SUN8I_SID_BASE, 0); + /* + * Note: according to Allwinner sources, H3 is expected + * to show up as 0x00, 0x81 or ("H3D") 0x58 here. + */ + return (sid0 & 0xff) != 0x42 && (sid0 & 0xff) != 0x83; +} + +/***************************************************************************** + * UART is mostly the same on A10/A13/A20/A31/H3/A64, except that newer SoCs * + * have changed the APB numbering scheme (A10/A13/A20 used to have APB0 and * + * APB1 names, but newer SoCs just have renamed them into APB1 and APB2). * + * The constants below are using the new APB numbering convention. * + * Also the newer SoCs have introduced the APB2_RESET register, but writing * + * to it effectively goes nowhere on older SoCs and is harmless. * + *****************************************************************************/ + +#define CONFIG_CONS_INDEX 1 +#define APB2_CFG (AW_CCM_BASE + 0x058) +#define APB1_GATE (AW_CCM_BASE + 0x068) +#define APB2_GATE (AW_CCM_BASE + 0x06C) +#define APB1_RESET (AW_CCM_BASE + 0x2D0) +#define APB2_RESET (AW_CCM_BASE + 0x2D8) +#define APB2_GATE_UART_SHIFT (16) +#define APB1_GATE_UART_SHIFT 20 +#define APB2_RESET_UART_SHIFT (16) +#define APB1_RESET_UART_SHIFT 20 + +#define H6_UART_GATE_RESET (H6_CCM_BASE + 0x90C) +#define R329_UART_GATE_RESET (R329_CCM_BASE + 0x90C) +#define H6_UART_GATE_SHIFT (0) +#define H6_UART_RESET_SHIFT (16) + +void clock_init_uart_legacy(void) +{ + /* Open the clock gate for UART0 */ + set_wbit(APB2_GATE, 1 << (APB2_GATE_UART_SHIFT + CONFIG_CONS_INDEX - 1)); + /* Deassert UART0 reset (only needed on A31/A64/H3) */ + set_wbit(APB2_RESET, 1 << (APB2_RESET_UART_SHIFT + CONFIG_CONS_INDEX - 1)); +} + +void clock_init_uart_suniv(void) +{ + /* open the clock for uart */ + set_wbit(APB1_GATE, + 1U << (APB1_GATE_UART_SHIFT + CONFIG_CONS_INDEX - 1)); + + /* deassert uart reset */ + set_wbit(APB1_RESET, + 1U << (APB1_RESET_UART_SHIFT + CONFIG_CONS_INDEX - 1)); +} + +void clock_init_uart_h6(void) +{ + /* Open the clock gate for UART0 */ + set_wbit(H6_UART_GATE_RESET, 1 << (H6_UART_GATE_SHIFT + CONFIG_CONS_INDEX - 1)); + /* Deassert UART0 reset */ + set_wbit(H6_UART_GATE_RESET, 1 << (H6_UART_RESET_SHIFT + CONFIG_CONS_INDEX - 1)); +} + +void clock_init_uart_r329(void) +{ + /* Open the clock gate for UART0 */ + set_wbit(R329_UART_GATE_RESET, 1 << (H6_UART_GATE_SHIFT + CONFIG_CONS_INDEX - 1)); + /* Deassert UART0 reset */ + set_wbit(R329_UART_GATE_RESET, 1 << (H6_UART_RESET_SHIFT + CONFIG_CONS_INDEX - 1)); +} + +void clock_init_uart(void) +{ + if (soc_is_h6() || soc_is_v831() || soc_is_h616() || soc_is_v5() || + soc_is_a63()) + clock_init_uart_h6(); + else if (soc_is_r329() || soc_is_v853() || soc_is_r528()) + clock_init_uart_r329(); + else if (soc_is_suniv()) + clock_init_uart_suniv(); + else + clock_init_uart_legacy(); +} + +/***************************************************************************** + * UART0 pins muxing is different for different SoC variants. * + * Allwinner A13 is a bit special, because there are no dedicated UART0 pins * + * and they are shared with MMC0. * + *****************************************************************************/ + +void gpio_init(void) +{ + if (soc_is_v853() || soc_is_r528()) { + /* GPIO V2 */ + pio_bank_size = 0x30; + pio_dat_off = 0x10; + pio_pull_off = 0x24; + } else { + /* GPIO V1 */ + pio_bank_size = 0x24; + pio_dat_off = 0x10; + pio_pull_off = 0x1c; + } + + if (soc_is_a10() || soc_is_a20() || soc_is_r40()) { + sunxi_gpio_set_cfgpin(SUNXI_GPB(22), SUN4I_GPB_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPB(23), SUN4I_GPB_UART0); + sunxi_gpio_set_pull(SUNXI_GPB(23), SUNXI_GPIO_PULL_UP); + } else if (soc_is_a10s()) { + sunxi_gpio_set_cfgpin(SUNXI_GPB(19), SUN5I_GPB_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPB(20), SUN5I_GPB_UART0); + sunxi_gpio_set_pull(SUNXI_GPB(20), SUNXI_GPIO_PULL_UP); + } else if (soc_is_a13()) { + /* Disable PB19/PB20 as UART0 to avoid conflict */ + gpio_direction_input(SUNXI_GPB(19)); + gpio_direction_input(SUNXI_GPB(20)); + /* Use SD breakout board to access UART0 on MMC0 pins */ + sunxi_gpio_set_cfgpin(SUNXI_GPF(2), SUNXI_GPF_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPF(4), SUNXI_GPF_UART0); + sunxi_gpio_set_pull(SUNXI_GPF(4), SUNXI_GPIO_PULL_UP); + } else if (soc_is_a31()) { + sunxi_gpio_set_cfgpin(SUNXI_GPH(20), SUN6I_GPH_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPH(21), SUN6I_GPH_UART0); + sunxi_gpio_set_pull(SUNXI_GPH(21), SUNXI_GPIO_PULL_UP); + } else if (soc_is_a64()) { + sunxi_gpio_set_cfgpin(SUNXI_GPB(8), SUN50I_A64_GPB_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPB(9), SUN50I_A64_GPB_UART0); + sunxi_gpio_set_pull(SUNXI_GPB(9), SUNXI_GPIO_PULL_UP); + } else if (soc_is_h3() || soc_is_h2_plus()) { + sunxi_gpio_set_cfgpin(SUNXI_GPA(4), SUN8I_H3_GPA_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPA(5), SUN8I_H3_GPA_UART0); + sunxi_gpio_set_pull(SUNXI_GPA(5), SUNXI_GPIO_PULL_UP); + } else if (soc_is_h5()) { + sunxi_gpio_set_cfgpin(SUNXI_GPA(4), SUN50I_H5_GPA_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPA(5), SUN50I_H5_GPA_UART0); + sunxi_gpio_set_pull(SUNXI_GPA(5), SUNXI_GPIO_PULL_UP); + } else if (soc_is_a63()) { + sunxi_gpio_set_cfgpin(SUNXI_GPB(9), SUN50I_A64_GPB_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPB(10), SUN50I_A64_GPB_UART0); + sunxi_gpio_set_pull(SUNXI_GPB(10), SUNXI_GPIO_PULL_UP); + } else if (soc_is_h6()) { + sunxi_gpio_set_cfgpin(SUNXI_GPH(0), SUN50I_H6_GPH_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPH(1), SUN50I_H6_GPH_UART0); + sunxi_gpio_set_pull(SUNXI_GPH(1), SUNXI_GPIO_PULL_UP); + } else if (soc_is_h616()) { + sunxi_gpio_set_cfgpin(SUNXI_GPH(0), SUN50I_H616_GPH_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPH(1), SUN50I_H616_GPH_UART0); + sunxi_gpio_set_pull(SUNXI_GPH(1), SUNXI_GPIO_PULL_UP); + } else if (soc_is_r329()) { + sunxi_gpio_set_cfgpin(SUNXI_GPB(4), SUN50I_R329_GPB_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPB(5), SUN50I_R329_GPB_UART0); + sunxi_gpio_set_pull(SUNXI_GPB(5), SUNXI_GPIO_PULL_UP); + } else if (soc_is_v3s()) { + sunxi_gpio_set_cfgpin(SUNXI_GPB(8), SUN8I_V3S_GPB_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPB(9), SUN8I_V3S_GPB_UART0); + sunxi_gpio_set_pull(SUNXI_GPB(9), SUNXI_GPIO_PULL_UP); + } else if (soc_is_v831()) { + sunxi_gpio_set_cfgpin(SUNXI_GPH(9), SUN8I_V831_GPH_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPH(10), SUN8I_V831_GPH_UART0); + sunxi_gpio_set_pull(SUNXI_GPH(10), SUNXI_GPIO_PULL_UP); + } else if (soc_is_v853()) { + sunxi_gpio_set_cfgpin(SUNXI_GPH(9), SUN8I_V853_GPH_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPH(10), SUN8I_V853_GPH_UART0); + sunxi_gpio_set_pull(SUNXI_GPH(10), SUNXI_GPIO_PULL_UP); + } else if (soc_is_r528()) { + sunxi_gpio_set_cfgpin(SUNXI_GPE(2), SUN8I_R528_GPE_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPE(3), SUN8I_R528_GPE_UART0); + sunxi_gpio_set_pull(SUNXI_GPE(3), SUNXI_GPIO_PULL_UP); + } else if (soc_is_v5()) { + sunxi_gpio_set_cfgpin(SUNXI_GPB(9), SUN8I_V5_GPB_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPB(10), SUN8I_V5_GPB_UART0); + sunxi_gpio_set_pull(SUNXI_GPB(10), SUNXI_GPIO_PULL_UP); + } else if (soc_is_suniv()) { + sunxi_gpio_set_cfgpin(SUNXI_GPE(0), SUNIV_GPE_UART0); + sunxi_gpio_set_cfgpin(SUNXI_GPE(1), SUNIV_GPE_UART0); + sunxi_gpio_set_pull(SUNXI_GPE(1), SUNXI_GPIO_PULL_UP); + } else { + /* Unknown SoC */ + while (1) {} + } +} + +/*****************************************************************************/ + +static u32 uart0_base; + +#define UART0_RBR (uart0_base + 0x0) /* receive buffer register */ +#define UART0_THR (uart0_base + 0x0) /* transmit holding register */ +#define UART0_DLL (uart0_base + 0x0) /* divisor latch low register */ + +#define UART0_DLH (uart0_base + 0x4) /* divisor latch high register */ +#define UART0_IER (uart0_base + 0x4) /* interrupt enable reigster */ + +#define UART0_IIR (uart0_base + 0x8) /* interrupt identity register */ +#define UART0_FCR (uart0_base + 0x8) /* fifo control register */ + +#define UART0_LCR (uart0_base + 0xc) /* line control register */ + +#define UART0_LSR (uart0_base + 0x14) /* line status register */ + +#define BAUD_115200 13 /* 24 * 1000 * 1000 / 16 / 115200 */ +/* The BROM sets the CPU clock to 204MHz, AHB=CPU/2, APB=AHB/2 => 51 MHz */ +#define BAUD_115200_SUNIV 28 /* 51 * 1000 * 1000 / 16 / 115200 */ +#define NO_PARITY (0) +#define ONE_STOP_BIT (0) +#define DAT_LEN_8_BITS (3) +#define LC_8_N_1 (NO_PARITY << 3 | ONE_STOP_BIT << 2 | DAT_LEN_8_BITS) + +void uart0_init(void) +{ + clock_init_uart(); + + /* select dll dlh */ + writel(0x80, UART0_LCR); + /* set baudrate */ + writel(0, UART0_DLH); + if (soc_is_suniv()) + writel(BAUD_115200_SUNIV, UART0_DLL); + else + writel(BAUD_115200, UART0_DLL); + /* set line control */ + writel(LC_8_N_1, UART0_LCR); +} + +void uart0_putc(char c) +{ + while (!(readl(UART0_LSR) & (1 << 6))) {} + writel(c, UART0_THR); +} + +void uart0_puts(const char *s) +{ + while (*s) { + if (*s == '\n') + uart0_putc('\r'); + uart0_putc(*s++); + } +} + +/*****************************************************************************/ + +/* A workaround for https://patchwork.ozlabs.org/patch/622173 */ +void __attribute__((section(".start"))) __attribute__((naked)) start(void) +{ + asm volatile("b main \n" + ".long 0xffffffff \n" + ".long 0xffffffff \n" + ".long 0xffffffff \n"); +} + +enum { BOOT_DEVICE_UNK, BOOT_DEVICE_FEL, BOOT_DEVICE_MMC0, BOOT_DEVICE_SPI }; + +int get_boot_device(void) +{ + u32 *spl_signature = (void *)0x4; + if (soc_is_a64() || soc_is_a80() || soc_is_h5()) + spl_signature = (void *)0x10004; + if (soc_is_h6() || soc_is_v831() || soc_is_h616() || soc_is_v853() || + soc_is_a63()) + spl_signature = (void *)0x20004; + if (soc_is_r329()) + spl_signature = (void *)0x100004; + + /* Check the eGON.BT0 magic in the SPL header */ + if (spl_signature[0] != 0x4E4F4765 || spl_signature[1] != 0x3054422E) + return BOOT_DEVICE_FEL; + + u32 boot_dev = spl_signature[9] & 0xFF; /* offset into SPL = 0x28 */ + if (boot_dev == 0) + return BOOT_DEVICE_MMC0; + if (boot_dev == 3) + return BOOT_DEVICE_SPI; + + return BOOT_DEVICE_UNK; +} + +void bases_init(void) +{ + if (soc_is_h6() || soc_is_v831() || soc_is_h616() || soc_is_v5() || + soc_is_a63()) { + pio_base = H6_PIO_BASE; + uart0_base = H6_UART0_BASE; + } else if (soc_is_r329()) { + pio_base = R329_PIO_BASE; + uart0_base = R329_UART0_BASE; + } else if (soc_is_v853() || soc_is_r528()) { + pio_base = V853_PIO_BASE; + uart0_base = R329_UART0_BASE; + } else if (soc_is_suniv()) { + pio_base = SUNXI_PIO_BASE; + uart0_base = SUNIV_UART0_BASE; + } else { + pio_base = SUNXI_PIO_BASE; + uart0_base = SUNXI_UART0_BASE; + } +} + +int main(void) +{ + soc_detection_init(); + bases_init(); + gpio_init(); + uart0_init(); + + uart0_puts("\nHello from "); + if (soc_is_a10()) + uart0_puts("Allwinner A10!\n"); + else if (soc_is_a10s()) + uart0_puts("Allwinner A10s!\n"); + else if (soc_is_a13()) + uart0_puts("Allwinner A13!\n"); + else if (soc_is_a20()) + uart0_puts("Allwinner A20!\n"); + else if (soc_is_a31()) + uart0_puts("Allwinner A31/A31s!\n"); + else if (soc_is_a64()) + uart0_puts("Allwinner A64!\n"); + else if (soc_is_h2_plus()) + uart0_puts("Allwinner H2+!\n"); + else if (soc_is_h3()) + uart0_puts("Allwinner H3!\n"); + else if (soc_is_h5()) + uart0_puts("Allwinner H5!\n"); + else if (soc_is_a63()) + uart0_puts("Allwinner A63!\n"); + else if (soc_is_h6()) + uart0_puts("Allwinner H6!\n"); + else if (soc_is_h616()) + uart0_puts("Allwinner H616!\n"); + else if (soc_is_r329()) + uart0_puts("Allwinner R329!\n"); + else if (soc_is_r40()) + uart0_puts("Allwinner R40!\n"); + else if (soc_is_v3s()) + uart0_puts("Allwinner V3s!\n"); + else if (soc_is_v831()) + uart0_puts("Allwinner V831!\n"); + else if (soc_is_v853()) + uart0_puts("Allwinner V853!\n"); + else if (soc_is_r528()) + uart0_puts("Allwinner R528/T113!\n"); + else if (soc_is_v5()) + uart0_puts("Allwinner V5!\n"); + else if (soc_is_suniv()) + uart0_puts("Allwinner F1C100s!\n"); + else + uart0_puts("unknown Allwinner SoC!\n"); + + switch (get_boot_device()) { + case BOOT_DEVICE_FEL: + uart0_puts("Returning back to FEL.\n"); + return 0; + case BOOT_DEVICE_MMC0: + uart0_puts("Booted from MMC0, entering an infinite loop.\n"); + while (1) {} + case BOOT_DEVICE_SPI: + uart0_puts("Booted from SPI0, entering an infinite loop.\n"); + while (1) {} + default: + uart0_puts("Booted from unknown media, entering an infinite loop.\n"); + while (1) {} + }; + + return 0; +} diff --git a/uart0-helloworld-sdboot.lds b/uart0-helloworld-sdboot.lds new file mode 100644 index 0000000..5c6bb51 --- /dev/null +++ b/uart0-helloworld-sdboot.lds @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 Siarhei Siamashka + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +SECTIONS +{ + . = 0x0000; + .start : { *(.start) } + .text : { *(.text) } + /DISCARD/ : { *(.dynstr*) } + /DISCARD/ : { *(.dynamic*) } + /DISCARD/ : { *(.plt*) } + /DISCARD/ : { *(.interp*) } + /DISCARD/ : { *(.gnu*) } + /DISCARD/ : { *(.note*) } +}