Как стать автором
Обновить

Black Swift: использование EJTAG

Время на прочтение13 мин
Количество просмотров14K

Моя предыдущая публикация EJTAG: аттракцион для хакеров хотя и была тепло встречена общественностью, имела некоторые недочёты: к примеру, была продемонстрирована уж слишком низкая производительность передачи по EJTAG (аж целых 2 КБ/с!).
К сожалению, я умудрился привести интерфейс JTAG платы MR3020 в полную негодность (был оторван провод TDI вместе с кусочком SMD-резистора R16). Так как устранить поломку не удалось, то недочёты остались неисправленными.
Несколько дней назад я получил от руководителя проекта Black Swift Дмитрия Жеребкова плату Black Swift Pro с адаптером. Плата Black Swift Pro во многом аналогична MR3020, а значит у меня появилась возможность написать публикацию про EJTAG на Black Swift и устранить прошлые недочёты!

Предупреждение: ниже излагается методика работы с EJTAG для платы Black Swift при помощи свободного ПО; предполагается, что читатель не впадает в ступор от интерфейса командной строки, знает кто такие AR9331, MR3020 и EJTAG, в курсе где брать кросс-компилятор для MIPS и догадывается зачем используют minicom. При возникновении какого-либо вопросов по использованию EJTAG на плате Black Swift автор рекомендует сначала поискать ответ в публикации EJTAG: аттракцион для хакеров.


Black Swift: отличие от TP-Link MR3020


Black Swift в отличии от TP-Link MR3020 не является законченным изделием, предназначенным для решения фиксированной задачи.
Как раз наоборот: плата Black Swift предназначена для того, чтобы пользователи могли легко создавать на её базе собственные изделия. По этой причине плата Black Swift имеет гораздо меньше ограничений (как программных, так и аппаратных) для проведения экспериментов.
Размеры Black Swift весьма невелики, на плате используются разъёмы, по своей природе аналогичные разъёмам на Arduino-подобных платах, но имеющие в два раза меньшее расстояние между соседними соединителями — 1,27 мм. По причине такой миниатюрности для упрощения макетирования с такой платой предлагается использовать специальный адаптер, который позволяет использовать привычные «большие» провода от Arduino:

SD-карта на фотографии приведена исключительно для масштаба.


В ПЗУ Black Swift прошит штатный для этой платы загрузчик на базе u-boot_mod.
При старте загрузчик выдаёт следующее сообщение в UART (настройки UART: 115200 8n1):
*********************************************
*        U-Boot 1.1.4  (Apr  1 2015)        *
*********************************************

U-Boot for Black Swift board (AR9331)

DRAM:   64 MB DDR2 16-bit
FLASH:  Winbond W25Q128 (16 MB)
CLOCKS: 400/400/200/33 MHz (CPU/RAM/AHB/SPI)

Если в течении 1 секунды пользователь не отправляет в UART никакого символа, то производится загрузка ОС на базе OpenWrt. Если же пользователь успеет отправить загрузчику какой-нибудь символ, то становится доступной командная строка загрузчика. Из этой строки возможно дать команду загрузить данные в ОЗУ (через UART по Y-Modem или через Ethernet по tftp) и передать управление на любой адрес; даже есть команды для перепрограммирования ПЗУ.

На первый взгляд Black Swift для разработчика встроенного программного обеспечения имеет массу преимуществ перед MR3020:
  • подключаться к практически любым интерфейсам AR9331 (в том числе и JTAG) стало гораздо проще;
  • загрузчик довольно дружелюбен (в MR3020 возможности по загрузке кодов пользователя в ОЗУ были сильно ограничены);
  • если ничего не трогать, вход GPIO11/JS AR9331 на Black Swift оказывается подключённым к VDD25, что соответствует разрешению использовать EJTAG (напомню, что на MR3020 для этого надо было специально жать кнопку SW1);
  • в версии Black Swift Pro появилась возможность подключить загрузочное ПЗУ к программатору (см. ниже об особенностях AR9331).

Но, как говорится, поверьте, у нас тут не всё так однозначно...

Ничто не предвещало беды


