跳转至

博客

每周分享第 25 期

  1. 关掉各种 Intel BUG 的 mitigation https://t.me/one_real_world/1517
  2. GCC 10 支持用 MMX 模拟 SSE http://www.phoronix.com/scan.php?page=news_item&px=GCC-10-Emulating-MMX-With-SSE
  3. Minecraft Earth 发布 https://www.minecraft.net/en-us/article/new-game--minecraft-earth#
  4. Python 官方 format 工具 https://github.com/python/black
  5. JS Binary AST Proposal https://github.com/tc39/proposal-binary-ast
  6. 用 Rust 实现的 ld https://github.com/aep/elfkit
  7. Verilog -> Minecraft https://github.com/itsFrank/MinecraftHDL
  8. Rust 实现的 光栅化输出 https://github.com/ecumene/rust-sloth/
  9. Nokia 的 Rust 内存 profiler https://github.com/nokia/memory-profiler
  10. Linux 5.2 更新的 Logitech Wireless Device 驱动 正好能用上 http://www.phoronix.com/scan.php?page=news_item&px=Better-Logitech-Linux-5.2

Nginx 反代到 HTTPS 上游

这次遇到一个需求,要反代到不在内网的地址,为了保证安全,还是得上 HTTPS,所以尝试了一下怎么给 upstream 配置自签名 HTTPS 证书的验证。

upstream subpath {
    server 4.3.2.1:4321;
}

server {
    listen 443 ssl;
    server_name test.example.com;

    location /abc {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_ssl_trusted_certificate /path/to/self_signed_cert.crt;
        proxy_ssl_name 1.2.3.4; // to override server name checking
        proxy_ssl_verify on;
        proxy_ssl_depth 2;
        proxy_ssl_reuse on;
        proxy_pass https://subpath;
    }
}

可以用 openssl 获得自签名的 cert :

echo | openssl s_client -showcerts -connect 4.3.2.1:4321 2>/dev/null | \
                              openssl x509 -text > /path/to/self_signed_cert.crt

ref: https://stackoverflow.com/questions/7885785/using-openssl-to-get-the-certificate-from-a-server

每周分享第 24 期

  1. 在线波形绘制 https://wavedrom.com/editor.html
  2. Python 波形绘制 https://github.com/wallento/wavedrompy
  3. GDB 8.3 发布 http://www.phoronix.com/scan.php?page=news_item&px=GDB-8.3-Debugger-Released
  4. 命令行自动搜索 StackOverflow https://github.com/WindSoilder/hors
  5. 从 C 到 Rust 的翻译 https://github.com/immunant/c2rust
  6. SHA-1 碰撞攻击新进展 https://www.zdnet.com/article/sha-1-collision-attacks-are-now-actually-practical-and-a-looming-danger/
  7. macOS 配置 AD 管理员组 https://derflounder.wordpress.com/2011/02/17/adding-groups-from-your-directory-service-to-your-macs-admin-group/
  8. ssh 后自动安装并打开 code-server 同步配置 https://github.com/cdr/sshcode
  9. HTTP Sunset 头 https://tools.ietf.org/html/rfc8594

在 FPGA 上实现路由器(2)

前言

月初的时候,有了一个完整可用的路由器(上一篇系列博文),但当时测了一下速度,只有几十 Mb/s,只要往上提就会失效,得 reset 才能继续。当时也先没管性能的事情,先把和 OS 交互的部分做了。现在又回头来做性能调优。

之前,逻辑部分的主频只有 10 MHz,这自然不行,不提高肯定做不到千兆。于是试着把主频拉高,FIFO 加大,然后遇到了很多问题,慢慢修复了,学到了很多新知识,目前也接近千兆的水平了吧,贴图:

TCP 测速:

UDP 测速:

测试环境是 macOS 虚拟机外打虚拟机内,走网桥把虚拟机和一个 USB 网卡接起来,然后从另一个 USB 网卡打到路由器。

尝试 700Mb/s

接下来讲讲,在这个过程中遇到了什么问题,怎么解决的。第一个是速度过快就会挂,这肯定是丢包逻辑没写对,后来在仿真里开够了时间,于是就找到了一个 BUG,其实就是一行的修复。接着就是提高主频,但大家也知道,CPU 不能随便超频,由于各种延迟的原因,比如 Setup 时间,如果超了一个时钟周期的时间,本来应该下个周期就得到新数据的,结果到了下下周期才有,那有的状态可能就乱了,我目前遇到的也主要就是这个问题。

于是就对着 Timing 里汇报的各种问题修啊修,发现了很多以前没有注意到的问题,它们不影响功能,但是会让逻辑变慢。第一个问题是 High Fanout,以上就是说一个输出接到了很多输入,这看起来没啥问题,但数设课上也讲过,每个门的输入输出电流是有限制的,例如按书上的数据,一个门输出只能带十个门,更多只能级联一层。级联的话,延迟自然就高了。后来发现,这里的原因是,开了一个大的数组,但是没有变成 RAM,综合出了几千个逻辑单元,自然是出问题。解决方法很简单,用 xpm_memory_tpdram 即可。这样一搞,主频就能上 200MHz 了。

这个时候测了一下,发现 UDP 能打到 700Mb/s 了,TCP 由于丢包率比较高,只有 400Mb/s,距离预期还有一段距离。于是继续进行优化。

向 900Mb/s 进发

要继续提速,自然要提高主频。下一个主频目标就是 250MHz。随着提高主频,时序的要求也会更高,自然也出现了新的问题。

这次的问题主要在于,一个路径上逻辑门数过多,多的有 7 到 10 个,每一步零点几到一点几纳秒,叠起来 4 纳秒哪里够用。于是把一些不需要依赖条件的逻辑挪到条件外面,这样就减少了一些路径的依赖。

解决了这个以后,现在的 WNS(Worst Negative Slack)只剩下 0.6 ns 了。这时候的问题一部分还是来自于逻辑门过多,但这个时候就没这么简单了,只能继续细化流水线,打一拍,这样才能把延迟降下来。

虽然 Timing 没有完全解决,但还是写进了 FPGA 中。幸好工作一切正常,就得到了上面那个图片的结果,接近千兆的速度了。

后续优化途径

后续优化途径的话,一方面是继续对逻辑进行细化和拆分,另一方面是对架构进行优化。当初设计的时候,可能没有考虑地那么周全,例如现在发现,其实 ARP Cache 可以放到每个网口一份,这样会降低花在仲裁上的时间。另外,路由表其实也可以分多份存,只要改的时候同一改就可以了。这样应该会更快。

每周分享第 23 期

  1. VS Code Remote https://code.visualstudio.com/blogs/2019/05/02/remote-development
  2. Rust 命令行看图片 https://github.com/atanunq/viu
  3. GCC 9.1 发布 https://readhacker.news/s/42Ruk
  4. Rust 的 Unikernel https://github.com/hermitcore/libhermit-rs
  5. Rust 的 QRCode 库 https://www.reddit.com/r/rust/comments/bk7z2x/announcing_bardecoder_a_qr_detector_and_decoder/
  6. 鲁迅说过搜索引擎 http://cx.luxunmuseum.com.cn/
  7. JIT 的 golang REPL https://github.com/gijit/gi
  8. Github 私有 Package Repo https://github.com/features/package-registry

给 Rocket Chip 挂接串口外设

前言

最近在给 rCore 添加 Rocket Chip 支持。下面讲讲最近做了哪些工作,遇到了哪些坑,都是怎么解决的。

踩坑过程

Rocket Chip 运行代码

首先分析了一下已有的代码和工作方式,这个 Rocket Chip(ucb-bar/fpga-zynq)的设计大概是这样的:在 PS 上通过 fesvr 向 Rocket Chip 写入程序。Rocket Chip 本身暴露出一个 TSI,一个串口的调试接口,通过 Zynq Adapter 挂到了 PS 下的 AXI 总线,暴露出若干个寄存器,大概如下:

  /**
   * Address Map
   * 0x00 - serial out FIFO data
   * 0x04 - serial out FIFO data available (words)
   * 0x08 - serial in  FIFO data
   * 0x0C - serial in  FIFO space available (words)
   * 0x10 - system reset
   * 0x20 - req FIFO data
   * 0x24 - req FIFO data available (words)
   * 0x28 - data FIFO data
   * 0x2C - data FIFO data available (words)
   * 0x30 - resp FIFO data
   * 0x34 - resp FIFO space available (words)
   * 0x38 - nsectors
   * 0x3C - max request length
   */

前面的是调试接口,后面的是 block device 和 network,我们暂时还没有用到这些 UCB BAR 做的私货。在 Vivado 中,地址 Offset 是 0x43C00000,所以代码中就这样访问对应的物理地址:

#define ZYNQ_BASE_PADDR 0x43C00000L

fd = open("/dev/mem", O_RDWR|O_SYNC);
assert(fd != -1);
dev = (uint8_t *) mmap(
  0, sysconf(_SC_PAGESIZE),
  PROT_READ|PROT_WRITE, MAP_SHARED, fd, ZYNQ_BASE_PADDR);
assert(dev != MAP_FAILED);

这块地址在 Device Tree 里也有对应的项,于是 PS 在访问的时候就会找到总线上的 Rocket Chip 的 Slave,也就对应到了上面的那个寄存器的 Map。接着就是由 fesvr 向 Rocket Chip 里写程序,然后跑起来了。

OpenSBI 输出

接着就需要先把输入输出做起来,需要移植一个 bootloader。相比之下,OpenSBI 明显比 bbl 更适合于这个用途,于是拿着 OpenSBI 就拿来改了。考虑到 fesvr 采用的是 htif 进行输入输出,于是从 bbl 里抄了相关代码过来,得到了正确的输出:

rCore,启动!

接下来,就想着怎么把 rCore 丢进去跑。把 payload 替换掉,丢进去,gg 了。一直出现一个玄学的问题,只要我修改页表,就出现 instruction access fault。也一直没有找到真实的原因,最后把 PMP 保护关了就好了。。怀疑是 Rocket Chip 实现有误。

又做了一些小的修改和适配,用户态也可以正常跑起来了。但是,现在串口只能轮询读取,而 htif 是通过读写内存进行的,也没有类似 MSI 的机制(现在想了想,其实可以,给 PS 挂一个 AXI Interrupt Controller,采用软件产生中断模式,然后接到 Rocket Chip 上,其实也是可以的),另外 Rocket Chip 原来的 Config 还没有向外暴露中断,我想挂一个串口也得让 Rocket Chip 访问得到。于是就开始了阅读 Chisel 代码然后魔改的过程了。

魔改 Rocket Chip

其实算不上魔改,克服了对 Rocket Chip 的恐惧,仔细阅读代码以后,发现还是比较容易理解的。譬如,我要添加一个外部总线,只需要添加一句:

  case ExtBus => up(ExtBus, site).copy(idBits = 6)

那么对应地,就多了一片地址映射:

60000000 - 80000000  RWX  mmio-port-axi4@60000000

意味着,只要我在 Rocket Chip 里访问这片地址,那么就会访问这个 AXI 总线上的外设了。但是事情没有这么简单,在踩了很久的坑以后才最终解决。。

AXI 总线地址的计算方式

首先谈谈 AXI 总线上地址是怎么计算的。AXI 总线是一个星形结构,一个 Master 多个 Slave,在这里出现的例子是:

  1. PS 是 Master,Rocket Chip 是 Slave — 刚才谈到过的那片 register space
  2. Rocket Chip 是 Master,PS 是 Slave — 让 Rocket Chip 访问 DDR 控制器
  3. Rocket Chip 是 Master,外设 是 Slave — 这就是现在要做的事情

之前省略没说的是上面的第二点,就是让 Rocket Chip 也可以拿到内存用。那问题来了,怎么让 Rocket Chip 和 ARM 上的 Linux 不要打架?把地址空间分成两块就好了:

wire [31:0] mem_araddr;
wire [31:0] mem_awaddr;

// Memory given to Rocket is the upper 256 MB of the 512 MB DRAM
assign S_AXI_araddr = {4'd1, mem_araddr[27:0]};
assign S_AXI_awaddr = {4'd1, mem_awaddr[27:0]};

这样做,就透明地把 Rocket Chip 看到的内存空间都映射到了实际内存的 [256MB, 512MB) 这片空间上了。

注:我实验用的板子实际上有 1GB 的 DRAM,但实际上已经足够用了,所以就没有改原来 zedboard 的配置。

我在前面也提到,在 PS 上访问 0x43C00000 就是对应了 Zynq Adapter 的 0x0 地址。这里也是,在 Rocket Chip 上访问 0x0 的地址,我强行改成了 0x10000000,然后 Offset 是 0,所以最后到内存就是 0x10000000 的地址了。

所以 AXI 总线上 Slave Register 的地址 = Master 地址 - 匹配到的 Slave 的 Offset 地址。但是,如果只有单个 Slave 的时候,AXI Interconnect 可能不检查地址范围,而是直接截断,但在有多个 Slave 的时候,会先检查地址范围,如果找不到就返回错误。这个问题让我困惑了许久,直到我挂上了 System ILA 看。。

AXI Uartlite

既然有了外设总线,第一个想到的外设就是 UART 咯,于是加了一个 AXI Uartlite,用 AXI Interconnect 连到外设总线上。写了程序简单测试了一下,确实读取到了数据,然后也很快就可以成功地从串口读数据,写数据。于是我又加了一个串口,拉到 AXI Interconnect 第二个接口上,结果就不工作了。

细心的读者可能已经从我上面讲到的一些内容上猜到了问题所在。。但这一点直到后面我才明白发生了什么,后面会再讲到这里的问题。

AXI Interrupt Controller

接着,回到我们最初的目标,既然可以输入输出了,还要真的串口干啥?添加中断或者拉一路 JTAG 出来,脱离 PS 来进行调试。所以接下来需要从 Rocket Chip 中暴露外部中断。在原来的代码中,是直接关掉了的:

target.tieOffInterrupts()

它的功能很简单,直接把中断置 0。但我要的是拉到外面,经过了一番挣扎:

val io = IO(new Bundle {
    val ps_axi_slave = Flipped(adapter.axi.cloneType)
    val mem_axi = target.mem_axi4.head.cloneType
    val mmio_axi = target.mmio_axi4.head.cloneType
    val interrupts = Input(UInt(2.W))
})

io.mem_axi <> target.mem_axi4.head
io.mmio_axi <> target.mmio_axi4.head
target.interrupts := io.interrupts

终于找到了正确的方法,拉出来两路中断信号。于是很开心地直接接到了 Uartlite 上,但是事情肯定不会这么简单:

Uartlite 是正边沿触发的中断,但是 Rocket Chip 期望的是高电平触发的中断。所以我们需要一个中断控制器来实现这个,加了一个 AXI Interrupt Controller。但是问题来了,我们还没解决那个只要加俩 Slave 就都访问不到的问题呢!于是到处挂 System ILA 调试,发现了问题所在:

我们之前提到,0x60000000 - 0x80000000 地址段是对应到了外部 AXI 总线的,这没错,但是,如果在 Rocket Chip 上读 0x60000000 的地址,到总线上还是 0x60000000 的地址而不是 0x0,而之前只有一个 Slave 的时候,没有真的去检查这个地址范围,所以找到了正确的寄存器,现在不行了,于是出错了。

解决方法也很简单,乖乖地把 Offset 调成 0x60000000 和 0x61200000,然后再映射到 rCore 的内核态里。一番挣扎,又参照了 Linux 的 Uartlite 驱动(发现了遗忘设置的 MER 寄存器,不认真阅读文档的后果),终于在 ILA 上看到 uartlite 的输出上有一个脉冲,紧接着看到中断控制器输出了一个 irq,然后 Rocket Chip 收到了 external interrupt。Cheers!

中断处理

事情虽然差不多解决了,但是还没有结束。收到了一个 interrupt 以后,打印了一下寄存器状态,马上又来下一个 interrupt 了。现在不仅要在 Rocket Chip 的 PLIC 上把中断给确认了(吐槽一下,Rocket 的 PLIC 文档也比较欠缺,我是照着 U54 核的地址找到,但是 U54 呢是一个 4+1 的组合,hart0 没有 M 态,所以我为了打开 hart0 的 S 态中断,要找 hart1 的 M 态地址,详见 SiFive 文档),在 AXI Interrupt Controller 上也得把中断给确认了。但还是不行,每次 interrupt controller 输出的 irq 拉低不久,uartlite 又出现了新的一次中断。

这次的问题很有戏剧性:处理中断的时候输出调试信息,调试信息把 tx fifo 写满了,uartlite 也会产生一个中断。。于是没完了。解决方法很简单,牺牲一些性能,每次输出的时候都等到 tx fifo 空了才写,然后在处理串口中断的时候不要输出调试信息。

这下没有新的问题了,串口中断终于是工作了。

总结和致谢

这么一番搞下来,对 Vivado 和 AXI 的相关知识都比较熟悉了吧,也踩了很多的坑。需要特别感谢 @cq_z4yx 提供的技术支持。

相关文档和链接:

  1. PG099 AXI Interrupt Controller
  2. PG142 AXI Uartlite
  3. OpenSBI 适配
  4. rCore 适配

每周分享第 22 期

  1. AXI-Stream Components https://github.com/alexforencich/verilog-axis
  2. go self update 库 https://github.com/rhysd/go-github-selfupdate
  3. Atomic Pi https://www.cnx-software.com/2019/04/26/buy-atomic-pi-cherry-trail-sbc/
  4. 静态网站生成器 Hugo https://github.com/gohugoio/hugo
  5. C++ enum 的反射 https://github.com/Neargye/magic_enum
  6. 又一个开源的反编译器 https://github.com/BoomerangDecompiler/boomerang

把博客生成器从 Jekyll 迁移到 Hugo

Jekyll 生成一次实在太慢,忍受不了,于是换成了 Hugo。为了保持链接不变,花了不少的时间在重命名上,不过目前似乎都完成了,希望没有导致 404 的问题。

新的主题还是好看很多的。Enjoy!

在 Linux 中用 C 代码获取 DNS 服务器列表

最近在做一个作业的时候,发现里面有个步骤是获取 Linux 系统中的 DNS 服务器列表,它的方法很粗暴,直接 cat grep cut 再处理。我就在想有没有完全代码的实现,然后搜了一下,果然有:

#include <resolv.h>
// ...
res_init();
// _res.nsaddr_list is an array of resolvers

用到了全局变量 _res ,虽然很 hacky,但是至少是工作的,不清楚兼容性几何。

每周分享第 21 期

来了来了

  1. Rust stackful generator 库 https://github.com/Xudong-Huang/generator-rs
  2. wireshark tui https://termshark.io/
  3. Pythonm 加 annotation 的调试 https://github.com/cool-RR/PySnooper
  4. Haskell 又一个教程 https://github.com/alpacaaa/zero-bullshit-haskell
  5. 直接在 Rust 中写 Python https://docs.rs/inline-python/0.2.0/inline_python/
  6. 直接把 regex 捕捉到的 group 丢到 struct 里 https://crates.io/crates/recap
  7. XDP 层的抓包 https://github.com/cloudflare/xdpcap
  8. AWS 开始提供香港的云服务 https://www.allthingsdistributed.com/2019/04/aws-region-asia-pacific-hong-kong.html

在 FPGA 上实现路由器

最近在做 FPGA 上硬件的路由器,感觉接近一个基本可用的阶段了吧,大概谈一谈做这个的思路、过程和踩过的坑。

首先,做实验用的板子是 Alinx AX7021,FPGA 是 Xilinx xc7z020clg484-2,扩展板上有 4PL+1PS 个网口和千兆 KSZ9031RNX PHY,采用的接口是 RGMII。一开始做的自然是做 RGMII,但是遇到了困难,RGMII 在千兆模式下传输的是 DDR 信号,而时序和延迟就是个比较麻烦的事情。一开始先直接拿 Xilinx 的 AXI Ethernet IP 来用,然后上 ILA 看到了 IDDR 后的信号,第一次看到了完整的以太网帧,从 Preamble 和 SFD 到最后的 FCS。于是就特别振奋,想着手写 RGMII,先做收,再做发。确实,收很容易,很快就做出来了,但是写总是出问题,当时也不懂跨时钟域的一些问题,总之各种没调出来。于是就退而求其次,选择了 Xilinx 的 Tri Mode Ethernet IP 了。

Tri Mode Ethernet IP 有很多选项,为了简单,直接采用了 AXI-Stream 的接口,不要 AXI4-Lite 什么的,都不要,因为我需要直接写剩余的逻辑。其他东西能省也都省掉了。这个 IP 确实很给力,很快就可以完成收和发的操作了,这次终于知道了怎么处理跨时钟域的问题 — XPM FIFO ASYNC,一下推进了很大的进度。

既然可以收,也可以发了,就扩展到多个网口。这个 IP 中可以选择 Shared Logic 在内部,也可以在外部,研究了一下发现,应该是一个放内部,其余选外部,然后接起来就可以了。不过目前为了简单,还是只用了俩端口。在这个基础上,就开始解析收进来的以太网帧了。

第一步自然是填 ARP 表,自然问题来了,如果多个网口同时进来数据,怎么保证 ARP 表读写的正确性?自然就想到总线上需要做仲裁,于是写了一个简单的总线仲裁,顺带学习到了 unique case(z)priority case(z) 的语法。然后 ARP 表怎么实现呢,大概就是一个哈希表,然后表里维护了(IP,MAC,PORT)三元组,然后实现了一些冲突和覆盖的处理逻辑,做这些的同时也对各个模块编写相应的测试。有了 ARP 表,就可以在解析以太网帧的时候,拆解出里面的信息,然后请求 ARP 表总线,然后写入。

第二步则是相应 ARP 请求,这就需要发出以太网帧。由于 4 个端口都可能向 4 个端口发出以太网帧,这就需要一个 4x4 matrix + 仲裁。不过目前为了简单,就还没有上 FIFO,直接仲裁进到目的端口的 TX FIFO 中了。这一步并不难,不过在最后 AXI-Stream 的一步遇到了一些困难。由于 Tri Mode Ethernet IP 对 tready 和 tvalid 有特定的要求,所以这里只能用 FWFT FIFO 进行,然后进行了一波神奇的操作,最后搞定了这个事情。成果就是可以从电脑上 arping 通指定的地址了。

第三步,也是正在做的一步,就是真正实现 IP 包的转发,这需要三个步骤:解析目的地址,查询路由表,查询 ARP 表。于是需要照着 ARP 表的方案同样做了路由表的仲裁,目前为了简单也还是把路由表设置为静态的。这里就需要做一些特殊的考虑,例如上面三步是串行的,但是我需要同时把 IP 包存一份,最后转发的时候修改一点就发出去了,所以需要等两步都做完,才能继续下一个包的处理。目前做到了第二小步,正在向最后一步查询 ARP 表进发。

UPDATE:现在最后一步也做好了,但是遇到了小问题,还是不能偷懒,需要写一个 XPM_MEMORY_SPRAM,直接写一个大的数组太浪费 LUT 了。

UPDATE-2019-04-27:It WORKS now. 不过也发现了之前写的 ARP 表有点问题。

jiegec.xyz 域名即将停用

最早买域名的时候,买的是 jiegec.xyz,后来发现了更好的域名,于是这个老域名一直就是一个简单的网页转址。很快,它就要过期了,我也不打算续费了,大家继续用我的新域名吧。

P.S. 忽然发现我之前配的 acme.sh 没有配自动 nginx -s reload ,所以之前 ssl 证书时间凉了。

每周分享第 20 期

写到了第 20 期了!最近几期总是在咕咕。

  1. Rust 的科学计算库 https://github.com/rust-ndarray/ndarray https://github.com/jturner314/ndarray-stats
  2. Rust Crate 生态可视化 https://rfdonnelly.github.io/crate-galaxy/
  3. 在地址栏里做动画 http://matthewrayfield.com/articles/animating-urls-with-javascript-and-emojis/#%F0%9F%8C%92
  4. Ruby 3 也要加类型了 https://twitter.com/darkdimius/status/1119115657776209920
  5. verilog 的 MAC + 简易网络栈 https://github.com/alexforencich/verilog-ethernet

每周分享第 19 期

忽然想起来忘了这件事情两天。。

  1. Pock 在 Touchbar 中显示并控制 Dock https://pock.pigigaldi.com/
  2. MacBook touchbar+keyboard+touchpad Linux 驱动 https://github.com/roadrunner2/macbook12-spi-driver
  3. Kendryte K210 上的 GBA 模拟器 https://github.com/44670/mgba-k210
  4. Rust 生成 PDF 文件的库 https://github.com/J-F-Liu/lopdf
  5. 中科大用 Rust 编写 FreeRTOS 的实验 https://github.com/OSH-2019/x-rust-freertos
  6. Google 发布 Cloud Code 插件,直接在 k8s 上调试运行 https://cloud.google.com/blog/products/devops-sre/announcing-cloud-code-accelerating-cloud-native-application-development
  7. 通过 libusb 跨平台的 usbip server https://github.com/jwise/pyusbip
  8. Linux 下 BCM43602 Firmware 问题的不完美解决方法 https://bugzilla.kernel.org/show_bug.cgi?id=193121
  9. 给树莓派上电的新方法 https://youtu.be/X2vF9KAEJx8
  10. Docker 里跑 Deepin Wechat 的镜像 https://github.com/bestwu/docker-wechat.git
  11. NS Switch 的包管理器 https://switchbrew.org/wiki/Main_Page

rCore 软路由实现

最近在研究软路由在 rCore 上的实现,但限于硬件限制,目前先在虚拟机里测试。软路由大概要做这些东西:

1. 抓包,解析包里的内容
2. 查路由表,找到下一跳在哪
3. 查 ARP,知道下一跳的 MAC 地址
4. 减少 TTL,更新 IP Checksum
5. 把包发出去

第一步直接拿 smoltcp 的 Raw Socket 即可,但是目前只能抓指定 IP Protocol 的包,我用的是 ICMP,但其他的就还抓不了,需要继续改 Smoltcp 源代码。

第二步用的是之前刚修好的 treebitmap 库,它提供了路由表的查询功能,目前路由表还是写死的,之后会用已经部分实现好的 Netlink 接口读取出来。

第三步则是 ioctl 发请求,然后从 smoltcp 内部的 ARP cache 里读取。

第四步很简单,不用多说。

第五步则需要指定出端口,用了一个 index,放在一个特定的 sockaddr 中。

最后的效果就是,能双向转发 ping 通。

网络拓扑:

可以,这很玄学。

后续在想在真机上实验,但是还缺一个网卡驱动,不然就可以用神奇的办法来做这个实验了。

每周分享第 18 期

不咕不咕

  1. Alfred pwgen workflow https://github.com/deanishe/alfred-pwgen
  2. 魔改主板,在老主板上放 16GB 内存 https://readhacker.news/s/3Zty4
  3. Mesalink TLS 库的 Rust 实现 https://github.com/mesalock-linux/mesalink
  4. 实测可用 Rust 写的 STM32 应用 https://github.com/lupyuen/stm32-blue-pill-rust
  5. 开源 PCIe 核 https://github.com/enjoy-digital/litepcie

欢迎投稿。

高云 FPGA 踩坑

最近拿到了高云 FPGA GW2A-18 开发版,想在这上面做一些小工程。不过首先要配置好环境什么的。官方提供了 Linux 和 Windows 的两套工具,自然是拥抱 Linux 咯,但是由于官方适配的是 Redhat 系的操作系统,所以用 Debian 系的时候出现了若干问题,后面会谈到怎么解决的。

首先是官网下载了它的软件,大概有 IDE,综合器,布线器和 Programmer 四个工具,然后开始跑,发现缺少了 libcrypt.so.1.0.0。上网搜了一下解决方案,需要重新编译 openssl-1.0.0,于是下载并且编译了 openssl-1.0.0t 并且把 .so 的路径调好了,这时候就可以打开 IDE 了。然后发现需要 License,这个很简单,去官网申请一下,一天邮件就下来了。

接下来配置 License,IDE 很容易,直接选择邮件里发下来的 node-locked License 即可。不过 Synplify Pro 的 Linux 版本不支持直接单文件 node-locked 的 License,只允许跑 SCL … 不过高云也提供了 SCL 的下载,和 IDE 的 License Server 放在一起,安装完以后,在得到的 License 里加上两行:

SERVER ${hostname} ${hostid} ${port}
VENDER snpslmd /path/to/scl/2018.06/linux64/bin/snpslmd

然后把 $LM_LICENSE_FILE 指向这个文件路径,就可以了。这一部分感谢 @Jackey-Huo。

随手写了一个简化版的点亮数字人生(没有数码管),得到了 bistream,准备往板子里刷,然后问题出现了:

ImportError: /path/to/Gowin_YunYuan_V1.9.0Beta_linux/Programmer/bin/librt.so.1: symbol __vdso_clock_gettime version GLIBC_PRIVATE not defined in file libc.so.6 with link time reference

目测是 glibc 版本问题 … 这就很难处理了。另外又从官网下载了独立的 Programmer,仍然不行,检测不到设备。

最后想了想,找到了终极办法,在 Docker 里运行 CentOS 的 Privileged Container,再跑 programmer:

$ docker pull centos
$ docker run -it --privileged -v /home:/home centos /bin/bash

CentOS 镜像出乎意料地小。进去以后,找到 Programmer 路径,然后 scan:

# ./programmer_cli --scan
 Scanning!
Current download-cable channel:0
Device Info:
        Family: GW2A
        Name: GW2A-18
        ID: 0xREDACTED
 1 device(s) found!
 Cost 0.54 second(s)

接着烧到 SRAM 中:

# ./programmer_cli -d GW2A-18 --fsFile /path/to/bitstream.fs --run 2
 "SRAM Program" starting on device-1...
Programming...: [#########################] 100%
 User Code: 0xREDACTED
 Status Code: 0xREDACTED
 Cost 4.54 second(s)

烧录成功,功能测试也没有问题。可以继续进行下一步工作了。

每周分享第 17 期

对不起咕咕了两天。。

  1. 关于 rel="noopener" 的一些细节 https://mathiasbynens.github.io/rel-noopener
  2. Microsft Defender 推出 macOS 版 https://arstechnica.com/gadgets/2019/03/microsoft-ships-anti-virus-for-macos-as-windows-defender-becomes-microsoft-defender/
  3. 基于 Docker 的快速启动在线 Linux box https://github.com/instantbox/instantbox
  4. rust 在窗口中显示 fb 的库 https://github.com/emoon/rust_minifb
  5. Vue 列表空间,为大量数据设计 https://github.com/tangbc/vue-virtual-scroll-list
  6. musl 各平台的交叉编译工具链 https://musl.cc/
  7. 在线的 markdown 转 pdf https://md2pdf.netlify.com/
  8. 一本关于 C64 的书 http://10print.org/
  9. 转自 dram "haskutil tql,自动更新 import,自动 {-# LANGUAGE #-} ,自动填 hole" https://github.com/EduardSergeev/vscode-haskutil
  10. ZFS on Linux 加入 TRIM 支持 http://www.phoronix.com/scan.php?page=news_item&px=ZFS-On-Linux-TRIM-Lands
  11. 又一个体现 JS 玄学之处的网站 https://getify.github.io/coercions-grid/

静态编译 sqlite3

最近 rCore 支持了动态链接库,于是想着在测试 sqlite 的时候直接用动态的,不过出现了玄学的问题,它会访问一个不存在的地址,看代码也没看出个所以然来。所以研究了一下 sqlite 的静态编译。首先在 configure 的时候尝试了一下:

$ ./configure CC=x86_64-linux-musl-gcc --disable-shared --enabled-static

发现 libsqlite 确实是静态了,但是 sqlite3 并不是。一番研究以后,发现是 libtool 的原因,只要这样编译:

$ make LTLINK_EXTRAS=-all-static

就可以编译出静态的 sqlite3

sqlite3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped

每周分享第 16 期

继续沉迷写 OS +1

  1. C++ 的一个比较 fancy 的 format 库 https://github.com/fmtlib/fmt
  2. Rust 的 rsync 替代品 https://github.com/wchang22/lumins
  3. VirtIO 1.1 要发布了 https://github.com/oasis-tcs/virtio-docs/commit/3b4130f3a8910dad89b8166e06e58806b9c60943
  4. 向二维码嵌入图片的工具 http://cgv.cs.nthu.edu.tw/Projects/Recreational_Graphics/Halftone_QRCodes/
  5. Google Chrome 发出奇怪的 DNS 请求的原因 https://unix.stackexchange.com/questions/363512/chrome-dns-requests-with-random-dns-names-malware
  6. 转换 .HEIC 到 .jpg 的批量方案:alias heic="magick mogrify -monitor -format jpg *.HEIC" ref: Apple SE
  7. 以打代码来练打字的网站 https://typing.io/
  8. VSCode 网页 server https://github.com/codercom/code-server
  9. 奇特的 Rust 网页栈实现 https://japaric.github.io/jnet/jnet/index.html
  10. H265 解析 gui https://github.com/virinext/hevcesbrowser 之前介绍过 H264 的
  11. Awesome Rust Embedded https://github.com/rust-embedded/awesome-embedded-rust
  12. 新的 SiFive 产品 https://www.crowdsupply.com/sifive/hifive1-rev-b
  13. Wireshark 3.0.0 发布 https://www.wireshark.org/docs/relnotes/wireshark-3.0.0.html

交叉编译 Nginx 1.14.2 到 RISC-V

最近又把一定的精力放到了 RISC-V 64 上的 rCore 用户态程序的支持上,同时也借到了 HiFive Unleashed 板子,所以有真实硬件可以拿来跑了。在这之前先在 QEMU 上把能跑的都跑起来。

由于 rCore 对 glibc 的支持一直有问题,RISC-V 也不例外,所以还是选择用 musl 来做这件事情。一般搜索,终于找到了 Linux 下能用的 musl-riscv-toolchain 。编译好工具链以后,很多需要 libc 的用户态都能跑了,于是想着试一下 nginx 的编译。试着编译了一下,遇到了各种问题,最后搜到了交叉编译 Hi3536 上面使用的 nginx,里面的方法解决了这个问题。最后总结出了这样的 patch :

diff --git a/nginx-1.14.2/auto/cc/name b/nginx-1.14.2/auto/cc/name
index ded93f5..d6ab27a 100644
--- a/nginx-1.14.2/auto/cc/name
+++ b/nginx-1.14.2/auto/cc/name
@@ -7,7 +7,7 @@ if [ "$NGX_PLATFORM" != win32 ]; then

     ngx_feature="C compiler"
     ngx_feature_name=
-    ngx_feature_run=yes
+    ngx_feature_run=no
     ngx_feature_incs=
     ngx_feature_path=
     ngx_feature_libs=
diff --git a/nginx-1.14.2/auto/lib/openssl/make b/nginx-1.14.2/auto/lib/openssl/make
index 126a238..7a0e768 100644
--- a/nginx-1.14.2/auto/lib/openssl/make
+++ b/nginx-1.14.2/auto/lib/openssl/make
@@ -51,7 +51,7 @@ END
 $OPENSSL/.openssl/include/openssl/ssl.h:   $NGX_MAKEFILE
    cd $OPENSSL \\
    && if [ -f Makefile ]; then \$(MAKE) clean; fi \\
-   && ./config --prefix=$ngx_prefix no-shared no-threads $OPENSSL_OPT \\
+   && ./config --prefix=$ngx_prefix no-shared no-threads --cross-compile-prefix=riscv64-linux-musl- $OPENSSL_OPT \\
    && \$(MAKE) \\
    && \$(MAKE) install_sw LIBDIR=lib

diff --git a/nginx-1.14.2/auto/types/sizeof b/nginx-1.14.2/auto/types/sizeof
index 480d8cf..52c7287 100644
--- a/nginx-1.14.2/auto/types/sizeof
+++ b/nginx-1.14.2/auto/types/sizeof
@@ -33,7 +33,7 @@ int main(void) {
 END


-ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS \
+ngx_test="gcc $CC_TEST_FLAGS $CC_AUX_FLAGS \
           -o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_LD_OPT $ngx_feature_libs"

 eval "$ngx_test >> $NGX_AUTOCONF_ERR 2>&1"

接着,在 ./configure --with-cc=riscv64-linux-musl-gcc --with-cc-opt=-static --with-ld-opt=-static --without-pcre --without-http_rewrite_module --without-http_gzip_module --with-poll_module --without-http_upstream_zone_module 之后,修改 objs/ngx_auto_config.h,加入:

#ifndef NGX_SYS_NERR
#define NGX_SYS_NERR  132
#endif

#ifndef NGX_HAVE_SYSVSHM
#define NGX_HAVE_SYSVSHM 1
#endif

接着就可以正常编译了:

$ file objs/nginx
objs/nginx: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, with debug_info, not stripped

在古老的 OS 上运行一个干净的新的环境

由于某些课程的原因,需要在一个 CentOS 7 上跑一些编译和运行代码。看到这么古老的软件,我心想不行,肯定要找新一些的软件来用。首先想到的是 tmux,于是按照网上的脚本 很快装了一个 tmux 2.8 版本,果然好了很多。但是常用的很多软件依然是个问题。试了一下最近比较新的 code-server,因为 ABI 问题跑不起来。

于是开始想玩骚操作。首先想到的是 Gentoo Prefix,不过既然是别人的机器,还是算了。又找了 fakeroot 配合 alpine rootfs 的方案,但编译不过,估计是内核版本问题。又试了一下 fakechroot,但它需要配合 fakeroot 使用,这就凉了。

然后又找了一些替代方案。一是 uchroot,但由于 CMake 版本太老也编译不过。最后发现了 PRoot ,直接提供 prebuilt 然后很容易就可以跑起来:

$ ./proot -b /proc -b /dev -r $CHROOT /bin/busybox sh

于是就进到了 alpine 的 rootfs 中,下载地址。进去以后发现没有编辑器,于是出来把 apk 的源改了,加了 resolv.conf,就成功地安装了很多很新的软件了。

每周分享第 15 期

继续沉迷写 OS

  1. 嵌入 graphics for rust 可以绘制 bmp 了 https://wapl.es/rust/2019/03/04/embedded-graphics-0.4.7-bmp-support.html
  2. rustup component history 方便 nightly 日期选择 https://rust-lang.github.io/rustup-components-history/index.html
  3. grpcurl 用于 grpc 调试 https://github.com/fullstorydev/grpcurl
  4. grafana 6.0 is out http://docs.grafana.org/guides/whats-new-in-v6-0/
  5. 康哥推荐的写论文工具:https://github.com/stsewd/ieee-pandoc-template

每周分享第 14 期

最近沉迷写 OS,没怎么搜罗新的东西(

  1. 用 Docker 做交叉编译 https://github.com/dockcross/dockcross#dockcross
  2. tar inplace extraction 有意思的思路 https://gitlab.com/antonok/taro
  3. los 16.0 发布 基于 Android Pie https://lineageos.org/Changelog-22/
  4. Rust 1.33.0 Pin 进入 stable https://blog.rust-lang.org/2019/02/28/Rust-1.33.0.html
  5. Rust 用于编写 cli 软件的工具库 https://rust-lang-nursery.github.io/cli-wg/index.html
  6. 自动使用 CI 发布二进制 prebuilt 的模板 https://github.com/japaric/trust
  7. 转换 gif 到 xlsx 很神奇 https://github.com/pugwonk/gif2xlsx/blob/master/README.md
  8. Go REPL https://github.com/cosmos72/gomacro
  9. 基于区块链的论坛海星 其实是区块链数据库 https://github.com/CovenantSQL/CovenantForum
  10. Rime emoji 嵌入输入法 https://github.com/rime/rime-emoji
  11. 一个神奇的数据库 https://github.com/mit-pdos/noria
  12. 一个讲内核的 gitbook https://richardweiyang.gitbooks.io/kernel-exploring/
  13. 用 React 写桌面控件 http://tracesof.net/uebersicht/

在 rCore 上运行 nginx

阿 西 吧 nginx 终于能在 rCore 上跑了 orrrrrrrz

通过这半个多月来的大量开发,我和王润基 @wangrunji0408 学长算是终于完成了第一个 milestone:跑起来一个 nginx。遇到了很多困难,大概有这些:

  1. syscall 实现不全。各种方面都缺,然后 nginx 在编译的时候又检测到比较新的 OS 版本,所以很多 syscall 都用了新的来替代老的,例如 readv/writev pread/pwrite accept4 等等,所以这方面做了一些工作。另外,还有很多新的 syscall 进来,太多了我就不细说了,基本上一个 commit 做一点一个 commit 做一点这个样子。
  2. nginx 用到了 SSE 的寄存器 xmm,但是之前是没有开的。所以把 sse 打开,然后切换上下文的时候把 sse 通过 fxsave 保存和 fxrstor 恢复(有意思的是,as 居然不认这俩,只好手动写字节码),然后为了 16bit 的对齐又写了几行汇编代码。这块问题不大,今天一会就搞定了。但是如果要性能更高一些的话,可能需要在第一次使用 xmm 的时候再开始保存,大概就是加一个 bit 的事情。
  3. 文件系统有点崩。实现还是有很多 BUG,表现就是需要经常重新 mksfs 一下,再重启加载完好的 fs,有时候强制关机一下就又崩了。
  4. 内存管理做了一些改变。为了实现更加完整的 mmap mumap 和 mprotect,又发现了一些新的 BUG 在里面,然后慢慢修复了。就是实现的有点粗暴。
  5. 死锁问题。这个其实现在还会出现,只是还没调出来,也不会百分百出现。我们计划在锁上面做一些死锁检测,例如记住是谁上锁的,等等。现在就遇到一个很玄学的死锁问题。

然后代码也是一边在写一边在重构吧,很多地方现在都写得很粗暴,FIXME 和 TODO 留了很多,很多地方也写得不够优雅。以后再慢慢重构 + 优化吧。

截图留念:

再往前的话,还有很多小的问题,例如网卡的中断启用了但没有改 mask,所以啥也没收到,靠 QEMU Tracing 找到问题。还有一个很有意思的现象,就是如果 elf 的 program header 没有 phdr 这个项的时候,我们发现,可以通过第一个 load(如果加载了完整的 elf 头的话),我们可以从这里推断出 phdr 的地址(load 的虚拟地址加偏移),然后丢到 auxv 里去让 musl 配置 tls。总之这些都解决了。也不用去考虑兼容 litc 了,已经全部向 linux 靠拢了,稳。

注:最简 nginx 编译参数:

./configure --with-cc=/usr/bin/musl-gcc --with-cc-opt=-static --with-ld-opt=-satic --without-pcre --without-http_rewrite_module --without-http_gzip_module --with-poll_module

这样编译出来是一个静态文件,并且在 strip 之后只有不到 1M 的大小。

最简 nginx 配置:

daemon off;
master_process off;

events {
    use poll;
}

http {
    server {
        listen 80;
        server_name _;

        root /;
    }
}

这样就免去了一些麻烦(多线程、多进程交互还是有很多问题),但确实可以跑起来了。

另外,还需要写一份 /etc/passwd 和 /etc/group 用于 nobody 和 nogroup。不需要其他额外的东西了。