Commit graph

1648 commits

Author SHA1 Message Date
Heinrich Schuchardt
4f0c4be1c3 x86: rename CONFIG_STACK_SIZE
Configuration variables should have the same meaning independent of the
architecture. x86 and ARM both use CONFIG_STACK_SIZE:

* x86: U-Boot's runtime stack size during reboot
* ARM: max stack size that can be used by U-Boot

Rename the x86 configuration variable to CONFIG_STACK_SIZE_REBOOT

Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
2020-08-01 11:58:23 +02:00
Simon Glass
9589c447e8 x86: chromebook_panther: Correct the image layout
This board does not have microcode but at present that is not supported
by Kconfig nor the binman image layout. Fix both of these.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-28 19:30:39 -06:00
Simon Glass
a78466af82 x86: Move the fdtmap away from the binary blobs
This causes conflicts on chromebook_link64. Move it to after U-Boot where
there should be plenty of space.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-28 19:30:39 -06:00
Simon Glass
bcd4e6f3bd x86: Change how selection of ROMs works
Most x86 boards build a u-boot.rom which is programmed into SPI flash. But
this is not unique to x86. For example some rockchip boards can also boot
from SPI flash.

Also, at least on x86, binary blobs are sadly quite common. It is not
possible to build a functional image without them, and U-Boot needs to
know this at build time.

Introduce a new CONFIG_HAS_ROM option which selects whether u-boot.rom is
built and a new CONFIG_ROM_NEEDS_BLOBS option to indicate whether binary
blobs are also needed. If they are not needed, it is safe to build the ROM
always. Otherwise we still require the BUILD_ROM environment variable.

For now this affects only x86, but future patches will enable this for
rockchip too.

Signed-off-by: Simon Glass <sjg@chromium.org>
2020-07-28 19:30:39 -06:00
Simon Glass
961420fa5f cpu: Convert the methods to use a const udevice *
These functions should not modify the device. Convert them to const so
that callers don't need to cast if they have a const udevice *.

Signed-off-by: Simon Glass <sjg@chromium.org>
2020-07-25 14:46:57 -06:00
Tom Rini
7208396bbf Revert "Merge tag 'dm-pull-20jul20' of git://git.denx.de/u-boot-dm"
This reverts commit 5d3a21df66, reversing
changes made to 56d37f1c56.

Unfortunately this is causing CI failures:
https://travis-ci.org/github/trini/u-boot/jobs/711313649

Signed-off-by: Tom Rini <trini@konsulko.com>
2020-07-24 08:42:06 -04:00
Simon Glass
f07e58b878 cpu: Convert the methods to use a const udevice *
These functions should not modify the device. Convert them to const so
that callers don't need to cast if they have a const udevice *.

Signed-off-by: Simon Glass <sjg@chromium.org>
2020-07-20 11:37:47 -06:00
Simon Glass
c6efee5031 x86: mp: Add more comments to the module
Add a description of how this module works and also some missing function
comments.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:48 +08:00
Simon Glass
8dda2baa97 x86: mtrr: Add support for writing to MTRRs on any CPU
To enable support for the 'mtrr' command, add a way to perform MTRR
operations on selected CPUs.

This works by setting up a little 'operation' structure and sending it
around the CPUs for action.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-20 09:46:47 +08:00
Simon Glass
aa3a4d870e x86: mtrr: Update MTRRs on all CPUs
When the boot CPU MTRRs are updated, perform the same update on all other
CPUs so they are kept in sync.

This avoids kernel warnings about mismatched MTRRs.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:47 +08:00
Simon Glass
3d734b59c7 x86: Don't enable SMP in SPL
SMP should be set up in U-Boot where possible, not SPL. Disable it in SPL.
For 64-bit U-Boot we should find a way to allow SMP operations in U-Boot,
but this is somewhat more complicated. For now that is disabled too.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-20 09:46:46 +08:00
Simon Glass
240752c612 x86: mtrr: Use MP calls to list the MTRRs
Update the mtrr command to use mp_run_on_cpus() to obtain its information.
Since the selected CPU is the boot CPU this does not change the result,
but it sets the stage for supporting other CPUs.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:46 +08:00
Simon Glass
0538d6833c x86: mp: Add iterators for CPUs
It is convenient to iterate through the CPUs performing work on each one
and processing the result. Add a few iterator functions which handle this.
These can be used by any client code. It can call mp_run_on_cpus() on
each CPU that is returned, handling them one at a time.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:46 +08:00
Simon Glass
99a573fb32 x86: mp: Park CPUs before running the OS
With the new MP features the CPUs are no-longer parked when the OS is run.
Fix this by calling a special function to park them, just before the OS is
started.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:46 +08:00
Simon Glass
84d3ed125a x86: mp: Allow running functions on multiple CPUs
Add a way to run a function on a selection of CPUs. This supports either
a single CPU, all CPUs, just the main CPU or just the 'APs', in Intel
terminology.