Оказалось, что штатная прошивка на базе u-boot_mod, хотя и позволяет загружать в ОЗУ код и исполнять его, но, как и штатная прошивка MR3020, враждебно настроена по отношению к любителям EJTAG и норовит программно отключить EJTAG вскоре после старта.
Использовать опыт MR3020, связанный с отключением загрузочного ПЗУ SPI Flash за счёт разрыва цепи CS, довольно затруднительно
несмотря на то, что на Black Swift Pro есть микропереключатели, обеспечивающие отключение загрузочного ПЗУ от AR9331 (что по задумке авторов позволяет использовать внешний программатор). Доступ к этим микропереключателям на плате, установленной на стандартный адаптер, мягко говоря затруднён. Для того, чтобы аккуратно перевести переключатели в нужное положение плату придётся снимать с адаптера. На плате Black Swift (не Pro!) и вовсе нет никаких микропереключателей. Теоретически возможно припаять пару проводов к микропереключателю или микросхеме ПЗУ для замыкания/размыкания цепи CS снаружи, но такая доработка требует умения хорошо паять (думаю фото ниже даёт представление о сложности доработки).

Зачем вообще понадобились микропереключатели на Black Swift Pro?
Дело в том, что попытки перепрограммировать (или даже просто прочитать) микросхему загрузочного ПЗУ SPI Flash без отсоединения от AR9331 обречены на неудачу − AR9331 упорно драйверит линии SPI даже при активной линии RESET. На Black Swift Pro четыре микропереключателя позволяют решить эту проблему, путём полного отключения загрузочного ПЗУ от AR9331, что и позволяет успешно использовать внешний программатор.

Таким образом, использовать методику оживления JTAG, успешно работающую на MR3020, будет затруднительно.
Мы пойдём другим путём.

«Грязный хак позволит поюзать JTAG»


Итак проблема состоит в том, что загрузчик u-boot_mod отключает JTAG.
Давайте посмотрим, что делает u-boot_mod при старте (напомню, что после снятия сигнала сброс любой процессор с архитектурой MIPS начинает исполнять инструкции с адреса 0xbfc00000):
    bfc00000:   100000ff        b       0xbfc00400
    bfc00004:   00000000        nop

Если заменить команду ветвления на адрес 0xbfc00400 на команду ветвления на адрес 0xbfc00000, вот так:
    bfc00000:   1000ffff        b       0xbfc00000
    bfc00004:   00000000        nop

то u-boot_mod не сможет сделать своё чёрное дело по отключению JTAG.
Конечно предложенное решение уж очень топорно...
и гораздо лучше было бы исправить штатную прошивку так, чтобы доступ к JTAG оставался всегда (или хотя бы при определённом условии), но, к сожаление, на момент подготовки данной публикации исходные тексты штатной прошивки недоступны (см. форум black-swift.ru), а вносить нетривиальные исправления в бинарную прошивку совершенно не хочется.

Замену можно сделать при помощи внешнего программатора, но чтобы не делать лишних движений лучше использовать barebox.

Предупреждение! Описанные ниже действия могут привести к полному окирпичиванию вашей платы Black Swift.

Сборка barebox


Для модификации уже прошитого в загрузочное ПЗУ загрузчика u-boot_mod мы используем barebox из официального git-репозитория barebox.org. Скачиваем исходные тексты из ветки next:
 $ git clone -b next git://git.pengutronix.de/git/barebox.git

