Pull to refresh

Обзор отечественного одноплатного компьютера MB 77.07: От распаковки и прошивки, до написания первой DSP программы

Reading time30 min
Views60K
В наше время рынок SBC (Single Board Computer) сильно разросся, и появилось огромное количество одноплатных компьютеров на самых различных процессорах, от старых и всем известных, до совсем новых и специализированных. Недавно ко мне попал Module MB 77.07 – одноплатный компьютер от НТЦ «Модуль» на процессоре отечественного производства К1879ХБ1Я, про который на хабре уже однажды оставили небольшую заметку люди из компании Promwad. С момента того поста плату стали продавать всем желающим и было выложено ПО на официальный github компании – http://github.com/RC-MODULE

В статье будет дано описание железа и софта, от распаковки и прошивки, до написания простого примера – аудио-эффекта эха на встроенном в используемый процессор DSP ядре NeuroMatrix.

Комплектация


Плата поставляется в цветной фирменной коробочке. Внутри находятся:

  • плата MB 77.07
  • Ethernet патчкорд
  • 5V/2A блок питания
  • USB-UART для тех, кто знакомится с embedded миром впервые

Тут можно поспорить, нужно ли всё это или хватило бы обычной OEM поставки в виде антистатического пакетика с платой. Думаю, новичкам будет полезно сразу получить USB-UART, будет меньше желающих загуглить, получить стандартный вредный совет «соедини с COM портом, ничего не будет» и спалить плату в первый же день после покупки.









Железо


Теперь стоит рассмотреть саму плату. На борту платы находится ARM1176, такое же ядро используется в Raspberry Pi. Можно запускать заточенные под r-pi дистрибутивы, естественно с оговоркой на то, что нужно будет подсунуть драйвера для местных IP core, использованных в процессоре. Для облегчения этой задачи, разработчик процессора, НТЦ «Модуль», предоставляет готовые образы Debian Jessie и Raspbian Wheezy, пакеты периодически обновляются.

Рядом с самим К1879ХБ1Я распаяно два чипа DDR2 оперативной памяти по 128MB, 1GB NAND памяти, USB-hub на 4 порта, Ethernet PHY и обвязка.

На пинах, расположенных по двум сторонам платы разместились различные внешние интерфейсы:

Правая сторона:

  • JTAG
  • 2xUSB – pinout точно такой же как и на материнских платах, доставайте планки из ящиков
  • UART с boot консолью

Левая сторона, длинный разъем напоминающий IDE:

  • 8x GPIO
  • 1x Transport Stream Interface (очень подробно здесь)
  • 1x I2C
  • 1x SPI
  • 1x I2S
  • 1x SPDIF
  • 1x External Interrupt
  • 1.5x UART – один UART полный, второй только RX
  • 1x RESET
  • 5V
  • 3.3V — за данный канал отвечает выделенный канал импульсного источника питания, выдающий 800мА, реально зная современные китайские компоненты, я бы не стал вешать сюда больше 500мА

Подробная распиновка доступна по ссылке: http://www.module.ru/mb7707/doc/MB77.07-X9-pinout.pdf

Рассмотрим GPIO подробнее, так как это самое первое место, которое захочется пощупать самому. GPIO здесь два разных типа. Первый тип, это те, что выведены на гребенку, их подцепляет драйвер gpio-pl061 (IP core ARM PrimeCell™ General Purpose Input/Output PL061). Эти GPIO могут дергать прерывания к ARM ядру, что иногда бывает полезным. Кроме того, если вам не нужен Transport Stream, то его _D[0-7] пины можно мультиплексировать в GPIO, итого, можно получить 16 управляемых ножек.

Нумерация GPIO ног в sysfs есть в документах выше по ссылке, и при желании можно подергать их через sysfs:

# перейдем в sysfs, в директорию, где лежат классы gpio
cd /sys/class/gpio
# экспортируем 23-тий пин
echo 23 > export
# переключим его направление в out
echo out > gpio23/direction
# установим высокий уровень
echo 1 > gpio23/value

Второй тип GPIO – более упрощенные, управляемые другим IP core, они не могут дергать прерывания. На эти GPIO заведены два наплатных светодиода, один из них регистрируется через LED framework ядра Linux как heartbeat и показывает Load Average. Полезно при дебаге визуально видеть, не зависло ли всё.

Отдельное место на плате занимают два пина для джампера boot.

Загрузка сделана просто и надежно: когда джампера нет – происходит загрузка из NAND памяти, когда джампер надет – плата ожидает загрузки через JTAG или EDCL. Положение джампера считывается при ресете платы. О последнем режиме стоит рассказать подробнее, так как возможно, вы с ним не сталкивались.

EDCL


EDCL или Ethernet Debug Communications Link – это аппаратная функция, встроенная в Ethernet контроллер, которая позволяет писать и читать физическую память, отправляя правильно сформированные Ethernet пакеты. Данная функция шла в комплекте с IP Core используемого Ethernet контроллера – greth. Сам по себе EDCL достаточно удобен, но если оставить его включенным в продакшене – получится очень большая дыра в безопасности (интересно, сколько процессоров с таким же Ethernet контроллером работают с включенным EDCL в продакшене?).

Поверх EDCL работает любопытный инструмент edcltool, который дает со стороны хоста lua API к edcl. Данная технология используется для анбрика и прошивки плат. Сам edcltool изначально предназначен для Linux, в отличие от популярных производителей SoC-ов, предоставляющих вам какой-нибудь livesuit/phoenixsuit или rkbatch.

Есть версия для Windows, но она эксперементальна, собрана на скорую руку через mingw и требует установки WinPCAP. Кроме того, работает довольно медленно.

Linux версия ставится стандартными ./bootstrap && ./configure --prefix=/usr && make && sudo make install и просит лишь lua5.1-dev или lua5.2-dev и libelf. Последний нужен если требуется работа с NeuroMatrix DSP, о котором речь пойдет ниже.

Сам edcltool исполняет edcl скрипты, которые на самом деле – обычные lua скрипты, выглядят очень просто и читаемо. Например, вот так выглядит скрипт, запускающий код на голом ARM:

fw = require("fw");
edcl_init();
fw.run_code("mboot-uemd.bin");

Далее, вот так выглядит прошивка загрузчика mboot:

fw = require("fw");
edcl_init();
fw.run_code("mboot-uemd.bin", true); -- start in slave mode
fw.write_bootloader("mboot-signed.bin")

Создание таблицы разделов в NAND, запись ядра, dtb и корневой файловой системы:

fw = require("fw");
edcl_init();
fw.run_code("mboot-uemd.bin",true); -- start in slave mode
fw.write_bootloader("mboot-signed.bin") -- save signed loader in NAND
-- all sizes are in bytes
partition_table = {
   { "kernel", 4*1024*1024 }, 
   { "rootfs", "-" }, 
}
fw.partition(partition_table);
-- prepare and erase all nand 
fw.mboot_cmd("parterase kernel y y")
fw.mboot_cmd("parterase rootfs y y")
fw.flash_part("kernel", "uImage", false);
fw.flash_part("dtb", "mb77.07.dtb", false);
fw.flash_part("rootfs", "filesystem.ubifs", false);
fw.mboot_cmd("setenv bootargs console=ttyS0,38400n8 earlyprintk=serial   ubi.mtd=4,2048 root=ubi0:rootfs rootfstype=ubifs");
fw.mboot_cmd("setenv bootfdt 1")
fw.mboot_cmd("save");

Вот так выглядит запуск кода на голом NeuroMatrix (сокращенно – NMC):

nmc = require("easynmc");
nmc.debug = true;
edcl_init();
nmc.init_core("ipl-K1879-nmc-debug.abs");
entry = nmc.upload("myfile.abs");
nmc.run(entry);
-- Warning: nmc stdio, arguments and return code are NOT yet supported 
-- when running nmc prog via edcltool.  Use linux libeasynmc.
-- Expect these implemented in future updates. Sorry.

Запуск самих скриптов очень прост:

edcltool -f script.edcl -i имя_интерфейса

Если забыть имя интерфейса, edcltool будет использовать eth0 (на Windows – 0-вой интерфейс). Полезной может оказаться команда edcltool –l перечисляющая доступные интерфейсы, особенно полезно на Windows, где может быть не очень понятно в панели управления, какой интерфейс под каким номером зарегистрирован в системе.

Кроме вышеперечисленных, в комплекте идут другие скрипты для DSP, фактически для разработки DSP кода можно не загружать Linux. Для написания своих скриптов в архиве с edcltool лежит файлик SCRIPTING.TXT, в котором подробно описаны доступные из edcl окружения функции.

Прошивки


Теперь поговорим о главном, что требуется от разработчика процессора после его выпуска – поддержке прошивок и комплекта их поставки. Каждая прошивка с официального сайта содержит полное окружение, включая загрузчик, ядро, скрипт edcl для прошивки, win-версию на всякий случай и README.

Подробная инструкция о текущем комплекте прошивки идет в каждом архиве, но цитировать её нет смысла, так как всё сводится к одному:

  • замкнуть джампер boot
  • нажать ресет на плате
  • запустить edcltool –f eupgrade.edcl
  • попить чаю 7-10 минут
  • снять джампер и нажать ресет

Окружение


Для полноценной работы с платой, нам потребуется окружение для сборки софта под процессор платы и его DSP – Модуль предоставляет уже собранные тулчейны и под Linux, и под Windows. Установка их достаточно проста, например, предположим что тулчейны хранятся в знакомом многим месте под названием ~/x-tools. Далее нужно положить туда скачанный тулчейн от Модуля и не забыть прописать очередной bin в свой PATH:

export PATH=$PATH:~/x-tools/arm-module-linux-gnueabi/bin

Дистрибутивы


Производитель процессора придерживается минимализма при сборке своих дистрибутивов и по умолчанию, Debian и Raspbian идут в минимальной комплектации с поддержкой сети и ssh. X11 на 324MHz как-то медленно работает, плюс, загрузчик не умеет ничего рисовать на экран. Главным способом связи с платой является её serial console, ради которой в комлпект поставки и был положен UART. По умолчанию сеть дистрибутивами от производителя поднимается с адресом платы 192.168.0.7, логин root, пароль 12345678.

Репозитории производителя выставляются следующим образом в sources.list:

# RC Module's repository with MB77.07 packages
deb http://www.module.ru/mb7707/ stable updates

Загрузка системы


Теперь немного о том, как система грузится с NAND. Первым исполняется IPL загрузчик и проверяет положение джампера boot и:

  • ждет по волшебному адресу 0x00100000 (начальный адрес первого SRAM банка памяти IM0) от edcl/jtag 32-битного адреса куда прыгнуть, если джампер установлен
  • загружает из NAND загрузчик в IM0, проверяет md5 и начинает его исполнение, если джампер не установлен

Первым стартует загрузчик mboot. Это форк u-boot, немного отрефакторенный. Работает из SRAM памяти, которой на процессоре на удивление много: 4 банка, под названиями IM[0-3]. Каждый банк по 256 килобайт. Как удалось выяснить, предназначение каждой следующее:

  • IM0 – память, откуда работает загрузчик, после загрузки не используется и простаивает, можно использовать для своих корыстных целей.
  • IM1, IM3 – память NeuroMatrix, откуда предполагается запускать NMC код, NMC имеет приоритет при обращении к ней.
  • IM2 – используется декодером h264. Если декодер не используется – можно использовать для своих нужд

К слову, старую версию mboot можно найти в google, лежит она на github аккаунте Сергея Миронова, ссылка для сокращения времени поиска: github.com/ierton/mboot, так же там можно найти overlay для Gentoo, но он не обновлялся довольно продолжительное время, так что вряд ли он кому-то пригодится.

Последнее, что можно сказать о загрузчике, это то, что кроме команды pmgr есть еще пара команд для апдейта – fwupgrade и eupgrade, предназначены для прошивки по tftp образов, которые могут быть больше чем размер доступной на плате DDR памяти, такое тоже случается. В остальном, это обычный u-boot, к которому многие привыкли. Из загрузчика доступна сеть, работа с NAND, SPI Flash (которого на MB77.07, к сожалению, не положили) и немного usb, без поддержки файловых систем.

Вернемся к памяти, в адресном пространстве сразу после IM0 идет IM1, а потом сразу IM3, это стоит помнить при написании низкоуровневого кода, работающего с SRAM, так же при написании подобного кода стоит помнить, что тут действительно много SRAM памяти, на каком-нибудь старом добром AT91RM9200 накристальной памяти было всего 4К. Именно по этой причине, загрузчик здесь работает напрямую в SRAM, без SPL. Такой подход очень полезен, так как в отличие от DDR, что у ARM, что у NeuroMatrix время доступа будет всего пару тактов. Другими словами – код оттуда будет работать очень и очень быстро. Более того, если код для NMC будет работать только из IM1/IM3, не обращаясь никак к DDR – на NMC можно будет крутить какую-нибудь очень жесткую realtime часть.

Далее, стоит рассмотреть NAND подробнее, он разбит на разделы, первыми идут boot, env и dtb. Они жестко забиты и изменять их размеры нельзя. В первом лежит загрузчик, во втором его окружение, в третьем скомпилированный Device Tree Blob. Остальное разбивается интерактивно командой загрузчика pmgr, либо автоматом при прошивке образа из edcl скрипта. Заданные разделы загрузчик передает ядру, дописывая в cmdline стандартные mtdparts.

С NAND вообще ожидалось увидеть что-нибудь станное. По опыту работы с linux-sunxi и linux-rockchip, NAND и там, и там был в ужасающем состоянии. Allwinner сделали из NAND свой собственный block device с частично закрытым FTL (flash translation layer), с своими накостылеными алгоритмами wear-leveling’а и прочего. Rockchip вообще предоставляет NAND только в виде закрытого модуля rknand.ko, исходников которого не дают даже под NDA партнерам компании, как мне рассказали в #linux-rockhip@freenode. Здесь же NAND представляет собой обычный standalone mtd device, поверх которого используется ставший уже стандартом UBIFS, что не может не радовать.

Теперь собственно о самой загрузке. Думаю, многим было бы интересно посмотреть на dmesg:
Загрузка системы
MBOOT (K1879 and friends): Version mboot-00063-g9302e24-dirty (Built Thu Aug 21 17:10:13 MSK 2014)
OTP info: boot_source 2 jtag_stop 0 words_len 1024
Maximum bank size: 0x10000000 bytes
Detected 134217728 bytes of EM0 memory
MEMORY: 40000000 -> 48000000
Memory layout
        0x00100010  early
        0x001001C8  text
        0x0011E9BC  data
        0x00127D40  signature
        0x00127D44  bss_start
        0x00135EB4  stack_start
        0x00137FF8^ stack_ptr
        0x00138000  malloc
        0x0017F000  env
mnand_read_id: flash id 0xD3
mnand_read_id: flash ext_id 0x95
mnand_read_id: CS0 NAND 1GiB 3,3V 8-bit size(1024) writesize(2048) oobsize(64) erasesize(131072)
mnand_read_id: flash id 0x00
mnand_read_id: WARNING: Unknown flash ID. Using default (0xF1)
mnand_read_id: flash ext_id 0x00
mnand: Chip configurations differ, ignoring CS1
greth: Setting GRETH base addr to 0x20034000
greth: Found GRETH at 0x20034000, irq 255
greth: Resetting GRETH
greth: greth: 'phyaddr' not set, fall back to built-in table
greth: greth: using preset PHY addr: 1f
greth: Resetting the PHY
greth: write_mii: 0x20034010 < 0xF809F801 [p:31 a:0 d:0xF809]
greth: write_mii: 0x20034010 < 0x0000F801 [p:31 a:0 d:0x0000]
greth: 10/100 GRETH Ethermac at [0x20034000] irq 255. Running 10 Mbps half duplex
PHY info not available
greth: greth_init
greth: greth_init: enabling receiver
ETH new device: name GRETH_10/100 
greth: GRETH: New MAC address: 02:00:f7:00:27:0f
USB thresholds: in 0x20 out 0x7e
Is there an EDCL emergency? Nope
edcl: Ethernet debug disabled by environment
MTD Partition:       boot @ 0x00000000 size 0x00040000
MTD Partition:        env @ 0x00040000 size 0x00020000
MTD Partition:        dtb @ 0x00060000 size 0x00020000
MTD Partition:     kernel @ 0x00080000 size 0x00400000
MTD Partition:     rootfs @ 0x00480000 size 0x3FB80000
Hit any key (in 2 sec) to skip autoload...
Running autoload command 'tftp;bootm;'
TFTP Using GRETH_10/100 device
TFTP params: server 192.168.0.1 our_ip 192.168.0.7
TFTP params: filename 'uImage-3' load_address 0x40100000
TFTP Loading: T #################################################################
        #################################################################
        #################################################################
        #################################################################
        #################################################################
        #################################################################
        #################################################################
        ################################
TFTP done
Linux preparing to boot the kernel: machid 0xcd1
Using Flatterned Device Tree boot method
IMG moving image: type 2 from 0x40100040 to 0x40008000
HINT: To optimize boot time adjust loadaddr to: 0x40007fc0
Linux entry 0x40008000
USB thresholds: in 0x20 out 0x7e
Uncompressing Linux... done, booting the kernel.
[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 3.10.28-shadow1-00032-gb8b1a50 (necromant@sylwer) (gcc version 4.8.1 (crosstool-NG 1.19.0) ) #141 Thu Aug 21 14:23:26 MSK 2014
[    0.000000] CPU: ARMv6-compatible processor [410fb767] revision 7 (ARMv7), cr=00c5387d
[    0.000000] CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
[    0.000000] Machine: Module MB77.07, model: Module MB77.07
[    0.000000] bootconsole [earlycon0] enabled
[    0.000000] Memory policy: ECC disabled, Data cache writeback
[    0.000000] On node 0 totalpages: 24128
[    0.000000] free_area_init_node: node 0, pgdat c050df84, node_mem_map c0596000
[    0.000000]   Normal zone: 288 pages used for memmap
[    0.000000]   Normal zone: 0 pages reserved
[    0.000000]   Normal zone: 24128 pages, LIFO batch:3
[    0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
[    0.000000] pcpu-alloc: [0] 0 
[    0.000000] Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 23840
[    0.000000] Kernel command line: debug console=ttyS0,38400n8 earlyprintk=serial  ubi.mtd=4,2048 root=ubi0:rootfs rootfstype=ubifs mtdparts=mnand:0x40000@0x0(boot),0x20000@0x40000(env),0x20000@0x60000(dtb),0x400000@0x80000(kernel),0x3FB8000)
[    0.000000] PID hash table entries: 512 (order: -1, 2048 bytes)
[    0.000000] Dentry cache hash table entries: 16384 (order: 4, 65536 bytes)
[    0.000000] Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
[    0.000000] Memory: 94MB 0MB = 94MB total
[    0.000000] Memory: 89208k/89208k available, 41864k reserved, 0K highmem
[    0.000000] Virtual kernel memory layout:
[    0.000000]     vector  : 0xffff0000 - 0xffff1000   (   4 kB)
[    0.000000]     fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
[    0.000000]     vmalloc : 0xc8800000 - 0xff000000   ( 872 MB)
[    0.000000]     lowmem  : 0xc0000000 - 0xc8000000   ( 128 MB)
[    0.000000]     modules : 0xbf000000 - 0xc0000000   (  16 MB)
[    0.000000]       .text : 0xc0008000 - 0xc04af9cc   (4767 kB)
[    0.000000]       .init : 0xc04b0000 - 0xc04d31d4   ( 141 kB)
[    0.000000]       .data : 0xc04d4000 - 0xc0515d50   ( 264 kB)
[    0.000000]        .bss : 0xc0515d50 - 0xc0595990   ( 512 kB)
[    0.000000] SLUB: HWalign=32, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
[    0.000000] NR_IRQS:128
[    0.000000] regs = 0xf8000000, irq_start = 0
[    0.000000] VIC @f8000000: id 0x00041192, vendor 0x41
[    0.000000] regs = 0xf8010000, irq_start = 32
[    0.000000] VIC @f8010000: id 0x00041192, vendor 0x41
[    0.000000] UEMD: Firing up timer system
[    0.000000] Clocksource: rate 54000000 mult 19418074 shift 20
[    0.000000] Clockevent: rate 54000000 mult 231928233 shift 32
[    0.000000] sched_clock: 32 bits at 100 Hz, resolution 10000000ns, wraps every 4294967286ms
[    0.000000] Console: colour dummy device 80x30
[    0.020000] Calibrating delay loop... 215.04 BogoMIPS (lpj=1075200)
[    0.100000] pid_max: default: 32768 minimum: 301
[    0.110000] Mount-cache hash table entries: 512
[    0.120000] CPU: Testing write buffer coherency: ok
[    0.130000] Setting up static identity map for 0xc03417b8 - 0xc03417f0
[    0.150000] devtmpfs: initialized
[    0.160000] NET: Registered protocol family 16
[    0.170000] DMA: preallocated 256 KiB pool for atomic coherent allocations
[    0.240000] OTP ROM is not flashed
[    0.250000] msvdhd: configuring memory
[    0.260000] hw-breakpoint: found 6 breakpoint and 1 watchpoint registers.
[    0.270000] hw-breakpoint: maximum watchpoint size is 4 bytes.
[    0.410000] bio: create slab <bio-0> at 0
[    0.430000] SCSI subsystem initialized
[    0.440000] ssp-pl022 2002e000.ssp: ARM PL022 driver, device ID: 0x00041022
[    0.450000] ssp-pl022 2002e000.ssp: BUSNO: 0
[    0.460000] pl022: mapped registers from 0x2002e000 to f802e000
[    0.470000] ssp-pl022 2002e000.ssp: registered master spi0
[    0.480000] spi spi0.0: allocated memory for controller's runtime state
[    0.490000] ssp-pl022 2002e000.ssp: SSP Target Frequency is: 25000000, Effective Frequency is 13500000
[    0.500000] ssp-pl022 2002e000.ssp: SSP cpsdvsr = 2, scr = 1
[    0.510000] spi spi0.0: 4 <= n <=8 bits per word
[    0.520000] spi spi0.0: DMA mode NOT set in controller state
[    0.530000] spi spi0.0: setup mode 1, 8 bits/w, 25000000 Hz max --> 0
[    0.540000] ssp-pl022 2002e000.ssp: registered child spi0.0
[    0.550000] ssp-pl022 2002e000.ssp: probe succeeded
[    0.570000] usbcore: registered new interface driver usbfs
[    0.580000] usbcore: registered new interface driver hub
[    0.590000] usbcore: registered new device driver usb
[    0.600000] media: Linux media interface: v0.10
[    0.610000] Linux video capture interface: v2.00
[    0.620000] Advanced Linux Sound Architecture Driver Initialized.
[    0.630000] Switching to clocksource uemd_timer1
[    0.730000] NET: Registered protocol family 2
[    0.740000] TCP established hash table entries: 1024 (order: 1, 8192 bytes)
[    0.750000] TCP bind hash table entries: 1024 (order: 0, 4096 bytes)
[    0.760000] TCP: Hash tables configured (established 1024 bind 1024)
[    0.780000] TCP: reno registered
[    0.790000] UDP hash table entries: 256 (order: 0, 4096 bytes)
[    0.800000] UDP-Lite hash table entries: 256 (order: 0, 4096 bytes)
[    0.810000] NET: Registered protocol family 1
[    0.820000] RPC: Registered named UNIX socket transport module.
[    0.830000] RPC: Registered udp transport module.
[    0.840000] RPC: Registered tcp transport module.
[    0.850000] RPC: Registered tcp NFSv4.1 backchannel transport module.
[    0.930000] squashfs: version 4.0 (2009/01/31) Phillip Lougher
[    0.950000] NFS: Registering the id_resolver key type
[    0.960000] Key type id_resolver registered
[    0.970000] Key type id_legacy registered
[    0.980000] msgmni has been set to 174
[    1.000000] alg: No test for stdrng (krng)
[    1.010000] io scheduler noop registered (default)
[    1.020000] fj_gpio: Added 32 gpio lines at base -981095888
[    1.040000] module_vdu 80173000.vdu: found VDU device at 80173000, id <ebebab01>
[    1.070000] Console: switching to colour frame buffer device 90x36
[    1.090000] fb0: Module VDU frame buffer device
[    1.140000] Serial: 8250/16550 driver, 3 ports, IRQ sharing disabled
[    1.150000] 2002b000.uart: ttyS0 at MMIO 0x2002b000 (irq = 7) is a 8250
[    1.160000] console [ttyS0] enabled, bootconsole disabled
[    1.160000] console [ttyS0] enabled, bootconsole disabled
[    1.170000] 20022000.uart: ttyS1 at MMIO 0x20022000 (irq = 9) is a 8250
[    1.180000] 2002c000.uart: ttyS2 at MMIO 0x2002c000 (irq = 8) is a 8250
[    1.200000] [drm] Initialized drm 1.1.0 20060810
[    1.240000] loop: module loaded
[    1.250000] msvdhd 80180000.video_decoder: found device at 0x80180000, id 0x0025300b
[    1.300000] EasyNMC Unified DSP Framework. (c) RC Module 2014
[    1.310000] easynmc-nmc3: imem at phys 0x140000 virt 0xc8900000 size 0x80000 bytes
[    1.320000] easynmc-nmc3: HP IRQ 14 LP IRQ 15
[    1.330000] easynmc: registering core K1879-nmc (nmc3) with id 0
[    1.350000] flash ext_id 0x95
[    1.360000] mnand CS0 Samsung size(1024) writesize(2048) oobsize(64) erasesize(131072)
[    1.370000] mnand: Bad chip id or no chip at CS1
[    1.380000] mnand: Detected 1073741824 bytes of NAND
[    1.390000] 5 cmdlinepart partitions found on MTD device mnand
[    1.400000] Creating 5 MTD partitions on "mnand":
[    1.410000] 0x000000000000-0x000000040000 : "boot"
[    1.430000] 0x000000040000-0x000000060000 : "env"
[    1.450000] 0x000000060000-0x000000080000 : "dtb"
[    1.470000] 0x000000080000-0x000000480000 : "kernel"
[    1.510000] 0x000000480000-0x000040000000 : "rootfs"
[    6.500000] libphy: greth-mdio: probed
[    9.590000] ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver
[    9.600000] uemd-ehci 10040000.ehci: UEMD EHCI
[    9.610000] uemd-ehci 10040000.ehci: new USB bus registered, assigned bus number 1
[    9.630000] uemd-ehci 10040000.ehci: irq 35, io mem 0x10040000
[    9.660000] uemd-ehci 10040000.ehci: USB 2.0 started, EHCI 1.00
[    9.680000] hub 1-0:1.0: USB hub found
[    9.690000] hub 1-0:1.0: 2 ports detected
[    9.700000] ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
[    9.710000] usbcore: registered new interface driver usb-storage
[    9.720000] usbcore: registered new interface driver usbserial
[    9.730000] mousedev: PS/2 mouse device common for all mice
[    9.740000] i2c /dev entries driver
[    9.770000] module_hdmi: module_hdmi: Device ID: 0x9132
[    9.780000] usbcore: registered new interface driver i2c-tiny-usb
[    9.800000] ledtrig-cpu: registered to indicate activity on CPUs
[    9.850000] usbcore: registered new interface driver usbhid
[    9.860000] usbhid: USB HID core driver
[    9.900000] TCP: cubic registered
[    9.910000] Key type dns_resolver registered
[    9.920000] VFP support v0.3: implementor 41 architecture 1 part 20 variant b rev 5
[    9.950000] UBI: attaching mtd4 to ubi0
[   10.020000] usb 1-1: new high-speed USB device number 2 using uemd-ehci
[   10.180000] hub 1-1:1.0: USB hub found
[   10.190000] hub 1-1:1.0: 4 ports detected
[   25.560000] UBI: scanning is finished
[   25.640000] UBI: attached mtd4 (name "rootfs", size 1019 MiB) to ubi0
[   25.650000] UBI: PEB size: 131072 bytes (128 KiB), LEB size: 126976 bytes
[   25.660000] UBI: min./max. I/O unit sizes: 2048/2048, sub-page size 2048
[   25.670000] UBI: VID header offset: 2048 (aligned 2048), data offset: 4096
[   25.680000] UBI: good PEBs: 8141, bad PEBs: 15, corrupted PEBs: 0
[   25.690000] UBI: user volume: 1, internal volumes: 1, max. volumes count: 128
[   25.700000] UBI: max/mean erase counter: 4/1, WL threshold: 4096, image sequence number: 23379108
[   25.710000] UBI: available PEBs: 0, total reserved PEBs: 8141, PEBs reserved for bad PEB handling: 145
[   25.720000] mvdu: will allocate buffers
[   25.730000] mvdu: did allocate buffers cc000000
[   25.740000] UBI: background thread "ubi_bgt0d" started, PID 620
[   25.750000] ALSA device list:
[   25.760000]   #0: Module MB7707
[   25.860000] UBIFS: recovery needed
[   26.940000] UBIFS: recovery deferred
[   26.950000] UBIFS: mounted UBI device 0, volume 0, name "rootfs", R/O mode
[   26.960000] UBIFS: LEB size: 126976 bytes (124 KiB), min./max. I/O unit sizes: 2048 bytes/2048 bytes
[   26.970000] UBIFS: FS size: 1013395456 bytes (966 MiB, 7981 LEBs), journal size 9023488 bytes (8 MiB, 72 LEBs)
[   26.980000] UBIFS: reserved for root: 0 bytes (0 KiB)
[   26.990000] UBIFS: media format: w4/r0 (latest is w4/r0), UUID D7B60B07-E5DE-408F-886D-5EACF67535FC, small LPT model
[   27.010000] VFS: Mounted root (ubifs filesystem) readonly on device 0:11.
[   27.020000] devtmpfs: mounted
[   27.030000] Freeing unused kernel memory: 140K (c04b0000 - c04d3000)
Mount failed for selinuxfs on /sys/fs/selinux:  No such file or directory
INIT: version 2.88 booting
[info] Using makefile-style concurrent boot in runlevel S.
[info] Setting the system clock.
head: cannot open '/etc/adjtime' for reading: No such file or directory
hwclock: Cannot access the Hardware Clock via any known method.
hwclock: Use the --debug option to see the details of our search for an access method.
[....] Unable to set System Clock to: Thu Jan 1 00:00:33 UTC 1970 ... (warning).
[....] Activating swap...done.
[   33.700000] UBIFS: completing deferred recovery
[   33.830000] UBIFS: background thread "ubifs_bgt0_0" started, PID 870
[   33.850000] UBIFS: deferred recovery completed
[....] Activating lvm and md swap...done.
[....] Checking file systems...fsck from util-linux 2.20.1
done.
[....] Cleaning up temporary files... /tmp. ok 
[....] Mounting local filesystems...done.
[....] Activating swapfile swap...done.
[....] Cleaning up temporary files.... ok 
[....] Setting kernel variables ...done.
[....] Configuring network interfaces...done.
[....] Cleaning up temporary files.... ok 
INIT: Entering runlevel: 2
[info] Using makefile-style concurrent boot in runlevel 2.
[....] Starting OpenBSD Secure Shell server: sshd. ok 

Debian GNU/Linux jessie/sid shadow ttyS0

shadow login: root
Password: 
Last login: Thu Jan  1 00:16:23 UTC 1970 from 192.168.0.1 on pts/0
Linux shadow 3.10.28-shadow1-00032-gb8b1a50 #141 Thu Aug 21 14:23:26 MSK 2014 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@shadow:~# uname -a
Linux shadow 3.10.28-shadow1-00032-gb8b1a50 #141 Thu Aug 21 14:23:26 MSK 2014 armv6l GNU/Linux
root@shadow:~#


Первое, что привлекает внимание это строчка «EasyNMC DSP Framework © RC Module 2014». Далее в лог пишется, сколько NMC ядер найдено и исходя из этого, можно предположить, что драйвера и утилиты заточены на то, что ядер NMC когда-нибудь будет много. В процессоре на этой плате ядро NMC только одно.

Следующим многим было бы интересно узнать, как NMC взаимодействует с Linux. У разработчиков была выпытана следующая информация: к NMC от ARM идет три прерывания. Немаскируемое NMI, HP (High Priority) и LP (Low Priority). В обратную сторону так же HP и LP. Для запуска DSP ядра необходимо подать начальный сброс и дернуть NMI. Так запускается IPL код NeuroMatrix. После первого запуска, NMC крутится в этом начальном коде, который обрабатывает NMI прерывание. Через этот начальный код организуется перезапуск приложения. Все довольно просто.

Время для DSP


В половине статьи постоянно упоминается DSP, теперь пришло время подробно рассмотреть, как же с ним работать. Для работы с ним есть пакет nmc-utils, состоит из двух утилит и библиотеки:

  • nmctl
  • nmrun
  • libeasynmc (используется самими утилитами)

В дереве исходников так же нашлись исходники IPL для NeuroMatrix, примеры и libeasynmc-nmc (выполняемая на nmc часть этой библиотеки). Бинарных блобов в ходе раскопок найдено не было, тайные агенты Free Software Foundation могут спать спокойно.

nmctl


Начнем с утилит. Первая это nmctl. Она умеет загружать начальный код, запускать, останавливать, мониторить события, отправлять прерывания и еще кучу всего.
Помощь по nmctl
root@shadow:~# nmctl --help
nmctl — The EasyNMC control utility
© 2014 RC Module | Andrew 'Necromant' Andrianov <andrew@ncrmnt.org>
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
License: LGPLv2
Usage: ./nmctl [options] [actions] — operate on core 0 (default)
./nmctl --core=n [options] — operate on selected core
./nmctl --core=all [options] — operate all cores
Valid options are:
--core=id — Select a core to operate on (--core=all selects all cores)
--list — list available nmc cores in this system and their state
--help — Show this help
--force — Disable internal seatbelts (DANGEROUS!)
--nostdio — Do not auto-attach stdio
--debug — print lots of debugging info (nmctl)
--debug-lib — print lots of debugging info (libeasynmc)
Valid actions are:
--boot — Load initcode and boot a core (all cores)
--reset-stats — Reset driver statistics for core (all cores)
--load=file.abs — Load abs file to core internal memory
--start=file.abs — Load abs file to core internal memory and start it
--irq=[nmi,lp,hp] — Send an interrupt to NMC
--kill — Abort nmc program execution
--mon — Monitor IRQs from NMC
--dump-ldr-regs — Dump init code memory registers

ProTIP(tm): You can supply init code file to use via NMC_STARTUPCODE env var
When no env is set nmctl will search a set of predefined paths

Одна из главных функций, вывод информации о количестве ядер NMC в системе:
nmctl --list
root@shadow:~# ./nmctl --list
0. name: K1879-nmc type: nmc3 (cold)
IRQs Recv: HP: 0 LP: 0
IRQs Sent: NMI: 0 HP: 0 LP: 0

nmrun


Далее рассмотрим nmrun.
Помощь по nmrun
root@shadow:~# ./nmrun --help
nmrun — The EasyNMC app runner wrapper
© 2014 RC Module | Andrew 'Necromant' Andrianov <andrew@ncrmnt.org>
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
License: LGPLv2
Usage: ./nmrun [options] myapp.abs [arguments] — operate on core 0 (default)
Valid options are:
--help — Show this help
--core=id — Select a core to operate on (Default — use first usused core)
--force — Disable internal seatbelts (DANGEROUS!)
--nostdio — Do not auto-attach stdio
--nosigint — Do not catch SIGINT
--detach — Run app in background (do not attach console)
Debugging options:
--debug — Print lots of debugging info (nmctl)
--debug-lib — Print lots of debugging info (libeasynmc)

Это враппер для DSP кода. Он запускает .abs файлы на NMC таким образом, что весь printf с NMC происходит в stdout этой обертки, а всё, что мы отправим на stdin – получает NMC. Есть поддержка передачи параметров, если после .abs файла перечислять параметры – они передадуться в NMC в виде аргументов argv. Return из main() выполняющегося на NMC передается nmrun, который он так же возвращает в систему в виде обычного return кода. Всё это моментально превращает сложную DSP программу в обычную command line утилиту, с которой справится даже новичок в DSP. Порог вхождения снижается в разы, ничего ненужно перешивать или специально обрабатывать.

Попробуем запустить простейший пример:

root@shadow:~# nmrun /usr/share/examples/easynmc-0.1/arguments.abs hello world
Application now started, hit CTRL+C to stop it
Hello world! I am the NMC blinking ledz!
I have been given 3 arguments
Argument 0 is nmrun
Argument 1 is hello
Argument 2 is world
App terminated with result 3, exiting
root@shadow:~# echo $?
3

Сами ядра NMC присутствуют в системе в виде /dev/nmc0io и /dev/nmc0mem, где:

  • /dev/nmc0mem – память NMC, те самые банки SRAM – IM1 + IM3, о которых речь велась выше. Просто выполнив cat /dev/nmc0mem > dump.bin можно снять дамп памяти DSP
  • /dev/nmc0io – виртуальный последовательный порт, можно писать и читать stdio, в том числе и в неблокирующем виде через poll/epoll

Для всех остальных операций – ioctl, одинаковые для обоих устройств, их использует libeasynmc.

Сами утилиты построенны на этой библиотеке, исходники так же полностью открыты под LGPLv2. Беглый осмотр показывает, что внутри кроется достаточно простой и понятный синхронный API. Казалось бы, зачем нам синхронный API во времена node.js? Оказалось, драйвера DSP поддерживают epoll/select не только для stdio, все события от NMC (прерывания, факт отправки NMI другим процессом) можно получать через poll/epoll. Соответственно, этот дескриптор можно подсунуть любимым libevent, libuv и т.д.

Action


Теперь, вооружившись этими зананиями, попробуем развернуть окружение для разработки и собрать чего-нибудь для NMC. Допустим, в MB77.07 мы прошили Raspbian, установили тулчейны.

Начнем с nmc-utils.

git clone github.com/RC-MODULE/nmc-utils.git 
cd nmc-utils

Осмотримся в том что мы склонировали с github:

  • в корне лежат исходники libeasynmc, их не особенно много, так как библиотека предельно простая. Исходный код nmctl можно использовать как простой пример работы с libeasynmc во всех его вариантах
  • в libeasynmc-nmc лежат исходники NMC части библиотеки, которую мы будем подключать к нашим NMC проектам
  • в doc/ есть подробное описание порядка работы с библиотекой и того, как комплировать. Особенно полезным будет на начальных этапах возможность статической сборки nm* утилит
  • в ipl/ лежат исходники начального кода, который крутится на NMC
  • в examples/ есть пачка простых примеров

Кросс-сборка сводится к следующей магии:

GNU_TARGET_NAME=arm-module-linux-gnueabihf make

Если необходимо собрать все утилиты (nmctl, mnrun) статически:

GNU_TARGET_NAME=arm-module-linux-gnueabihf make STATIC=y

Если плата загружена по NFS и корень доступен где-то рядом, то можно установить все в него запустив следующее:

DESTDIR=/srv/rootfs/mb7707/ make install

Далее, при желании, на .deb based системах, можно собрать пачку deb-пакетов:

GNU_TARGET_NAME=arm-module-linux-gnueabihf ARCH=armhf make deb

При сборке deb пакетов требуется наличие в системе dpkg-deb и надо указывать deb-архитектуру, которая будет фигурировать в пакете: armhf для Raspbian, armel для Debian. На выходе получим файлы:

  • nmc-utils-abs-0.1-armhf.deb — Бинарники примеров, ставятся в /usr/share/examples/easynmc-0.1
  • nmc-utils-bin-0.1-armhf.deb — nmctl, nmrun и сама библиотека libeasynmc.so
  • nmc-utils-dev-0.1-armhf.deb — Заголовочные файлы, pkg-config .pc файл и прочее
  • nmc-utils-doc-0.1-armhf.deb — README и все содержимое doc/
  • nmc-utils-ipl-0.1-armhf.deb — IPL, ставится в /usr/share/easynmc-0.1/ipl/

Ставить всё это руками не обязательно, так как все свежие прошивки от Модуля уже содержат все эти пакеты по умолчанию.

Для первой своей программы на NMC было решено поиграться со звуком и сделать нечто, что будет в реальном времени на NMC добавлять эффект эхо. Можно использовать следующий pipeline:
arecord | nmrun ./echo.abs | aplay

Где arecord будет записывать данные с микрофона и передавать прямиком на наш DSP, который будет добавлять эхо и выдавать сэмплы на выход, далее передаем их на воспроизведение в aplay. Для захвата звука была использована дешевая китайская USB звуковая карта, валявшаяся годами на полке. В данном примере использовать libeasynmc не потребуется, хватит обычного nmrun.

Прежде чем продолжить, нужно сделать отступление и рассказать кое что о производительности такого решения. stdin/stdout хоть и являются самым простым способом обмена данными, но все же достаточно медленны. Они используют два кольцевых буфера, каждый байт должен быть сначала записан ARM ядром в кольцевой буфер, потом считан оттуда в другой буфер NMC ядром, обработан, результат записан в другой кольцевой буфер, откуда в свою очередь будет скопирован опять ARM. Мягко говоря, это не очень быстро, хоть кольцевые буферы и лежат в SRAM памяти. По этому, если нужна будет максимальная производительность, то придется все же забыть про stdio и командную строку и использовать libeasynmc.

При разработке на C/С++ под NeuroMatrix надо учитывать несколько особенностей, которые могут повергнуть многих, кто никогда ничего не писал под DSP процессоре, в культурный шок. Далее собран список граблей, на которые можно наступить:

  • Отсутствует байтовая адресация. sizeof(char) == 1, sizeof(short) == 1. Указатель в случае NMC указывает на 32-х битные ячейки памяти. Соответственно адрес в пространстве NMC можно получить сдвигом на два бита вправо
  • Из за предыдущего пункта все ASCII представляют собой массив uint32_t[] в младшем байте каждого нужный нам символ, в старших нули
  • nmrun по умолчанию говорит драйверу делать переформатирование строк туда и обратно для stdin и stdout (можно отключить)
  • NMC не имеет MMU и адресует физическую память. Потому, просто передать указатель на буфер с данными из приложения мы не можем
  • Если использовать NMC ядром какую-нибудь периферию напрямую, то лучше выгрузить соответствующие драйвера со стороны Linux. Ни один драйвер не ожидает, что с его регистрами будет работать кто-то еще, кроме него самого
  • В чипе есть некоторые блоки (несколько таймеров, например) доступные только со стороны NMC. API можно посмотреть в nc_int_soc.h из состава тулчейна
  • Библиотека языка С достаточно спартанская
  • Так как NeuroMatrix имеет доступ ко всему адресному пространству, ошибка в коде для него может легко завалить всю систему и вызвать необходимость перезапуска
  • Исходя из предыдущего, если выставить плату в интернет, то удаленный атакующий получив доступ к NMC, может потенциально заставить его пропатчить память ядра для поднятия прав в системе. Соответственно права на /dev/nmc надо выставлять очень аккуратно. Иначе получится как в прошлый раз у Samsung с /dev/exynos-mem
  • NeuroMatrix состоит из RISC части и векторного ядра. Все, что пишется на C, исполняется на не особенно быстрой RISC части, от которой не следует ожидать чудес производительности. Исполняя код на DSP вы не делаете его по взмаху волшебной палочки идеально быстрым. Реальный выигрыш можно получить только задействовав векторное ядро, на ассемблере. Не все алгоритмы хорошо ложаться на то, что может сделать векторное ядро

Эхо будем делать на C. Для простоты сэмплы пойдут на NMC 8-битные, соответственно с включенным переформатированием мы получим их на DSP в виде 32-х битных чисел.
Начнем с того, что возьмем пример проекта hello world и ознакомимся с содержимым:

  • Makefile – основной мейкфайл
  • colorizer.mk – инклюдится в Makefile, тут лежит вся расцветка вывода. Расцветка реализована переносимо для Windows/Linux/Mac, потому выглядит весьма хакерски. Под Windows она использует cecho
  • easyconf.asm – ассемблерный файл с конфигурацией io/args. Содержит всего пару макросов, задающих размер области для argc/argv и размер кольцевых буферов для stdio.
  • K1879.cfg – конфигурационный файл линковщика (о нем ниже)
  • main.c – собственно наш файл с main()

Рассмотрим Makefile. Он более-менее общий и предельно прост. Единственное, что нам требуется поменять – это EASYNMC_DIR, путь к каталогу libeasynmc-nmc где находится NMC часть библиотеки libeasynmc. Если необходимо будет подключить какие-то еще библиотеки, то их надо будет указать в libs.

Makefile
#
# This is a basic Makefile template for a Neuromatrix DSP project 
# to be run on Module MB77.07. To compile it you need: 
# * Latest NMSDK installed with utilities in your $PATH
# * NEURO environment variable pointing to NMSDK directory
# * Host GCC (Since nmcpp doesn't support generating deps, 
#              we use gcc for that) 
#
# For verbose build run 'make VERBOSE=y'
#

-include colorizer.mk
-include *.dep

.SUFFIXES:

OBJECTS :=  \
        main.o \
        easyconf.o

TARGET=helloworld

# Set this to libeasynmc-nmc dir. Relative or absolute. 
# Make sure you build it prior to building the actual project. 

EASYNMC_DIR     = ../../libeasynmc-nmc

CROSS_COMPILE   =
NMCPP_FLAGS     = -DNEURO -OPT2 -inline -I$(EASYNMC_DIR)/include
ASM_FLAGS       = -soc -Sc -Stmp -X-q -I$(EASYNMC_DIR)/include
C2ASM_FLAGS     = -soc -q 

#BIG FAT WARNING: easynmc.lib MUST go BEFORE libc
#BIG FAT WARNING: Otherwise argc/argv won't work 

LIBS            = easynmc.lib libc05.lib

BUILDER_FLAGS   = -cK1879.cfg -m -heap=0 -heap1=0 -heap2=0 -heap3=0 -stack=20000 \
                  -full_names
IDIRS           = -I. -I"$(NEURO)/include" 
LIBDIR          = -l"$(NEURO)/lib" -l"$(EASYNMC_DIR)"

.DEFAULT_GOAL=all

all: $(TARGET).abs

%.asmx: %.cpp
        $(SILENT_DEP)gcc -E -MM $(<) -o$(@).dep
        $(SILENT_NMCPP)$(CROSS_COMPILE)nmcpp -Tp $(NMCPP_FLAGS) $(<) -O$(@) $(IDIRS) 

%.asmx: %.c
        $(SILENT_DEP)gcc -E -MM $(<) -o$(@).dep
        $(SILENT_NMCPP)$(CROSS_COMPILE)nmcpp -Tc99 $(NMCPP_FLAGS) $(<) -O$(@) $(IDIRS) 

%.o: %.asmx
        $(SILENT_ASM)$(CROSS_COMPILE)asm $(C2ASM_FLAGS) $(<) -o$(@)

%.o: %.asm
        $(SILENT_DEP)gcc -E -MM -xassembler-with-cpp $(<) -o$(@).dep
        $(SILENT_ASM)$(CROSS_COMPILE)nmcc $(ASM_FLAGS) $(<) -o$(@)

$(TARGET).lib: $(OBJECTS)
        -$(SILENT_LIBRARIAN)$(CROSS_COMPILE)libr -c $(@) $(^) > /dev/null

$(TARGET).abs: $(OBJECTS)
        -$(SILENT_LINKER)$(CROSS_COMPILE)linker  $(BUILDER_FLAGS) -o$(@) $(^) $(LIBS) $(LIBDIR)

$(TARGET).dump: $(TARGET).abs
        -$(SILENT_NMDUMP)$(CROSS_COMPILE)nmdump -f $(^) > $(@)

run: $(TARGET).abs
        edcltool -f run_nmc_code.edcl -i eth1

clean:
        -$(SILENT_CLEAN)rm -f *.asmx; rm -f *.o; rm -f $(TARGET).abs $(TARGET).dump *.dep \
        *.ac *.map *~ *.abs *.lib


Обращаю внимание на путь к библиотекек easynmc.lib – без нее у нас не заработают stdin/stdout, argc/argv и код возврата, так же в документации указано, что она должна идти перед библиотекой языка C.

Второй по значимости файл – mb7707brd.cfg, для компилятора NeuroMatrix выполняет ту же роль, что для gcc LD скрипт. Здесь можно разложить секции по разным местам в памяти. Значения по умолчанию вполне вменяемы и можно их спокойно использовать:
.cfg
MEMORY
{
        //-------------- NMC ---------------------------------------
        LOADERMEM:              at  0x00000000,         len = 0x00000200; 
        IM1:                    at  0x00000200,         len = 0x0000fe00; 
        IM3:                    at  0x00010000,         len = 0x00010000; 
        //------------- ARM ----------------------------------------
        INTERNAL_MEMORY0:       at      0x00040000,     len = 0x00010000;       // 256K-IM0 ARM         (ARM:0x00100000 0x0013ffff      0x4000(256kB))
        INTERNAL_MEMORY2:       at      0x20040000,     len = 0x00010000;       // 256K-IM2 ARM         (ARM:0x80100000 0x8013ffff      0x4000(256kB))
        //------------- DDR ----------------------------------------
        EXTERNAL_MEMORY0:       at      0x10000000,     len = 0x10000000;       // 16MB-EM0-DDR         (ARM:0x40000000 0x7fffffff) 
        EXTERNAL_MEMORY1:       at      0x30000000,     len = 0x10000000;       // 16MB-EM1-DDR         (ARM:0xc0000000 0xffffffff) 
}


SEGMENTS
{
        code            : in IM3;
        data            : in IM1;
}

SECTIONS
{
        .text                           : in code;
        .init                           : in code;
        .fini                           : in code;
        .data                           : in code;
        .bss                            : in code;
        .stack                          : in code;
        .heap                           : in code;
        .heap1                          : in code;
        .heap2                          : in code;
        .heap3                          : in code;
}


К слову о memory map – её можно взять на http://www.module.ru/mb7707/doc/K1879-memory-map.pdf

После сборки командой make на выходе получаются два файла – hello.abs и hello.dump. abs файл – ELF с абсолютными адресами в адресном пространстве NMC. Его можно загрузить в банки IM1 или IM3, когда работаем из Linux на плате, так как за всю остальную память отвечает ядро Linux, в то время как при работе с edcl правила легче, и мы можем раскладывать секции по всему адресному пространству. dump – это дизассемблированный листинг, который может быть полезен тому, кто знает ассемблер под NMC.

Движемся дальше, к проекту подключается один ассемблерный файл – easyconf.asm. Ассемблер здесь достаточно навороченный, с макросами и прочими радостями жизни. В easyconf лежит настройка stdio и аргументов, что делается буквально двумя макросами:
.asm
import from "easynmc/easynmc.mlb";
/* Declare 2 circular buffers for stdio */
const EASYNMC_IOBUFLEN = 128;
EASYNMC_CBUF(".easynmc_stdin",  _easynmc_stdin_hdr,  _easynmc_stdin_data,  EASYNMC_IOBUFLEN);
EASYNMC_CBUF(".easynmc_stdout", _easynmc_stdout_hdr, _easynmc_stdout_data, EASYNMC_IOBUFLEN);
/* Reserve 128 32-bit words for arguments */
EASYNMC_ARGS(128);


Этот код резервирует место в abs файле для аргументов и кольцевых буферов, можно заметить, что на stdio выдается 128 байт, как и на аргументы. Если nmrun при запуске этой программы передать аргументов больше, чем 128 символов – nmrun выдаст ошибку.

Наконец, рассмотрим main.c, который выглядит довольно просто:
main.c
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <easynmc/easynmc.h>
int main(int argc, char** argv)
{
        printf("Hello world from NeuroMatrix! I am the NMC printf'ing to you!\n");
        return 0; 
} 


Это был стандартный проект hello world, теперь пришло время его модифицировать под нашу задачу. Поступим следующим образом, размер буфера задержки дадим где-то 200мс, с частотой дискретизации это будет около 1600 сэмплов. Округляем до ближайшего значения степени двойки и получаем 2048. Так как в плавающей точке NMC скорее всего не силен (хотя компилятор кушает float’ы), то сделаем все в целых числах, затухание сделаем битовым сдвигом.

Получится что-то в духе этого:
main.c
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <easynmc/easynmc.h>
#define SIZE 2048
static unsigned int delaybuf[SIZE];
static unsigned int pos; 

int main()
{
	while(1) { 
		unsigned int in = getc();
		unsigned int out = in + (delaybuf[pos] >> 2); 
		if (out >255)
			out = 255;
		delaybuf[pos++] = in;
		pos &= (SIZE -1);
		putc(out);
	}
}


Собираем, копируем abs на плату. По умолчанию у платы есть только одно ALSA устройство – HDMI. Мы же хотим вывод/ввод использовать от USB звуковой карты. Необходимо её название в ALSA, делается это достаточно просто, с помощью -L к arecord или aplay. В итоге должно получиться вот так:

arecord -D default:CARD=Device | nmrun echo.abs | aplay -D default:CARD=Device

Подключаем микрофон и наушники и наслаждаемся, только что нами было написано первое приложение для отечественного DSP процессора.

Заключение


Пожалуй, это всё на сегодня. Осталось сделать выводы. MB77.07 оставляет положительное впечатление, если, конечно, закрыть глаза на довольно слабоватый ARM. Перечислим общие плюсы и минусы:

+ Официально открытые исходники всего, что только можно, от загрузчика до IPL кода DSP процессора. На github от производителя чипа. Из других производителей такое существует разве что у Freescale и у Qualcomm (Code Aurora). Обычно все вываливают в лучшем случае в виде GPL source-drop в виде запакованных в rar tar’ов на ftp сервера, разгребать потом это – отдельное удовольствие. Еще, бывает, скидывают все огромной папкой вместе .git индексом.

+ DSP ядро, компилятор и инструментарий для него. Изначально продуман так, что бы новичку было работать очень просто, для старта хватит прочитанного по диагонали K&R. Особенно учитывая, что NMC в такой конфигурации может легко обеспечить жесткий realtime – для робототехников и станочников это просто находка.

+ Драйвера. Нельзя сказать, что идеал, но откровенной жути как у Allwinner (libnand), Rockchip (rk_fb, rknand), Mediatek (вообще всё) и им подобным замеченно не было. Везде стараются использовать стандартные подсистемы ядра. При наличии сообщества или желания разработчиков, большая часть может без проблем попасть в mainline. Проприетарные блобы в ядре отсутствуют.

+ Версия ядра. Сейчас есть 3.10 LTS, осенью разработчики пообщелаи подумать насчет следующего LTS. Для сравнения, в linux-sunxi коммьюнити ядро – 3.4, а в upstream практически нет мультимедиа. Rockchip только недавно выдавил из себя 3.10.x с очень большим скрипом и только для rk3288. Все попытки завести это дело популярном rk3188 провалилсь – не работает MMC контроллер. HardKernel ODROID сидит на 3.8.y linux-stable + ubuntu merge, хотя есть и движение в сторону поддержки в mainline.

+ Неубиваемость. Даже если физически убить NAND или отпаять, MB77.07 можно будет оживить и загрузить по EDCL. По этому, такая плата может оказаться полезной новичкам, желающим узнать, как на голом железе поднять загрузчик, операционную систему и т.д. Можно не бояться убить плату. Коротить ноги NAND в случае косяков как на RK3188 для перепрошивки загрузчика здесь не потребуется никогда.

+ deb-пакеты, репозитории и прочее. Многое сделано не дожидаясь появления коммьюнити, многое еще предстоит сделать.

+ Интересно разведено, все разъемы на одной стороне, намного удобнее чем на r-pi.

— Цена. Этот факт, конечно, малину портит (pun intended)

— Нет RTC. Для многих проектов может оказаться неважно, но может потребоваться. Плюс в системе первым приходится поднимать ntp.

— Производительность. Да, 324MHz ARM1176 это не особенно весело. Отчасти компенсируется тем, что как ARM, так и NMC могут выполнять свой код из накристальной памяти, откуда работают просто реактивно, но тем не менее. Будем надеятся что дальше будет интереснее и Модуль не бросит OpenSource и мы увидим следующие их проекты, уже на более производительных решениях.

— Сложность заказа. Тут уже чисто вопрос удобства и скорее вопрос к маркетингу, чем к инженерам. На сайте Модуля нехватает огромной жирной кнопки «купить» в полэкрана и моментальной оплаты через qiwi.

Ссылки


Tags:
Hubs:
+77
Comments54

Articles

Change theme settings