It works by writing into a mailbox and then waiting for the CPUs to notice
it, take action and indicate they are done.

When SMP is not yet enabled, this just calls the function on the main CPU.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:45 +08:00
Simon Glass
db3a37c711 x86: Set the SMP flag when MP init is complete
Set this flag so we can track when it is safe to use CPUs other than the
main one.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:45 +08:00
Simon Glass
c33aa3527d x86: mp: Support APs waiting for instructions
At present the APs (non-boot CPUs) are inited once and then parked ready
for the OS to use them. However in some cases we want to send new requests
through, such as to change MTRRs and keep them consistent across CPUs.

Change the last state of the flight plan to go into a wait loop, accepting
instructions from the main CPU.

Drop cpu_map since it is not used.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:45 +08:00
Simon Glass
8bccbc5ac0 x86: cpu: Remove unnecessary #ifdefs
Drop some #ifdefs that are not needed or can be converted to compile-time
checks.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:44 +08:00
Simon Glass
20b049e885 x86: mp_init: Adjust bsp_init() to return more information
This function is misnamed since it does not actually init the BSP. Also
it is convenient to adjust it to return a little more information.

Rename and update the function, to allow it to return the BSP CPU device
and number, as well as the total number of CPUs.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:44 +08:00
Simon Glass
77a5e2d3bc x86: mp_init: Set up the CPU numbers at the start
At present each CPU is given a number when it starts itself up. While this
saves a tiny amount of time by doing the device-tree read in parallel, it
is confusing that the numbering happens on the fly.

Move this code into mp_init() and do it at the start.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:44 +08:00
Simon Glass
a6c9fd4da0 x86: mp_init: Drop the num_cpus static variable
This does not need to be global across all functions in this file. Pass a
parameter instead.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:43 +08:00
Simon Glass
3a5752ccff x86: mp_init: Switch parameter names in start_aps()
These parameters are named differently from elsewhere in this file. Switch
them to avoid confusion.

Also add comments to this function.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-20 09:46:43 +08:00
Simon Glass
e624858407 x86: mp_init: Avoid declarations in header files
The functions used by the flight plan are declared in the header file but
are not used in any other file.

Move the flight plan steps down to just above where it is used so that we
can make these function static.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:43 +08:00
Simon Glass
78d57d63d7 x86: Move MP code into mp_init
At present the 'flight plan' for CPUs is passed into mp_init. But it is
always the same. Move it into the mp_init file so everything is in one
place. Also drop the SMI function since it does nothing. If we implement
SMIs, more refactoring will be needed anyway.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:42 +08:00
Simon Glass
cb1cb7146f x86: mp_init: Switch to livetree
Update this code to use livetree calls instead of flat-tree.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-20 09:46:42 +08:00
Masahiro Yamada
b75d8dc564 treewide: convert bd_t to struct bd_info by coccinelle
The Linux coding style guide (Documentation/process/coding-style.rst)
clearly says:

  It's a **mistake** to use typedef for structures and pointers.

Besides, using typedef for structures is annoying when you try to make
headers self-contained.

Let's say you have the following function declaration in a header:

  void foo(bd_t *bd);

This is not self-contained since bd_t is not defined.

To tell the compiler what 'bd_t' is, you need to include <asm/u-boot.h>

  #include <asm/u-boot.h>
  void foo(bd_t *bd);

Then, the include direcective pulls in more bloat needlessly.

If you use 'struct bd_info' instead, it is enough to put a forward
declaration as follows:

  struct bd_info;
  void foo(struct bd_info *bd);

Right, typedef'ing bd_t is a mistake.

I used coccinelle to generate this commit.

The semantic patch that makes this change is as follows:

  <smpl>
  @@
  typedef bd_t;
  @@
  -bd_t
  +struct bd_info
  </smpl>

Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
2020-07-17 09:30:13 -04:00
Simon Glass
4021ee6388 x86: Rename board_final_cleanup() to board_final_init()
This function sounds like something that is called when U-Boot is about to
jump to Linux. In fact it is an init function.

Rename it to reduce confusion.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
22a7396f7f x86: acpi: Correct the version of the MADT
Currently U-Boot implements version 2 but reports version 4. Correct it.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
a308b1fa39 x86: Drop setup_pcat_compatibility()
This function does not exist anymore. Drop it from the header file.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
538c9b3d29 x86: Update the comment about booting for FSP2
The comment here applies only to FSP1, so update it.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
9ef168676c x86: Store the coreboot table address in global_data
At present this information is used to locate and parse the tables but is
not stored. Store it so that we can display it to the user, e.g. with the
'bdinfo' command.