Почему надо скачивать именно ветку next из git-репозитория?
Ветку next мы тащим ради единственного тривиального изменения, которое научит barebox работать с микросхемой ПЗУ, установленной в Black Swift:
commit bd3e5011346e3d4d03ac076ada5768c2cf197dc4
Author: Antony Pavlov <antonynpavlov@gmail.com>
Date:   Mon Apr 13 23:56:41 2015 +0300

    mtd: m25p80: import ID for Winbond W25Q128FVSSI from linux

    Winbond W25Q128FVSSI chip is used in Black Swift board,
    (see http://www.black-swift.com for details).

На момент публикации данное изменение ещё не попало в стабильную версию barebox.

Переходим в каталог с исходными текстами, производим конфигурацию и сборку:
 $ cd barebox
 barebox$ export ARCH=mips
 barebox$ export CROSS_COMPILE=/opt/mips-2014.11/bin/mips-linux-gnu-
 barebox$ make tplink-mr3020_defconfig
 barebox$ sed "s/# CONFIG_PARTITION is not set/CONFIG_PARTITION=y/" -i .config
 barebox$ sed "s/# CONFIG_CMD_PARTITION is not set/CONFIG_CMD_PARTITION=y/" -i .config
 barebox$ make

Если сборка прошла успешно, то получим файл barebox.bin.
Остаётся загрузить этот файл в ОЗУ Black Swift и стартовать.

Подключение макетки FT2232H к Black Swift


Для подключения к интерфейсам UART и JTAG микросхемы AR9331 будем использовать макетку FT2232H (см. также FT2232 breakout board).
Хотя плата Black Swift Pro имеет собственный преобразователь USB-UART, использовать для подключения UART макетку FT2232H по ряду причин удобнее:
  • в этом случае для подключения используется только один кабель USB;
  • запитывая Black Swift от макетки (хотя её нагрузочная способность ограничена) и желая на некоторое время отключить питание от Black Swift мы отключаем линию питания от макетки; так мы избавлены от проблемы с перенумерацией последовательных портов USB (ttyUSBxx) в linux, которая может возникнуть, если мы временно отключим Black Swift Pro;
  • уменьшаяется вероятность повредить Black Swift вставляя/вынимая разъём microUSB;
  • используя макетку не надо задумываться, с каким вариантом Black Swift работаешь (Pro или не Pro) (всё равно макетка нужна для подключения по JTAG).

Вот схема подключения:

Внешний вид Black Swift с подключённой макеткой FT2232H:


Модификация u-boot_mod


После того, как макетка FT2232H подключена к Black Swift можно приступить к модификации u-boot_mod. Для этого запускаем minicom.
Далее перезапускаем Black Swift (сделать это можно либо временно оторвав питание от платы, либо замкнув вход RESET на «землю» (0V)).
Как только в minicom заметим сообщение
Hit any key to stop autobooting

нужно прервать загрузку, нажав в minicom любую клавишу, после чего должно появиться приглашение загрузчика BSB>.
Теперь загружаем в ОЗУ barebox.bin по протоколу Y-Modem (для использования Y-Modem в Debian помимо minicom должен быть установлен пакет lrzsz).
Для этого в командной строке загрузчика запускаем приём данных по Y-Modem:
BSB> loady 0xa0200000

В minicom'е запускаем отправку. Если minicom настроен так, как в публикации EJTAG: аттракцион для хакеров то для отправки файла понадобиться нажать Ctrl-B S, в меню выбрать ymodem, а затем либо выбрать файл при помощи меню, либо нажать Enter и ввести путь к файлу barebox.bin вручную.
После успешного окончания загрузки остаётся только передать управление на адрес загрузки barebox.bin:
BSB> go 0xa0200000
## Starting application at 0xA0200000...


barebox 2015.04.0-00103-g8397d68 #1 Fri Apr 17 08:59:12 MSK 2015


Board: TP-LINK MR3020
m25p80 m25p80@00: w25q128 (16384 Kbytes)
malloc space: 0xa0400000 -> 0xa07fffff (size 4 MiB)
environment load /dev/env0: No such file or directory
Maybe you have to create the partition.
running /env/bin/init...
/env/bin/init not found
barebox:/


Строка
m25p80 m25p80@00: w25q128 (16384 Kbytes)

из выдачи выше и говорит нам, что barebox нашёл загрузочное ПЗУ w25q128.
Для доступа к загрузочному ПЗУ barebox создаёт устройство /dev/spiflash. Для того, чтобы работать с ПЗУ было удобнее, а также чтобы уменьшить вероятность возникновения ошибки, разделим ПЗУ на разделы. Для этого обратимся к описанию структуры флэш-памяти
Используем команду addpart для выделения разделов:
barebox:/ addpart /dev/spiflash 128K@0(u-boot)
barebox:/ addpart /dev/spiflash 64K@128K(u-boot-env)
barebox:/ addpart /dev/spiflash 16128K@192K(open-wrt)
barebox:/ addpart /dev/spiflash 64K@16320K(art)

Хотя нас интересует только раздел с u-boot, из чистого любопытства проверим, что находится в первых 64 байтах каждого раздела:
barebox:/ md -s /dev/spiflash.u-boot 0+64
00000000: 100000ff 00000000 100000fd 00000000                ................
00000010: 1000018e 00000000 1000018c 00000000                ................
00000020: 1000018a 00000000 10000188 00000000                ................
00000030: 10000186 00000000 10000184 00000000                ................

barebox:/ md -s /dev/spiflash.u-boot-env 0+64
00000000: 071043c4 626f6f74 636d643d 626f6f74                ..C.bootcmd=boot
00000010: 6d203078 39463033 30303030 00626f6f                m 0x9F030000.boo
00000020: 7464656c 61793d31 00626175 64726174                tdelay=1.baudrat
00000030: 653d3131 35323030 00697061 6464723d                e=115200.ipaddr=

barebox:/ md -s /dev/spiflash.open-wrt 0+64
00000000: 27051956 acb82611 551bcb1f 001503ef                '..V....U.......
00000010: 80060000 80060000 bc6b30a9 05050203                .........k0.....
00000020: 4d495053 204f7065 6e577274 204c696e                MIPS OpenWrt Lin
00000030: 75782d33 2e31302e 34390000 00000000                ux-3.10.49......

barebox:/ md -s /dev/spiflash.art 0+64
00000000: 807b8591 2010ffff ffffffff ffffffff                .{.. ...........
00000010: ffffffff ffffffff ffffffff ffffffff                ................
00000020: ffffffff ffffffff ffffffff ffffffff                ................
00000030: ffffffff ffffffff ffffffff ffffffff                ................

Эта маленькая проверка показывает, что ошибки в определении смещений разделов нет − в spiflash.u-boot сидит начало u-boot_mod, в spiflash.u-boot-env действительно находятся переменные u-boot_mod, а в разделе spiflash.open-wrt заголовок образа Linux OpenWrt.
В разделе art хранится базовый MAC-адрес платы, который используется для назначения MAC-адресов всем трём сетевым интерфейсам (одному WiFi и двум Ethernet).
Вот, к примеру, что сообщает linux про MAC-адреса этой же платы
root@BlackSwift:/dev# ifconfig -a | grep HWaddr
br-lan    Link encap:Ethernet  HWaddr 80:7B:85:91:20:12
eth0      Link encap:Ethernet  HWaddr 80:7B:85:91:20:11
eth1      Link encap:Ethernet  HWaddr 80:7B:85:91:20:12
wlan0     Link encap:Ethernet  HWaddr 80:7B:85:91:20:10

Теперь зачитаем образ u-boot_mod из ПЗУ в ОЗУ:
barebox:/ memcpy -s /dev/spiflash.u-boot 0 0xa0100000

В barebox эта операция чтения bit-bang'ом занимает 49 секунд.
А как с чтением из ПЗУ в linux?
в linux основной прошивки с доступом к флешу всё гораздо лучше (производительность чтения более 2 МБ/с):
root@BlackSwift:/dev# time dd if=mtdblock2 of=/tmp/test bs=1M
15+1 records in
15+1 records out
real    0m 7.07s
user    0m 0.00s
sys     0m 0.48s

Модифицируем образ u-boot_mod в ОЗУ и запишем его назад в ПЗУ.
barebox:/ mw 0xa0100000 0x1000ffff
barebox:/ erase /dev/spiflash.u-boot
barebox:/ memcpy -d /dev/spiflash.u-boot 0xa0100000 0 0x20000

Обновлённое содержимое ПЗУ:
barebox:/ md -s /dev/spiflash.u-boot 0+64
00000000: 1000ffff 00000000 100000fd 00000000                ................
00000010: 1000018e 00000000 1000018c 00000000                ................
00000020: 1000018a 00000000 10000188 00000000                ................
00000030: 10000186 00000000 10000184 00000000                ................


Внимание, проверьте внимательно содержимое /dev/spiflash.u-boot ещё раз пока не поздно!

Теперь Black Swift можно перезапустить. Из barebox перезапуск можно осуществить при помощи команды reset:
barebox:/ reset

Теперь плата не может работать самостоятельно, зато появляется возможность использовать EJTAG. При необходимости, использую EJTAG, плату легко привести в исходное состояние.

Использование EJTAG


К сожалению, тупо воспользоваться скриптами openocd для MR3020 для Black Swift не получится: MR3020 использует микросхему оперативной памяти DDR1 в то время как Black Swift использует микросхему DDR2, а значит используется другая процедура инициализации контроллера ОЗУ.
Отмечу проблемы публикации EJTAG: аттракцион для хакеров в части openocd:
  • по мнению Павла Ферцера (а уж он-то знает толк в openocd!), публикация пропагандировала порочную практику работы с openocd;
  • была продемонстрирована уж слишком низкая производительность передачи по EJTAG (аж целых 2 КБ/с!).

В этот раз я постараюсь избежать старых ошибок. В составе openocd уже имеется конфигурационный файл для TIAO USB Multi-Protocol Adapter (TUMPA), который можно использовать для FT2232 Board, поэтому вместо полного описания просто импортируем конфигурационный файл для TUMPA (interface/ftdi/tumpa.cfg).
Для описания подключения JTAG к микросхеме AR9331 Алексеем Ремпелем уже создан конфигурационный файл atheros_ar9331.cfg, так что его также можно импортировать. К сожалению, этот файл добавлен в репозиторий openocd совсем недавно (см. вот этот commit) и не входит в состав пакета openocd для Debian.
Придётся скачать его отдельно:
$ wget -O atheros_ar9331.cfg "http://openocd.zylin.com/gitweb?p=openocd.git;a=blob_plain;f=tcl/target/atheros_ar9331.cfg;hb=7e66b02ba4f1453ab1c45eaebbeee6eaa0cfb436"

В соответствии с традицией openocd в сотдельном файле с именем black-swift.cfg сохраним описание платы. Это описание использует atheros_ar9331.cfg и содержит процедуру инициализации DDR2 (файл создан по мотивам tcl/board/tp-link_tl-mr3020.cfg):
source [find atheros_ar9331.cfg]

# ar9331_25mhz_pll_init is imported from tcl/board/tp-link_tl-mr3020.cfg
proc ar9331_25mhz_pll_init {} {
        mww 0xb8050008 0x00018004        ;# bypass PLL; AHB_POST_DIV - ratio 4
        mww 0xb8050004 0x00000352        ;# 34000(ns)/40ns(25MHz) = 0x352 (850)
        mww 0xb8050000 0x40818000        ;# Power down control for CPU PLL
                                         ;# OUTDIV | REFDIV | DIV_INT
        mww 0xb8050010 0x001003e8        ;# CPU PLL Dither FRAC Register
                                         ;# (disabled?)
        mww 0xb8050000 0x00818000        ;# Power on | OUTDIV | REFDIV | DIV_INT
        mww 0xb8050008 0x00008000        ;# remove bypass;
                                         ;# AHB_POST_DIV - ratio 2
}

proc ar9331_ddr2_init {} {
        mww 0xb8000000 0x7fbc8cd0        ;# DDR_CONFIG - lots of DRAM confs
        mww 0xb8000004 0x9dd0e6a8        ;# DDR_CONFIG2 - more DRAM confs

        mww 0xb800008c 0x00000a59
        mww 0xb8000010 0x00000008
        mww 0xb8000090 0x00000000
        mww 0xb8000010 0x00000010
        mww 0xb8000094 0x00000000
        mww 0xb8000010 0x00000020
        mww 0xb800000c 0x00000000
        mww 0xb8000010 0x00000002
        mww 0xb8000008 0x00000100
        mww 0xb8000010 0x00000001
        mww 0xb8000010 0x00000008
        mww 0xb8000010 0x00000004
        mww 0xb8000010 0x00000004
        mww 0xb8000008 0x00000a33
        mww 0xb8000010 0x00000001
        mww 0xb800000c 0x00000382
        mww 0xb8000010 0x00000002
        mww 0xb800000c 0x00000402
        mww 0xb8000010 0x00000002
        mww 0xb8000014 0x00004186
        mww 0xb800001c 0x00000008
        mww 0xb8000020 0x00000009
        mww 0xb8000018 0x000000ff
}

$TARGETNAME configure -event reset-init {
        ar9331_25mhz_pll_init
        sleep 1
        ar9331_ddr2_init
}

set ram_boot_address 0xa0000000
$TARGETNAME configure -work-area-phys 0xa1ffe000 -work-area-size 0x1000

У читателя может возникнуть вопрос ...
как была получена последовательность записей в регистры, обеспечивающая инициализацию контроллера памяти DDR2? Такая последовательность получается при помощи EJTAG! Достаточно протрассировать исполнение u-boot_mod: инициализация контроллера памяти производится на самом раннем этапе, выбрав из трассы инструкции записи (store), получим искомую последовательность.

Конфигурационный файл run-barebox.cfg, который собирает всё воедино и обеспечивает загрузку и запуск barebox.bin выглядит так:
source [find interface/ftdi/tumpa.cfg]

adapter_khz 6000

source [find black-swift.cfg]

init
halt

reset init

load_image barebox/barebox.bin 0xa0100000 bin

# General Purpose I/O Function (GPIO_FUNCTION_1)
#
#   SPI_EN  (18) enables SPI SPA Interface signals
#                in GPIO_2, GPIO_3, GPIO_4 and GPIO_5.
#   RES     (15) reserved. This bit must be written with 1.
#   UART_EN  (2) enables UART I/O on GPIO_9 (SIN) and GPIO_10 (SOUT).
#
mww 0xb8040028 0x48002

resume 0xa0100000
shutdown

Итак, после того, как у нас в рабочем каталоге появились файлы atheros_ar9331.cfg, black-swift.cfg, run-barebox.cfg мы можем загрузить и запустить barebox:
$ sudo openocd -f run-barebox.cfg
Open On-Chip Debugger 0.8.0 (2014-10-20-21:48)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.sourceforge.net/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'jtag'
none separate
adapter speed: 6000 kHz
Error: no device found
Error: unable to open ftdi device with vid 0403, pid 8a98, description '*' and serial '*'
Info : clock speed 6000 kHz
Info : JTAG tap: ar9331.cpu tap/device found: 0x00000001 (mfg: 0x000, part: 0x0000, ver: 0x0)
target state: halted
target halted in MIPS32 mode due to debug-request, pc: 0xa080078c
189216 bytes written at address 0xa0100000
downloaded 189216 bytes in 0.459115s (402.473 KiB/s)
shutdown command invoked

После окончания работы openocd в окне minicom мы можем наблюдать сообщения о старте barebox.
Замечание: в моём частном случае поднять частоту тактового сигнала JTAG выше 6 МГц не представляется возможным, так как при этом наблюдается порча передаваемых данных.


Как вернуться к оригинальной прошивке?


Для одноразового запуска оригинальной прошивки годится вот такой скрипт run-u-boot_mod.cfg:
source [find interface/ftdi/tumpa.cfg]

adapter_khz 6000

source [find black-swift.cfg]

init
halt
resume 0xbfc00400
shutdown

Замечание: скрипт run-u-boot_mod.cfg не сработает непосредственно после подачи питания на плату, так как u-boot_mod для борьбы с таинственными ошибками AR9331 производит дополнительный сброс. Для того, чтобы скрипт сработал, перед его запуском на плату надо подать сигнал сброс. Другой вариант: запустить скрипт run-u-boot_mod.cfg дважды.


Для перманентного восстановления оригинальной прошивки достаточно выполнить описанную выше процедуру по модификации первой инструкции ветвления загрузчика u-boot_mod с той лишь разницей, что вместо замены 0x100000ff на 0x1000ffff, надо заменить 0x1000ffff обратно на 0x100000ff.

Итоги


Как было продемонстрировано выше, работать по EJTAG с Black Swift вполне возможно. Пропускная способность в 400 КБ/с позволяет комфортно грузить не только barebox, но и ядро linux; это тем более актуально, что при использовании штатного простенького адаптера интерфейс Ethernet остаётся не выведенным.

Благодарности


Автор выражает благодарность людям, без участия которых данная публикация едва ли была бы возможна:
  • Павлу Ферцеру за ценные замечания и предложения;
  • Алексею Ремпелю за добавление поддержки платы TP-Link MR3020 в openocd;
  • Дмитрию Жеребкову за предоставленную плату Black Swift Pro.
Теги:
Хабы:
Всего голосов 25: ↑25 и ↓0+25
Комментарии14

Публикации

Истории

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань