惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
雷峰网
雷峰网
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Spread Privacy
Spread Privacy
H
Hacker News: Front Page
PCI Perspectives
PCI Perspectives
Webroot Blog
Webroot Blog
罗磊的独立博客
H
Heimdal Security Blog
TaoSecurity Blog
TaoSecurity Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Last Week in AI
Last Week in AI
美团技术团队
Help Net Security
Help Net Security
The Hacker News
The Hacker News
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
The Register - Security
The Register - Security
IT之家
IT之家
WordPress大学
WordPress大学
Jina AI
Jina AI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Help Net Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
NISL@THU
NISL@THU
爱范儿
爱范儿
The GitHub Blog
The GitHub Blog
Scott Helme
Scott Helme
V
Vulnerabilities – Threatpost
B
Blog
T
Tenable Blog
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
S
Security Affairs
小众软件
小众软件
Hacker News: Ask HN
Hacker News: Ask HN
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
A
Arctic Wolf
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
M
MIT News - Artificial intelligence

网络热度

LeetCode 75 学习计划清单和参考答案 AI辅助生成的统计博客文章浏览量的网络爬虫 程序员面试题精解(5)— 素数判定 程序员面试题精解(4)— 双指针 哈佛 CS50AI 课程介绍:从零开始学习人工智能 解决不蒜子文章阅读计数错误的问题 RSA的攻与防(三) RSA的攻与防(二) RSA的攻与防(二) 启用 TLS 1.3 提升网络应用的安全性和性能 程序员面试题精解(3)— 二分查找 嵌入式Linux系统中uClibc标准库使用经验笔记 巧解picoCTF的RSA挑战题Sum-O-Primes 请马上停止使用TLS 1.0和TLS 1.1! 自己动手使用树莓派搭建家用NAS和流媒体服务器 iTerm2 + Oh-My-Zsh + Powerlevel10k 打造酷炫macOS终端 AddressSanitizer — 程序员检测内存访问错误的利器 程序员面试题精解(2)— 平方根运算 思科 Catalyst Wi-Fi 6 MU-MIMO 带你畅享速度与激情 程序员面试题精解(1)— 比特位计数
C编程支持16位存取接口的存储设备
子曦 · 2021-04-11 · via 网络热度

在计算机网络设备和嵌入式系统的研发中,常常需要硬件和软件设计人员紧密配合,以实现精准而有效的平台支持。特别地,对于需要数据存取的设备,了解设备的基本工作原理、控制和数据线路的连接方式及信号流程,对于可靠的软件设计和实现必不可少。否则,如果硬件电路设计或软件程序编写基于错误的假定,将需要更多的时间调试、排错和补救,可能严重影响项目的进度。

Low-level programming is good for the programmer's soul.
John Carmack(约翰·卡马克,美国电玩游戏程序员、id Software的创始人之一)

存取模式

这里以东芝TC55VBM416静态随机存取存储器(Static Random Access Memory,简称SRAM)为例,说明系统开发中硬软件协同工作的重要性。由于SRAM有快速和极低功耗的特点,它非常适合用于低储存密度的数据保存应用。比如使用电池供电,SRAM可以用来储存系统配置文件等,达到等同于非易失性随机存取存储器(Non-Volatile RAM,简称NVRAM)的功效。TC55VBM416 SRAM的存储容量为16,777,216 (\(16\times2^{20}\)) 比特位,提供1,048,576 (1M) 个16位字存取或2,097,152 (2M) 个8位字节存取单元。

那么,我们如何在系统硬件设计中选择哪一种存取模式呢?答案就在TC55VBM416的数据表(datasheet)里。参考数据表中对48个针脚的功能描述:

结合数据表中操作模式的说明,TC55VBM416的存取模式是这样工作的:当\(\overline{\mathsf{BYTE}}\)针脚的输入为高电平时,芯片执行16位字存取模式,针脚A0~A19的输入可用来寻址1M地址空间,16比特数据通过针脚I/O1~I/O16读写;当\(\overline{\mathsf{BYTE}}\)针脚的输入为低电平时,激活8位字节存取模式,这时针脚A-1和A0~A19组合一同寻址2M的地址空间,单字节数据通过针脚I/O1~I/O8读写。注意I/O16与A-1实际是一个针脚复用,因为这两种工作模式互斥,所以不会冲突。

硬件设计

了解了TC55VBM416的工作原理,再来看看嵌入式系统硬件设计是如何使用它的。网上搜索到工业及车载应用中使用的富士通MB91F467C微控制器,其硬件系统设计就包括了东芝TC55VBM416 SRAM芯片,相关的电路图截取如下:

从此电路图可以看出:

  • 系统地址线A1~A20接到SRAM针脚A0~A19
  • 系统数据线D31~D17接到SRAM针脚I/O1~I/O15
  • 跳线器JP94选择系统地址线A0或数据线D16连到I/O16/A-1针脚
  • 跳线器JP60选择高电平(VDD35)或低电平(GND)连到\(\overline{\mathsf{BYTE}}\)针脚

很明显,这一设计由跳线器决定SRAM的存取模式。当JP60设定上端连通高电平到\(\overline{\mathsf{BYTE}}\)针脚时,相应的JP94应该设定下端连通D16到I/O16针脚,芯片执行16位字存取。由于系统地址线A0不连接到SRAM,软件系统将使用偶寻址存取双字节16比特数据。反之,当JP60设定下端连通低电平到\(\overline{\mathsf{BYTE}}\)针脚时,相应的JP94应该设定上端连通系统地址线A0到A-1针脚,芯片执行8位字节存取。这时系统地址线A0~A19全部用来寻址,程序可以直接存取单字节8比特数据。

软件支持

对于平台软件的开发人员,如果不事先审核系统硬件功能规范、不与硬件设计工程师沟通好存储设备的存取模式,就会出现意想不到的错误,耽误工程的进展。设想对于上面的硬件设计,系统默认SRAM的存取模式为16位,而软件程序员并不知情,继续沿用单字节8寻址的代码读写SRAM中的配置信息:

1
2
3
4
5
6
7
8
9
inline char read_conf_byte (char *confddr) 
{
return *confaddr
}

inline void write_conf_byte (char *confddr, char c)
{
*confaddr = c
}

那么当confaddr为奇数时,实际读写的地址为confaddr-1,因为系统地址线A0不参与寻址。这样的程序测试时当然会错误百出,表现为有些数据对、有些数据错,而写对的数据有时也可能读错等等。解决的办法是同时更改两个跳线器,将存取模式设置为8位。

在类似这种情况下,如果硬件设计不能动态调整,就需要修改软件支持不同的存取模式。对于16位存取接口,要更新单个字节,可以先读取对应偶地址的双字节,再根据原始的写请求地址及系统的字节顺序(Endianness)决定要替换高低哪一个字节,更新完毕后将16位数据一步写回对应偶地址。这一算法简单表述如下:

用给定地址计算出对应的偶地址,然后从该地址读取双字节

  • 如果给定地址为奇数,同时为大端序;或给定地址为偶数,同时为小端序
    • 将双字节数据低8位清零,再写入单个字节
    • 将新的双字节数据写入对应的偶地址
  • 如果给定地址为偶数,同时为大端序;或给定地址为偶数,同时为大端序
    • 将双字节数据高8位清零,再写入单个字节
    • 将新的双字节数据写入对应的偶地址

据此我们可以重写write_conf_byte()函数,使之同时支持8位和16位两种写入模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef enum {
CONF_8BIT_ACCESS = 1,
CONF_16BIT_ACCESS = 2
} conf_access_type;

int test_endian() {
int x = 1;
return *((char *)&x);
}

void write_conf_byte (char *confaddr, char c, conf_access_type type)
{
ushort *d_16;

if (type == CONF_16BIT_ACCESS) {
d_16 = (ushort *)((uintptr_t)confaddr & (~0x1));
if ((((uintptr_t)confaddr & 0x01) && (test_endian() == 0)) ||
((((uintptr_t)confaddr & 0x01) == 0) && test_endian())) {

*d_16 = (*d_16 & 0xFF00) | c;
} else {
*d_16 = (*d_16 & 0x00FF) | (c<<8);
}
} else {
*confaddr = c;
}
}

这里还给出另一个复制字节序列到基于SRAM的配置存储地址的函数confcpy()。它也支持8位和16位两种存取接口类型,但只工作于大端序的系统环境。作为练习,读者可自行修改代码让它也可以在小端序系统中运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42








void* confcpy (char *dst, char *src, int len, int type)
{
uchar *s = src;
uchar *d = dst;
ushort *d_16 = (ushort *)((uintptr_t)d & ~0x1);

if (!s || !d) {
return (NULL);
}

if (type == CONF_16BIT_ACCESS) {
if ((uintptr_t)d & 0x01) {
*d_16 = (*d_16 & 0xFF00) | *s;
s++;
d_16++;
len--;
}
while (len >= 2) {
*d_16 = (*s<<8) | *((uchar *)(s+1));
d_16++;
s += 2;
len -= 2;
}
if (len ==1) {
*d_16 = (*d_16 & 0x00FF) | ((*s)<<8);
}
} else {
while (len > 0) {
*d++ = *s++;
len--;
}
}
return (dst);
}

测试程序

最后,下面一段小程序可用来测试write_conf_byte()函数。它定义了两个4字节的缓冲区buffer8buffer16,分别用来仿真8位和16位存取的存储设备。程序用一个for循环调用write_conf_byte()函数逐个写入4个单字节的数据,调用时对应不同缓冲区使用不同的存取类别参数(CONF_8BIT_ACCESSCONF_16BIT_ACCESS)。写入完毕后使用断言(assert())函数比较二者数值是否一致,最后打印缓冲区内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main() {
char *buffer8, *buffer16;

printf("%s Endian system\n", test_endian() ? "Little" : "Big");
buffer8 = calloc(4, 1);
buffer16 = calloc(4, 1);

for (int i=0; i<4; i++) {
write_conf_byte(buffer8+i, (char)i, CONF_8BIT_ACCESS);
write_conf_byte(buffer16+i, (char)i, CONF_16BIT_ACCESS);
}

assert(*(uint *)buffer8 == *(uint *)buffer16);
printf("Buffer content: 0x%08x\n", *(uint *)buffer16);
return 0;
}

在Intel 64位虚拟机加载Ubuntu 20.04的环境里,编译链接后程序运行的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ uname -a
Linux zixi-VirtualBox 5.8.0-48-generic
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.3.0-17ubuntu1~20.04' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-HskZEa/gcc-9-9.3.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
$ gcc -o support-16bit-access support-16bit-access.c
$ ./support-16bit-access
Little Endian system
Buffer content: 0x03020100

这一结果正确。由于系统是小端序的,先写入的为低位字节,所以结果以32位整数表示为0x03020100。

在同一系统中怎么测试大端序的场景呢?这就需要安装交叉编译的工具链了。如果选用大端序的MIPS处理器,可以安装对应的Ubuntu软件包gcc-multilib-mips-linux-gnugcc-mips-linux-gnuqemu-user,支持以MIPS为目标架构的程序编译链接和仿真运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sudo apt-get install gcc-multilib-mips-linux-gnu gcc-mips-linux-gnu qemu-user
...
$ mips-linux-gnu-gcc -v
Using built-in specs.
COLLECT_GCC=mips-linux-gnu-gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc-cross/mips-linux-gnu/9/lto-wrapper
Target: mips-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.3.0-17ubuntu1~20.04' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libitm --disable-libsanitizer --disable-libquadmath --disable-libquadmath-support --enable-plugin --with-system-zlib --without-target-system-zlib --enable-libpth-m2 --enable-multiarch --disable-werror --enable-multilib --with-arch-32=mips32r2 --with-fp-32=xx --with-lxc1-sxc1=no --enable-targets=all --with-arch-64=mips64r2 --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=mips-linux-gnu --program-prefix=mips-linux-gnu- --includedir=/usr/mips-linux-gnu/include
Thread model: posix
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
$ mips-linux-gnu-gcc -o support-16bit-access-mips support-16bit-access.c -static
$ ./support-16bit-access-mips
Big Endian system
Buffer content: 0x00010203

上面的记录显示,使用安装好的mips-linux-gnu-gcc编译器,成功编译链接同一个C程序。运行结果打印出大端序的系统信息,以及正确的缓冲区数据内容。注意到编译链接的命令行使用了静态链接选项-static,这是必须的。因为此时宿主系统为Intel x86_64架构,如果使用动态链接,程序将无法找到相应的MIPS动态链接库文件,运行失败。

比较两个生成的可执行文件,可看到MIPS文件support-16bit-access-mips远远大于x86_64文件support-16bit-access。再使用file命令检查二者,印证了support-16bit-access-mips确实是MIPS GCC静态链接生成的:

1
2
3
4
5
6
7
8
$ ls -al support-16bit-access*
-rwxrwxr-x 1 zixi zixi 16984 Apr 11 19:31 support-16bit-access
-rw-rw-r-- 1 zixi zixi 1282 Apr 11 19:30 support-16bit-access.c
-rwxrwxr-x 1 zixi zixi 611592 Apr 11 19:31 support-16bit-access-mips
$ file support-16bit-access
support-16bit-access: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=883f851beccb99c970c0d0924ff23479080f12b1, for GNU/Linux 3.2.0, not stripped
$ file support-16bit-access-mips
support-16bit-access-mips: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, BuildID[sha1]=dceaf3d5f41f3208046b5a56b1187f03f8f63114, for GNU/Linux 3.2.0, not stripped

还可以调整系统配置,直接运行MIPS GCC动态链接生成的可执行文件,详细见此文:Transparently running binaries from any architecture in Linux with QEMU and binfmt_misc

完整的程序可点击这里下载:support-16bit-access.c.gz