На прошлых выходных был получен i-Pi SMARC 1200 development kit от ADLINK на базе MediaTek Genio 1200 — восьмиядерного процессора Cortex-A78/A55 для AIoT. После осмотра оборудования возникло желание установить образ Yocto Linux, но процесс остановился из-за предположения, что требуется предварительная установка Ubuntu 18.04 в виртуальной машине или на другом компьютере. Однако документация была обновлена, и теперь ясно указано, что требуется «Ubuntu 18.04 или новее». В результате образ был успешно записан с ноутбука под управлением Ubuntu 22.04 после установки необходимых зависимостей и инструментов:
$ sudo apt install android-tools-adb android-tools-fastboot
$ echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0e8d", ATTR{idProduct}=="201c", MODE="0660", $ GROUP="plugdev"' | sudo tee -a /etc/udev/rules.d/96-rity.rules
$ echo -n 'SUBSYSTEM=="usb", ATTR{idVendor}=="0e8d", ATTR{idProduct}=="201c", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="usb", ATTR{idVendor}=="0e8d", ATTR{idProduct}=="0003", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", MODE="0660", TAG+="uaccess" SUBSYSTEM=="gpio", MODE="0660",
TAG+="uaccess" ' | sudo tee /etc/udev/rules.d/72-aiot.rules
$ sudo udevadm control --reload-rules
$ sudo udevadm trigger
$ sudo usermod -a -G plugdev $USER
$ pip3 install -U -e "git+https://gitlab.com/mediatek/aiot/bsp/aiot-tools.git#egg=aiot-tools"
Набор инструментов готов. В будущем devkit будет поддерживать три образа: Yocto Linux, Android 13 (июль 2023) и Ubuntu 20.04 (3 квартал 2023). На данный момент на странице загрузок доступен только образ Yocto Linux, который и будет использоваться. Перед записью образа необходимо подключить плату к хосту через micro USB кабель, а также подать питание 12V, после чего можно выполнить команду для прошивки:
unzip LEC-1200-IPi-SMARC-PLUS_Yocto_Kirkstone_V2_R6_2023_04_24.zip
cd LEC-1200-IPi-SMARC-PLUS_Yocto_Kirkstone_V2_R6_2023_04_24
<span class="line">aiot-flash</span>
Вывод команды:
AIoT Tools: v1.3.1.dev7+gbc65b89
Yocto Image:
name: Rity Demo Image (rity-demo-image)
distro: Rity Demo Layer 22.2-release (rity-demo)
codename: kirkstone
machine: lec-mtk-i1200-ufs
overlays: []
WARNING:aiot:No 'ftdi-cbus' device found
Looking for MediaTek SoC matching USB device 0e8d:0003
Opening /dev/ttyACM0 using baudate=115200
Connected to MediaTek MT8195 SoC
Sending bootstrap to address: 0x201000
Jumping to bootstrap at address 0x201000 in AArch64 mode
erasing mmc0
< waiting for any device >
Erasing 'mmc0' (bootloader) request sz: 0xee5800000, real erase len: 0x0
OKAY [ 4.655s]
Finished. Total time: 4.658s
erasing mmc0boot0
Erasing 'mmc0boot0' (bootloader) request sz: 0x400000, real erase len: 0x400000
OKAY [ 0.008s]
Finished. Total time: 0.012s
erasing mmc0boot1
Erasing 'mmc0boot1' (bootloader) request sz: 0x400000, real erase len: 0x400000
OKAY [ 0.008s]
Finished. Total time: 0.012s
flashing mmc0=rity-demo-image-lec-mtk-i1200-ufs.wic.img
Sending sparse 'mmc0' 1/10 (251301 KB) OKAY [ 8.227s]
Writing 'mmc0' OKAY [ 3.355s]
Sending sparse 'mmc0' 2/10 (262141 KB) OKAY [ 8.439s]
Writing 'mmc0' OKAY [ 2.692s]
Sending sparse 'mmc0' 3/10 (262140 KB) OKAY [ 8.424s]
Writing 'mmc0' OKAY [ 2.385s]
Sending sparse 'mmc0' 4/10 (239060 KB) OKAY [ 7.770s]
Writing 'mmc0' OKAY [ 1.923s]
Sending sparse 'mmc0' 5/10 (254764 KB) OKAY [ 8.330s]
Writing 'mmc0' OKAY [ 2.601s]
Sending sparse 'mmc0' 6/10 (262140 KB) OKAY [ 8.564s]
Writing 'mmc0' OKAY [ 2.096s]
Sending sparse 'mmc0' 7/10 (262140 KB) OKAY [ 8.556s]
Writing 'mmc0' OKAY [ 2.089s]
Sending sparse 'mmc0' 8/10 (262140 KB) OKAY [ 8.556s]
Writing 'mmc0' OKAY [ 2.108s]
Sending sparse 'mmc0' 9/10 (262140 KB) OKAY [ 8.455s]
Writing 'mmc0' OKAY [ 2.086s]
Sending sparse 'mmc0' 10/10 (40 KB) OKAY [ 0.004s]
Writing 'mmc0' OKAY [ 0.009s]
Finished. Total time: 96.679s
flashing mmc0boot0=bl2.img
Sending 'mmc0boot0' (207 KB) OKAY [ 0.009s]
Writing 'mmc0boot0' OKAY [ 0.017s]
Finished. Total time: 0.159s
flashing mmc0boot1=u-boot-env.bin
Sending 'mmc0boot1' (4 KB) OKAY [ 0.002s]
Writing 'mmc0boot1' OKAY [ 0.007s]
Finished. Total time: 0.014s
Rebooting OKAY [ 0.002s]
Finished. Total time: 0.052s
После завершения процесса плата подключается к HDMI монитору, Ethernet кабелю и RF-адаптерам для мыши и клавиатуры для доступа к Yocto Desktop.
Рабочий стол практически пуст, но можно запустить терминал из окружения рабочего стола или подключиться по SSH (root без пароля) для выполнения тестов:
root@lec-mtk-i1200-ufs:~/benchmark_suite# ./run.sh
Coremark 102310.51 iter/sec
Coremark 10.28 score/MHz
Dhrystone 24343.50 DMIPS
Dhrystone 18.37 DMIPS/MHz
c-ray 133 milliseconds
STREAM 25952.80 MB/s
LMBench_L1 2.572 ns
LMBench_L2 2.405 ns
Whetstone 5742.39 MWIPS
Memset 17.00 GB/sec
Memcpy 10.66 GB/sec
Please connect HDMI monitor to your board, and make sure
DSI panel is disabled.
glmark2 1433
benchmark_model(mobilenet_v2_1.0_224, cpu) 21.85 ms
benchmark_model(mobilenet_v2_1.0_224, gpu) 12.70 ms
benchmark_model(mobilenet_v2_1.0_224_quant, cpu) 32.94 ms
benchmark_model(mobilenet_v2_1.0_224_quant, gpu) 14.02 ms
Тесты включают CPU бенчмарки (Coremark, Dhrystone), тесты пропускной способности памяти (memcpy/memset), графические тесты (glmark2) и AI бенчмарки (MobileNet V2), выполняемые на CPU и GPU, но, по-видимому, не на AI акселераторе (требует уточнения). Результат glmark2:
root@lec-mtk-i1200-ufs:~/benchmark_suite/glmark2# cat run.log
=======================================================
glmark2 2021.02
=======================================================
OpenGL Information
GL_VENDOR: ARM
GL_RENDERER: Mali-G57
GL_VERSION: OpenGL ES 3.2 v1.r32p0-01eac1.a614746065a3dea01b0881dde5799119
=======================================================
[build] use-vbo=false: FPS: 2178 FrameTime: 0.459 ms
[build] use-vbo=true: FPS: 2101 FrameTime: 0.476 ms
[texture] texture-filter=nearest: FPS: 2447 FrameTime: 0.409 ms
[texture] texture-filter=linear: FPS: 2047 FrameTime: 0.489 ms
[texture] texture-filter=mipmap: FPS: 1928 FrameTime: 0.519 ms
[shading] shading=gouraud: FPS: 1530 FrameTime: 0.654 ms
[shading] shading=blinn-phong-inf: FPS: 1461 FrameTime: 0.684 ms
[shading] shading=phong: FPS: 1423 FrameTime: 0.703 ms
[shading] shading=cel: FPS: 1393 FrameTime: 0.718 ms
[bump] bump-render=high-poly: FPS: 976 FrameTime: 1.025 ms
[bump] bump-render=normals: FPS: 1941 FrameTime: 0.515 ms
[bump] bump-render=height: FPS: 1873 FrameTime: 0.534 ms
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 1506 FrameTime: 0.664 ms
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 788 FrameTime: 1.269 ms
[pulsar] light=false:quads=5:texture=false: FPS: 1807 FrameTime: 0.553 ms
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 875 FrameTime: 1.143 ms
[desktop] effect=shadow:windows=4: FPS: 1460 FrameTime: 0.685 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 296 FrameTime: 3.378 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 319 FrameTime: 3.135 ms
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 468 FrameTime: 2.137 ms
[ideas] speed=duration: FPS: 871 FrameTime: 1.148 ms
[jellyfish] <default>: FPS: 1064 FrameTime: 0.940 ms
[terrain] <default>: FPS: 266 FrameTime: 3.759 ms
[shadow] <default>: FPS: 931 FrameTime: 1.074 ms
[refract] <default>: FPS: 427 FrameTime: 2.342 ms
[conditionals] fragment-steps=0:vertex-steps=0: FPS: 1927 FrameTime: 0.519 ms
[conditionals] fragment-steps=5:vertex-steps=0: FPS: 1769 FrameTime: 0.565 ms
[conditionals] fragment-steps=0:vertex-steps=5: FPS: 1865 FrameTime: 0.536 ms
[function] fragment-complexity=low:fragment-steps=5: FPS: 1926 FrameTime: 0.519 ms
[function] fragment-complexity=medium:fragment-steps=5: FPS: 1589 FrameTime: 0.629 ms
[loop] fragment-loop=false:fragment-steps=5:vertex-steps=5: FPS: 1814 FrameTime: 0.551 ms
[loop] fragment-steps=5:fragment-uniform=false:vertex-steps=5: FPS: 1860 FrameTime: 0.538 ms
[loop] fragment-steps=5:fragment-uniform=true:vertex-steps=5: FPS: 1684 FrameTime: 0.594 ms
=======================================================
glmark2 Score: 1418
=======================================================
Точное сравнение затруднено из-за неизвестных параметров тестирования, но для справки: результат Coremark в 102310.51 iter/sec находится между показателями AMD FX-8350 и Intel Core-i7 2600, выпущенных в 2011 году (см. таблицу ниже).
Система на Rockchip RK3588 показала 73178.23 iter/sec в CoreMark (данные Phoronix).
Немного информации о системе:
root@lec-mtk-i1200-ufs:~# uname -a
Linux lec-mtk-i1200-ufs 5.15.37-lecmtki1200-2v6 #1 SMP PREEMPT Tue Apr 18 05:37:09 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux
root@lec-mtk-i1200-ufs:~# cat /etc/issue
Rity Demo Layer 22.2-release \n \l
root@lec-mtk-i1200-ufs:~# cat /proc/cpuinfo
processor : 0
BogoMIPS : 26.00
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x2
CPU part : 0xd05
CPU revision : 0
...
processor : 7
BogoMIPS : 26.00
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x1
CPU part : 0xd41
CPU revision : 0
root@lec-mtk-i1200-ufs:~# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 3.9G 2.2G 1.5G 61% /
devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 765M 14M 752M 2% /run
tmpfs 4.0M 0 4.0M 0% /sys/fs/cgroup
tmpfs 1.9G 0 1.9G 0% /tmp
tmpfs 1.9G 524K 1.9G 1% /var/volatile
tmpfs 383M 0 383M 0% /run/user/0
root@lec-mtk-i1200-ufs:~# free -h
total used free shared buff/cache available
Mem: 3.7Gi 357Mi 3.0Gi 20Mi 390Mi 3.2Gi
Swap: 0B 0B 0B
Видно, что корневой раздел занимает всего 3.9GB, хотя модуль оснащен 64GB UFS накопителем.
Была предпринята попытка изменить размер корневого раздела с помощью fdisk, оставив незанятые секторы на всякий случай:
root@lec-mtk-i1200-ufs:~# fdisk /dev/sdc
Welcome to fdisk (util-linux 2.37.4).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
GPT PMBR size mismatch (1071659 != 15620095) will be corrected by write.
The backup GPT table is not on the end of the device. This problem will be corrected by write.
This disk is currently in use - repartitioning is probably a bad idea.
It's recommended to umount all file systems, and swapoff all swap
partitions on this disk.
Command (m for help): p
Disk /dev/sdc: 59.59 GiB, 63979913216 bytes, 15620096 sectors
Disk model: MT064GASAO2U21
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytes
I/O size (minimum/optimal): 131072 bytes / 131072 bytes
Disklabel type: gpt
Disk identifier: 380B98BD-37A5-4CA3-8451-1CE76690645B
Device Start End Sectors Size Type
/dev/sdc1 128 1151 1024 4M Microsoft basic data
/dev/sdc2 1152 4991 3840 15M Microsoft basic data
/dev/sdc3 4992 1071625 1066634 4.1G Linux root (ARM-64)
Command (m for help): d
Partition number (1-3, default 3): 3
Partition 3 has been deleted.
Command (m for help): n
Partition number (3-128, default 3): 3
First sector (4992-15620090, default 5120): 4992
Last sector, +/-sectors or +/-size{K,M,G,T,P} (4992-15620090, default 15620090): 15000000
Created a new partition 3 of type 'Linux filesystem' and of size 57.2 GiB.
Partition #3 contains a ext4 signature.
Do you want to remove the signature? [Y]es/[N]o: N
Command (m for help): t
Partition number (1-3, default 3): 3
Partition type or alias (type L to list all): 25
Changed type of partition 'Linux filesystem' to 'Linux root (ARM-64)'.
Command (m for help): p
Disk /dev/sdc: 59.59 GiB, 63979913216 bytes, 15620096 sectors
Disk model: MT064GASAO2U21
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytes
I/O size (minimum/optimal): 131072 bytes / 131072 bytes
Disklabel type: gpt
Disk identifier: 380B98BD-37A5-4CA3-8451-1CE76690645B
Device Start End Sectors Size Type
/dev/sdc1 128 1151 1024 4M Microsoft basic data
/dev/sdc2 1152 4991 3840 15M Microsoft basic data
/dev/sdc3 4992 15000000 14995009 57.2G Linux root (ARM-64)
Перед выполнением resize2fs:
root@lec-mtk-i1200-ufs:~# resize2fs /dev/sdc3
resize2fs 1.46.5 (30-Dec-2021)
Filesystem at /dev/sdc3 is mounted on /; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 8
The filesystem on /dev/sdc3 is now 14995009 (4k) blocks long.
root@lec-mtk-i1200-ufs:~# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 56G 2.2G 51G 5% /
devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 765M 12M 754M 2% /run
tmpfs 4.0M 0 4.0M 0% /sys/fs/cgroup
tmpfs 1.9G 0 1.9G 0% /tmp
tmpfs 1.9G 472K 1.9G 1% /var/volatile
tmpfs 383M 0 383M 0% /run/user/0
Размер раздела изменен успешно, но после перезагрузки изменения не сохраняются. Пришлось восстанавливать образ с помощью aiot-flash. Несколько попыток с разными параметрами не принесли результата.
Проверим производительность Gigabit Ethernet с помощью iperf3:
Загрузка:
$ iperf3 -t 60 -c 192.168.31.253 -i 10
Connecting to host 192.168.31.253, port 5201
[ 5] local 192.168.31.85 port 54740 connected to 192.168.31.253 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-10.00 sec 1.10 GBytes 942 Mbits/sec 10 540 KBytes
[ 5] 10.00-20.00 sec 1.10 GBytes 941 Mbits/sec 0 577 KBytes
[ 5] 20.00-30.00 sec 1.10 GBytes 943 Mbits/sec 0 918 KBytes
[ 5] 30.00-40.00 sec 1.10 GBytes 942 Mbits/sec 0 918 KBytes
[ 5] 40.00-50.00 sec 1.10 GBytes 942 Mbits/sec 0 918 KBytes
[ 5] 50.00-60.00 sec 1.10 GBytes 942 Mbits/sec 0 918 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-60.00 sec 6.58 GBytes 942 Mbits/sec 10 sender
[ 5] 0.00-60.00 sec 6.58 GBytes 941 Mbits/sec receiver
iperf Done.
Отправка:
$ iperf3 -t 60 -c 192.168.31.253 -i 10 -R
Connecting to host 192.168.31.253, port 5201
Reverse mode, remote host 192.168.31.253 is sending
[ 5] local 192.168.31.85 port 33710 connected to 192.168.31.253 port 5201
[ ID] Interval Transfer Bitrate
[ 5] 0.00-10.00 sec 1.09 GBytes 936 Mbits/sec
[ 5] 10.00-20.00 sec 1.09 GBytes 940 Mbits/sec
[ 5] 20.00-30.00 sec 1.09 GBytes 940 Mbits/sec
[ 5] 30.00-40.00 sec 1.09 GBytes 940 Mbits/sec
[ 5] 40.00-50.00 sec 1.09 GBytes 935 Mbits/sec
[ 5] 50.00-60.00 sec 1.08 GBytes 927 Mbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-60.00 sec 6.54 GBytes 937 Mbits/sec 0 sender
[ 5] 0.00-60.00 sec 6.54 GBytes 937 Mbits/sec receiver
iperf Done.
Полнодуплексный режим:
$ iperf3 -t 60 -c 192.168.31.253 -i 10 --bidir
Connecting to host 192.168.31.253, port 5201
[ 5] local 192.168.31.85 port 56934 connected to 192.168.31.253 port 5201
[ 7] local 192.168.31.85 port 56948 connected to 192.168.31.253 port 5201
[ ID][Role] Interval Transfer Bitrate Retr Cwnd
[ 5][TX-C] 0.00-10.00 sec 1.08 GBytes 926 Mbits/sec 9 872 KBytes
[ 7][RX-C] 0.00-10.00 sec 1014 MBytes 850 Mbits/sec
[ 5][TX-C] 10.00-20.00 sec 1.09 GBytes 933 Mbits/sec 1 1.12 MBytes
[ 7][RX-C] 10.00-20.00 sec 1.01 GBytes 865 Mbits/sec
[ 5][TX-C] 20.00-30.00 sec 1.09 GBytes 933 Mbits/sec 0 1.12 MBytes
[ 7][RX-C] 20.00-30.00 sec 1.01 GBytes 864 Mbits/sec
[ 5][TX-C] 30.00-40.00 sec 1.09 GBytes 933 Mbits/sec 3 1.12 MBytes
[ 7][RX-C] 30.00-40.00 sec 1.01 GBytes 866 Mbits/sec
[ 5][TX-C] 40.00-50.00 sec 1.09 GBytes 933 Mbits/sec 0 1.12 MBytes
[ 7][RX-C] 40.00-50.00 sec 1.01 GBytes 865 Mbits/sec
[ 5][TX-C] 50.00-60.00 sec 1.09 GBytes 933 Mbits/sec 0 1.12 MBytes
[ 7][RX-C] 50.00-60.00 sec 1.01 GBytes 866 Mbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID][Role] Interval Transfer Bitrate Retr
[ 5][TX-C] 0.00-60.00 sec 6.51 GBytes 932 Mbits/sec 13 sender
[ 5][TX-C] 0.00-60.00 sec 6.51 GBytes 932 Mbits/sec receiver
[ 7][RX-C] 0.00-60.00 sec 6.03 GBytes 863 Mbits/sec 0 sender
[ 7][RX-C] 0.00-60.00 sec 6.02 GBytes 863 Mbits/sec receiver
iperf Done.
Сеть работает отлично.
Хотелось протестировать производительность UFS с помощью iozone, но компилятор gcc отсутствует в образе Yocto Linux. Зато обнаружилось, что установлен fio .
Скорость последовательного чтения:
mkdir /home/root/fiotest
fio --directory=/home/root/fiotest --direct=1 --rw=read --bs=1024k --ioengine=libaio --iodepth=64 --size=4G --runtime=10 --numjobs=4 --group_reporting --name=fio-rand-read-sequential --eta-newline=1 | grep READ
READ: bw=517MiB/s (542MB/s), 517MiB/s-517MiB/s (542MB/s-542MB/s), io=5175MiB (5426MB), run=10018-10018msec
Скорость последовательной записи:
root@lec-mtk-i1200-ufs:~# fio --directory=/home/root/fiotest --direct=1 --rw=write --bs=1024k --ioengine=libaio --iodepth=64 --size=4G --runtime=10 --numjobs=4 --group_reporting --name=fio-rand-write-sequential --eta-newline=1 | grep WRITE
WRITE: bw=161MiB/s (169MB/s), 161MiB/s-161MiB/s (169MB/s-169MB/s), io=1813MiB (1901MB), run=11250-11250msec
542MB/s при чтении значительно быстрее любого протестированного eMMC накопителя, а 169MB/s при записи также находятся на высоком уровне.
В i-Pi SMARC 1200 development kit также есть слот M.2 (PCIe), поэтому для дальнейшего тестирования был установлен M.2 2242 NVMe SSD в разъем Key-B.
Однако накопитель не определяется, а команда lspci показывает только два PCIe устройства:
root@lec-mtk-i1200-ufs:~# lspci
00:00.0 PCI bridge: MEDIATEK Corp. Device 8195 (rev 01)
01:00.0 Ethernet controller: Marvell Technology Group Ltd. Device 2b42 (rev 11)
Неясно, что нужно сделать для активации SSD. Также есть сообщения о проблемах с подключением M.2 WiFi модуля .
ADLINK i.Pi SMARC 1200 выглядит перспективным devkit с мощным процессором Cortex-A78/A55, быстрым UFS накопителем, AI акселератором на 4.8 TOPS и широким набором интерфейсов. Однако поддержка ПО и документация требуют доработки, и, надеюсь, это будет исправлено в ближайшие месяцы. Пока можно создать собственный образ Yocto Linux для i-Pi SMARC 1200, используя meta-layer на GitHub . i-PI SMARC 1200 доступен за $379 в магазине i-Pi , но сейчас отсутствует в наличии.
Выражаем свою благодарность источнику, с которого взята и переведена статья, сайту cnx-software.com.
Оригинал статьи вы можете прочитать здесь.