Note that now the GD_FLG_SKIP_LL_INIT flag is set in get_coreboot_info(),
so it is always set when booting from coreboot.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
487852b51b x86: apl: Adjust FSP-M code to avoid hard-coded address
Update this code to calculate the address to use, rather than hard-coding
it. Obtain the requested stack size from the FSP.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
efd3132852 x86: apl: Set the correct boot mode in the FSP-M code
If there is MRC information we should run FSP-M with a different
boot_mode flag since it is supposed to do a 'fast path' through the
memory init. Fix this.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
b336a2b8f6 x86: Add debugging to table writing
Writing tables is currently pretty opaque. Add a bit of debugging to the
process so we can see what tables are written and where they start/end in
memory.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
b95611f67e x86: apl: Fix save/restore of ITSS priorities
The FSP-S changes the ITSS priorities. The code that tries to save it
before running FSP-S and restore it afterwards does not work as U-Boot
relocates in between the save and restore. This means that the driver
data saved before relocation is lost and the new driver just sees zeroes.

Fix this by allocating space in the relocated memory for the ITSS data.
Save it there and access it from the driver after relocation.

This fixes interrupt handling on coral.

Also drop the log_msg_ret() in irq_first_device_type() since this function
can be called speculatively in places where we are not sure if there is
an interrupt controller of that type. The resulting log errors are
confusing when there is no error.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
a8c2789c09 x86: irq: Support flags for acpi_gpe
This binding currently has a flags cell but it is not used. Make use of it
to create ACPI tables for interrupts.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
62fba44d6d x86: apl: Hide the p2sb on exit from U-Boot
This confuses Linux's PCI probing so needs to be hidden when booting
Linux. Add a remove() method to handle this.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Tested-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
f549d9bbde x86: apl: Support set_hide() in p2sb driver
Add support for this new method in the driver and in the fsp-s setup.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Tested-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
eb2ebbcf56 x86: apl: Use memory-mapped access for VBT
Use the new binman memory-mapping function to access the VBT, to simplify
the code.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
31b410a68c x86: Add error checking for csrt table generation
Generation of this table can fail, so update the function to return an
error code.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
59cf26480b x86: pinctrl: Drop the acpi_path member
This is in the device tree now, so drop the unnecessary field here.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
05516e3d47 x86: pinctrl: Update comment for intel_pinctrl_get_pad()
Add information about what is returned on error.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
4916f4586e x86: pinctrl: Add a way to get the pinctrl reg address
At present we can query the offset of a pinctrl register within the p2sb.
For ACPI we need to get the actual address of the register. Add a function
to handle this and rename the old one to more accurately reflect its
purpose.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
7f926c9648 x86: Add support for building up an NHLT structure
The Intel Non-High-Definition-Audio Link Table (NHLT) table describes the
audio codecs and connections in a system. Various devices can contribute
information to produce the table.

Add functions to allow adding to the structure that is eventually written
to the ACPI tables. Also add the device-tree bindings.

Signed-off-by: Simon Glass <sjg@chromium.org>
2020-07-17 14:32:24 +08:00
Simon Glass
8d7ff12e63 acpi: Allow creating the GNVS to fail
In some cases an internal error may prevent this from working. Update the
function return value and report the error. At present the API for writing
tables does not easily support reporting errors, but once it is fully
updated to use a context pointer, this will be easier.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
58a6ccd34e x86: Allow devices to write to DSDT
Call the new core function to inject ASL programmatically into the DSDT.
This is made up of fragments generated by devices that have the
inject_dsdt() method. The normal, compiled ASL file is added after this.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
351fef5c57 x86: Allow devices to write an SSDT
Call the new core function to write the SSDT. This is made up of fragments
generated by devices that have the fill_ssdt() method.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
85f2def907 x86: acpi: Move MADT down a bit
Put this table before MCFG so that it matches the order that coreboot uses
when passing tables to Linux. This is a cosmetic change since the order of
the tables does not otherwise matter.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00
Simon Glass
0990c894cc x86: fsp: Support a warning message when DRAM init is slow
With DDR4, Intel SOCs take quite a long time to init their memory. During
this time, if the user is watching, it looks like SPL has hung. Add a
message in this case.

This works by adding a return code to fspm_update_config() that indicates
whether MRC data was found and a new property to the device tree.

Also add one more debug message while starting.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Tested-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
2020-07-17 14:32:24 +08:00
Simon Glass
ef5f5f6ca6 x86: Avoid #ifdef with CONFIG_HAVE_ACPI_RESUME
At present this enables a few arch-specific members of the global_data
struct which are otherwise not part of the struct. As a result we have to
use #ifdef in various places.

The cost of always having these in the struct is small. Adjust things so
that we can use compile-time code instead of #ifdefs.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-07-17 14:32:24 +08:00